1/********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
7
8This program is free software; you can redistribute it and/or modify
9it under the terms of the GNU General Public License as published by
10the Free Software Foundation; either version 2 of the License, or
11(at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with this program. If not, see <http://www.gnu.org/licenses/>.
20*********************************************************************/
21
22/*
23
24 This file contains things relevant to handling incoming events.
25
26*/
27
28#include <config-X11.h>
29
30#include "client.h"
31#include "cursor.h"
32#include "decorations.h"
33#include "focuschain.h"
34#include "netinfo.h"
35#include "workspace.h"
36#include "atoms.h"
37#ifdef KWIN_BUILD_TABBOX
38#include "tabbox.h"
39#endif
40#include "group.h"
41#include "overlaywindow.h"
42#include "rules.h"
43#include "unmanaged.h"
44#include "useractions.h"
45#include "effects.h"
46#ifdef KWIN_BUILD_SCREENEDGES
47#include "screenedge.h"
48#endif
49#include "screens.h"
50#include "xcbutils.h"
51
52#include <QWhatsThis>
53
54#include <kkeyserver.h>
55
56#include <X11/extensions/shape.h>
57#include <X11/extensions/Xfixes.h>
58#include <X11/extensions/Xrandr.h>
59#include <X11/Xatom.h>
60#include <QX11Info>
61
62#include "composite.h"
63#include "killwindow.h"
64
65namespace KWin
66{
67
68extern int currentRefreshRate();
69
70// ****************************************
71// Workspace
72// ****************************************
73
74/*!
75 Handles workspace specific XEvents
76 */
77bool Workspace::workspaceEvent(XEvent * e)
78{
79 if (effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()
80 && (e->type == KeyPress || e->type == KeyRelease))
81 return false; // let Qt process it, it'll be intercepted again in eventFilter()
82
83 if (!m_windowKiller.isNull() && m_windowKiller->isActive() && m_windowKiller->isResponsibleForEvent(e->type)) {
84 m_windowKiller->processEvent(e);
85 // filter out the event
86 return true;
87 }
88
89 if (e->type == PropertyNotify || e->type == ClientMessage) {
90 unsigned long dirty[ NETRootInfo::PROPERTIES_SIZE ];
91 rootInfo()->event(e, dirty, NETRootInfo::PROPERTIES_SIZE);
92 if (dirty[ NETRootInfo::PROTOCOLS ] & NET::DesktopNames)
93 VirtualDesktopManager::self()->save();
94 if (dirty[ NETRootInfo::PROTOCOLS2 ] & NET::WM2DesktopLayout)
95 VirtualDesktopManager::self()->updateLayout();
96 }
97
98 // events that should be handled before Clients can get them
99 switch(e->type) {
100 case ButtonPress:
101 case ButtonRelease:
102 was_user_interaction = true;
103 // fallthrough
104 case MotionNotify:
105#ifdef KWIN_BUILD_TABBOX
106 if (TabBox::TabBox::self()->isGrabbed()) {
107#ifdef KWIN_BUILD_SCREENEDGES
108 ScreenEdges::self()->check(QPoint(e->xbutton.x_root, e->xbutton.y_root), QDateTime::fromMSecsSinceEpoch(xTime()), true);
109#endif
110 return TabBox::TabBox::self()->handleMouseEvent(e);
111 }
112#endif
113 if (effects && static_cast<EffectsHandlerImpl*>(effects)->checkInputWindowEvent(e)) {
114 return true;
115 }
116#ifdef KWIN_BUILD_SCREENEDGES
117 if (QWidget::mouseGrabber()) {
118 ScreenEdges::self()->check(QPoint(e->xbutton.x_root, e->xbutton.y_root), QDateTime::fromMSecsSinceEpoch(xTime()), true);
119 }
120#endif
121 break;
122 case KeyPress: {
123 was_user_interaction = true;
124 int keyQt;
125 KKeyServer::xEventToQt(e, &keyQt);
126// kDebug(125) << "Workspace::keyPress( " << keyQt << " )";
127 if (movingClient) {
128 movingClient->keyPressEvent(keyQt);
129 return true;
130 }
131#ifdef KWIN_BUILD_TABBOX
132 if (TabBox::TabBox::self()->isGrabbed()) {
133 TabBox::TabBox::self()->keyPress(keyQt);
134 return true;
135 }
136#endif
137 break;
138 }
139 case KeyRelease:
140 was_user_interaction = true;
141#ifdef KWIN_BUILD_TABBOX
142 if (TabBox::TabBox::self()->isGrabbed()) {
143 TabBox::TabBox::self()->keyRelease(e->xkey);
144 return true;
145 }
146#endif
147 break;
148 case ConfigureNotify:
149 if (e->xconfigure.event == rootWindow())
150 x_stacking_dirty = true;
151 break;
152 };
153
154 if (Client* c = findClient(WindowMatchPredicate(e->xany.window))) {
155 if (c->windowEvent(e))
156 return true;
157 } else if (Client* c = findClient(WrapperIdMatchPredicate(e->xany.window))) {
158 if (c->windowEvent(e))
159 return true;
160 } else if (Client* c = findClient(FrameIdMatchPredicate(e->xany.window))) {
161 if (c->windowEvent(e))
162 return true;
163 } else if (Client *c = findClient(InputIdMatchPredicate(e->xany.window))) {
164 if (c->windowEvent(e))
165 return true;
166 } else if (Unmanaged* c = findUnmanaged(WindowMatchPredicate(e->xany.window))) {
167 if (c->windowEvent(e))
168 return true;
169 } else {
170 Window special = findSpecialEventWindow(e);
171 if (special != None)
172 if (Client* c = findClient(WindowMatchPredicate(special))) {
173 if (c->windowEvent(e))
174 return true;
175 }
176
177 // We want to pass root window property events to effects
178 if (e->type == PropertyNotify && e->xany.window == rootWindow()) {
179 XPropertyEvent* re = &e->xproperty;
180 emit propertyNotify(re->atom);
181 }
182 }
183 if (movingClient != NULL && movingClient->moveResizeGrabWindow() == e->xany.window
184 && (e->type == MotionNotify || e->type == ButtonPress || e->type == ButtonRelease)) {
185 if (movingClient->windowEvent(e))
186 return true;
187 }
188
189 switch(e->type) {
190 case CreateNotify:
191 if (e->xcreatewindow.parent == rootWindow() &&
192 !QWidget::find(e->xcreatewindow.window) &&
193 !e->xcreatewindow.override_redirect) {
194 // see comments for allowClientActivation()
195 Time t = xTime();
196 XChangeProperty(display(), e->xcreatewindow.window,
197 atoms->kde_net_wm_user_creation_time, XA_CARDINAL,
198 32, PropModeReplace, (unsigned char *)&t, 1);
199 }
200 break;
201
202 case UnmapNotify: {
203 return (e->xunmap.event != e->xunmap.window); // hide wm typical event from Qt
204 }
205 case ReparentNotify: {
206 //do not confuse Qt with these events. After all, _we_ are the
207 //window manager who does the reparenting.
208 return true;
209 }
210 case DestroyNotify: {
211 return false;
212 }
213 case MapRequest: {
214 updateXTime();
215
216 if (Client* c = findClient(WindowMatchPredicate(e->xmaprequest.window))) {
217 // e->xmaprequest.window is different from e->xany.window
218 // TODO this shouldn't be necessary now
219 c->windowEvent(e);
220 FocusChain::self()->update(c, FocusChain::Update);
221 } else if ( true /*|| e->xmaprequest.parent != root */ ) {
222 // NOTICE don't check for the parent being the root window, this breaks when some app unmaps
223 // a window, changes something and immediately maps it back, without giving KWin
224 // a chance to reparent it back to root
225 // since KWin can get MapRequest only for root window children and
226 // children of WindowWrapper (=clients), the check is AFAIK useless anyway
227 // NOTICE: The save-set support in Client::mapRequestEvent() actually requires that
228 // this code doesn't check the parent to be root.
229 if (!createClient(e->xmaprequest.window, false))
230 XMapRaised(display(), e->xmaprequest.window);
231 }
232 return true;
233 }
234 case MapNotify: {
235 if (e->xmap.override_redirect) {
236 Unmanaged* c = findUnmanaged(WindowMatchPredicate(e->xmap.window));
237 if (c == NULL)
238 c = createUnmanaged(e->xmap.window);
239 if (c)
240 return c->windowEvent(e);
241 }
242 return (e->xmap.event != e->xmap.window); // hide wm typical event from Qt
243 }
244
245 case EnterNotify: {
246 if (QWhatsThis::inWhatsThisMode()) {
247 QWidget* w = QWidget::find(e->xcrossing.window);
248 if (w)
249 QWhatsThis::leaveWhatsThisMode();
250 }
251#ifdef KWIN_BUILD_SCREENEDGES
252 if (ScreenEdges::self()->isEntered(e))
253 return true;
254#endif
255 break;
256 }
257 case LeaveNotify: {
258 if (!QWhatsThis::inWhatsThisMode())
259 break;
260 // TODO is this cliente ever found, given that client events are searched above?
261 Client* c = findClient(FrameIdMatchPredicate(e->xcrossing.window));
262 if (c && e->xcrossing.detail != NotifyInferior)
263 QWhatsThis::leaveWhatsThisMode();
264 break;
265 }
266 case ConfigureRequest: {
267 if (e->xconfigurerequest.parent == rootWindow()) {
268 XWindowChanges wc;
269 wc.border_width = e->xconfigurerequest.border_width;
270 wc.x = e->xconfigurerequest.x;
271 wc.y = e->xconfigurerequest.y;
272 wc.width = e->xconfigurerequest.width;
273 wc.height = e->xconfigurerequest.height;
274 wc.sibling = None;
275 wc.stack_mode = Above;
276 unsigned int value_mask = e->xconfigurerequest.value_mask
277 & (CWX | CWY | CWWidth | CWHeight | CWBorderWidth);
278 XConfigureWindow(display(), e->xconfigurerequest.window, value_mask, &wc);
279 return true;
280 }
281 break;
282 }
283 case FocusIn:
284 if (e->xfocus.window == rootWindow()
285 && (e->xfocus.detail == NotifyDetailNone || e->xfocus.detail == NotifyPointerRoot)) {
286 updateXTime(); // focusToNull() uses xTime(), which is old now (FocusIn has no timestamp)
287 Window focus;
288 int revert;
289 XGetInputFocus(display(), &focus, &revert);
290 if (focus == None || focus == PointerRoot) {
291 //kWarning( 1212 ) << "X focus set to None/PointerRoot, reseting focus" ;
292 Client *c = mostRecentlyActivatedClient();
293 if (c != NULL)
294 requestFocus(c, true);
295 else if (activateNextClient(NULL))
296 ; // ok, activated
297 else
298 focusToNull();
299 }
300 }
301 // fall through
302 case FocusOut:
303 return true; // always eat these, they would tell Qt that KWin is the active app
304 case ClientMessage:
305#ifdef KWIN_BUILD_SCREENEDGES
306 if (ScreenEdges::self()->isEntered(e))
307 return true;
308#endif
309 break;
310 case Expose:
311 if (compositing()
312 && (e->xexpose.window == rootWindow() // root window needs repainting
313 || (m_compositor->overlayWindow() != None && e->xexpose.window == m_compositor->overlayWindow()))) { // overlay needs repainting
314 m_compositor->addRepaint(e->xexpose.x, e->xexpose.y, e->xexpose.width, e->xexpose.height);
315 }
316 break;
317 case VisibilityNotify:
318 if (compositing() && m_compositor->overlayWindow() != None && e->xvisibility.window == m_compositor->overlayWindow()) {
319 bool was_visible = m_compositor->isOverlayWindowVisible();
320 m_compositor->setOverlayWindowVisibility((e->xvisibility.state != VisibilityFullyObscured));
321 if (!was_visible && m_compositor->isOverlayWindowVisible()) {
322 // hack for #154825
323 m_compositor->addRepaintFull();
324 QTimer::singleShot(2000, m_compositor, SLOT(addRepaintFull()));
325 }
326 m_compositor->scheduleRepaint();
327 }
328 break;
329 default:
330 if (e->type == Xcb::Extensions::self()->randrNotifyEvent() && Xcb::Extensions::self()->isRandrAvailable()) {
331 XRRUpdateConfiguration(e);
332 if (compositing()) {
333 // desktopResized() should take care of when the size or
334 // shape of the desktop has changed, but we also want to
335 // catch refresh rate changes
336 if (m_compositor->xrrRefreshRate() != currentRefreshRate())
337 m_compositor->setCompositeResetTimer(0);
338 }
339
340 } else if (e->type == Xcb::Extensions::self()->syncAlarmNotifyEvent() && Xcb::Extensions::self()->isSyncAvailable()) {
341#ifdef HAVE_XSYNC
342 foreach (Client * c, clients)
343 c->syncEvent(reinterpret_cast< XSyncAlarmNotifyEvent* >(e));
344 foreach (Client * c, desktops)
345 c->syncEvent(reinterpret_cast< XSyncAlarmNotifyEvent* >(e));
346#endif
347 } else if (e->type == Xcb::Extensions::self()->fixesCursorNotifyEvent() && Xcb::Extensions::self()->isFixesAvailable()) {
348 Cursor::self()->notifyCursorChanged(reinterpret_cast<XFixesCursorNotifyEvent*>(e)->cursor_serial);
349 }
350 break;
351 }
352 return false;
353}
354
355// Used only to filter events that need to be processed by Qt first
356// (e.g. keyboard input to be composed), otherwise events are
357// handle by the XEvent filter above
358bool Workspace::workspaceEvent(QEvent* e)
359{
360 if ((e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease || e->type() == QEvent::ShortcutOverride)
361 && effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) {
362 static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(static_cast< QKeyEvent* >(e));
363 return true;
364 }
365 return false;
366}
367
368// Some events don't have the actual window which caused the event
369// as e->xany.window (e.g. ConfigureRequest), but as some other
370// field in the XEvent structure.
371xcb_window_t Workspace::findSpecialEventWindow(XEvent *e)
372{
373 switch(e->type) {
374 case CreateNotify:
375 return e->xcreatewindow.window;
376 case DestroyNotify:
377 return e->xdestroywindow.window;
378 case UnmapNotify:
379 return e->xunmap.window;
380 case MapNotify:
381 return e->xmap.window;
382 case MapRequest:
383 return e->xmaprequest.window;
384 case ReparentNotify:
385 return e->xreparent.window;
386 case ConfigureNotify:
387 return e->xconfigure.window;
388 case GravityNotify:
389 return e->xgravity.window;
390 case ConfigureRequest:
391 return e->xconfigurerequest.window;
392 case CirculateNotify:
393 return e->xcirculate.window;
394 case CirculateRequest:
395 return e->xcirculaterequest.window;
396 default:
397 return None;
398 };
399}
400
401// ****************************************
402// Client
403// ****************************************
404
405/*!
406 General handler for XEvents concerning the client window
407 */
408bool Client::windowEvent(XEvent* e)
409{
410 if (e->xany.window == window()) { // avoid doing stuff on frame or wrapper
411 unsigned long dirty[ 2 ];
412 double old_opacity = opacity();
413 info->event(e, dirty, 2); // pass through the NET stuff
414
415 if ((dirty[ NETWinInfo::PROTOCOLS ] & NET::WMName) != 0)
416 fetchName();
417 if ((dirty[ NETWinInfo::PROTOCOLS ] & NET::WMIconName) != 0)
418 fetchIconicName();
419 if ((dirty[ NETWinInfo::PROTOCOLS ] & NET::WMStrut) != 0
420 || (dirty[ NETWinInfo::PROTOCOLS2 ] & NET::WM2ExtendedStrut) != 0) {
421 workspace()->updateClientArea();
422 }
423 if ((dirty[ NETWinInfo::PROTOCOLS ] & NET::WMIcon) != 0)
424 getIcons();
425 // Note there's a difference between userTime() and info->userTime()
426 // info->userTime() is the value of the property, userTime() also includes
427 // updates of the time done by KWin (ButtonPress on windowrapper etc.).
428 if ((dirty[ NETWinInfo::PROTOCOLS2 ] & NET::WM2UserTime) != 0) {
429 workspace()->setWasUserInteraction();
430 updateUserTime(info->userTime());
431 }
432 if ((dirty[ NETWinInfo::PROTOCOLS2 ] & NET::WM2StartupId) != 0)
433 startupIdChanged();
434 if (dirty[ NETWinInfo::PROTOCOLS2 ] & NET::WM2Opacity) {
435 if (compositing()) {
436 addRepaintFull();
437 emit opacityChanged(this, old_opacity);
438 } else {
439 // forward to the frame if there's possibly another compositing manager running
440 NETWinInfo2 i(display(), frameId(), rootWindow(), 0);
441 i.setOpacity(info->opacity());
442 }
443 }
444 if (dirty[ NETWinInfo::PROTOCOLS2 ] & NET::WM2FrameOverlap) {
445 // ### Inform the decoration
446 }
447 }
448
449 switch(e->type) {
450 case UnmapNotify:
451 unmapNotifyEvent(&e->xunmap);
452 break;
453 case DestroyNotify:
454 destroyNotifyEvent(&e->xdestroywindow);
455 break;
456 case MapRequest:
457 // this one may pass the event to workspace
458 return mapRequestEvent(&e->xmaprequest);
459 case ConfigureRequest:
460 configureRequestEvent(&e->xconfigurerequest);
461 break;
462 case PropertyNotify:
463 propertyNotifyEvent(&e->xproperty);
464 break;
465 case KeyPress:
466 updateUserTime();
467 workspace()->setWasUserInteraction();
468 break;
469 case ButtonPress:
470 updateUserTime();
471 workspace()->setWasUserInteraction();
472 buttonPressEvent(e->xbutton.window, e->xbutton.button, e->xbutton.state,
473 e->xbutton.x, e->xbutton.y, e->xbutton.x_root, e->xbutton.y_root);
474 break;
475 case KeyRelease:
476 // don't update user time on releases
477 // e.g. if the user presses Alt+F2, the Alt release
478 // would appear as user input to the currently active window
479 break;
480 case ButtonRelease:
481 // don't update user time on releases
482 // e.g. if the user presses Alt+F2, the Alt release
483 // would appear as user input to the currently active window
484 buttonReleaseEvent(e->xbutton.window, e->xbutton.button, e->xbutton.state,
485 e->xbutton.x, e->xbutton.y, e->xbutton.x_root, e->xbutton.y_root);
486 break;
487 case MotionNotify:
488 motionNotifyEvent(e->xmotion.window, e->xmotion.state,
489 e->xmotion.x, e->xmotion.y, e->xmotion.x_root, e->xmotion.y_root);
490 workspace()->updateFocusMousePosition(QPoint(e->xmotion.x_root, e->xmotion.y_root));
491 break;
492 case EnterNotify:
493 enterNotifyEvent(&e->xcrossing);
494 // MotionNotify is guaranteed to be generated only if the mouse
495 // move start and ends in the window; for cases when it only
496 // starts or only ends there, Enter/LeaveNotify are generated.
497 // Fake a MotionEvent in such cases to make handle of mouse
498 // events simpler (Qt does that too).
499 motionNotifyEvent(e->xcrossing.window, e->xcrossing.state,
500 e->xcrossing.x, e->xcrossing.y, e->xcrossing.x_root, e->xcrossing.y_root);
501 workspace()->updateFocusMousePosition(QPoint(e->xcrossing.x_root, e->xcrossing.y_root));
502 break;
503 case LeaveNotify:
504 motionNotifyEvent(e->xcrossing.window, e->xcrossing.state,
505 e->xcrossing.x, e->xcrossing.y, e->xcrossing.x_root, e->xcrossing.y_root);
506 leaveNotifyEvent(&e->xcrossing);
507 // not here, it'd break following enter notify handling
508 // workspace()->updateFocusMousePosition( QPoint( e->xcrossing.x_root, e->xcrossing.y_root ));
509 break;
510 case FocusIn:
511 focusInEvent(&e->xfocus);
512 break;
513 case FocusOut:
514 focusOutEvent(&e->xfocus);
515 break;
516 case ReparentNotify:
517 break;
518 case ClientMessage:
519 clientMessageEvent(&e->xclient);
520 break;
521 default:
522 if (e->xany.window == window()) {
523 if (e->type == Xcb::Extensions::self()->shapeNotifyEvent()) {
524 detectShape(window()); // workaround for #19644
525 updateShape();
526 }
527 }
528 if (e->xany.window == frameId()) {
529 if (e->type == Xcb::Extensions::self()->damageNotifyEvent())
530 damageNotifyEvent();
531 }
532 break;
533 }
534 return true; // eat all events
535}
536
537/*!
538 Handles map requests of the client window
539 */
540bool Client::mapRequestEvent(XMapRequestEvent* e)
541{
542 if (e->window != window()) {
543 // Special support for the save-set feature, which is a bit broken.
544 // If there's a window from one client embedded in another one,
545 // e.g. using XEMBED, and the embedder suddenly loses its X connection,
546 // save-set will reparent the embedded window to its closest ancestor
547 // that will remains. Unfortunately, with reparenting window managers,
548 // this is not the root window, but the frame (or in KWin's case,
549 // it's the wrapper for the client window). In this case,
550 // the wrapper will get ReparentNotify for a window it won't know,
551 // which will be ignored, and then it gets MapRequest, as save-set
552 // always maps. Returning true here means that Workspace::workspaceEvent()
553 // will handle this MapRequest and manage this window (i.e. act as if
554 // it was reparented to root window).
555 if (e->parent == wrapperId())
556 return false;
557 return true; // no messing with frame etc.
558 }
559 // also copied in clientMessage()
560 if (isMinimized())
561 unminimize();
562 if (isShade())
563 setShade(ShadeNone);
564 if (!isOnCurrentDesktop()) {
565 if (workspace()->allowClientActivation(this))
566 workspace()->activateClient(this);
567 else
568 demandAttention();
569 }
570 return true;
571}
572
573/*!
574 Handles unmap notify events of the client window
575 */
576void Client::unmapNotifyEvent(XUnmapEvent* e)
577{
578 if (e->window != window())
579 return;
580 if (e->event != wrapperId()) {
581 // most probably event from root window when initially reparenting
582 bool ignore = true;
583 if (e->event == rootWindow() && e->send_event)
584 ignore = false; // XWithdrawWindow()
585 if (ignore)
586 return;
587 }
588
589 // check whether this is result of an XReparentWindow - client then won't be parented by wrapper
590 // in this case do not release the client (causes reparent to root, removal from saveSet and what not)
591 // but just destroy the client
592 Xcb::Tree tree(m_client);
593 xcb_window_t daddy = tree.parent();
594 if (daddy == m_wrapper) {
595 releaseWindow(); // unmapped from a regular client state
596 } else {
597 destroyClient(); // the client was moved to some other parent
598 }
599}
600
601void Client::destroyNotifyEvent(XDestroyWindowEvent* e)
602{
603 if (e->window != window())
604 return;
605 destroyClient();
606}
607
608
609/*!
610 Handles client messages for the client window
611*/
612void Client::clientMessageEvent(XClientMessageEvent* e)
613{
614 if (e->window != window())
615 return; // ignore frame/wrapper
616 // WM_STATE
617 if (e->message_type == atoms->kde_wm_change_state) {
618 bool avoid_animation = (e->data.l[ 1 ]);
619 if (e->data.l[ 0 ] == IconicState)
620 minimize();
621 else if (e->data.l[ 0 ] == NormalState) {
622 // copied from mapRequest()
623 if (isMinimized())
624 unminimize(avoid_animation);
625 if (isShade())
626 setShade(ShadeNone);
627 if (!isOnCurrentDesktop()) {
628 if (workspace()->allowClientActivation(this))
629 workspace()->activateClient(this);
630 else
631 demandAttention();
632 }
633 }
634 } else if (e->message_type == atoms->wm_change_state) {
635 if (e->data.l[0] == IconicState)
636 minimize();
637 return;
638 }
639}
640
641
642/*!
643 Handles configure requests of the client window
644 */
645void Client::configureRequestEvent(XConfigureRequestEvent* e)
646{
647 if (e->window != window())
648 return; // ignore frame/wrapper
649 if (isResize() || isMove())
650 return; // we have better things to do right now
651
652 if (fullscreen_mode == FullScreenNormal) { // refuse resizing of fullscreen windows
653 // but allow resizing fullscreen hacks in order to let them cancel fullscreen mode
654 sendSyntheticConfigureNotify();
655 return;
656 }
657 if (isSplash()) { // no manipulations with splashscreens either
658 sendSyntheticConfigureNotify();
659 return;
660 }
661
662 if (e->value_mask & CWBorderWidth) {
663 // first, get rid of a window border
664 XWindowChanges wc;
665 unsigned int value_mask = 0;
666
667 wc.border_width = 0;
668 value_mask = CWBorderWidth;
669 XConfigureWindow(display(), window(), value_mask, & wc);
670 }
671
672 if (e->value_mask & (CWX | CWY | CWHeight | CWWidth))
673 configureRequest(e->value_mask, e->x, e->y, e->width, e->height, 0, false);
674
675 if (e->value_mask & CWStackMode)
676 restackWindow(e->above, e->detail, NET::FromApplication, userTime(), false);
677
678 // Sending a synthetic configure notify always is fine, even in cases where
679 // the ICCCM doesn't require this - it can be though of as 'the WM decided to move
680 // the window later'. The client should not cause that many configure request,
681 // so this should not have any significant impact. With user moving/resizing
682 // the it should be optimized though (see also Client::setGeometry()/plainResize()/move()).
683 sendSyntheticConfigureNotify();
684
685 // SELI TODO accept configure requests for isDesktop windows (because kdesktop
686 // may get XRANDR resize event before kwin), but check it's still at the bottom?
687}
688
689
690/*!
691 Handles property changes of the client window
692 */
693void Client::propertyNotifyEvent(XPropertyEvent* e)
694{
695 Toplevel::propertyNotifyEvent(e);
696 if (e->window != window())
697 return; // ignore frame/wrapper
698 switch(e->atom) {
699 case XA_WM_NORMAL_HINTS:
700 getWmNormalHints();
701 break;
702 case XA_WM_NAME:
703 fetchName();
704 break;
705 case XA_WM_ICON_NAME:
706 fetchIconicName();
707 break;
708 case XA_WM_TRANSIENT_FOR:
709 readTransient();
710 break;
711 case XA_WM_HINTS:
712 getWMHints();
713 getIcons(); // because KWin::icon() uses WMHints as fallback
714 break;
715 default:
716 if (e->atom == atoms->wm_protocols)
717 getWindowProtocols();
718 else if (e->atom == atoms->motif_wm_hints)
719 getMotifHints();
720 else if (e->atom == atoms->net_wm_sync_request_counter)
721 getSyncCounter();
722 else if (e->atom == atoms->activities)
723 checkActivities();
724 else if (e->atom == atoms->kde_net_wm_block_compositing)
725 updateCompositeBlocking(true);
726 else if (e->atom == atoms->kde_first_in_window_list)
727 updateFirstInTabBox();
728 break;
729 }
730}
731
732
733void Client::enterNotifyEvent(XCrossingEvent* e)
734{
735 if (e->window != frameId())
736 return; // care only about entering the whole frame
737
738#define MOUSE_DRIVEN_FOCUS (!options->focusPolicyIsReasonable() || \
739 (options->focusPolicy() == Options::FocusFollowsMouse && options->isNextFocusPrefersMouse()))
740 if (e->mode == NotifyNormal || (e->mode == NotifyUngrab && MOUSE_DRIVEN_FOCUS)) {
741
742 if (options->isShadeHover()) {
743 cancelShadeHoverTimer();
744 if (isShade()) {
745 shadeHoverTimer = new QTimer(this);
746 connect(shadeHoverTimer, SIGNAL(timeout()), this, SLOT(shadeHover()));
747 shadeHoverTimer->setSingleShot(true);
748 shadeHoverTimer->start(options->shadeHoverInterval());
749 }
750 }
751#undef MOUSE_DRIVEN_FOCUS
752
753 if (options->focusPolicy() == Options::ClickToFocus || workspace()->userActionsMenu()->isShown())
754 return;
755
756 QPoint currentPos(e->x_root, e->y_root);
757 if (options->isAutoRaise() && !isDesktop() &&
758 !isDock() && workspace()->focusChangeEnabled() &&
759 currentPos != workspace()->focusMousePosition() &&
760 workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(),
761 options->isSeparateScreenFocus() ? screen() : -1) != this) {
762 delete autoRaiseTimer;
763 autoRaiseTimer = new QTimer(this);
764 connect(autoRaiseTimer, SIGNAL(timeout()), this, SLOT(autoRaise()));
765 autoRaiseTimer->setSingleShot(true);
766 autoRaiseTimer->start(options->autoRaiseInterval());
767 }
768
769 if (isDesktop() || isDock())
770 return;
771 // for FocusFollowsMouse, change focus only if the mouse has actually been moved, not if the focus
772 // change came because of window changes (e.g. closing a window) - #92290
773 if (options->focusPolicy() != Options::FocusFollowsMouse
774 || currentPos != workspace()->focusMousePosition()) {
775 workspace()->requestDelayFocus(this);
776 }
777 return;
778 }
779}
780
781void Client::leaveNotifyEvent(XCrossingEvent* e)
782{
783 if (e->window != frameId())
784 return; // care only about leaving the whole frame
785 if (e->mode == NotifyNormal) {
786 if (!buttonDown) {
787 mode = PositionCenter;
788 updateCursor();
789 }
790 bool lostMouse = !rect().contains(QPoint(e->x, e->y));
791 // 'lostMouse' wouldn't work with e.g. B2 or Keramik, which have non-rectangular decorations
792 // (i.e. the LeaveNotify event comes before leaving the rect and no LeaveNotify event
793 // comes after leaving the rect) - so lets check if the pointer is really outside the window
794
795 // TODO this still sucks if a window appears above this one - it should lose the mouse
796 // if this window is another client, but not if it's a popup ... maybe after KDE3.1 :(
797 // (repeat after me 'AARGHL!')
798 if (!lostMouse && e->detail != NotifyInferior) {
799 int d1, d2, d3, d4;
800 unsigned int d5;
801 Window w, child;
802 if (XQueryPointer(display(), frameId(), &w, &child, &d1, &d2, &d3, &d4, &d5) == False
803 || child == None)
804 lostMouse = true; // really lost the mouse
805 }
806 if (lostMouse) {
807 cancelAutoRaise();
808 workspace()->cancelDelayFocus();
809 cancelShadeHoverTimer();
810 if (shade_mode == ShadeHover && !moveResizeMode && !buttonDown) {
811 shadeHoverTimer = new QTimer(this);
812 connect(shadeHoverTimer, SIGNAL(timeout()), this, SLOT(shadeUnhover()));
813 shadeHoverTimer->setSingleShot(true);
814 shadeHoverTimer->start(options->shadeHoverInterval());
815 }
816 }
817 if (options->focusPolicy() == Options::FocusStrictlyUnderMouse && isActive() && lostMouse) {
818 workspace()->requestDelayFocus(0);
819 }
820 return;
821 }
822}
823
824#define XCapL KKeyServer::modXLock()
825#define XNumL KKeyServer::modXNumLock()
826#define XScrL KKeyServer::modXScrollLock()
827void Client::grabButton(int modifier)
828{
829 unsigned int mods[ 8 ] = {
830 0, XCapL, XNumL, XNumL | XCapL,
831 XScrL, XScrL | XCapL,
832 XScrL | XNumL, XScrL | XNumL | XCapL
833 };
834 for (int i = 0;
835 i < 8;
836 ++i)
837 XGrabButton(display(), AnyButton,
838 modifier | mods[ i ],
839 wrapperId(), false, ButtonPressMask,
840 GrabModeSync, GrabModeAsync, None, None);
841}
842
843void Client::ungrabButton(int modifier)
844{
845 unsigned int mods[ 8 ] = {
846 0, XCapL, XNumL, XNumL | XCapL,
847 XScrL, XScrL | XCapL,
848 XScrL | XNumL, XScrL | XNumL | XCapL
849 };
850 for (int i = 0;
851 i < 8;
852 ++i)
853 XUngrabButton(display(), AnyButton,
854 modifier | mods[ i ], wrapperId());
855}
856#undef XCapL
857#undef XNumL
858#undef XScrL
859
860/*
861 Releases the passive grab for some modifier combinations when a
862 window becomes active. This helps broken X programs that
863 missinterpret LeaveNotify events in grab mode to work properly
864 (Motif, AWT, Tk, ...)
865 */
866void Client::updateMouseGrab()
867{
868 if (workspace()->globalShortcutsDisabled()) {
869 XUngrabButton(display(), AnyButton, AnyModifier, wrapperId());
870 // keep grab for the simple click without modifiers if needed (see below)
871 bool not_obscured = workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), -1, true, false) == this;
872 if (!(!options->isClickRaise() || not_obscured))
873 grabButton(None);
874 return;
875 }
876 if (isActive() && !workspace()->forcedGlobalMouseGrab()) { // see Workspace::establishTabBoxGrab()
877 // first grab all modifier combinations
878 XGrabButton(display(), AnyButton, AnyModifier, wrapperId(), false,
879 ButtonPressMask,
880 GrabModeSync, GrabModeAsync,
881 None, None);
882 // remove the grab for no modifiers only if the window
883 // is unobscured or if the user doesn't want click raise
884 // (it is unobscured if it the topmost in the unconstrained stacking order, i.e. it is
885 // the most recently raised window)
886 bool not_obscured = workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), -1, true, false) == this;
887 if (!options->isClickRaise() || not_obscured)
888 ungrabButton(None);
889 else
890 grabButton(None);
891 ungrabButton(ShiftMask);
892 ungrabButton(ControlMask);
893 ungrabButton(ControlMask | ShiftMask);
894 } else {
895 XUngrabButton(display(), AnyButton, AnyModifier, wrapperId());
896 // simply grab all modifier combinations
897 XGrabButton(display(), AnyButton, AnyModifier, wrapperId(), false,
898 ButtonPressMask,
899 GrabModeSync, GrabModeAsync,
900 None, None);
901 }
902}
903
904// Qt propagates mouse events up the widget hierachy, which means events
905// for the decoration window cannot be (easily) intercepted as X11 events
906bool Client::eventFilter(QObject* o, QEvent* e)
907{
908 if (decoration == NULL
909 || o != decoration->widget())
910 return false;
911 if (e->type() == QEvent::MouseButtonPress) {
912 QMouseEvent* ev = static_cast< QMouseEvent* >(e);
913 return buttonPressEvent(decorationId(), qtToX11Button(ev->button()), qtToX11State(ev->buttons(), ev->modifiers()),
914 ev->x(), ev->y(), ev->globalX(), ev->globalY());
915 }
916 if (e->type() == QEvent::MouseButtonRelease) {
917 QMouseEvent* ev = static_cast< QMouseEvent* >(e);
918 return buttonReleaseEvent(decorationId(), qtToX11Button(ev->button()), qtToX11State(ev->buttons(), ev->modifiers()),
919 ev->x(), ev->y(), ev->globalX(), ev->globalY());
920 }
921 if (e->type() == QEvent::MouseMove) { // FRAME i fake z enter/leave?
922 QMouseEvent* ev = static_cast< QMouseEvent* >(e);
923 return motionNotifyEvent(decorationId(), qtToX11State(ev->buttons(), ev->modifiers()),
924 ev->x(), ev->y(), ev->globalX(), ev->globalY());
925 }
926 if (e->type() == QEvent::Wheel) {
927 QWheelEvent* ev = static_cast< QWheelEvent* >(e);
928 bool r = buttonPressEvent(decorationId(), ev->delta() > 0 ? Button4 : Button5, qtToX11State(ev->buttons(), ev->modifiers()),
929 ev->x(), ev->y(), ev->globalX(), ev->globalY());
930 r = r || buttonReleaseEvent(decorationId(), ev->delta() > 0 ? Button4 : Button5, qtToX11State(ev->buttons(), ev->modifiers()),
931 ev->x(), ev->y(), ev->globalX(), ev->globalY());
932 return r;
933 }
934 if (e->type() == QEvent::Resize) {
935 QResizeEvent* ev = static_cast< QResizeEvent* >(e);
936 // Filter out resize events that inform about size different than frame size.
937 // This will ensure that decoration->width() etc. and decoration->widget()->width() will be in sync.
938 // These events only seem to be delayed events from initial resizing before show() was called
939 // on the decoration widget.
940 if (ev->size() != (size() + QSize(padding_left + padding_right, padding_top + padding_bottom)))
941 return true;
942 // HACK: Avoid decoration redraw delays. On resize Qt sets WA_WStateConfigPending
943 // which delays all painting until a matching ConfigureNotify event comes.
944 // But this process itself is the window manager, so it's not needed
945 // to wait for that event, the geometry is known.
946 // Note that if Qt in the future changes how this flag is handled and what it
947 // triggers then this may potentionally break things. See mainly QETWidget::translateConfigEvent().
948 decoration->widget()->setAttribute(Qt::WA_WState_ConfigPending, false);
949 decoration->widget()->update();
950 return false;
951 }
952 return false;
953}
954
955static bool modKeyDown(int state) {
956 const uint keyModX = (options->keyCmdAllModKey() == Qt::Key_Meta) ?
957 KKeyServer::modXMeta() : KKeyServer::modXAlt();
958 return keyModX && (state & KKeyServer::accelModMaskX()) == keyModX;
959}
960
961
962// return value matters only when filtering events before decoration gets them
963bool Client::buttonPressEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root)
964{
965 if (buttonDown) {
966 if (w == wrapperId())
967 XAllowEvents(display(), SyncPointer, CurrentTime); //xTime());
968 return true;
969 }
970
971 if (w == wrapperId() || w == frameId() || w == decorationId() || w == inputId()) {
972 // FRAME neco s tohohle by se melo zpracovat, nez to dostane dekorace
973 updateUserTime();
974 workspace()->setWasUserInteraction();
975 const bool bModKeyHeld = modKeyDown(state);
976
977 if (isSplash()
978 && button == Button1 && !bModKeyHeld) {
979 // hide splashwindow if the user clicks on it
980 hideClient(true);
981 if (w == wrapperId())
982 XAllowEvents(display(), SyncPointer, CurrentTime); //xTime());
983 return true;
984 }
985
986 Options::MouseCommand com = Options::MouseNothing;
987 bool was_action = false;
988 bool perform_handled = false;
989 if (bModKeyHeld) {
990 was_action = true;
991 switch(button) {
992 case Button1:
993 com = options->commandAll1();
994 break;
995 case Button2:
996 com = options->commandAll2();
997 break;
998 case Button3:
999 com = options->commandAll3();
1000 break;
1001 case Button4:
1002 case Button5:
1003 com = options->operationWindowMouseWheel(button == Button4 ? 120 : -120);
1004 break;
1005 }
1006 } else {
1007 // inactive inner window
1008 if (!isActive() && w == wrapperId() && button < 6) {
1009 was_action = true;
1010 perform_handled = true;
1011 switch(button) {
1012 case Button1:
1013 com = options->commandWindow1();
1014 break;
1015 case Button2:
1016 com = options->commandWindow2();
1017 break;
1018 case Button3:
1019 com = options->commandWindow3();
1020 break;
1021 case Button4:
1022 case Button5:
1023 com = options->commandWindowWheel();
1024 break;
1025 }
1026 }
1027 // active inner window
1028 if (isActive() && w == wrapperId()
1029 && options->isClickRaise() && button < 4) { // exclude wheel
1030 com = Options::MouseActivateRaiseAndPassClick;
1031 was_action = true;
1032 perform_handled = true;
1033 }
1034 }
1035 if (was_action) {
1036 bool replay = performMouseCommand(com, QPoint(x_root, y_root), perform_handled);
1037
1038 if (isSpecialWindow())
1039 replay = true;
1040
1041 if (w == wrapperId()) // these can come only from a grab
1042 XAllowEvents(display(), replay ? ReplayPointer : SyncPointer, CurrentTime); //xTime());
1043 return true;
1044 }
1045 }
1046
1047 if (w == wrapperId()) { // these can come only from a grab
1048 XAllowEvents(display(), ReplayPointer, CurrentTime); //xTime());
1049 return true;
1050 }
1051 if (w == inputId()) {
1052 x = x_root - geometry().x() + padding_left;
1053 y = y_root - geometry().y() + padding_top;
1054 // New API processes core events FIRST and only passes unused ones to the decoration
1055 return processDecorationButtonPress(button, state, x, y, x_root, y_root, true);
1056 }
1057 if (w == decorationId()) {
1058 if (dynamic_cast<KDecorationUnstable*>(decoration))
1059 // New API processes core events FIRST and only passes unused ones to the decoration
1060 return processDecorationButtonPress(button, state, x, y, x_root, y_root, true);
1061 return false;
1062 }
1063 if (w == frameId())
1064 processDecorationButtonPress(button, state, x, y, x_root, y_root);
1065 return true;
1066}
1067
1068
1069// this function processes button press events only after decoration decides not to handle them,
1070// unlike buttonPressEvent(), which (when the window is decoration) filters events before decoration gets them
1071bool Client::processDecorationButtonPress(int button, int /*state*/, int x, int y, int x_root, int y_root,
1072 bool ignoreMenu)
1073{
1074 Options::MouseCommand com = Options::MouseNothing;
1075 bool active = isActive();
1076 if (!wantsInput()) // we cannot be active, use it anyway
1077 active = true;
1078
1079 if (button == Button1)
1080 com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1();
1081 else if (button == Button2)
1082 com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2();
1083 else if (button == Button3)
1084 com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3();
1085 if (button == Button1
1086 && com != Options::MouseOperationsMenu // actions where it's not possible to get the matching
1087 && com != Options::MouseMinimize // mouse release event
1088 && com != Options::MouseDragTab) {
1089 mode = mousePosition(QPoint(x, y));
1090 buttonDown = true;
1091 moveOffset = QPoint(x - padding_left, y - padding_top);
1092 invertedMoveOffset = rect().bottomRight() - moveOffset;
1093 unrestrictedMoveResize = false;
1094 startDelayedMoveResize();
1095 updateCursor();
1096 }
1097 // In the new API the decoration may process the menu action to display an inactive tab's menu.
1098 // If the event is unhandled then the core will create one for the active window in the group.
1099 if (!ignoreMenu || com != Options::MouseOperationsMenu)
1100 performMouseCommand(com, QPoint(x_root, y_root));
1101 return !( // Return events that should be passed to the decoration in the new API
1102 com == Options::MouseRaise ||
1103 com == Options::MouseOperationsMenu ||
1104 com == Options::MouseActivateAndRaise ||
1105 com == Options::MouseActivate ||
1106 com == Options::MouseActivateRaiseAndPassClick ||
1107 com == Options::MouseActivateAndPassClick ||
1108 com == Options::MouseDragTab ||
1109 com == Options::MouseNothing);
1110}
1111
1112// called from decoration
1113void Client::processMousePressEvent(QMouseEvent* e)
1114{
1115 if (e->type() != QEvent::MouseButtonPress) {
1116 kWarning(1212) << "processMousePressEvent()" ;
1117 return;
1118 }
1119 int button;
1120 switch(e->button()) {
1121 case Qt::LeftButton:
1122 button = Button1;
1123 break;
1124 case Qt::MidButton:
1125 button = Button2;
1126 break;
1127 case Qt::RightButton:
1128 button = Button3;
1129 break;
1130 default:
1131 return;
1132 }
1133 processDecorationButtonPress(button, e->buttons(), e->x(), e->y(), e->globalX(), e->globalY());
1134}
1135
1136// return value matters only when filtering events before decoration gets them
1137bool Client::buttonReleaseEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root)
1138{
1139 if (w == decorationId() && !buttonDown)
1140 return false;
1141 if (w == wrapperId()) {
1142 XAllowEvents(display(), SyncPointer, CurrentTime); //xTime());
1143 return true;
1144 }
1145 if (w != frameId() && w != decorationId() && w != inputId() && w != moveResizeGrabWindow())
1146 return true;
1147 x = this->x(); // translate from grab window to local coords
1148 y = this->y();
1149
1150 // Check whether other buttons are still left pressed
1151 int buttonMask = XCB_BUTTON_MASK_1 | XCB_BUTTON_MASK_2 | XCB_BUTTON_MASK_3;
1152 if (button == XCB_BUTTON_INDEX_1)
1153 buttonMask &= ~XCB_BUTTON_MASK_1;
1154 else if (button == XCB_BUTTON_INDEX_2)
1155 buttonMask &= ~XCB_BUTTON_MASK_2;
1156 else if (button == XCB_BUTTON_INDEX_3)
1157 buttonMask &= ~XCB_BUTTON_MASK_3;
1158
1159 if ((state & buttonMask) == 0) {
1160 buttonDown = false;
1161 stopDelayedMoveResize();
1162 if (moveResizeMode) {
1163 finishMoveResize(false);
1164 // mouse position is still relative to old Client position, adjust it
1165 QPoint mousepos(x_root - x + padding_left, y_root - y + padding_top);
1166 mode = mousePosition(mousepos);
1167 } else if (decorationPlugin()->supportsTabbing())
1168 return false;
1169 updateCursor();
1170 }
1171 return true;
1172}
1173
1174static bool was_motion = false;
1175static Time next_motion_time = CurrentTime;
1176// Check whole incoming X queue for MotionNotify events
1177// checking whole queue is done by always returning False in the predicate.
1178// If there are more MotionNotify events in the queue, all until the last
1179// one may be safely discarded (if a ButtonRelease event comes, a MotionNotify
1180// will be faked from it, so there's no need to check other events).
1181// This helps avoiding being overloaded by being flooded from many events
1182// from the XServer.
1183static Bool motion_predicate(Display*, XEvent* ev, XPointer)
1184{
1185 if (ev->type == MotionNotify) {
1186 was_motion = true;
1187 next_motion_time = ev->xmotion.time; // for setting time
1188 }
1189 return False;
1190}
1191
1192static bool waitingMotionEvent()
1193{
1194// The queue doesn't need to be checked until the X timestamp
1195// of processes events reaches the timestamp of the last suitable
1196// MotionNotify event in the queue.
1197 if (next_motion_time != CurrentTime
1198 && timestampCompare(xTime(), next_motion_time) < 0)
1199 return true;
1200 was_motion = false;
1201 XSync(display(), False); // this helps to discard more MotionNotify events
1202 XEvent dummy;
1203 XCheckIfEvent(display(), &dummy, motion_predicate, NULL);
1204 return was_motion;
1205}
1206
1207// Checks if the mouse cursor is near the edge of the screen and if so activates quick tiling or maximization
1208void Client::checkQuickTilingMaximizationZones(int xroot, int yroot)
1209{
1210
1211 QuickTileMode mode = QuickTileNone;
1212 for (int i=0; i<screens()->count(); ++i) {
1213
1214 if (!screens()->geometry(i).contains(QPoint(xroot, yroot)))
1215 continue;
1216
1217 QRect area = workspace()->clientArea(MaximizeArea, QPoint(xroot, yroot), desktop());
1218 if (options->electricBorderTiling()) {
1219 if (xroot <= area.x() + 20)
1220 mode |= QuickTileLeft;
1221 else if (xroot >= area.x() + area.width() - 20)
1222 mode |= QuickTileRight;
1223 }
1224
1225 if (mode != QuickTileNone) {
1226 if (yroot <= area.y() + area.height() * options->electricBorderCornerRatio())
1227 mode |= QuickTileTop;
1228 else if (yroot >= area.y() + area.height() - area.height() * options->electricBorderCornerRatio())
1229 mode |= QuickTileBottom;
1230 } else if (options->electricBorderMaximize() && yroot <= area.y() + 5 && isMaximizable())
1231 mode = QuickTileMaximize;
1232 break; // no point in checking other screens to contain this... "point"...
1233 }
1234 setElectricBorderMode(mode);
1235 setElectricBorderMaximizing(mode != QuickTileNone);
1236}
1237
1238// return value matters only when filtering events before decoration gets them
1239bool Client::motionNotifyEvent(xcb_window_t w, int state, int x, int y, int x_root, int y_root)
1240{
1241 if (w != frameId() && w != decorationId() && w != inputId() && w != moveResizeGrabWindow())
1242 return true; // care only about the whole frame
1243 if (!buttonDown) {
1244 QPoint mousePos(x, y);
1245 if (w == frameId())
1246 mousePos += QPoint(padding_left, padding_top);
1247 if (w == inputId()) {
1248 int x = x_root - geometry().x() + padding_left;
1249 int y = y_root - geometry().y() + padding_top;
1250 mousePos = QPoint(x, y);
1251 }
1252 Position newmode = modKeyDown(state) ? PositionCenter : mousePosition(mousePos);
1253 if (newmode != mode) {
1254 mode = newmode;
1255 updateCursor();
1256 }
1257 // reset the timestamp for the optimization, otherwise with long passivity
1258 // the option in waitingMotionEvent() may be always true
1259 next_motion_time = CurrentTime;
1260 return false;
1261 }
1262 if (w == moveResizeGrabWindow()) {
1263 x = this->x(); // translate from grab window to local coords
1264 y = this->y();
1265 }
1266 if (!waitingMotionEvent()) {
1267 QRect oldGeo = geometry();
1268 handleMoveResize(x, y, x_root, y_root);
1269 if (!isFullScreen() && isMove()) {
1270 if (quick_tile_mode != QuickTileNone && oldGeo != geometry()) {
1271 GeometryUpdatesBlocker blocker(this);
1272 setQuickTileMode(QuickTileNone);
1273 moveOffset = QPoint(double(moveOffset.x()) / double(oldGeo.width()) * double(geom_restore.width()),
1274 double(moveOffset.y()) / double(oldGeo.height()) * double(geom_restore.height()));
1275 moveResizeGeom = geom_restore;
1276 handleMoveResize(x, y, x_root, y_root); // fix position
1277 } else if (quick_tile_mode == QuickTileNone && isResizable()) {
1278 checkQuickTilingMaximizationZones(x_root, y_root);
1279 }
1280 }
1281 }
1282 return true;
1283}
1284
1285void Client::focusInEvent(XFocusInEvent* e)
1286{
1287 if (e->window != window())
1288 return; // only window gets focus
1289 if (e->mode == NotifyUngrab)
1290 return; // we don't care
1291 if (e->detail == NotifyPointer)
1292 return; // we don't care
1293 if (!isShown(false) || !isOnCurrentDesktop()) // we unmapped it, but it got focus meanwhile ->
1294 return; // activateNextClient() already transferred focus elsewhere
1295 // check if this client is in should_get_focus list or if activation is allowed
1296 bool activate = workspace()->allowClientActivation(this, -1U, true);
1297 workspace()->gotFocusIn(this); // remove from should_get_focus list
1298 if (activate)
1299 setActive(true);
1300 else {
1301 workspace()->restoreFocus();
1302 demandAttention();
1303 }
1304}
1305
1306// When a client loses focus, FocusOut events are usually immediatelly
1307// followed by FocusIn events for another client that gains the focus
1308// (unless the focus goes to another screen, or to the nofocus widget).
1309// Without this check, the former focused client would have to be
1310// deactivated, and after that, the new one would be activated, with
1311// a short time when there would be no active client. This can cause
1312// flicker sometimes, e.g. when a fullscreen is shown, and focus is transferred
1313// from it to its transient, the fullscreen would be kept in the Active layer
1314// at the beginning and at the end, but not in the middle, when the active
1315// client would be temporarily none (see Client::belongToLayer() ).
1316// Therefore, the events queue is checked, whether it contains the matching
1317// FocusIn event, and if yes, deactivation of the previous client will
1318// be skipped, as activation of the new one will automatically deactivate
1319// previously active client.
1320static bool follows_focusin = false;
1321static bool follows_focusin_failed = false;
1322static Bool predicate_follows_focusin(Display*, XEvent* e, XPointer arg)
1323{
1324 Q_UNUSED(arg)
1325 if (follows_focusin || follows_focusin_failed)
1326 return False;
1327 if (e->type == FocusIn && workspace()->findClient(WindowMatchPredicate(e->xfocus.window))) {
1328 // found FocusIn
1329 follows_focusin = true;
1330 return False;
1331 }
1332 // events that may be in the queue before the FocusIn event that's being
1333 // searched for
1334 if (e->type == FocusIn || e->type == FocusOut || e->type == KeymapNotify)
1335 return False;
1336 follows_focusin_failed = true; // a different event - stop search
1337 return False;
1338}
1339
1340static bool check_follows_focusin(Client* c)
1341{
1342 follows_focusin = follows_focusin_failed = false;
1343 XEvent dummy;
1344 // XCheckIfEvent() is used to make the search non-blocking, the predicate
1345 // always returns False, so nothing is removed from the events queue.
1346 // XPeekIfEvent() would block.
1347 XCheckIfEvent(display(), &dummy, predicate_follows_focusin, (XPointer)c);
1348 return follows_focusin;
1349}
1350
1351
1352void Client::focusOutEvent(XFocusOutEvent* e)
1353{
1354 if (e->window != window())
1355 return; // only window gets focus
1356 if (e->mode == NotifyGrab)
1357 return; // we don't care
1358 if (isShade())
1359 return; // here neither
1360 if (e->detail != NotifyNonlinear
1361 && e->detail != NotifyNonlinearVirtual)
1362 // SELI check all this
1363 return; // hack for motif apps like netscape
1364 if (QApplication::activePopupWidget())
1365 return;
1366 if (!check_follows_focusin(this))
1367 setActive(false);
1368}
1369
1370// performs _NET_WM_MOVERESIZE
1371void Client::NETMoveResize(int x_root, int y_root, NET::Direction direction)
1372{
1373 if (direction == NET::Move)
1374 performMouseCommand(Options::MouseMove, QPoint(x_root, y_root));
1375 else if (moveResizeMode && direction == NET::MoveResizeCancel) {
1376 finishMoveResize(true);
1377 buttonDown = false;
1378 updateCursor();
1379 } else if (direction >= NET::TopLeft && direction <= NET::Left) {
1380 static const Position convert[] = {
1381 PositionTopLeft,
1382 PositionTop,
1383 PositionTopRight,
1384 PositionRight,
1385 PositionBottomRight,
1386 PositionBottom,
1387 PositionBottomLeft,
1388 PositionLeft
1389 };
1390 if (!isResizable() || isShade())
1391 return;
1392 if (moveResizeMode)
1393 finishMoveResize(false);
1394 buttonDown = true;
1395 moveOffset = QPoint(x_root - x(), y_root - y()); // map from global
1396 invertedMoveOffset = rect().bottomRight() - moveOffset;
1397 unrestrictedMoveResize = false;
1398 mode = convert[ direction ];
1399 if (!startMoveResize())
1400 buttonDown = false;
1401 updateCursor();
1402 } else if (direction == NET::KeyboardMove) {
1403 // ignore mouse coordinates given in the message, mouse position is used by the moving algorithm
1404 Cursor::setPos(geometry().center());
1405 performMouseCommand(Options::MouseUnrestrictedMove, geometry().center());
1406 } else if (direction == NET::KeyboardSize) {
1407 // ignore mouse coordinates given in the message, mouse position is used by the resizing algorithm
1408 Cursor::setPos(geometry().bottomRight());
1409 performMouseCommand(Options::MouseUnrestrictedResize, geometry().bottomRight());
1410 }
1411}
1412
1413void Client::keyPressEvent(uint key_code)
1414{
1415 updateUserTime();
1416 if (!isMove() && !isResize())
1417 return;
1418 bool is_control = key_code & Qt::CTRL;
1419 bool is_alt = key_code & Qt::ALT;
1420 key_code = key_code & ~Qt::KeyboardModifierMask;
1421 int delta = is_control ? 1 : is_alt ? 32 : 8;
1422 QPoint pos = cursorPos();
1423 switch(key_code) {
1424 case Qt::Key_Left:
1425 pos.rx() -= delta;
1426 break;
1427 case Qt::Key_Right:
1428 pos.rx() += delta;
1429 break;
1430 case Qt::Key_Up:
1431 pos.ry() -= delta;
1432 break;
1433 case Qt::Key_Down:
1434 pos.ry() += delta;
1435 break;
1436 case Qt::Key_Space:
1437 case Qt::Key_Return:
1438 case Qt::Key_Enter:
1439 finishMoveResize(false);
1440 buttonDown = false;
1441 updateCursor();
1442 break;
1443 case Qt::Key_Escape:
1444 finishMoveResize(true);
1445 buttonDown = false;
1446 updateCursor();
1447 break;
1448 default:
1449 return;
1450 }
1451 Cursor::setPos(pos);
1452}
1453
1454#ifdef HAVE_XSYNC
1455void Client::syncEvent(XSyncAlarmNotifyEvent* e)
1456{
1457 if (e->alarm == syncRequest.alarm && XSyncValueEqual(e->counter_value, syncRequest.value)) {
1458 setReadyForPainting();
1459 syncRequest.isPending = false;
1460 if (syncRequest.failsafeTimeout)
1461 syncRequest.failsafeTimeout->stop();
1462 if (isResize()) {
1463 if (syncRequest.timeout)
1464 syncRequest.timeout->stop();
1465 performMoveResize();
1466 } else // setReadyForPainting does as well, but there's a small chance for resize syncs after the resize ended
1467 addRepaintFull();
1468 }
1469}
1470#endif
1471
1472// ****************************************
1473// Unmanaged
1474// ****************************************
1475
1476bool Unmanaged::windowEvent(XEvent* e)
1477{
1478 double old_opacity = opacity();
1479 unsigned long dirty[ 2 ];
1480 info->event(e, dirty, 2); // pass through the NET stuff
1481 if (dirty[ NETWinInfo::PROTOCOLS2 ] & NET::WM2Opacity) {
1482 if (compositing()) {
1483 addRepaintFull();
1484 emit opacityChanged(this, old_opacity);
1485 }
1486 }
1487 switch(e->type) {
1488 case UnmapNotify:
1489 workspace()->updateFocusMousePosition(Cursor::pos());
1490 unmapNotifyEvent(&e->xunmap);
1491 break;
1492 case MapNotify:
1493 mapNotifyEvent(&e->xmap);
1494 break;
1495 case ConfigureNotify:
1496 configureNotifyEvent(&e->xconfigure);
1497 break;
1498 case PropertyNotify:
1499 propertyNotifyEvent(&e->xproperty);
1500 break;
1501 default: {
1502 if (e->type == Xcb::Extensions::self()->shapeNotifyEvent()) {
1503 detectShape(window());
1504 addRepaintFull();
1505 addWorkspaceRepaint(geometry()); // in case shape change removes part of this window
1506 emit geometryShapeChanged(this, geometry());
1507 }
1508 if (e->type == Xcb::Extensions::self()->damageNotifyEvent())
1509 damageNotifyEvent();
1510 break;
1511 }
1512 }
1513 return false; // don't eat events, even our own unmanaged widgets are tracked
1514}
1515
1516void Unmanaged::mapNotifyEvent(XMapEvent*)
1517{
1518}
1519
1520void Unmanaged::unmapNotifyEvent(XUnmapEvent*)
1521{
1522 release();
1523}
1524
1525void Unmanaged::configureNotifyEvent(XConfigureEvent* e)
1526{
1527 if (effects)
1528 static_cast<EffectsHandlerImpl*>(effects)->checkInputWindowStacking(); // keep them on top
1529 QRect newgeom(e->x, e->y, e->width, e->height);
1530 if (newgeom != geom) {
1531 addWorkspaceRepaint(visibleRect()); // damage old area
1532 QRect old = geom;
1533 geom = newgeom;
1534 emit geometryChanged(); // update shadow region
1535 addRepaintFull();
1536 if (old.size() != geom.size())
1537 discardWindowPixmap();
1538 emit geometryShapeChanged(this, old);
1539 }
1540}
1541
1542// ****************************************
1543// Toplevel
1544// ****************************************
1545
1546void Toplevel::propertyNotifyEvent(XPropertyEvent* e)
1547{
1548 if (e->window != window())
1549 return; // ignore frame/wrapper
1550 switch(e->atom) {
1551 default:
1552 if (e->atom == atoms->wm_client_leader)
1553 getWmClientLeader();
1554 else if (e->atom == atoms->wm_window_role)
1555 getWindowRole();
1556 else if (e->atom == atoms->kde_net_wm_shadow)
1557 getShadow();
1558 else if (e->atom == atoms->net_wm_opaque_region)
1559 getWmOpaqueRegion();
1560 else if (e->atom == atoms->kde_skip_close_animation)
1561 getSkipCloseAnimation();
1562 break;
1563 }
1564 emit propertyNotify(this, e->atom);
1565}
1566
1567// ****************************************
1568// Group
1569// ****************************************
1570
1571bool Group::groupEvent(XEvent* e)
1572{
1573 unsigned long dirty[ 2 ];
1574 leader_info->event(e, dirty, 2); // pass through the NET stuff
1575 if ((dirty[ NETWinInfo::PROTOCOLS2 ] & NET::WM2StartupId) != 0)
1576 startupIdChanged();
1577 return false;
1578}
1579
1580
1581} // namespace
1582