├── README.md ├── common.py ├── config.example ├── goto ├── i3-exec ├── mark ├── nextfind ├── nextmatch ├── nextmatch-query-ask ├── nextmatch-query-saved ├── paste-primary-if-oneline ├── unmark └── vimb-bookmark /README.md: -------------------------------------------------------------------------------- 1 | i3-wm-scripts 2 | ============= 3 | 4 | i3 Window Manager Scripts 5 | 6 | These are python scripts that read/write to i3 using i3-msg. By using regular expressions it is possible to 7 | search for windows with particular names and jump to them. 8 | 9 | Dependencies: i3, i3-msg (distributed with i3), dmenu 10 | 11 | There are 4 scripts: nextmatch, nextfind, goto, mark 12 | 13 | ### nextmatch 14 | 15 | Syntax: nextmatch \ 16 | 17 | This script takes one regular expression input e.g. '(fire|chrom)' and searches for that name in the list of 18 | available windows. If there is a match then it jumps to the match. If you are already at a match it goes to the 19 | next match (or stays if there is only one) 20 | 21 | Example Binding: 22 | ``` 23 | bindsym $mod+q exec python ~/bin/nextmatch vim # cycle through vim sessions 24 | bindsym $mod+w exec python ~/bin/nextmatch '(chromium|firefox)' # cycle through browsers using regex 25 | ``` 26 | ### nextfind 27 | 28 | Syntax: nextfind 29 | 30 | This script works similarly to the previous but takes no arguments and instead 31 | provides a dmenu list of arguments. If you select one a window that is active 32 | and there are multiple with the same name then it jumps to the next one, 33 | otherwise it stays at the current. 34 | 35 | Example Binding: 36 | ``` 37 | bindsym $mod+e exec python ~/bin/nextfind # use dmenu to select an open window 38 | ``` 39 | 40 | ### mark/goto/unmark 41 | These scripts mark and jump to a specified script. I rarely use them, 42 | because I prefer the other two, but they can be useful if you are jumping to a 43 | lot of windows with the same name (like terminals). Basically you can mark the 44 | current window by calling mark (or binding it them using the key-combination). 45 | Then you can get a list of marked windows (similar to nextfind, but only 46 | with marked windows included) to jumpt to. 47 | 48 | Example bindings: 49 | ``` 50 | bindsym $mod+Shift+grave exec ~/bin/unmark # unmark the current window 51 | bindsym $mod+grave exec ~/bin/mark # mark the current window 52 | bindsym $mod+Tab exec exec ~/bin/goto # get dmenu of marked windows 53 | ``` 54 | 55 | ### Install 56 | To install just put the scripts in your path, or bind the scripts in you config 57 | file. I installed mine in ~/bin/, then made the bindings in my config file. 58 | 59 | -------------------------------------------------------------------------------- /common.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python3 2 | import json 3 | import subprocess 4 | from collections import defaultdict 5 | 6 | def get_tree(): 7 | """return i3 container tree""" 8 | data = subprocess.check_output(["i3-msg", "-t", "get_tree"]) 9 | data = json.loads(data.decode("UTF-8")) 10 | return data 11 | 12 | def get_named_windows(): 13 | """return (current-id, dict of (wm-class,window-name) to list of IDs)""" 14 | i3_tree = get_tree() 15 | assert i3_tree["name"] == "root", i3_tree["name"] 16 | queue = [i3_tree] 17 | current_id = None 18 | data = defaultdict(list) 19 | while queue: 20 | current = queue.pop() 21 | if current["layout"] == "dockarea": 22 | continue 23 | nodes = current.get("nodes") 24 | if nodes: 25 | queue.extend(nodes) 26 | continue 27 | if not current["geometry"]["width"]: 28 | continue 29 | if current.get("type") != "con": 30 | continue 31 | if current.get("focused"): 32 | current_id = current["id"] 33 | key = (current["window_properties"]["class"], current["name"].strip()) 34 | data[key].append(current["id"]) 35 | return current_id, data 36 | 37 | def cycle_selected(current, selected): 38 | """return next selected window""" 39 | if current in selected: 40 | selected.sort() 41 | idx = (selected.index(current) + 1) % len(selected) 42 | return selected[idx] 43 | return selected[0] 44 | 45 | def focus_window(id): 46 | subprocess.check_call(["i3-msg", "[con_id=\"{}\"] focus".format(id)]) 47 | -------------------------------------------------------------------------------- /config.example: -------------------------------------------------------------------------------- 1 | # This file has been auto-generated by i3-config-wizard(1). 2 | # It will not be overwritten, so edit it as you like. 3 | # 4 | # Should you change your keyboard layout somewhen, delete 5 | # this file and re-run i3-config-wizard(1). 6 | # 7 | 8 | # i3 config file (v4) 9 | # 10 | # Please see http://i3wm.org/docs/userguide.html for a complete reference! 11 | 12 | set $mod Mod4 13 | 14 | force_xinerama yes 15 | new_window none 16 | 17 | # font for window titles. ISO 10646 = Unicode 18 | #font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 19 | font -adobe-courier-medium-r-normal-*-14-*-100-100-m-90-iso10646-1 20 | 21 | # Use Mouse+$mod to drag floating windows to their wanted position 22 | floating_modifier $mod 23 | 24 | # start a terminal 25 | bindsym $mod+Return exec urxvtc 26 | 27 | #name/jump 28 | bindsym $mod+q exec python ~/bin/nextmatch.py vim 29 | bindsym $mod+w exec python ~/bin/nextmatch.py '(chromium|firefox)' 30 | bindsym $mod+e exec python ~/bin/nextfind.py 31 | bindsym $mod+m exec ~/bin/mark 32 | bindsym $mod+Shift+E exec ~/bin/goto 33 | 34 | # kill focused window 35 | bindsym $mod+Shift+C kill 36 | 37 | # start dmenu (a program launcher) 38 | bindsym $mod+r exec dmenu_run 39 | 40 | # change focus 41 | bindsym $mod+j focus down 42 | bindsym $mod+k focus up 43 | bindsym $mod+l focus right 44 | bindsym $mod+h focus left 45 | 46 | # alternatively, you can use the cursor keys: 47 | bindsym $mod+Left focus left 48 | bindsym $mod+Down focus down 49 | bindsym $mod+Up focus up 50 | bindsym $mod+Right focus right 51 | 52 | # move focused window 53 | bindsym $mod+Shift+K move up 54 | bindsym $mod+Shift+L move right 55 | bindsym $mod+Shift+J move down 56 | bindsym $mod+Shift+H move left 57 | 58 | # alternatively, you can use the cursor keys: 59 | bindsym $mod+Shift+Left move left 60 | bindsym $mod+Shift+Down move down 61 | bindsym $mod+Shift+Up move up 62 | bindsym $mod+Shift+Right move right 63 | 64 | 65 | # split in vertical orientation 66 | bindsym $mod+Shift+V split v 67 | bindsym $mod+Shift+G split h 68 | 69 | # enter fullscreen mode for the focused container 70 | bindsym $mod+f fullscreen 71 | 72 | # change container layout (stacked, tabbed, default) 73 | bindsym $mod+Shift+S layout stacking 74 | bindsym $mod+Shift+T layout tabbed 75 | bindsym $mod+Shift+D layout default 76 | 77 | # toggle tiling / floating 78 | bindsym $mod+Shift+space floating toggle 79 | 80 | # change focus between tiling / floating windows 81 | bindsym $mod+space focus mode_toggle 82 | 83 | # focus the parent container 84 | #bindsym $mod+a focus parent 85 | 86 | # focus the child container 87 | #bindcode $mod+d focus child 88 | 89 | # switch to workspace 90 | bindsym $mod+1 workspace 1 91 | bindsym $mod+2 workspace 2 92 | bindsym $mod+3 workspace 3 93 | bindsym $mod+4 workspace 4 94 | bindsym $mod+5 workspace 5 95 | bindsym $mod+6 workspace 6 96 | bindsym $mod+7 workspace 7 97 | bindsym $mod+8 workspace 8 98 | bindsym $mod+9 workspace 9 99 | bindsym $mod+0 workspace 10 100 | 101 | # move focused container to workspace 102 | bindsym $mod+Shift+exclam move workspace 1 103 | bindsym $mod+Shift+at move workspace 2 104 | bindsym $mod+Shift+numbersign move workspace 3 105 | bindsym $mod+Shift+dollar move workspace 4 106 | bindsym $mod+Shift+percent move workspace 5 107 | bindsym $mod+Shift+asciicircum move workspace 6 108 | bindsym $mod+Shift+ampersand move workspace 7 109 | bindsym $mod+Shift+asterisk move workspace 8 110 | bindsym $mod+Shift+parenleft move workspace 9 111 | bindsym $mod+Shift+parenright move workspace 10 112 | 113 | # reload the configuration file 114 | bindsym $mod+Shift+R reload 115 | # restart i3 inplace (preserves your layout/session, can be used to upgrade i3) 116 | #bindsym $mod+Shift+R restart 117 | # exit i3 (logs you out of your X session) 118 | bindsym $mod+Shift+Q exit 119 | 120 | bindsym $mod+z mode "resize" 121 | 122 | for_window [title="LONI Pipeline"] floating enable 123 | for_window [title="FSLView.*"] floating enable 124 | for_window [title="Execution Information"] floating enable 125 | for_window [title="Server changer"] floating enable 126 | for_window [class="Pidgin"] floating enable 127 | for_window [class="weka"] floating enable 128 | for_window [class="VirtualBox"] floating enable 129 | 130 | 131 | # resize window (you can also use the mouse for that) 132 | mode "resize" { 133 | # These bindings trigger as soon as you enter the resize mode 134 | 135 | # They resize the border in the direction you pressed, e.g. 136 | # when pressing left, the window is resized so that it has 137 | # more space on its left 138 | 139 | bindsym j resize shrink left 10 px or 10 ppt 140 | bindsym Shift+J resize grow left 10 px or 10 ppt 141 | 142 | bindsym k resize shrink down 10 px or 10 ppt 143 | bindsym Shift+K resize grow down 10 px or 10 ppt 144 | 145 | bindsym l resize shrink up 10 px or 10 ppt 146 | bindsym Shift+L resize grow up 10 px or 10 ppt 147 | 148 | bindsym semicolon resize shrink right 10 px or 10 ppt 149 | bindsym Shift+colon resize grow right 10 px or 10 ppt 150 | 151 | # same bindings, but for the arrow keys 152 | bindsym Left resize shrink left 10 px or 10 ppt 153 | bindsym Shift+Left resize grow left 10 px or 10 ppt 154 | 155 | bindsym Down resize shrink down 10 px or 10 ppt 156 | bindsym Shift+Down resize grow down 10 px or 10 ppt 157 | 158 | bindsym Up resize shrink up 10 px or 10 ppt 159 | bindsym Shift+Up resize grow up 10 px or 10 ppt 160 | 161 | bindsym Right resize shrink right 10 px or 10 ppt 162 | bindsym Shift+Right resize grow right 10 px or 10 ppt 163 | 164 | # back to normal: Enter or Escape 165 | bindsym Return mode "default" 166 | bindsym Escape mode "default" 167 | } 168 | 169 | 170 | # Start i3bar to display a workspace bar (plus the system information i3status 171 | # finds out, if available) 172 | bar { 173 | status_command i3status 174 | } 175 | -------------------------------------------------------------------------------- /goto: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ue 2 | # Tool for listing all the i3 windows in dmenu 3 | # 4 | # Usage: 5 | # 6 | # goto [dmenu args] 7 | # 8 | # Example 9 | # 10 | # goto -l 10 -b 11 | # 12 | 13 | name="$(i3-msg -t get_marks | tr -d '[],' | sed -e 's/""/\n/g' | tr -d '"' | dmenu -p jump "$@")" 14 | exec i3-msg "[con_mark=$name]" focus 15 | -------------------------------------------------------------------------------- /i3-exec: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ue 2 | # i3-exec COMMAND [ARG].. 3 | # 4 | # Tell i3 to start a command. The environment and other process state is inherited from i3, not i3-exec. 5 | # 6 | # The command is *not* a shell command, and thus redirections, globs, or other shell features will not work. If you want that, run "i3-msg exec". If you want your head to explode instead, run "i3-exec sh -c 'command'". 7 | # 8 | # Options: 9 | # --startup-id command uses $DESKTOP_STARTUP_ID 10 | 11 | fatal() { 12 | printf %s\\n "$(basename "$0"): $2" >&2 13 | exit $1 14 | } 15 | 16 | startup_id=--no-startup-id 17 | handle_option() { 18 | case "$1" in 19 | startup-id) [ $# = 1 ] || fatal 64 "unexpected --$1 value" 20 | startup_id= ;; 21 | no-startup-id) [ $# = 1 ] || fatal 64 "unexpected --$1 value" 22 | startup_id=--no-startup-id ;; 23 | *) fatal 64 "unknown option: $1" ;; 24 | esac 25 | } 26 | while [ $# -gt 0 ]; do 27 | case "$1" in 28 | --) 29 | shift 30 | break;; 31 | --*=*) 32 | x="${1#--}" 33 | handle_option "${x%%=*}" "${x#*=}" 34 | shift;; 35 | --*) 36 | handle_option "${1#--}" 37 | shift;; 38 | -?*) 39 | if [ ${#1} = 2 ]; then 40 | handle_option "${1#-}" 41 | else 42 | v="${1#??}" 43 | x="${1%"$v"}" 44 | handle_option "${x#-}" "$v" 45 | fi 46 | shift;; 47 | *) 48 | break;; 49 | esac 50 | done 51 | 52 | if [ $# = 0 ]; then 53 | fatal 64 'missing command' 54 | fi 55 | 56 | i3_exec_quote() { 57 | # for sh, wrap in single quotes with escaped existing single quote 58 | # for i3, escape double quotes 59 | printf %s "$1"x | sed "s/'/'\\\\''/g; 1s/^/'/; \$s/x\$/'/; s/\"/\\\\\"/g" 60 | } 61 | 62 | cmd=exec 63 | for arg; do 64 | cmd="$cmd $(i3_exec_quote "$arg")" 65 | done 66 | 67 | exec i3-msg -q "exec $startup_id \"cd $(i3_exec_quote "$PWD") && $cmd\"" 68 | -------------------------------------------------------------------------------- /mark: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ue 2 | name="$(i3-msg -t get_marks | tr -d '[],' | sed -e 's/""/\n/g' | tr -d '"' | dmenu -p mark)" 3 | exec i3-msg mark "$name" 4 | -------------------------------------------------------------------------------- /nextfind: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import re 5 | import subprocess 6 | import sys 7 | 8 | import common 9 | 10 | def iter_plus(it, extra): 11 | yield from it 12 | yield extra 13 | def joinlines(L): 14 | return "\n".join(iter_plus(L, "")) 15 | 16 | def select_window(windows, dmenu_args): 17 | """return (dmenu-output, list of IDs) for the selected option""" 18 | windows = sorted(windows.items(), key=lambda x: tuple(map(str.lower, x[0]))) 19 | options = [] 20 | for (wm_class, title), L in windows: 21 | mult = "" 22 | if len(L) > 1: 23 | mult = " x{}".format(len(L)) 24 | display = "({}{}) {}".format(wm_class, mult, title) 25 | options.append((display, L)) 26 | try: 27 | ret = subprocess.check_output(["dmenu"] + dmenu_args, input=joinlines(x for x, _ in options).encode("UTF-8")) 28 | except subprocess.CalledProcessError as e: 29 | sys.exit(e.returncode) 30 | ret = ret.decode("UTF-8").strip() 31 | selected = next(v for k, v in options if k == ret) 32 | return ret, selected 33 | 34 | def main(args): 35 | current, windows = common.get_named_windows() 36 | if not windows: 37 | return 38 | query, selected = select_window(windows, args) 39 | common.focus_window(common.cycle_selected(current, selected)) 40 | with open(os.path.dirname(__file__) + "/.query.tmp", "w") as f: 41 | f.write("^" + re.escape(query.partition(") ")[2]) + "$") 42 | 43 | if __name__ == "__main__": 44 | sys.exit(main(sys.argv[1:])) 45 | -------------------------------------------------------------------------------- /nextmatch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import re 3 | import string 4 | import sys 5 | 6 | import common 7 | 8 | def search_window(windows, expr): 9 | flags = re.I 10 | for x in expr: 11 | if x in string.ascii_uppercase: 12 | flags = 0 13 | break 14 | regex = re.compile(expr, re.I) 15 | matches = [] 16 | for (wm_class, name), L in windows.items(): 17 | print(wm_class) 18 | if regex.match(wm_class) or regex.search(name): 19 | matches.extend(L) 20 | return matches 21 | 22 | def main(args): 23 | if len(args) != 1: 24 | return "usage: nextmatch [TEXT]" 25 | current, windows = common.get_named_windows() 26 | matches = search_window(windows, args[0]) 27 | print(matches) 28 | if matches: 29 | common.focus_window(common.cycle_selected(current, matches)) 30 | 31 | if __name__ == "__main__": 32 | sys.exit(main(sys.argv[1:])) 33 | -------------------------------------------------------------------------------- /nextmatch-query-ask: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ue 2 | q=$(dmenu -p /) 3 | here="$(dirname "$0")" 4 | printf %s "$q" >"$here/.query.tmp" 5 | exec "$here/nextmatch" "$q" 6 | -------------------------------------------------------------------------------- /nextmatch-query-saved: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ue 2 | here="$(dirname "$0")" 3 | q=$(cat "$here/.query.tmp") || true 4 | echo "q: $q" 5 | if [ -z "$q" ]; then 6 | q=$(dmenu -p '(empty) /') 7 | printf %s "$q" >"$here/.query.tmp" 8 | fi 9 | exec "$here/nextmatch" "$q" 10 | -------------------------------------------------------------------------------- /paste-primary-if-oneline: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ue 2 | if [ -z "$(xsel | head -n2 | tail -n+2 | head -n1)" ]; then 3 | xsel | xvkbd -xsendevent -file - 2>/dev/null 4 | fi 5 | -------------------------------------------------------------------------------- /unmark: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ue 2 | name="$(i3-msg -t get_marks | tr -d '[],' | sed -e 's/""/\n/g' | tr -d '"' | dmenu -p unmark)" 3 | if [ -z "$name" ]; then 4 | exit 1 5 | fi 6 | exec i3-msg unmark "$name" 7 | -------------------------------------------------------------------------------- /vimb-bookmark: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # vimb-bookmark [dmenu-command] 3 | # 4 | # Open dmenu-selected URLs from vimb bookmarks. 5 | # 6 | # Note: dmenu must be line-buffered (eg. "stdbuf --output=L dmenu ..." on Linux) if you want multiple selections (ctrl+return) to start immediately. 7 | 8 | import os 9 | import re 10 | import subprocess 11 | import sys 12 | 13 | def get_bookmark_lines(filename): 14 | with open(filename) as f: 15 | for line in f: 16 | line = line.rstrip() 17 | if line.startswith("#"): 18 | continue 19 | if not line: 20 | yield "" 21 | continue 22 | line = re.sub(" {2,}", " ", line) 23 | 24 | url, _, rest = line.partition("\t") 25 | url = url.strip() 26 | title, _, tags = rest.partition("\t") 27 | title = title.strip() 28 | tags = tags.strip().split(" ") 29 | 30 | if title: 31 | out = title + " " 32 | else: 33 | out = "" 34 | if tags: 35 | out += " ".join("#" + x for x in tags) 36 | yield "{} ({})".format(out, url) 37 | 38 | def main(args): 39 | if not args: 40 | args = ["dmenu"] 41 | filename = (os.environ.get("XDG_CONFIG_HOME") or os.environ["HOME"] + "/.config") + "/vimb/bookmark" 42 | with popen(args) as dmenu: 43 | for line in get_bookmark_lines(filename): 44 | dmenu.stdin.write(line + "\n") 45 | dmenu.stdin.close() 46 | seen = {""} 47 | for line in iter(dmenu.stdout.readline, ""): 48 | url = line.rstrip() 49 | if " (" in url: 50 | url = url.partition(" (")[2][:-1] 51 | if url not in seen: 52 | seen.add(url) 53 | os.spawnvp(os.P_NOWAIT, "vimb", ["vimb", url]) 54 | 55 | def popen(*args, **kwds): 56 | assert "stderr" not in kwds 57 | return subprocess.Popen(*args, bufsize=1, universal_newlines="\n", stdin=subprocess.PIPE, stdout=subprocess.PIPE, **kwds) 58 | 59 | if __name__ == "__main__": 60 | sys.exit(main(sys.argv[1:])) 61 | --------------------------------------------------------------------------------