events.cpp 55.5 KB
Newer Older
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1
2
3
/*
    KWin - the KDE window manager
    This file is part of the KDE project.
4

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
5
6
    SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
    SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
7

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
8
9
    SPDX-License-Identifier: GPL-2.0-or-later
*/
10
11
12
13
14
15
16

/*

 This file contains things relevant to handling incoming events.

*/

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
17
#include "x11client.h"
18
#include "cursor.h"
19
#include "focuschain.h"
20
#include "netinfo.h"
21
22
#include "workspace.h"
#include "atoms.h"
23
#ifdef KWIN_BUILD_TABBOX
24
#include "tabbox.h"
25
#endif
26
27
28
#include "group.h"
#include "rules.h"
#include "unmanaged.h"
29
#include "useractions.h"
30
#include "effects.h"
31
#include "screens.h"
32
#include "xcbutils.h"
33

34
35
#include <KDecoration2/Decoration>

36
#include <QApplication>
Martin Flöser's avatar
Martin Flöser committed
37
#include <QDebug>
38
39
40
#include <QHoverEvent>
#include <QKeyEvent>
#include <QMouseEvent>
41
#include <QStyleHints>
42
#include <QWheelEvent>
43
44
45

#include <kkeyserver.h>

46
#include <xcb/sync.h>
47
#ifdef XCB_ICCCM_FOUND
48
#include <xcb/xcb_icccm.h>
49
#endif
50

51
#include "composite.h"
Fredrik Höglund's avatar
Fredrik Höglund committed
52
#include "x11eventfilter.h"
53

54
#include "wayland_server.h"
55
#include <KWaylandServer/surface_interface.h>
56

57
58
59
60
61
62
63
64
65
66
67
68
69
#ifndef XCB_GE_GENERIC
#define XCB_GE_GENERIC 35
typedef struct xcb_ge_generic_event_t {
    uint8_t  response_type; /**<  */
    uint8_t  extension; /**<  */
    uint16_t sequence; /**<  */
    uint32_t length; /**<  */
    uint16_t event_type; /**<  */
    uint8_t  pad0[22]; /**<  */
    uint32_t full_sequence; /**<  */
} xcb_ge_generic_event_t;
#endif

