-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscreen.c
464 lines (398 loc) · 14.4 KB
/
screen.c
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
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
/* evilwm - minimalist window manager for X11
* Copyright (C) 1999-2022 Ciaran Anscomb <[email protected]>
* see README for license and other details. */
// Screen management.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#ifdef RANDR
#include <X11/extensions/Xrandr.h>
#endif
#include "bind.h"
#include "client.h"
#include "display.h"
#include "evilwm.h"
#include "ewmh.h"
#include "list.h"
#include "log.h"
#include "screen.h"
#include "util.h"
#include "xalloc.h"
// Set up DISPLAY environment variable to use
static char *screen_to_display_str(int i) {
char *ds = DisplayString(display.dpy);
char *dpy_str = xmalloc(20 + strlen(ds));
strcpy(dpy_str, "DISPLAY=");
strcat(dpy_str, ds);
char *colon = strrchr(dpy_str, ':');
if (!colon || display.nscreens < 2)
return dpy_str;
char *dot = strchr(colon, '.');
if (!dot)
dot = colon + strlen(colon);
snprintf(dot, 12, ".%d", i);
return dpy_str;
}
static _Bool vdeskfromroot(struct screen *s) {
unsigned long nitems;
unsigned *ret = get_property(s->root, X_ATOM(_NET_CURRENT_DESKTOP), XA_CARDINAL, &nitems);
if (nitems != 1) { LOG_DEBUG("nitems=%lu for _NET_CURRENT_DESKTOP on screen %i (%s)\n",
nitems, s->screen, screen_to_display_str(s->screen)); }
if (!nitems) return False;
if (!ret) return False;
s->vdesk = *ret;//[0];
XFree(ret);
LOG_DEBUG("vdeskfromroot set vdesk to %u\n", s->vdesk);
return True;
}
static _Bool vdeskfrompointerwindow(struct screen *s) {
Window child;
int cx; int cy; Window root; int rx; int ry; unsigned mask; //dummy
if (!XQueryPointer(display.dpy, s->root, &root, &child, &rx, &ry, &cx, &cy, &mask)) return False;
unsigned long nitems;
unsigned *ret = get_property(child, X_ATOM(_NET_WM_DESKTOP), XA_CARDINAL, &nitems);
if (nitems != 1) { LOG_DEBUG("nitems=%lu for _NET_WM_DESKTOP on window 0x%lx\n", nitems, child); }
if (!nitems) return False;
if (!ret) return False;
s->vdesk = *ret;
LOG_DEBUG("vdeskfrompointerwindow set vdesk to %u\n", s->vdesk);
return True;
}
// Called once per screen when display is being initialised.
void screen_init(struct screen *s) {
int i = s->screen;
// Used to set the DISPLAY environment variable to something like
// ":0.x" depending on which screen a terminal is launched from.
s->display = screen_to_display_str(i);
s->root = RootWindow(display.dpy, i);
#ifdef RANDR
s->nmonitors = 0;
s->monitors = NULL;
if (display.have_randr) {
XRRSelectInput(display.dpy, s->root, RRScreenChangeNotifyMask);
}
#endif
screen_probe_monitors(s);
if (!vdeskfromroot(s))
if (!vdeskfrompointerwindow(s))
s->vdesk = 0;
// In case the visual for this screen uses a colourmap, ensure our
// border colours are in it.
XColor dummy;
XAllocNamedColor(display.dpy, DefaultColormap(display.dpy, i), option.fg, &s->fg, &dummy);
XAllocNamedColor(display.dpy, DefaultColormap(display.dpy, i), option.bg, &s->bg, &dummy);
XAllocNamedColor(display.dpy, DefaultColormap(display.dpy, i), option.fc, &s->fc, &dummy);
#ifdef GC_INVERT
// When dragging an outline, we use an inverting graphics context
// (GCFunction + GXinvert) so that simply drawing it again will erase
// it from the screen.
XGCValues gv;
gv.function = GXinvert;
gv.subwindow_mode = IncludeInferiors;
gv.line_width = 1; // option.bw
#ifdef FONT
gv.font = display.font->fid;
#else
#undef GCFont
#define GCFont 0
#endif
s->invert_gc = XCreateGC(display.dpy, s->root,
GCFunction | GCSubwindowMode | GCLineWidth | GCFont, &gv);
#endif
// We handle events to the root window:
// SubstructureRedirectMask - create, destroy, configure window notifications
// SubstructureNotifyMask - configure window requests
// EnterWindowMask - enter events
// ColormapChangeMask - when a new colourmap is needed
XSetWindowAttributes attr;
attr.event_mask = SubstructureRedirectMask | SubstructureNotifyMask
| EnterWindowMask | ColormapChangeMask;
XChangeWindowAttributes(display.dpy, s->root, CWEventMask, &attr);
// Grab the various keyboard shortcuts
bind_grab_for_screen(s);
s->active = None;
s->docks_visible = option.docks;
Atom supported[] = {
X_ATOM(_NET_CLIENT_LIST),
X_ATOM(_NET_CLIENT_LIST_STACKING),
X_ATOM(_NET_NUMBER_OF_DESKTOPS),
X_ATOM(_NET_DESKTOP_GEOMETRY),
X_ATOM(_NET_DESKTOP_VIEWPORT),
X_ATOM(_NET_CURRENT_DESKTOP),
X_ATOM(_NET_ACTIVE_WINDOW),
X_ATOM(_NET_WORKAREA),
X_ATOM(_NET_SUPPORTING_WM_CHECK),
X_ATOM(_NET_CLOSE_WINDOW),
X_ATOM(_NET_MOVERESIZE_WINDOW),
X_ATOM(_NET_RESTACK_WINDOW),
X_ATOM(_NET_REQUEST_FRAME_EXTENTS),
X_ATOM(_NET_WM_DESKTOP),
X_ATOM(_NET_WM_WINDOW_TYPE),
X_ATOM(_NET_WM_WINDOW_TYPE_DESKTOP),
X_ATOM(_NET_WM_WINDOW_TYPE_DOCK),
X_ATOM(_NET_WM_STATE),
X_ATOM(_NET_WM_STATE_MAXIMIZED_VERT),
X_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ),
X_ATOM(_NET_WM_STATE_HIDDEN),
X_ATOM(_NET_WM_STATE_FULLSCREEN),
X_ATOM(_NET_WM_STATE_FOCUSED),
X_ATOM(_NET_WM_ALLOWED_ACTIONS),
// Not sure if it makes any sense including every action here
// as they'll already be listed per-client in the
// _NET_WM_ALLOWED_ACTIONS property, but EWMH spec is unclear.
X_ATOM(_NET_WM_ACTION_MOVE),
X_ATOM(_NET_WM_ACTION_RESIZE),
X_ATOM(_NET_WM_ACTION_MAXIMIZE_HORZ),
X_ATOM(_NET_WM_ACTION_MAXIMIZE_VERT),
X_ATOM(_NET_WM_ACTION_FULLSCREEN),
X_ATOM(_NET_WM_ACTION_CHANGE_DESKTOP),
X_ATOM(_NET_WM_ACTION_CLOSE),
X_ATOM(_NET_FRAME_EXTENTS),
};
unsigned long num_desktops = option.vdesks;
unsigned long vdesk = s->vdesk;
unsigned long pid = getpid();
s->supporting = XCreateSimpleWindow(display.dpy, s->root, 0, 0, 1, 1, 0, 0, 0);
XChangeProperty(display.dpy, s->root, X_ATOM(_NET_SUPPORTED),
XA_ATOM, 32, PropModeReplace,
(unsigned char *)&supported,
sizeof(supported) / sizeof(Atom));
XChangeProperty(display.dpy, s->root, X_ATOM(_NET_NUMBER_OF_DESKTOPS),
XA_CARDINAL, 32, PropModeReplace,
(unsigned char *)&num_desktops, 1);
XChangeProperty(display.dpy, s->root, X_ATOM(_NET_CURRENT_DESKTOP),
XA_CARDINAL, 32, PropModeReplace,
(unsigned char *)&vdesk, 1);
XChangeProperty(display.dpy, s->root, X_ATOM(_NET_ACTIVE_WINDOW),
XA_WINDOW, 32, PropModeReplace,
(unsigned char *)&s->active, 1);
XChangeProperty(display.dpy, s->root, X_ATOM(_NET_SUPPORTING_WM_CHECK),
XA_WINDOW, 32, PropModeReplace,
(unsigned char *)&s->supporting, 1);
XChangeProperty(display.dpy, s->supporting, X_ATOM(_NET_SUPPORTING_WM_CHECK),
XA_WINDOW, 32, PropModeReplace,
(unsigned char *)&s->supporting, 1);
XChangeProperty(display.dpy, s->supporting, X_ATOM(_NET_WM_NAME),
XA_STRING, 8, PropModeReplace,
(const unsigned char *)"evilwm", 6);
XChangeProperty(display.dpy, s->supporting, X_ATOM(_NET_WM_PID),
XA_CARDINAL, 32, PropModeReplace,
(unsigned char *)&pid, 1);
ewmh_set_screen_workarea(s);
}
void screen_deinit(struct screen *s) {
XDeleteProperty(display.dpy, s->root, X_ATOM(_NET_SUPPORTED));
XDeleteProperty(display.dpy, s->root, X_ATOM(_NET_CLIENT_LIST));
XDeleteProperty(display.dpy, s->root, X_ATOM(_NET_CLIENT_LIST_STACKING));
XDeleteProperty(display.dpy, s->root, X_ATOM(_NET_NUMBER_OF_DESKTOPS));
XDeleteProperty(display.dpy, s->root, X_ATOM(_NET_DESKTOP_GEOMETRY));
XDeleteProperty(display.dpy, s->root, X_ATOM(_NET_DESKTOP_VIEWPORT));
XDeleteProperty(display.dpy, s->root, X_ATOM(_NET_CURRENT_DESKTOP));//disable to use vdeskfromroot more
XDeleteProperty(display.dpy, s->root, X_ATOM(_NET_ACTIVE_WINDOW));
XDeleteProperty(display.dpy, s->root, X_ATOM(_NET_WORKAREA));
XDeleteProperty(display.dpy, s->root, X_ATOM(_NET_SUPPORTING_WM_CHECK));
XDestroyWindow(display.dpy, s->supporting);
free(s->monitors);
}
// Switch virtual desktop. Hides clients on different vdesks, shows clients on
// the selected one. Docks are always shown (unless user has hidden them
// explicitly). Fixed clients are always shown.
void switch_vdesk(struct screen *s, unsigned v) {
if (!valid_vdesk(v)) return;
if (v == s->vdesk) return; // already there
LOG_ENTER("switch_vdesk(screen=%d, from=%u, to=%u)", s->screen, s->vdesk, v);
// hide everything on old vdesk
for (struct list *iter = clients_tab_order; iter; iter = iter->next) {
struct client *c = iter->data;
if (c->screen != s) continue;
if (is_visible(c) && !is_fixed(c)) client_hide(c);
}
// Store previous vdesk, so that user may toggle back to it
s->old_vdesk = s->vdesk;
// Update current vdesk (including EWMH properties)
s->vdesk = v;
// show everything on vdesk
for (struct list *iter = clients_tab_order; iter; iter = iter->next) {
struct client *c = iter->data;
if (c->screen != s) continue;
if (is_visible(c) && !is_fixed(c)) client_show(c);
}
ewmh_set_net_current_desktop(s);
LOG_LEAVE();
}
// Set whether docks are visible on the current screen.
void set_docks_visible(struct screen *s, int is_visible) {
LOG_ENTER("set_docks_visible(screen=%d, is_visible=%d)", s->screen, is_visible);
s->docks_visible = is_visible;
// Traverse client list and hide or show any docks on this screen as
// appropriate.
for (struct list *iter = clients_tab_order; iter; iter = iter->next) {
struct client *c = iter->data;
if (c->screen != s) continue;
if (c->is_dock) {
if (is_visible) {
// XXX I've assumed that if you want to see
// them, you also want them raised...
if (on_vdesk(c)) {
client_show(c);
client_raise(c);
}
} else {
client_hide(c);
}
}
}
LOG_LEAVE();
}
// Get a list of monitors for the screen. If Randr >= 1.5 is unavailable, or
// the "wholescreen" option has been specified, assume a single monitor
// covering the whole screen.
void screen_probe_monitors(struct screen *s) {
#if defined(RANDR) && (RANDR_MAJOR == 1) && (RANDR_MINOR >= 5)
if (display.have_randr && !option.wholescreen) {
// Populate list of active monitors
LOG_XENTER("XRRGetMonitors(screen=%d)", s->screen);
int nmonitors;
XRRMonitorInfo *monitors = XRRGetMonitors(display.dpy, s->root, True, &nmonitors);
if (monitors) {
struct monitor *new_monitors = s->monitors;
if (nmonitors != s->nmonitors) {
// allocating in multiple of 4 should stop us
// having to reallocate at all in the most
// common uses
int n = (nmonitors | 3) + 1;
new_monitors = realloc(s->monitors, n * sizeof(struct monitor));
}
if (new_monitors) {
s->monitors = new_monitors;
for (int i = 0; i < nmonitors; i++) {
LOG_XDEBUG("monitor %d: %dx%d+%d+%d\n", i, monitors[i].width, monitors[i].height, monitors[i].x, monitors[i].y);
s->monitors[i].name = monitors[i].name;
s->monitors[i].x = monitors[i].x;
s->monitors[i].y = monitors[i].y;
s->monitors[i].width = monitors[i].width;
s->monitors[i].height = monitors[i].height;
s->monitors[i].area = monitors[i].width * monitors[i].height;
}
s->nmonitors = nmonitors;
}
LOG_XLEAVE();
XRRFreeMonitors(monitors);
return;
}
LOG_XLEAVE();
}
#endif
s->nmonitors = 1;
if (!s->monitors) {
s->monitors = xmalloc(sizeof(struct monitor));
}
s->monitors[0].x = 0;
s->monitors[0].y = 0;
s->monitors[0].width = DisplayWidth(display.dpy, s->screen);
s->monitors[0].height = DisplayHeight(display.dpy, s->screen);
s->monitors[0].area = s->monitors[0].width * s->monitors[0].height;
}
#ifdef RANDR
// If a screen has been resized (due to RandR), some windows have the
// possibility of:
//
// a) not being visible
// b) being vertically/horizontally maximised to the wrong extent
//
// Our approach is now to:
//
// 1) record client positions as proportions of offset into their current
// monitor
// 2) apply screen geometry changes and rescan list of monitors
// 3) move any client that no longer intersects a monitor to the same
// proportional position within its nearest monitor
// 4) adjust geometry of maximised clients to any "new" monitor
// Record old monitor offset for each client before resize.
void scan_clients_before_resize(struct screen *s) {
for (struct list *iter = clients_tab_order; iter; iter = iter->next) {
struct client *c = iter->data;
// only handle clients on the screen being resized
if (c->screen != s)
continue;
struct monitor *m = client_monitor(c, NULL);
int mw = m->width;
int mh = m->height;
int cx = c->oldw ? c->oldx : c->x;
int cy = c->oldh ? c->oldy : c->y;
c->mon_offx = (double)(cx - m->x) / (double)mw;
c->mon_offy = (double)(cy - m->y) / (double)mh;
if (!c->mon_save) c->mon_name = m->name;
}
}
// Fix up maximised and non-intersecting clients after resize.
void fix_screen_after_resize(struct screen *s) {
LOG_ENTER("fix_screen_after_resize(screen %i)",s->screen);
for (struct list *iter = clients_tab_order; iter; iter = iter->next) {
struct client *c = iter->data;
// only handle clients on the screen being resized
if (c->screen != s) continue;
// Check for either: the monitor with a matching name, or just the closest monitor
struct monitor *m = NULL;
if (c->mon_name!=None) {
for (int i = 0; i < c->screen->nmonitors; i++) {
if (c->mon_name != c->screen->monitors[i].name) continue;
m = &c->screen->monitors[i];
c->mon_save = 0;
break;
}
if (!m) c->mon_save = 1;
}
LOG_DEBUG("w%lx: m%lx %s\n",c->window,c->mon_name,m?"✓":"✗");
Bool intersects = 0;
if (!m) m = client_monitor(c, &intersects);
if (c->oldw) {
// horiz maximised: update width, update old x pos
c->x = m->x - c->border;
c->width = m->width;
c->oldx = m->x + c->mon_offx * m->width;
} else {
// horiz normal: update x pos
if (!intersects)
c->x = m->x + c->mon_offx * m->width;
}
if (c->oldh) {
// vert maximised: update height, update old y pos
c->y = m->y - c->border;
c->height = m->height;
c->oldy = m->y + c->mon_offy * m->height;
} else {
// vert normal: update y pos
if (!intersects)
c->y = m->y + c->mon_offy * m->height;
}
client_moveresize(c);
}
LOG_LEAVE();
}
#endif
// Find screen corresponding to specified root window.
struct screen *find_screen(Window root) {
for (int i = 0; i < display.nscreens; i++) {
if (display.screens[i].root == root)
return &display.screens[i];
}
return NULL;
}
// Find screen corresponding to the root window the pointer is currently on.
struct screen *find_current_screen(void) {
Window cur_root;
Window dw; // dummy
int di; // dummy
unsigned dui; // dummy
// XQueryPointer is useful for getting the current pointer root
XQueryPointer(display.dpy, display.screens[0].root, &cur_root, &dw, &di, &di, &di, &di, &dui);
return find_screen(cur_root);
}