-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy path03-basic-wm.pl6
353 lines (314 loc) · 11.1 KB
/
03-basic-wm.pl6
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# basic_wm example ported from https://github.com/jichu4n/basic_wm
use X11::Xlib::Raw;
use NativeCall;
use NativeHelpers::Pointer;
use X11::Xlib::Raw::X;
use X11::Xlib::Raw::keysym;
class WindowManager { ... };
sub MAIN {
my $display = XOpenDisplay("") or die 'Cannot open display';
WindowManager.new(display => $display).run;
}
class WindowManager {
has X11::Xlib::Raw::Display $.display;
has %!clients;
method run {
$.become-wm or exit 1;
$.set-error-handler;
$.frame-existing-windows;
$.event-loop;
}
method become-wm {
note "Requesting to become WM on " ~ XDisplayName("");
my Bool $another_wm_detected;
# Temporary error handler while we attempt to become WM for this display
XSetErrorHandler(-> $disp, $error {
# In the case of an already running window manager, the error code from
# XSelectInput is BadAccess. We don't expect this handler to receive any
# other errors.
$error.error_code == BadAccess or note "Unexpected error";
note "Got error while requesting to become WM: " ~ $error.gist;
$another_wm_detected = True;
} );
# Stake the claim on needed events
XSelectInput($.display, $.display.DefaultRootWindow, SubstructureRedirectMask +| SubstructureNotifyMask);
# Since Xlib buffers requests, we need to manually sync to see if XSelectInput succeeded
XSync($.display, False);
if $another_wm_detected {
note "Detected another window manager on display " ~ XDisplayString($.display);
return False;
} else {
note "Ok, We are the WM";
return True;
}
}
method set-error-handler {
# Just dump any errors to console
XSetErrorHandler(-> $disp, $error {
note "Got error: " ~ $error.gist;
return 0; # ignored anyways
} );
}
method frame-existing-windows {
# 1. Grab X server to prevent windows from changing under us.
XGrabServer($.display);
# 2. Reparent existing top-level windows.
# i. Query existing top-level windows.
my $top_level_windows := Pointer[Window].new;
XQueryTree($.display, $.display.DefaultRootWindow, my Window $root_return, my Window $parent, $top_level_windows, my uint32 $num_top_level_windows);
# ii. Frame each top-level window.
for 0..^$num_top_level_windows -> $i {
my $window = ($top_level_windows + $i).deref;
$.frame( $$window );
}
# ii. Free top-level window array.
XFree($top_level_windows);
# 3. Ungrab X server.
XUngrabServer($.display);
}
method frame(Window $w) {
note "Framing $w";
# Visual properties of the frame to create.
constant BORDER_WIDTH = 3;
constant BORDER_COLOR = 0xff0000;
constant BG_COLOR = 0x0000ff;
if %!clients{ $w }:exists {
note "Already have a frame for $w";
return;
}
# 1. Retrieve attributes of window to frame.
my XWindowAttributes $x_window_attrs .= new;
XGetWindowAttributes($.display, $w, $x_window_attrs);
# 2. Create frame.
my Window $frame = XCreateSimpleWindow(
$.display,
$.display.DefaultRootWindow,
$x_window_attrs.x,
$x_window_attrs.y,
$x_window_attrs.width,
$x_window_attrs.height,
BORDER_WIDTH,
BORDER_COLOR,
BG_COLOR
);
# 3. Select events on frame.
XSelectInput($.display, $frame, SubstructureRedirectMask +| SubstructureNotifyMask);
# 4. Add client to save set, so that it will be restored and kept alive if we crash.
XAddToSaveSet($.display, $w);
# 5. Reparent client window.
XReparentWindow($.display, $w, $frame, 0, 0);
# 6. Map frame.
XMapWindow($.display, $frame);
# 7. Save frame handle.
%!clients{ $w } = $frame;
# 8. Grab universal window management actions on client window.
# a. Move windows with alt + left button.
XGrabButton(
$.display,
Button1,
Mod1Mask,
$w,
False,
ButtonPressMask +| ButtonReleaseMask +| ButtonMotionMask,
GrabModeAsync,
GrabModeAsync,
None,
None) or note "Unable to Grab Window";
# b. Resize windows with alt + right button.
XGrabButton(
$.display,
Button3,
Mod1Mask,
$w,
False,
ButtonPressMask +| ButtonReleaseMask +| ButtonMotionMask,
GrabModeAsync,
GrabModeAsync,
None,
None);
# c. Kill windows with alt + f4.
XGrabKey(
$.display,
XKeysymToKeycode($.display, XK_F4),
Mod1Mask,
$w,
False,
GrabModeAsync,
GrabModeAsync);
# d. Switch windows with alt + tab.
XGrabKey(
$.display,
XKeysymToKeycode($.display, XK_Tab),
Mod1Mask,
$w,
False,
GrabModeAsync,
GrabModeAsync);
note "Framed window $w [$frame]";
}
#| Main event loop.
method event-loop {
my XEvent $e .= new;
note '/* event loop */';
loop {
# 1. Get next event.
XNextEvent($.display, $e);
note "Received Event: ", $e;
# 2. Dispatch event.
given $e.type {
# when CreateNotify {
# OnCreateNotify($e.xcreatewindow);
# }
# when DestroyNotify {
# OnDestroyNotify($e.xdestroywindow);
# }
# when ReparentNotify {
# OnReparentNotify($e.xreparent);
# }
# when MapNotify {
# OnMapNotify($e.xmap);
# }
when UnmapNotify {
# If the window is a client window we manage, unframe it upon UnmapNotify. We
# need the check because other than a client window, we can receive an
# UnmapNotify for
# - A frame we just destroyed ourselves.
# - A pre-existing and mapped top-level window we reparented.
if ! ( %!clients{ $e.xunmap.window }:exists ) {
note "Ignore UnmapNotify for non-client window {$e.xunmap.window}";
note %!clients;
next;
}
if $e.xunmap.event == $.display.DefaultRootWindow {
note "Ignore UnmapNotify for reparented pre-existing window {$e.xunmap.window}";
next;
}
$.unframe($e.xunmap.window);
}
# when ConfigureNotify {
# OnConfigureNotify($e.xconfigure);
# }
when MapRequest {
# 1. Frame or re-frame window.
$.frame($e.xmaprequest.window);
# 2. Actually map window.
XMapWindow($.display, $e.xmaprequest.window);
}
# when ConfigureRequest {
# OnConfigureRequest($e.xconfigurerequest);
# }
when ButtonPress {
# CHECK(clients_.count($e.xbutton.window));
my $frame = %!clients{ $e.xbutton.window };
# # 1. Save initial cursor position.
# drag_start_pos_ = Position<int>($e.xbutton.x_root, $e.xbutton.y_root);
#
# # 2. Save initial window info.
# Window returned_root;
# int x, y;
# unsigned width, height, border_width, depth;
# CHECK(XGetGeometry(
# $.display,
# frame,
# &returned_root,
# &x, &y,
# &width, &height,
# &border_width,
# &depth));
# drag_start_frame_pos_ = Position<int>(x, y);
# drag_start_frame_size_ = Size<int>(width, height);
# 3. Raise clicked window to top.
XRaiseWindow($.display, $frame);
}
# when ButtonRelease {
# OnButtonRelease($e.xbutton);
# }
# when MotionNotify {
# # Skip any already pending motion events.
# while (XCheckTypedWindowEvent(
# $.display, $e.xmotion.window, MotionNotify, $e)) {}
# OnMotionNotify($e.xmotion);
# }
when KeyPress {
note "KeyPress Indeed, inspecting "
~ sprintf(" state %b & %b, keycode: %x == %x ", $e.xkey.state, Mod1Mask, $e.xkey.keycode, XKeysymToKeycode($.display, XK_F4));
if ($e.xkey.state +& Mod1Mask) &&
($e.xkey.keycode == XKeysymToKeycode($.display, XK_F4)) {
note "Closing Window $e.xkey.window";
# alt + f4: Close window.
#
# There are two ways to tell an X window to close. The first is to send it
# a message of type WM_PROTOCOLS and value WM_DELETE_WINDOW. If the client
# has not explicitly marked itself as supporting this more civilized
# behavior (using XSetWMProtocols()), we kill it with XKillClient().
# Atom* supported_protocols;
# int num_supported_protocols;
# if (XGetWMProtocols(display_,
# e.window,
# &supported_protocols,
# &num_supported_protocols) &&
# (std::find(supported_protocols,
# supported_protocols + num_supported_protocols,
# WM_DELETE_WINDOW) !=
# supported_protocols + num_supported_protocols)) {
# LOG(INFO) << "Gracefully deleting window " << e.window;
# # 1. Construct message.
# XEvent msg;
# memset(&msg, 0, sizeof(msg));
# msg.xclient.type = ClientMessage;
# msg.xclient.message_type = WM_PROTOCOLS;
# msg.xclient.window = e.window;
# msg.xclient.format = 32;
# msg.xclient.data.l[0] = WM_DELETE_WINDOW;
# # 2. Send message to window to be closed.
# CHECK(XSendEvent(display_, e.window, false, 0, &msg));
# } else {
# LOG(INFO) << "Killing window " << e.window;
# XKillClient(display_, e.window);
# }
}
elsif ($e.xkey.state +& Mod1Mask) &&
($e.xkey.keycode == XKeysymToKeycode($.display, XK_Tab)) {
note "Alt + tab - switching window";
# # alt + tab: Switch window.
# # 1. Find next window.
# auto i = clients_.find(e.window);
# CHECK(i != clients_.end());
# ++i;
# if (i == clients_.end()) {
# i = clients_.begin();
# }
# # 2. Raise and set focus.
# XRaiseWindow(display_, i->second);
# XSetInputFocus(display_, i->first, RevertToPointerRoot, CurrentTime);
}
}
# when KeyRelease {
# OnKeyRelease($e.xkey);
# }
default {
note "Ignored event";
}
}
}
}
method unframe($w) {
# We reverse the steps taken in Frame().
my $frame = %!clients{ $w };
if ! $frame {
note "Request to unframe window that we did not track: $w";
return;
}
# 1. Unmap frame.
XUnmapWindow($.display, $frame);
# 2. Reparent client window.
XReparentWindow( $.display, $w, $.display.DefaultRootWindow, 0, 0);
# 3. Remove client window from save set, as it is now unrelated to us.
XRemoveFromSaveSet($.display, $w);
# 4. Destroy frame.
XDestroyWindow($.display, $frame);
# 5. Drop reference to frame handle.
%!clients{ $w }:delete;
note "Unframed window $w [ $frame ]";
}
}