70
71
72
73
74
75
76
namespace KWin
{

// ****************************************
// Workspace
// ****************************************

77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
static xcb_window_t findEventWindow(xcb_generic_event_t *event)
{
    const uint8_t eventType = event->response_type & ~0x80;
    switch(eventType) {
    case XCB_KEY_PRESS:
    case XCB_KEY_RELEASE:
        return reinterpret_cast<xcb_key_press_event_t*>(event)->event;
    case XCB_BUTTON_PRESS:
    case XCB_BUTTON_RELEASE:
        return reinterpret_cast<xcb_button_press_event_t*>(event)->event;
    case XCB_MOTION_NOTIFY:
        return reinterpret_cast<xcb_motion_notify_event_t*>(event)->event;
    case XCB_ENTER_NOTIFY:
    case XCB_LEAVE_NOTIFY:
        return reinterpret_cast<xcb_enter_notify_event_t*>(event)->event;
    case XCB_FOCUS_IN:
    case XCB_FOCUS_OUT:
        return reinterpret_cast<xcb_focus_in_event_t*>(event)->event;
    case XCB_EXPOSE:
        return reinterpret_cast<xcb_expose_event_t*>(event)->window;
    case XCB_GRAPHICS_EXPOSURE:
        return reinterpret_cast<xcb_graphics_exposure_event_t*>(event)->drawable;
    case XCB_NO_EXPOSURE:
        return reinterpret_cast<xcb_no_exposure_event_t*>(event)->drawable;
    case XCB_VISIBILITY_NOTIFY:
        return reinterpret_cast<xcb_visibility_notify_event_t*>(event)->window;
    case XCB_CREATE_NOTIFY:
        return reinterpret_cast<xcb_create_notify_event_t*>(event)->window;
    case XCB_DESTROY_NOTIFY:
        return reinterpret_cast<xcb_destroy_notify_event_t*>(event)->window;
    case XCB_UNMAP_NOTIFY:
        return reinterpret_cast<xcb_unmap_notify_event_t*>(event)->window;
    case XCB_MAP_NOTIFY:
        return reinterpret_cast<xcb_map_notify_event_t*>(event)->window;
    case XCB_MAP_REQUEST:
        return reinterpret_cast<xcb_map_request_event_t*>(event)->window;
    case XCB_REPARENT_NOTIFY:
        return reinterpret_cast<xcb_reparent_notify_event_t*>(event)->window;
    case XCB_CONFIGURE_NOTIFY:
        return reinterpret_cast<xcb_configure_notify_event_t*>(event)->window;
    case XCB_CONFIGURE_REQUEST:
        return reinterpret_cast<xcb_configure_request_event_t*>(event)->window;
    case XCB_GRAVITY_NOTIFY:
        return reinterpret_cast<xcb_gravity_notify_event_t*>(event)->window;
    case XCB_RESIZE_REQUEST:
        return reinterpret_cast<xcb_resize_request_event_t*>(event)->window;
    case XCB_CIRCULATE_NOTIFY:
    case XCB_CIRCULATE_REQUEST:
        return reinterpret_cast<xcb_circulate_notify_event_t*>(event)->window;
    case XCB_PROPERTY_NOTIFY:
        return reinterpret_cast<xcb_property_notify_event_t*>(event)->window;
    case XCB_COLORMAP_NOTIFY:
        return reinterpret_cast<xcb_colormap_notify_event_t*>(event)->window;
    case XCB_CLIENT_MESSAGE:
        return reinterpret_cast<xcb_client_message_event_t*>(event)->window;
    default:
        // extension handling
        if (eventType == Xcb::Extensions::self()->shapeNotifyEvent()) {
            return reinterpret_cast<xcb_shape_notify_event_t*>(event)->affected_window;
        }
        if (eventType == Xcb::Extensions::self()->damageNotifyEvent()) {
            return reinterpret_cast<xcb_damage_notify_event_t*>(event)->drawable;
        }
        return XCB_WINDOW_NONE;
    }
}

144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
QVector<QByteArray> s_xcbEerrors({
    QByteArrayLiteral("Success"),
    QByteArrayLiteral("BadRequest"),
    QByteArrayLiteral("BadValue"),
    QByteArrayLiteral("BadWindow"),
    QByteArrayLiteral("BadPixmap"),
    QByteArrayLiteral("BadAtom"),
    QByteArrayLiteral("BadCursor"),
    QByteArrayLiteral("BadFont"),
    QByteArrayLiteral("BadMatch"),
    QByteArrayLiteral("BadDrawable"),
    QByteArrayLiteral("BadAccess"),
    QByteArrayLiteral("BadAlloc"),
    QByteArrayLiteral("BadColor"),
    QByteArrayLiteral("BadGC"),
    QByteArrayLiteral("BadIDChoice"),
    QByteArrayLiteral("BadName"),
    QByteArrayLiteral("BadLength"),
    QByteArrayLiteral("BadImplementation"),
    QByteArrayLiteral("Unknown")});
Fredrik Höglund's avatar
Fredrik Höglund committed
164
165
166
167


void Workspace::registerEventFilter(X11EventFilter *filter)
{
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
    if (filter->isGenericEvent()) {
        m_genericEventFilters.append(new X11EventFilterContainer(filter));
    } else {
        m_eventFilters.append(new X11EventFilterContainer(filter));
    }
}

static X11EventFilterContainer *takeEventFilter(X11EventFilter *eventFilter,
                                                QList<QPointer<X11EventFilterContainer>> &list)
{
    for (int i = 0; i < list.count(); ++i) {
        X11EventFilterContainer *container = list.at(i);
        if (container->filter() == eventFilter) {
            return list.takeAt(i);
        }
    }
    return nullptr;
Fredrik Höglund's avatar
Fredrik Höglund committed
185
186
187
188
}

void Workspace::unregisterEventFilter(X11EventFilter *filter)
{
189
190
191
192
193
194
195
    X11EventFilterContainer *container = nullptr;
    if (filter->isGenericEvent()) {
        container = takeEventFilter(filter, m_genericEventFilters);
    } else {
        container = takeEventFilter(filter, m_eventFilters);
    }
    delete container;
Fredrik Höglund's avatar
Fredrik Höglund committed
196
197
198
}


Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
199
200
/**
 * Handles workspace specific XCB event
201
 */
202
bool Workspace::workspaceEvent(xcb_generic_event_t *e)
203
{
204
    const uint8_t eventType = e->response_type & ~0x80;
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
    if (!eventType) {
        // let's check whether it's an error from one of the extensions KWin uses
        xcb_generic_error_t *error = reinterpret_cast<xcb_generic_error_t*>(e);
        const QVector<Xcb::ExtensionData> extensions = Xcb::Extensions::self()->extensions();
        for (const auto &extension : extensions) {
            if (error->major_code == extension.majorOpcode) {
                QByteArray errorName;
                if (error->error_code < s_xcbEerrors.size()) {
                    errorName = s_xcbEerrors.at(error->error_code);
                } else if (error->error_code >= extension.errorBase) {
                    const int index = error->error_code - extension.errorBase;
                    if (index >= 0 && index < extension.errorCodes.size()) {
                        errorName = extension.errorCodes.at(index);
                    }
                }
                if (errorName.isEmpty()) {
                    errorName = QByteArrayLiteral("Unknown");
                }
223
                qCWarning(KWIN_CORE, "XCB error: %d (%s), sequence: %d, resource id: %d, major code: %d (%s), minor code: %d (%s)",
224
225
226
227
228
229
230
231
232
233
                         int(error->error_code), errorName.constData(),
                         int(error->sequence), int(error->resource_id),
                         int(error->major_code), extension.name.constData(),
                         int(error->minor_code),
                         extension.opCodes.size() > error->minor_code ? extension.opCodes.at(error->minor_code).constData() : "Unknown");
                return true;
            }
        }
        return false;
    }
Fredrik Höglund's avatar
Fredrik Höglund committed
234
235
236
237

    if (eventType == XCB_GE_GENERIC) {
        xcb_ge_generic_event_t *ge = reinterpret_cast<xcb_ge_generic_event_t *>(e);

238
239
240
241
242
243
244
245
246
        // We need to make a shadow copy of the event filter list because an activated event
        // filter may mutate it by removing or installing another event filter.
        const auto eventFilters = m_genericEventFilters;

        for (X11EventFilterContainer *container : eventFilters) {
            if (!container) {
                continue;
            }
            X11EventFilter *filter = container->filter();
247
            if (filter->extension() == ge->extension && filter->genericEventTypes().contains(ge->event_type) && filter->event(e)) {
Fredrik Höglund's avatar
Fredrik Höglund committed
248
249
250
251
                return true;
            }
        }
    } else {
252
253
254
255
256
257
258
259
260
        // We need to make a shadow copy of the event filter list because an activated event
        // filter may mutate it by removing or installing another event filter.
        const auto eventFilters = m_eventFilters;

        for (X11EventFilterContainer *container : eventFilters) {
            if (!container) {
                continue;
            }
            X11EventFilter *filter = container->filter();
261
            if (filter->eventTypes().contains(eventType) && filter->event(e)) {
Fredrik Höglund's avatar
Fredrik Höglund committed
262
263
264
265
266
                return true;
            }
        }
    }

267
    if (effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()
268
            && (eventType == XCB_KEY_PRESS || eventType == XCB_KEY_RELEASE))
269
270
271
        return false; // let Qt process it, it'll be intercepted again in eventFilter()

    // events that should be handled before Clients can get them
Martin Flöser's avatar
Martin Flöser committed
272
    switch (eventType) {
273
274
    case XCB_CONFIGURE_NOTIFY:
        if (reinterpret_cast<xcb_configure_notify_event_t*>(e)->event == rootWindow())
275
            markXStackingOrderAsDirty();
276
277
278
        break;
    };

279
    const xcb_window_t eventWindow = findEventWindow(e);
280
    if (eventWindow != XCB_WINDOW_NONE) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
281
        if (X11Client *c = findClient(Predicate::WindowMatch, eventWindow)) {
282
283
            if (c->windowEvent(e))
                return true;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
284
        } else if (X11Client *c = findClient(Predicate::WrapperIdMatch, eventWindow)) {
285
286
            if (c->windowEvent(e))
                return true;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
287
        } else if (X11Client *c = findClient(Predicate::FrameIdMatch, eventWindow)) {
288
289
            if (c->windowEvent(e))
                return true;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
290
        } else if (X11Client *c = findClient(Predicate::InputIdMatch, eventWindow)) {
291
292
            if (c->windowEvent(e))
                return true;
293
        } else if (Unmanaged* c = findUnmanaged(eventWindow)) {
294
295
            if (c->windowEvent(e))
                return true;
296
        }
297
    }
298

299
300
301
302
303
304
    switch (eventType) {
    case XCB_CREATE_NOTIFY: {
        const auto *event = reinterpret_cast<xcb_create_notify_event_t*>(e);
        if (event->parent == rootWindow() &&
                !QWidget::find(event->window) &&
                !event->override_redirect) {
305
            // see comments for allowClientActivation()
306
            updateXTime();
307
308
            const xcb_timestamp_t t = xTime();
            xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, event->window, atoms->kde_net_wm_user_creation_time, XCB_ATOM_CARDINAL, 32, 1, &t);
309
        }
310
        break;
311
    }
312
313
314
    case XCB_UNMAP_NOTIFY: {
        const auto *event = reinterpret_cast<xcb_unmap_notify_event_t*>(e);
        return (event->event != event->window);   // hide wm typical event from Qt
315
    }
316
    case XCB_REPARENT_NOTIFY: {
317
318
        //do not confuse Qt with these events. After all, _we_ are the
        //window manager who does the reparenting.
319
320
        return true;
    }
321
    case XCB_MAP_REQUEST: {
322
323
        updateXTime();

324
        const auto *event = reinterpret_cast<xcb_map_request_event_t*>(e);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
325
        if (X11Client *c = findClient(Predicate::WindowMatch, event->window)) {
326
327
            // e->xmaprequest.window is different from e->xany.window
            // TODO this shouldn't be necessary now
328
            c->windowEvent(e);
329
            FocusChain::self()->update(c, FocusChain::Update);
330
331
332
333
334
335
        } else if ( true /*|| e->xmaprequest.parent != root */ ) {
            // NOTICE don't check for the parent being the root window, this breaks when some app unmaps
            // a window, changes something and immediately maps it back, without giving KWin
            // a chance to reparent it back to root
            // since KWin can get MapRequest only for root window children and
            // children of WindowWrapper (=clients), the check is AFAIK useless anyway
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
336
            // NOTICE: The save-set support in X11Client::mapRequestEvent() actually requires that
337
            // this code doesn't check the parent to be root.
338
339
340
341
342
            if (!createClient(event->window, false)) {
                xcb_map_window(connection(), event->window);
                const uint32_t values[] = { XCB_STACK_MODE_ABOVE };
                xcb_configure_window(connection(), event->window, XCB_CONFIG_WINDOW_STACK_MODE, values);
            }
343
        }
344
        return true;
345
    }
346
347
348
    case XCB_MAP_NOTIFY: {
        const auto *event = reinterpret_cast<xcb_map_notify_event_t*>(e);
        if (event->override_redirect) {
349
            Unmanaged* c = findUnmanaged(event->window);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
350
            if (c == nullptr)
351
                c = createUnmanaged(event->window);
352
            if (c) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
353
                // if hasScheduledRelease is true, it means a unamp and map sequence has occurred.
354
355
356
357
358
359
360
361
362
                // since release is scheduled after map notify, this old Unmanaged will get released
                // before KWIN has chance to remanage it again. so release it right now.
                if (c->hasScheduledRelease()) {
                    c->release();
                    c = createUnmanaged(event->window);
                }
                if (c)
                    return c->windowEvent(e);
            }
363
        }
364
        return (event->event != event->window);   // hide wm typical event from Qt
365
    }
