i3
Main Config
Since I'm using i3 4.19 I don't have access to the new 'include' directive in 4.20. As a workaround for device specific settings I'm using yadm's templating feature so I've included that template below instead of a working i3 config.
# DO NOT EDIT ~/.config/i3/config # MODIFY YOUR TEMPLATE ~/.config/i3/config##template # colors used, for reference #333333 dark grey #56ADBC teal blue #666666 charcoal grey #8C6BC8 deep lavendar #AE81FF liliac #E3E3DD light grey #FA9429 pumpkin #66D9EF robin's egg # Window Colors # class border backgr. text indicator child_border client.focused #333333 #70162E #E3E3DD #CA6402 #70162E client.unfocused #333333 #333333 #E3E3DD #333333 #333333 client.focused_inactive #666666 #333333 #E3E3DD #333333 #333333 client.urgent #AE81FF #8C6BC8 #E3E3DD #8C6BC8 #8C6BC8 client.placeholder #333333 #333333 #E3E3DD #333333 #333333 client.background #E3E3DD # i3 config file (v4) # # Please see http://i3wm.org/docs/userguide.html for a complete reference! show_marks no set $mod Mod4 font pango:Hack 8 # start a terminal bindsym $mod+grave exec urxvt # rox-filer shortcuts bindsym $mod+Tab exec rox-filer # kill focused window bindsym $mod+Shift+apostrophe kill # start dmenu (a program launcher) bindsym $mod+p exec dmenu_run -fn "Hack:size=8" # There also is the (new) i3-dmenu-desktop which only displays applications # shipping a .desktop file. It is a wrapper around dmenu, so you need that # installed. bindsym $mod+y exec --no-startup-id i3-dmenu-desktop --dmenu='dmenu -i -fn "Hack:size=8"' # change focus bindsym $mod+h focus left bindsym $mod+l focus right bindsym $mod+k focus up bindsym $mod+j focus down # move focused window bindsym $mod+Shift+h move left bindsym $mod+Shift+l move right bindsym $mod+Shift+k move up bindsym $mod+Shift+j move down # switch workspaces bindsym $mod+Left workspace prev bindsym $mod+Right workspace next # Rearrange workspaces bindsym $mod+Shift+Left move workspace to output left bindsym $mod+Shift+Down move workspace to output down bindsym $mod+Shift+Up move workspace to output up bindsym $mod+Shift+Right move workspace to output right # split in horizontal orientation bindsym $mod+s split h # split in vertical orientation bindsym $mod+v split v # enter fullscreen mode for the focused container bindsym $mod+u fullscreen toggle bindsym $mod+Shift+u fullscreen toggle global # change container layout (stacked, tabbed, toggle split) bindsym $mod+apostrophe layout stacking bindsym $mod+comma layout tabbed bindsym $mod+period layout toggle split # Use Mouse+$mod to drag floating windows to their wanted position floating_modifier $mod # toggle tiling / floating bindsym $mod+Shift+space floating toggle # change focus between tiling / floating windows bindsym $mod+space focus mode_toggle # always float for_window [class="^Yad$"] floating enable for_window [class="^Geeqie$"] floating enable for_window [class="^.*gThumb$"] floating enable # all steam windows except the main one for_window [title="^(?!.*Steam).*$" class="^steam$"] floating enable # scratchpad bindsym $mod+shift+z move scratchpad bindsym $mod+shift+v scratchpad show # focus the parent container bindsym $mod+a focus parent # focus the child container bindsym $mod+Shift+a focus child # switch to workspace bindsym $mod+1 workspace number 1 bindsym $mod+2 workspace number 2 bindsym $mod+3 workspace number 3 bindsym $mod+4 workspace number 4 bindsym $mod+5 workspace number 5 bindsym $mod+6 workspace number 6 bindsym $mod+7 workspace number 7 bindsym $mod+8 workspace number 8 bindsym $mod+9 workspace number 9 bindsym $mod+0 workspace number 10 bindsym $mod+F1 workspace number 11 bindsym $mod+F2 workspace number 12 bindsym $mod+F3 workspace number 13 bindsym $mod+F4 workspace number 14 bindsym $mod+F5 workspace number 15 bindsym $mod+F6 workspace number 16 bindsym $mod+F7 workspace number 17 bindsym $mod+F8 workspace number 18 bindsym $mod+F9 workspace number 19 bindsym $mod+F10 workspace number 20 # move focused container to workspace bindsym $mod+Shift+1 move container to workspace number 1 bindsym $mod+Shift+2 move container to workspace number 2 bindsym $mod+Shift+3 move container to workspace number 3 bindsym $mod+Shift+4 move container to workspace number 4 bindsym $mod+Shift+5 move container to workspace number 5 bindsym $mod+Shift+6 move container to workspace number 6 bindsym $mod+Shift+7 move container to workspace number 7 bindsym $mod+Shift+8 move container to workspace number 8 bindsym $mod+Shift+9 move container to workspace number 9 bindsym $mod+Shift+0 move container to workspace number 10 bindsym $mod+Shift+F1 move container to workspace number 11 bindsym $mod+Shift+F2 move container to workspace number 12 bindsym $mod+Shift+F3 move container to workspace number 13 bindsym $mod+Shift+F4 move container to workspace number 14 bindsym $mod+Shift+F5 move container to workspace number 15 bindsym $mod+Shift+F6 move container to workspace number 16 bindsym $mod+Shift+F7 move container to workspace number 17 bindsym $mod+Shift+F8 move container to workspace number 18 bindsym $mod+Shift+F9 move container to workspace number 19 bindsym $mod+Shift+F10 move container to workspace number 20 # new workspace bindsym $mod+n exec "i3-msg workspace number $(($(i3-msg -t get_workspaces |jq -r '.[].name'|cut -d':' -f 1|sort -n| tail -1) + 1))" # work with workspaces bindsym $mod+Shift+r exec i3-msg rename workspace \ $(i3-msg -t get_workspaces | jq '.[] | select(.focused==true).name') \ to \ $(i3-msg -t get_workspaces | jq '.[] | select(.focused==true).num'):$(yad --entry --title 'Rename workspace' --text "New workspace name:") bindsym $mod+Mod1+Shift+m exec "~/.config/i3/ws_main.sh" bindsym $mod+Mod1+Shift+c exec "~/.config/i3/ws_new_code.sh" bindsym $mod+Mod1+Shift+d exec "~/.config/i3/ws_new_doc.sh" bindsym $mod+Mod1+Shift+y exec "~/.config/i3/ws_youtube_mp3.sh" # remark windows that are already swallowed for_window [class="^Atril$|^Devhelp$|^Evince$|^Gvim$|^calibre$" workspace="^\d+:doc" con_mark="^swallowed\d$"] mark --replace unswallowed # toggle it. (swallowed windows become unmarked, unswallowed windows are now marked) for_window [class="^Atril$|^Devhelp$|^Evince$|^Gvim$|^calibre$" workspace="^\d+:doc"] mark --toggle unswallowed # move unswallowed windows to the desired mark for_window [class="^Atril$|^Devhelp$|^Evince$|^Gvim$|^calibre$" workspace="^\d+:doc" con_mark="^unswallowed$"] move window to mark doc # Lock with xscreensaver (Hyper+Alt+l) {% if yadm.hostname == "dreadnought" %} # Save screenshots of all workspaces, then lock bindsym $mod+Mod1+l exec "~/.scripts/i3_screenshot.py; xscreensaver-command -lock" {% else %} bindsym $mod+Mod1+l exec "xscreensaver-command -lock" {% endif %} # reload the configuration file bindsym $mod+Shift+y reload # restart i3 inplace (preserves your layout/session, can be used to upgrade i3) bindsym $mod+Shift+p restart # exit i3 (logs you out of your X session) bindsym $mod+Shift+period exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'" # mouse actions bindsym --release $mod+button2 kill # Issue #2523 numlock is somehow Mod2. dumb. bindsym --release Mod2+button10 kill bindsym --release button10 kill bindsym --release $mod+button3 floating toggle # move all but main (1) workspace to chosen output # (uses xrandr, grep, cut, yad, i3-msg, jq) bindsym $Mod+Shift+m exec "~/.config/i3/mv_ws.sh" # change screen layout mode "screenlayout" { bindsym grave exec autorandr common; mode "default" bindsym 1 exec autorandr --load "Magedok 9 - |⠀⡀⠀⠀|"; mode "default" bindsym 2 exec autorandr --load "U2414H - |⠰⠶⠀⠀|"; mode "default" bindsym 3 exec autorandr --load "U2515H - |⠀⠀⠿⠿|"; mode "default" bindsym 4 exec autorandr --load "MageDok 15 - |⠈⠁⠀⠀|"; mode "default" bindsym 5 exec autorandr --load "All - |⠸⡷⠿⠿|"; mode "default" # new background bindsym 0 exec --no-startup-id ~/.fehbg # reset background if it gets shifted bindsym 9 exec --no-startup-id feh --no-fehbg --bg-fill "$(cat ~/.last_fehbg)" bindsym t exec ~/.screenlayout/touch_display.sh bindsym $mod+x mode "default" bindsym Return mode "default" bindsym Escape mode "default" # exit with Russian layout, separate from Русский layout # prevents multiple screenlayout definitions bindsym $mod+Cyrillic_i mode "default" } bindsym $mod+x mode "screenlayout" bindsym $mod+Shift+x exec "~/.screenlayout/autorandr.sh" #Toggle touchscreen/touchpad bindsym $mod+Shift+s exec --no-startup-id notify-send "$(~/.config/i3/toggle_ts.sh)" -t 1400 bindsym $mod+Shift+t exec --no-startup-id notify-send "$(~/.config/i3/toggle_tp.sh)" -t 1400 # Multimedia, brightness keys {% if yadm.hostname == "dreadnought" %} bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume alsa_output.usb-0d8c_USB_Sound_Device-00.analog-stereo +3% && pactl set-sink-volume alsa_output.pci-0000_0f_00.6.analog-stereo +3% bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume alsa_output.usb-0d8c_USB_Sound_Device-00.analog-stereo -3% && pactl set-sink-volume alsa_output.pci-0000_0f_00.6.analog-stereo -3% bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute alsa_output.usb-0d8c_USB_Sound_Device-00.analog-stereo toggle && pactl set-sink-mute alsa_output.pci-0000_0f_00.6.analog-stereo toggle {% else %} bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume 0 +3% bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume 0 -3% bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute 0 toggle {% endif %} bindsym XF86Display exec --no-startup-id lxrandr bindsym XF86MonBrightnessUp exec brightnessctl s +10% bindsym XF86MonBrightnessDown exec brightnessctl s 10%- bindsym XF86AudioPlay exec playerctl play-pause bindsym XF86AudioPrev exec playerctl previous bindsym XF86AudioNext exec playerctl next bindsym $mod+Shift+Mod1+space exec playerctl play-pause bindsym $mod+Up exec playerctl previous bindsym $mod+Down exec playerctl next # Start i3bar to display a workspace bar (plus the system information i3status # finds out, if available) bar { status_command i3status tray_output primary bindsym button2 exec "i3-msg workspace number $(($(i3-msg -t get_workspaces |jq -r '.[].name'|cut -d':' -f 1|sort -n| tail -1) + 1))" bindsym button4 workspace prev_on_output bindsym button5 workspace next_on_output colors { background #1B1D1E statusline #E3E3DD focused_workspace #666666 #66D9EF #333333 active_workspace #666666 #333333 #56ADBC urgent_workspace #666666 #FA9429 #333333 binding_mode #666666 #FA9429 #333333 } } # cursor setup, background exec --no-startup-id xsetroot -cursor_name left_ptr exec --no-startup-id ~/.fehbg # autostart apps exec --no-startup-id picom exec --no-startup-id /usr/lib/x86_64-linux-gnu/xfce4/notifyd/xfce4-notifyd exec --no-startup-id keepassx exec --no-startup-id remmina -i exec --no-startup-id krb5-auth-dialog exec --no-startup-id pasystray exec --no-startup-id nm-applet exec --no-startup-id udiskie --tray exec --no-startup-id xfce4-notes exec --no-startup-id kdeconnect-indicator exec --no-startup-id blueman-applet exec --no-startup-id usbguard-applet-qt exec --no-startup-id fusuma --daemon {% if yadm.hostname == "dreadnought" %} exec --no-startup-id ~/.dropbox/dropbox_start.py {% endif %} {% if yadm.hostname == "delta" %} exec --no-startup-id ~/.dropbox/dropbox_start.py exec --no-startup-id brightnessctl --device='tpacpi::kbd_backlight' set 1 {% else %} exec --no-startup-id xscreensaver -nosplash {% endif %} {% if yadm.hostname == "delta" %} # remarkable vnsee display # exec --no-startup-id xrandr --newmode 1404x1872 0 1404 1404 1404 1404 1872 1872 1872 1872 # exec --no-startup-id xrandr --addmode VIRTUAL1 1404x1872 # exec --no-startup-id xrandr --output VIRTUAL1 --mode 1404x1872 --right-of eDP1 # bindsym $mod+F11 exec "pkill -f 'x11vnc -repeat -forever -nocursor -allow 10.11.99.1'" # bindsym $mod+F12 exec "x11vnc -repeat -forever -nocursor -allow 10.11.99.1 -nopw -clip $(xrandr | perl -n -e'/VIRTUAL1 .*?(\d+x\d+\+\d+\+\d+)/ && print $1')" {% endif %} ############################################## ############ Русский layout binds ############ ############################################## bindsym $mod+less exec urxvt # kill focused window bindsym $mod+Shift+Cyrillic_SHORTI kill # start dmenu, i3-dmenu-desktop bindsym $mod+Cyrillic_ka exec dmenu_run -fn "Hack:size=8" bindsym $mod+Cyrillic_ie exec --no-startup-id i3-dmenu-desktop --dmenu='dmenu -i -fn "Hack:size=8"' # change focus bindsym $mod+Cyrillic_o focus left bindsym $mod+Cyrillic_ze focus right bindsym $mod+Cyrillic_em focus up bindsym $mod+Cyrillic_es focus down # move focused window bindsym $mod+Shift+Cyrillic_O move left bindsym $mod+Shift+Cyrillic_ZE move right bindsym $mod+Shift+Cyrillic_EM move up bindsym $mod+Shift+Cyrillic_ES move down # split in horizontal orientation bindsym $mod+Cyrillic_zhe split h # split in vertical orientation bindsym $mod+Cyrillic_yu split v # enter fullscreen mode for the focused container bindsym $mod+Cyrillic_a fullscreen toggle bindsym $mod+Shift+Cyrillic_A fullscreen toggle global # change container layout (stacked, tabbed, toggle split) bindsym $mod+Cyrillic_shorti layout stacking bindsym $mod+Cyrillic_tse layout tabbed bindsym $mod+Cyrillic_u layout toggle split # scratchpad binds omitted, unused, and I don't want to bind the equivalent keys # focus the parent container bindsym $mod+Cyrillic_ef focus parent # focus the child container bindsym $mod+Shift+Cyrillic_EF focus child # new workspace bindsym $mod+Cyrillic_de exec "i3-msg workspace number $(($(i3-msg -t get_workspaces |jq -r '.[].name'|cut -d':' -f 1|sort -n| tail -1) + 1))" # work with workspaces bindsym $mod+Shift+Cyrillic_SHCHA exec i3-msg rename workspace \ $(i3-msg -t get_workspaces | jq '.[] | select(.focused==true).name') \ to \ $(i3-msg -t get_workspaces | jq '.[] | select(.focused==true).num'):$(yad --entry --title 'Rename workspace' --text "New workspace name:") bindsym $mod+Mod1+Shift+Cyrillic_SOFTSIGN exec "~/.config/i3/ws_main.sh" bindsym $mod+Mod1+Shift+Cyrillic_SHA exec "~/.config/i3/ws_new_code.sh" bindsym $mod+Mod1+Shift+Cyrillic_ER exec "~/.config/i3/ws_new_doc.sh" # Lock with xscreensaver (Hyper+Alt+l) bindsym $mod+Mod1+Cyrillic_ze exec "xscreensaver-command -lock" # reload the configuration file bindsym $mod+Shift+Cyrillic_IE reload # restart i3 inplace (preserves your layout/session, can be used to upgrade i3) bindsym $mod+Shift+Cyrillic_KA restart # exit i3 (logs you out of your X session) bindsym $mod+Shift+Cyrillic_U exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'" # move all but main (1) workspace to chosen output # (uses xrandr, grep, cut, yad, i3-msg, jq) bindsym $Mod+Shift+Cyrillic_SOFTSIGN exec "~/.config/i3/mv_ws.sh" # change screen layout with Russian bindsym $mod+Cyrillic_i mode "screenlayout" #Toggle touchscreen/touchpad bindsym $mod+Shift+Cyrillic_ZHE exec --no-startup-id notify-send "$(~/.config/i3/toggle_ts.sh)" -t 1400 bindsym $mod+Shift+Cyrillic_EL exec --no-startup-id notify-send "$(~/.config/i3/toggle_tp.sh)" -t 1400
Helper utilites
i3bar
Stock i3 status and workspace bar
i3status
My default config:
# i3status configuration file. # see "man i3status" for documentation. # It is important that this file is edited as UTF-8. # The following line should contain a sharp s: # ß # If the above line is not correctly displayed, fix your editor first! general { colors = true color_good = "#cfd0c2" color_bad = "#fc981f" color_degraded = "#e7db75" interval = 5 } order += "disk /" order += "disk /home" order += "wireless _first_" order += "ethernet _first_" order += "battery all" order += "load" order += "volume desktop" order += "tztime local" wireless _first_ { format_up = "W: (%quality at %essid) %ip" format_down = "" } ethernet _first_ { # if you use %speed, i3status requires root privileges format_up = "E: %ip (%speed)" format_down = "" color_degraded = "#cfd0c2" } battery all { format = "%status %percentage %remaining" format_down = "" status_chr = "⚡" status_unk = "⚡" status_bat = "🔋" low_threshold = 10 threshold_type = time integer_battery_capacity = true } tztime local { # abbr. weekday YYYY-MM-DD HH:mm:ss format = "%a %F %T" } load { format = "%1min" } disk "/" { format = "root: %avail" } disk "/home" { format = "home: %avail" } volume desktop { format = "🔊: %volume" format_muted = "🔊: muted (%volume)" device = "pulse:0" }
My main desktop config, notable for the volume blocks being manually specified:
# i3status configuration file. # see "man i3status" for documentation. # It is important that this file is edited as UTF-8. # The following line should contain a sharp s: # ß # If the above line is not correctly displayed, fix your editor first! general { colors = true color_good = "#cfd0c2" color_bad = "#fc981f" color_degraded = "#e7db75" interval = 5 } order += "disk /" order += "disk /home" order += "wireless _first_" order += "ethernet enp13s0" order += "ethernet enp14s0" order += "battery all" order += "load" order += "volume desktop" order += "volume music" order += "tztime local" wireless _first_ { format_up = "W: (%quality at %essid) %ip" format_down = "" } ethernet enp13s0 { # if you use %speed, i3status requires root privileges format_up = "E: %ip (%speed)" format_down = "" color_degraded = "#cfd0c2" } ethernet enp14s0 { # if you use %speed, i3status requires root privileges format_up = "E: %ip (%speed)" format_down = "" color_degraded = "#cfd0c2" } battery all { format = "%status %percentage Rem:%remaining" format_down = "" status_chr = "⚡" status_bat = "🔋" low_threshold = 10 threshold_type = time integer_battery_capacity = true } tztime local { # abbr. weekday YYYY-MM-DD HH:mm:ss format = "%a %F %T" } load { format = "%1min" } disk "/" { format = "root: %avail" } disk "/home" { format = "home: %avail" } volume desktop { format = "🔊: %volume" format_muted = "🔊: muted (%volume)" device = "pulse:alsa_output.pci-0000_11_00.6.analog-stereo" } volume music { format = "♪: %volume" format_muted = "♪: muted (%volume)" device = "pulse:alsa_output.usb-0d8c_USB_Sound_Device-00.analog-stereo" }
feh
For setting backgrounds. I have this small script to randomly select a file and track the previous wallpaper for reverting.
#!/bin/sh wallpaper_path="$(find ~/.wallpapers/|sort -R|tail -n 1)" echo "$wallpaper_path" > ~/.last_fehbg feh --no-fehbg --bg-fill "$wallpaper_path"
Scripts
Load a predefined workspace/layout
- Main workspace
Listing 5: ~/.config/i3/ws_main.sh#!/bin/bash workspace_shape=$(i3-msg -t get_workspaces|jq -r '.[]|select (.focused==true)|if .rect.width >= .rect.height then "landscape" else "portrait" end') # Select currently focused workspace number workspace_number=$(($(i3-msg -t get_workspaces|jq -r '.[]|select (.focused==true)|.name' \ |cut -d':' -f 1 |sort -n| tail -1))) # Rename workspace i3-msg rename workspace to "$workspace_number:main" # Load appropriate layout if [ "$workspace_shape" == "portrait" ]; then i3-msg append_layout "$HOME/.config/i3/main-portrait.json" elif [ "$workspace_shape" == "landscape" ]; then i3-msg append_layout "$HOME/.config/i3/main-landscape.json" fi i3-msg "exec cantata; exec claws-mail; exec firefox; exec hexchat"
- Main layouts
Listing 6: ~/.config/i3/main-portrait.json// vim:ts=4:sw=4:et { // tabbed split container with 3 children "border": "normal", "floating": "auto_off", "layout": "tabbed", "percent": 0.4, "type": "con", "nodes": [ { "border": "normal", "current_border_width": 2, "floating": "auto_off", "percent": 0.333333333333333, "swallows": [ { "class": "^Hexchat$", "instance": "^hexchat$" } ], "type": "con" }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "percent": 0.333333333333333, "swallows": [ { "class": "^Claws\\-mail$", "instance": "^claws\\-mail$" } ], "type": "con" }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "percent": 0.333333333333333, "swallows": [ { "class": "^cantata$", "instance": "^cantata$" } ], "type": "con" } ] } { // tabbed split container with 2 children "border": "normal", "floating": "auto_off", "layout": "tabbed", "percent": 0.6, "type": "con", "nodes": [ { "border": "normal", "current_border_width": 2, "floating": "auto_off", "percent": 0.5, "swallows": [ { "class": "^Firefox\\-esr$", "instance": "^Navigator$" } ], "type": "con" }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "geometry": { "height": 1400, "width": 1772, "x": 1920, "y": 352 }, "name": "KeePassX", "percent": 0.5, "swallows": [ { "class": "^Keepassx$", "instance": "^keepassx$" } ], "type": "con" } ] }Listing 7: ~/.config/i3/main-landscape.json// vim:ts=4:sw=4:et { // splitv split container with 2 children "border": "normal", "floating": "auto_off", "layout": "splitv", "percent": 0.365, "type": "con", "nodes": [ { // tabbed split container with 2 children "border": "normal", "floating": "auto_off", "layout": "tabbed", "percent": 0.5, "type": "con", "nodes": [ { "border": "normal", "current_border_width": 2, "floating": "auto_off", "percent": 0.333333333333333, "swallows": [ { "class": "^Claws\\-mail$", "instance": "^claws\\-mail$" } ], "type": "con" }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "percent": 0.333333333333333, "swallows": [ { "class": "^cantata$", "instance": "^cantata$" } ], "type": "con" } ] }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "percent": 0.5, "swallows": [ { "class": "^Hexchat$", "instance": "^hexchat$" } ], "type": "con" } ] } { // tabbed split container with 2 children "border": "normal", "floating": "auto_off", "layout": "tabbed", "percent": 0.635, "type": "con", "nodes": [ { "border": "normal", "current_border_width": 2, "floating": "auto_off", "percent": 0.5, "swallows": [ { "class": "^Keepassx$", "instance": "^keepassx$" } ], "type": "con" }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "percent": 0.5, "swallows": [ { "class": "^Firefox\\-esr$", "instance": "^Navigator$" } ], "type": "con" } ] } - Code workspace
Listing 8: ~/.config/i3/ws_new_code.sh#!/bin/bash workspace_shape=$(i3-msg -t get_workspaces|jq -r '.[]|select (.focused==true)|if .rect.width >= .rect.height then "landscape" else "portrait" end') if [ "$workspace_shape" == "portrait" ]; then workspace_size=$(i3-msg -t get_workspaces|jq -r '.[]|select (.focused==true)|.rect.height') elif [ "$workspace_shape" == "landscape" ]; then workspace_size=$(i3-msg -t get_workspaces|jq -r '.[]|select (.focused==true)|.rect.width') fi # Select next highest unused number workspace_number=$(($(i3-msg -t get_workspaces |jq -r '.[].name' \ |cut -d':' -f 1 |sort -n| tail -1) + 1)) # Switch to and rename workspace i3-msg workspace number $workspace_number if [ -d "$1" ]; then i3-msg rename workspace to "$workspace_number:$(basename "$1")" else i3-msg rename workspace to "$workspace_number:code" fi # Load appropriate layout if [ "$workspace_shape" == "portrait" ]; then # TODO make a portrait layout when the need arises # We still spawn a workplace for now because it's easy to select # all windows and move them to the correct monitor if [ "$workspace_size" -lt 2000 ]; then i3-msg append_layout "$HOME/.config/i3/code_1080_landscape.json" else i3-msg append_layout "$HOME/.config/i3/code_2560_landscape.json" fi elif [ "$workspace_shape" == "landscape" ]; then if [ "$workspace_size" -lt 2000 ]; then i3-msg append_layout "$HOME/.config/i3/code_1080_landscape.json" else i3-msg append_layout "$HOME/.config/i3/code_2560_landscape.json" fi fi # Run applications to fill layout if [ -d "$1" ]; then i3-msg "exec rox-filer '$1'; exec urxvt -cd '$1'; exec urxvt -cd '$1'; exec gvim '$1'; exec gvim '$1'" if [ "$workspace_size" -gt 2000 ]; then # extra filer/editor for larger screens i3-msg "exec rox-filer '$1'; exec gvim '$1'" fi else i3-msg "exec rox-filer; exec urxvt; exec urxvt; exec gvim ./; exec gvim ./" if [ "$workspace_size" -gt 2000 ]; then # extra filer/editor for larger screens i3-msg "exec rox-filer; exec gvim ./" fi fi
- Code layouts
Listing 9: ~/.config/i3/code_1080_landscape.json// vim:ts=4:sw=4:et // Code layout for 1920x1080p landscape displays { // splith split container with 3 children "border": "normal", "floating": "auto_off", "layout": "splith", "percent": 1, "type": "con", "nodes": [ { // splitv split container with 3 children "border": "normal", "floating": "auto_off", "layout": "splitv", "percent": 0.25, "type": "con", "nodes": [ { "border": "normal", "current_border_width": 2, "floating": "auto_off", "geometry": { "height": 282, "width": 400, "x": 0, "y": 0 }, "percent": 0.25, "swallows": [ { "class": "^Rox\\-filer$", "instance": "^rox\\-filer$" } ], "type": "con" }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "geometry": { "height": 316, "width": 400, "x": 0, "y": 0 }, "percent": 0.25, "swallows": [ { "class": "^URxvt$", "instance": "^urxvt$" } ], "type": "con" }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "geometry": { "height": 316, "width": 400, "x": 0, "y": 0 }, "percent": 0.25, "swallows": [ { "class": "^URxvt$", "instance": "^urxvt$" } ], "type": "con" } ] }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "geometry": { "height": 316, "width": 754, "x": 0, "y": 0 }, "percent": 0.375, "swallows": [ { "class": "^Gvim$", "instance": "^gvim$" } ], "type": "con" }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "geometry": { "height": 316, "width": 754, "x": 0, "y": 0 }, "percent": 0.375, "swallows": [ { "class": "^Gvim$", "instance": "^gvim$" } ], "type": "con" } ] }Listing 10: ~/.config/i3/code_2560_landscape.json// vim:ts=4:sw=4:et // Code layout for 2560x1440 landscape displays { // splith split container with 4 children "border": "normal", "floating": "auto_off", "layout": "splith", "percent": 1, "type": "con", "nodes": [ { // splitv split container with 4 children "border": "normal", "floating": "auto_off", "layout": "splitv", "percent": 0.25, "type": "con", "nodes": [ { "border": "normal", "current_border_width": 2, "floating": "auto_off", "geometry": { "height": 282, "width": 564, "x": 0, "y": 0 }, "percent": 0.25, "swallows": [ { "class": "^Rox\\-filer$", "instance": "^rox\\-filer$" } ], "type": "con" }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "geometry": { "height": 146, "width": 304, "x": 0, "y": 0 }, "percent": 0.25, "swallows": [ { "class": "^Rox\\-filer$", "instance": "^rox\\-filer$" } ], "type": "con" }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "geometry": { "height": 316, "width": 576, "x": 0, "y": 0 }, "percent": 0.25, "swallows": [ { "class": "^URxvt$", "instance": "^urxvt$" } ], "type": "con" }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "geometry": { "height": 316, "width": 576, "x": 0, "y": 0 }, "percent": 0.25, "swallows": [ { "class": "^URxvt$", "instance": "^urxvt$" } ], "type": "con" } ] }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "geometry": { "height": 316, "width": 576, "x": 0, "y": 0 }, "percent": 0.25, "swallows": [ { "class": "^Gvim$", "instance": "^gvim$" } ], "type": "con" }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "geometry": { "height": 316, "width": 576, "x": 0, "y": 0 }, "percent": 0.25, "swallows": [ { "class": "^Gvim$", "instance": "^gvim$" } ], "type": "con" }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "geometry": { "height": 316, "width": 576, "x": 0, "y": 0 }, "percent": 0.25, "swallows": [ { "class": "^Gvim$", "instance": "^gvim$" } ], "type": "con" } ] } - Documentation workspace
Listing 11: ~/.config/i3/ws_new_doc.sh#!/bin/bash workspace_shape=$(i3-msg -t get_workspaces|jq -r '.[]|select (.focused==true)|if .rect.width >= .rect.height then "landscape" else "portrait" end') # Select next highest unused number workspace_number=$(($(i3-msg -t get_workspaces |jq -r '.[].name' \ |cut -d':' -f 1 |sort -n| tail -1) + 1)) # Switch to and rename workspace i3-msg workspace number $workspace_number i3-msg rename workspace to "$workspace_number:doc" # Load appropriate layout if [ "$workspace_shape" == "portrait" ]; then i3-msg append_layout "$HOME/.config/i3/doc-portrait.json" elif [ "$workspace_shape" == "landscape" ]; then i3-msg append_layout "$HOME/.config/i3/doc-landscape.json" fi # Run applications to fill layout i3-msg exec rox-filer "$HOME/documents"
- Documentation layouts
Listing 12: ~/.config/i3/doc-portrait.json// vim:ts=4:sw=4:et { // splith split container with 1 children "border": "normal", "floating": "auto_off", "layout": "splith", "percent": 0.294520547945205, "type": "con", "nodes": [ { "border": "normal", "current_border_width": 2, "floating": "auto_off", "geometry": { "height": 304, "width": 657, "x": 0, "y": 0 }, "percent": 1, "swallows": [ { "class": "^Rox\\-filer$", "instance": "^rox\\-filer$" } ], "type": "con" } ] } { // tabbed split container "border": "normal", "floating": "auto_off", "layout": "tabbed", "percent": 0.705479452054795, "type": "con", "swallows": [ { "class": "^Atril$|^Devhelp$|^Evince$|^Gvim$|^calibre$", "instance": "^atril$|^calibre\\-ebook\\-viewer$|^devhelp$|^evince$|^gvim$" } ], "nodes": [ { "border": "normal", "current_border_width": 2, "floating": "auto_off", "percent": 0.5, "swallows": [ { "class": "^Atril$|^Devhelp$|^Evince$|^Gvim$|^calibre$", "instance": "^atril$|^calibre\\-ebook\\-viewer$|^devhelp$|^evince$|^gvim$" } ], "type": "con", "marks": [ "swallowed1" ] }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "percent": 0.5, "swallows": [ { "class": "^Atril$|^Devhelp$|^Evince$|^Gvim$|^calibre$", "instance": "^atril$|^calibre\\-ebook\\-viewer$|^devhelp$|^evince$|^gvim$" } ], "type": "con", "marks": [ "swallowed1" ] } ], "mark": "doc" }Listing 13: ~/.config/i3/doc-landscape.json// vim:ts=4:sw=4:et { "border": "normal", "current_border_width": 2, "floating": "auto_off", "geometry": { "height": 480, "width": 657, "x": 0, "y": 0 }, "percent": 0.22, "swallows": [ { "class": "^Rox\\-filer$", "instance": "^rox\\-filer$" } ], "type": "con" } { // tabbed split container with 2 children "border": "normal", "floating": "auto_off", "layout": "tabbed", "percent": 0.39, "type": "con", "nodes": [ { "border": "normal", "current_border_width": 2, "floating": "auto_off", "percent": 0.5, "swallows": [ { "class": "^Atril$|^Devhelp$|^Evince$|^Gvim$|^calibre$", "instance": "^atril$|^calibre\\-ebook\\-viewer$|^devhelp$|^evince$|^gvim$" } ], "type": "con", "marks": [ "swallowed1" ] }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "percent": 0.5, "swallows": [ { "class": "^Atril$|^Devhelp$|^Evince$|^Gvim$|^calibre$", "instance": "^atril$|^calibre\\-ebook\\-viewer$|^devhelp$|^evince$|^gvim$" } ], "type": "con", "marks": [ "swallowed2" ] } ], "mark": "doc2" } { // tabbed split container with 2 children "border": "normal", "floating": "auto_off", "layout": "tabbed", "percent": 0.39, "type": "con", "nodes": [ { "border": "normal", "current_border_width": 2, "floating": "auto_off", "percent": 0.5, "swallows": [ { "class": "^Atril$|^Devhelp$|^Evince$|^Gvim$|^calibre$", "instance": "^atril$|^calibre\\-ebook\\-viewer$|^devhelp$|^evince$|^gvim$" } ], "type": "con", "marks": [ "swallowed3" ] }, { "border": "normal", "current_border_width": 2, "floating": "auto_off", "percent": 0.5, "swallows": [ { "class": "^Atril$|^Devhelp$|^Evince$|^Gvim$|^calibre$", "instance": "^atril$|^calibre\\-ebook\\-viewer$|^devhelp$|^evince$|^gvim$" } ], "type": "con", "marks": [ "swallowed4" ] } ], "mark": "doc" }
Screen layout (autorandr)
Screen layout relies on autorandr and a script to select from detected layouts.
The autorandr setup file relies on EDIDs in each configuration's setup file. The best source seems to be sysfs, but the card port number don't necessarily line up with the xorg outputs. To get a suitable hex string EDID I use (adjust card0-DP-0 to suit):
xxd -p /sys/class/drm/card0-DP-0/edid|tr -d '\n';echo -e
My script uses zenity to display simple dialog with all detected inputs. This is because complexity exploded with 4 displays, and it was no longer feasible to remember or fit all the screen configurations on a reasonable set of binds.
#!/bin/bash autorandr --match-edid --force --load "$(autorandr --match-edid --detected | sed 's/ - /\n/'| yad --list --title "Which layout?" --column "Description" --column "Layout" --separator=" - " --height 250 --width 220 | sed 's/ - $//')" return
The autorandr postswitch scripts still rely on my older support scripts for some things. These get output names from monitor serial numbers since outputs naming isn't idempotent.
#!/bin/sh # for i in /sys/class/graphics/fb0/device/drm/card0/*/edid; do edid-decode < $i; done|grep Product m7_serial="123456789" m7=$(~/.screenlayout/serial2output.sh "$m7_serial") export m7 u2515h_serial="123456789" u2515h=$(~/.screenlayout/serial2output.sh "$u2515h_serial") export u2515h # USB C input, may be different for HDMI magedok_9_serial="123456789" magedok_9=$(~/.screenlayout/serial2output.sh "$magedok_9_serial") export magedok_9 magedok_15_serial="123456789" magedok_15=$(~/.screenlayout/serial2output.sh "$magedok_15_serial") export magedok_15 # not currently used displays u2414h_one_serial="123456789" u2414h_one=$(~/.screenlayout/serial2output.sh "$u2414h_one_serial") export u2414h_one u2414h_two_serial="123456789" u2414h_two=$(~/.screenlayout/serial2output.sh "$u2414h_two_serial") export u2414h_two # one of two 11.6 1080p, probably the one with the vesa mount screwholes magedok_vesa_serial="123456789" magedok_vesa=$(~/.screenlayout/serial2output.sh "$magedok_vesa_serial") export magedok_vesa
#!/bin/bash # this probably came from: https://stackoverflow.com/a/24933353 while read -r output hex; do serial=$(echo "$hex" | xxd -r -p -) if [ "$1" = "$serial" ]; then echo "$output" exit fi done < <(xrandr --prop | awk ' !/^[ \t]/ { if (output && hex) print output, hex output=$1 hex="" } /[:.]/ && h { sub(/.*000000ff00/, "", hex) hex = substr(hex, 0, 26) "0a" sub(/0a.*/, "", hex) h=0 } h {sub(/[ \t]+/, ""); hex = hex $0} /EDID.*:/ {h=1} END {if (output && hex) print output, hex} ' | sort )
- autorandr postswitch scripts
Listing 17: ~/.config/autorandr/postswitch.d/1-set_xrandr_dpi#!/bin/sh # Just use whatever the DPI value is (if it is numeric) DPI=$(xrdb -query|grep Xft.dpi|cut -d ' ' -f 2) case "$DPI" in ''|*[!0-9]*) : ;; *) xrandr --dpi $DPI;; esac
Listing 18: ~/.config/autorandr/postswitch.d/3-restart_i3_fix_wallpaper#!/bin/sh sleep 1 i3-msg restart feh --no-fehbg --bg-fill "$(cat ~/.last_fehbg)"
Listing 19: ~/.config/autorandr/postswitch.d/7-assign_touch_inputs#!/bin/sh sleep 6 . ~/.screenlayout/screen_names.sh if xrandr --listactivemonitors | grep -q "$magedok_9"; then xinput enable "ILITEK ILITEK-TP" xinput map-to-output "ILITEK ILITEK-TP" "$magedok_9" else xinput disable "ILITEK ILITEK-TP" fi if xrandr --listactivemonitors | grep -q "$magedok_15"; then xinput enable "G2Touch Multi-Touch by G2TSP" xinput map-to-output "G2Touch Multi-Touch by G2TSP" "$magedok_15" else xinput disable "G2Touch Multi-Touch by G2TSP" fi if xrandr --listactivemonitors | grep -q "$magedok_vesa"; then xinput enable "N-trig DuoSense" xinput enable "N-trig DuoSense Mouse" xinput enable "N-trig DuoSense Stylus" xinput map-to-output "N-trig DuoSense" "$magedok_vesa" xinput map-to-output "N-trig DuoSense Mouse" "$magedok_vesa" xinput map-to-output "N-trig DuoSense Stylus" "$magedok_vesa" else xinput disable "N-trig DuoSense" xinput disable "N-trig DuoSense Mouse" xinput disable "N-trig DuoSense Stylus" fi
Move workspaces script
Script to move all workspaces to a particular display (excluding workspace 1). This is because I find taking away displays from i3 isn't always predictable. If I pre-move workspaces then I can disable monitors without annoying hiccups.
This script prompts the user for the target output using zenity.
#!/bin/bash if output=$(xrandr --current --query|grep " connected "|cut -d ' ' -f 1 | yad --list --title "Which output?" --column "Outputs" --separator ' '); then while read -r name; do i3-msg workspace "${name}" i3-msg move workspace to output "$output" done< <(i3-msg -t get_workspaces | jq -r '.[]|select (.num>1) | "\(.name)"') fi
Disable input device scripts
These are for the Thinkpad X280. Just simple toggles for the touchpad and touchscreen when I don't want them.
#!/bin/bash declare -i ID ID=`xinput list | grep -Eo 'Synaptics TM3288-003\s*id\=[0-9]{1,2}' | grep -Eo 'id\=[0-9]{1,2}'|grep -Eo '[0-9]{1,2}'` declare -i STATE STATE=`xinput list-props $ID|grep 'Device Enabled'|awk '{print $4}'` if [ $STATE -eq 1 ] then xinput disable $ID echo "Touchpad disabled." else xinput enable $ID echo "Touchpad enabled." fi
#!/bin/bash declare -i ID ID=`xinput list | grep -Eo 'Touchscreen\s*id\=[0-9]{1,2}' | grep -Eo 'id\=[0-9]{1,2}'|grep -Eo '[0-9]{1,2}'` declare -i STATE STATE=`xinput list-props $ID|grep 'Device Enabled'|awk '{print $4}'` if [ $STATE -eq 1 ] then xinput disable $ID echo "Touchscreen disabled." else xinput enable $ID echo "Touchscreen enabled." fi