-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathswaymonad.py
executable file
·156 lines (122 loc) · 4.86 KB
/
swaymonad.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#!/usr/bin/env python3
import argparse
from collections.abc import Callable, Iterator
import functools
import itertools
import logging
import shlex
import sys
import traceback
import time
try:
from typing import Concatenate, ParamSpec
except ImportError:
from typing_extensions import Concatenate, ParamSpec
import i3ipc
import common
import cycle_windows
import layout
import master_operations
import n_col
import nop_layout
import transformations
argparser = argparse.ArgumentParser(description='An xmonad-like auto-tiler for sway.')
argparser.add_argument('--default-layout', default="tall",
help="Layout to use for workspaces where the layout has not been manually set."
"Valid options are 'tall', '3_col', and 'nop'.")
argparser.add_argument('--verbose', "-v", action="count", help="Enable debug logging.")
argparser.add_argument('--log-file', help="Log file path, defaults to stderr.")
argparser.add_argument('--delay', default=0.0, type=float,
help=("Sleep for n seconds before sending every command to sway, "
"allowing a human to observe intermediate state,"))
args = argparser.parse_args()
P = ParamSpec("P")
Command = Callable[Concatenate[i3ipc.Connection, i3ipc.Event, P], None]
COMMANDS: dict[str, Command] = {
"promote_window": master_operations.promote_window,
"focus_master": master_operations.focus_master,
"resize_master": master_operations.resize_master,
"reflectx": layout.reflectx_dispatcher,
"reflecty": layout.reflecty_dispatcher,
"transpose": layout.transpose_dispatcher,
"focus_next_window": cycle_windows.focus_next_window,
"focus_prev_window": cycle_windows.focus_prev_window,
"swap_with_next_window": cycle_windows.swap_with_next_window,
"swap_with_prev_window": cycle_windows.swap_with_prev_window,
"set_layout": layout.set_layout,
"increment_masters": layout.increment_masters_dispatcher,
"decrement_masters": layout.decrement_masters_dispatcher,
"move": layout.move_dispatcher,
"fullscreen": layout.fullscreen_dispatcher,
}
def parse_binding(event: i3ipc.Event) -> Iterator[list[str]]:
logging.debug(f"Parsing command: {event.binding.command}")
split_commands = shlex.split(event.binding.command)
delims = ';,'
for _, group in itertools.groupby(split_commands, key=lambda s: s in delims):
group = list(group)
if group[0] == 'nop':
yield group[1:]
def command_dispatcher(i3: i3ipc.Connection, event: i3ipc.Event):
logging.debug(f"Receved command event: {event.ipc_data}")
commands = list(parse_binding(event))
logging.debug(f"Parsed commands: {commands}")
if not commands:
return
try:
i3.enable_command_buffering()
for command in commands:
COMMANDS.get(command[0], lambda i3, event, *args: None)(i3, event, *command[1:])
i3.disable_command_buffering()
except Exception as ex:
traceback.print_exc()
layout.LAYOUTS.update({
"tall": functools.partial(n_col.NCol, n_columns=2),
"3_col": functools.partial(n_col.NCol, n_columns=3),
"nop": nop_layout.Nop,
})
class Connection(i3ipc.Connection):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.buffering_commands = False
self.command_buffer: list[str] = []
def command(self, payload: str) -> list[i3ipc.CommandReply]:
if self.buffering_commands:
logging.debug(f"Buffering command: {payload}", stacklevel=2)
self.command_buffer.append(payload)
return []
logging.debug(f"Executing command: {payload}", stacklevel=2)
time.sleep(args.delay)
return super().command(payload)
def enable_command_buffering(self) -> None:
self.buffering_commands = True
def disable_command_buffering(self) -> list[i3ipc.CommandReply]:
self.buffering_commands = False
if not self.command_buffer:
return []
command = ";".join(self.command_buffer)
self.command_buffer = []
return self.command(command)
def get_tree(self) -> i3ipc.Con:
# TODO: handle returned errors
self.disable_command_buffering()
tree = super().get_tree()
self.enable_command_buffering()
return tree
def get_workspaces(self) -> list[i3ipc.replies.WorkspaceReply]:
# TODO: handle returned errors
self.disable_command_buffering()
workspaces = super().get_workspaces()
self.enable_command_buffering()
return workspaces
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG if args.verbose else logging.WARNING,
filename=args.log_file,
format='%(asctime)s, %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s')
layout.DEFAULT_LAYOUT = args.default_layout
i3 = Connection()
i3.on(i3ipc.Event.BINDING, command_dispatcher)
i3.on(i3ipc.Event.WINDOW_NEW, layout.layout_dispatcher)
i3.on(i3ipc.Event.WINDOW_CLOSE, layout.layout_dispatcher)
i3.on(i3ipc.Event.WINDOW_MOVE, layout.layout_dispatcher)
i3.main()