366

367
368
369
    case XCB_CONFIGURE_REQUEST: {
        const auto *event = reinterpret_cast<xcb_configure_request_event_t*>(e);
        if (event->parent == rootWindow()) {
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
            uint32_t values[5] = { 0, 0, 0, 0, 0};
            const uint32_t value_mask = event->value_mask
                                        & (XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH);
            int i = 0;
            if (value_mask & XCB_CONFIG_WINDOW_X) {
                values[i++] = event->x;
            }
            if (value_mask & XCB_CONFIG_WINDOW_Y) {
                values[i++] = event->y;
            }
            if (value_mask & XCB_CONFIG_WINDOW_WIDTH) {
                values[i++] = event->width;
            }
            if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
                values[i++] = event->height;
            }
            if (value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) {
                values[i++] = event->border_width;
            }
            xcb_configure_window(connection(), event->window, value_mask, values);
390
391
392
393
            return true;
        }
        break;
    }
394
395
396
    case XCB_FOCUS_IN: {
        const auto *event = reinterpret_cast<xcb_focus_in_event_t*>(e);
        if (event->event == rootWindow()
397
                && (event->detail == XCB_NOTIFY_DETAIL_NONE || event->detail == XCB_NOTIFY_DETAIL_POINTER_ROOT || event->detail == XCB_NOTIFY_DETAIL_INFERIOR)) {
398
            Xcb::CurrentInput currentInput;
399
            updateXTime(); // focusToNull() uses xTime(), which is old now (FocusIn has no timestamp)
400
401
402
403
            // it seems we can "loose" focus reversions when the closing client hold a grab
            // => catch the typical pattern (though we don't want the focus on the root anyway) #348935
            const bool lostFocusPointerToRoot = currentInput->focus == rootWindow() && event->detail == XCB_NOTIFY_DETAIL_INFERIOR;
            if (!currentInput.isNull() && (currentInput->focus == XCB_WINDOW_NONE || currentInput->focus == XCB_INPUT_FOCUS_POINTER_ROOT || lostFocusPointerToRoot)) {
404
                //kWarning( 1212 ) << "X focus set to None/PointerRoot, reseting focus" ;
405
                AbstractClient *c = mostRecentlyActivatedClient();
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
406
                if (c != nullptr)
407
                    requestFocus(c, true);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
408
                else if (activateNextClient(nullptr))
409
410
411
                    ; // ok, activated
                else
                    focusToNull();
412
            }
413
        }
414
    }
