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