1 //
2 // ManagerWindows.cc for pekwm
3 // Copyright (C) 2009-2020 Claes Nästén <pekdon@gmail.com>
4 //
5 // This program is licensed under the GNU GPL.
6 // See the LICENSE file for more information.
7 //
8
9 #include "config.h"
10
11 #include "Config.hh"
12 #include "Debug.hh"
13 #include "ActionHandler.hh"
14 #include "ManagerWindows.hh"
15 #include "Workspaces.hh"
16 #include "Util.hh"
17
18 #include <string>
19
20 extern "C" {
21 #include <unistd.h>
22 }
23
24 // Static initializers
25 const std::string HintWO::WM_NAME = "pekwm";
26 const unsigned int HintWO::DISPLAY_WAIT = 10;
27
28 const unsigned long RootWO::EVENT_MASK =
29 StructureNotifyMask|PropertyChangeMask|
30 SubstructureNotifyMask|SubstructureRedirectMask|
31 ColormapChangeMask|FocusChangeMask|EnterWindowMask|
32 ButtonPressMask|ButtonReleaseMask|ButtonMotionMask;
33 const unsigned long RootWO::EXPECTED_DESKTOP_NAMES_LENGTH = 256;
34
35 /**
36 * Hint window constructor, creates window and sets supported
37 * protocols.
38 */
HintWO(Window root)39 HintWO::HintWO(Window root)
40 : PWinObj(false)
41 {
42 _type = WO_SCREEN_HINT;
43 setLayer(LAYER_NONE);
44 _sticky = true; // Hack, do not map/unmap this window
45 _iconified = true; // Hack, to be ignored when placing
46
47 // Create window
48 _window = X11::createSimpleWindow(root,
49 -200, -200, 5, 5, 0, 0, 0);
50
51 // Remove override redirect from window
52 XSetWindowAttributes attr;
53 attr.override_redirect = True;
54 attr.event_mask = PropertyChangeMask;
55 X11::changeWindowAttributes(_window, CWEventMask|CWOverrideRedirect, attr);
56
57 // Set hints not being updated
58 X11::setUtf8String(_window, NET_WM_NAME, WM_NAME);
59 X11::setWindow(_window, NET_SUPPORTING_WM_CHECK, _window);
60 }
61
62 /**
63 * Hint WO destructor, destroy hint window.
64 */
~HintWO(void)65 HintWO::~HintWO(void)
66 {
67 X11::destroyWindow(_window);
68 }
69
70 /**
71 * Get current time of server by generating an event and reading the
72 * timestamp on it.
73 *
74 * @return Time on server.
75 */
76 Time
getTime(void)77 HintWO::getTime(void)
78 {
79 XEvent event;
80
81 // Generate event on ourselves
82 X11::changeProperty(_window,
83 X11::getAtom(WM_CLASS), X11::getAtom(STRING),
84 8, PropModeAppend, 0, 0);
85 XWindowEvent(X11::getDpy(), _window, PropertyChangeMask, &event);
86
87 return event.xproperty.time;
88 }
89
90 /**
91 * Claim ownership over the current display.
92 *
93 * @param replace Replace current running window manager.
94 */
95 bool
claimDisplay(bool replace)96 HintWO::claimDisplay(bool replace)
97 {
98 bool status = true;
99
100 // Get atom for the current screen and it's owner
101 std::string session_name("WM_S"
102 + std::to_string(DefaultScreen(X11::getDpy())));
103 Atom session_atom = XInternAtom(X11::getDpy(), session_name.c_str(), false);
104 Window session_owner = XGetSelectionOwner(X11::getDpy(), session_atom);
105
106 if (session_owner && session_owner != _window) {
107 if (! replace) {
108 P_LOG("window manager already running");
109 return false;
110 }
111
112 X11::sync(False);
113 setXErrorsIgnore(true);
114 uint errors_before = xerrors_count;
115
116 // Select event to get notified when current owner dies.
117 X11::selectInput(session_owner, StructureNotifyMask);
118
119 X11::sync(False);
120 setXErrorsIgnore(false);
121 if (errors_before != xerrors_count) {
122 session_owner = None;
123 }
124 }
125
126 Time timestamp = getTime();
127
128 XSetSelectionOwner(X11::getDpy(), session_atom, _window, timestamp);
129 if (XGetSelectionOwner(X11::getDpy(), session_atom) == _window) {
130 if (session_owner) {
131 // Wait for the previous window manager to go away and update owner.
132 status = claimDisplayWait(session_owner);
133 if (status) {
134 claimDisplayOwner(session_atom, timestamp);
135 }
136 }
137 } else {
138 std::cerr << "pekwm: unable to replace current window manager."
139 << std::endl;
140 status = false;
141 }
142
143 return status;
144 }
145
146
147 /**
148 * After claiming the display, wait for the previous window manager to
149 * go away.
150 */
151 bool
claimDisplayWait(Window session_owner)152 HintWO::claimDisplayWait(Window session_owner)
153 {
154 XEvent event;
155
156 P_LOG("waiting for previous window manager to exit");
157
158 for (uint waited = 0; waited < HintWO::DISPLAY_WAIT; ++waited) {
159 if (XCheckWindowEvent(X11::getDpy(), session_owner,
160 StructureNotifyMask, &event)
161 && event.type == DestroyNotify) {
162 return true;
163 }
164
165 sleep(1);
166 }
167
168 P_LOG("previous window manager did not exit");
169
170 return false;
171 }
172
173 /**
174 * Send message updating the owner of the screen.
175 */
176 void
claimDisplayOwner(Window session_atom,Time timestamp)177 HintWO::claimDisplayOwner(Window session_atom, Time timestamp)
178 {
179 XEvent event;
180 // FIXME: One should use _root_wo here?
181 Window root = X11::getRoot();
182
183 event.xclient.type = ClientMessage;
184 event.xclient.message_type = X11::getAtom(MANAGER);
185 event.xclient.display = X11::getDpy();
186 event.xclient.window = root;
187 event.xclient.format = 32;
188 event.xclient.data.l[0] = timestamp;
189 event.xclient.data.l[1] = session_atom;
190 event.xclient.data.l[2] = _window;
191 event.xclient.data.l[3] = 0;
192
193 XSendEvent(X11::getDpy(), root, false, SubstructureNotifyMask, &event);
194 }
195
196 /**
197 * Root window constructor, reads geometry and sets basic atoms.
198 */
RootWO(Window root,HintWO * hint_wo,Config * cfg)199 RootWO::RootWO(Window root, HintWO *hint_wo, Config *cfg)
200 : PWinObj(false),
201 _hint_wo(hint_wo),
202 _cfg(cfg)
203 {
204 _type = WO_SCREEN_ROOT;
205 setLayer(LAYER_NONE);
206 _mapped = true;
207
208 _window = root;
209 _gm.width = X11::getWidth();
210 _gm.height = X11::getHeight();
211
212 X11::sync(False);
213 setXErrorsIgnore(true);
214 uint errors_before = xerrors_count;
215
216 // Select window events
217 X11::selectInput(_window, RootWO::EVENT_MASK);
218
219 X11::sync(False);
220 setXErrorsIgnore(false);
221 if (errors_before != xerrors_count) {
222 std::cerr << "pekwm: root window unavailable, can't start!"
223 << std::endl;
224 throw StopException("stop");
225 }
226
227 // Set hits on the hint window, these are not updated so they are
228 // set in the constructor.
229 X11::setCardinal(_window, NET_WM_PID, static_cast<Cardinal>(getpid()));
230 X11::setString(_window, WM_CLIENT_MACHINE, Util::getHostname());
231
232 X11::setWindow(_window, NET_SUPPORTING_WM_CHECK, _hint_wo->getWindow());
233 X11::setEwmhAtomsSupport(_window);
234 X11::setCardinal(_window, NET_NUMBER_OF_DESKTOPS,
235 static_cast<Cardinal>(_cfg->getWorkspaces()));
236 X11::setCardinal(_window, NET_CURRENT_DESKTOP, 0);
237
238 Cardinal desktop_geometry[2];
239 desktop_geometry[0] = static_cast<Cardinal>(_gm.width);
240 desktop_geometry[1] = static_cast<Cardinal>(_gm.height);
241 X11::setCardinals(_window, NET_DESKTOP_GEOMETRY, desktop_geometry, 2);
242
243 woListAdd(this);
244 _wo_map[_window] = this;
245
246 initStrutHead();
247 }
248
249 /**
250 * Root window destructor, clears atoms set.
251 */
~RootWO(void)252 RootWO::~RootWO(void)
253 {
254 // Remove atoms, PID will not be valid on shutdown.
255 X11::unsetProperty(_window, NET_WM_PID);
256 X11::unsetProperty(_window, WM_CLIENT_MACHINE);
257
258 _wo_map.erase(_window);
259 woListRemove(this);
260 }
261
262 /**
263 * Button press event handler, gets actions from root list.
264 */
265 ActionEvent*
handleButtonPress(XButtonEvent * ev)266 RootWO::handleButtonPress(XButtonEvent *ev)
267 {
268 return ActionHandler::findMouseAction(ev->button, ev->state, MOUSE_EVENT_PRESS,
269 _cfg->getMouseActionList(MOUSE_ACTION_LIST_ROOT));
270 }
271
272 /**
273 * Button release event handler, gets actions from root list.
274 */
275 ActionEvent*
handleButtonRelease(XButtonEvent * ev)276 RootWO::handleButtonRelease(XButtonEvent *ev)
277 {
278 MouseEventType mb = MOUSE_EVENT_RELEASE;
279
280 // first we check if it's a double click
281 if (X11::isDoubleClick(ev->window, ev->button - 1, ev->time,
282 _cfg->getDoubleClickTime())) {
283 X11::setLastClickID(ev->window);
284 X11::setLastClickTime(ev->button - 1, 0);
285
286 mb = MOUSE_EVENT_DOUBLE;
287
288 } else {
289 X11::setLastClickID(ev->window);
290 X11::setLastClickTime(ev->button - 1, ev->time);
291 }
292
293 return ActionHandler::findMouseAction(ev->button, ev->state, mb,
294 _cfg->getMouseActionList(MOUSE_ACTION_LIST_ROOT));
295 }
296
297 /**
298 * Motion event handler, gets actions from root list.
299 */
300 ActionEvent*
handleMotionEvent(XMotionEvent * ev)301 RootWO::handleMotionEvent(XMotionEvent *ev)
302 {
303 unsigned int button = X11::getButtonFromState(ev->state);
304
305 return ActionHandler::findMouseAction(button, ev->state, MOUSE_EVENT_MOTION,
306 _cfg->getMouseActionList(MOUSE_ACTION_LIST_ROOT));
307 }
308
309 /**
310 * Enter event handler, gets actions from root list.
311 */
312 ActionEvent*
handleEnterEvent(XCrossingEvent * ev)313 RootWO::handleEnterEvent(XCrossingEvent *ev)
314 {
315 return ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_ENTER,
316 _cfg->getMouseActionList(MOUSE_ACTION_LIST_ROOT));
317 }
318
319 /**
320 * Leave event handler, gets actions from root list.
321 */
322 ActionEvent*
handleLeaveEvent(XCrossingEvent * ev)323 RootWO::handleLeaveEvent(XCrossingEvent *ev)
324 {
325 return ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_LEAVE,
326 _cfg->getMouseActionList(MOUSE_ACTION_LIST_ROOT));
327 }
328
329
330 /**
331 * Makes sure the Geometry is inside the screen.
332 */
333 void
placeInsideScreen(Geometry & gm,bool without_edge)334 RootWO::placeInsideScreen(Geometry& gm, bool without_edge)
335 {
336 uint head_nr = X11::getNearestHead(gm.x, gm.y);
337 Geometry head;
338 if (without_edge) {
339 X11::getHeadInfo(head_nr, head);
340 } else {
341 getHeadInfoWithEdge(head_nr, head);
342 }
343
344 if (gm.x + gm.width > head.x + head.width) {
345 gm.x = head.x + head.width - gm.width;
346 }
347 if (gm.x < head.x) {
348 gm.x = head.x;
349 }
350
351 if (gm.y + gm.height > head.y + head.height) {
352 gm.y = head.y + head.height - gm.height;
353 }
354 if (gm.y < head.y) {
355 gm.y = head.y;
356 }
357 }
358
359 /**
360 * Fill information about head and the strut.
361 */
362 void
getHeadInfoWithEdge(uint num,Geometry & head)363 RootWO::getHeadInfoWithEdge(uint num, Geometry &head)
364 {
365 if (! X11::getHeadInfo(num, head)) {
366 return;
367 }
368
369 int strut_val;
370 Strut &strut = _strut_head[num];
371
372 // Remove the strut area from the head info
373 strut_val = (head.x == 0) ? std::max(_strut.left, strut.left) : strut.left;
374 head.x += strut_val;
375 head.width -= strut_val;
376
377 strut_val = ((head.x + head.width) == _gm.width)
378 ? std::max(_strut.right, strut.right) : strut.right;
379 head.width -= strut_val;
380
381 strut_val = (head.y == 0) ? std::max(_strut.top, strut.top) : strut.top;
382 head.y += strut_val;
383 head.height -= strut_val;
384
385 strut_val = (head.y + head.height == _gm.height)
386 ? std::max(_strut.bottom, strut.bottom) : strut.bottom;
387 head.height -= strut_val;
388 }
389
390
391 void
updateGeometry(uint width,uint height)392 RootWO::updateGeometry(uint width, uint height)
393 {
394 if (! X11::updateGeometry(width, height)) {
395 return;
396 }
397
398 initStrutHead();
399
400 resize(width, height);
401 updateStrut();
402 }
403
404 //! @brief Adds a strut to the strut list, updating max strut sizes
405 void
addStrut(Strut * strut)406 RootWO::addStrut(Strut *strut)
407 {
408 _struts.push_back(strut);
409 updateStrut();
410 }
411
412 //! @brief Removes a strut from the strut list
413 void
removeStrut(Strut * strut)414 RootWO::removeStrut(Strut *strut)
415 {
416 std::vector<Strut*>::iterator it =
417 find(_struts.begin(), _struts.end(), strut);
418 if (it != _struts.end()) {
419 _struts.erase(it);
420 }
421 updateStrut();
422 }
423
424 //! @brief Updates strut max size.
425 void
updateStrut(void)426 RootWO::updateStrut(void)
427 {
428 // Reset strut data.
429 _strut.left = 0;
430 _strut.right = 0;
431 _strut.top = 0;
432 _strut.bottom = 0;
433
434 std::vector<Strut>::iterator hit = _strut_head.begin();
435 for (; hit != _strut_head.end(); ++hit) {
436 hit->left = 0;
437 hit->right = 0;
438 hit->top = 0;
439 hit->bottom = 0;
440 }
441
442 Strut *strut;
443 std::vector<Strut*>::iterator it = _struts.begin();
444 for (; it != _struts.end(); ++it) {
445 if ((*it)->head < 0) {
446 strut = &_strut;
447 } else if (static_cast<uint>((*it)->head) < _strut_head.size()) {
448 strut = &(_strut_head[(*it)->head]);
449 } else {
450 continue;
451 }
452
453 if (strut->left < (*it)->left) {
454 strut->left = (*it)->left;
455 }
456 if (strut->right < (*it)->right) {
457 strut->right = (*it)->right;
458 }
459 if (strut->top < (*it)->top) {
460 strut->top = (*it)->top;
461 }
462 if (strut->bottom < (*it)->bottom) {
463 strut->bottom = (*it)->bottom;
464 }
465 }
466
467 // Update hints on the root window
468 Geometry workarea(_strut.left, _strut.top,
469 _gm.width - _strut.left - _strut.right,
470 _gm.height - _strut.top - _strut.bottom);
471
472 setEwmhWorkarea(workarea);
473 }
474
475 /**
476 * Handling of XPropertyEvent on the Root window. Entry point for EWMH
477 * message handling.
478 */
479 void
handlePropertyChange(XPropertyEvent * ev)480 RootWO::handlePropertyChange(XPropertyEvent *ev)
481 {
482 if (ev->atom == X11::getAtom(NET_DESKTOP_NAMES)) {
483 readEwmhDesktopNames();
484 Workspaces::setNames();
485 }
486 }
487
488 /**
489 * Update _NET_WORKAREA property.
490 *
491 * @param workarea Geometry with work area.
492 */
493 void
setEwmhWorkarea(const Geometry & workarea)494 RootWO::setEwmhWorkarea(const Geometry &workarea)
495 {
496 Cardinal workarea_array[4];
497 workarea_array[0] = static_cast<Cardinal>(workarea.x);
498 workarea_array[1] = static_cast<Cardinal>(workarea.y);
499 workarea_array[2] = static_cast<Cardinal>(workarea.width);
500 workarea_array[3] = static_cast<Cardinal>(workarea.height);
501 X11::setCardinals(_window, NET_WORKAREA, workarea_array, 4);
502 }
503
504 /**
505 * Update _NET_ACTIVE_WINDOW property.
506 *
507 * @param win Window to set as active window.
508 */
509 void
setEwmhActiveWindow(Window win)510 RootWO::setEwmhActiveWindow(Window win)
511 {
512 X11::setWindow(X11::getRoot(), NET_ACTIVE_WINDOW, win);
513 }
514
515 /**
516 * Reads the _NET_DESKTOP_NAMES hint and sets the workspaces names accordingly.
517 */
518 void
readEwmhDesktopNames(void)519 RootWO::readEwmhDesktopNames(void)
520 {
521 uchar *data;
522 ulong data_length;
523 if (X11::getProperty(X11::getRoot(), X11::getAtom(NET_DESKTOP_NAMES),
524 X11::getAtom(UTF8_STRING),
525 EXPECTED_DESKTOP_NAMES_LENGTH, &data, &data_length)) {
526 _cfg->setDesktopNamesUTF8(reinterpret_cast<char *>(data), data_length);
527
528 X11::free(data);
529 }
530 }
531
532 /**
533 * Update _NET_DESKTOP_NAMES property on the root window.
534 */
535 void
setEwmhDesktopNames(void)536 RootWO::setEwmhDesktopNames(void)
537 {
538 unsigned char *desktopnames = 0;
539 unsigned int length = 0;
540 _cfg->getDesktopNamesUTF8(&desktopnames, &length);
541
542 if (desktopnames) {
543 X11::setUtf8StringArray(X11::getRoot(), NET_DESKTOP_NAMES,
544 desktopnames, length);
545 delete [] desktopnames;
546 }
547 }
548
549 /**
550 * Update _NET_DESKTOP_LAYOUT property on the root window.
551 */
552 void
setEwmhDesktopLayout(void)553 RootWO::setEwmhDesktopLayout(void)
554 {
555 // This property is defined to be set by the pager, however, as pekwm
556 // displays a "pager" when changing workspaces and other applications
557 // might want to read this information set it anyway.
558 Cardinal desktop_layout[] = {
559 NET_WM_ORIENTATION_HORZ,
560 static_cast<Cardinal>(Workspaces::getPerRow()),
561 static_cast<Cardinal>(Workspaces::getRows()),
562 NET_WM_TOPLEFT
563 };
564 X11::setCardinals(X11::getRoot(), NET_DESKTOP_LAYOUT, desktop_layout,
565 sizeof(desktop_layout)/sizeof(desktop_layout[0]));
566 }
567
568 /**
569 * Edge window constructor, create window, setup strut and register
570 * window.
571 */
EdgeWO(RootWO * root_wo,EdgeType edge,bool set_strut,Config * cfg)572 EdgeWO::EdgeWO(RootWO* root_wo, EdgeType edge, bool set_strut,
573 Config* cfg)
574 : PWinObj(false),
575 _root_wo(root_wo),
576 _edge(edge),
577 _cfg(cfg)
578 {
579 _type = WO_SCREEN_EDGE;
580 setLayer(LAYER_NONE); // hack, goes over LAYER_MENU
581 _sticky = true; // don't map/unmap
582 _iconified = true; // hack, to be ignored when placing
583 _focusable = false; // focusing input only windows crashes X
584
585 XSetWindowAttributes sattr;
586 sattr.override_redirect = True;
587 sattr.event_mask =
588 EnterWindowMask|LeaveWindowMask|ButtonPressMask|ButtonReleaseMask;
589
590 _window = X11::createWindow(root_wo->getWindow(),
591 0, 0, 1, 1, 0,
592 CopyFromParent, InputOnly, CopyFromParent,
593 CWOverrideRedirect|CWEventMask, &sattr);
594
595 configureStrut(set_strut);
596 _root_wo->addStrut(&_strut);
597
598 woListAdd(this);
599 _wo_map[_window] = this;
600 }
601
602 /**
603 * Edge window destructor, remove strut and destroy window resources.
604 */
~EdgeWO(void)605 EdgeWO::~EdgeWO(void)
606 {
607 _root_wo->removeStrut(&_strut);
608 _wo_map.erase(_window);
609 woListRemove(this);
610
611 X11::destroyWindow(_window);
612 }
613
614 /**
615 * Configure strut on edge window.
616 *
617 * @param set_strut If true, set actual values on the strut, false sets all to 0.
618 */
619 void
configureStrut(bool set_strut)620 EdgeWO::configureStrut(bool set_strut)
621 {
622 // Reset value, on strut to zero.
623 _strut.left = _strut.right = _strut.top = _strut.bottom = 0;
624
625 // Set strut if requested.
626 if (set_strut) {
627 switch (_edge) {
628 case SCREEN_EDGE_TOP:
629 _strut.top = _gm.height;
630 break;
631 case SCREEN_EDGE_BOTTOM:
632 _strut.bottom = _gm.height;
633 break;
634 case SCREEN_EDGE_LEFT:
635 _strut.left = _gm.width;
636 break;
637 case SCREEN_EDGE_RIGHT:
638 _strut.right = _gm.width;
639 break;
640 case SCREEN_EDGE_NO:
641 default:
642 // do nothing
643 break;
644 }
645 }
646 }
647
648 /**
649 * Edge version of mapped window, makes sure the iconified state is
650 * set at all times in order to avoid counting the edge windows when
651 * snapping windows etc.
652 */
653 void
mapWindow(void)654 EdgeWO::mapWindow(void)
655 {
656 if (_mapped) {
657 return;
658 }
659
660 PWinObj::mapWindow();
661 _iconified = true;
662 }
663
664 /**
665 * Enter event handler, gets actions from EdgeList on _edge.
666 */
667 ActionEvent*
handleEnterEvent(XCrossingEvent * ev)668 EdgeWO::handleEnterEvent(XCrossingEvent *ev)
669 {
670 return ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_ENTER,
671 _cfg->getEdgeListFromPosition(_edge));
672 }
673
674 /**
675 * Button press event handler, gets actions from EdgeList on _edge.
676 */
677 ActionEvent*
handleButtonPress(XButtonEvent * ev)678 EdgeWO::handleButtonPress(XButtonEvent *ev)
679 {
680 return ActionHandler::findMouseAction(ev->button, ev->state, MOUSE_EVENT_PRESS,
681 _cfg->getEdgeListFromPosition(_edge));
682 }
683
684 /**
685 * Button release event handler, gets actions from EdgeList on _edge.
686 */
687 ActionEvent*
handleButtonRelease(XButtonEvent * ev)688 EdgeWO::handleButtonRelease(XButtonEvent *ev)
689 {
690 // Make sure the release is on the actual window. This probably
691 // could be done smarter.
692 if (ev->x_root < _gm.x
693 || ev->x_root > static_cast<int>(_gm.x + _gm.width)
694 || ev->y_root < _gm.y
695 || ev->y_root > static_cast<int>(_gm.y + _gm.height)) {
696 return 0;
697 }
698
699 MouseEventType mb = MOUSE_EVENT_RELEASE;
700
701 // first we check if it's a double click
702 if (X11::isDoubleClick(ev->window, ev->button - 1, ev->time,
703 _cfg->getDoubleClickTime())) {
704 X11::setLastClickID(ev->window);
705 X11::setLastClickTime(ev->button - 1, 0);
706
707 mb = MOUSE_EVENT_DOUBLE;
708 } else {
709 X11::setLastClickID(ev->window);
710 X11::setLastClickTime(ev->button - 1, ev->time);
711 }
712
713 return ActionHandler::findMouseAction(ev->button, ev->state, mb,
714 _cfg->getEdgeListFromPosition(_edge));
715 }
716
717 void
initStrutHead()718 RootWO::initStrutHead()
719 {
720 _strut_head.clear();
721 for (int i = 0; i < X11::getNumHeads(); i++) {
722 _strut_head.push_back(Strut(0, 0, 0, 0, i));
723 }
724 }
725