415
        // fall through
416
    case XCB_FOCUS_OUT:
417
418
419
        return true; // always eat these, they would tell Qt that KWin is the active app
    default:
        break;
420
    }
421
422
    return false;
}
423
424
425
426

// Used only to filter events that need to be processed by Qt first
// (e.g. keyboard input to be composed), otherwise events are
// handle by the XEvent filter above
427
428
429
430
431
bool Workspace::workspaceEvent(QEvent* e)
{
    if ((e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease || e->type() == QEvent::ShortcutOverride)
            && effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) {
        static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(static_cast< QKeyEvent* >(e));
432
433
        return true;
    }
434
435
    return false;
}
436
437
438
439
440

// ****************************************
// Client
// ****************************************

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
441
442
/**
 * General handler for XEvents concerning the client window
443
 */
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
444
bool X11Client::windowEvent(xcb_generic_event_t *e)
445
{
446
    if (findEventWindow(e) == window()) { // avoid doing stuff on frame or wrapper
447
448
449
        NET::Properties dirtyProperties;
        NET::Properties2 dirtyProperties2;
        info->event(e, &dirtyProperties, &dirtyProperties2);   // pass through the NET stuff
450

451
        if ((dirtyProperties & NET::WMName) != 0)
452
            fetchName();
453
        if ((dirtyProperties & NET::WMIconName) != 0)
454
            fetchIconicName();
455
456
        if ((dirtyProperties & NET::WMStrut) != 0
                || (dirtyProperties2 & NET::WM2ExtendedStrut) != 0) {
457
            workspace()->updateClientArea();
458
        }
459
        if ((dirtyProperties & NET::WMIcon) != 0)
460
461
462
463
            getIcons();
        // Note there's a difference between userTime() and info->userTime()
        // info->userTime() is the value of the property, userTime() also includes
        // updates of the time done by KWin (ButtonPress on windowrapper etc.).
464
        if ((dirtyProperties2 & NET::WM2UserTime) != 0) {
465
            workspace()->setWasUserInteraction();
466
467
            updateUserTime(info->userTime());
        }
468
        if ((dirtyProperties2 & NET::WM2StartupId) != 0)
469
            startupIdChanged();
470
        if (dirtyProperties2 & NET::WM2Opacity) {
471
            if (compositing()) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
472
                setOpacity(info->opacityF());
473
474
            } else {
                // forward to the frame if there's possibly another compositing manager running
475
                NETWinInfo i(connection(), frameId(), rootWindow(), NET::Properties(), NET::Properties2());
476
                i.setOpacity(info->opacity());
477
            }
478
        }
479
        if (dirtyProperties2 & NET::WM2FrameOverlap) {
480
            // ### Inform the decoration
481
        }
482
483
484
        if (dirtyProperties2.testFlag(NET::WM2WindowRole)) {
            emit windowRoleChanged();
        }
485
486
487
        if (dirtyProperties2.testFlag(NET::WM2WindowClass)) {
            getResourceClass();
        }
488
489
490
        if (dirtyProperties2.testFlag(NET::WM2BlockCompositing)) {
            setBlockingCompositing(info->isBlockingCompositing());
        }
491
492
493
494
495
496
497
        if (dirtyProperties2.testFlag(NET::WM2GroupLeader)) {
            checkGroup();
            updateAllowedActions(); // Group affects isMinimizable()
        }
        if (dirtyProperties2.testFlag(NET::WM2Urgency)) {
            updateUrgency();
        }
Martin Flöser's avatar
Martin Flöser committed
498
499
500
        if (dirtyProperties2 & NET::WM2OpaqueRegion) {
            getWmOpaqueRegion();
        }
501
502
503
        if (dirtyProperties2 & NET::WM2DesktopFileName) {
            setDesktopFileName(QByteArray(info->desktopFileName()));
        }
504
505
506
        if (dirtyProperties2 & NET::WM2GTKFrameExtents) {
            setClientFrameExtents(info->gtkFrameExtents());
        }
507
    }
