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