From 544dc84c26da14ee65518ab5c4d2c6b1ceb354bd Mon Sep 17 00:00:00 2001 From: Tim McCarthy Date: Tue, 25 May 2021 23:20:05 -0700 Subject: [PATCH] i3wm configs --- bin/i3_empty_workspace.sh | 10 + bin/i3_reconfigure | 5 + bin/i3_send_to_workspace.sh | 14 + bin/i3_switch_workspace.sh | 17 ++ compton.conf | 146 ++++++++++ deploy.py | 2 + i3/binds.py | 545 ++++++++++++++++++++++++++++++++++++ i3/config | 246 ++++++++++++++++ i3/postscript | 14 + i3/preamble | 11 + 10 files changed, 1010 insertions(+) create mode 100755 bin/i3_empty_workspace.sh create mode 100755 bin/i3_reconfigure create mode 100755 bin/i3_send_to_workspace.sh create mode 100755 bin/i3_switch_workspace.sh create mode 100644 compton.conf create mode 100755 i3/binds.py create mode 100644 i3/config create mode 100644 i3/postscript create mode 100644 i3/preamble diff --git a/bin/i3_empty_workspace.sh b/bin/i3_empty_workspace.sh new file mode 100755 index 0000000..b4c28dc --- /dev/null +++ b/bin/i3_empty_workspace.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +MAX_DESKTOPS=20 + +WORKSPACES=$(seq -s '\n' 1 1 ${MAX_DESKTOPS}) + +EMPTY_WORKSPACE=$( (i3-msg -t get_workspaces | tr ',' '\n' | grep num | awk -F: '{print int($2)}' ; \ + echo -e ${WORKSPACES} ) | sort -n | uniq -u | head -n 1) + +i3-msg workspace ${EMPTY_WORKSPACE} diff --git a/bin/i3_reconfigure b/bin/i3_reconfigure new file mode 100755 index 0000000..a3a045a --- /dev/null +++ b/bin/i3_reconfigure @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +pushd ~/.dotfiles/i3/ &> /dev/null +python3 binds.py > config +i3-msg reload diff --git a/bin/i3_send_to_workspace.sh b/bin/i3_send_to_workspace.sh new file mode 100755 index 0000000..ac960dc --- /dev/null +++ b/bin/i3_send_to_workspace.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +function gen_workspaces() +{ + i3-msg -t get_workspaces | tr ',' '\n' | grep "name" | sed 's/"name":"\(.*\)"/\1/g' | sort -n +} + + +WORKSPACE=$( gen_workspaces | rofi -dmenu -i -p "Select workspace:") + +if [ -n "${WORKSPACE}" ] +then + i3-msg move container to workspace "${WORKSPACE}" +fi diff --git a/bin/i3_switch_workspace.sh b/bin/i3_switch_workspace.sh new file mode 100755 index 0000000..ab1e304 --- /dev/null +++ b/bin/i3_switch_workspace.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +function gen_workspaces() +{ + i3-msg -t get_workspaces | tr ',' '\n' | grep "name" | sed 's/"name":"\(.*\)"/\1/g' | sort -n +} + + +WORKSPACE=$( (echo empty; gen_workspaces) | rofi -dmenu -i -p "Select workspace:") + +if [ x"empty" = x"${WORKSPACE}" ] +then + i3_empty_workspace.sh +elif [ -n "${WORKSPACE}" ] +then + i3-msg workspace "${WORKSPACE}" +fi diff --git a/compton.conf b/compton.conf new file mode 100644 index 0000000..dc62a3f --- /dev/null +++ b/compton.conf @@ -0,0 +1,146 @@ +# This is my compton configuration after a quick cleanup. (It's still none too organized; sorry about that.) +# With this file at ~/.config/compton.conf, I can run compton without any arguments (just plain `compton`). +# +# In the hopes that explaining my software and hardware environment might be helpful to you: +# +# I use this configuration on Ubuntu 15.10 (and have used it on previous releases); I am currently using the 352.63 ("long-lived +# branch") NVIDIA binary drivers, installed from the Ubuntu software repositories. I use fluxbox as my window manager; most of +# the other components of my desktop environment are borrowed from Xfce. +# +# My workstation at home has an i7-4930K and a GTX 970 in it, which are together more than enough to drive several 4K displays +# without tearing or lagging. Even with quite a few windows open at the moment, 'nvidia-smi' shows the X.org server using +# about 600 MiB of video memory; both the CPU and the GPU are effectively idle. +# +# I use the version of compton packaged in my PPA (https://launchpad.net/~kelleyk/+archive/ubuntu/compton). As of the time of this +# writing, that is d7f95b5, which is the what's on the master branch of the upstream compton repository. +# +# My X11 configuration is very "normal". On 15.04, I did have the following in a file in /etc/X11/xorg.conf.d/; I have not +# investigated yet, but I haven't noticed any ill effects from *not* having it since installing 15.10 a few hours ago. +# In some cases, UseEvents can make things unstable (though I haven't run into that in a long time); however, not enabling +# it will cause the X server to spin (leading to high CPU usage) while waiting for certain resources. +# --------------------------- +# Section "Device" +# Identifier "Device0" +# Option "UseEvents" "True" +# EndSection +# --------------------------- +# +# Good luck! + +# References: +# - https://github.com/chjj/compton/wiki +# - http://ubuntuforums.org/showthread.php?t=2144468 + +### Backend/performance options +backend = "glx"; +paint-on-overlay = true; +glx-no-stencil = true; +glx-no-rebind-pixmap = true; + +# (TODO: Clean up my notes about selecting a vsync implementation.) +xrender-sync-fence = true; +vsync = "opengl-swc"; + +# This option throttles refresh rates. Not compatible with vsync=drm/opengl/opengl-oml. +# (Note: for me, enabling this just makes the choppy dragging more noticeable, like the update rate has gone down.) +sw-opti = true; + +# # Per compton performance tips (from the GitHub wiki), only one of these three +# # (glx-use-copysubbuffermesa, glx-copy-from-front, glx-swap-method) +# # should be used. +# glx-use-copysubbuffermesa = true; +# glx-copy-from-front = false; +# glx-swap-method = "undefined"; +glx-swap-method = "exchange"; # requires "allow flipping" in nvidia-settings + +### Shadow +shadow = true; +no-dnd-shadow = true; +no-dock-shadow = true; +clear-shadow = true; +shadow-radius = 7; +shadow-offset-x = -7; +shadow-offset-y = -7; +shadow-opacity = 0.5; +# shadow-red = 0.0; +# shadow-green = 0.0; +# shadow-blue = 0.0; +shadow-exclude = [ + # From the Ubuntu forums link ('screaminj3sus') + "! name~=''", + "n:e:Notification", + "n:e:Plank", + "n:e:Docky", + "g:e:Synapse", + "g:e:Kupfer", + "g:e:Conky", + "n:w:*Firefox*", + "n:w:*Chrome*", + "n:w:*Chromium*", + "class_g ?= 'Notify-osd'", + "class_g ?= 'Cairo-dock'", + "class_g ?= 'Xfce4-notifyd'", + "class_g ?= 'Xfce4-power-manager'" +]; +shadow-ignore-shaped = false; +# shadow-exclude-reg = "x10+0+0"; +# xinerama-shadow-crop = true; + +### Opacity +menu-opacity = 0.95; +# inactive-opacity = 0.85; +# active-opacity = 0.8; +frame-opacity = 0.50; # i.e. titlebars, borders +inactive-opacity-override = false; +alpha-step = 0.06; +opacity-rule = [ "90:class_g = 'kitty'" ]; + +# inactive-dim = 0.2; +# inactive-dim-fixed = true; + +### Blur options +# blur-background = true; +# blur-background-frame = true; +# blur-kern = "3x3box" +# blur-kern = "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +# blur-background-fixed = true; +blur-background-exclude = [ "window_type = 'dock'", "window_type = 'desktop'" ]; + +### Fading +fading = true; +fade-delta = 5; # 30; +fade-in-step = 0.03; +fade-out-step = 0.03; +# no-fading-openclose = true; +fade-exclude = [ ]; + +### Other + +mark-wmwin-focused = true; +mark-ovredir-focused = true; + +# Use EWMH _NET_WM_ACTIVE_WINDOW to determine which window is focused instead of using FocusIn/Out events. +# Usually more reliable but depends on a EWMH-compliant WM. +use-ewmh-active-win = true; + +# # Detect rounded corners and treat them as rectangular when --shadow-ignore-shaped is on. +# detect-rounded-corners = true; + +detect-client-opacity = true; +refresh-rate = 0; +dbe = false; +unredir-if-possible = true; +# unredir-if-possible-delay = 5000; +# unredir-if-possible-exclude = [ ]; +# focus-exclude = [ "class_g = 'Cairo-clock'" ]; +detect-transient = true; +detect-client-leader = true; +invert-color-include = [ ]; +# resize-damage = 1; + +# Window type settings +wintypes: +{ + tooltip = { fade = true; shadow = false; opacity = 0.75; focus = true; }; +}; + diff --git a/deploy.py b/deploy.py index 5e974e3..de3b699 100644 --- a/deploy.py +++ b/deploy.py @@ -22,6 +22,8 @@ targeted = { "doom": ".doom.d", "fish": ".config/fish", "alacritty": ".config/alacritty", + "i3": ".config/i3", + "compton.conf": ".config/compton.conf", } diff --git a/i3/binds.py b/i3/binds.py new file mode 100755 index 0000000..503a9ae --- /dev/null +++ b/i3/binds.py @@ -0,0 +1,545 @@ +# A script to generate keybindings for i3 Window Manager modes with consistent behavior and help text. +from __future__ import annotations + +import io +from contextlib import redirect_stdout +from dataclasses import dataclass, field +from pathlib import Path +from textwrap import indent +from typing import List, Optional, Dict, Tuple + + +@dataclass +class Binding: + """An abstract superclass for both mode and command bindings.""" + + name: str + key: str = field(default="") + + # Execute the binding only on key release + release: bool = field(default=False) + + # A hidden binding won't show up in mode descriptions. Useful if you want to have + # a second version of a binding with a modifier key, but don't need it to show up separately.""" + hidden: bool = field(default=False) + # Override the binding's default hint text. + hint: str = field(init=False, default="") + + def __post_init__(self): + if not self.key: + self.key = self.name.lower()[0] + + def binding(self, in_mode: Optional[str] = None) -> str: + pass + + def _bind_prefix(self, key: Optional[str] = None) -> str: + prefix = "bindsym " + if self.release: + prefix += "--release " + prefix += key or self.key + return prefix + + def mode_hint(self) -> str: + """How to display this binding in the help text of a Mode.""" + if self.hint: + return self.hint + + if len(self.key) == 1 and self.key in self.name: + return self.name.replace(self.key, f"{self.key}", 1) + + return f"[{self.key}]{self.name}" + + +@dataclass +class Mode(Binding): + """A mode which can contain a list of sub-bindings, which may be a mix of commands and modes. + exit_bindings contains a list of binds that will cancel the mode and return to default.""" + + bindings: List[Binding] = field(default_factory=list) + + exit_bindings = [ + "Return", + "Escape", + "control+g", + "control+bracketleft", + ] + + def __post_init__(self): + super().__post_init__() + + used_keys: Dict[str, Binding] = {} + for binding in self.bindings: + # Deconflicting directional bindings is complicated, so let's punt on it for now + if isinstance(binding, Directional): + continue + err_base = ( + f"Binding {binding.key} for {self.name}.{binding.name} conflicts with " + ) + if binding.key in self.exit_bindings: + raise ValueError(err_base + "mode escape key.") + if binding.key in used_keys: + raise ValueError(err_base + used_keys[binding.key].name) + used_keys[binding.key] = binding + + def mode_name(self, parent: Optional[str]) -> str: + """The full name of the mode including its parents names, for disambiguation.""" + if parent: + return f"{parent}_{self.name}" + else: + return self.name + + def mode_var(self, parent: Optional[str]) -> str: + """The name of the variable representing the mode.""" + return f"$mode_{self.mode_name(parent)}" + + def mode_label(self, parent: Optional[str]) -> str: + """The mode label to show at the start of the mode help text.""" + lineage = self.mode_name(parent).split("_") + + # Remove the common [space] prefix on nested modes. + if len(lineage) > 1: + lineage.remove("space") + return "".join(f"[{part}]" for part in lineage) + + def mode_def(self, parent: Optional[str]) -> str: + name = self.mode_name(parent) + var = self.mode_var(parent) + + binding_names = ", ".join( + [binding.mode_hint() for binding in self.bindings if not binding.hidden] + ) + help_text = f"{self.mode_label(parent)}: {binding_names}" + + mode = f"set {var} {help_text}\n" f'mode --pango_markup "{var}" {{\n' + + for binding in self.bindings: + mode += indent(binding.binding(name) + "\n", " ") + + for binding in self.exit_bindings: + mode += indent(f'{self._bind_prefix(binding)} mode "default"\n', " ") + + mode += "}\n" + + submodes = [binding for binding in self.bindings if isinstance(binding, Mode)] + + for sub in submodes: + mode += "\n" + mode += sub.mode_def(name) + + return mode + + def binding(self, parent: Optional[str] = None) -> str: + return f'{self._bind_prefix()} mode "{self.mode_var(parent)}"' + + +@dataclass +class Command(Binding): + command: Optional[str] = field(default=None) + exit_mode: bool = field(default=True) + + def __post_init__(self): + super().__post_init__() + if not self.command: + self.command = self.name.lower() + + def binding(self, parent: Optional[str] = None) -> str: + return self._binding(self.key, self.command, parent) + + def _binding(self, key: str, command: str, parent: Optional[str] = None) -> str: + bind = f"{self._bind_prefix(key)} {command}" + if parent and self.exit_mode: + bind += ', mode "default"' + return bind + + +@dataclass +class Exec(Command): + startup_id: bool = field(default=False) + + def binding(self, parent: Optional[str] = None) -> str: + return self._binding( + self.key, + f"exec {'--no-startup-id ' if not self.startup_id else ''}{self.command}", + parent, + ) + + +@dataclass +class Directional(Command): + """A convenience class for directional commands that can be executed upleft/down/up/right with h/j/k/l or the + arrow keys. Optional parameters include a subset of directions to use, and a modifier for the command. + The command should include the template variable {direction}.""" + + subset: Optional[List[str]] = None + modifier: Optional[str] = None + flip: bool = False + + flips = {"left": "right", "right": "left", "down": "up", "up": "down"} + + directions = { + "left": ["h", "Left"], + "down": ["j", "Down"], + "up": ["k", "Up"], + "right": ["l", "Right"], + } + + def prefix(self) -> str: + return self.modifier + "+" if self.modifier else "" + + def used_keys(self) -> Tuple[List[str], List[str]]: + letter_keys = [] + arrow_keys = [] + + for direction in self.directions: + if not self.subset or direction in self.subset: + letter, arrow = self.directions[direction] + letter_keys.append(letter) + arrow_keys.append(arrow) + + return letter_keys, arrow_keys + + def mode_hint(self) -> str: + letter_keys, arrow_keys = self.used_keys() + + hint = self.prefix() + "" + + hint += "/".join(letter_keys) + hint += "/" + hint += "/".join(arrow_keys) + + hint += f" {self.name}" + return hint + + def binding(self, parent: Optional[str] = None): + bind = "" + for direction, keys in self.directions.items(): + if not self.subset or direction in self.subset: + if self.flip: + direction = self.flips[direction] + for key in keys: + bind += self._binding(self.prefix() + key, self.command).format( + direction=direction + ) + bind += "\n" + return bind + + +@dataclass +class App: + """ + Required args: + name: The name of the app. + Accepted args: + key: The key to use. Defaults to the first lower case letter of the name. + Regular press will try to switch to the app if it's already open. + Shift press will guarantee opening a new instance. Shift bindings are hidden from mode help text. + + path: The binary to execute. Defaults to the lowercase version of the name. + args: Args to pass to the app on launch + class: The X11 Window Class to search for. Defaults to the app name. Case insensitive. + title: The X11 Window Title to search for. + switch: If True, switch to the app instead of launching a new instance unless shift is held. Default True. + """ + + name: str + key: str = field(default="") + path: str = field(default="") + args: str = field(default="") + window_class: str = field(default="") + window_title: str = field(default="") + switch: bool = field(default=True) + + def __post_init__(self): + if not self.key: + self.key = self.name.lower()[0] + if not self.path: + self.path = self.name.lower() + if not self.window_class: + self.window_class = self.name + + @property + def class_query(self) -> str: + return f'class="(?i){self.window_class}"' + + @property + def title_query(self) -> str: + if self.window_title: + return f' title="(?i){self.window_title}"' + else: + return "" + + @property + def query(self) -> str: + return f"[{self.class_query}{self.title_query}]" + + def commands(self) -> List[Command]: + cmd = f"{self.path} {self.args}" + switch_cmd = f"pgrep {self.path} && i3-msg '{self.query} focus' || {cmd}" + + commands = [ + Exec(self.name, key=self.key, command=switch_cmd if self.switch else cmd), + Exec(self.name, key=f"Shift+{self.key}", command=cmd, hidden=True), + ] + + return commands + + @staticmethod + def find(name: str) -> Optional[App]: + for app in apps: + if app.name == name: + return app + return None + + +apps = [ + App("firefox"), + App("kitty", key="t", args="--single-instance", switch=False), + App("emacs"), + App("pycharm"), + App("idea", key="j"), + App("discord"), + App("slack"), + App("zoom"), + App( + "windows", + path="virt-viewer", + args="--connect qemu:///system -w Windows10", + window_class="virt-viewer", + ), +] + +bindings = [ + Exec("Terminal", key="$mod+Return", command="kitty --single-instance"), + Command("next workspace", key="$mod+Tab", command="workspace next_on_output"), + Command("prev workspace", key="$mod+Shift+Tab", command="workspace prev_on_output"), + # Volume control + Exec( + "Volume Up", + key="XF86AudioRaiseVolume", + command="pactl set-sink-volume @DEFAULT_SINK@ +10%", + startup_id=False, + ), + Exec( + "Volume Down", + key="XF86AudioLowerVolume", + command="pactl set-sink-volume @DEFAULT_SINK@ -10%", + startup_id=False, + ), + Exec( + "Mute", + key="XF86AudioMute", + command="pactl set-sink-mute @DEFAULT_SINK@ toggle", + startup_id=False, + ), + Directional("Focus", command="focus {direction}", modifier="$mod"), + Directional( + "Move", command="move {direction}", modifier="$mod+Shift", release=True + ), + Mode( + "space", + key="$mod+space", + bindings=[ + Exec("Run", key="space", command="rofi -show run"), + Mode( + "workspace", + key="p", + bindings=[ + Mode( + "move", + bindings=[ + Directional( + "Move", + command="exec --no-startup-id i3-msg move workspace to output {direction}", + subset=["left", "right"], + exit_mode=False, + ), + ], + ), + Exec("switch to", key="p", command="i3_switch_workspace.sh"), + ], + ), + Mode( + "window", + bindings=[ + Command("delete", command="kill"), + Command("fullscreen", command="fullscreen toggle"), + Command("float", key="o", command="floating toggle"), + Command("split", command="split h"), + Command("vertical split", key="v", command="split v"), + Command("parent", command="focus parent", exit_mode=False), + Mode( + "move", + bindings=[ + Directional( + "Move", + command="move {direction}", + exit_mode=False, + release=True, + ), + Exec( + "to workspace", + key="p", + command="i3_send_to_workspace.sh", + ), + ], + ), + Mode( + "resize", + bindings=[ + Directional( + "Grow", + command="resize grow {direction} 10 px or 10 ppt", + exit_mode=False, + ), + Directional( + "Shrink", + command="resize shrink {direction} 10 px or 10 ppt", + modifier="Shift", + exit_mode=False, + flip=True, + ), + ], + ), + Mode( + "layout", + bindings=[ + Command("split", command="toggle split"), + Command("tabbed", command="layout tabbed"), + Command("stacking", key="k", command="layout stacking"), + ], + ), + ], + ), + Mode("open", bindings=[cmd for app in apps for cmd in app.commands()]), + Mode( + "goto", + bindings=[ + Exec("server", command="rofi -show ssh"), + ], + ), + Mode( + "quit", + bindings=[ + Exec("logout", key="q", command="i3-msg exit"), + Exec("reload", command="i3_reconfigure", key="r"), + Command("restart", key="Shift+r"), + Mode( + "system", + bindings=[ + Exec( + "suspend", startup_id=False, command="systemctl suspend" + ), + Exec( + "reboot", startup_id=False, command="systemctl reboot" + ), + Exec( + "power off", + startup_id=False, + command="systemctl poweroff", + ), + ], + ), + ], + ), + ], + ), +] + + +@dataclass +class Output: + name: str + pos: str + mode: str = field(default="2560x1440") + rate: int = field(default=60) + primary: bool = field(default=False) + + def xrandr_flags(self) -> str: + flags = f"--output {self.name} --pos {self.pos} --mode {self.mode} --rate {self.rate}" + if self.primary: + flags += " --primary" + return flags + + +outputs = [ + Output("DP-0", "0x0"), + Output("DP-4", "2560x0", rate=144, primary=True), + Output("HDMI-0", "5120x0"), +] + + +def xrandr_command() -> str: + cmd = "exec --no-startup-id xrandr\\\n" + for output in outputs: + cmd += indent(output.xrandr_flags() + " \\\n", " ") + cmd = cmd[:-3] + return cmd + + +@dataclass +class Workspace: + name: str + output: str = field(default="primary") + assigned_apps: List[str] = field(default_factory=list) + + def config(self, index: int) -> str: + ws_var = f"$ws_{self.name.lower()}" + cfg = f"set {ws_var} {index}: {self.name}\n" + cfg += f'workspace "{ws_var}" output {self.output}\n' + for a in self.assigned_apps: + app = App.find(a) + if not app: + raise ValueError(f"Couldn't find app {a}") + cfg += f'assign {app.query} "{ws_var}"\n' + + return cfg + + +workspaces = [ + Workspace("Main", output="DP-4"), + Workspace("Tasks", output="DP-0", assigned_apps=["windows"]), + Workspace("Comms", output="HDMI-0", assigned_apps=["discord", "slack"]), + Workspace("Python", output="primary", assigned_apps=["pycharm"]), + Workspace("Java", output="primary", assigned_apps=["idea"]), +] + + +def workspace_config() -> str: + f = io.StringIO() + + with redirect_stdout(f): + for idx, workspace in enumerate(workspaces): + print(workspace.config(idx + 1)) + + return f.getvalue() + + +def binds_config() -> str: + f = io.StringIO() + + preamble = (Path(__file__).parent / "preamble").read_text() + postscript = (Path(__file__).parent / "postscript").read_text() + + with redirect_stdout(f): + print(preamble) + print() + print(xrandr_command()) + print() + for binding in bindings: + if isinstance(binding, Mode): + print(binding.mode_def(None)) + print(binding.binding()) + + print() + print(workspace_config(), end="") + print(postscript) + + return f.getvalue() + + +def main(): + print(binds_config()) + + +if __name__ == "__main__": + main() diff --git a/i3/config b/i3/config new file mode 100644 index 0000000..5add964 --- /dev/null +++ b/i3/config @@ -0,0 +1,246 @@ +set $mod Mod4 +set $alt Mod1 + +# Use Mouse+$mod to drag floating windows to their wanted position +floating_modifier $alt + +# Font for window titles. Will also be used by the bar unless a different font +# is used in the bar {} block below. +font pango:DejaVu Sans Mono 10 + +exec --no-startup-id compton + + +exec --no-startup-id xrandr\ + --output DP-0 --pos 0x0 --mode 2560x1440 --rate 60 \ + --output DP-4 --pos 2560x0 --mode 2560x1440 --rate 144 --primary \ + --output HDMI-0 --pos 5120x0 --mode 2560x1440 --rate 60 + +bindsym $mod+Return exec --no-startup-id kitty --single-instance +bindsym $mod+Tab workspace next_on_output +bindsym $mod+Shift+Tab workspace prev_on_output +bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +10% +bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -10% +bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle +bindsym $mod+h focus left +bindsym $mod+Left focus left +bindsym $mod+j focus down +bindsym $mod+Down focus down +bindsym $mod+k focus up +bindsym $mod+Up focus up +bindsym $mod+l focus right +bindsym $mod+Right focus right + +bindsym --release $mod+Shift+h move left +bindsym --release $mod+Shift+Left move left +bindsym --release $mod+Shift+j move down +bindsym --release $mod+Shift+Down move down +bindsym --release $mod+Shift+k move up +bindsym --release $mod+Shift+Up move up +bindsym --release $mod+Shift+l move right +bindsym --release $mod+Shift+Right move right + +set $mode_space [space]: [space]Run, workspace, window, open, goto, quit +mode --pango_markup "$mode_space" { + bindsym space exec --no-startup-id rofi -show run, mode "default" + bindsym p mode "$mode_space_workspace" + bindsym w mode "$mode_space_window" + bindsym o mode "$mode_space_open" + bindsym g mode "$mode_space_goto" + bindsym q mode "$mode_space_quit" + bindsym Return mode "default" + bindsym Escape mode "default" + bindsym control+g mode "default" + bindsym control+bracketleft mode "default" +} + +set $mode_space_workspace [workspace]: move, [p]switch to +mode --pango_markup "$mode_space_workspace" { + bindsym m mode "$mode_space_workspace_move" + bindsym p exec --no-startup-id i3_switch_workspace.sh, mode "default" + bindsym Return mode "default" + bindsym Escape mode "default" + bindsym control+g mode "default" + bindsym control+bracketleft mode "default" +} + +set $mode_space_workspace_move [workspace][move]: h/l/Left/Right Move +mode --pango_markup "$mode_space_workspace_move" { + bindsym h exec --no-startup-id i3-msg move workspace to output left + bindsym Left exec --no-startup-id i3-msg move workspace to output left + bindsym l exec --no-startup-id i3-msg move workspace to output right + bindsym Right exec --no-startup-id i3-msg move workspace to output right + + bindsym Return mode "default" + bindsym Escape mode "default" + bindsym control+g mode "default" + bindsym control+bracketleft mode "default" +} + +set $mode_space_window [window]: delete, fullscreen, float, split, vertical split, parent, move, resize, layout +mode --pango_markup "$mode_space_window" { + bindsym d kill, mode "default" + bindsym f fullscreen toggle, mode "default" + bindsym o floating toggle, mode "default" + bindsym s split h, mode "default" + bindsym v split v, mode "default" + bindsym p focus parent + bindsym m mode "$mode_space_window_move" + bindsym r mode "$mode_space_window_resize" + bindsym l mode "$mode_space_window_layout" + bindsym Return mode "default" + bindsym Escape mode "default" + bindsym control+g mode "default" + bindsym control+bracketleft mode "default" +} + +set $mode_space_window_move [window][move]: h/j/k/l/Left/Down/Up/Right Move, to workspace +mode --pango_markup "$mode_space_window_move" { + bindsym --release h move left + bindsym --release Left move left + bindsym --release j move down + bindsym --release Down move down + bindsym --release k move up + bindsym --release Up move up + bindsym --release l move right + bindsym --release Right move right + + bindsym p exec --no-startup-id i3_send_to_workspace.sh, mode "default" + bindsym Return mode "default" + bindsym Escape mode "default" + bindsym control+g mode "default" + bindsym control+bracketleft mode "default" +} + +set $mode_space_window_resize [window][resize]: h/j/k/l/Left/Down/Up/Right Grow, Shift+h/j/k/l/Left/Down/Up/Right Shrink +mode --pango_markup "$mode_space_window_resize" { + bindsym h resize grow left 10 px or 10 ppt + bindsym Left resize grow left 10 px or 10 ppt + bindsym j resize grow down 10 px or 10 ppt + bindsym Down resize grow down 10 px or 10 ppt + bindsym k resize grow up 10 px or 10 ppt + bindsym Up resize grow up 10 px or 10 ppt + bindsym l resize grow right 10 px or 10 ppt + bindsym Right resize grow right 10 px or 10 ppt + + bindsym Shift+h resize shrink right 10 px or 10 ppt + bindsym Shift+Left resize shrink right 10 px or 10 ppt + bindsym Shift+j resize shrink up 10 px or 10 ppt + bindsym Shift+Down resize shrink up 10 px or 10 ppt + bindsym Shift+k resize shrink down 10 px or 10 ppt + bindsym Shift+Up resize shrink down 10 px or 10 ppt + bindsym Shift+l resize shrink left 10 px or 10 ppt + bindsym Shift+Right resize shrink left 10 px or 10 ppt + + bindsym Return mode "default" + bindsym Escape mode "default" + bindsym control+g mode "default" + bindsym control+bracketleft mode "default" +} + +set $mode_space_window_layout [window][layout]: split, tabbed, stacking +mode --pango_markup "$mode_space_window_layout" { + bindsym s toggle split, mode "default" + bindsym t layout tabbed, mode "default" + bindsym k layout stacking, mode "default" + bindsym Return mode "default" + bindsym Escape mode "default" + bindsym control+g mode "default" + bindsym control+bracketleft mode "default" +} + +set $mode_space_open [open]: firefox, kitty, emacs, pycharm, [j]idea, discord, slack, zoom, windows +mode --pango_markup "$mode_space_open" { + bindsym f exec --no-startup-id pgrep firefox && i3-msg '[class="(?i)firefox"] focus' || firefox , mode "default" + bindsym Shift+f exec --no-startup-id firefox , mode "default" + bindsym t exec --no-startup-id kitty --single-instance, mode "default" + bindsym Shift+t exec --no-startup-id kitty --single-instance, mode "default" + bindsym e exec --no-startup-id pgrep emacs && i3-msg '[class="(?i)emacs"] focus' || emacs , mode "default" + bindsym Shift+e exec --no-startup-id emacs , mode "default" + bindsym p exec --no-startup-id pgrep pycharm && i3-msg '[class="(?i)pycharm"] focus' || pycharm , mode "default" + bindsym Shift+p exec --no-startup-id pycharm , mode "default" + bindsym j exec --no-startup-id pgrep idea && i3-msg '[class="(?i)idea"] focus' || idea , mode "default" + bindsym Shift+j exec --no-startup-id idea , mode "default" + bindsym d exec --no-startup-id pgrep discord && i3-msg '[class="(?i)discord"] focus' || discord , mode "default" + bindsym Shift+d exec --no-startup-id discord , mode "default" + bindsym s exec --no-startup-id pgrep slack && i3-msg '[class="(?i)slack"] focus' || slack , mode "default" + bindsym Shift+s exec --no-startup-id slack , mode "default" + bindsym z exec --no-startup-id pgrep zoom && i3-msg '[class="(?i)zoom"] focus' || zoom , mode "default" + bindsym Shift+z exec --no-startup-id zoom , mode "default" + bindsym w exec --no-startup-id pgrep virt-viewer && i3-msg '[class="(?i)virt-viewer"] focus' || virt-viewer --connect qemu:///system -w Windows10, mode "default" + bindsym Shift+w exec --no-startup-id virt-viewer --connect qemu:///system -w Windows10, mode "default" + bindsym Return mode "default" + bindsym Escape mode "default" + bindsym control+g mode "default" + bindsym control+bracketleft mode "default" +} + +set $mode_space_goto [goto]: server +mode --pango_markup "$mode_space_goto" { + bindsym s exec --no-startup-id rofi -show ssh, mode "default" + bindsym Return mode "default" + bindsym Escape mode "default" + bindsym control+g mode "default" + bindsym control+bracketleft mode "default" +} + +set $mode_space_quit [quit]: [q]logout, reload, [Shift+r]restart, system +mode --pango_markup "$mode_space_quit" { + bindsym q exec --no-startup-id i3-msg exit, mode "default" + bindsym r exec --no-startup-id i3_reconfigure, mode "default" + bindsym Shift+r restart, mode "default" + bindsym s mode "$mode_space_quit_system" + bindsym Return mode "default" + bindsym Escape mode "default" + bindsym control+g mode "default" + bindsym control+bracketleft mode "default" +} + +set $mode_space_quit_system [quit][system]: suspend, reboot, power off +mode --pango_markup "$mode_space_quit_system" { + bindsym s exec --no-startup-id systemctl suspend, mode "default" + bindsym r exec --no-startup-id systemctl reboot, mode "default" + bindsym p exec --no-startup-id systemctl poweroff, mode "default" + bindsym Return mode "default" + bindsym Escape mode "default" + bindsym control+g mode "default" + bindsym control+bracketleft mode "default" +} + +bindsym $mod+space mode "$mode_space" + +set $ws_main 1: Main +workspace "$ws_main" output DP-4 + +set $ws_tasks 2: Tasks +workspace "$ws_tasks" output DP-0 +assign [class="(?i)virt-viewer"] "$ws_tasks" + +set $ws_comms 3: Comms +workspace "$ws_comms" output HDMI-0 +assign [class="(?i)discord"] "$ws_comms" +assign [class="(?i)slack"] "$ws_comms" + +set $ws_python 4: Python +workspace "$ws_python" output primary +assign [class="(?i)pycharm"] "$ws_python" + +set $ws_java 5: Java +workspace "$ws_java" output primary +assign [class="(?i)idea"] "$ws_java" + +# xss-lock grabs a logind suspend inhibit lock and will use i3lock to lock the +# screen before suspend. Use loginctl lock-session to lock your screen. +exec --no-startup-id xss-lock --transfer-sleep-lock -- i3lock --nofork + +# Start i3bar to display a workspace bar (plus the system information i3status +# finds out, if available) +bar { + tray_output primary + tray_padding 20 px + status_command i3status +} + +# Wallpaper +exec --no-startup-id nitrogen --restore + diff --git a/i3/postscript b/i3/postscript new file mode 100644 index 0000000..dbcc03c --- /dev/null +++ b/i3/postscript @@ -0,0 +1,14 @@ +# xss-lock grabs a logind suspend inhibit lock and will use i3lock to lock the +# screen before suspend. Use loginctl lock-session to lock your screen. +exec --no-startup-id xss-lock --transfer-sleep-lock -- i3lock --nofork + +# Start i3bar to display a workspace bar (plus the system information i3status +# finds out, if available) +bar { + tray_output primary + tray_padding 20 px + status_command i3status +} + +# Wallpaper +exec --no-startup-id nitrogen --restore \ No newline at end of file diff --git a/i3/preamble b/i3/preamble new file mode 100644 index 0000000..22834fe --- /dev/null +++ b/i3/preamble @@ -0,0 +1,11 @@ +set $mod Mod4 +set $alt Mod1 + +# Use Mouse+$mod to drag floating windows to their wanted position +floating_modifier $alt + +# Font for window titles. Will also be used by the bar unless a different font +# is used in the bar {} block below. +font pango:DejaVu Sans Mono 10 + +exec --no-startup-id compton