508

509
510
    const uint8_t eventType = e->response_type & ~0x80;
    switch(eventType) {
511
512
    case XCB_UNMAP_NOTIFY:
        unmapNotifyEvent(reinterpret_cast<xcb_unmap_notify_event_t*>(e));
513
        break;
514
515
    case XCB_DESTROY_NOTIFY:
        destroyNotifyEvent(reinterpret_cast<xcb_destroy_notify_event_t*>(e));
516
        break;
517
    case XCB_MAP_REQUEST:
518
        // this one may pass the event to workspace
519
        return mapRequestEvent(reinterpret_cast<xcb_map_request_event_t*>(e));
520
521
    case XCB_CONFIGURE_REQUEST:
        configureRequestEvent(reinterpret_cast<xcb_configure_request_event_t*>(e));
522
        break;
523
524
    case XCB_PROPERTY_NOTIFY:
        propertyNotifyEvent(reinterpret_cast<xcb_property_notify_event_t*>(e));
525
        break;
526
    case XCB_KEY_PRESS:
527
        updateUserTime(reinterpret_cast<xcb_key_press_event_t*>(e)->time);
528
        break;
529
530
    case XCB_BUTTON_PRESS: {
        const auto *event = reinterpret_cast<xcb_button_press_event_t*>(e);
531
        updateUserTime(event->time);
532
        buttonPressEvent(event->event, event->detail, event->state,
533
                         event->event_x, event->event_y, event->root_x, event->root_y, event->time);
534
        break;
535
536
    }
    case XCB_KEY_RELEASE:
537
538
539
540
        // don't update user time on releases
        // e.g. if the user presses Alt+F2, the Alt release
        // would appear as user input to the currently active window
        break;
541
542
    case XCB_BUTTON_RELEASE: {
        const auto *event = reinterpret_cast<xcb_button_release_event_t*>(e);
543
544
545
        // don't update user time on releases
        // e.g. if the user presses Alt+F2, the Alt release
        // would appear as user input to the currently active window
546
547
        buttonReleaseEvent(event->event, event->detail, event->state,
                           event->event_x, event->event_y, event->root_x, event->root_y);
548
        break;
549
550
551
552
553
554
    }
    case XCB_MOTION_NOTIFY: {
        const auto *event = reinterpret_cast<xcb_motion_notify_event_t*>(e);
        motionNotifyEvent(event->event, event->state,
                          event->event_x, event->event_y, event->root_x, event->root_y);
        workspace()->updateFocusMousePosition(QPoint(event->root_x, event->root_y));
555
        break;
556
    }
557
558
559
    case XCB_ENTER_NOTIFY: {
        auto *event = reinterpret_cast<xcb_enter_notify_event_t*>(e);
        enterNotifyEvent(event);
560
561
562
563
564
        // MotionNotify is guaranteed to be generated only if the mouse
        // move start and ends in the window; for cases when it only
        // starts or only ends there, Enter/LeaveNotify are generated.
        // Fake a MotionEvent in such cases to make handle of mouse
        // events simpler (Qt does that too).
565
566
567
        motionNotifyEvent(event->event, event->state,
                          event->event_x, event->event_y, event->root_x, event->root_y);
        workspace()->updateFocusMousePosition(QPoint(event->root_x, event->root_y));
568
        break;
569
    }
570
571
572
573
574
    case XCB_LEAVE_NOTIFY: {
        auto *event = reinterpret_cast<xcb_leave_notify_event_t*>(e);
        motionNotifyEvent(event->event, event->state,
                          event->event_x, event->event_y, event->root_x, event->root_y);
        leaveNotifyEvent(event);
575
576
577
        // not here, it'd break following enter notify handling
        // workspace()->updateFocusMousePosition( QPoint( e->xcrossing.x_root, e->xcrossing.y_root ));
        break;
578
    }
579
580
    case XCB_FOCUS_IN:
        focusInEvent(reinterpret_cast<xcb_focus_in_event_t*>(e));
581
        break;
582
583
    case XCB_FOCUS_OUT:
        focusOutEvent(reinterpret_cast<xcb_focus_out_event_t*>(e));
584
        break;
585
    case XCB_REPARENT_NOTIFY:
586
        break;
587
588
    case XCB_CLIENT_MESSAGE:
        clientMessageEvent(reinterpret_cast<xcb_client_message_event_t*>(e));
589
        break;
590
591
592
593
594
595
596
597
    case XCB_EXPOSE: {
        xcb_expose_event_t *event = reinterpret_cast<xcb_expose_event_t*>(e);
        if (event->window == frameId() && !Compositor::self()->isActive()) {
            // TODO: only repaint required areas
            triggerDecorationRepaint();
        }
        break;
    }
598
    default:
599
600
601
        if (eventType == Xcb::Extensions::self()->shapeNotifyEvent() && reinterpret_cast<xcb_shape_notify_event_t*>(e)->affected_window == window()) {
            detectShape(window());  // workaround for #19644
            updateShape();
602
        }
603
        if (eventType == Xcb::Extensions::self()->damageNotifyEvent() && reinterpret_cast<xcb_damage_notify_event_t*>(e)->drawable == frameId())
604
            damageNotifyEvent();
605
        break;
606
    }
