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 | // own |
22 | #include "client.h" |
23 | // kwin |
24 | #ifdef KWIN_BUILD_ACTIVITIES |
25 | #include "activities.h" |
26 | #endif |
27 | #ifdef KWIN_BUILD_KAPPMENU |
28 | #include "appmenu.h" |
29 | #endif |
30 | #include "atoms.h" |
31 | #include "bridge.h" |
32 | #include "client_machine.h" |
33 | #include "composite.h" |
34 | #include "cursor.h" |
35 | #include "decorations.h" |
36 | #include "deleted.h" |
37 | #include "focuschain.h" |
38 | #include "group.h" |
39 | #include "paintredirector.h" |
40 | #include "shadow.h" |
41 | #ifdef KWIN_BUILD_TABBOX |
42 | #include "tabbox.h" |
43 | #endif |
44 | #include "workspace.h" |
45 | // KDE |
46 | #include <KDE/KIconLoader> |
47 | #include <KDE/KStandardDirs> |
48 | #include <KDE/KWindowSystem> |
49 | // Qt |
50 | #include <QApplication> |
51 | #include <QProcess> |
52 | #ifdef KWIN_BUILD_SCRIPTING |
53 | #include <QScriptEngine> |
54 | #include <QScriptProgram> |
55 | #endif |
56 | #include <QWhatsThis> |
57 | // X |
58 | #ifdef HAVE_XSYNC |
59 | #include <X11/extensions/sync.h> |
60 | #endif |
61 | // system |
62 | #include <unistd.h> |
63 | #include <signal.h> |
64 | |
65 | // Put all externs before the namespace statement to allow the linker |
66 | // to resolve them properly |
67 | |
68 | namespace KWin |
69 | { |
70 | |
71 | bool Client::s_haveResizeEffect = false; |
72 | |
73 | // Creating a client: |
74 | // - only by calling Workspace::createClient() |
75 | // - it creates a new client and calls manage() for it |
76 | // |
77 | // Destroying a client: |
78 | // - destroyClient() - only when the window itself has been destroyed |
79 | // - releaseWindow() - the window is kept, only the client itself is destroyed |
80 | |
81 | /** |
82 | * \class Client client.h |
83 | * \brief The Client class encapsulates a window decoration frame. |
84 | */ |
85 | |
86 | /** |
87 | * This ctor is "dumb" - it only initializes data. All the real initialization |
88 | * is done in manage(). |
89 | */ |
90 | Client::Client() |
91 | : Toplevel() |
92 | , m_client(XCB_WINDOW_NONE) |
93 | , m_wrapper() |
94 | , decoration(NULL) |
95 | , bridge(new Bridge(this)) |
96 | , m_activityUpdatesBlocked(false) |
97 | , m_blockedActivityUpdatesRequireTransients(false) |
98 | , m_moveResizeGrabWindow() |
99 | , move_resize_has_keyboard_grab(false) |
100 | , m_managed(false) |
101 | , transient_for (NULL) |
102 | , m_transientForId(XCB_WINDOW_NONE) |
103 | , m_originalTransientForId(XCB_WINDOW_NONE) |
104 | , shade_below(NULL) |
105 | , skip_switcher(false) |
106 | , blocks_compositing(false) |
107 | , m_cursor(Qt::ArrowCursor) |
108 | , autoRaiseTimer(NULL) |
109 | , shadeHoverTimer(NULL) |
110 | , delayedMoveResizeTimer(NULL) |
111 | , m_colormap(XCB_COLORMAP_NONE) |
112 | , in_group(NULL) |
113 | , m_windowGroup(XCB_WINDOW_NONE) |
114 | , tab_group(NULL) |
115 | , in_layer(UnknownLayer) |
116 | , ping_timer(NULL) |
117 | , m_killHelperPID(0) |
118 | , m_pingTimestamp(XCB_TIME_CURRENT_TIME) |
119 | , m_userTime(XCB_TIME_CURRENT_TIME) // Not known yet |
120 | , allowed_actions(0) |
121 | , block_geometry_updates(0) |
122 | , pending_geometry_update(PendingGeometryNone) |
123 | , shade_geometry_change(false) |
124 | , border_left(0) |
125 | , border_right(0) |
126 | , border_top(0) |
127 | , border_bottom(0) |
128 | , padding_left(0) |
129 | , padding_right(0) |
130 | , padding_top(0) |
131 | , padding_bottom(0) |
132 | , sm_stacking_order(-1) |
133 | , paintRedirector(0) |
134 | , m_firstInTabBox(false) |
135 | , electricMaximizing(false) |
136 | , activitiesDefined(false) |
137 | , needsSessionInteract(false) |
138 | , needsXWindowMove(false) |
139 | #ifdef KWIN_BUILD_KAPPMENU |
140 | , m_menuAvailable(false) |
141 | #endif |
142 | , m_decoInputExtent() |
143 | { |
144 | // TODO: Do all as initialization |
145 | #ifdef HAVE_XSYNC |
146 | syncRequest.counter = syncRequest.alarm = None; |
147 | syncRequest.timeout = syncRequest.failsafeTimeout = NULL; |
148 | syncRequest.isPending = false; |
149 | #endif |
150 | |
151 | // Set the initial mapping state |
152 | mapping_state = Withdrawn; |
153 | quick_tile_mode = QuickTileNone; |
154 | desk = 0; // No desktop yet |
155 | |
156 | mode = PositionCenter; |
157 | buttonDown = false; |
158 | moveResizeMode = false; |
159 | |
160 | info = NULL; |
161 | |
162 | shade_mode = ShadeNone; |
163 | active = false; |
164 | deleting = false; |
165 | keep_above = false; |
166 | keep_below = false; |
167 | motif_may_move = true; |
168 | motif_may_resize = true; |
169 | motif_may_close = true; |
170 | fullscreen_mode = FullScreenNone; |
171 | skip_taskbar = false; |
172 | original_skip_taskbar = false; |
173 | minimized = false; |
174 | hidden = false; |
175 | modal = false; |
176 | noborder = false; |
177 | app_noborder = false; |
178 | motif_noborder = false; |
179 | urgency = false; |
180 | ignore_focus_stealing = false; |
181 | demands_attention = false; |
182 | check_active_modal = false; |
183 | |
184 | Pdeletewindow = 0; |
185 | Ptakefocus = 0; |
186 | Ptakeactivity = 0; |
187 | Pcontexthelp = 0; |
188 | Pping = 0; |
189 | input = false; |
190 | skip_pager = false; |
191 | |
192 | max_mode = MaximizeRestore; |
193 | |
194 | //Client to workspace connections require that each |
195 | //client constructed be connected to the workspace wrapper |
196 | |
197 | #ifdef KWIN_BUILD_TABBOX |
198 | // TabBoxClient |
199 | m_tabBoxClient = QSharedPointer<TabBox::TabBoxClientImpl>(new TabBox::TabBoxClientImpl(this)); |
200 | #endif |
201 | |
202 | geom = QRect(0, 0, 100, 100); // So that decorations don't start with size being (0,0) |
203 | client_size = QSize(100, 100); |
204 | ready_for_painting = false; // wait for first damage or sync reply |
205 | |
206 | connect(this, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), SIGNAL(geometryChanged())); |
207 | connect(this, SIGNAL(clientMaximizedStateChanged(KWin::Client*,KDecorationDefines::MaximizeMode)), SIGNAL(geometryChanged())); |
208 | connect(this, SIGNAL(clientStepUserMovedResized(KWin::Client*,QRect)), SIGNAL(geometryChanged())); |
209 | connect(this, SIGNAL(clientStartUserMovedResized(KWin::Client*)), SIGNAL(moveResizedChanged())); |
210 | connect(this, SIGNAL(clientFinishUserMovedResized(KWin::Client*)), SIGNAL(moveResizedChanged())); |
211 | connect(this, SIGNAL(clientStartUserMovedResized(KWin::Client*)), SLOT(removeCheckScreenConnection())); |
212 | connect(this, SIGNAL(clientFinishUserMovedResized(KWin::Client*)), SLOT(setupCheckScreenConnection())); |
213 | |
214 | connect(clientMachine(), SIGNAL(localhostChanged()), SLOT(updateCaption())); |
215 | connect(options, SIGNAL(condensedTitleChanged()), SLOT(updateCaption())); |
216 | |
217 | // SELI TODO: Initialize xsizehints?? |
218 | } |
219 | |
220 | /** |
221 | * "Dumb" destructor. |
222 | */ |
223 | Client::~Client() |
224 | { |
225 | if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive |
226 | ::kill(m_killHelperPID, SIGTERM); |
227 | m_killHelperPID = 0; |
228 | } |
229 | //SWrapper::Client::clientRelease(this); |
230 | #ifdef HAVE_XSYNC |
231 | if (syncRequest.alarm != None) |
232 | XSyncDestroyAlarm(display(), syncRequest.alarm); |
233 | #endif |
234 | assert(!moveResizeMode); |
235 | assert(m_client == XCB_WINDOW_NONE); |
236 | assert(m_wrapper == XCB_WINDOW_NONE); |
237 | //assert( frameId() == None ); |
238 | assert(decoration == NULL); |
239 | assert(block_geometry_updates == 0); |
240 | assert(!check_active_modal); |
241 | delete bridge; |
242 | } |
243 | |
244 | // Use destroyClient() or releaseWindow(), Client instances cannot be deleted directly |
245 | void Client::deleteClient(Client* c) |
246 | { |
247 | delete c; |
248 | } |
249 | |
250 | /** |
251 | * Releases the window. The client has done its job and the window is still existing. |
252 | */ |
253 | void Client::releaseWindow(bool on_shutdown) |
254 | { |
255 | assert(!deleting); |
256 | deleting = true; |
257 | Deleted* del = NULL; |
258 | if (!on_shutdown) { |
259 | del = Deleted::create(this); |
260 | } |
261 | if (moveResizeMode) |
262 | emit clientFinishUserMovedResized(this); |
263 | emit windowClosed(this, del); |
264 | finishCompositing(); |
265 | RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules |
266 | StackingUpdatesBlocker blocker(workspace()); |
267 | if (moveResizeMode) |
268 | leaveMoveResize(); |
269 | finishWindowRules(); |
270 | ++block_geometry_updates; |
271 | if (isOnCurrentDesktop() && isShown(true)) |
272 | addWorkspaceRepaint(visibleRect()); |
273 | // Grab X during the release to make removing of properties, setting to withdrawn state |
274 | // and repareting to root an atomic operation (http://lists.kde.org/?l=kde-devel&m=116448102901184&w=2) |
275 | grabXServer(); |
276 | exportMappingState(WithdrawnState); |
277 | setModal(false); // Otherwise its mainwindow wouldn't get focus |
278 | hidden = true; // So that it's not considered visible anymore (can't use hideClient(), it would set flags) |
279 | if (!on_shutdown) |
280 | workspace()->clientHidden(this); |
281 | XUnmapWindow(display(), frameId()); // Destroying decoration would cause ugly visual effect |
282 | destroyDecoration(); |
283 | cleanGrouping(); |
284 | if (!on_shutdown) { |
285 | workspace()->removeClient(this); |
286 | // Only when the window is being unmapped, not when closing down KWin (NETWM sections 5.5,5.7) |
287 | info->setDesktop(0); |
288 | desk = 0; |
289 | info->setState(0, info->state()); // Reset all state flags |
290 | } else |
291 | untab(); |
292 | xcb_connection_t *c = connection(); |
293 | xcb_delete_property(c, m_client, atoms->kde_net_wm_user_creation_time); |
294 | xcb_delete_property(c, m_client, atoms->net_frame_extents); |
295 | xcb_delete_property(c, m_client, atoms->kde_net_wm_frame_strut); |
296 | xcb_reparent_window(c, m_client, rootWindow(), x(), y()); |
297 | xcb_change_save_set(c, XCB_SET_MODE_DELETE, m_client); |
298 | XSelectInput(display(), m_client, NoEventMask); |
299 | if (on_shutdown) |
300 | // Map the window, so it can be found after another WM is started |
301 | xcb_map_window(connection(), m_client); |
302 | // TODO: Preserve minimized, shaded etc. state? |
303 | else // Make sure it's not mapped if the app unmapped it (#65279). The app |
304 | // may do map+unmap before we initially map the window by calling rawShow() from manage(). |
305 | xcb_unmap_window(connection(), m_client); |
306 | m_client = XCB_WINDOW_NONE; |
307 | m_wrapper.reset(); |
308 | XDestroyWindow(display(), frameId()); |
309 | //frame = None; |
310 | --block_geometry_updates; // Don't use GeometryUpdatesBlocker, it would now set the geometry |
311 | if (!on_shutdown) { |
312 | disownDataPassedToDeleted(); |
313 | del->unrefWindow(); |
314 | } |
315 | checkNonExistentClients(); |
316 | deleteClient(this); |
317 | ungrabXServer(); |
318 | } |
319 | |
320 | /** |
321 | * Like releaseWindow(), but this one is called when the window has been already destroyed |
322 | * (E.g. The application closed it) |
323 | */ |
324 | void Client::destroyClient() |
325 | { |
326 | assert(!deleting); |
327 | deleting = true; |
328 | Deleted* del = Deleted::create(this); |
329 | if (moveResizeMode) |
330 | emit clientFinishUserMovedResized(this); |
331 | emit windowClosed(this, del); |
332 | finishCompositing(); |
333 | RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules |
334 | StackingUpdatesBlocker blocker(workspace()); |
335 | if (moveResizeMode) |
336 | leaveMoveResize(); |
337 | finishWindowRules(); |
338 | ++block_geometry_updates; |
339 | if (isOnCurrentDesktop() && isShown(true)) |
340 | addWorkspaceRepaint(visibleRect()); |
341 | setModal(false); |
342 | hidden = true; // So that it's not considered visible anymore |
343 | workspace()->clientHidden(this); |
344 | destroyDecoration(); |
345 | cleanGrouping(); |
346 | workspace()->removeClient(this); |
347 | m_client = XCB_WINDOW_NONE; // invalidate |
348 | m_wrapper.reset(); |
349 | XDestroyWindow(display(), frameId()); |
350 | //frame = None; |
351 | --block_geometry_updates; // Don't use GeometryUpdatesBlocker, it would now set the geometry |
352 | disownDataPassedToDeleted(); |
353 | del->unrefWindow(); |
354 | checkNonExistentClients(); |
355 | deleteClient(this); |
356 | } |
357 | |
358 | // DnD handling for input shaping is broken in the clients for all Qt versions before 4.8.3 |
359 | // NOTICE do not query the Qt version macro, this is a runtime problem! |
360 | // TODO KDE5 remove this |
361 | static inline bool qtBefore483() |
362 | { |
363 | QStringList l = QString(qVersion()).split("." ); |
364 | // "4.x.y" |
365 | return l.at(1).toUInt() < 5 && l.at(1).toUInt() < 9 && l.at(2).toUInt() < 3; |
366 | } |
367 | |
368 | void Client::updateInputWindow() |
369 | { |
370 | static bool brokenQtInputHandling = qtBefore483(); |
371 | if (brokenQtInputHandling) |
372 | return; |
373 | |
374 | if (!Xcb::Extensions::self()->isShapeInputAvailable()) |
375 | return; |
376 | |
377 | QRegion region; |
378 | |
379 | if (!noBorder()) { |
380 | // This function is implemented as a slot to avoid breaking binary |
381 | // compatibility |
382 | QMetaObject::invokeMethod(decoration, "region" , Qt::DirectConnection, |
383 | Q_RETURN_ARG(QRegion, region), |
384 | Q_ARG(KDecorationDefines::Region, KDecorationDefines::ExtendedBorderRegion)); |
385 | } |
386 | |
387 | if (region.isEmpty()) { |
388 | m_decoInputExtent.reset(); |
389 | return; |
390 | } |
391 | |
392 | QRect bounds = region.boundingRect(); |
393 | input_offset = bounds.topLeft(); |
394 | |
395 | // Move the bounding rect to screen coordinates |
396 | bounds.translate(geometry().topLeft()); |
397 | |
398 | // Move the region to input window coordinates |
399 | region.translate(-input_offset); |
400 | |
401 | if (!m_decoInputExtent.isValid()) { |
402 | const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; |
403 | const uint32_t values[] = {true, |
404 | XCB_EVENT_MASK_ENTER_WINDOW | |
405 | XCB_EVENT_MASK_LEAVE_WINDOW | |
406 | XCB_EVENT_MASK_BUTTON_PRESS | |
407 | XCB_EVENT_MASK_BUTTON_RELEASE | |
408 | XCB_EVENT_MASK_POINTER_MOTION |
409 | }; |
410 | m_decoInputExtent.create(bounds, XCB_WINDOW_CLASS_INPUT_ONLY, mask, values); |
411 | if (mapping_state == Mapped) |
412 | m_decoInputExtent.map(); |
413 | } else { |
414 | m_decoInputExtent.setGeometry(bounds); |
415 | } |
416 | |
417 | const QVector<xcb_rectangle_t> rects = Xcb::regionToRects(region); |
418 | xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, |
419 | m_decoInputExtent, 0, 0, rects.count(), rects.constData()); |
420 | } |
421 | |
422 | void Client::updateDecoration(bool check_workspace_pos, bool force) |
423 | { |
424 | if (!force && |
425 | ((decoration == NULL && noBorder()) || (decoration != NULL && !noBorder()))) |
426 | return; |
427 | QRect oldgeom = geometry(); |
428 | blockGeometryUpdates(true); |
429 | if (force) |
430 | destroyDecoration(); |
431 | if (!noBorder()) { |
432 | createDecoration(oldgeom); |
433 | } else |
434 | destroyDecoration(); |
435 | if (check_workspace_pos) |
436 | checkWorkspacePosition(oldgeom); |
437 | updateInputWindow(); |
438 | blockGeometryUpdates(false); |
439 | if (!noBorder()) |
440 | decoration->widget()->show(); |
441 | updateFrameExtents(); |
442 | } |
443 | |
444 | void Client::createDecoration(const QRect& oldgeom) |
445 | { |
446 | setMask(QRegion()); // Reset shape mask |
447 | if (decorationPlugin()->isDisabled()) { |
448 | decoration = NULL; |
449 | return; |
450 | } else { |
451 | decoration = decorationPlugin()->createDecoration(bridge); |
452 | } |
453 | connect(this, SIGNAL(shadeChanged()), decoration, SLOT(shadeChange())); |
454 | connect(this, SIGNAL(desktopChanged()), decoration, SLOT(desktopChange())); |
455 | connect(this, SIGNAL(captionChanged()), decoration, SLOT(captionChange())); |
456 | connect(this, SIGNAL(iconChanged()), decoration, SLOT(iconChange())); |
457 | connect(this, SIGNAL(activeChanged()), decoration, SLOT(activeChange())); |
458 | connect(this, SIGNAL(clientMaximizedStateChanged(KWin::Client*,KDecorationDefines::MaximizeMode)), |
459 | decoration, SLOT(maximizeChange())); |
460 | connect(this, SIGNAL(keepAboveChanged(bool)), decoration, SIGNAL(keepAboveChanged(bool))); |
461 | connect(this, SIGNAL(keepBelowChanged(bool)), decoration, SIGNAL(keepBelowChanged(bool))); |
462 | #ifdef KWIN_BUILD_KAPPMENU |
463 | connect(this, SIGNAL(showRequest()), decoration, SIGNAL(showRequest())); |
464 | connect(this, SIGNAL(appMenuAvailable()), decoration, SIGNAL(appMenuAvailable())); |
465 | connect(this, SIGNAL(appMenuUnavailable()), decoration, SIGNAL(appMenuUnavailable())); |
466 | connect(this, SIGNAL(menuHidden()), decoration, SIGNAL(menuHidden())); |
467 | #endif |
468 | // TODO: Check decoration's minimum size? |
469 | decoration->init(); |
470 | decoration->widget()->installEventFilter(this); |
471 | xcb_reparent_window(connection(), decoration->widget()->winId(), frameId(), 0, 0); |
472 | decoration->widget()->lower(); |
473 | decoration->borders(border_left, border_right, border_top, border_bottom); |
474 | padding_left = padding_right = padding_top = padding_bottom = 0; |
475 | if (KDecorationUnstable *deco2 = dynamic_cast<KDecorationUnstable*>(decoration)) |
476 | deco2->padding(padding_left, padding_right, padding_top, padding_bottom); |
477 | Xcb::moveWindow(decoration->widget()->winId(), -padding_left, -padding_top); |
478 | move(calculateGravitation(false)); |
479 | plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); |
480 | if (Compositor::compositing()) { |
481 | paintRedirector = PaintRedirector::create(this, decoration->widget()); |
482 | discardWindowPixmap(); |
483 | } |
484 | emit geometryShapeChanged(this, oldgeom); |
485 | } |
486 | |
487 | void Client::destroyDecoration() |
488 | { |
489 | QRect oldgeom = geometry(); |
490 | if (decoration != NULL) { |
491 | delete decoration; |
492 | decoration = NULL; |
493 | paintRedirector = NULL; |
494 | QPoint grav = calculateGravitation(true); |
495 | border_left = border_right = border_top = border_bottom = 0; |
496 | setMask(QRegion()); // Reset shape mask |
497 | plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); |
498 | move(grav); |
499 | if (compositing()) |
500 | discardWindowPixmap(); |
501 | if (!deleting) { |
502 | emit geometryShapeChanged(this, oldgeom); |
503 | } |
504 | } |
505 | m_decoInputExtent.reset(); |
506 | } |
507 | |
508 | bool Client::checkBorderSizes(bool also_resize) |
509 | { |
510 | if (decoration == NULL) |
511 | return false; |
512 | |
513 | int new_left = 0, new_right = 0, new_top = 0, new_bottom = 0; |
514 | if (KDecorationUnstable *deco2 = dynamic_cast<KDecorationUnstable*>(decoration)) |
515 | deco2->padding(new_left, new_right, new_top, new_bottom); |
516 | if (padding_left != new_left || padding_top != new_top) |
517 | Xcb::moveWindow(decoration->widget()->winId(), -new_left, -new_top); |
518 | padding_left = new_left; |
519 | padding_right = new_right; |
520 | padding_top = new_top; |
521 | padding_bottom = new_bottom; |
522 | decoration->borders(new_left, new_right, new_top, new_bottom); |
523 | if (new_left == border_left && new_right == border_right && |
524 | new_top == border_top && new_bottom == border_bottom) |
525 | return false; |
526 | if (!also_resize) { |
527 | border_left = new_left; |
528 | border_right = new_right; |
529 | border_top = new_top; |
530 | border_bottom = new_bottom; |
531 | return true; |
532 | } |
533 | GeometryUpdatesBlocker blocker(this); |
534 | move(calculateGravitation(true)); |
535 | border_left = new_left; |
536 | border_right = new_right; |
537 | border_top = new_top; |
538 | border_bottom = new_bottom; |
539 | move(calculateGravitation(false)); |
540 | QRect oldgeom = geometry(); |
541 | plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); |
542 | checkWorkspacePosition(oldgeom); |
543 | return true; |
544 | } |
545 | |
546 | void Client::triggerDecorationRepaint() |
547 | { |
548 | if (decoration != NULL) |
549 | decoration->widget()->update(); |
550 | } |
551 | |
552 | void Client::layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom, Client::CoordinateMode mode) const |
553 | { |
554 | QRect r = decoration->widget()->rect(); |
555 | if (mode == WindowRelative) |
556 | r.translate(-padding_left, -padding_top); |
557 | |
558 | NETStrut strut = info->frameOverlap(); |
559 | |
560 | // Ignore the overlap strut when compositing is disabled |
561 | if (!compositing() || !decorationPlugin()->supportsFrameOverlap()) |
562 | strut.left = strut.top = strut.right = strut.bottom = 0; |
563 | else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1) { |
564 | top = QRect(r.x(), r.y(), r.width(), r.height() / 3); |
565 | left = QRect(r.x(), r.y() + top.height(), width() / 2, r.height() / 3); |
566 | right = QRect(r.x() + left.width(), r.y() + top.height(), r.width() - left.width(), left.height()); |
567 | bottom = QRect(r.x(), r.y() + top.height() + left.height(), r.width(), r.height() - left.height() - top.height()); |
568 | return; |
569 | } |
570 | |
571 | top = QRect(r.x(), r.y(), r.width(), padding_top + border_top + strut.top); |
572 | bottom = QRect(r.x(), r.y() + r.height() - padding_bottom - border_bottom - strut.bottom, |
573 | r.width(), padding_bottom + border_bottom + strut.bottom); |
574 | left = QRect(r.x(), r.y() + top.height(), |
575 | padding_left + border_left + strut.left, r.height() - top.height() - bottom.height()); |
576 | right = QRect(r.x() + r.width() - padding_right - border_right - strut.right, r.y() + top.height(), |
577 | padding_right + border_right + strut.right, r.height() - top.height() - bottom.height()); |
578 | } |
579 | |
580 | QRegion Client::decorationPendingRegion() const |
581 | { |
582 | if (!paintRedirector) |
583 | return QRegion(); |
584 | return paintRedirector->scheduledRepaintRegion().translated(x() - padding_left, y() - padding_top); |
585 | } |
586 | |
587 | QRect Client::transparentRect() const |
588 | { |
589 | if (isShade()) |
590 | return QRect(); |
591 | |
592 | NETStrut strut = info->frameOverlap(); |
593 | // Ignore the strut when compositing is disabled or the decoration doesn't support it |
594 | if (!compositing() || !decorationPlugin()->supportsFrameOverlap()) |
595 | strut.left = strut.top = strut.right = strut.bottom = 0; |
596 | else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1) |
597 | return QRect(); |
598 | |
599 | const QRect r = QRect(clientPos(), clientSize()) |
600 | .adjusted(strut.left, strut.top, -strut.right, -strut.bottom); |
601 | if (r.isValid()) |
602 | return r; |
603 | |
604 | return QRect(); |
605 | } |
606 | |
607 | void Client::detectNoBorder() |
608 | { |
609 | if (shape()) { |
610 | noborder = true; |
611 | app_noborder = true; |
612 | return; |
613 | } |
614 | switch(windowType()) { |
615 | case NET::Desktop : |
616 | case NET::Dock : |
617 | case NET::TopMenu : |
618 | case NET::Splash : |
619 | noborder = true; |
620 | app_noborder = true; |
621 | break; |
622 | case NET::Unknown : |
623 | case NET::Normal : |
624 | case NET::Toolbar : |
625 | case NET::Menu : |
626 | case NET::Dialog : |
627 | case NET::Utility : |
628 | noborder = false; |
629 | break; |
630 | default: |
631 | abort(); |
632 | } |
633 | // NET::Override is some strange beast without clear definition, usually |
634 | // just meaning "noborder", so let's treat it only as such flag, and ignore it as |
635 | // a window type otherwise (SUPPORTED_WINDOW_TYPES_MASK doesn't include it) |
636 | if (info->windowType(SUPPORTED_MANAGED_WINDOW_TYPES_MASK | NET::OverrideMask) == NET::Override) { |
637 | noborder = true; |
638 | app_noborder = true; |
639 | } |
640 | } |
641 | |
642 | void Client::updateFrameExtents() |
643 | { |
644 | NETStrut strut; |
645 | strut.left = border_left; |
646 | strut.right = border_right; |
647 | strut.top = border_top; |
648 | strut.bottom = border_bottom; |
649 | info->setFrameExtents(strut); |
650 | } |
651 | |
652 | /** |
653 | * Resizes the decoration, and makes sure the decoration widget gets resize event |
654 | * even if the size hasn't changed. This is needed to make sure the decoration |
655 | * re-layouts (e.g. when maximization state changes, |
656 | * the decoration may alter some borders, but the actual size |
657 | * of the decoration stays the same). |
658 | */ |
659 | void Client::resizeDecoration(const QSize& s) |
660 | { |
661 | if (decoration == NULL) |
662 | return; |
663 | QSize newSize = s + QSize(padding_left + padding_right, padding_top + padding_bottom); |
664 | QSize oldSize = decoration->widget()->size(); |
665 | decoration->resize(newSize); |
666 | if (oldSize == newSize) { |
667 | QResizeEvent e(newSize, oldSize); |
668 | QApplication::sendEvent(decoration->widget(), &e); |
669 | } else if (paintRedirector) { // oldSize != newSize |
670 | paintRedirector->resizePixmaps(); |
671 | } else { |
672 | triggerDecorationRepaint(); |
673 | } |
674 | updateInputWindow(); |
675 | } |
676 | |
677 | bool Client::noBorder() const |
678 | { |
679 | return decorationPlugin()->isDisabled() || noborder || isFullScreen(); |
680 | } |
681 | |
682 | bool Client::userCanSetNoBorder() const |
683 | { |
684 | return !isFullScreen() && !isShade() && !tabGroup(); |
685 | } |
686 | |
687 | void Client::setNoBorder(bool set) |
688 | { |
689 | if (!userCanSetNoBorder()) |
690 | return; |
691 | set = rules()->checkNoBorder(set); |
692 | if (noborder == set) |
693 | return; |
694 | noborder = set; |
695 | updateDecoration(true, false); |
696 | updateWindowRules(Rules::NoBorder); |
697 | } |
698 | |
699 | void Client::checkNoBorder() |
700 | { |
701 | setNoBorder(app_noborder); |
702 | } |
703 | |
704 | void Client::updateShape() |
705 | { |
706 | if (shape()) { |
707 | // Workaround for #19644 - Shaped windows shouldn't have decoration |
708 | if (!app_noborder) { |
709 | // Only when shape is detected for the first time, still let the user to override |
710 | app_noborder = true; |
711 | noborder = rules()->checkNoBorder(true); |
712 | updateDecoration(true); |
713 | } |
714 | if (noBorder()) { |
715 | xcb_shape_combine(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_SHAPE_SK_BOUNDING, |
716 | frameId(), clientPos().x(), clientPos().y(), window()); |
717 | } |
718 | } else if (app_noborder) { |
719 | xcb_shape_mask(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, frameId(), 0, 0, XCB_PIXMAP_NONE); |
720 | detectNoBorder(); |
721 | app_noborder = noborder; |
722 | noborder = rules()->checkNoBorder(noborder || motif_noborder); |
723 | updateDecoration(true); |
724 | } |
725 | |
726 | // Decoration mask (i.e. 'else' here) setting is done in setMask() |
727 | // when the decoration calls it or when the decoration is created/destroyed |
728 | updateInputShape(); |
729 | if (compositing()) { |
730 | addRepaintFull(); |
731 | addWorkspaceRepaint(visibleRect()); // In case shape change removes part of this window |
732 | } |
733 | emit geometryShapeChanged(this, geometry()); |
734 | } |
735 | |
736 | static Xcb::Window shape_helper_window(XCB_WINDOW_NONE); |
737 | |
738 | void Client::updateInputShape() |
739 | { |
740 | if (hiddenPreview()) // Sets it to none, don't change |
741 | return; |
742 | if (Xcb::Extensions::self()->isShapeInputAvailable()) { |
743 | // There appears to be no way to find out if a window has input |
744 | // shape set or not, so always propagate the input shape |
745 | // (it's the same like the bounding shape by default). |
746 | // Also, build the shape using a helper window, not directly |
747 | // in the frame window, because the sequence set-shape-to-frame, |
748 | // remove-shape-of-client, add-input-shape-of-client has the problem |
749 | // that after the second step there's a hole in the input shape |
750 | // until the real shape of the client is added and that can make |
751 | // the window lose focus (which is a problem with mouse focus policies) |
752 | // TODO: It seems there is, after all - XShapeGetRectangles() - but maybe this is better |
753 | if (!shape_helper_window.isValid()) |
754 | shape_helper_window.create(QRect(0, 0, 1, 1)); |
755 | shape_helper_window.resize(width(), height()); |
756 | xcb_connection_t *c = connection(); |
757 | xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING, |
758 | shape_helper_window, 0, 0, frameId()); |
759 | xcb_shape_combine(c, XCB_SHAPE_SO_SUBTRACT, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING, |
760 | shape_helper_window, clientPos().x(), clientPos().y(), window()); |
761 | xcb_shape_combine(c, XCB_SHAPE_SO_UNION, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT, |
762 | shape_helper_window, clientPos().x(), clientPos().y(), window()); |
763 | xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT, |
764 | frameId(), 0, 0, shape_helper_window); |
765 | } |
766 | } |
767 | |
768 | void Client::setMask(const QRegion& reg, int mode) |
769 | { |
770 | QRegion r = reg.translated(-padding_left, -padding_right) & QRect(0, 0, width(), height()); |
771 | if (_mask == r) |
772 | return; |
773 | _mask = r; |
774 | xcb_connection_t *c = connection(); |
775 | xcb_window_t shape_window = frameId(); |
776 | if (shape()) { |
777 | // The same way of applying a shape without strange intermediate states like above |
778 | if (!shape_helper_window.isValid()) |
779 | shape_helper_window.create(QRect(0, 0, 1, 1)); |
780 | shape_window = shape_helper_window; |
781 | } |
782 | if (_mask.isEmpty()) { |
783 | xcb_shape_mask(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, shape_window, 0, 0, XCB_PIXMAP_NONE); |
784 | } else { |
785 | const QVector< QRect > rects = _mask.rects(); |
786 | QVector< xcb_rectangle_t > xrects(rects.count()); |
787 | for (int i = 0; i < rects.count(); ++i) { |
788 | const QRect &rect = rects.at(i); |
789 | xcb_rectangle_t xrect; |
790 | xrect.x = rect.x(); |
791 | xrect.y = rect.y(); |
792 | xrect.width = rect.width(); |
793 | xrect.height = rect.height(); |
794 | xrects[i] = xrect; |
795 | } |
796 | xcb_shape_rectangles(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, mode, shape_window, |
797 | 0, 0, xrects.count(), xrects.constData()); |
798 | } |
799 | if (shape()) { |
800 | // The rest of the applying using a temporary window |
801 | xcb_rectangle_t rec = { 0, 0, static_cast<uint16_t>(clientSize().width()), |
802 | static_cast<uint16_t>(clientSize().height()) }; |
803 | xcb_shape_rectangles(c, XCB_SHAPE_SO_SUBTRACT, XCB_SHAPE_SK_BOUNDING, XCB_CLIP_ORDERING_UNSORTED, |
804 | shape_helper_window, clientPos().x(), clientPos().y(), 1, &rec); |
805 | xcb_shape_combine(c, XCB_SHAPE_SO_UNION, XCB_SHAPE_SK_BOUNDING, XCB_SHAPE_SK_BOUNDING, |
806 | shape_helper_window, clientPos().x(), clientPos().y(), window()); |
807 | xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_SHAPE_SK_BOUNDING, |
808 | frameId(), 0, 0, shape_helper_window); |
809 | } |
810 | emit geometryShapeChanged(this, geometry()); |
811 | updateShape(); |
812 | } |
813 | |
814 | QRegion Client::mask() const |
815 | { |
816 | if (_mask.isEmpty()) |
817 | return QRegion(0, 0, width(), height()); |
818 | return _mask; |
819 | } |
820 | |
821 | void Client::hideClient(bool hide) |
822 | { |
823 | if (hidden == hide) |
824 | return; |
825 | hidden = hide; |
826 | updateVisibility(); |
827 | } |
828 | |
829 | /** |
830 | * Returns whether the window is minimizable or not |
831 | */ |
832 | bool Client::isMinimizable() const |
833 | { |
834 | if (isSpecialWindow() && !isTransient()) |
835 | return false; |
836 | if (!rules()->checkMinimize(true)) |
837 | return false; |
838 | |
839 | if (isTransient()) { |
840 | // #66868 - Let other xmms windows be minimized when the mainwindow is minimized |
841 | bool shown_mainwindow = false; |
842 | ClientList mainclients = mainClients(); |
843 | for (ClientList::ConstIterator it = mainclients.constBegin(); |
844 | it != mainclients.constEnd(); |
845 | ++it) |
846 | if ((*it)->isShown(true)) |
847 | shown_mainwindow = true; |
848 | if (!shown_mainwindow) |
849 | return true; |
850 | } |
851 | #if 0 |
852 | // This is here because kicker's taskbar doesn't provide separate entries |
853 | // for windows with an explicitly given parent |
854 | // TODO: perhaps this should be redone |
855 | // Disabled for now, since at least modal dialogs should be minimizable |
856 | // (resulting in the mainwindow being minimized too). |
857 | if (transientFor() != NULL) |
858 | return false; |
859 | #endif |
860 | if (!wantsTabFocus()) // SELI, TODO: - NET::Utility? why wantsTabFocus() - skiptaskbar? ? |
861 | return false; |
862 | return true; |
863 | } |
864 | |
865 | void Client::setMinimized(bool set) |
866 | { |
867 | set ? minimize() : unminimize(); |
868 | } |
869 | |
870 | /** |
871 | * Minimizes this client plus its transients |
872 | */ |
873 | void Client::minimize(bool avoid_animation) |
874 | { |
875 | if (!isMinimizable() || isMinimized()) |
876 | return; |
877 | |
878 | if (isShade()) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded |
879 | info->setState(0, NET::Shaded); |
880 | |
881 | minimized = true; |
882 | |
883 | updateVisibility(); |
884 | updateAllowedActions(); |
885 | workspace()->updateMinimizedOfTransients(this); |
886 | updateWindowRules(Rules::Minimize); |
887 | FocusChain::self()->update(this, FocusChain::MakeFirstMinimized); |
888 | // TODO: merge signal with s_minimized |
889 | emit clientMinimized(this, !avoid_animation); |
890 | |
891 | // Update states of all other windows in this group |
892 | if (tabGroup()) |
893 | tabGroup()->updateStates(this, TabGroup::Minimized); |
894 | emit minimizedChanged(); |
895 | } |
896 | |
897 | void Client::unminimize(bool avoid_animation) |
898 | { |
899 | if (!isMinimized()) |
900 | return; |
901 | |
902 | if (rules()->checkMinimize(false)) { |
903 | return; |
904 | } |
905 | |
906 | if (isShade()) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded |
907 | info->setState(NET::Shaded, NET::Shaded); |
908 | |
909 | minimized = false; |
910 | updateVisibility(); |
911 | updateAllowedActions(); |
912 | workspace()->updateMinimizedOfTransients(this); |
913 | updateWindowRules(Rules::Minimize); |
914 | emit clientUnminimized(this, !avoid_animation); |
915 | |
916 | // Update states of all other windows in this group |
917 | if (tabGroup()) |
918 | tabGroup()->updateStates(this, TabGroup::Minimized); |
919 | emit minimizedChanged(); |
920 | } |
921 | |
922 | QRect Client::iconGeometry() const |
923 | { |
924 | NETRect r = info->iconGeometry(); |
925 | QRect geom(r.pos.x, r.pos.y, r.size.width, r.size.height); |
926 | if (geom.isValid()) |
927 | return geom; |
928 | else { |
929 | // Check all mainwindows of this window (recursively) |
930 | foreach (Client * mainwin, mainClients()) { |
931 | geom = mainwin->iconGeometry(); |
932 | if (geom.isValid()) |
933 | return geom; |
934 | } |
935 | // No mainwindow (or their parents) with icon geometry was found |
936 | return QRect(); |
937 | } |
938 | } |
939 | |
940 | bool Client::isShadeable() const |
941 | { |
942 | return !isSpecialWindow() && !noBorder() && (rules()->checkShade(ShadeNormal) != rules()->checkShade(ShadeNone)); |
943 | } |
944 | |
945 | void Client::setShade(bool set) { |
946 | set ? setShade(ShadeNormal) : setShade(ShadeNone); |
947 | } |
948 | |
949 | void Client::setShade(ShadeMode mode) |
950 | { |
951 | if (mode == ShadeHover && isMove()) |
952 | return; // causes geometry breaks and is probably nasty |
953 | if (isSpecialWindow() || noBorder()) |
954 | mode = ShadeNone; |
955 | mode = rules()->checkShade(mode); |
956 | if (shade_mode == mode) |
957 | return; |
958 | bool was_shade = isShade(); |
959 | ShadeMode was_shade_mode = shade_mode; |
960 | shade_mode = mode; |
961 | |
962 | // Decorations may turn off some borders when shaded |
963 | // this has to happen _before_ the tab alignment since it will restrict the minimum geometry |
964 | if (decoration) |
965 | decoration->borders(border_left, border_right, border_top, border_bottom); |
966 | |
967 | // Update states of all other windows in this group |
968 | if (tabGroup()) |
969 | tabGroup()->updateStates(this, TabGroup::Shaded); |
970 | |
971 | if (was_shade == isShade()) { |
972 | // Decoration may want to update after e.g. hover-shade changes |
973 | emit shadeChanged(); |
974 | return; // No real change in shaded state |
975 | } |
976 | |
977 | assert(decoration != NULL); // noborder windows can't be shaded |
978 | GeometryUpdatesBlocker blocker(this); |
979 | |
980 | // TODO: All this unmapping, resizing etc. feels too much duplicated from elsewhere |
981 | if (isShade()) { |
982 | // shade_mode == ShadeNormal |
983 | addWorkspaceRepaint(visibleRect()); |
984 | // Shade |
985 | shade_geometry_change = true; |
986 | QSize s(sizeForClientSize(QSize(clientSize()))); |
987 | s.setHeight(border_top + border_bottom); |
988 | XSelectInput(display(), m_wrapper, ClientWinMask); // Avoid getting UnmapNotify |
989 | m_wrapper.unmap(); |
990 | xcb_unmap_window(connection(), m_client); |
991 | XSelectInput(display(), m_wrapper, ClientWinMask | SubstructureNotifyMask); |
992 | exportMappingState(IconicState); |
993 | plainResize(s); |
994 | shade_geometry_change = false; |
995 | if (was_shade_mode == ShadeHover) { |
996 | if (shade_below && workspace()->stackingOrder().indexOf(shade_below) > -1) |
997 | workspace()->restack(this, shade_below); |
998 | if (isActive()) |
999 | workspace()->activateNextClient(this); |
1000 | } else if (isActive()) { |
1001 | workspace()->focusToNull(); |
1002 | } |
1003 | } else { |
1004 | shade_geometry_change = true; |
1005 | QSize s(sizeForClientSize(clientSize())); |
1006 | shade_geometry_change = false; |
1007 | plainResize(s); |
1008 | if ((shade_mode == ShadeHover || shade_mode == ShadeActivated) && rules()->checkAcceptFocus(input)) |
1009 | setActive(true); |
1010 | if (shade_mode == ShadeHover) { |
1011 | ToplevelList order = workspace()->stackingOrder(); |
1012 | // invalidate, since "this" could be the topmost toplevel and shade_below dangeling |
1013 | shade_below = NULL; |
1014 | // this is likely related to the index parameter?! |
1015 | for (int idx = order.indexOf(this) + 1; idx < order.count(); ++idx) { |
1016 | shade_below = qobject_cast<Client*>(order.at(idx)); |
1017 | if (shade_below) { |
1018 | break; |
1019 | } |
1020 | } |
1021 | if (shade_below && shade_below->isNormalWindow()) |
1022 | workspace()->raiseClient(this); |
1023 | else |
1024 | shade_below = NULL; |
1025 | } |
1026 | XMapWindow(display(), wrapperId()); |
1027 | XMapWindow(display(), window()); |
1028 | exportMappingState(NormalState); |
1029 | if (isActive()) |
1030 | workspace()->requestFocus(this); |
1031 | } |
1032 | info->setState(isShade() ? NET::Shaded : 0, NET::Shaded); |
1033 | info->setState(isShown(false) ? 0 : NET::Hidden, NET::Hidden); |
1034 | discardWindowPixmap(); |
1035 | updateVisibility(); |
1036 | updateAllowedActions(); |
1037 | updateWindowRules(Rules::Shade); |
1038 | |
1039 | emit shadeChanged(); |
1040 | } |
1041 | |
1042 | void Client::shadeHover() |
1043 | { |
1044 | setShade(ShadeHover); |
1045 | cancelShadeHoverTimer(); |
1046 | } |
1047 | |
1048 | void Client::shadeUnhover() |
1049 | { |
1050 | if (!tabGroup() || tabGroup()->current() == this || |
1051 | tabGroup()->current()->shadeMode() == ShadeNormal) |
1052 | setShade(ShadeNormal); |
1053 | cancelShadeHoverTimer(); |
1054 | } |
1055 | |
1056 | void Client::cancelShadeHoverTimer() |
1057 | { |
1058 | delete shadeHoverTimer; |
1059 | shadeHoverTimer = 0; |
1060 | } |
1061 | |
1062 | void Client::toggleShade() |
1063 | { |
1064 | // If the mode is ShadeHover or ShadeActive, cancel shade too |
1065 | setShade(shade_mode == ShadeNone ? ShadeNormal : ShadeNone); |
1066 | } |
1067 | |
1068 | void Client::updateVisibility() |
1069 | { |
1070 | if (deleting) |
1071 | return; |
1072 | if (hidden && isCurrentTab()) { |
1073 | info->setState(NET::Hidden, NET::Hidden); |
1074 | setSkipTaskbar(true, false); // Also hide from taskbar |
1075 | if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) |
1076 | internalKeep(); |
1077 | else |
1078 | internalHide(); |
1079 | return; |
1080 | } |
1081 | if (isCurrentTab()) |
1082 | setSkipTaskbar(original_skip_taskbar, false); // Reset from 'hidden' |
1083 | if (minimized) { |
1084 | info->setState(NET::Hidden, NET::Hidden); |
1085 | if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) |
1086 | internalKeep(); |
1087 | else |
1088 | internalHide(); |
1089 | return; |
1090 | } |
1091 | info->setState(0, NET::Hidden); |
1092 | if (!isOnCurrentDesktop()) { |
1093 | if (compositing() && options->hiddenPreviews() != HiddenPreviewsNever) |
1094 | internalKeep(); |
1095 | else |
1096 | internalHide(); |
1097 | return; |
1098 | } |
1099 | if (!isOnCurrentActivity()) { |
1100 | if (compositing() && options->hiddenPreviews() != HiddenPreviewsNever) |
1101 | internalKeep(); |
1102 | else |
1103 | internalHide(); |
1104 | return; |
1105 | } |
1106 | if (isManaged()) |
1107 | resetShowingDesktop(true); |
1108 | internalShow(); |
1109 | } |
1110 | |
1111 | |
1112 | void Client::resetShowingDesktop(bool keep_hidden) |
1113 | { |
1114 | if (isDock() || !workspace()->showingDesktop()) |
1115 | return; |
1116 | bool belongs_to_desktop = false; |
1117 | for (ClientList::ConstIterator it = group()->members().constBegin(), |
1118 | end = group()->members().constEnd(); it != end; ++it) |
1119 | if ((belongs_to_desktop = (*it)->isDesktop())) |
1120 | break; |
1121 | |
1122 | if (!belongs_to_desktop) |
1123 | workspace()->resetShowingDesktop(keep_hidden); |
1124 | } |
1125 | |
1126 | /** |
1127 | * Sets the client window's mapping state. Possible values are |
1128 | * WithdrawnState, IconicState, NormalState. |
1129 | */ |
1130 | void Client::exportMappingState(int s) |
1131 | { |
1132 | assert(m_client != XCB_WINDOW_NONE); |
1133 | assert(!deleting || s == WithdrawnState); |
1134 | if (s == WithdrawnState) { |
1135 | XDeleteProperty(display(), window(), atoms->wm_state); |
1136 | return; |
1137 | } |
1138 | assert(s == NormalState || s == IconicState); |
1139 | |
1140 | unsigned long data[2]; |
1141 | data[0] = (unsigned long) s; |
1142 | data[1] = (unsigned long) None; |
1143 | XChangeProperty(display(), window(), atoms->wm_state, atoms->wm_state, 32, |
1144 | PropModeReplace, (unsigned char*)(data), 2); |
1145 | } |
1146 | |
1147 | void Client::internalShow() |
1148 | { |
1149 | if (mapping_state == Mapped) |
1150 | return; |
1151 | MappingState old = mapping_state; |
1152 | mapping_state = Mapped; |
1153 | if (old == Unmapped || old == Withdrawn) |
1154 | map(); |
1155 | if (old == Kept) { |
1156 | m_decoInputExtent.map(); |
1157 | updateHiddenPreview(); |
1158 | } |
1159 | if (Compositor::isCreated()) { |
1160 | Compositor::self()->checkUnredirect(); |
1161 | } |
1162 | } |
1163 | |
1164 | void Client::internalHide() |
1165 | { |
1166 | if (mapping_state == Unmapped) |
1167 | return; |
1168 | MappingState old = mapping_state; |
1169 | mapping_state = Unmapped; |
1170 | if (old == Mapped || old == Kept) |
1171 | unmap(); |
1172 | if (old == Kept) |
1173 | updateHiddenPreview(); |
1174 | addWorkspaceRepaint(visibleRect()); |
1175 | workspace()->clientHidden(this); |
1176 | if (Compositor::isCreated()) { |
1177 | Compositor::self()->checkUnredirect(); |
1178 | } |
1179 | } |
1180 | |
1181 | void Client::internalKeep() |
1182 | { |
1183 | assert(compositing()); |
1184 | if (mapping_state == Kept) |
1185 | return; |
1186 | MappingState old = mapping_state; |
1187 | mapping_state = Kept; |
1188 | if (old == Unmapped || old == Withdrawn) |
1189 | map(); |
1190 | m_decoInputExtent.unmap(); |
1191 | if (isActive()) |
1192 | workspace()->focusToNull(); // get rid of input focus, bug #317484 |
1193 | updateHiddenPreview(); |
1194 | addWorkspaceRepaint(visibleRect()); |
1195 | workspace()->clientHidden(this); |
1196 | if (Compositor::isCreated()) { |
1197 | Compositor::self()->checkUnredirect(); |
1198 | } |
1199 | } |
1200 | |
1201 | /** |
1202 | * Maps (shows) the client. Note that it is mapping state of the frame, |
1203 | * not necessarily the client window itself (i.e. a shaded window is here |
1204 | * considered mapped, even though it is in IconicState). |
1205 | */ |
1206 | void Client::map() |
1207 | { |
1208 | // XComposite invalidates backing pixmaps on unmap (minimize, different |
1209 | // virtual desktop, etc.). We kept the last known good pixmap around |
1210 | // for use in effects, but now we want to have access to the new pixmap |
1211 | if (compositing()) |
1212 | discardWindowPixmap(); |
1213 | if (decoration != NULL) |
1214 | decoration->widget()->show(); // Not really necessary, but let it know the state |
1215 | XMapWindow(display(), frameId()); |
1216 | if (!isShade()) { |
1217 | m_wrapper.map(); |
1218 | xcb_map_window(connection(), m_client); |
1219 | m_decoInputExtent.map(); |
1220 | exportMappingState(NormalState); |
1221 | } else |
1222 | exportMappingState(IconicState); |
1223 | } |
1224 | |
1225 | /** |
1226 | * Unmaps the client. Again, this is about the frame. |
1227 | */ |
1228 | void Client::unmap() |
1229 | { |
1230 | // Here it may look like a race condition, as some other client might try to unmap |
1231 | // the window between these two XSelectInput() calls. However, they're supposed to |
1232 | // use XWithdrawWindow(), which also sends a synthetic event to the root window, |
1233 | // which won't be missed, so this shouldn't be a problem. The chance the real UnmapNotify |
1234 | // will be missed is also very minimal, so I don't think it's needed to grab the server |
1235 | // here. |
1236 | XSelectInput(display(), m_wrapper, ClientWinMask); // Avoid getting UnmapNotify |
1237 | XUnmapWindow(display(), frameId()); |
1238 | m_wrapper.unmap(); |
1239 | xcb_unmap_window(connection(), m_client); |
1240 | m_decoInputExtent.unmap(); |
1241 | XSelectInput(display(), m_wrapper, ClientWinMask | SubstructureNotifyMask); |
1242 | if (decoration != NULL) |
1243 | decoration->widget()->hide(); // Not really necessary, but let it know the state |
1244 | exportMappingState(IconicState); |
1245 | } |
1246 | |
1247 | /** |
1248 | * XComposite doesn't keep window pixmaps of unmapped windows, which means |
1249 | * there wouldn't be any previews of windows that are minimized or on another |
1250 | * virtual desktop. Therefore rawHide() actually keeps such windows mapped. |
1251 | * However special care needs to be taken so that such windows don't interfere. |
1252 | * Therefore they're put very low in the stacking order and they have input shape |
1253 | * set to none, which hopefully is enough. If there's no input shape available, |
1254 | * then it's hoped that there will be some other desktop above it *shrug*. |
1255 | * Using normal shape would be better, but that'd affect other things, e.g. painting |
1256 | * of the actual preview. |
1257 | */ |
1258 | void Client::updateHiddenPreview() |
1259 | { |
1260 | if (hiddenPreview()) { |
1261 | workspace()->forceRestacking(); |
1262 | if (Xcb::Extensions::self()->isShapeInputAvailable()) { |
1263 | xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, |
1264 | XCB_CLIP_ORDERING_UNSORTED, frameId(), 0, 0, 0, NULL); |
1265 | } |
1266 | } else { |
1267 | workspace()->forceRestacking(); |
1268 | updateInputShape(); |
1269 | } |
1270 | } |
1271 | |
1272 | void Client::sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, long data1, long data2, long data3) |
1273 | { |
1274 | xcb_client_message_event_t ev; |
1275 | memset(&ev, 0, sizeof(ev)); |
1276 | ev.response_type = XCB_CLIENT_MESSAGE; |
1277 | ev.window = w; |
1278 | ev.type = a; |
1279 | ev.format = 32; |
1280 | ev.data.data32[0] = protocol; |
1281 | ev.data.data32[1] = xTime(); |
1282 | ev.data.data32[2] = data1; |
1283 | ev.data.data32[3] = data2; |
1284 | ev.data.data32[4] = data3; |
1285 | uint32_t eventMask = 0; |
1286 | if (w == rootWindow()) { |
1287 | eventMask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Magic! |
1288 | } |
1289 | xcb_send_event(connection(), false, w, eventMask, reinterpret_cast<const char*>(&ev)); |
1290 | xcb_flush(connection()); |
1291 | } |
1292 | |
1293 | /** |
1294 | * Returns whether the window may be closed (have a close button) |
1295 | */ |
1296 | bool Client::isCloseable() const |
1297 | { |
1298 | return rules()->checkCloseable(motif_may_close && !isSpecialWindow()); |
1299 | } |
1300 | |
1301 | /** |
1302 | * Closes the window by either sending a delete_window message or using XKill. |
1303 | */ |
1304 | void Client::closeWindow() |
1305 | { |
1306 | if (!isCloseable()) |
1307 | return; |
1308 | |
1309 | // Update user time, because the window may create a confirming dialog. |
1310 | updateUserTime(); |
1311 | |
1312 | if (Pdeletewindow) { |
1313 | sendClientMessage(window(), atoms->wm_protocols, atoms->wm_delete_window); |
1314 | pingWindow(); |
1315 | } else // Client will not react on wm_delete_window. We have not choice |
1316 | // but destroy his connection to the XServer. |
1317 | killWindow(); |
1318 | } |
1319 | |
1320 | |
1321 | /** |
1322 | * Kills the window via XKill |
1323 | */ |
1324 | void Client::killWindow() |
1325 | { |
1326 | kDebug(1212) << "Client::killWindow():" << caption(); |
1327 | killProcess(false); |
1328 | XKillClient(display(), window()); // Always kill this client at the server |
1329 | destroyClient(); |
1330 | } |
1331 | |
1332 | /** |
1333 | * Send a ping to the window using _NET_WM_PING if possible if it |
1334 | * doesn't respond within a reasonable time, it will be killed. |
1335 | */ |
1336 | void Client::pingWindow() |
1337 | { |
1338 | if (!Pping) |
1339 | return; // Can't ping :( |
1340 | if (options->killPingTimeout() == 0) |
1341 | return; // Turned off |
1342 | if (ping_timer != NULL) |
1343 | return; // Pinging already |
1344 | ping_timer = new QTimer(this); |
1345 | connect(ping_timer, SIGNAL(timeout()), SLOT(pingTimeout())); |
1346 | ping_timer->setSingleShot(true); |
1347 | ping_timer->start(options->killPingTimeout()); |
1348 | m_pingTimestamp = xTime(); |
1349 | workspace()->sendPingToWindow(window(), m_pingTimestamp); |
1350 | } |
1351 | |
1352 | void Client::gotPing(xcb_timestamp_t timestamp) |
1353 | { |
1354 | // Just plain compare is not good enough because of 64bit and truncating and whatnot |
1355 | if (NET::timestampCompare(timestamp, m_pingTimestamp) != 0) |
1356 | return; |
1357 | delete ping_timer; |
1358 | ping_timer = NULL; |
1359 | if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive |
1360 | ::kill(m_killHelperPID, SIGTERM); |
1361 | m_killHelperPID = 0; |
1362 | } |
1363 | } |
1364 | |
1365 | void Client::pingTimeout() |
1366 | { |
1367 | kDebug(1212) << "Ping timeout:" << caption(); |
1368 | ping_timer->deleteLater(); |
1369 | ping_timer = NULL; |
1370 | killProcess(true, m_pingTimestamp); |
1371 | } |
1372 | |
1373 | void Client::killProcess(bool ask, xcb_timestamp_t timestamp) |
1374 | { |
1375 | if (m_killHelperPID && !::kill(m_killHelperPID, 0)) // means the process is alive |
1376 | return; |
1377 | Q_ASSERT(!ask || timestamp != XCB_TIME_CURRENT_TIME); |
1378 | pid_t pid = info->pid(); |
1379 | if (pid <= 0 || clientMachine()->hostName().isEmpty()) // Needed properties missing |
1380 | return; |
1381 | kDebug(1212) << "Kill process:" << pid << "(" << clientMachine()->hostName() << ")" ; |
1382 | if (!ask) { |
1383 | if (!clientMachine()->isLocal()) { |
1384 | QStringList lst; |
1385 | lst << clientMachine()->hostName() << "kill" << QString::number(pid); |
1386 | QProcess::startDetached("xon" , lst); |
1387 | } else |
1388 | ::kill(pid, SIGTERM); |
1389 | } else { |
1390 | QString hostname = clientMachine()->isLocal() ? "localhost" : clientMachine()->hostName(); |
1391 | QProcess::startDetached(KStandardDirs::findExe("kwin_killer_helper" ), |
1392 | QStringList() << "--pid" << QByteArray().setNum(unsigned(pid)) << "--hostname" << hostname |
1393 | << "--windowname" << caption() |
1394 | << "--applicationname" << resourceClass() |
1395 | << "--wid" << QString::number(window()) |
1396 | << "--timestamp" << QString::number(timestamp), |
1397 | QString(), &m_killHelperPID); |
1398 | } |
1399 | } |
1400 | |
1401 | void Client::setSkipTaskbar(bool b, bool from_outside) |
1402 | { |
1403 | int was_wants_tab_focus = wantsTabFocus(); |
1404 | if (from_outside) { |
1405 | b = rules()->checkSkipTaskbar(b); |
1406 | original_skip_taskbar = b; |
1407 | } |
1408 | if (b == skipTaskbar()) |
1409 | return; |
1410 | skip_taskbar = b; |
1411 | info->setState(b ? NET::SkipTaskbar : 0, NET::SkipTaskbar); |
1412 | updateWindowRules(Rules::SkipTaskbar); |
1413 | if (was_wants_tab_focus != wantsTabFocus()) |
1414 | FocusChain::self()->update(this, |
1415 | isActive() ? FocusChain::MakeFirst : FocusChain::Update); |
1416 | emit skipTaskbarChanged(); |
1417 | } |
1418 | |
1419 | void Client::(bool b) |
1420 | { |
1421 | b = rules()->checkSkipPager(b); |
1422 | if (b == skipPager()) |
1423 | return; |
1424 | skip_pager = b; |
1425 | info->setState(b ? NET::SkipPager : 0, NET::SkipPager); |
1426 | updateWindowRules(Rules::SkipPager); |
1427 | emit skipPagerChanged(); |
1428 | } |
1429 | |
1430 | void Client::setSkipSwitcher(bool set) |
1431 | { |
1432 | set = rules()->checkSkipSwitcher(set); |
1433 | if (set == skipSwitcher()) |
1434 | return; |
1435 | skip_switcher = set; |
1436 | updateWindowRules(Rules::SkipSwitcher); |
1437 | emit skipSwitcherChanged(); |
1438 | } |
1439 | |
1440 | void Client::setModal(bool m) |
1441 | { |
1442 | // Qt-3.2 can have even modal normal windows :( |
1443 | if (modal == m) |
1444 | return; |
1445 | modal = m; |
1446 | emit modalChanged(); |
1447 | // Changing modality for a mapped window is weird (?) |
1448 | // _NET_WM_STATE_MODAL should possibly rather be _NET_WM_WINDOW_TYPE_MODAL_DIALOG |
1449 | } |
1450 | |
1451 | void Client::setDesktop(int desktop) |
1452 | { |
1453 | const int numberOfDesktops = VirtualDesktopManager::self()->count(); |
1454 | if (desktop != NET::OnAllDesktops) // Do range check |
1455 | desktop = qMax(1, qMin(numberOfDesktops, desktop)); |
1456 | desktop = qMin(numberOfDesktops, rules()->checkDesktop(desktop)); |
1457 | if (desk == desktop) |
1458 | return; |
1459 | |
1460 | int was_desk = desk; |
1461 | const bool wasOnCurrentDesktop = isOnCurrentDesktop(); |
1462 | desk = desktop; |
1463 | info->setDesktop(desktop); |
1464 | if ((was_desk == NET::OnAllDesktops) != (desktop == NET::OnAllDesktops)) { |
1465 | // onAllDesktops changed |
1466 | workspace()->updateOnAllDesktopsOfTransients(this); |
1467 | } |
1468 | |
1469 | ClientList transients_stacking_order = workspace()->ensureStackingOrder(transients()); |
1470 | for (ClientList::ConstIterator it = transients_stacking_order.constBegin(); |
1471 | it != transients_stacking_order.constEnd(); |
1472 | ++it) |
1473 | (*it)->setDesktop(desktop); |
1474 | |
1475 | if (isModal()) // if a modal dialog is moved, move the mainwindow with it as otherwise |
1476 | // the (just moved) modal dialog will confusingly return to the mainwindow with |
1477 | // the next desktop change |
1478 | { |
1479 | foreach (Client * c2, mainClients()) |
1480 | c2->setDesktop(desktop); |
1481 | } |
1482 | |
1483 | FocusChain::self()->update(this, FocusChain::MakeFirst); |
1484 | updateVisibility(); |
1485 | updateWindowRules(Rules::Desktop); |
1486 | |
1487 | // Update states of all other windows in this group |
1488 | if (tabGroup()) |
1489 | tabGroup()->updateStates(this, TabGroup::Desktop); |
1490 | emit desktopChanged(); |
1491 | if (wasOnCurrentDesktop != isOnCurrentDesktop()) |
1492 | emit desktopPresenceChanged(this, was_desk); |
1493 | } |
1494 | |
1495 | /** |
1496 | * Sets whether the client is on @p activity. |
1497 | * If you remove it from its last activity, then it's on all activities. |
1498 | * |
1499 | * Note: If it was on all activities and you try to remove it from one, nothing will happen; |
1500 | * I don't think that's an important enough use case to handle here. |
1501 | */ |
1502 | void Client::setOnActivity(const QString &activity, bool enable) |
1503 | { |
1504 | #ifdef KWIN_BUILD_ACTIVITIES |
1505 | QStringList newActivitiesList = activities(); |
1506 | if (newActivitiesList.contains(activity) == enable) //nothing to do |
1507 | return; |
1508 | if (enable) { |
1509 | QStringList allActivities = Activities::self()->all(); |
1510 | if (!allActivities.contains(activity)) //bogus ID |
1511 | return; |
1512 | newActivitiesList.append(activity); |
1513 | } else |
1514 | newActivitiesList.removeOne(activity); |
1515 | setOnActivities(newActivitiesList); |
1516 | #else |
1517 | Q_UNUSED(activity) |
1518 | Q_UNUSED(enable) |
1519 | #endif |
1520 | } |
1521 | |
1522 | /** |
1523 | * set exactly which activities this client is on |
1524 | */ |
1525 | void Client::setOnActivities(QStringList newActivitiesList) |
1526 | { |
1527 | #ifdef KWIN_BUILD_ACTIVITIES |
1528 | QString joinedActivitiesList = newActivitiesList.join("," ); |
1529 | joinedActivitiesList = rules()->checkActivity(joinedActivitiesList, false); |
1530 | newActivitiesList = joinedActivitiesList.split(',', QString::SkipEmptyParts); |
1531 | |
1532 | QStringList allActivities = Activities::self()->all(); |
1533 | if ( newActivitiesList.isEmpty() || |
1534 | (newActivitiesList.count() > 1 && newActivitiesList.count() == allActivities.count()) || |
1535 | (newActivitiesList.count() == 1 && newActivitiesList.at(0) == Activities::nullUuid())) { |
1536 | activityList.clear(); |
1537 | const QByteArray nullUuid = Activities::nullUuid().toUtf8(); |
1538 | XChangeProperty(display(), window(), atoms->activities, XA_STRING, 8, |
1539 | PropModeReplace, (const unsigned char *)nullUuid.constData(), nullUuid.length()); |
1540 | |
1541 | } else { |
1542 | QByteArray joined = joinedActivitiesList.toAscii(); |
1543 | char *data = joined.data(); |
1544 | activityList = newActivitiesList; |
1545 | XChangeProperty(display(), window(), atoms->activities, XA_STRING, 8, |
1546 | PropModeReplace, (unsigned char *)data, joined.size()); |
1547 | |
1548 | } |
1549 | |
1550 | updateActivities(false); |
1551 | #else |
1552 | Q_UNUSED(newActivitiesList) |
1553 | #endif |
1554 | } |
1555 | |
1556 | void Client::blockActivityUpdates(bool b) |
1557 | { |
1558 | if (b) { |
1559 | ++m_activityUpdatesBlocked; |
1560 | } else { |
1561 | Q_ASSERT(m_activityUpdatesBlocked); |
1562 | --m_activityUpdatesBlocked; |
1563 | if (!m_activityUpdatesBlocked) |
1564 | updateActivities(m_blockedActivityUpdatesRequireTransients); |
1565 | } |
1566 | } |
1567 | |
1568 | /** |
1569 | * update after activities changed |
1570 | */ |
1571 | void Client::updateActivities(bool includeTransients) |
1572 | { |
1573 | if (m_activityUpdatesBlocked) { |
1574 | m_blockedActivityUpdatesRequireTransients |= includeTransients; |
1575 | return; |
1576 | } |
1577 | emit activitiesChanged(this); |
1578 | m_blockedActivityUpdatesRequireTransients = false; // reset |
1579 | FocusChain::self()->update(this, FocusChain::MakeFirst); |
1580 | updateVisibility(); |
1581 | updateWindowRules(Rules::Activity); |
1582 | |
1583 | // Update states of all other windows in this group |
1584 | if (tabGroup()) |
1585 | tabGroup()->updateStates(this, TabGroup::Activity); |
1586 | } |
1587 | |
1588 | /** |
1589 | * Returns the virtual desktop within the workspace() the client window |
1590 | * is located in, 0 if it isn't located on any special desktop (not mapped yet), |
1591 | * or NET::OnAllDesktops. Do not use desktop() directly, use |
1592 | * isOnDesktop() instead. |
1593 | */ |
1594 | int Client::desktop() const |
1595 | { |
1596 | if (needsSessionInteract) { |
1597 | return NET::OnAllDesktops; |
1598 | } |
1599 | return desk; |
1600 | } |
1601 | |
1602 | /** |
1603 | * Returns the list of activities the client window is on. |
1604 | * if it's on all activities, the list will be empty. |
1605 | * Don't use this, use isOnActivity() and friends (from class Toplevel) |
1606 | */ |
1607 | QStringList Client::activities() const |
1608 | { |
1609 | if (needsSessionInteract) { |
1610 | return QStringList(); |
1611 | } |
1612 | return activityList; |
1613 | } |
1614 | |
1615 | void Client::setOnAllDesktops(bool b) |
1616 | { |
1617 | if ((b && isOnAllDesktops()) || |
1618 | (!b && !isOnAllDesktops())) |
1619 | return; |
1620 | if (b) |
1621 | setDesktop(NET::OnAllDesktops); |
1622 | else |
1623 | setDesktop(VirtualDesktopManager::self()->current()); |
1624 | |
1625 | // Update states of all other windows in this group |
1626 | if (tabGroup()) |
1627 | tabGroup()->updateStates(this, TabGroup::Desktop); |
1628 | } |
1629 | |
1630 | /** |
1631 | * if @p on is true, sets on all activities. |
1632 | * if it's false, sets it to only be on the current activity |
1633 | */ |
1634 | void Client::setOnAllActivities(bool on) |
1635 | { |
1636 | #ifdef KWIN_BUILD_ACTIVITIES |
1637 | if (on == isOnAllActivities()) |
1638 | return; |
1639 | if (on) { |
1640 | setOnActivities(QStringList()); |
1641 | |
1642 | } else { |
1643 | setOnActivity(Activities::self()->current(), true); |
1644 | } |
1645 | #endif |
1646 | } |
1647 | |
1648 | /** |
1649 | * Performs activation and/or raising of the window |
1650 | */ |
1651 | void Client::takeActivity(int flags, bool handled) |
1652 | { |
1653 | if (!handled || !Ptakeactivity) { |
1654 | if (flags & ActivityFocus) |
1655 | takeFocus(); |
1656 | if (flags & ActivityRaise) |
1657 | workspace()->raiseClient(this); |
1658 | return; |
1659 | } |
1660 | |
1661 | #ifndef NDEBUG |
1662 | static Time previous_activity_timestamp; |
1663 | static Client* previous_client; |
1664 | |
1665 | //if ( previous_activity_timestamp == xTime() && previous_client != this ) |
1666 | // { |
1667 | // kDebug( 1212 ) << "Repeated use of the same X timestamp for activity"; |
1668 | // kDebug( 1212 ) << kBacktrace(); |
1669 | // } |
1670 | |
1671 | previous_activity_timestamp = xTime(); |
1672 | previous_client = this; |
1673 | #endif |
1674 | |
1675 | workspace()->sendTakeActivity(this, xTime(), flags); |
1676 | } |
1677 | |
1678 | /** |
1679 | * Performs the actual focusing of the window using XSetInputFocus and WM_TAKE_FOCUS |
1680 | */ |
1681 | void Client::takeFocus() |
1682 | { |
1683 | #ifndef NDEBUG |
1684 | static Time previous_focus_timestamp; |
1685 | static Client* previous_client; |
1686 | |
1687 | //if ( previous_focus_timestamp == xTime() && previous_client != this ) |
1688 | // { |
1689 | // kDebug( 1212 ) << "Repeated use of the same X timestamp for focus"; |
1690 | // kDebug( 1212 ) << kBacktrace(); |
1691 | // } |
1692 | |
1693 | previous_focus_timestamp = xTime(); |
1694 | previous_client = this; |
1695 | #endif |
1696 | if (rules()->checkAcceptFocus(input)) |
1697 | XSetInputFocus(display(), window(), RevertToPointerRoot, xTime()); |
1698 | else |
1699 | demandAttention(false); // window cannot take input, at least withdraw urgency |
1700 | if (Ptakefocus) |
1701 | sendClientMessage(window(), atoms->wm_protocols, atoms->wm_take_focus); |
1702 | workspace()->setShouldGetFocus(this); |
1703 | } |
1704 | |
1705 | /** |
1706 | * Returns whether the window provides context help or not. If it does, |
1707 | * you should show a help menu item or a help button like '?' and call |
1708 | * contextHelp() if this is invoked. |
1709 | * |
1710 | * \sa contextHelp() |
1711 | */ |
1712 | bool Client::providesContextHelp() const |
1713 | { |
1714 | return Pcontexthelp; |
1715 | } |
1716 | |
1717 | /** |
1718 | * Invokes context help on the window. Only works if the window |
1719 | * actually provides context help. |
1720 | * |
1721 | * \sa providesContextHelp() |
1722 | */ |
1723 | void Client::showContextHelp() |
1724 | { |
1725 | if (Pcontexthelp) { |
1726 | sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_context_help); |
1727 | QWhatsThis::enterWhatsThisMode(); // SELI TODO: ? |
1728 | } |
1729 | } |
1730 | |
1731 | /** |
1732 | * Fetches the window's caption (WM_NAME property). It will be |
1733 | * stored in the client's caption(). |
1734 | */ |
1735 | void Client::fetchName() |
1736 | { |
1737 | setCaption(readName()); |
1738 | } |
1739 | |
1740 | QString Client::readName() const |
1741 | { |
1742 | if (info->name() && info->name()[0] != '\0') |
1743 | return QString::fromUtf8(info->name()); |
1744 | else |
1745 | return KWindowSystem::readNameProperty(window(), XA_WM_NAME); |
1746 | } |
1747 | |
1748 | KWIN_COMPARE_PREDICATE(FetchNameInternalPredicate, Client, const Client*, (!cl->isSpecialWindow() || cl->isToolbar()) && cl != value && cl->caption() == value->caption()); |
1749 | |
1750 | // The list is taken from http://www.unicode.org/reports/tr9/ (#154840) |
1751 | QChar LRM(0x200E); |
1752 | QChar RLM(0x200F); |
1753 | QChar LRE(0x202A); |
1754 | QChar RLE(0x202B); |
1755 | QChar LRO(0x202D); |
1756 | QChar RLO(0x202E); |
1757 | QChar PDF(0x202C); |
1758 | |
1759 | void Client::setCaption(const QString& _s, bool force) |
1760 | { |
1761 | if (!force && _s == cap_normal) |
1762 | return; |
1763 | QString s(_s); |
1764 | for (int i = 0; i < s.length(); ++i) |
1765 | if (!s[i].isPrint()) |
1766 | s[i] = QChar(' '); |
1767 | cap_normal = s; |
1768 | #ifdef KWIN_BUILD_SCRIPTING |
1769 | if (options->condensedTitle()) { |
1770 | static QScriptEngine engine; |
1771 | static QScriptProgram stripTitle; |
1772 | static QScriptValue script; |
1773 | if (stripTitle.isNull()) { |
1774 | const QString scriptFile = KStandardDirs::locate("data" , QLatin1String(KWIN_NAME) + "/stripTitle.js" ); |
1775 | if (!scriptFile.isEmpty()) { |
1776 | QFile f(scriptFile); |
1777 | if (f.open(QIODevice::ReadOnly|QIODevice::Text)) { |
1778 | f.reset(); |
1779 | stripTitle = QScriptProgram(QString::fromLocal8Bit(f.readAll()), "stripTitle.js" ); |
1780 | f.close(); |
1781 | } |
1782 | } |
1783 | if (stripTitle.isNull()) |
1784 | stripTitle = QScriptProgram("(function(title, wm_name, wm_class){ return title ; })" , "stripTitle.js" ); |
1785 | script = engine.evaluate(stripTitle); |
1786 | } |
1787 | QScriptValueList args; |
1788 | args << _s << QString(resourceName()) << QString(resourceClass()); |
1789 | s = script.call(QScriptValue(), args).toString(); |
1790 | } |
1791 | #endif |
1792 | if (!force && s == cap_deco) |
1793 | return; |
1794 | cap_deco = s; |
1795 | |
1796 | bool reset_name = force; |
1797 | bool was_suffix = (!cap_suffix.isEmpty()); |
1798 | cap_suffix.clear(); |
1799 | QString machine_suffix; |
1800 | if (!options->condensedTitle()) { // machine doesn't qualify for "clean" |
1801 | if (clientMachine()->hostName() != ClientMachine::localhost() && !clientMachine()->isLocal()) |
1802 | machine_suffix = QString(" <@" ) + clientMachine()->hostName() + '>' + LRM; |
1803 | } |
1804 | QString shortcut_suffix = !shortcut().isEmpty() ? (" {" + shortcut().toString() + '}') : QString(); |
1805 | cap_suffix = machine_suffix + shortcut_suffix; |
1806 | if ((!isSpecialWindow() || isToolbar()) && workspace()->findClient(FetchNameInternalPredicate(this))) { |
1807 | int i = 2; |
1808 | do { |
1809 | cap_suffix = machine_suffix + " <" + QString::number(i) + '>' + LRM; |
1810 | i++; |
1811 | } while (workspace()->findClient(FetchNameInternalPredicate(this))); |
1812 | info->setVisibleName(caption().toUtf8()); |
1813 | reset_name = false; |
1814 | } |
1815 | if ((was_suffix && cap_suffix.isEmpty()) || reset_name) { |
1816 | // If it was new window, it may have old value still set, if the window is reused |
1817 | info->setVisibleName("" ); |
1818 | info->setVisibleIconName("" ); |
1819 | } else if (!cap_suffix.isEmpty() && !cap_iconic.isEmpty()) |
1820 | // Keep the same suffix in iconic name if it's set |
1821 | info->setVisibleIconName(QString(cap_iconic + cap_suffix).toUtf8()); |
1822 | |
1823 | emit captionChanged(); |
1824 | } |
1825 | |
1826 | void Client::updateCaption() |
1827 | { |
1828 | setCaption(cap_normal, true); |
1829 | } |
1830 | |
1831 | void Client::fetchIconicName() |
1832 | { |
1833 | QString s; |
1834 | if (info->iconName() && info->iconName()[0] != '\0') |
1835 | s = QString::fromUtf8(info->iconName()); |
1836 | else |
1837 | s = KWindowSystem::readNameProperty(window(), XA_WM_ICON_NAME); |
1838 | if (s != cap_iconic) { |
1839 | bool was_set = !cap_iconic.isEmpty(); |
1840 | cap_iconic = s; |
1841 | if (!cap_suffix.isEmpty()) { |
1842 | if (!cap_iconic.isEmpty()) // Keep the same suffix in iconic name if it's set |
1843 | info->setVisibleIconName(QString(s + cap_suffix).toUtf8()); |
1844 | else if (was_set) |
1845 | info->setVisibleIconName("" ); |
1846 | } |
1847 | } |
1848 | } |
1849 | |
1850 | /** |
1851 | * \reimp |
1852 | */ |
1853 | QString Client::caption(bool full, bool stripped) const |
1854 | { |
1855 | QString cap = stripped ? cap_deco : cap_normal; |
1856 | if (full) |
1857 | cap += cap_suffix; |
1858 | return cap; |
1859 | } |
1860 | |
1861 | bool Client::tabTo(Client *other, bool behind, bool activate) |
1862 | { |
1863 | Q_ASSERT(other && other != this); |
1864 | |
1865 | if (tab_group && tab_group == other->tabGroup()) { // special case: move inside group |
1866 | tab_group->move(this, other, behind); |
1867 | return true; |
1868 | } |
1869 | |
1870 | GeometryUpdatesBlocker blocker(this); |
1871 | const bool wasBlocking = signalsBlocked(); |
1872 | blockSignals(true); // prevent client emitting "retabbed to nowhere" cause it's about to be entabbed the next moment |
1873 | untab(); |
1874 | blockSignals(wasBlocking); |
1875 | |
1876 | TabGroup *newGroup = other->tabGroup() ? other->tabGroup() : new TabGroup(other); |
1877 | |
1878 | if (!newGroup->add(this, other, behind, activate)) { |
1879 | if (newGroup->count() < 2) { // adding "c" to "to" failed for whatever reason |
1880 | newGroup->remove(other); |
1881 | delete newGroup; |
1882 | } |
1883 | return false; |
1884 | } |
1885 | return true; |
1886 | } |
1887 | |
1888 | bool Client::untab(const QRect &toGeometry, bool clientRemoved) |
1889 | { |
1890 | TabGroup *group = tab_group; |
1891 | if (group && group->remove(this)) { // remove sets the tabgroup to "0", therefore the pointer is cached |
1892 | if (group->isEmpty()) { |
1893 | delete group; |
1894 | } |
1895 | if (clientRemoved) |
1896 | return true; // there's been a broadcast signal that this client is now removed - don't touch it |
1897 | setClientShown(!(isMinimized() || isShade())); |
1898 | bool keepSize = toGeometry.size() == size(); |
1899 | bool changedSize = false; |
1900 | if (quickTileMode() != QuickTileNone) { |
1901 | changedSize = true; |
1902 | setQuickTileMode(QuickTileNone); // if we leave a quicktiled group, assume that the user wants to untile |
1903 | } |
1904 | if (toGeometry.isValid()) { |
1905 | if (maximizeMode() != Client::MaximizeRestore) { |
1906 | changedSize = true; |
1907 | maximize(Client::MaximizeRestore); // explicitly calling for a geometry -> unmaximize |
1908 | } |
1909 | if (keepSize && changedSize) { |
1910 | geom_restore = geometry(); // checkWorkspacePosition() invokes it |
1911 | QPoint cpoint = Cursor::pos(); |
1912 | QPoint point = cpoint; |
1913 | point.setX((point.x() - toGeometry.x()) * geom_restore.width() / toGeometry.width()); |
1914 | point.setY((point.y() - toGeometry.y()) * geom_restore.height() / toGeometry.height()); |
1915 | geom_restore.moveTo(cpoint-point); |
1916 | } else { |
1917 | geom_restore = toGeometry; // checkWorkspacePosition() invokes it |
1918 | } |
1919 | setGeometry(geom_restore); |
1920 | checkWorkspacePosition(); |
1921 | } |
1922 | return true; |
1923 | } |
1924 | return false; |
1925 | } |
1926 | |
1927 | void Client::setTabGroup(TabGroup *group) |
1928 | { |
1929 | tab_group = group; |
1930 | if (group) { |
1931 | unsigned long data = qHash(group); //->id(); |
1932 | XChangeProperty(display(), window(), atoms->kde_net_wm_tab_group, XA_CARDINAL, 32, |
1933 | PropModeReplace, (unsigned char*)(&data), 1); |
1934 | } |
1935 | else |
1936 | XDeleteProperty(display(), window(), atoms->kde_net_wm_tab_group); |
1937 | emit tabGroupChanged(); |
1938 | } |
1939 | |
1940 | bool Client::isCurrentTab() const |
1941 | { |
1942 | return !tab_group || tab_group->current() == this; |
1943 | } |
1944 | |
1945 | void Client::syncTabGroupFor(QString property, bool fromThisClient) |
1946 | { |
1947 | if (tab_group) |
1948 | tab_group->sync(property.toAscii().data(), fromThisClient ? this : tab_group->current()); |
1949 | } |
1950 | |
1951 | void Client::dontMoveResize() |
1952 | { |
1953 | buttonDown = false; |
1954 | stopDelayedMoveResize(); |
1955 | if (moveResizeMode) |
1956 | finishMoveResize(false); |
1957 | } |
1958 | |
1959 | void Client::setClientShown(bool shown) |
1960 | { |
1961 | if (deleting) |
1962 | return; // Don't change shown status if this client is being deleted |
1963 | if (shown != hidden) |
1964 | return; // nothing to change |
1965 | hidden = !shown; |
1966 | if (options->isInactiveTabsSkipTaskbar()) |
1967 | setSkipTaskbar(hidden, false); // TODO: Causes reshuffle of the taskbar |
1968 | if (shown) { |
1969 | map(); |
1970 | takeFocus(); |
1971 | autoRaise(); |
1972 | FocusChain::self()->update(this, FocusChain::MakeFirst); |
1973 | } else { |
1974 | unmap(); |
1975 | // Don't move tabs to the end of the list when another tab get's activated |
1976 | if (isCurrentTab()) |
1977 | FocusChain::self()->update(this, FocusChain::MakeLast); |
1978 | addWorkspaceRepaint(visibleRect()); |
1979 | } |
1980 | } |
1981 | |
1982 | void Client::getWMHints() |
1983 | { |
1984 | XWMHints* hints = XGetWMHints(display(), window()); |
1985 | input = true; |
1986 | m_windowGroup = XCB_WINDOW_NONE; |
1987 | urgency = false; |
1988 | if (hints) { |
1989 | if (hints->flags & InputHint) |
1990 | input = hints->input; |
1991 | if (hints->flags & WindowGroupHint) |
1992 | m_windowGroup = hints->window_group; |
1993 | urgency = !!(hints->flags & UrgencyHint); // Need boolean, it's a uint bitfield |
1994 | XFree((char*)hints); |
1995 | } |
1996 | checkGroup(); |
1997 | updateUrgency(); |
1998 | updateAllowedActions(); // Group affects isMinimizable() |
1999 | } |
2000 | |
2001 | void Client::getMotifHints() |
2002 | { |
2003 | bool mgot_noborder, mnoborder, mresize, mmove, mminimize, mmaximize, mclose; |
2004 | Motif::readFlags(m_client, mgot_noborder, mnoborder, mresize, mmove, mminimize, mmaximize, mclose); |
2005 | if (mgot_noborder && motif_noborder != mnoborder) { |
2006 | motif_noborder = mnoborder; |
2007 | // If we just got a hint telling us to hide decorations, we do so. |
2008 | if (motif_noborder) |
2009 | noborder = rules()->checkNoBorder(true); |
2010 | // If the Motif hint is now telling us to show decorations, we only do so if the app didn't |
2011 | // instruct us to hide decorations in some other way, though. |
2012 | else if (!app_noborder) |
2013 | noborder = rules()->checkNoBorder(false); |
2014 | } |
2015 | if (!hasNETSupport()) { |
2016 | // NETWM apps should set type and size constraints |
2017 | motif_may_resize = mresize; // This should be set using minsize==maxsize, but oh well |
2018 | motif_may_move = mmove; |
2019 | } else |
2020 | motif_may_resize = motif_may_move = true; |
2021 | |
2022 | // mminimize; - Ignore, bogus - E.g. shading or sending to another desktop is "minimizing" too |
2023 | // mmaximize; - Ignore, bogus - Maximizing is basically just resizing |
2024 | const bool closabilityChanged = motif_may_close != mclose; |
2025 | motif_may_close = mclose; // Motif apps like to crash when they set this hint and WM closes them anyway |
2026 | if (isManaged()) |
2027 | updateDecoration(true); // Check if noborder state has changed |
2028 | if (decoration && closabilityChanged) |
2029 | decoration->reset(KDecoration::SettingButtons); |
2030 | } |
2031 | |
2032 | void Client::readIcons(xcb_window_t win, QPixmap* icon, QPixmap* miniicon, QPixmap* bigicon, QPixmap* hugeicon) |
2033 | { |
2034 | // Get the icons, allow scaling |
2035 | if (icon != NULL) |
2036 | *icon = KWindowSystem::icon(win, 32, 32, true, KWindowSystem::NETWM | KWindowSystem::WMHints); |
2037 | if (miniicon != NULL) { |
2038 | if (icon == NULL || !icon->isNull()) |
2039 | *miniicon = KWindowSystem::icon(win, 16, 16, true, KWindowSystem::NETWM | KWindowSystem::WMHints); |
2040 | else |
2041 | *miniicon = QPixmap(); |
2042 | } |
2043 | if (bigicon != NULL) { |
2044 | if (icon == NULL || !icon->isNull()) |
2045 | *bigicon = KWindowSystem::icon(win, 64, 64, false, KWindowSystem::NETWM | KWindowSystem::WMHints); |
2046 | else |
2047 | *bigicon = QPixmap(); |
2048 | } |
2049 | if (hugeicon != NULL) { |
2050 | if (icon == NULL || !icon->isNull()) |
2051 | *hugeicon = KWindowSystem::icon(win, 128, 128, false, KWindowSystem::NETWM | KWindowSystem::WMHints); |
2052 | else |
2053 | *hugeicon = QPixmap(); |
2054 | } |
2055 | } |
2056 | |
2057 | void Client::getIcons() |
2058 | { |
2059 | // First read icons from the window itself |
2060 | readIcons(window(), &icon_pix, &miniicon_pix, &bigicon_pix, &hugeicon_pix); |
2061 | if (icon_pix.isNull()) { |
2062 | // Then try window group |
2063 | icon_pix = group()->icon(); |
2064 | miniicon_pix = group()->miniIcon(); |
2065 | bigicon_pix = group()->bigIcon(); |
2066 | hugeicon_pix = group()->hugeIcon(); |
2067 | } |
2068 | if (icon_pix.isNull() && isTransient()) { |
2069 | // Then mainclients |
2070 | ClientList mainclients = mainClients(); |
2071 | for (ClientList::ConstIterator it = mainclients.constBegin(); |
2072 | it != mainclients.constEnd() && icon_pix.isNull(); |
2073 | ++it) { |
2074 | icon_pix = (*it)->icon(); |
2075 | miniicon_pix = (*it)->miniIcon(); |
2076 | bigicon_pix = (*it)->bigIcon(); |
2077 | hugeicon_pix = (*it)->hugeIcon(); |
2078 | } |
2079 | } |
2080 | if (icon_pix.isNull()) { |
2081 | // And if nothing else, load icon from classhint or xapp icon |
2082 | icon_pix = KWindowSystem::icon(window(), 32, 32, true, KWindowSystem::ClassHint | KWindowSystem::XApp); |
2083 | miniicon_pix = KWindowSystem::icon(window(), 16, 16, true, KWindowSystem::ClassHint | KWindowSystem::XApp); |
2084 | bigicon_pix = KWindowSystem::icon(window(), 64, 64, false, KWindowSystem::ClassHint | KWindowSystem::XApp); |
2085 | hugeicon_pix = KWindowSystem::icon(window(), 128, 128, false, KWindowSystem::ClassHint | KWindowSystem::XApp); |
2086 | } |
2087 | emit iconChanged(); |
2088 | } |
2089 | |
2090 | QPixmap Client::icon(const QSize& size) const |
2091 | { |
2092 | const int iconSize = qMin(size.width(), size.height()); |
2093 | if (iconSize <= 16) |
2094 | return miniIcon(); |
2095 | else if (iconSize <= 32) |
2096 | return icon(); |
2097 | if (iconSize <= 64) |
2098 | return bigIcon(); |
2099 | else |
2100 | return hugeIcon(); |
2101 | } |
2102 | |
2103 | void Client::getWindowProtocols() |
2104 | { |
2105 | Atom* p; |
2106 | int i, n; |
2107 | |
2108 | Pdeletewindow = 0; |
2109 | Ptakefocus = 0; |
2110 | Ptakeactivity = 0; |
2111 | Pcontexthelp = 0; |
2112 | Pping = 0; |
2113 | |
2114 | if (XGetWMProtocols(display(), window(), &p, &n)) { |
2115 | for (i = 0; i < n; ++i) { |
2116 | if (p[i] == atoms->wm_delete_window) |
2117 | Pdeletewindow = 1; |
2118 | else if (p[i] == atoms->wm_take_focus) |
2119 | Ptakefocus = 1; |
2120 | else if (p[i] == atoms->net_wm_take_activity) |
2121 | Ptakeactivity = 1; |
2122 | else if (p[i] == atoms->net_wm_context_help) |
2123 | Pcontexthelp = 1; |
2124 | else if (p[i] == atoms->net_wm_ping) |
2125 | Pping = 1; |
2126 | } |
2127 | if (n > 0) |
2128 | XFree(p); |
2129 | } |
2130 | } |
2131 | |
2132 | void Client::getSyncCounter() |
2133 | { |
2134 | #ifdef HAVE_XSYNC |
2135 | if (!Xcb::Extensions::self()->isSyncAvailable()) |
2136 | return; |
2137 | |
2138 | Atom retType; |
2139 | unsigned long nItemRet; |
2140 | unsigned long byteRet; |
2141 | int formatRet; |
2142 | unsigned char* propRet; |
2143 | int ret = XGetWindowProperty(display(), window(), atoms->net_wm_sync_request_counter, |
2144 | 0, 1, false, XA_CARDINAL, &retType, &formatRet, &nItemRet, &byteRet, &propRet); |
2145 | |
2146 | if (ret == Success && formatRet == 32) { |
2147 | syncRequest.counter = *(long*)(propRet); |
2148 | XSyncIntToValue(&syncRequest.value, 0); |
2149 | XSyncValue zero; |
2150 | XSyncIntToValue(&zero, 0); |
2151 | XSyncSetCounter(display(), syncRequest.counter, zero); |
2152 | if (syncRequest.alarm == None) { |
2153 | XSyncAlarmAttributes attrs; |
2154 | attrs.trigger.counter = syncRequest.counter; |
2155 | attrs.trigger.value_type = XSyncRelative; |
2156 | attrs.trigger.test_type = XSyncPositiveTransition; |
2157 | XSyncIntToValue(&attrs.trigger.wait_value, 1); |
2158 | XSyncIntToValue(&attrs.delta, 1); |
2159 | syncRequest.alarm = XSyncCreateAlarm(display(), |
2160 | XSyncCACounter | XSyncCAValueType | XSyncCATestType | XSyncCADelta | XSyncCAValue, |
2161 | &attrs); |
2162 | } |
2163 | } |
2164 | |
2165 | if (ret == Success) |
2166 | XFree(propRet); |
2167 | #endif |
2168 | } |
2169 | |
2170 | /** |
2171 | * Send the client a _NET_SYNC_REQUEST |
2172 | */ |
2173 | void Client::sendSyncRequest() |
2174 | { |
2175 | #ifdef HAVE_XSYNC |
2176 | if (syncRequest.counter == None || syncRequest.isPending) |
2177 | return; // do NOT, NEVER send a sync request when there's one on the stack. the clients will just stop respoding. FOREVER! ... |
2178 | |
2179 | if (!syncRequest.failsafeTimeout) { |
2180 | syncRequest.failsafeTimeout = new QTimer(this); |
2181 | connect(syncRequest.failsafeTimeout, SIGNAL(timeout()), SLOT(removeSyncSupport())); |
2182 | syncRequest.failsafeTimeout->setSingleShot(true); |
2183 | } |
2184 | // if there's no response within 10 seconds, sth. went wrong and we remove XSYNC support from this client. |
2185 | // see events.cpp Client::syncEvent() |
2186 | syncRequest.failsafeTimeout->start(ready_for_painting ? 10000 : 1000); |
2187 | |
2188 | // We increment before the notify so that after the notify |
2189 | // syncCounterSerial will equal the value we are expecting |
2190 | // in the acknowledgement |
2191 | int overflow; |
2192 | XSyncValue one; |
2193 | XSyncIntToValue(&one, 1); |
2194 | #undef XSyncValueAdd // It causes a warning :-/ |
2195 | XSyncValueAdd(&syncRequest.value, syncRequest.value, one, &overflow); |
2196 | |
2197 | // Send the message to client |
2198 | XEvent ev; |
2199 | ev.xclient.type = ClientMessage; |
2200 | ev.xclient.window = window(); |
2201 | ev.xclient.format = 32; |
2202 | ev.xclient.message_type = atoms->wm_protocols; |
2203 | ev.xclient.data.l[0] = atoms->net_wm_sync_request; |
2204 | ev.xclient.data.l[1] = xTime(); |
2205 | ev.xclient.data.l[2] = XSyncValueLow32(syncRequest.value); |
2206 | ev.xclient.data.l[3] = XSyncValueHigh32(syncRequest.value); |
2207 | ev.xclient.data.l[4] = 0; |
2208 | syncRequest.isPending = true; |
2209 | XSendEvent(display(), window(), False, NoEventMask, &ev); |
2210 | XSync(display(), false); |
2211 | #endif |
2212 | } |
2213 | |
2214 | void Client::removeSyncSupport() |
2215 | { |
2216 | if (!ready_for_painting) { |
2217 | setReadyForPainting(); |
2218 | return; |
2219 | } |
2220 | #ifdef HAVE_XSYNC |
2221 | syncRequest.isPending = false; |
2222 | syncRequest.counter = syncRequest.alarm = None; |
2223 | delete syncRequest.timeout; delete syncRequest.failsafeTimeout; |
2224 | syncRequest.timeout = syncRequest.failsafeTimeout = NULL; |
2225 | #endif |
2226 | } |
2227 | |
2228 | bool Client::wantsTabFocus() const |
2229 | { |
2230 | return (isNormalWindow() || isDialog()) && wantsInput(); |
2231 | } |
2232 | |
2233 | bool Client::wantsInput() const |
2234 | { |
2235 | return rules()->checkAcceptFocus(input || Ptakefocus); |
2236 | } |
2237 | |
2238 | bool Client::isSpecialWindow() const |
2239 | { |
2240 | // TODO |
2241 | return isDesktop() || isDock() || isSplash() || isToolbar(); |
2242 | } |
2243 | |
2244 | /** |
2245 | * Sets an appropriate cursor shape for the logical mouse position \a m |
2246 | */ |
2247 | void Client::updateCursor() |
2248 | { |
2249 | Position m = mode; |
2250 | if (!isResizable() || isShade()) |
2251 | m = PositionCenter; |
2252 | Qt::CursorShape c = Qt::ArrowCursor; |
2253 | switch(m) { |
2254 | case PositionTopLeft: |
2255 | case PositionBottomRight: |
2256 | c = Qt::SizeFDiagCursor; |
2257 | break; |
2258 | case PositionBottomLeft: |
2259 | case PositionTopRight: |
2260 | c = Qt::SizeBDiagCursor; |
2261 | break; |
2262 | case PositionTop: |
2263 | case PositionBottom: |
2264 | c = Qt::SizeVerCursor; |
2265 | break; |
2266 | case PositionLeft: |
2267 | case PositionRight: |
2268 | c = Qt::SizeHorCursor; |
2269 | break; |
2270 | default: |
2271 | if (moveResizeMode) |
2272 | c = Qt::SizeAllCursor; |
2273 | else |
2274 | c = Qt::ArrowCursor; |
2275 | break; |
2276 | } |
2277 | if (c == m_cursor) |
2278 | return; |
2279 | m_cursor = c; |
2280 | if (decoration != NULL) |
2281 | decoration->widget()->setCursor(m_cursor); |
2282 | xcb_cursor_t nativeCursor = Cursor::x11Cursor(m_cursor); |
2283 | Xcb::defineCursor(frameId(), nativeCursor); |
2284 | if (m_decoInputExtent.isValid()) |
2285 | m_decoInputExtent.defineCursor(nativeCursor); |
2286 | if (moveResizeMode) { |
2287 | // changing window attributes doesn't change cursor if there's pointer grab active |
2288 | xcb_change_active_pointer_grab(connection(), nativeCursor, xTime(), |
2289 | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW); |
2290 | } |
2291 | } |
2292 | |
2293 | void Client::updateCompositeBlocking(bool readProperty) |
2294 | { |
2295 | if (readProperty) { |
2296 | const unsigned long properties[2] = {0, NET::WM2BlockCompositing}; |
2297 | NETWinInfo2 i(display(), window(), rootWindow(), properties, 2); |
2298 | setBlockingCompositing(i.isBlockingCompositing()); |
2299 | } |
2300 | else |
2301 | setBlockingCompositing(blocks_compositing); |
2302 | } |
2303 | |
2304 | void Client::setBlockingCompositing(bool block) |
2305 | { |
2306 | const bool usedToBlock = blocks_compositing; |
2307 | blocks_compositing = rules()->checkBlockCompositing(block); |
2308 | if (usedToBlock != blocks_compositing) { |
2309 | emit blockingCompositingChanged(blocks_compositing ? this : 0); |
2310 | } |
2311 | } |
2312 | |
2313 | Client::Position Client::mousePosition(const QPoint& p) const |
2314 | { |
2315 | if (decoration != NULL) |
2316 | return decoration->mousePosition(p); |
2317 | return PositionCenter; |
2318 | } |
2319 | |
2320 | void Client::updateAllowedActions(bool force) |
2321 | { |
2322 | if (!isManaged() && !force) |
2323 | return; |
2324 | unsigned long old_allowed_actions = allowed_actions; |
2325 | allowed_actions = 0; |
2326 | if (isMovable()) |
2327 | allowed_actions |= NET::ActionMove; |
2328 | if (isResizable()) |
2329 | allowed_actions |= NET::ActionResize; |
2330 | if (isMinimizable()) |
2331 | allowed_actions |= NET::ActionMinimize; |
2332 | if (isShadeable()) |
2333 | allowed_actions |= NET::ActionShade; |
2334 | // Sticky state not supported |
2335 | if (isMaximizable()) |
2336 | allowed_actions |= NET::ActionMax; |
2337 | if (userCanSetFullScreen()) |
2338 | allowed_actions |= NET::ActionFullScreen; |
2339 | allowed_actions |= NET::ActionChangeDesktop; // Always (Pagers shouldn't show Docks etc.) |
2340 | if (isCloseable()) |
2341 | allowed_actions |= NET::ActionClose; |
2342 | if (old_allowed_actions == allowed_actions) |
2343 | return; |
2344 | // TODO: This could be delayed and compressed - It's only for pagers etc. anyway |
2345 | info->setAllowedActions(allowed_actions); |
2346 | |
2347 | // ONLY if relevant features have changed (and the window didn't just get/loose moveresize for maximization state changes) |
2348 | const unsigned long relevant = ~(NET::ActionMove|NET::ActionResize); |
2349 | if (decoration && (allowed_actions & relevant) != (old_allowed_actions & relevant)) |
2350 | decoration->reset(KDecoration::SettingButtons); |
2351 | } |
2352 | |
2353 | void Client::autoRaise() |
2354 | { |
2355 | workspace()->raiseClient(this); |
2356 | cancelAutoRaise(); |
2357 | } |
2358 | |
2359 | void Client::cancelAutoRaise() |
2360 | { |
2361 | delete autoRaiseTimer; |
2362 | autoRaiseTimer = 0; |
2363 | } |
2364 | |
2365 | void Client::debug(QDebug& stream) const |
2366 | { |
2367 | print<QDebug>(stream); |
2368 | } |
2369 | |
2370 | QPixmap* () |
2371 | { |
2372 | static QPixmap p; |
2373 | if (p.isNull()) |
2374 | p = SmallIcon("bx2" ); |
2375 | return &p; |
2376 | } |
2377 | |
2378 | void Client::checkActivities() |
2379 | { |
2380 | #ifdef KWIN_BUILD_ACTIVITIES |
2381 | QStringList newActivitiesList; |
2382 | QByteArray prop = getStringProperty(window(), atoms->activities); |
2383 | activitiesDefined = !prop.isEmpty(); |
2384 | if (prop == Activities::nullUuid()) { |
2385 | //copied from setOnAllActivities to avoid a redundant XChangeProperty. |
2386 | if (!activityList.isEmpty()) { |
2387 | activityList.clear(); |
2388 | updateActivities(true); |
2389 | } |
2390 | return; |
2391 | } |
2392 | if (prop.isEmpty()) { |
2393 | //note: this makes it *act* like it's on all activities but doesn't set the property to 'ALL' |
2394 | if (!activityList.isEmpty()) { |
2395 | activityList.clear(); |
2396 | updateActivities(true); |
2397 | } |
2398 | return; |
2399 | } |
2400 | |
2401 | newActivitiesList = QString(prop).split(','); |
2402 | |
2403 | if (newActivitiesList == activityList) |
2404 | return; //expected change, it's ok. |
2405 | |
2406 | //otherwise, somebody else changed it. we need to validate before reacting |
2407 | QStringList allActivities = Activities::self()->all(); |
2408 | if (allActivities.isEmpty()) { |
2409 | kDebug() << "no activities!?!?" ; |
2410 | //don't touch anything, there's probably something bad going on and we don't wanna make it worse |
2411 | return; |
2412 | } |
2413 | for (int i = 0; i < newActivitiesList.size(); ++i) { |
2414 | if (! allActivities.contains(newActivitiesList.at(i))) { |
2415 | kDebug() << "invalid:" << newActivitiesList.at(i); |
2416 | newActivitiesList.removeAt(i--); |
2417 | } |
2418 | } |
2419 | setOnActivities(newActivitiesList); |
2420 | #endif |
2421 | } |
2422 | |
2423 | void Client::setSessionInteract(bool needed) |
2424 | { |
2425 | needsSessionInteract = needed; |
2426 | } |
2427 | |
2428 | QRect Client::decorationRect() const |
2429 | { |
2430 | if (decoration && decoration->widget()) { |
2431 | return decoration->widget()->rect().translated(-padding_left, -padding_top); |
2432 | } else { |
2433 | return QRect(0, 0, width(), height()); |
2434 | } |
2435 | } |
2436 | |
2437 | KDecorationDefines::Position Client::titlebarPosition() const |
2438 | { |
2439 | Position titlePos = PositionCenter; // PositionTop is returned by the default implementation |
2440 | // this will hint errors in the metaobject usage ;-) |
2441 | if (decoration) |
2442 | QMetaObject::invokeMethod(decoration, "titlebarPosition" , Qt::DirectConnection, |
2443 | Q_RETURN_ARG(KDecorationDefines::Position, titlePos)); |
2444 | return titlePos; |
2445 | } |
2446 | |
2447 | void Client::updateFirstInTabBox() |
2448 | { |
2449 | // TODO: move into KWindowInfo |
2450 | Atom type; |
2451 | int format, status; |
2452 | unsigned long nitems = 0; |
2453 | unsigned long = 0; |
2454 | unsigned char *data = 0; |
2455 | status = XGetWindowProperty(display(), window(), atoms->kde_first_in_window_list, 0, 1, false, atoms->kde_first_in_window_list, &type, &format, &nitems, &extra, &data); |
2456 | if (status == Success && format == 32 && nitems == 1) { |
2457 | setFirstInTabBox(true); |
2458 | } else { |
2459 | setFirstInTabBox(false); |
2460 | } |
2461 | if (data) |
2462 | XFree(data); |
2463 | } |
2464 | |
2465 | bool Client::isClient() const |
2466 | { |
2467 | return true; |
2468 | } |
2469 | |
2470 | #ifdef KWIN_BUILD_KAPPMENU |
2471 | void Client::() |
2472 | { |
2473 | m_menuAvailable = true; |
2474 | emit appMenuAvailable(); |
2475 | } |
2476 | |
2477 | void Client::() |
2478 | { |
2479 | m_menuAvailable = false; |
2480 | emit appMenuUnavailable(); |
2481 | } |
2482 | |
2483 | void Client::(const QPoint &p) |
2484 | { |
2485 | ApplicationMenu::self()->showApplicationMenu(p, window()); |
2486 | } |
2487 | #endif |
2488 | |
2489 | NET::WindowType Client::windowType(bool direct, int supportedTypes) const |
2490 | { |
2491 | // TODO: does it make sense to cache the returned window type for SUPPORTED_MANAGED_WINDOW_TYPES_MASK? |
2492 | if (supportedTypes == 0) { |
2493 | supportedTypes = SUPPORTED_MANAGED_WINDOW_TYPES_MASK; |
2494 | } |
2495 | NET::WindowType wt = info->windowType(supportedTypes); |
2496 | if (direct) { |
2497 | return wt; |
2498 | } |
2499 | NET::WindowType wt2 = client_rules.checkType(wt); |
2500 | if (wt != wt2) { |
2501 | wt = wt2; |
2502 | info->setWindowType(wt); // force hint change |
2503 | } |
2504 | // hacks here |
2505 | if (wt == NET::Unknown) // this is more or less suggested in NETWM spec |
2506 | wt = isTransient() ? NET::Dialog : NET::Normal; |
2507 | return wt; |
2508 | } |
2509 | |
2510 | bool Client::decorationHasAlpha() const |
2511 | { |
2512 | if (!decoration || !decorationPlugin()->hasAlpha()) { |
2513 | // either no decoration or decoration has alpha disabled |
2514 | return false; |
2515 | } |
2516 | if (decorationPlugin()->supportsAnnounceAlpha()) { |
2517 | return decoration->isAlphaEnabled(); |
2518 | } else { |
2519 | // decoration has alpha enabled and does not support alpha announcement |
2520 | return true; |
2521 | } |
2522 | } |
2523 | |
2524 | } // namespace |
2525 | |
2526 | #include "client.moc" |
2527 | |