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