607
608
    return true; // eat all events
}
609

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
610
611
/**
 * Handles map requests of the client window
612
 */
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
613
bool X11Client::mapRequestEvent(xcb_map_request_event_t *e)
614
615
{
    if (e->window != window()) {
616
617
        // Special support for the save-set feature, which is a bit broken.
        // If there's a window from one client embedded in another one,
Frederik Schwarzer's avatar
Frederik Schwarzer committed
618
        // e.g. using XEMBED, and the embedder suddenly loses its X connection,
619
620
621
622
623
624
625
626
627
        // save-set will reparent the embedded window to its closest ancestor
        // that will remains. Unfortunately, with reparenting window managers,
        // this is not the root window, but the frame (or in KWin's case,
        // it's the wrapper for the client window). In this case,
        // the wrapper will get ReparentNotify for a window it won't know,
        // which will be ignored, and then it gets MapRequest, as save-set
        // always maps. Returning true here means that Workspace::workspaceEvent()
        // will handle this MapRequest and manage this window (i.e. act as if
        // it was reparented to root window).
628
        if (e->parent == wrapperId())
629
630
            return false;
        return true; // no messing with frame etc.
631
    }
632
    // also copied in clientMessage()
633
    if (isMinimized())
634
        unminimize();
635
636
637
638
639
    if (isShade())
        setShade(ShadeNone);
    if (!isOnCurrentDesktop()) {
        if (workspace()->allowClientActivation(this))
            workspace()->activateClient(this);
640
641
        else
            demandAttention();
642
    }
643
644
    return true;
}
645

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
646
647
/**
 * Handles unmap notify events of the client window
648
 */
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
649
void X11Client::unmapNotifyEvent(xcb_unmap_notify_event_t *e)
650
651
{
    if (e->window != window())
652
        return;
653
654
    if (e->event != wrapperId()) {
        // most probably event from root window when initially reparenting
655
        bool ignore = true;
656
        if (e->event == rootWindow() && (e->response_type & 0x80))
657
            ignore = false; // XWithdrawWindow()
658
        if (ignore)
659
660
            return;
    }
661
662
663
664

    // check whether this is result of an XReparentWindow - client then won't be parented by wrapper
    // in this case do not release the client (causes reparent to root, removal from saveSet and what not)
    // but just destroy the client
Martin Flöser's avatar
Martin Flöser committed
665
    Xcb::Tree tree(m_client);
666
    xcb_window_t daddy = tree.parent();
667
    if (daddy == m_wrapper) {
668
669
670
671
        releaseWindow(); // unmapped from a regular client state
    } else {
        destroyClient(); // the client was moved to some other parent
    }
672
}
673

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
674
void X11Client::destroyNotifyEvent(xcb_destroy_notify_event_t *e)
675
676
{
    if (e->window != window())
677
678
        return;
    destroyClient();
679
680
681
}


Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
682
683
/**
 * Handles client messages for the client window
684
 */
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
685
void X11Client::clientMessageEvent(xcb_client_message_event_t *e)
686
{
687
    Toplevel::clientMessageEvent(e);
688
    if (e->window != window())
689
690
        return; // ignore frame/wrapper
    // WM_STATE
691
    if (e->type == atoms->wm_change_state) {
692
        if (e->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC)
693
694
695
            minimize();
        return;
    }
696
}
697
698


Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
699
700
/**
 * Handles configure  requests of the client window
701
 */
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
702
void X11Client::configureRequestEvent(xcb_configure_request_event_t *e)
703
704
{
    if (e->window != window())
705
        return; // ignore frame/wrapper
706
    if (isResize() || isMove())
707
708
        return; // we have better things to do right now

709
    if (m_fullscreenMode == FullScreenNormal) { // refuse resizing of fullscreen windows
710
        // but allow resizing fullscreen hacks in order to let them cancel fullscreen mode
711
712
        sendSyntheticConfigureNotify();
        return;
713
    }
Arthur Arlt's avatar
Arthur Arlt committed
714
    if (isSplash()) {  // no manipulations with splashscreens either
715
716
        sendSyntheticConfigureNotify();
        return;
717
    }
718

719
    if (e->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) {
720
        // first, get rid of a window border
721
        m_client.setBorderWidth(0);
722
    }
723

724
    if (e->value_mask & (XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_WIDTH))
725
        configureRequest(e->value_mask, e->x, e->y, e->width, e->height, 0, false);
726

727
728
    if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE)
        restackWindow(e->sibling, e->stack_mode, NET::FromApplication, userTime(), false);
