i3-bind 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
i3_bind-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: i3-bind
3
+ Version: 1.0.0
4
+ Summary: Add, remove, and list i3 keybindings from the command line
5
+ Author-email: "@readwith" <talwrii@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/talwrii/i3-bind
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: Operating System :: POSIX :: Linux
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Desktop Environment
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+
16
+ # i3-bind
17
+ Explore and create bindings for the i3wm window manager.
18
+
19
+ Unreviewed AI-generated code. ButI use it.
20
+
21
+ ## Alternatives and prior work
22
+ You could edit `~/.config/i3/config` by hand or use `grep` on it. However, I am lazy. I could find now tool providing this functionality.
23
+
24
+ I made a tool called kde-bind which is this tool or kde. `i3keys-gui` which provides a gui for exploring bindings.
25
+
26
+ ## Installation
27
+ `pipx install i3-bind`
28
+
29
+ ## Usage
30
+ List bindings:
31
+
32
+ `i3-bind`
33
+
34
+ Create a binding:
35
+
36
+ `i3-bind meta+shift+n gtk-launch focus-obsidina.desktop`
37
+
38
+ Run reload after you run the command. I have a keybinding in i3 for reload.
39
+
40
+
41
+ ## About
42
+ I am @readwith. I make tools for reading research and agency with and without AI. You can follow me on [X](https://x.com/readwithai) or my [blog](https://readwithai.substack.com/).
43
+
44
+ I also produce a stream of small tools like this. If that sounds interesting I suggest following me on X.
45
+
46
+
@@ -0,0 +1,31 @@
1
+ # i3-bind
2
+ Explore and create bindings for the i3wm window manager.
3
+
4
+ Unreviewed AI-generated code. ButI use it.
5
+
6
+ ## Alternatives and prior work
7
+ You could edit `~/.config/i3/config` by hand or use `grep` on it. However, I am lazy. I could find now tool providing this functionality.
8
+
9
+ I made a tool called kde-bind which is this tool or kde. `i3keys-gui` which provides a gui for exploring bindings.
10
+
11
+ ## Installation
12
+ `pipx install i3-bind`
13
+
14
+ ## Usage
15
+ List bindings:
16
+
17
+ `i3-bind`
18
+
19
+ Create a binding:
20
+
21
+ `i3-bind meta+shift+n gtk-launch focus-obsidina.desktop`
22
+
23
+ Run reload after you run the command. I have a keybinding in i3 for reload.
24
+
25
+
26
+ ## About
27
+ I am @readwith. I make tools for reading research and agency with and without AI. You can follow me on [X](https://x.com/readwithai) or my [blog](https://readwithai.substack.com/).
28
+
29
+ I also produce a stream of small tools like this. If that sounds interesting I suggest following me on X.
30
+
31
+
File without changes
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ i3-bind — explore and create i3 keybindings from the command line.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import re
9
+ import shutil
10
+ import subprocess
11
+ import sys
12
+ import argparse
13
+ from pathlib import Path
14
+ from typing import Optional
15
+
16
+ # ── Config discovery ──────────────────────────────────────────────────────────
17
+
18
+ CONFIG_PATHS = [
19
+ Path.home() / ".config/i3/config",
20
+ Path.home() / ".i3/config",
21
+ Path("/etc/i3/config"),
22
+ ]
23
+
24
+
25
+ def find_config(override=None):
26
+ if override:
27
+ p = Path(override)
28
+ if not p.exists():
29
+ die(f"config not found: {p}")
30
+ return p
31
+ for p in CONFIG_PATHS:
32
+ if p.exists():
33
+ return p
34
+ die("no i3 config found")
35
+
36
+
37
+ def die(msg):
38
+ print(f"i3-bind: {msg}", file=sys.stderr)
39
+ sys.exit(1)
40
+
41
+
42
+ # ── Parsing ───────────────────────────────────────────────────────────────────
43
+
44
+ def get_mod_var(config_path):
45
+ for line in config_path.read_text().splitlines():
46
+ m = re.match(r'^set\s+\$mod\s+(\S+)', line)
47
+ if m:
48
+ return m.group(1)
49
+ return "Mod4"
50
+
51
+
52
+ def iter_bindings(config_path):
53
+ mod_var = get_mod_var(config_path)
54
+ current_mode = "default"
55
+ brace_depth = 0
56
+
57
+ for raw in config_path.read_text().splitlines():
58
+ line = raw.strip()
59
+ if line.startswith("# "):
60
+ continue
61
+ m = re.match(r'^mode\s+["\']?([^"\'{}\\s]+)["\']?\s*{?', line)
62
+ if m and not line.startswith("bindsym"):
63
+ current_mode = m.group(1)
64
+ if "{" in line:
65
+ brace_depth += 1
66
+ continue
67
+ opens = line.count("{")
68
+ closes = line.count("}")
69
+ if opens and not re.match(r'^mode\b', line):
70
+ brace_depth += opens
71
+ if closes:
72
+ brace_depth -= closes
73
+ if brace_depth <= 0:
74
+ brace_depth = 0
75
+ current_mode = "default"
76
+ continue
77
+ m = re.match(r'^bindsym\s+(\S+)\s+(.*)', line)
78
+ if m:
79
+ key = m.group(1).replace("$mod", mod_var)
80
+ yield current_mode, key, m.group(2).strip()
81
+
82
+
83
+ # ── Config writing ────────────────────────────────────────────────────────────
84
+
85
+ def backup(config_path):
86
+ shutil.copy2(config_path, config_path.with_suffix(".bak"))
87
+
88
+
89
+ def do_add(key, command, mode, config_path):
90
+ text = config_path.read_text()
91
+ line = f"bindsym {key} {command}\n"
92
+ backup(config_path)
93
+ if mode == "default":
94
+ text = text.rstrip("\n") + "\n\n" + line
95
+ else:
96
+ pattern = re.compile(
97
+ r'(^mode\s+["\']?' + re.escape(mode) + r'["\']?\s*\{[^}]*)(\})',
98
+ re.MULTILINE | re.DOTALL
99
+ )
100
+ m = pattern.search(text)
101
+ if not m:
102
+ die(f"mode '{mode}' not found in config")
103
+ text = text[:m.start(2)] + " " + line + text[m.start(2):]
104
+ config_path.write_text(text)
105
+
106
+
107
+ def do_delete(key, mode, config_path):
108
+ mod_var = get_mod_var(config_path)
109
+ lines = config_path.read_text().splitlines(keepends=True)
110
+ current_mode = "default"
111
+ brace_depth = 0
112
+ target = None
113
+
114
+ for i, raw in enumerate(lines):
115
+ line = raw.strip()
116
+ m = re.match(r'^mode\s+["\']?([^"\'{}\\s]+)["\']?\s*{?', line)
117
+ if m and not line.startswith("bindsym"):
118
+ current_mode = m.group(1)
119
+ if "{" in line:
120
+ brace_depth += 1
121
+ continue
122
+ opens = line.count("{")
123
+ closes = line.count("}")
124
+ if opens and not re.match(r'^mode\b', line):
125
+ brace_depth += opens
126
+ if closes:
127
+ brace_depth -= closes
128
+ if brace_depth <= 0:
129
+ brace_depth = 0
130
+ current_mode = "default"
131
+ continue
132
+ m = re.match(r'^bindsym\s+(\S+)', line)
133
+ if m and current_mode == mode:
134
+ if m.group(1).replace("$mod", mod_var) == key:
135
+ target = i
136
+ break
137
+
138
+ if target is None:
139
+ return False
140
+ backup(config_path)
141
+ lines[target] = "# [deleted] " + lines[target]
142
+ config_path.write_text("".join(lines))
143
+ return True
144
+
145
+
146
+ def reload_i3():
147
+ subprocess.Popen(["i3-msg", "reload"],
148
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
149
+
150
+
151
+ # ── Commands ──────────────────────────────────────────────────────────────────
152
+
153
+ def cmd_list(args):
154
+ config = find_config(args.config)
155
+ for mode, key, command in iter_bindings(config):
156
+ if args.mode and args.mode != "default" and mode != args.mode:
157
+ continue
158
+ if args.mode and args.mode != "default":
159
+ print(f"{key}\t{command}")
160
+ else:
161
+ print(f"{mode}\t{key}\t{command}")
162
+
163
+
164
+ def cmd_add(args):
165
+ config = find_config(args.config)
166
+ do_add(args.key, args.command, args.mode, config)
167
+ suffix = f" (mode: {args.mode})" if args.mode != "default" else ""
168
+ print(f"added: bindsym {args.key} {args.command}{suffix}")
169
+ if not args.no_reload:
170
+ reload_i3()
171
+
172
+
173
+ def cmd_delete(args):
174
+ config = find_config(args.config)
175
+ ok = do_delete(args.key, args.mode, config)
176
+ if not ok:
177
+ suffix = f" in mode {args.mode}" if args.mode != "default" else ""
178
+ die(f"binding not found: {args.key}{suffix}")
179
+ suffix = f" (mode: {args.mode})" if args.mode != "default" else ""
180
+ print(f"deleted: {args.key}{suffix}")
181
+ if not args.no_reload:
182
+ reload_i3()
183
+
184
+
185
+ def cmd_modes(args):
186
+ config = find_config(args.config)
187
+ seen = dict.fromkeys(mode for mode, *_ in iter_bindings(config))
188
+ for m in seen:
189
+ print(m)
190
+
191
+
192
+ # ── Entry point ───────────────────────────────────────────────────────────────
193
+
194
+ def main():
195
+ parser = argparse.ArgumentParser(
196
+ prog="i3-bind",
197
+ description="Explore and create i3 keybindings from the command line.")
198
+ parser.add_argument("--config", default=None)
199
+ parser.add_argument("--no-reload", action="store_true")
200
+ parser.add_argument("--mode", default="default",
201
+ help="i3 mode (default: default)")
202
+
203
+ sub = parser.add_subparsers(dest="command")
204
+
205
+ p_add = sub.add_parser("add", help="Add a keybinding")
206
+ p_add.add_argument("key")
207
+ p_add.add_argument("command")
208
+ p_add.set_defaults(func=cmd_add)
209
+
210
+ p_del = sub.add_parser("delete", aliases=["del", "rm"])
211
+ p_del.add_argument("key")
212
+ p_del.set_defaults(func=cmd_delete)
213
+
214
+ p_list = sub.add_parser("list", aliases=["ls"])
215
+ p_list.set_defaults(func=cmd_list)
216
+
217
+ p_modes = sub.add_parser("modes")
218
+ p_modes.set_defaults(func=cmd_modes)
219
+
220
+ args = parser.parse_args()
221
+ if args.command is None:
222
+ cmd_list(args)
223
+ else:
224
+ args.func(args)
225
+
226
+
227
+ if __name__ == "__main__":
228
+ main()
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: i3-bind
3
+ Version: 1.0.0
4
+ Summary: Add, remove, and list i3 keybindings from the command line
5
+ Author-email: "@readwith" <talwrii@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/talwrii/i3-bind
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: Operating System :: POSIX :: Linux
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Desktop Environment
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+
16
+ # i3-bind
17
+ Explore and create bindings for the i3wm window manager.
18
+
19
+ Unreviewed AI-generated code. ButI use it.
20
+
21
+ ## Alternatives and prior work
22
+ You could edit `~/.config/i3/config` by hand or use `grep` on it. However, I am lazy. I could find now tool providing this functionality.
23
+
24
+ I made a tool called kde-bind which is this tool or kde. `i3keys-gui` which provides a gui for exploring bindings.
25
+
26
+ ## Installation
27
+ `pipx install i3-bind`
28
+
29
+ ## Usage
30
+ List bindings:
31
+
32
+ `i3-bind`
33
+
34
+ Create a binding:
35
+
36
+ `i3-bind meta+shift+n gtk-launch focus-obsidina.desktop`
37
+
38
+ Run reload after you run the command. I have a keybinding in i3 for reload.
39
+
40
+
41
+ ## About
42
+ I am @readwith. I make tools for reading research and agency with and without AI. You can follow me on [X](https://x.com/readwithai) or my [blog](https://readwithai.substack.com/).
43
+
44
+ I also produce a stream of small tools like this. If that sounds interesting I suggest following me on X.
45
+
46
+
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ i3_bind/__init__.py
4
+ i3_bind/main.py
5
+ i3_bind.egg-info/PKG-INFO
6
+ i3_bind.egg-info/SOURCES.txt
7
+ i3_bind.egg-info/dependency_links.txt
8
+ i3_bind.egg-info/entry_points.txt
9
+ i3_bind.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ i3-bind = i3_bind.main:main
@@ -0,0 +1 @@
1
+ i3_bind
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = [ "setuptools>=68.0",]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "i3-bind"
7
+ version = "1.0.0"
8
+ description = "Add, remove, and list i3 keybindings from the command line"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ classifiers = [ "Development Status :: 3 - Alpha", "Environment :: Console", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3", "Topic :: Desktop Environment",]
13
+ [[project.authors]]
14
+ name = "@readwith"
15
+ email = "talwrii@gmail.com"
16
+
17
+ [project.urls]
18
+ Homepage = "https://github.com/talwrii/i3-bind"
19
+
20
+ [project.scripts]
21
+ i3-bind = "i3_bind.main:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+