1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6     SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
7 
8     SPDX-License-Identifier: GPL-2.0-or-later
9 */
10 
11 /*
12 
13  This file contains things relevant to handling incoming events.
14 
15 */
16 
17 #include "x11client.h"
18 #include "cursor.h"
19 #include "focuschain.h"
20 #include "netinfo.h"
21 #include "workspace.h"
22 #include "atoms.h"
23 #ifdef KWIN_BUILD_TABBOX
24 #include "tabbox.h"
25 #endif
26 #include "group.h"
27 #include "rules.h"
28 #include "unmanaged.h"
29 #include "useractions.h"
30 #include "effects.h"
31 #include "screens.h"
32 #include "xcbutils.h"
33 
34 #include <KDecoration2/Decoration>
35 
36 #include <QApplication>
37 #include <QDebug>
38 #include <QHoverEvent>
39 #include <QKeyEvent>
40 #include <QMouseEvent>
41 #include <QStyleHints>
42 #include <QWheelEvent>
43 
44 #include <kkeyserver.h>
45 
46 #include <xcb/damage.h>
47 #include <xcb/sync.h>
48 #ifdef XCB_ICCCM_FOUND
49 #include <xcb/xcb_icccm.h>
50 #endif
51 
52 #include "composite.h"
53 #include "x11eventfilter.h"
54 
55 #include "wayland_server.h"
56 #include <KWaylandServer/surface_interface.h>
57 
58 #ifndef XCB_GE_GENERIC
59 #define XCB_GE_GENERIC 35
60 typedef struct xcb_ge_generic_event_t {
61     uint8_t  response_type; /**<  */
62     uint8_t  extension; /**<  */
63     uint16_t sequence; /**<  */
64     uint32_t length; /**<  */
65     uint16_t event_type; /**<  */
66     uint8_t  pad0[22]; /**<  */
67     uint32_t full_sequence; /**<  */
68 } xcb_ge_generic_event_t;
69 #endif
70 
71 namespace KWin
72 {
73 
74 // ****************************************
75 // Workspace
76 // ****************************************
77 
findEventWindow(xcb_generic_event_t * event)78 static xcb_window_t findEventWindow(xcb_generic_event_t *event)
79 {
80     const uint8_t eventType = event->response_type & ~0x80;
81     switch(eventType) {
82     case XCB_KEY_PRESS:
83     case XCB_KEY_RELEASE:
84         return reinterpret_cast<xcb_key_press_event_t*>(event)->event;
85     case XCB_BUTTON_PRESS:
86     case XCB_BUTTON_RELEASE:
87         return reinterpret_cast<xcb_button_press_event_t*>(event)->event;
88     case XCB_MOTION_NOTIFY:
89         return reinterpret_cast<xcb_motion_notify_event_t*>(event)->event;
90     case XCB_ENTER_NOTIFY:
91     case XCB_LEAVE_NOTIFY:
92         return reinterpret_cast<xcb_enter_notify_event_t*>(event)->event;
93     case XCB_FOCUS_IN:
94     case XCB_FOCUS_OUT:
95         return reinterpret_cast<xcb_focus_in_event_t*>(event)->event;
96     case XCB_EXPOSE:
97         return reinterpret_cast<xcb_expose_event_t*>(event)->window;
98     case XCB_GRAPHICS_EXPOSURE:
99         return reinterpret_cast<xcb_graphics_exposure_event_t*>(event)->drawable;
100     case XCB_NO_EXPOSURE:
101         return reinterpret_cast<xcb_no_exposure_event_t*>(event)->drawable;
102     case XCB_VISIBILITY_NOTIFY:
103         return reinterpret_cast<xcb_visibility_notify_event_t*>(event)->window;
104     case XCB_CREATE_NOTIFY:
105         return reinterpret_cast<xcb_create_notify_event_t*>(event)->window;
106     case XCB_DESTROY_NOTIFY:
107         return reinterpret_cast<xcb_destroy_notify_event_t*>(event)->window;
108     case XCB_UNMAP_NOTIFY:
109         return reinterpret_cast<xcb_unmap_notify_event_t*>(event)->window;
110     case XCB_MAP_NOTIFY:
111         return reinterpret_cast<xcb_map_notify_event_t*>(event)->window;
112     case XCB_MAP_REQUEST:
113         return reinterpret_cast<xcb_map_request_event_t*>(event)->window;
114     case XCB_REPARENT_NOTIFY:
115         return reinterpret_cast<xcb_reparent_notify_event_t*>(event)->window;
116     case XCB_CONFIGURE_NOTIFY:
117         return reinterpret_cast<xcb_configure_notify_event_t*>(event)->window;
118     case XCB_CONFIGURE_REQUEST:
119         return reinterpret_cast<xcb_configure_request_event_t*>(event)->window;
120     case XCB_GRAVITY_NOTIFY:
121         return reinterpret_cast<xcb_gravity_notify_event_t*>(event)->window;
122     case XCB_RESIZE_REQUEST:
123         return reinterpret_cast<xcb_resize_request_event_t*>(event)->window;
124     case XCB_CIRCULATE_NOTIFY:
125     case XCB_CIRCULATE_REQUEST:
126         return reinterpret_cast<xcb_circulate_notify_event_t*>(event)->window;
127     case XCB_PROPERTY_NOTIFY:
128         return reinterpret_cast<xcb_property_notify_event_t*>(event)->window;
129     case XCB_COLORMAP_NOTIFY:
130         return reinterpret_cast<xcb_colormap_notify_event_t*>(event)->window;
131     case XCB_CLIENT_MESSAGE:
132         return reinterpret_cast<xcb_client_message_event_t*>(event)->window;
133     default:
134         // extension handling
135         if (eventType == Xcb::Extensions::self()->shapeNotifyEvent()) {
136             return reinterpret_cast<xcb_shape_notify_event_t*>(event)->affected_window;
137         }
138         if (eventType == Xcb::Extensions::self()->damageNotifyEvent()) {
139             return reinterpret_cast<xcb_damage_notify_event_t*>(event)->drawable;
140         }
141         return XCB_WINDOW_NONE;
142     }
143 }
144 
145 /**
146  * Handles workspace specific XCB event
147  */
workspaceEvent(xcb_generic_event_t * e)148 bool Workspace::workspaceEvent(xcb_generic_event_t *e)
149 {
150     const uint8_t eventType = e->response_type & ~0x80;
151     if (effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()
152             && (eventType == XCB_KEY_PRESS || eventType == XCB_KEY_RELEASE))
153         return false; // let Qt process it, it'll be intercepted again in eventFilter()
154 
155     // events that should be handled before Clients can get them
156     switch (eventType) {
157     case XCB_CONFIGURE_NOTIFY:
158         if (reinterpret_cast<xcb_configure_notify_event_t*>(e)->event == rootWindow())
159             markXStackingOrderAsDirty();
160         break;
161     };
162 
163     const xcb_window_t eventWindow = findEventWindow(e);
164     if (eventWindow != XCB_WINDOW_NONE) {
165         if (X11Client *c = findClient(Predicate::WindowMatch, eventWindow)) {
166             if (c->windowEvent(e))
167                 return true;
168         } else if (X11Client *c = findClient(Predicate::WrapperIdMatch, eventWindow)) {
169             if (c->windowEvent(e))
170                 return true;
171         } else if (X11Client *c = findClient(Predicate::FrameIdMatch, eventWindow)) {
172             if (c->windowEvent(e))
173                 return true;
174         } else if (X11Client *c = findClient(Predicate::InputIdMatch, eventWindow)) {
175             if (c->windowEvent(e))
176                 return true;
177         } else if (Unmanaged* c = findUnmanaged(eventWindow)) {
178             if (c->windowEvent(e))
179                 return true;
180         }
181     }
182 
183     switch (eventType) {
184     case XCB_CREATE_NOTIFY: {
185         const auto *event = reinterpret_cast<xcb_create_notify_event_t*>(e);
186         if (event->parent == rootWindow() &&
187                 !QWidget::find(event->window) &&
188                 !event->override_redirect) {
189             // see comments for allowClientActivation()
190             updateXTime();
191             const xcb_timestamp_t t = xTime();
192             xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, event->window, atoms->kde_net_wm_user_creation_time, XCB_ATOM_CARDINAL, 32, 1, &t);
193         }
194         break;
195     }
196     case XCB_UNMAP_NOTIFY: {
197         const auto *event = reinterpret_cast<xcb_unmap_notify_event_t*>(e);
198         return (event->event != event->window);   // hide wm typical event from Qt
199     }
200     case XCB_REPARENT_NOTIFY: {
201         //do not confuse Qt with these events. After all, _we_ are the
202         //window manager who does the reparenting.
203         return true;
204     }
205     case XCB_MAP_REQUEST: {
206         updateXTime();
207 
208         const auto *event = reinterpret_cast<xcb_map_request_event_t*>(e);
209         if (X11Client *c = findClient(Predicate::WindowMatch, event->window)) {
210             // e->xmaprequest.window is different from e->xany.window
211             // TODO this shouldn't be necessary now
212             c->windowEvent(e);
213             FocusChain::self()->update(c, FocusChain::Update);
214         } else if ( true /*|| e->xmaprequest.parent != root */ ) {
215             // NOTICE don't check for the parent being the root window, this breaks when some app unmaps
216             // a window, changes something and immediately maps it back, without giving KWin
217             // a chance to reparent it back to root
218             // since KWin can get MapRequest only for root window children and
219             // children of WindowWrapper (=clients), the check is AFAIK useless anyway
220             // NOTICE: The save-set support in X11Client::mapRequestEvent() actually requires that
221             // this code doesn't check the parent to be root.
222             if (!createClient(event->window, false)) {
223                 xcb_map_window(connection(), event->window);
224                 const uint32_t values[] = { XCB_STACK_MODE_ABOVE };
225                 xcb_configure_window(connection(), event->window, XCB_CONFIG_WINDOW_STACK_MODE, values);
226             }
227         }
228         return true;
229     }
230     case XCB_MAP_NOTIFY: {
231         const auto *event = reinterpret_cast<xcb_map_notify_event_t*>(e);
232         if (event->override_redirect) {
233             Unmanaged* c = findUnmanaged(event->window);
234             if (c == nullptr)
235                 c = createUnmanaged(event->window);
236             if (c) {
237                 // if hasScheduledRelease is true, it means a unamp and map sequence has occurred.
238                 // since release is scheduled after map notify, this old Unmanaged will get released
239                 // before KWIN has chance to remanage it again. so release it right now.
240                 if (c->hasScheduledRelease()) {
241                     c->release();
242                     c = createUnmanaged(event->window);
243                 }
244                 if (c)
245                     return c->windowEvent(e);
246             }
247         }
248         return (event->event != event->window);   // hide wm typical event from Qt
249     }
250 
251     case XCB_CONFIGURE_REQUEST: {
252         const auto *event = reinterpret_cast<xcb_configure_request_event_t*>(e);
253         if (event->parent == rootWindow()) {
254             uint32_t values[5] = { 0, 0, 0, 0, 0};
255             const uint32_t value_mask = event->value_mask
256                                         & (XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH);
257             int i = 0;
258             if (value_mask & XCB_CONFIG_WINDOW_X) {
259                 values[i++] = event->x;
260             }
261             if (value_mask & XCB_CONFIG_WINDOW_Y) {
262                 values[i++] = event->y;
263             }
264             if (value_mask & XCB_CONFIG_WINDOW_WIDTH) {
265                 values[i++] = event->width;
266             }
267             if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
268                 values[i++] = event->height;
269             }
270             if (value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) {
271                 values[i++] = event->border_width;
272             }
273             xcb_configure_window(connection(), event->window, value_mask, values);
274             return true;
275         }
276         break;
277     }
278     case XCB_FOCUS_IN: {
279         const auto *event = reinterpret_cast<xcb_focus_in_event_t*>(e);
280         if (event->event == rootWindow()
281                 && (event->detail == XCB_NOTIFY_DETAIL_NONE || event->detail == XCB_NOTIFY_DETAIL_POINTER_ROOT || event->detail == XCB_NOTIFY_DETAIL_INFERIOR)) {
282             Xcb::CurrentInput currentInput;
283             updateXTime(); // focusToNull() uses xTime(), which is old now (FocusIn has no timestamp)
284             // it seems we can "loose" focus reversions when the closing client hold a grab
285             // => catch the typical pattern (though we don't want the focus on the root anyway) #348935
286             const bool lostFocusPointerToRoot = currentInput->focus == rootWindow() && event->detail == XCB_NOTIFY_DETAIL_INFERIOR;
287             if (!currentInput.isNull() && (currentInput->focus == XCB_WINDOW_NONE || currentInput->focus == XCB_INPUT_FOCUS_POINTER_ROOT || lostFocusPointerToRoot)) {
288                 //kWarning( 1212 ) << "X focus set to None/PointerRoot, reseting focus" ;
289                 AbstractClient *c = mostRecentlyActivatedClient();
290                 if (c != nullptr)
291                     requestFocus(c, true);
292                 else if (activateNextClient(nullptr))
293                     ; // ok, activated
294                 else
295                     focusToNull();
296             }
297         }
298     }
299         // fall through
300     case XCB_FOCUS_OUT:
301         return true; // always eat these, they would tell Qt that KWin is the active app
302     default:
303         break;
304     }
305     return false;
306 }
307 
308 // Used only to filter events that need to be processed by Qt first
309 // (e.g. keyboard input to be composed), otherwise events are
310 // handle by the XEvent filter above
workspaceEvent(QEvent * e)311 bool Workspace::workspaceEvent(QEvent* e)
312 {
313     if ((e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease || e->type() == QEvent::ShortcutOverride)
314             && effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) {
315         static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(static_cast< QKeyEvent* >(e));
316         return true;
317     }
318     return false;
319 }
320 
321 // ****************************************
322 // Client
323 // ****************************************
324 
325 /**
326  * General handler for XEvents concerning the client window
327  */
windowEvent(xcb_generic_event_t * e)328 bool X11Client::windowEvent(xcb_generic_event_t *e)
329 {
330     if (findEventWindow(e) == window()) { // avoid doing stuff on frame or wrapper
331         NET::Properties dirtyProperties;
332         NET::Properties2 dirtyProperties2;
333         info->event(e, &dirtyProperties, &dirtyProperties2);   // pass through the NET stuff
334 
335         if ((dirtyProperties & NET::WMName) != 0)
336             fetchName();
337         if ((dirtyProperties & NET::WMIconName) != 0)
338             fetchIconicName();
339         if ((dirtyProperties & NET::WMStrut) != 0
340                 || (dirtyProperties2 & NET::WM2ExtendedStrut) != 0) {
341             workspace()->updateClientArea();
342         }
343         if ((dirtyProperties & NET::WMIcon) != 0)
344             getIcons();
345         // Note there's a difference between userTime() and info->userTime()
346         // info->userTime() is the value of the property, userTime() also includes
347         // updates of the time done by KWin (ButtonPress on windowrapper etc.).
348         if ((dirtyProperties2 & NET::WM2UserTime) != 0) {
349             workspace()->setWasUserInteraction();
350             updateUserTime(info->userTime());
351         }
352         if ((dirtyProperties2 & NET::WM2StartupId) != 0)
353             startupIdChanged();
354         if (dirtyProperties2 & NET::WM2Opacity) {
355             if (Compositor::compositing()) {
356                 setOpacity(info->opacityF());
357             } else {
358                 // forward to the frame if there's possibly another compositing manager running
359                 NETWinInfo i(connection(), frameId(), rootWindow(), NET::Properties(), NET::Properties2());
360                 i.setOpacity(info->opacity());
361             }
362         }
363         if (dirtyProperties2.testFlag(NET::WM2WindowRole)) {
364             Q_EMIT windowRoleChanged();
365         }
366         if (dirtyProperties2.testFlag(NET::WM2WindowClass)) {
367             getResourceClass();
368         }
369         if (dirtyProperties2.testFlag(NET::WM2BlockCompositing)) {
370             setBlockingCompositing(info->isBlockingCompositing());
371         }
372         if (dirtyProperties2.testFlag(NET::WM2GroupLeader)) {
373             checkGroup();
374             updateAllowedActions(); // Group affects isMinimizable()
375         }
376         if (dirtyProperties2.testFlag(NET::WM2Urgency)) {
377             updateUrgency();
378         }
379         if (dirtyProperties2 & NET::WM2OpaqueRegion) {
380             getWmOpaqueRegion();
381         }
382         if (dirtyProperties2 & NET::WM2DesktopFileName) {
383             setDesktopFileName(QByteArray(info->desktopFileName()));
384         }
385         if (dirtyProperties2 & NET::WM2GTKFrameExtents) {
386             setClientFrameExtents(info->gtkFrameExtents());
387         }
388     }
389 
390     const uint8_t eventType = e->response_type & ~0x80;
391     switch(eventType) {
392     case XCB_UNMAP_NOTIFY:
393         unmapNotifyEvent(reinterpret_cast<xcb_unmap_notify_event_t*>(e));
394         break;
395     case XCB_DESTROY_NOTIFY:
396         destroyNotifyEvent(reinterpret_cast<xcb_destroy_notify_event_t*>(e));
397         break;
398     case XCB_MAP_REQUEST:
399         // this one may pass the event to workspace
400         return mapRequestEvent(reinterpret_cast<xcb_map_request_event_t*>(e));
401     case XCB_CONFIGURE_REQUEST:
402         configureRequestEvent(reinterpret_cast<xcb_configure_request_event_t*>(e));
403         break;
404     case XCB_PROPERTY_NOTIFY:
405         propertyNotifyEvent(reinterpret_cast<xcb_property_notify_event_t*>(e));
406         break;
407     case XCB_KEY_PRESS:
408         updateUserTime(reinterpret_cast<xcb_key_press_event_t*>(e)->time);
409         break;
410     case XCB_BUTTON_PRESS: {
411         const auto *event = reinterpret_cast<xcb_button_press_event_t*>(e);
412         updateUserTime(event->time);
413         buttonPressEvent(event->event, event->detail, event->state,
414                          event->event_x, event->event_y, event->root_x, event->root_y, event->time);
415         break;
416     }
417     case XCB_KEY_RELEASE:
418         // don't update user time on releases
419         // e.g. if the user presses Alt+F2, the Alt release
420         // would appear as user input to the currently active window
421         break;
422     case XCB_BUTTON_RELEASE: {
423         const auto *event = reinterpret_cast<xcb_button_release_event_t*>(e);
424         // don't update user time on releases
425         // e.g. if the user presses Alt+F2, the Alt release
426         // would appear as user input to the currently active window
427         buttonReleaseEvent(event->event, event->detail, event->state,
428                            event->event_x, event->event_y, event->root_x, event->root_y);
429         break;
430     }
431     case XCB_MOTION_NOTIFY: {
432         const auto *event = reinterpret_cast<xcb_motion_notify_event_t*>(e);
433         motionNotifyEvent(event->event, event->state,
434                           event->event_x, event->event_y, event->root_x, event->root_y);
435         workspace()->updateFocusMousePosition(QPoint(event->root_x, event->root_y));
436         break;
437     }
438     case XCB_ENTER_NOTIFY: {
439         auto *event = reinterpret_cast<xcb_enter_notify_event_t*>(e);
440         enterNotifyEvent(event);
441         // MotionNotify is guaranteed to be generated only if the mouse
442         // move start and ends in the window; for cases when it only
443         // starts or only ends there, Enter/LeaveNotify are generated.
444         // Fake a MotionEvent in such cases to make handle of mouse
445         // events simpler (Qt does that too).
446         motionNotifyEvent(event->event, event->state,
447                           event->event_x, event->event_y, event->root_x, event->root_y);
448         workspace()->updateFocusMousePosition(QPoint(event->root_x, event->root_y));
449         break;
450     }
451     case XCB_LEAVE_NOTIFY: {
452         auto *event = reinterpret_cast<xcb_leave_notify_event_t*>(e);
453         motionNotifyEvent(event->event, event->state,
454                           event->event_x, event->event_y, event->root_x, event->root_y);
455         leaveNotifyEvent(event);
456         // not here, it'd break following enter notify handling
457         // workspace()->updateFocusMousePosition( QPoint( e->xcrossing.x_root, e->xcrossing.y_root ));
458         break;
459     }
460     case XCB_FOCUS_IN:
461         focusInEvent(reinterpret_cast<xcb_focus_in_event_t*>(e));
462         break;
463     case XCB_FOCUS_OUT:
464         focusOutEvent(reinterpret_cast<xcb_focus_out_event_t*>(e));
465         break;
466     case XCB_REPARENT_NOTIFY:
467         break;
468     case XCB_CLIENT_MESSAGE:
469         clientMessageEvent(reinterpret_cast<xcb_client_message_event_t*>(e));
470         break;
471     case XCB_EXPOSE: {
472         xcb_expose_event_t *event = reinterpret_cast<xcb_expose_event_t*>(e);
473         if (event->window == frameId() && !Compositor::self()->isActive()) {
474             // TODO: only repaint required areas
475             triggerDecorationRepaint();
476         }
477         break;
478     }
479     default:
480         if (eventType == Xcb::Extensions::self()->shapeNotifyEvent() && reinterpret_cast<xcb_shape_notify_event_t*>(e)->affected_window == window()) {
481             detectShape(window());  // workaround for #19644
482             updateShape();
483         }
484         if (eventType == Xcb::Extensions::self()->damageNotifyEvent() && reinterpret_cast<xcb_damage_notify_event_t*>(e)->drawable == frameId())
485             damageNotifyEvent();
486         break;
487     }
488     return true; // eat all events
489 }
490 
491 /**
492  * Handles map requests of the client window
493  */
mapRequestEvent(xcb_map_request_event_t * e)494 bool X11Client::mapRequestEvent(xcb_map_request_event_t *e)
495 {
496     if (e->window != window()) {
497         // Special support for the save-set feature, which is a bit broken.
498         // If there's a window from one client embedded in another one,
499         // e.g. using XEMBED, and the embedder suddenly loses its X connection,
500         // save-set will reparent the embedded window to its closest ancestor
501         // that will remains. Unfortunately, with reparenting window managers,
502         // this is not the root window, but the frame (or in KWin's case,
503         // it's the wrapper for the client window). In this case,
504         // the wrapper will get ReparentNotify for a window it won't know,
505         // which will be ignored, and then it gets MapRequest, as save-set
506         // always maps. Returning true here means that Workspace::workspaceEvent()
507         // will handle this MapRequest and manage this window (i.e. act as if
508         // it was reparented to root window).
509         if (e->parent == wrapperId())
510             return false;
511         return true; // no messing with frame etc.
512     }
513     // also copied in clientMessage()
514     if (isMinimized())
515         unminimize();
516     if (isShade())
517         setShade(ShadeNone);
518     if (!isOnCurrentDesktop()) {
519         if (workspace()->allowClientActivation(this))
520             workspace()->activateClient(this);
521         else
522             demandAttention();
523     }
524     return true;
525 }
526 
527 /**
528  * Handles unmap notify events of the client window
529  */
unmapNotifyEvent(xcb_unmap_notify_event_t * e)530 void X11Client::unmapNotifyEvent(xcb_unmap_notify_event_t *e)
531 {
532     if (e->window != window())
533         return;
534     if (e->event != wrapperId()) {
535         // most probably event from root window when initially reparenting
536         bool ignore = true;
537         if (e->event == rootWindow() && (e->response_type & 0x80))
538             ignore = false; // XWithdrawWindow()
539         if (ignore)
540             return;
541     }
542 
543     // check whether this is result of an XReparentWindow - client then won't be parented by wrapper
544     // in this case do not release the client (causes reparent to root, removal from saveSet and what not)
545     // but just destroy the client
546     Xcb::Tree tree(m_client);
547     xcb_window_t daddy = tree.parent();
548     if (daddy == m_wrapper) {
549         releaseWindow(); // unmapped from a regular client state
550     } else {
551         destroyClient(); // the client was moved to some other parent
552     }
553 }
554 
destroyNotifyEvent(xcb_destroy_notify_event_t * e)555 void X11Client::destroyNotifyEvent(xcb_destroy_notify_event_t *e)
556 {
557     if (e->window != window())
558         return;
559     destroyClient();
560 }
561 
562 
563 /**
564  * Handles client messages for the client window
565  */
clientMessageEvent(xcb_client_message_event_t * e)566 void X11Client::clientMessageEvent(xcb_client_message_event_t *e)
567 {
568     Toplevel::clientMessageEvent(e);
569     if (e->window != window())
570         return; // ignore frame/wrapper
571     // WM_STATE
572     if (e->type == atoms->wm_change_state) {
573         if (e->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC)
574             minimize();
575         return;
576     }
577 }
578 
579 
580 /**
581  * Handles configure  requests of the client window
582  */
configureRequestEvent(xcb_configure_request_event_t * e)583 void X11Client::configureRequestEvent(xcb_configure_request_event_t *e)
584 {
585     if (e->window != window())
586         return; // ignore frame/wrapper
587     if (isInteractiveResize() || isInteractiveMove())
588         return; // we have better things to do right now
589 
590     if (m_fullscreenMode == FullScreenNormal) { // refuse resizing of fullscreen windows
591         // but allow resizing fullscreen hacks in order to let them cancel fullscreen mode
592         sendSyntheticConfigureNotify();
593         return;
594     }
595     if (isSplash()) {  // no manipulations with splashscreens either
596         sendSyntheticConfigureNotify();
597         return;
598     }
599 
600     if (e->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) {
601         // first, get rid of a window border
602         m_client.setBorderWidth(0);
603     }
604 
605     if (e->value_mask & (XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_WIDTH))
606         configureRequest(e->value_mask, e->x, e->y, e->width, e->height, 0, false);
607 
608     if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE)
609         restackWindow(e->sibling, e->stack_mode, NET::FromApplication, userTime(), false);
610 
611     // Sending a synthetic configure notify always is fine, even in cases where
612     // the ICCCM doesn't require this - it can be though of as 'the WM decided to move
613     // the window later'. The client should not cause that many configure request,
614     // so this should not have any significant impact. With user moving/resizing
615     // the it should be optimized though (see also X11Client::setGeometry()/resize()/move()).
616     sendSyntheticConfigureNotify();
617 
618     // SELI TODO accept configure requests for isDesktop windows (because kdesktop
619     // may get XRANDR resize event before kwin), but check it's still at the bottom?
620 }
621 
622 
623 /**
624  * Handles property changes of the client window
625  */
propertyNotifyEvent(xcb_property_notify_event_t * e)626 void X11Client::propertyNotifyEvent(xcb_property_notify_event_t *e)
627 {
628     Toplevel::propertyNotifyEvent(e);
629     if (e->window != window())
630         return; // ignore frame/wrapper
631     switch(e->atom) {
632     case XCB_ATOM_WM_NORMAL_HINTS:
633         getWmNormalHints();
634         break;
635     case XCB_ATOM_WM_NAME:
636         fetchName();
637         break;
638     case XCB_ATOM_WM_ICON_NAME:
639         fetchIconicName();
640         break;
641     case XCB_ATOM_WM_TRANSIENT_FOR:
642         readTransient();
643         break;
644     case XCB_ATOM_WM_HINTS:
645         getIcons(); // because KWin::icon() uses WMHints as fallback
646         break;
647     default:
648         if (e->atom == atoms->motif_wm_hints) {
649             getMotifHints();
650         } else if (e->atom == atoms->net_wm_sync_request_counter)
651             getSyncCounter();
652         else if (e->atom == atoms->activities)
653             checkActivities();
654         else if (e->atom == atoms->kde_first_in_window_list)
655             updateFirstInTabBox();
656         else if (e->atom == atoms->kde_color_sheme)
657             updateColorScheme();
658         else if (e->atom == atoms->kde_screen_edge_show)
659             updateShowOnScreenEdge();
660         else if (e->atom == atoms->kde_net_wm_appmenu_service_name)
661             checkApplicationMenuServiceName();
662         else if (e->atom == atoms->kde_net_wm_appmenu_object_path)
663             checkApplicationMenuObjectPath();
664         break;
665     }
666 }
667 
668 
enterNotifyEvent(xcb_enter_notify_event_t * e)669 void X11Client::enterNotifyEvent(xcb_enter_notify_event_t *e)
670 {
671     if (waylandServer()) {
672         return;
673     }
674     if (e->event != frameId())
675         return; // care only about entering the whole frame
676 
677 #define MOUSE_DRIVEN_FOCUS (!options->focusPolicyIsReasonable() || \
678                             (options->focusPolicy() == Options::FocusFollowsMouse && options->isNextFocusPrefersMouse()))
679     if (e->mode == XCB_NOTIFY_MODE_NORMAL || (e->mode == XCB_NOTIFY_MODE_UNGRAB && MOUSE_DRIVEN_FOCUS)) {
680 #undef MOUSE_DRIVEN_FOCUS
681 
682         enterEvent(QPoint(e->root_x, e->root_y));
683         return;
684     }
685 }
686 
leaveNotifyEvent(xcb_leave_notify_event_t * e)687 void X11Client::leaveNotifyEvent(xcb_leave_notify_event_t *e)
688 {
689     if (waylandServer()) {
690         return;
691     }
692     if (e->event != frameId())
693         return; // care only about leaving the whole frame
694     if (e->mode == XCB_NOTIFY_MODE_NORMAL) {
695         if (!isInteractiveMoveResizePointerButtonDown()) {
696             setInteractiveMoveResizePointerMode(PositionCenter);
697             updateCursor();
698         }
699         bool lostMouse = !rect().contains(QPoint(e->event_x, e->event_y));
700         // 'lostMouse' wouldn't work with e.g. B2 or Keramik, which have non-rectangular decorations
701         // (i.e. the LeaveNotify event comes before leaving the rect and no LeaveNotify event
702         // comes after leaving the rect) - so lets check if the pointer is really outside the window
703 
704         // TODO this still sucks if a window appears above this one - it should lose the mouse
705         // if this window is another client, but not if it's a popup ... maybe after KDE3.1 :(
706         // (repeat after me 'AARGHL!')
707         if (!lostMouse && e->detail != XCB_NOTIFY_DETAIL_INFERIOR) {
708             Xcb::Pointer pointer(frameId());
709             if (!pointer || !pointer->same_screen || pointer->child == XCB_WINDOW_NONE) {
710                 // really lost the mouse
711                 lostMouse = true;
712             }
713         }
714         if (lostMouse) {
715             leaveEvent();
716             if (isDecorated()) {
717                 // sending a move instead of a leave. With leave we need to send proper coords, with move it's handled internally
718                 QHoverEvent leaveEvent(QEvent::HoverMove, QPointF(-1, -1), QPointF(-1, -1), Qt::NoModifier);
719                 QCoreApplication::sendEvent(decoration(), &leaveEvent);
720             }
721         }
722         if (options->focusPolicy() == Options::FocusStrictlyUnderMouse && isActive() && lostMouse) {
723             workspace()->requestDelayFocus(nullptr);
724         }
725         return;
726     }
727 }
728 
x11CommandAllModifier()729 static uint16_t x11CommandAllModifier()
730 {
731     switch (options->commandAllModifier()) {
732     case Qt::MetaModifier:
733         return KKeyServer::modXMeta();
734     case Qt::AltModifier:
735         return KKeyServer::modXAlt();
736     default:
737         return 0;
738     }
739 }
740 
741 #define XCapL KKeyServer::modXLock()
742 #define XNumL KKeyServer::modXNumLock()
743 #define XScrL KKeyServer::modXScrollLock()
establishCommandWindowGrab(uint8_t button)744 void X11Client::establishCommandWindowGrab(uint8_t button)
745 {
746     // Unfortunately there are a lot of possible modifier combinations that we need to take into
747     // account. We tackle that problem in a kind of smart way. First, we grab the button with all
748     // possible modifiers, then we ungrab the ones that are relevant only to commandAllx().
749 
750     m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, XCB_MOD_MASK_ANY, button);
751 
752     uint16_t x11Modifier = x11CommandAllModifier();
753 
754     unsigned int mods[ 8 ] = {
755         0, XCapL, XNumL, XNumL | XCapL,
756         XScrL, XScrL | XCapL,
757         XScrL | XNumL, XScrL | XNumL | XCapL
758     };
759     for (int i = 0;
760             i < 8;
761             ++i)
762         m_wrapper.ungrabButton(x11Modifier | mods[ i ], button);
763 }
764 
establishCommandAllGrab(uint8_t button)765 void X11Client::establishCommandAllGrab(uint8_t button)
766 {
767     uint16_t x11Modifier = x11CommandAllModifier();
768 
769     unsigned int mods[ 8 ] = {
770         0, XCapL, XNumL, XNumL | XCapL,
771         XScrL, XScrL | XCapL,
772         XScrL | XNumL, XScrL | XNumL | XCapL
773     };
774     for (int i = 0;
775             i < 8;
776             ++i)
777         m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, x11Modifier | mods[ i ], button);
778 }
779 #undef XCapL
780 #undef XNumL
781 #undef XScrL
782 
updateMouseGrab()783 void X11Client::updateMouseGrab()
784 {
785     if (waylandServer()) {
786         return;
787     }
788 
789     xcb_ungrab_button(connection(), XCB_BUTTON_INDEX_ANY, m_wrapper, XCB_MOD_MASK_ANY);
790 
791     if (TabBox::TabBox::self()->forcedGlobalMouseGrab()) { // see TabBox::establishTabBoxGrab()
792         m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
793         return;
794     }
795 
796     // When a passive grab is activated or deactivated, the X server will generate crossing
797     // events as if the pointer were suddenly to warp from its current position to some position
798     // in the grab window. Some /broken/ X11 clients do get confused by such EnterNotify and
799     // LeaveNotify events so we release the passive grab for the active window.
800     //
801     // The passive grab below is established so the window can be raised or activated when it
802     // is clicked.
803     if ((options->focusPolicyIsReasonable() && !isActive()) ||
804             (options->isClickRaise() && !isMostRecentlyRaised())) {
805         if (options->commandWindow1() != Options::MouseNothing) {
806             establishCommandWindowGrab(XCB_BUTTON_INDEX_1);
807         }
808         if (options->commandWindow2() != Options::MouseNothing) {
809             establishCommandWindowGrab(XCB_BUTTON_INDEX_2);
810         }
811         if (options->commandWindow3() != Options::MouseNothing) {
812             establishCommandWindowGrab(XCB_BUTTON_INDEX_3);
813         }
814         if (options->commandWindowWheel() != Options::MouseNothing) {
815             establishCommandWindowGrab(XCB_BUTTON_INDEX_4);
816             establishCommandWindowGrab(XCB_BUTTON_INDEX_5);
817         }
818     }
819 
820     // We want to grab <command modifier> + buttons no matter what state the window is in. The
821     // client will receive funky EnterNotify and LeaveNotify events, but there is nothing that
822     // we can do about it, unfortunately.
823 
824     if (!workspace()->globalShortcutsDisabled()) {
825         if (options->commandAll1() != Options::MouseNothing) {
826             establishCommandAllGrab(XCB_BUTTON_INDEX_1);
827         }
828         if (options->commandAll2() != Options::MouseNothing) {
829             establishCommandAllGrab(XCB_BUTTON_INDEX_2);
830         }
831         if (options->commandAll3() != Options::MouseNothing) {
832             establishCommandAllGrab(XCB_BUTTON_INDEX_3);
833         }
834         if (options->commandAllWheel() != Options::MouseWheelNothing) {
835             establishCommandAllGrab(XCB_BUTTON_INDEX_4);
836             establishCommandAllGrab(XCB_BUTTON_INDEX_5);
837         }
838     }
839 }
840 
modKeyDown(int state)841 static bool modKeyDown(int state) {
842     const uint keyModX = (options->keyCmdAllModKey() == Qt::Key_Meta) ?
843                                                     KKeyServer::modXMeta() : KKeyServer::modXAlt();
844     return keyModX  && (state & KKeyServer::accelModMaskX()) == keyModX;
845 }
846 
847 
848 // return value matters only when filtering events before decoration gets them
buttonPressEvent(xcb_window_t w,int button,int state,int x,int y,int x_root,int y_root,xcb_timestamp_t time)849 bool X11Client::buttonPressEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root, xcb_timestamp_t time)
850 {
851     if (waylandServer()) {
852         return true;
853     }
854     if (isInteractiveMoveResizePointerButtonDown()) {
855         if (w == wrapperId())
856             xcb_allow_events(connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME);  //xTime());
857         return true;
858     }
859 
860     if (w == wrapperId() || w == frameId() || w == inputId()) {
861         // FRAME neco s tohohle by se melo zpracovat, nez to dostane dekorace
862         updateUserTime(time);
863         const bool bModKeyHeld = modKeyDown(state);
864 
865         if (isSplash()
866                 && button == XCB_BUTTON_INDEX_1 && !bModKeyHeld) {
867             // hide splashwindow if the user clicks on it
868             hideClient(true);
869             if (w == wrapperId())
870                 xcb_allow_events(connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME);  //xTime());
871             return true;
872         }
873 
874         Options::MouseCommand com = Options::MouseNothing;
875         bool was_action = false;
876         if (bModKeyHeld) {
877             was_action = true;
878             switch(button) {
879             case XCB_BUTTON_INDEX_1:
880                 com = options->commandAll1();
881                 break;
882             case XCB_BUTTON_INDEX_2:
883                 com = options->commandAll2();
884                 break;
885             case XCB_BUTTON_INDEX_3:
886                 com = options->commandAll3();
887                 break;
888             case XCB_BUTTON_INDEX_4:
889             case XCB_BUTTON_INDEX_5:
890                 com = options->operationWindowMouseWheel(button == XCB_BUTTON_INDEX_4 ? 120 : -120);
891                 break;
892             }
893         } else {
894             if (w == wrapperId()) {
895                 if (button < 4) {
896                     com = getMouseCommand(x11ToQtMouseButton(button), &was_action);
897                 } else if (button < 6) {
898                     com = getWheelCommand(Qt::Vertical, &was_action);
899                 }
900             }
901         }
902         if (was_action) {
903             bool replay = performMouseCommand(com, QPoint(x_root, y_root));
904 
905             if (isSpecialWindow())
906                 replay = true;
907 
908             if (w == wrapperId())  // these can come only from a grab
909                 xcb_allow_events(connection(), replay ? XCB_ALLOW_REPLAY_POINTER : XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME);  //xTime());
910             return true;
911         }
912     }
913 
914     if (w == wrapperId()) { // these can come only from a grab
915         xcb_allow_events(connection(), XCB_ALLOW_REPLAY_POINTER, XCB_TIME_CURRENT_TIME);  //xTime());
916         return true;
917     }
918     if (w == inputId()) {
919         x = x_root - frameGeometry().x();
920         y = y_root - frameGeometry().y();
921         // New API processes core events FIRST and only passes unused ones to the decoration
922         QMouseEvent ev(QMouseEvent::MouseButtonPress, QPoint(x, y), QPoint(x_root, y_root),
923                        x11ToQtMouseButton(button), x11ToQtMouseButtons(state), Qt::KeyboardModifiers());
924         return processDecorationButtonPress(&ev, true);
925     }
926     if (w == frameId() && isDecorated()) {
927         if (button >= 4 && button <= 7) {
928             const Qt::KeyboardModifiers modifiers = x11ToQtKeyboardModifiers(state);
929             // Logic borrowed from qapplication_x11.cpp
930             const int delta = 120 * ((button == 4 || button == 6) ? 1 : -1);
931             const bool hor = (((button == 4 || button == 5) && (modifiers & Qt::AltModifier))
932                              || (button == 6 || button == 7));
933 
934             const QPoint angle = hor ? QPoint(delta, 0) : QPoint(0, delta);
935             QWheelEvent event(QPointF(x, y),
936                               QPointF(x_root, y_root),
937                               QPoint(),
938                               angle,
939                               delta,
940                               hor ? Qt::Horizontal : Qt::Vertical,
941                               x11ToQtMouseButtons(state),
942                               modifiers);
943             event.setAccepted(false);
944             QCoreApplication::sendEvent(decoration(), &event);
945             if (!event.isAccepted() && !hor) {
946                 if (titlebarPositionUnderMouse()) {
947                     performMouseCommand(options->operationTitlebarMouseWheel(delta), QPoint(x_root, y_root));
948                 }
949             }
950         } else {
951             QMouseEvent event(QEvent::MouseButtonPress, QPointF(x, y), QPointF(x_root, y_root),
952                             x11ToQtMouseButton(button), x11ToQtMouseButtons(state), x11ToQtKeyboardModifiers(state));
953             event.setAccepted(false);
954             QCoreApplication::sendEvent(decoration(), &event);
955             if (!event.isAccepted()) {
956                 processDecorationButtonPress(&event);
957             }
958         }
959         return true;
960     }
961     return true;
962 }
963 
964 // return value matters only when filtering events before decoration gets them
buttonReleaseEvent(xcb_window_t w,int button,int state,int x,int y,int x_root,int y_root)965 bool X11Client::buttonReleaseEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root)
966 {
967     if (waylandServer()) {
968         return true;
969     }
970     if (w == frameId() && isDecorated()) {
971         // wheel handled on buttonPress
972         if (button < 4 || button > 7) {
973             QMouseEvent event(QEvent::MouseButtonRelease,
974                               QPointF(x, y),
975                               QPointF(x_root, y_root),
976                               x11ToQtMouseButton(button),
977                               x11ToQtMouseButtons(state) & ~x11ToQtMouseButton(button),
978                               x11ToQtKeyboardModifiers(state));
979             event.setAccepted(false);
980             QCoreApplication::sendEvent(decoration(), &event);
981             if (event.isAccepted() || !titlebarPositionUnderMouse()) {
982                 invalidateDecorationDoubleClickTimer(); // click was for the deco and shall not init a doubleclick
983             }
984         }
985     }
986     if (w == wrapperId()) {
987         xcb_allow_events(connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME);  //xTime());
988         return true;
989     }
990     if (w != frameId() && w != inputId() && w != moveResizeGrabWindow())
991         return true;
992     if (w == frameId() && workspace()->userActionsMenu() && workspace()->userActionsMenu()->isShown()) {
993         const_cast<UserActionsMenu*>(workspace()->userActionsMenu())->grabInput();
994     }
995     x = this->x(); // translate from grab window to local coords
996     y = this->y();
997 
998     // Check whether other buttons are still left pressed
999     int buttonMask = XCB_BUTTON_MASK_1 | XCB_BUTTON_MASK_2 | XCB_BUTTON_MASK_3;
1000     if (button == XCB_BUTTON_INDEX_1)
1001         buttonMask &= ~XCB_BUTTON_MASK_1;
1002     else if (button == XCB_BUTTON_INDEX_2)
1003         buttonMask &= ~XCB_BUTTON_MASK_2;
1004     else if (button == XCB_BUTTON_INDEX_3)
1005         buttonMask &= ~XCB_BUTTON_MASK_3;
1006 
1007     if ((state & buttonMask) == 0) {
1008         endInteractiveMoveResize();
1009     }
1010     return true;
1011 }
1012 
1013 // return value matters only when filtering events before decoration gets them
motionNotifyEvent(xcb_window_t w,int state,int x,int y,int x_root,int y_root)1014 bool X11Client::motionNotifyEvent(xcb_window_t w, int state, int x, int y, int x_root, int y_root)
1015 {
1016     if (waylandServer()) {
1017         return true;
1018     }
1019     if (w == frameId() && isDecorated() && !isMinimized()) {
1020         // TODO Mouse move event dependent on state
1021         QHoverEvent event(QEvent::HoverMove, QPointF(x, y), QPointF(x, y));
1022         QCoreApplication::instance()->sendEvent(decoration(), &event);
1023     }
1024     if (w != frameId() && w != inputId() && w != moveResizeGrabWindow())
1025         return true; // care only about the whole frame
1026     if (!isInteractiveMoveResizePointerButtonDown()) {
1027         if (w == inputId()) {
1028             int x = x_root - frameGeometry().x();// + padding_left;
1029             int y = y_root - frameGeometry().y();// + padding_top;
1030 
1031             if (isDecorated()) {
1032                 QHoverEvent event(QEvent::HoverMove, QPointF(x, y), QPointF(x, y));
1033                 QCoreApplication::instance()->sendEvent(decoration(), &event);
1034             }
1035         }
1036         Position newmode = modKeyDown(state) ? PositionCenter : mousePosition();
1037         if (newmode != interactiveMoveResizePointerMode()) {
1038             setInteractiveMoveResizePointerMode(newmode);
1039             updateCursor();
1040         }
1041         return false;
1042     }
1043     if (w == moveResizeGrabWindow()) {
1044         x = this->x(); // translate from grab window to local coords
1045         y = this->y();
1046     }
1047 
1048     handleInteractiveMoveResize(QPoint(x, y), QPoint(x_root, y_root));
1049     return true;
1050 }
1051 
focusInEvent(xcb_focus_in_event_t * e)1052 void X11Client::focusInEvent(xcb_focus_in_event_t *e)
1053 {
1054     if (e->event != window())
1055         return; // only window gets focus
1056     if (e->mode == XCB_NOTIFY_MODE_UNGRAB)
1057         return; // we don't care
1058     if (e->detail == XCB_NOTIFY_DETAIL_POINTER)
1059         return;  // we don't care
1060     if (!isShown(false) || !isOnCurrentDesktop())    // we unmapped it, but it got focus meanwhile ->
1061         return;            // activateNextClient() already transferred focus elsewhere
1062     workspace()->forEachClient([](X11Client *client) {
1063         client->cancelFocusOutTimer();
1064     });
1065     // check if this client is in should_get_focus list or if activation is allowed
1066     bool activate =  workspace()->allowClientActivation(this, -1U, true);
1067     workspace()->gotFocusIn(this);   // remove from should_get_focus list
1068     if (activate) {
1069         setActive(true);
1070     } else {
1071         if (workspace()->restoreFocus()) {
1072             demandAttention();
1073         } else {
1074             qCWarning(KWIN_CORE, "Failed to restore focus. Activating 0x%x", window());
1075             setActive(true);
1076         }
1077     }
1078 }
1079 
focusOutEvent(xcb_focus_out_event_t * e)1080 void X11Client::focusOutEvent(xcb_focus_out_event_t *e)
1081 {
1082     if (e->event != window())
1083         return; // only window gets focus
1084     if (e->mode == XCB_NOTIFY_MODE_GRAB)
1085         return; // we don't care
1086     if (isShade())
1087         return; // here neither
1088     if (e->detail != XCB_NOTIFY_DETAIL_NONLINEAR
1089             && e->detail != XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL)
1090         // SELI check all this
1091         return; // hack for motif apps like netscape
1092     if (QApplication::activePopupWidget())
1093         return;
1094 
1095     // When a client loses focus, FocusOut events are usually immediatelly
1096     // followed by FocusIn events for another client that gains the focus
1097     // (unless the focus goes to another screen, or to the nofocus widget).
1098     // Without this check, the former focused client would have to be
1099     // deactivated, and after that, the new one would be activated, with
1100     // a short time when there would be no active client. This can cause
1101     // flicker sometimes, e.g. when a fullscreen is shown, and focus is transferred
1102     // from it to its transient, the fullscreen would be kept in the Active layer
1103     // at the beginning and at the end, but not in the middle, when the active
1104     // client would be temporarily none (see X11Client::belongToLayer() ).
1105     // Therefore the setActive(false) call is moved to the end of the current
1106     // event queue. If there is a matching FocusIn event in the current queue
1107     // this will be processed before the setActive(false) call and the activation
1108     // of the Client which gained FocusIn will automatically deactivate the
1109     // previously active client.
1110     if (!m_focusOutTimer) {
1111         m_focusOutTimer = new QTimer(this);
1112         m_focusOutTimer->setSingleShot(true);
1113         m_focusOutTimer->setInterval(0);
1114         connect(m_focusOutTimer, &QTimer::timeout, this, [this]() {
1115             setActive(false);
1116         });
1117     }
1118     m_focusOutTimer->start();
1119 }
1120 
1121 // performs _NET_WM_MOVERESIZE
NETMoveResize(int x_root,int y_root,NET::Direction direction)1122 void X11Client::NETMoveResize(int x_root, int y_root, NET::Direction direction)
1123 {
1124     if (direction == NET::Move) {
1125         // move cursor to the provided position to prevent the window jumping there on first movement
1126         // the expectation is that the cursor is already at the provided position,
1127         // thus it's more a safety measurement
1128         Cursors::self()->mouse()->setPos(QPoint(x_root, y_root));
1129         performMouseCommand(Options::MouseMove, QPoint(x_root, y_root));
1130     } else if (isInteractiveMoveResize() && direction == NET::MoveResizeCancel) {
1131         finishInteractiveMoveResize(true);
1132         setInteractiveMoveResizePointerButtonDown(false);
1133         updateCursor();
1134     } else if (direction >= NET::TopLeft && direction <= NET::Left) {
1135         static const Position convert[] = {
1136             PositionTopLeft,
1137             PositionTop,
1138             PositionTopRight,
1139             PositionRight,
1140             PositionBottomRight,
1141             PositionBottom,
1142             PositionBottomLeft,
1143             PositionLeft
1144         };
1145         if (!isResizable() || isShade())
1146             return;
1147         if (isInteractiveMoveResize())
1148             finishInteractiveMoveResize(false);
1149         setInteractiveMoveResizePointerButtonDown(true);
1150         setInteractiveMoveOffset(QPoint(x_root - x(), y_root - y()));  // map from global
1151         setInvertedInteractiveMoveOffset(rect().bottomRight() - interactiveMoveOffset());
1152         setUnrestrictedInteractiveMoveResize(false);
1153         setInteractiveMoveResizePointerMode(convert[ direction ]);
1154         if (!startInteractiveMoveResize())
1155             setInteractiveMoveResizePointerButtonDown(false);
1156         updateCursor();
1157     } else if (direction == NET::KeyboardMove) {
1158         // ignore mouse coordinates given in the message, mouse position is used by the moving algorithm
1159         Cursors::self()->mouse()->setPos(frameGeometry().center());
1160         performMouseCommand(Options::MouseUnrestrictedMove, frameGeometry().center());
1161     } else if (direction == NET::KeyboardSize) {
1162         // ignore mouse coordinates given in the message, mouse position is used by the resizing algorithm
1163         Cursors::self()->mouse()->setPos(frameGeometry().bottomRight());
1164         performMouseCommand(Options::MouseUnrestrictedResize, frameGeometry().bottomRight());
1165     }
1166 }
1167 
keyPressEvent(uint key_code,xcb_timestamp_t time)1168 void X11Client::keyPressEvent(uint key_code, xcb_timestamp_t time)
1169 {
1170     updateUserTime(time);
1171     AbstractClient::keyPressEvent(key_code);
1172 }
1173 
1174 // ****************************************
1175 // Unmanaged
1176 // ****************************************
1177 
windowEvent(xcb_generic_event_t * e)1178 bool Unmanaged::windowEvent(xcb_generic_event_t *e)
1179 {
1180     NET::Properties dirtyProperties;
1181     NET::Properties2 dirtyProperties2;
1182     info->event(e, &dirtyProperties, &dirtyProperties2);   // pass through the NET stuff
1183     if (dirtyProperties2 & NET::WM2Opacity) {
1184         if (Compositor::compositing()) {
1185             setOpacity(info->opacityF());
1186         }
1187     }
1188     if (dirtyProperties2 & NET::WM2OpaqueRegion) {
1189         getWmOpaqueRegion();
1190     }
1191     if (dirtyProperties2.testFlag(NET::WM2WindowRole)) {
1192         Q_EMIT windowRoleChanged();
1193     }
1194     if (dirtyProperties2.testFlag(NET::WM2WindowClass)) {
1195         getResourceClass();
1196     }
1197     const uint8_t eventType = e->response_type & ~0x80;
1198     switch (eventType) {
1199     case XCB_DESTROY_NOTIFY:
1200         release(ReleaseReason::Destroyed);
1201         break;
1202     case XCB_UNMAP_NOTIFY:{
1203         workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event
1204 
1205         // unmap notify might have been emitted due to a destroy notify
1206         // but unmap notify gets emitted before the destroy notify, nevertheless at this
1207         // point the window is already destroyed. This means any XCB request with the window
1208         // will cause an error.
1209         // To not run into these errors we try to wait for the destroy notify. For this we
1210         // generate a round trip to the X server and wait a very short time span before
1211         // handling the release.
1212         updateXTime();
1213         // using 1 msec to not just move it at the end of the event loop but add an very short
1214         // timespan to cover cases like unmap() followed by destroy(). The only other way to
1215         // ensure that the window is not destroyed when we do the release handling is to grab
1216         // the XServer which we do not want to do for an Unmanaged. The timespan of 1 msec is
1217         // short enough to not cause problems in the close window animations.
1218         // It's of course still possible that we miss the destroy in which case non-fatal
1219         // X errors are reported to the event loop and logged by Qt.
1220         m_scheduledRelease = true;
1221         QTimer::singleShot(1, this, [this]() { release(); });
1222         break;
1223     }
1224     case XCB_CONFIGURE_NOTIFY:
1225         configureNotifyEvent(reinterpret_cast<xcb_configure_notify_event_t*>(e));
1226         break;
1227     case XCB_PROPERTY_NOTIFY:
1228         propertyNotifyEvent(reinterpret_cast<xcb_property_notify_event_t*>(e));
1229         break;
1230     case XCB_CLIENT_MESSAGE:
1231         clientMessageEvent(reinterpret_cast<xcb_client_message_event_t*>(e));
1232         break;
1233     default: {
1234         if (eventType == Xcb::Extensions::self()->shapeNotifyEvent()) {
1235             detectShape(window());
1236             addRepaintFull();
1237             addWorkspaceRepaint(frameGeometry());  // in case shape change removes part of this window
1238             Q_EMIT geometryShapeChanged(this, frameGeometry());
1239         }
1240         if (eventType == Xcb::Extensions::self()->damageNotifyEvent())
1241             damageNotifyEvent();
1242         break;
1243     }
1244     }
1245     return false; // don't eat events, even our own unmanaged widgets are tracked
1246 }
1247 
configureNotifyEvent(xcb_configure_notify_event_t * e)1248 void Unmanaged::configureNotifyEvent(xcb_configure_notify_event_t *e)
1249 {
1250     if (effects)
1251         static_cast<EffectsHandlerImpl*>(effects)->checkInputWindowStacking(); // keep them on top
1252     QRect newgeom(e->x, e->y, e->width, e->height);
1253     if (newgeom != m_frameGeometry) {
1254         QRect old = m_frameGeometry;
1255         m_clientGeometry = newgeom;
1256         m_frameGeometry = newgeom;
1257         m_bufferGeometry = newgeom;
1258         Q_EMIT bufferGeometryChanged(this, old);
1259         Q_EMIT clientGeometryChanged(this, old);
1260         Q_EMIT frameGeometryChanged(this, old);
1261         Q_EMIT geometryShapeChanged(this, old);
1262     }
1263 }
1264 
1265 // ****************************************
1266 // Toplevel
1267 // ****************************************
1268 
propertyNotifyEvent(xcb_property_notify_event_t * e)1269 void Toplevel::propertyNotifyEvent(xcb_property_notify_event_t *e)
1270 {
1271     if (e->window != window())
1272         return; // ignore frame/wrapper
1273     switch(e->atom) {
1274     default:
1275         if (e->atom == atoms->wm_client_leader)
1276             getWmClientLeader();
1277         else if (e->atom == atoms->kde_net_wm_shadow)
1278             updateShadow();
1279         else if (e->atom == atoms->kde_skip_close_animation)
1280             getSkipCloseAnimation();
1281         break;
1282     }
1283 }
1284 
clientMessageEvent(xcb_client_message_event_t * e)1285 void Toplevel::clientMessageEvent(xcb_client_message_event_t *e)
1286 {
1287     if (e->type == atoms->wl_surface_id) {
1288         m_pendingSurfaceId = e->data.data32[0];
1289         if (auto w = waylandServer()) {
1290             if (auto s = KWaylandServer::SurfaceInterface::get(m_pendingSurfaceId, w->xWaylandConnection())) {
1291                 setSurface(s);
1292             }
1293         }
1294     }
1295 }
1296 
1297 } // namespace
1298