729

730
    // Sending a synthetic configure notify always is fine, even in cases where
731
732
733
    // the ICCCM doesn't require this - it can be though of as 'the WM decided to move
    // the window later'. The client should not cause that many configure request,
    // so this should not have any significant impact. With user moving/resizing
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
734
    // the it should be optimized though (see also X11Client::setGeometry()/plainResize()/move()).
735
736
737
738
    sendSyntheticConfigureNotify();

    // SELI TODO accept configure requests for isDesktop windows (because kdesktop
    // may get XRANDR resize event before kwin), but check it's still at the bottom?
739
}
740
741


Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
742
743
/**
 * Handles property changes of the client window
744
 */
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
745
void X11Client::propertyNotifyEvent(xcb_property_notify_event_t *e)
746
747
748
{
    Toplevel::propertyNotifyEvent(e);
    if (e->window != window())
749
        return; // ignore frame/wrapper
750
    switch(e->atom) {
751
    case XCB_ATOM_WM_NORMAL_HINTS:
752
753
        getWmNormalHints();
        break;
754
    case XCB_ATOM_WM_NAME:
755
756
        fetchName();
        break;
757
    case XCB_ATOM_WM_ICON_NAME:
758
759
        fetchIconicName();
        break;
760
    case XCB_ATOM_WM_TRANSIENT_FOR:
761
762
        readTransient();
        break;
763
    case XCB_ATOM_WM_HINTS:
764
765
766
        getIcons(); // because KWin::icon() uses WMHints as fallback
        break;
    default:
767
        if (e->atom == atoms->motif_wm_hints) {
768
            getMotifHints();
769
        } else if (e->atom == atoms->net_wm_sync_request_counter)
770
771
772
            getSyncCounter();
        else if (e->atom == atoms->activities)
            checkActivities();
773
774
        else if (e->atom == atoms->kde_first_in_window_list)
            updateFirstInTabBox();
775
776
        else if (e->atom == atoms->kde_color_sheme)
            updateColorScheme();
777
778
        else if (e->atom == atoms->kde_screen_edge_show)
            updateShowOnScreenEdge();
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
779
780
781
782
        else if (e->atom == atoms->kde_net_wm_appmenu_service_name)
            checkApplicationMenuServiceName();
        else if (e->atom == atoms->kde_net_wm_appmenu_object_path)
            checkApplicationMenuObjectPath();
783
        break;
784
    }
785
}
786
787


Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
788
void X11Client::enterNotifyEvent(xcb_enter_notify_event_t *e)
789
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
790
791
792
    if (waylandServer()) {
        return;
    }
793
    if (e->event != frameId())
794
        return; // care only about entering the whole frame
795
796
797

#define MOUSE_DRIVEN_FOCUS (!options->focusPolicyIsReasonable() || \
                            (options->focusPolicy() == Options::FocusFollowsMouse && options->isNextFocusPrefersMouse()))
798
    if (e->mode == XCB_NOTIFY_MODE_NORMAL || (e->mode == XCB_NOTIFY_MODE_UNGRAB && MOUSE_DRIVEN_FOCUS)) {
799
#undef MOUSE_DRIVEN_FOCUS
800

801
        enterEvent(QPoint(e->root_x, e->root_y));
802
        return;
803
    }
804
}
805

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
806
void X11Client::leaveNotifyEvent(xcb_leave_notify_event_t *e)
807
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
808
809
810
    if (waylandServer()) {
        return;
    }
811
    if (e->event != frameId())
812
        return; // care only about leaving the whole frame
813
    if (e->mode == XCB_NOTIFY_MODE_NORMAL) {
814
        if (!isMoveResizePointerButtonDown()) {
815
            setMoveResizePointerMode(PositionCenter);
816
            updateCursor();
817
        }
818
        bool lostMouse = !rect().contains(QPoint(e->event_x, e->event_y));
819
820
821
822
823
824
825
        // 'lostMouse' wouldn't work with e.g. B2 or Keramik, which have non-rectangular decorations
        // (i.e. the LeaveNotify event comes before leaving the rect and no LeaveNotify event
        // comes after leaving the rect) - so lets check if the pointer is really outside the window

        // TODO this still sucks if a window appears above this one - it should lose the mouse
        // if this window is another client, but not if it's a popup ... maybe after KDE3.1 :(
        // (repeat after me 'AARGHL!')
826
        if (!lostMouse && e->detail != XCB_NOTIFY_DETAIL_INFERIOR) {
827
828
829
830
831
            Xcb::Pointer pointer(frameId());
            if (!pointer || !pointer->same_screen || pointer->child == XCB_WINDOW_NONE) {
                // really lost the mouse
                lostMouse = true;
            }
832
833
        }
        if (lostMouse) {
834
            leaveEvent();
835
            if (isDecorated()) {
836
837
                // sending a move instead of a leave. With leave we need to send proper coords, with move it's handled internally
                QHoverEvent leaveEvent(QEvent::HoverMove, QPointF(-1, -1), QPointF(-1, -1), Qt::NoModifier);
838
                QCoreApplication::sendEvent(decoration(), &leaveEvent);
839
            }
840
        }
841
        if (options->focusPolicy() == Options::FocusStrictlyUnderMouse && isActive() && lostMouse) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
842
            workspace()->requestDelayFocus(nullptr);
843
        }
