-
Notifications
You must be signed in to change notification settings - Fork 0
/
wlctrl.py
executable file
·161 lines (142 loc) · 4.47 KB
/
wlctrl.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
156
157
158
159
160
161
#!/usr/bin/env python3
from wl_framework.network.connection import (
WaylandConnection,
WaylandDisconnected
)
from wl_framework.protocols.foreign_toplevel import ForeignTopLevel
class WlCtrl(WaylandConnection):
EXIT_OK, \
EXIT_ARG_ERROR, \
EXIT_NO_MATCH, \
EXIT_MULTIPLE_MATCHES = range(4)
def __init__(self, sys_args, *args, **kwargs):
if not sys_args:
self.action = 'list'
else:
actions = {
'focus': ('activate', None),
'activate': ('activate', None),
'maximize': ('set_maximize', (True,)),
'minimize': ('set_minimize', (True,)),
'fullscreen': ('set_fullscreen', (True,)),
'unmaximize': ('set_maximize', (False,)),
'unminimize': ('set_minimize', (False,)),
'unfullscreen': ('set_fullscreen', (False,)),
'close': ('close', tuple())
}
self.target = sys_args[0]
self.action = actions.get(sys_args[1])
if (
not self.action
or not self.target
or self.target[0] not in '#@=:'
or len(self.target) < 2
):
raise RuntimeError(usage)
super().__init__(*args, **kwargs)
self.return_code = WlCtrl.EXIT_OK
def quit(self, data=None):
self.shutdown()
def on_initial_sync(self, data):
super().on_initial_sync(data)
if self.action[0] == 'activate':
# As we are subclassing the connection itself
# we have to patch the action here because
# the seat is only set in this very function.
self.action = ('activate', (self.display.seat,))
self.toplevels = ForeignTopLevel(self)
# ForeignTopLevel will .bind() in its constructor
# which will then cause the server to send all of
# the initial toplevel states. Thus we just wait
# for that to happen by queueing a callback and
# then looping over the results.
self.sync(self.info_done)
def info_done(self, data):
if self.action == 'list':
self._print_list()
self.quit()
return
target_matches = {
'#': lambda window, target : int(target[1:]) == window.obj_id,
'@': lambda window, target : target[1:].lower() == window.app_id.lower(),
'=': lambda window, target : target[1:].lower() == window.title.lower(),
':': lambda window, target : target[1:].lower() in window.title.lower()
}.get(self.target[0])
windows_found = list()
for window in self.toplevels.windows.values():
if target_matches(window, self.target):
windows_found.append(window)
if not windows_found:
print(f"No window matches target {self.target}")
self.return_code = WlCtrl.EXIT_NO_MATCH
self.quit()
return
if len(windows_found) > 1:
print(f"Found multiple windows. Not doing anything.")
self._print_list(windows_found)
self.return_code = WlCtrl.EXIT_MULTIPLE_MATCHES
self.quit()
return
func_name, func_args = self.action
func = getattr(windows_found[0], func_name)
func(*func_args)
# Wait for roundtrip to return before closing the connection
self.sync(self.quit)
def _print_list(self, windows=None):
if windows is None:
windows = self.toplevels.windows.values()
if not windows:
print("No windows opened")
self.quit()
return
class d:
obj_id = ' Handle'
app_id = ' AppID'
title = ' Title'
states = ''
handle_max = max(len(d.obj_id), max(len(str(x.obj_id)) for x in windows))
app_id_max = max(len(d.app_id), max(len(x.app_id) for x in windows))
title_max = max(len(d.title), max(len(x.title) for x in windows))
fmt = " {0.obj_id:{1}} {0.app_id:{2}} {0.title:{3}} {0.states}"
print()
print(fmt.format(d, handle_max, app_id_max, title_max))
print(" {:-<{}} {:-^{}} {:->{}}".format('', handle_max, '', app_id_max, '', title_max))
for window in windows:
print(fmt.format(window, handle_max, app_id_max, title_max))
print()
if __name__ == '__main__':
import sys
from wl_framework.loop_integrations import PollIntegration
usage = \
f"""
Usage: {sys.argv[0]} [<window-handle> <action>]
Without arguments: List windows
<window-handle> should be one of:
#handle match handle
@app_id match app_id
=title match title
:title match part of title
Warning: #handle might be reused or differ completely on the next call!
<action> should be one of:
activate | focus
close
maximize
minimize
fullscreen
unmaximize
unminimize
unfullscreen
"""
if len(sys.argv) not in (1, 3):
print(usage)
sys.exit(1)
loop = PollIntegration()
try:
app = WlCtrl(sys.argv[1:], eventloop_integration=loop)
except RuntimeError as e:
print(e)
sys.exit(WlCtrl.EXIT_ARG_ERROR)
try:
loop.run()
except WaylandDisconnected:
sys.exit(app.return_code)