844
        return;
845
    }
846
}
847

848
static uint16_t x11CommandAllModifier()
849
{
850
    switch (options->commandAllModifier()) {
851
    case Qt::MetaModifier:
852
        return KKeyServer::modXMeta();
853
    case Qt::AltModifier:
854
        return KKeyServer::modXAlt();
855
    default:
856
        return 0;
857
    }
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
}

#define XCapL KKeyServer::modXLock()
#define XNumL KKeyServer::modXNumLock()
#define XScrL KKeyServer::modXScrollLock()
void X11Client::establishCommandWindowGrab(uint8_t button)
{
    // Unfortunately there are a lot of possible modifier combinations that we need to take into
    // account. We tackle that problem in a kind of smart way. First, we grab the button with all
    // possible modifiers, then we ungrab the ones that are relevant only to commandAllx().

    m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, XCB_MOD_MASK_ANY, button);

    uint16_t x11Modifier = x11CommandAllModifier();

    unsigned int mods[ 8 ] = {
        0, XCapL, XNumL, XNumL | XCapL,
        XScrL, XScrL | XCapL,
        XScrL | XNumL, XScrL | XNumL | XCapL
    };
    for (int i = 0;
            i < 8;
            ++i)
        m_wrapper.ungrabButton(x11Modifier | mods[ i ], button);
}

void X11Client::establishCommandAllGrab(uint8_t button)
{
    uint16_t x11Modifier = x11CommandAllModifier();
887

888
    unsigned int mods[ 8 ] = {
889
890
891
        0, XCapL, XNumL, XNumL | XCapL,
        XScrL, XScrL | XCapL,
        XScrL | XNumL, XScrL | XNumL | XCapL
892
893
894
895
    };
    for (int i = 0;
            i < 8;
            ++i)
896
        m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, x11Modifier | mods[ i ], button);
897
}
898
899
900
901
#undef XCapL
#undef XNumL
#undef XScrL

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
902
void X11Client::updateMouseGrab()
903
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
904
905
906
907
    if (waylandServer()) {
        return;
    }

908
909
910
911
    xcb_ungrab_button(connection(), XCB_BUTTON_INDEX_ANY, m_wrapper, XCB_MOD_MASK_ANY);

    if (TabBox::TabBox::self()->forcedGlobalMouseGrab()) { // see TabBox::establishTabBoxGrab()
        m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
912
        return;
913
    }
914
915
916
917
918
919
920
921
922
923
924

    // When a passive grab is activated or deactivated, the X server will generate crossing
    // events as if the pointer were suddenly to warp from its current position to some position
    // in the grab window. Some /broken/ X11 clients do get confused by such EnterNotify and
    // LeaveNotify events so we release the passive grab for the active window.
    //
    // The passive grab below is established so the window can be raised or activated when it
    // is clicked.
    if ((options->focusPolicyIsReasonable() && !isActive()) ||
            (options->isClickRaise() && !isMostRecentlyRaised())) {
        if (options->commandWindow1() != Options::MouseNothing) {
925
            establishCommandWindowGrab(XCB_BUTTON_INDEX_1);
926
927
        }
        if (options->commandWindow2() != Options::MouseNothing) {
928
            establishCommandWindowGrab(XCB_BUTTON_INDEX_2);
929
930
        }
        if (options->commandWindow3() != Options::MouseNothing) {
931
            establishCommandWindowGrab(XCB_BUTTON_INDEX_3);
932
933
        }
        if (options->commandWindowWheel() != Options::MouseNothing) {
934
935
            establishCommandWindowGrab(XCB_BUTTON_INDEX_4);
            establishCommandWindowGrab(XCB_BUTTON_INDEX_5);
936
937
938
939
940
941
942
943
944
        }
    }

    // We want to grab <command modifier> + buttons no matter what state the window is in. The
    // client will receive funky EnterNotify and LeaveNotify events, but there is nothing that
    // we can do about it, unfortunately.

    if (!workspace()->globalShortcutsDisabled()) {
        if (options->commandAll1() != Options::MouseNothing) {
945
            establishCommandAllGrab(XCB_BUTTON_INDEX_1);
946
947
        }
        if (options->commandAll2() != Options::MouseNothing) {
948
            establishCommandAllGrab(XCB_BUTTON_INDEX_2);
949
950
        }
        if (options->commandAll3() != Options::MouseNothing) {
951
            establishCommandAllGrab(XCB_BUTTON_INDEX_3);
952
953
        }
        if (options->commandAllWheel() != Options::MouseWheelNothing) {
954
955
            establishCommandAllGrab(XCB_BUTTON_INDEX_4);
            establishCommandAllGrab(XCB_BUTTON_INDEX_5);
956
        }
957
    }
958
}
959

960
961
962
963
964
965
966
static bool modKeyDown(int state) {
    const uint keyModX = (options->keyCmdAllModKey() == Qt::Key_Meta) ?
                                                    KKeyServer::modXMeta() : KKeyServer::modXAlt();
    return keyModX  && (state & KKeyServer::accelModMaskX()) == keyModX;
}


967
// return value matters only when filtering events before decoration gets them
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
968
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)
969
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
970
971
972
    if (waylandServer()) {
        return true;
    }