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 "workspace.h" |
23 | // kwin libs |
24 | #include <kdecorationfactory.h> |
25 | #include <kwinglplatform.h> |
26 | // kwin |
27 | #ifdef KWIN_BUILD_ACTIVITIES |
28 | #include "activities.h" |
29 | #endif |
30 | #ifdef KWIN_BUILD_KAPPMENU |
31 | #include "appmenu.h" |
32 | #endif |
33 | #include "atoms.h" |
34 | #include "client.h" |
35 | #include "composite.h" |
36 | #include "cursor.h" |
37 | #include "dbusinterface.h" |
38 | #include "decorations.h" |
39 | #include "deleted.h" |
40 | #include "effects.h" |
41 | #include "focuschain.h" |
42 | #include "group.h" |
43 | #include "killwindow.h" |
44 | #include "netinfo.h" |
45 | #include "outline.h" |
46 | #include "placement.h" |
47 | #include "rules.h" |
48 | #ifdef KWIN_BUILD_SCREENEDGES |
49 | #include "screenedge.h" |
50 | #endif |
51 | #include "screens.h" |
52 | #ifdef KWIN_BUILD_SCRIPTING |
53 | #include "scripting/scripting.h" |
54 | #endif |
55 | #ifdef KWIN_BUILD_TABBOX |
56 | #include "tabbox.h" |
57 | #endif |
58 | #include "unmanaged.h" |
59 | #include "useractions.h" |
60 | #include "virtualdesktops.h" |
61 | #include "xcbutils.h" |
62 | // KDE |
63 | #include <kdeversion.h> |
64 | #include <KDE/KActionCollection> |
65 | #include <KDE/KCmdLineArgs> |
66 | #include <KDE/KConfig> |
67 | #include <KDE/KConfigGroup> |
68 | #include <KDE/KGlobal> |
69 | #include <KDE/KGlobalSettings> |
70 | #include <KDE/KStartupInfo> |
71 | #include <KDE/KWindowInfo> |
72 | #include <KDE/KWindowSystem> |
73 | // Qt |
74 | #include <QtConcurrentRun> |
75 | |
76 | namespace KWin |
77 | { |
78 | |
79 | extern int screen_number; |
80 | extern bool is_multihead; |
81 | |
82 | ColorMapper::ColorMapper(QObject *parent) |
83 | : QObject(parent) |
84 | , m_default(defaultScreen()->default_colormap) |
85 | , m_installed(defaultScreen()->default_colormap) |
86 | { |
87 | } |
88 | |
89 | ColorMapper::~ColorMapper() |
90 | { |
91 | } |
92 | |
93 | void ColorMapper::update() |
94 | { |
95 | xcb_colormap_t cmap = m_default; |
96 | if (Client *c = Workspace::self()->activeClient()) { |
97 | if (c->colormap() != XCB_COLORMAP_NONE) { |
98 | cmap = c->colormap(); |
99 | } |
100 | } |
101 | if (cmap != m_installed) { |
102 | xcb_install_colormap(connection(), cmap); |
103 | m_installed = cmap; |
104 | } |
105 | } |
106 | |
107 | Workspace* Workspace::_self = 0; |
108 | |
109 | Workspace::Workspace(bool restore) |
110 | : QObject(0) |
111 | , m_compositor(NULL) |
112 | // Unsorted |
113 | , active_popup(NULL) |
114 | , active_popup_client(NULL) |
115 | , active_client(0) |
116 | , last_active_client(0) |
117 | , most_recently_raised(0) |
118 | , movingClient(0) |
119 | , pending_take_activity(NULL) |
120 | , delayfocus_client(0) |
121 | , force_restacking(false) |
122 | , x_stacking_dirty(true) |
123 | , showing_desktop(false) |
124 | , block_showing_desktop(0) |
125 | , was_user_interaction(false) |
126 | , session_saving(false) |
127 | , block_focus(0) |
128 | , m_userActionsMenu(new UserActionsMenu(this)) |
129 | , keys(0) |
130 | , client_keys(NULL) |
131 | , disable_shortcuts_keys(NULL) |
132 | , client_keys_dialog(NULL) |
133 | , client_keys_client(NULL) |
134 | , global_shortcuts_disabled_for_client(false) |
135 | , workspaceInit(true) |
136 | , startup(0) |
137 | , set_active_client_recursion(0) |
138 | , block_stacking_updates(0) |
139 | , forced_global_mouse_grab(false) |
140 | { |
141 | // If KWin was already running it saved its configuration after loosing the selection -> Reread |
142 | QFuture<void> reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration); |
143 | |
144 | #ifdef KWIN_BUILD_KAPPMENU |
145 | ApplicationMenu::create(this); |
146 | #endif |
147 | |
148 | _self = this; |
149 | |
150 | // first initialize the extensions |
151 | Extensions::init(); |
152 | Xcb::Extensions::self(); |
153 | |
154 | // start the cursor support |
155 | Cursor::create(this); |
156 | |
157 | #ifdef KWIN_BUILD_ACTIVITIES |
158 | Activities *activities = Activities::create(this); |
159 | connect(activities, SIGNAL(currentChanged(QString)), SLOT(updateCurrentActivity(QString))); |
160 | #endif |
161 | |
162 | // PluginMgr needs access to the config file, so we need to wait for it for finishing |
163 | reparseConfigFuture.waitForFinished(); |
164 | |
165 | // get screen support |
166 | Screens *screens = Screens::create(this); |
167 | connect(screens, SIGNAL(changed()), SLOT(desktopResized())); |
168 | |
169 | options->loadConfig(); |
170 | options->loadCompositingConfig(false); |
171 | DecorationPlugin::create(this); |
172 | ColorMapper *colormaps = new ColorMapper(this); |
173 | connect(this, SIGNAL(clientActivated(KWin::Client*)), colormaps, SLOT(update())); |
174 | |
175 | updateXTime(); // Needed for proper initialization of user_time in Client ctor |
176 | |
177 | delayFocusTimer = 0; |
178 | |
179 | if (restore) |
180 | loadSessionInfo(); |
181 | |
182 | RuleBook::create(this)->load(); |
183 | |
184 | // Call this before XSelectInput() on the root window |
185 | startup = new KStartupInfo( |
186 | KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this); |
187 | |
188 | // Select windowmanager privileges |
189 | XSelectInput(display(), rootWindow(), |
190 | KeyPressMask | |
191 | PropertyChangeMask | |
192 | ColormapChangeMask | |
193 | SubstructureRedirectMask | |
194 | SubstructureNotifyMask | |
195 | FocusChangeMask | // For NotifyDetailNone |
196 | ExposureMask |
197 | ); |
198 | |
199 | #ifdef KWIN_BUILD_SCREENEDGES |
200 | ScreenEdges::create(this); |
201 | #endif |
202 | |
203 | // VirtualDesktopManager needs to be created prior to init shortcuts |
204 | // and prior to TabBox, due to TabBox connecting to signals |
205 | // actual initialization happens in init() |
206 | VirtualDesktopManager::create(this); |
207 | |
208 | #ifdef KWIN_BUILD_TABBOX |
209 | // need to create the tabbox before compositing scene is setup |
210 | TabBox::TabBox::create(this); |
211 | #endif |
212 | |
213 | m_compositor = Compositor::create(this); |
214 | connect(this, SIGNAL(currentDesktopChanged(int,KWin::Client*)), m_compositor, SLOT(addRepaintFull())); |
215 | connect(m_compositor, SIGNAL(compositingToggled(bool)), decorationPlugin(), SLOT(resetCompositing())); |
216 | |
217 | new DBusInterface(this); |
218 | |
219 | // Compatibility |
220 | long data = 1; |
221 | |
222 | XChangeProperty( |
223 | display(), |
224 | rootWindow(), |
225 | atoms->kwin_running, |
226 | atoms->kwin_running, |
227 | 32, |
228 | PropModeAppend, |
229 | (unsigned char*)(&data), |
230 | 1 |
231 | ); |
232 | |
233 | client_keys = new KActionCollection(this); |
234 | |
235 | Outline::create(this); |
236 | |
237 | initShortcuts(); |
238 | |
239 | init(); |
240 | } |
241 | |
242 | void Workspace::init() |
243 | { |
244 | Screens *screens = Screens::self(); |
245 | screens->setConfig(KGlobal::config()); |
246 | screens->reconfigure(); |
247 | connect(options, SIGNAL(configChanged()), screens, SLOT(reconfigure())); |
248 | #ifdef KWIN_BUILD_SCREENEDGES |
249 | ScreenEdges *screenEdges = ScreenEdges::self(); |
250 | screenEdges->setConfig(KGlobal::config()); |
251 | screenEdges->init(); |
252 | connect(options, SIGNAL(configChanged()), screenEdges, SLOT(reconfigure())); |
253 | connect(VirtualDesktopManager::self(), SIGNAL(layoutChanged(int,int)), screenEdges, SLOT(updateLayout())); |
254 | connect(this, SIGNAL(clientActivated(KWin::Client*)), screenEdges, SIGNAL(checkBlocking())); |
255 | #endif |
256 | |
257 | FocusChain *focusChain = FocusChain::create(this); |
258 | connect(this, SIGNAL(clientRemoved(KWin::Client*)), focusChain, SLOT(remove(KWin::Client*))); |
259 | connect(this, SIGNAL(clientActivated(KWin::Client*)), focusChain, SLOT(setActiveClient(KWin::Client*))); |
260 | connect(VirtualDesktopManager::self(), SIGNAL(countChanged(uint,uint)), focusChain, SLOT(resize(uint,uint))); |
261 | connect(VirtualDesktopManager::self(), SIGNAL(currentChanged(uint,uint)), focusChain, SLOT(setCurrentDesktop(uint,uint))); |
262 | connect(options, SIGNAL(separateScreenFocusChanged(bool)), focusChain, SLOT(setSeparateScreenFocus(bool))); |
263 | focusChain->setSeparateScreenFocus(options->isSeparateScreenFocus()); |
264 | |
265 | const uint32_t nullFocusValues[] = {true}; |
266 | m_nullFocus.reset(new Xcb::Window(QRect(-1, -1, 1, 1), XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, nullFocusValues)); |
267 | m_nullFocus->map(); |
268 | |
269 | RootInfo *rootInfo = RootInfo::create(); |
270 | |
271 | // create VirtualDesktopManager and perform dependency injection |
272 | VirtualDesktopManager *vds = VirtualDesktopManager::self(); |
273 | connect(vds, SIGNAL(desktopsRemoved(uint)), SLOT(moveClientsFromRemovedDesktops())); |
274 | connect(vds, SIGNAL(countChanged(uint,uint)), SLOT(slotDesktopCountChanged(uint,uint))); |
275 | connect(vds, SIGNAL(currentChanged(uint,uint)), SLOT(slotCurrentDesktopChanged(uint,uint))); |
276 | vds->setNavigationWrappingAround(options->isRollOverDesktops()); |
277 | connect(options, SIGNAL(rollOverDesktopsChanged(bool)), vds, SLOT(setNavigationWrappingAround(bool))); |
278 | vds->setRootInfo(rootInfo); |
279 | vds->setConfig(KGlobal::config()); |
280 | |
281 | // Now we know how many desktops we'll have, thus we initialize the positioning object |
282 | Placement::create(this); |
283 | |
284 | // positioning object needs to be created before the virtual desktops are loaded. |
285 | vds->load(); |
286 | vds->updateLayout(); |
287 | |
288 | // Extra NETRootInfo instance in Client mode is needed to get the values of the properties |
289 | NETRootInfo client_info(display(), NET::ActiveWindow | NET::CurrentDesktop); |
290 | int initial_desktop; |
291 | if (!kapp->isSessionRestored()) |
292 | initial_desktop = client_info.currentDesktop(); |
293 | else { |
294 | KConfigGroup group(kapp->sessionConfig(), "Session" ); |
295 | initial_desktop = group.readEntry("desktop" , 1); |
296 | } |
297 | if (!VirtualDesktopManager::self()->setCurrent(initial_desktop)) |
298 | VirtualDesktopManager::self()->setCurrent(1); |
299 | #ifdef KWIN_BUILD_ACTIVITIES |
300 | Activities::self()->update(false, true); |
301 | #endif |
302 | |
303 | reconfigureTimer.setSingleShot(true); |
304 | updateToolWindowsTimer.setSingleShot(true); |
305 | |
306 | connect(&reconfigureTimer, SIGNAL(timeout()), this, SLOT(slotReconfigure())); |
307 | connect(&updateToolWindowsTimer, SIGNAL(timeout()), this, SLOT(slotUpdateToolWindows())); |
308 | |
309 | connect(KGlobalSettings::self(), SIGNAL(appearanceChanged()), this, SLOT(reconfigure())); |
310 | connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), this, SLOT(slotSettingsChanged(int))); |
311 | connect(KGlobalSettings::self(), SIGNAL(blockShortcuts(int)), this, SLOT(slotBlockShortcuts(int))); |
312 | |
313 | active_client = NULL; |
314 | rootInfo->setActiveWindow(None); |
315 | focusToNull(); |
316 | if (!kapp->isSessionRestored()) |
317 | ++block_focus; // Because it will be set below |
318 | |
319 | { |
320 | // Begin updates blocker block |
321 | StackingUpdatesBlocker blocker(this); |
322 | |
323 | bool fixoffset = KCmdLineArgs::parsedArgs()->getOption("crashes" ).toInt() > 0; |
324 | |
325 | Xcb::Tree tree(rootWindow()); |
326 | xcb_window_t *wins = xcb_query_tree_children(tree.data()); |
327 | |
328 | QVector<Xcb::WindowAttributes> windowAttributes(tree->children_len); |
329 | QVector<Xcb::WindowGeometry> windowGeometries(tree->children_len); |
330 | |
331 | // Request the attributes and geometries of all toplevel windows |
332 | for (int i = 0; i < tree->children_len; i++) { |
333 | windowAttributes[i] = Xcb::WindowAttributes(wins[i]); |
334 | windowGeometries[i] = Xcb::WindowGeometry(wins[i]); |
335 | } |
336 | |
337 | // Get the replies |
338 | for (int i = 0; i < tree->children_len; i++) { |
339 | Xcb::WindowAttributes attr(windowAttributes.at(i)); |
340 | |
341 | if (attr.isNull()) { |
342 | continue; |
343 | } |
344 | |
345 | if (attr->override_redirect) { |
346 | if (attr->map_state == XCB_MAP_STATE_VIEWABLE && |
347 | attr->_class != XCB_WINDOW_CLASS_INPUT_ONLY) |
348 | // ### This will request the attributes again |
349 | createUnmanaged(wins[i]); |
350 | } else if (attr->map_state != XCB_MAP_STATE_UNMAPPED) { |
351 | if (fixoffset) { |
352 | fixPositionAfterCrash(wins[i], windowGeometries[i].data()); |
353 | } |
354 | |
355 | // ### This will request the attributes again |
356 | createClient(wins[i], true); |
357 | } |
358 | } |
359 | |
360 | // Propagate clients, will really happen at the end of the updates blocker block |
361 | updateStackingOrder(true); |
362 | |
363 | saveOldScreenSizes(); |
364 | updateClientArea(); |
365 | |
366 | // NETWM spec says we have to set it to (0,0) if we don't support it |
367 | NETPoint* viewports = new NETPoint[VirtualDesktopManager::self()->count()]; |
368 | rootInfo->setDesktopViewport(VirtualDesktopManager::self()->count(), *viewports); |
369 | delete[] viewports; |
370 | QRect geom; |
371 | for (int i = 0; i < screens->count(); i++) { |
372 | geom |= screens->geometry(i); |
373 | } |
374 | NETSize desktop_geometry; |
375 | desktop_geometry.width = geom.width(); |
376 | desktop_geometry.height = geom.height(); |
377 | rootInfo->setDesktopGeometry(-1, desktop_geometry); |
378 | setShowingDesktop(false); |
379 | |
380 | } // End updates blocker block |
381 | |
382 | Client* new_active_client = NULL; |
383 | if (!kapp->isSessionRestored()) { |
384 | --block_focus; |
385 | new_active_client = findClient(WindowMatchPredicate(client_info.activeWindow())); |
386 | } |
387 | if (new_active_client == NULL |
388 | && activeClient() == NULL && should_get_focus.count() == 0) { |
389 | // No client activated in manage() |
390 | if (new_active_client == NULL) |
391 | new_active_client = topClientOnDesktop(VirtualDesktopManager::self()->current(), -1); |
392 | if (new_active_client == NULL && !desktops.isEmpty()) |
393 | new_active_client = findDesktop(true, VirtualDesktopManager::self()->current()); |
394 | } |
395 | if (new_active_client != NULL) |
396 | activateClient(new_active_client); |
397 | |
398 | |
399 | #ifdef KWIN_BUILD_SCRIPTING |
400 | Scripting::create(this); |
401 | #endif |
402 | |
403 | // SELI TODO: This won't work with unreasonable focus policies, |
404 | // and maybe in rare cases also if the selected client doesn't |
405 | // want focus |
406 | workspaceInit = false; |
407 | |
408 | // broadcast that Workspace is ready, but first process all events. |
409 | QMetaObject::invokeMethod(this, "workspaceInitialized" , Qt::QueuedConnection); |
410 | |
411 | // TODO: ungrabXServer() |
412 | } |
413 | |
414 | Workspace::~Workspace() |
415 | { |
416 | delete m_compositor; |
417 | m_compositor = NULL; |
418 | blockStackingUpdates(true); |
419 | |
420 | // TODO: grabXServer(); |
421 | |
422 | // Use stacking_order, so that kwin --replace keeps stacking order |
423 | const ToplevelList stack = stacking_order; |
424 | // "mutex" the stackingorder, since anything trying to access it from now on will find |
425 | // many dangeling pointers and crash |
426 | stacking_order.clear(); |
427 | |
428 | for (ToplevelList::const_iterator it = stack.constBegin(), end = stack.constEnd(); it != end; ++it) { |
429 | Client *c = qobject_cast<Client*>(const_cast<Toplevel*>(*it)); |
430 | if (!c) { |
431 | continue; |
432 | } |
433 | // Only release the window |
434 | c->releaseWindow(true); |
435 | // No removeClient() is called, it does more than just removing. |
436 | // However, remove from some lists to e.g. prevent performTransiencyCheck() |
437 | // from crashing. |
438 | clients.removeAll(c); |
439 | desktops.removeAll(c); |
440 | } |
441 | for (UnmanagedList::iterator it = unmanaged.begin(), end = unmanaged.end(); it != end; ++it) |
442 | (*it)->release(true); |
443 | XDeleteProperty(display(), rootWindow(), atoms->kwin_running); |
444 | |
445 | delete RuleBook::self(); |
446 | KGlobal::config()->sync(); |
447 | |
448 | RootInfo::destroy(); |
449 | delete startup; |
450 | delete Placement::self(); |
451 | delete client_keys_dialog; |
452 | foreach (SessionInfo * s, session) |
453 | delete s; |
454 | |
455 | // TODO: ungrabXServer(); |
456 | |
457 | Xcb::Extensions::destroy(); |
458 | _self = 0; |
459 | } |
460 | |
461 | Client* Workspace::createClient(xcb_window_t w, bool is_mapped) |
462 | { |
463 | StackingUpdatesBlocker blocker(this); |
464 | Client* c = new Client(); |
465 | connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); |
466 | connect(c, SIGNAL(activeChanged()), m_compositor, SLOT(checkUnredirect())); |
467 | connect(c, SIGNAL(fullScreenChanged()), m_compositor, SLOT(checkUnredirect())); |
468 | connect(c, SIGNAL(geometryChanged()), m_compositor, SLOT(checkUnredirect())); |
469 | connect(c, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), m_compositor, SLOT(checkUnredirect())); |
470 | connect(c, SIGNAL(blockingCompositingChanged(KWin::Client*)), m_compositor, SLOT(updateCompositeBlocking(KWin::Client*))); |
471 | #ifdef KWIN_BUILD_SCREENEDGES |
472 | connect(c, SIGNAL(clientFullScreenSet(KWin::Client*,bool,bool)), ScreenEdges::self(), SIGNAL(checkBlocking())); |
473 | #endif |
474 | connect(c, SIGNAL(desktopPresenceChanged(KWin::Client*,int)), SIGNAL(desktopPresenceChanged(KWin::Client*,int)), Qt::QueuedConnection); |
475 | if (!c->manage(w, is_mapped)) { |
476 | Client::deleteClient(c); |
477 | return NULL; |
478 | } |
479 | addClient(c); |
480 | return c; |
481 | } |
482 | |
483 | Unmanaged* Workspace::createUnmanaged(xcb_window_t w) |
484 | { |
485 | if (m_compositor && m_compositor->checkForOverlayWindow(w)) |
486 | return NULL; |
487 | Unmanaged* c = new Unmanaged(); |
488 | if (!c->track(w)) { |
489 | Unmanaged::deleteUnmanaged(c); |
490 | return NULL; |
491 | } |
492 | connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); |
493 | addUnmanaged(c); |
494 | emit unmanagedAdded(c); |
495 | return c; |
496 | } |
497 | |
498 | void Workspace::addClient(Client* c) |
499 | { |
500 | Group* grp = findGroup(c->window()); |
501 | |
502 | KWindowInfo info = KWindowSystem::windowInfo(c->window(), -1U, NET::WM2WindowClass); |
503 | |
504 | emit clientAdded(c); |
505 | |
506 | if (grp != NULL) |
507 | grp->gotLeader(c); |
508 | |
509 | if (c->isDesktop()) { |
510 | desktops.append(c); |
511 | if (active_client == NULL && should_get_focus.isEmpty() && c->isOnCurrentDesktop()) |
512 | requestFocus(c); // TODO: Make sure desktop is active after startup if there's no other window active |
513 | } else { |
514 | FocusChain::self()->update(c, FocusChain::Update); |
515 | clients.append(c); |
516 | } |
517 | if (!unconstrained_stacking_order.contains(c)) |
518 | unconstrained_stacking_order.append(c); // Raise if it hasn't got any stacking position yet |
519 | if (!stacking_order.contains(c)) // It'll be updated later, and updateToolWindows() requires |
520 | stacking_order.append(c); // c to be in stacking_order |
521 | x_stacking_dirty = true; |
522 | updateClientArea(); // This cannot be in manage(), because the client got added only now |
523 | updateClientLayer(c); |
524 | if (c->isDesktop()) { |
525 | raiseClient(c); |
526 | // If there's no active client, make this desktop the active one |
527 | if (activeClient() == NULL && should_get_focus.count() == 0) |
528 | activateClient(findDesktop(true, VirtualDesktopManager::self()->current())); |
529 | } |
530 | c->checkActiveModal(); |
531 | checkTransients(c->window()); // SELI TODO: Does this really belong here? |
532 | updateStackingOrder(true); // Propagate new client |
533 | if (c->isUtility() || c->isMenu() || c->isToolbar()) |
534 | updateToolWindows(true); |
535 | checkNonExistentClients(); |
536 | #ifdef KWIN_BUILD_TABBOX |
537 | if (TabBox::TabBox::self()->isDisplayed()) |
538 | TabBox::TabBox::self()->reset(true); |
539 | #endif |
540 | #ifdef KWIN_BUILD_KAPPMENU |
541 | if (ApplicationMenu::self()->hasMenu(c->window())) |
542 | c->setAppMenuAvailable(); |
543 | #endif |
544 | } |
545 | |
546 | void Workspace::addUnmanaged(Unmanaged* c) |
547 | { |
548 | unmanaged.append(c); |
549 | x_stacking_dirty = true; |
550 | } |
551 | |
552 | /** |
553 | * Destroys the client \a c |
554 | */ |
555 | void Workspace::removeClient(Client* c) |
556 | { |
557 | emit clientRemoved(c); |
558 | |
559 | if (c == active_popup_client) |
560 | closeActivePopup(); |
561 | if (m_userActionsMenu->isMenuClient(c)) { |
562 | m_userActionsMenu->close(); |
563 | } |
564 | |
565 | c->untab(QRect(), true); |
566 | |
567 | if (client_keys_client == c) |
568 | setupWindowShortcutDone(false); |
569 | if (!c->shortcut().isEmpty()) { |
570 | c->setShortcut(QString()); // Remove from client_keys |
571 | clientShortcutUpdated(c); // Needed, since this is otherwise delayed by setShortcut() and wouldn't run |
572 | } |
573 | |
574 | #ifdef KWIN_BUILD_TABBOX |
575 | TabBox::TabBox *tabBox = TabBox::TabBox::self(); |
576 | if (tabBox->isDisplayed() && tabBox->currentClient() == c) |
577 | tabBox->nextPrev(true); |
578 | #endif |
579 | |
580 | Q_ASSERT(clients.contains(c) || desktops.contains(c)); |
581 | // TODO: if marked client is removed, notify the marked list |
582 | clients.removeAll(c); |
583 | desktops.removeAll(c); |
584 | x_stacking_dirty = true; |
585 | attention_chain.removeAll(c); |
586 | showing_desktop_clients.removeAll(c); |
587 | Group* group = findGroup(c->window()); |
588 | if (group != NULL) |
589 | group->lostLeader(); |
590 | |
591 | if (c == most_recently_raised) |
592 | most_recently_raised = 0; |
593 | should_get_focus.removeAll(c); |
594 | Q_ASSERT(c != active_client); |
595 | if (c == last_active_client) |
596 | last_active_client = 0; |
597 | if (c == pending_take_activity) |
598 | pending_take_activity = NULL; |
599 | if (c == delayfocus_client) |
600 | cancelDelayFocus(); |
601 | |
602 | updateStackingOrder(true); |
603 | |
604 | #ifdef KWIN_BUILD_TABBOX |
605 | if (tabBox->isDisplayed()) |
606 | tabBox->reset(true); |
607 | #endif |
608 | |
609 | updateClientArea(); |
610 | } |
611 | |
612 | void Workspace::removeUnmanaged(Unmanaged* c) |
613 | { |
614 | assert(unmanaged.contains(c)); |
615 | unmanaged.removeAll(c); |
616 | x_stacking_dirty = true; |
617 | } |
618 | |
619 | void Workspace::addDeleted(Deleted* c, Toplevel *orig) |
620 | { |
621 | assert(!deleted.contains(c)); |
622 | deleted.append(c); |
623 | const int unconstraintedIndex = unconstrained_stacking_order.indexOf(orig); |
624 | if (unconstraintedIndex != -1) { |
625 | unconstrained_stacking_order.replace(unconstraintedIndex, c); |
626 | } else { |
627 | unconstrained_stacking_order.append(c); |
628 | } |
629 | const int index = stacking_order.indexOf(orig); |
630 | if (index != -1) { |
631 | stacking_order.replace(index, c); |
632 | } else { |
633 | stacking_order.append(c); |
634 | } |
635 | x_stacking_dirty = true; |
636 | connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); |
637 | } |
638 | |
639 | void Workspace::removeDeleted(Deleted* c) |
640 | { |
641 | assert(deleted.contains(c)); |
642 | emit deletedRemoved(c); |
643 | deleted.removeAll(c); |
644 | unconstrained_stacking_order.removeAll(c); |
645 | stacking_order.removeAll(c); |
646 | x_stacking_dirty = true; |
647 | if (c->wasClient() && m_compositor) { |
648 | m_compositor->updateCompositeBlocking(); |
649 | } |
650 | } |
651 | |
652 | void Workspace::updateToolWindows(bool also_hide) |
653 | { |
654 | // TODO: What if Client's transiency/group changes? should this be called too? (I'm paranoid, am I not?) |
655 | if (!options->isHideUtilityWindowsForInactive()) { |
656 | for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) |
657 | if (!(*it)->tabGroup() || (*it)->tabGroup()->current() == *it) |
658 | (*it)->hideClient(false); |
659 | return; |
660 | } |
661 | const Group* group = NULL; |
662 | const Client* client = active_client; |
663 | // Go up in transiency hiearchy, if the top is found, only tool transients for the top mainwindow |
664 | // will be shown; if a group transient is group, all tools in the group will be shown |
665 | while (client != NULL) { |
666 | if (!client->isTransient()) |
667 | break; |
668 | if (client->groupTransient()) { |
669 | group = client->group(); |
670 | break; |
671 | } |
672 | client = client->transientFor(); |
673 | } |
674 | // Use stacking order only to reduce flicker, it doesn't matter if block_stacking_updates == 0, |
675 | // I.e. if it's not up to date |
676 | |
677 | // SELI TODO: But maybe it should - what if a new client has been added that's not in stacking order yet? |
678 | ClientList to_show, to_hide; |
679 | for (ToplevelList::ConstIterator it = stacking_order.constBegin(); |
680 | it != stacking_order.constEnd(); |
681 | ++it) { |
682 | Client *c = qobject_cast<Client*>(*it); |
683 | if (!c) { |
684 | continue; |
685 | } |
686 | if (c->isUtility() || c->isMenu() || c->isToolbar()) { |
687 | bool show = true; |
688 | if (!c->isTransient()) { |
689 | if (c->group()->members().count() == 1) // Has its own group, keep always visible |
690 | show = true; |
691 | else if (client != NULL && c->group() == client->group()) |
692 | show = true; |
693 | else |
694 | show = false; |
695 | } else { |
696 | if (group != NULL && c->group() == group) |
697 | show = true; |
698 | else if (client != NULL && client->hasTransient(c, true)) |
699 | show = true; |
700 | else |
701 | show = false; |
702 | } |
703 | if (!show && also_hide) { |
704 | const ClientList mainclients = c->mainClients(); |
705 | // Don't hide utility windows which are standalone(?) or |
706 | // have e.g. kicker as mainwindow |
707 | if (mainclients.isEmpty()) |
708 | show = true; |
709 | for (ClientList::ConstIterator it2 = mainclients.constBegin(); |
710 | it2 != mainclients.constEnd(); |
711 | ++it2) { |
712 | if ((*it2)->isSpecialWindow()) |
713 | show = true; |
714 | } |
715 | if (!show) |
716 | to_hide.append(c); |
717 | } |
718 | if (show) |
719 | to_show.append(c); |
720 | } |
721 | } // First show new ones, then hide |
722 | for (int i = to_show.size() - 1; |
723 | i >= 0; |
724 | --i) // From topmost |
725 | // TODO: Since this is in stacking order, the order of taskbar entries changes :( |
726 | to_show.at(i)->hideClient(false); |
727 | if (also_hide) { |
728 | for (ClientList::ConstIterator it = to_hide.constBegin(); |
729 | it != to_hide.constEnd(); |
730 | ++it) // From bottommost |
731 | (*it)->hideClient(true); |
732 | updateToolWindowsTimer.stop(); |
733 | } else // setActiveClient() is after called with NULL client, quickly followed |
734 | // by setting a new client, which would result in flickering |
735 | resetUpdateToolWindowsTimer(); |
736 | } |
737 | |
738 | |
739 | void Workspace::resetUpdateToolWindowsTimer() |
740 | { |
741 | updateToolWindowsTimer.start(200); |
742 | } |
743 | |
744 | void Workspace::slotUpdateToolWindows() |
745 | { |
746 | updateToolWindows(true); |
747 | } |
748 | |
749 | void Workspace::slotReloadConfig() |
750 | { |
751 | reconfigure(); |
752 | } |
753 | |
754 | void Workspace::reconfigure() |
755 | { |
756 | reconfigureTimer.start(200); |
757 | } |
758 | |
759 | /** |
760 | * This D-Bus call is used by the compositing kcm. Since the reconfigure() |
761 | * D-Bus call delays the actual reconfiguring, it is not possible to immediately |
762 | * call compositingActive(). Therefore the kcm will instead call this to ensure |
763 | * the reconfiguring has already happened. |
764 | */ |
765 | bool Workspace::waitForCompositingSetup() |
766 | { |
767 | if (reconfigureTimer.isActive()) { |
768 | reconfigureTimer.stop(); |
769 | slotReconfigure(); |
770 | } |
771 | if (m_compositor) { |
772 | return m_compositor->isActive(); |
773 | } |
774 | return false; |
775 | } |
776 | |
777 | void Workspace::slotSettingsChanged(int category) |
778 | { |
779 | kDebug(1212) << "Workspace::slotSettingsChanged()" ; |
780 | if (category == KGlobalSettings::SETTINGS_SHORTCUTS) |
781 | m_userActionsMenu->discard(); |
782 | } |
783 | |
784 | /** |
785 | * Reread settings |
786 | */ |
787 | KWIN_PROCEDURE(CheckBorderSizesProcedure, Client, cl->checkBorderSizes(true)); |
788 | |
789 | void Workspace::slotReconfigure() |
790 | { |
791 | kDebug(1212) << "Workspace::slotReconfigure()" ; |
792 | reconfigureTimer.stop(); |
793 | |
794 | bool borderlessMaximizedWindows = options->borderlessMaximizedWindows(); |
795 | |
796 | KGlobal::config()->reparseConfiguration(); |
797 | unsigned long changed = options->updateSettings(); |
798 | |
799 | emit configChanged(); |
800 | m_userActionsMenu->discard(); |
801 | updateToolWindows(true); |
802 | |
803 | DecorationPlugin *deco = DecorationPlugin::self(); |
804 | if (!deco->isDisabled() && deco->reset(changed)) { |
805 | // Decorations need to be recreated |
806 | |
807 | // This actually seems to make things worse now |
808 | //QWidget curtain; |
809 | //curtain.setBackgroundMode( NoBackground ); |
810 | //curtain.setGeometry( Kephal::ScreenUtils::desktopGeometry() ); |
811 | //curtain.show(); |
812 | |
813 | for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) |
814 | (*it)->updateDecoration(true, true); |
815 | // If the new decoration doesn't supports tabs then ungroup clients |
816 | if (!decorationPlugin()->supportsTabbing()) { |
817 | foreach (Client * c, clients) |
818 | c->untab(); |
819 | } |
820 | deco->destroyPreviousPlugin(); |
821 | } else { |
822 | forEachClient(CheckBorderSizesProcedure()); |
823 | foreach (Client * c, clients) |
824 | c->triggerDecorationRepaint(); |
825 | } |
826 | |
827 | RuleBook::self()->load(); |
828 | for (ClientList::Iterator it = clients.begin(); |
829 | it != clients.end(); |
830 | ++it) { |
831 | (*it)->setupWindowRules(true); |
832 | (*it)->applyWindowRules(); |
833 | RuleBook::self()->discardUsed(*it, false); |
834 | } |
835 | |
836 | if (borderlessMaximizedWindows != options->borderlessMaximizedWindows() && |
837 | !options->borderlessMaximizedWindows()) { |
838 | // in case borderless maximized windows option changed and new option |
839 | // is to have borders, we need to unset the borders for all maximized windows |
840 | for (ClientList::Iterator it = clients.begin(); |
841 | it != clients.end(); |
842 | ++it) { |
843 | if ((*it)->maximizeMode() == MaximizeFull) |
844 | (*it)->checkNoBorder(); |
845 | } |
846 | } |
847 | |
848 | if (!deco->isDisabled()) { |
849 | rootInfo()->setSupported(NET::WM2FrameOverlap, deco->factory()->supports(AbilityExtendIntoClientArea)); |
850 | } else { |
851 | rootInfo()->setSupported(NET::WM2FrameOverlap, false); |
852 | } |
853 | } |
854 | |
855 | /** |
856 | * During virt. desktop switching, desktop areas covered by windows that are |
857 | * going to be hidden are first obscured by new windows with no background |
858 | * ( i.e. transparent ) placed right below the windows. These invisible windows |
859 | * are removed after the switch is complete. |
860 | * Reduces desktop ( wallpaper ) repaints during desktop switching |
861 | */ |
862 | class ObscuringWindows |
863 | { |
864 | public: |
865 | ~ObscuringWindows(); |
866 | void create(Client* c); |
867 | private: |
868 | QList<Window> obscuring_windows; |
869 | static QList<Window>* cached; |
870 | static unsigned int max_cache_size; |
871 | }; |
872 | |
873 | QList<Window>* ObscuringWindows::cached = 0; |
874 | unsigned int ObscuringWindows::max_cache_size = 0; |
875 | |
876 | void ObscuringWindows::create(Client* c) |
877 | { |
878 | if (cached == 0) |
879 | cached = new QList<Window>; |
880 | Window obs_win; |
881 | XWindowChanges chngs; |
882 | int mask = CWSibling | CWStackMode; |
883 | if (cached->count() > 0) { |
884 | cached->removeAll(obs_win = cached->first()); |
885 | chngs.x = c->x(); |
886 | chngs.y = c->y(); |
887 | chngs.width = c->width(); |
888 | chngs.height = c->height(); |
889 | mask |= CWX | CWY | CWWidth | CWHeight; |
890 | } else { |
891 | XSetWindowAttributes a; |
892 | a.background_pixmap = None; |
893 | a.override_redirect = True; |
894 | obs_win = XCreateWindow(display(), rootWindow(), c->x(), c->y(), |
895 | c->width(), c->height(), 0, CopyFromParent, InputOutput, |
896 | CopyFromParent, CWBackPixmap | CWOverrideRedirect, &a); |
897 | } |
898 | chngs.sibling = c->frameId(); |
899 | chngs.stack_mode = Below; |
900 | XConfigureWindow(display(), obs_win, mask, &chngs); |
901 | XMapWindow(display(), obs_win); |
902 | obscuring_windows.append(obs_win); |
903 | } |
904 | |
905 | ObscuringWindows::~ObscuringWindows() |
906 | { |
907 | max_cache_size = qMax(int(max_cache_size), obscuring_windows.count() + 4) - 1; |
908 | for (QList<Window>::ConstIterator it = obscuring_windows.constBegin(); |
909 | it != obscuring_windows.constEnd(); |
910 | ++it) { |
911 | XUnmapWindow(display(), *it); |
912 | if (cached->count() < int(max_cache_size)) |
913 | cached->prepend(*it); |
914 | else |
915 | XDestroyWindow(display(), *it); |
916 | } |
917 | } |
918 | |
919 | void Workspace::slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop) |
920 | { |
921 | closeActivePopup(); |
922 | ++block_focus; |
923 | StackingUpdatesBlocker blocker(this); |
924 | updateClientVisibilityOnDesktopChange(oldDesktop, newDesktop); |
925 | // Restore the focus on this desktop |
926 | --block_focus; |
927 | |
928 | activateClientOnNewDesktop(newDesktop); |
929 | emit currentDesktopChanged(oldDesktop, movingClient); |
930 | } |
931 | |
932 | void Workspace::updateClientVisibilityOnDesktopChange(uint oldDesktop, uint newDesktop) |
933 | { |
934 | ++block_showing_desktop; |
935 | ObscuringWindows obs_wins; |
936 | for (ToplevelList::ConstIterator it = stacking_order.constBegin(); |
937 | it != stacking_order.constEnd(); |
938 | ++it) { |
939 | Client *c = qobject_cast<Client*>(*it); |
940 | if (!c) { |
941 | continue; |
942 | } |
943 | if (!c->isOnDesktop(newDesktop) && c != movingClient && c->isOnCurrentActivity()) { |
944 | if (c->isShown(true) && c->isOnDesktop(oldDesktop) && !compositing()) |
945 | obs_wins.create(c); |
946 | (c)->updateVisibility(); |
947 | } |
948 | } |
949 | // Now propagate the change, after hiding, before showing |
950 | rootInfo()->setCurrentDesktop(VirtualDesktopManager::self()->current()); |
951 | |
952 | if (movingClient && !movingClient->isOnDesktop(newDesktop)) { |
953 | movingClient->setDesktop(newDesktop); |
954 | } |
955 | |
956 | for (int i = stacking_order.size() - 1; i >= 0 ; --i) { |
957 | Client *c = qobject_cast<Client*>(stacking_order.at(i)); |
958 | if (!c) { |
959 | continue; |
960 | } |
961 | if (c->isOnDesktop(newDesktop) && c->isOnCurrentActivity()) |
962 | c->updateVisibility(); |
963 | } |
964 | --block_showing_desktop; |
965 | if (showingDesktop()) // Do this only after desktop change to avoid flicker |
966 | resetShowingDesktop(false); |
967 | } |
968 | |
969 | void Workspace::activateClientOnNewDesktop(uint desktop) |
970 | { |
971 | Client* c = NULL; |
972 | if (options->focusPolicyIsReasonable()) { |
973 | c = findClientToActivateOnDesktop(desktop); |
974 | } |
975 | // If "unreasonable focus policy" and active_client is on_all_desktops and |
976 | // under mouse (Hence == old_active_client), conserve focus. |
977 | // (Thanks to Volker Schatz <V.Schatz at thphys.uni-heidelberg.de>) |
978 | else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop()) |
979 | c = active_client; |
980 | |
981 | if (c == NULL && !desktops.isEmpty()) |
982 | c = findDesktop(true, desktop); |
983 | |
984 | if (c != active_client) |
985 | setActiveClient(NULL); |
986 | |
987 | if (c) |
988 | requestFocus(c); |
989 | else if (!desktops.isEmpty()) |
990 | requestFocus(findDesktop(true, desktop)); |
991 | else |
992 | focusToNull(); |
993 | } |
994 | |
995 | Client *Workspace::findClientToActivateOnDesktop(uint desktop) |
996 | { |
997 | if (movingClient != NULL && active_client == movingClient && |
998 | FocusChain::self()->contains(active_client, desktop) && |
999 | active_client->isShown(true) && active_client->isOnCurrentDesktop()) { |
1000 | // A requestFocus call will fail, as the client is already active |
1001 | return active_client; |
1002 | } |
1003 | // from actiavtion.cpp |
1004 | if (options->isNextFocusPrefersMouse()) { |
1005 | ToplevelList::const_iterator it = stackingOrder().constEnd(); |
1006 | while (it != stackingOrder().constBegin()) { |
1007 | Client *client = qobject_cast<Client*>(*(--it)); |
1008 | if (!client) { |
1009 | continue; |
1010 | } |
1011 | |
1012 | if (!(client->isShown(false) && client->isOnDesktop(desktop) && |
1013 | client->isOnCurrentActivity() && client->isOnActiveScreen())) |
1014 | continue; |
1015 | |
1016 | if (client->geometry().contains(Cursor::pos())) { |
1017 | if (!client->isDesktop()) |
1018 | return client; |
1019 | break; // unconditional break - we do not pass the focus to some client below an unusable one |
1020 | } |
1021 | } |
1022 | } |
1023 | return FocusChain::self()->getForActivation(desktop); |
1024 | } |
1025 | |
1026 | /** |
1027 | * Updates the current activity when it changes |
1028 | * do *not* call this directly; it does not set the activity. |
1029 | * |
1030 | * Shows/Hides windows according to the stacking order |
1031 | */ |
1032 | |
1033 | void Workspace::updateCurrentActivity(const QString &new_activity) |
1034 | { |
1035 | #ifdef KWIN_BUILD_ACTIVITIES |
1036 | //closeActivePopup(); |
1037 | ++block_focus; |
1038 | // TODO: Q_ASSERT( block_stacking_updates == 0 ); // Make sure stacking_order is up to date |
1039 | StackingUpdatesBlocker blocker(this); |
1040 | |
1041 | ++block_showing_desktop; //FIXME should I be using that? |
1042 | // Optimized Desktop switching: unmapping done from back to front |
1043 | // mapping done from front to back => less exposure events |
1044 | //Notify::raise((Notify::Event) (Notify::DesktopChange+new_desktop)); |
1045 | |
1046 | ObscuringWindows obs_wins; |
1047 | |
1048 | const QString &old_activity = Activities::self()->previous(); |
1049 | |
1050 | for (ToplevelList::ConstIterator it = stacking_order.constBegin(); |
1051 | it != stacking_order.constEnd(); |
1052 | ++it) { |
1053 | Client *c = qobject_cast<Client*>(*it); |
1054 | if (!c) { |
1055 | continue; |
1056 | } |
1057 | if (!c->isOnActivity(new_activity) && c != movingClient && c->isOnCurrentDesktop()) { |
1058 | if (c->isShown(true) && c->isOnActivity(old_activity) && !compositing()) |
1059 | obs_wins.create(c); |
1060 | c->updateVisibility(); |
1061 | } |
1062 | } |
1063 | |
1064 | // Now propagate the change, after hiding, before showing |
1065 | //rootInfo->setCurrentDesktop( currentDesktop() ); |
1066 | |
1067 | /* TODO someday enable dragging windows to other activities |
1068 | if ( movingClient && !movingClient->isOnDesktop( new_desktop )) |
1069 | { |
1070 | movingClient->setDesktop( new_desktop ); |
1071 | */ |
1072 | |
1073 | for (int i = stacking_order.size() - 1; i >= 0 ; --i) { |
1074 | Client *c = qobject_cast<Client*>(stacking_order.at(i)); |
1075 | if (!c) { |
1076 | continue; |
1077 | } |
1078 | if (c->isOnActivity(new_activity)) |
1079 | c->updateVisibility(); |
1080 | } |
1081 | |
1082 | --block_showing_desktop; |
1083 | //FIXME not sure if I should do this either |
1084 | if (showingDesktop()) // Do this only after desktop change to avoid flicker |
1085 | resetShowingDesktop(false); |
1086 | |
1087 | // Restore the focus on this desktop |
1088 | --block_focus; |
1089 | Client* c = 0; |
1090 | |
1091 | //FIXME below here is a lot of focuschain stuff, probably all wrong now |
1092 | if (options->focusPolicyIsReasonable()) { |
1093 | // Search in focus chain |
1094 | c = FocusChain::self()->getForActivation(VirtualDesktopManager::self()->current()); |
1095 | } |
1096 | // If "unreasonable focus policy" and active_client is on_all_desktops and |
1097 | // under mouse (Hence == old_active_client), conserve focus. |
1098 | // (Thanks to Volker Schatz <V.Schatz at thphys.uni-heidelberg.de>) |
1099 | else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop() && active_client->isOnCurrentActivity()) |
1100 | c = active_client; |
1101 | |
1102 | if (c == NULL && !desktops.isEmpty()) |
1103 | c = findDesktop(true, VirtualDesktopManager::self()->current()); |
1104 | |
1105 | if (c != active_client) |
1106 | setActiveClient(NULL); |
1107 | |
1108 | if (c) |
1109 | requestFocus(c); |
1110 | else if (!desktops.isEmpty()) |
1111 | requestFocus(findDesktop(true, VirtualDesktopManager::self()->current())); |
1112 | else |
1113 | focusToNull(); |
1114 | |
1115 | // Not for the very first time, only if something changed and there are more than 1 desktops |
1116 | |
1117 | //if ( effects != NULL && old_desktop != 0 && old_desktop != new_desktop ) |
1118 | // static_cast<EffectsHandlerImpl*>( effects )->desktopChanged( old_desktop ); |
1119 | if (compositing() && m_compositor) |
1120 | m_compositor->addRepaintFull(); |
1121 | #else |
1122 | Q_UNUSED(new_activity) |
1123 | #endif |
1124 | } |
1125 | |
1126 | void Workspace::moveClientsFromRemovedDesktops() |
1127 | { |
1128 | for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { |
1129 | if (!(*it)->isOnAllDesktops() && (*it)->desktop() > static_cast<int>(VirtualDesktopManager::self()->count())) |
1130 | sendClientToDesktop(*it, VirtualDesktopManager::self()->count(), true); |
1131 | } |
1132 | } |
1133 | |
1134 | void Workspace::slotDesktopCountChanged(uint previousCount, uint newCount) |
1135 | { |
1136 | Q_UNUSED(previousCount) |
1137 | Placement::self()->reinitCascading(0); |
1138 | |
1139 | resetClientAreas(newCount); |
1140 | } |
1141 | |
1142 | void Workspace::resetClientAreas(uint desktopCount) |
1143 | { |
1144 | // Make it +1, so that it can be accessed as [1..numberofdesktops] |
1145 | workarea.clear(); |
1146 | workarea.resize(desktopCount + 1); |
1147 | restrictedmovearea.clear(); |
1148 | restrictedmovearea.resize(desktopCount + 1); |
1149 | screenarea.clear(); |
1150 | |
1151 | updateClientArea(true); |
1152 | } |
1153 | |
1154 | /** |
1155 | * Sends client \a c to desktop \a desk. |
1156 | * |
1157 | * Takes care of transients as well. |
1158 | */ |
1159 | void Workspace::sendClientToDesktop(Client* c, int desk, bool dont_activate) |
1160 | { |
1161 | if ((desk < 1 && desk != NET::OnAllDesktops) || desk > static_cast<int>(VirtualDesktopManager::self()->count())) |
1162 | return; |
1163 | int old_desktop = c->desktop(); |
1164 | bool was_on_desktop = c->isOnDesktop(desk) || c->isOnAllDesktops(); |
1165 | c->setDesktop(desk); |
1166 | if (c->desktop() != desk) // No change or desktop forced |
1167 | return; |
1168 | desk = c->desktop(); // Client did range checking |
1169 | |
1170 | if (c->isOnDesktop(VirtualDesktopManager::self()->current())) { |
1171 | if (c->wantsTabFocus() && options->focusPolicyIsReasonable() && |
1172 | !was_on_desktop && // for stickyness changes |
1173 | !dont_activate) |
1174 | requestFocus(c); |
1175 | else |
1176 | restackClientUnderActive(c); |
1177 | } else |
1178 | raiseClient(c); |
1179 | |
1180 | c->checkWorkspacePosition( QRect(), old_desktop ); |
1181 | |
1182 | ClientList transients_stacking_order = ensureStackingOrder(c->transients()); |
1183 | for (ClientList::ConstIterator it = transients_stacking_order.constBegin(); |
1184 | it != transients_stacking_order.constEnd(); |
1185 | ++it) |
1186 | sendClientToDesktop(*it, desk, dont_activate); |
1187 | updateClientArea(); |
1188 | } |
1189 | |
1190 | /** |
1191 | * checks whether the X Window with the input focus is on our X11 screen |
1192 | * if the window cannot be determined or inspected, resturn depends on whether there's actually |
1193 | * more than one screen |
1194 | * |
1195 | * this is NOT in any way related to XRandR multiscreen |
1196 | * |
1197 | */ |
1198 | extern bool is_multihead; // main.cpp |
1199 | bool Workspace::isOnCurrentHead() |
1200 | { |
1201 | if (!is_multihead) { |
1202 | return true; |
1203 | } |
1204 | |
1205 | Xcb::CurrentInput currentInput; |
1206 | if (currentInput.window() == XCB_WINDOW_NONE) { |
1207 | return !is_multihead; |
1208 | } |
1209 | |
1210 | Xcb::WindowGeometry geometry(currentInput.window()); |
1211 | if (geometry.isNull()) { // should not happen |
1212 | return !is_multihead; |
1213 | } |
1214 | |
1215 | return rootWindow() == geometry->root; |
1216 | } |
1217 | |
1218 | void Workspace::sendClientToScreen(Client* c, int screen) |
1219 | { |
1220 | c->sendToScreen(screen); |
1221 | } |
1222 | |
1223 | void Workspace::sendPingToWindow(xcb_window_t window, xcb_timestamp_t timestamp) |
1224 | { |
1225 | rootInfo()->sendPing(window, timestamp); |
1226 | } |
1227 | |
1228 | void Workspace::sendTakeActivity(KWin::Client *c, xcb_timestamp_t timestamp, long int flags) |
1229 | { |
1230 | rootInfo()->takeActivity(c->window(), timestamp, flags); |
1231 | pending_take_activity = c; |
1232 | } |
1233 | |
1234 | /** |
1235 | * Delayed focus functions |
1236 | */ |
1237 | void Workspace::delayFocus() |
1238 | { |
1239 | requestFocus(delayfocus_client); |
1240 | cancelDelayFocus(); |
1241 | } |
1242 | |
1243 | void Workspace::requestDelayFocus(Client* c) |
1244 | { |
1245 | delayfocus_client = c; |
1246 | delete delayFocusTimer; |
1247 | delayFocusTimer = new QTimer(this); |
1248 | connect(delayFocusTimer, SIGNAL(timeout()), this, SLOT(delayFocus())); |
1249 | delayFocusTimer->setSingleShot(true); |
1250 | delayFocusTimer->start(options->delayFocusInterval()); |
1251 | } |
1252 | |
1253 | void Workspace::cancelDelayFocus() |
1254 | { |
1255 | delete delayFocusTimer; |
1256 | delayFocusTimer = 0; |
1257 | } |
1258 | |
1259 | bool Workspace::checkStartupNotification(xcb_window_t w, KStartupInfoId &id, KStartupInfoData &data) |
1260 | { |
1261 | return startup->checkStartup(w, id, data) == KStartupInfo::Match; |
1262 | } |
1263 | |
1264 | /** |
1265 | * Puts the focus on a dummy window |
1266 | * Just using XSetInputFocus() with None would block keyboard input |
1267 | */ |
1268 | void Workspace::focusToNull() |
1269 | { |
1270 | m_nullFocus->focus(); |
1271 | } |
1272 | |
1273 | void Workspace::setShowingDesktop(bool showing) |
1274 | { |
1275 | rootInfo()->setShowingDesktop(showing); |
1276 | showing_desktop = showing; |
1277 | ++block_showing_desktop; |
1278 | if (showing_desktop) { |
1279 | showing_desktop_clients.clear(); |
1280 | ++block_focus; |
1281 | ToplevelList cls = stackingOrder(); |
1282 | // Find them first, then minimize, otherwise transients may get minimized with the window |
1283 | // they're transient for |
1284 | for (ToplevelList::ConstIterator it = cls.constBegin(); |
1285 | it != cls.constEnd(); |
1286 | ++it) { |
1287 | Client *c = qobject_cast<Client*>(*it); |
1288 | if (!c) { |
1289 | continue; |
1290 | } |
1291 | if (c->isOnCurrentActivity() && c->isOnCurrentDesktop() && c->isShown(true) && !c->isSpecialWindow()) |
1292 | showing_desktop_clients.prepend(c); // Topmost first to reduce flicker |
1293 | } |
1294 | for (ClientList::ConstIterator it = showing_desktop_clients.constBegin(); |
1295 | it != showing_desktop_clients.constEnd(); |
1296 | ++it) |
1297 | (*it)->minimize(); |
1298 | --block_focus; |
1299 | if (Client* desk = findDesktop(true, VirtualDesktopManager::self()->current())) |
1300 | requestFocus(desk); |
1301 | } else { |
1302 | for (ClientList::ConstIterator it = showing_desktop_clients.constBegin(); |
1303 | it != showing_desktop_clients.constEnd(); |
1304 | ++it) |
1305 | (*it)->unminimize(); |
1306 | if (showing_desktop_clients.count() > 0) |
1307 | requestFocus(showing_desktop_clients.first()); |
1308 | showing_desktop_clients.clear(); |
1309 | } |
1310 | --block_showing_desktop; |
1311 | } |
1312 | |
1313 | /** |
1314 | * Following Kicker's behavior: |
1315 | * Changing a virtual desktop resets the state and shows the windows again. |
1316 | * Unminimizing a window resets the state but keeps the windows hidden (except |
1317 | * the one that was unminimized). |
1318 | * A new window resets the state and shows the windows again, with the new window |
1319 | * being active. Due to popular demand (#67406) by people who apparently |
1320 | * don't see a difference between "show desktop" and "minimize all", this is not |
1321 | * true if "showDesktopIsMinimizeAll" is set in kwinrc. In such case showing |
1322 | * a new window resets the state but doesn't show windows. |
1323 | */ |
1324 | void Workspace::resetShowingDesktop(bool keep_hidden) |
1325 | { |
1326 | if (block_showing_desktop > 0) |
1327 | return; |
1328 | rootInfo()->setShowingDesktop(false); |
1329 | showing_desktop = false; |
1330 | ++block_showing_desktop; |
1331 | if (!keep_hidden) { |
1332 | for (ClientList::ConstIterator it = showing_desktop_clients.constBegin(); |
1333 | it != showing_desktop_clients.constEnd(); |
1334 | ++it) |
1335 | (*it)->unminimize(); |
1336 | } |
1337 | showing_desktop_clients.clear(); |
1338 | --block_showing_desktop; |
1339 | } |
1340 | |
1341 | static bool pending_dfc = false; |
1342 | |
1343 | void Workspace::disableGlobalShortcutsForClient(bool disable) |
1344 | { |
1345 | if (global_shortcuts_disabled_for_client == disable) |
1346 | return; |
1347 | if (disable) |
1348 | pending_dfc = true; |
1349 | KGlobalSettings::self()->emitChange(KGlobalSettings::BlockShortcuts, disable); |
1350 | // KWin will get the kipc message too |
1351 | } |
1352 | |
1353 | void Workspace::slotBlockShortcuts(int data) |
1354 | { |
1355 | if (pending_dfc && data) { |
1356 | global_shortcuts_disabled_for_client = true; |
1357 | pending_dfc = false; |
1358 | } else { |
1359 | global_shortcuts_disabled_for_client = false; |
1360 | } |
1361 | // Update also Alt+LMB actions etc. |
1362 | for (ClientList::ConstIterator it = clients.constBegin(); |
1363 | it != clients.constEnd(); |
1364 | ++it) |
1365 | (*it)->updateMouseGrab(); |
1366 | } |
1367 | |
1368 | QString Workspace::supportInformation() const |
1369 | { |
1370 | QString support; |
1371 | |
1372 | support.append(ki18nc("Introductory text shown in the support information." , |
1373 | "KWin Support Information:\n" |
1374 | "The following information should be used when requesting support on e.g. http://forum.kde.org.\n" |
1375 | "It provides information about the currently running instance, which options are used,\n" |
1376 | "what OpenGL driver and which effects are running.\n" |
1377 | "Please post the information provided underneath this introductory text to a paste bin service\n" |
1378 | "like http://paste.kde.org instead of pasting into support threads.\n" ).toString()); |
1379 | support.append("\n==========================\n\n" ); |
1380 | // all following strings are intended for support. They need to be pasted to e.g forums.kde.org |
1381 | // it is expected that the support will happen in English language or that the people providing |
1382 | // help understand English. Because of that all texts are not translated |
1383 | support.append("Version\n" ); |
1384 | support.append("=======\n" ); |
1385 | support.append("KWin version: " ); |
1386 | support.append(KWIN_VERSION_STRING); |
1387 | support.append('\n'); |
1388 | support.append("KDE SC version (runtime): " ); |
1389 | support.append(KDE::versionString()); |
1390 | support.append('\n'); |
1391 | support.append("KDE SC version (compile): " ); |
1392 | support.append(KDE_VERSION_STRING); |
1393 | support.append('\n'); |
1394 | support.append("Qt Version: " ); |
1395 | support.append(qVersion()); |
1396 | support.append("\n\n" ); |
1397 | support.append("Options\n" ); |
1398 | support.append("=======\n" ); |
1399 | const QMetaObject *metaOptions = options->metaObject(); |
1400 | for (int i=0; i<metaOptions->propertyCount(); ++i) { |
1401 | const QMetaProperty property = metaOptions->property(i); |
1402 | if (QLatin1String(property.name()) == "objectName" ) { |
1403 | continue; |
1404 | } |
1405 | support.append(QLatin1String(property.name()) % ": " % options->property(property.name()).toString() % '\n'); |
1406 | } |
1407 | #ifdef KWIN_BUILD_SCREENEDGES |
1408 | support.append("\nScreen Edges\n" ); |
1409 | support.append( "============\n" ); |
1410 | const QMetaObject *metaScreenEdges = ScreenEdges::self()->metaObject(); |
1411 | for (int i=0; i<metaScreenEdges->propertyCount(); ++i) { |
1412 | const QMetaProperty property = metaScreenEdges->property(i); |
1413 | if (QLatin1String(property.name()) == "objectName" ) { |
1414 | continue; |
1415 | } |
1416 | support.append(QLatin1String(property.name()) % ": " % ScreenEdges::self()->property(property.name()).toString() % '\n'); |
1417 | } |
1418 | #endif |
1419 | support.append("\nScreens\n" ); |
1420 | support.append( "=======\n" ); |
1421 | support.append("Multi-Head: " ); |
1422 | if (is_multihead) { |
1423 | support.append("yes\n" ); |
1424 | support.append(QString("Head: %1\n" ).arg(screen_number)); |
1425 | } else { |
1426 | support.append("no\n" ); |
1427 | } |
1428 | support.append("Active screen follows mouse: " ); |
1429 | if (screens()->isCurrentFollowsMouse()) |
1430 | support.append(" yes\n" ); |
1431 | else |
1432 | support.append(" no\n" ); |
1433 | support.append(QString("Number of Screens: %1\n" ).arg(screens()->count())); |
1434 | for (int i=0; i<screens()->count(); ++i) { |
1435 | const QRect geo = screens()->geometry(i); |
1436 | support.append(QString("Screen %1 Geometry: %2,%3,%4x%5\n" ) |
1437 | .arg(i) |
1438 | .arg(geo.x()) |
1439 | .arg(geo.y()) |
1440 | .arg(geo.width()) |
1441 | .arg(geo.height())); |
1442 | } |
1443 | support.append("\nDecoration\n" ); |
1444 | support.append( "==========\n" ); |
1445 | support.append(decorationPlugin()->supportInformation()); |
1446 | support.append("\nCompositing\n" ); |
1447 | support.append( "===========\n" ); |
1448 | support.append("Qt Graphics System: " ); |
1449 | if (Extensions::nonNativePixmaps()) { |
1450 | support.append("raster\n" ); |
1451 | } else { |
1452 | support.append("native\n" ); |
1453 | } |
1454 | if (effects) { |
1455 | support.append("Compositing is active\n" ); |
1456 | switch (effects->compositingType()) { |
1457 | case OpenGL1Compositing: |
1458 | case OpenGL2Compositing: |
1459 | case OpenGLCompositing: { |
1460 | #ifdef KWIN_HAVE_OPENGLES |
1461 | support.append("Compositing Type: OpenGL ES 2.0\n" ); |
1462 | #else |
1463 | support.append("Compositing Type: OpenGL\n" ); |
1464 | #endif |
1465 | |
1466 | GLPlatform *platform = GLPlatform::instance(); |
1467 | support.append("OpenGL vendor string: " % platform->glVendorString() % '\n'); |
1468 | support.append("OpenGL renderer string: " % platform->glRendererString() % '\n'); |
1469 | support.append("OpenGL version string: " % platform->glVersionString() % '\n'); |
1470 | |
1471 | if (platform->supports(LimitedGLSL) || platform->supports(GLSL)) |
1472 | support.append("OpenGL shading language version string: " % platform->glShadingLanguageVersionString() % '\n'); |
1473 | |
1474 | support.append("Driver: " % GLPlatform::driverToString(platform->driver()) % '\n'); |
1475 | if (!platform->isMesaDriver()) |
1476 | support.append("Driver version: " % GLPlatform::versionToString(platform->driverVersion()) % '\n'); |
1477 | |
1478 | support.append("GPU class: " % GLPlatform::chipClassToString(platform->chipClass()) % '\n'); |
1479 | |
1480 | support.append("OpenGL version: " % GLPlatform::versionToString(platform->glVersion()) % '\n'); |
1481 | |
1482 | if (platform->supports(LimitedGLSL) || platform->supports(GLSL)) |
1483 | support.append("GLSL version: " % GLPlatform::versionToString(platform->glslVersion()) % '\n'); |
1484 | |
1485 | if (platform->isMesaDriver()) |
1486 | support.append("Mesa version: " % GLPlatform::versionToString(platform->mesaVersion()) % '\n'); |
1487 | if (platform->serverVersion() > 0) |
1488 | support.append("X server version: " % GLPlatform::versionToString(platform->serverVersion()) % '\n'); |
1489 | if (platform->kernelVersion() > 0) |
1490 | support.append("Linux kernel version: " % GLPlatform::versionToString(platform->kernelVersion()) % '\n'); |
1491 | |
1492 | support.append("Direct rendering: " ); |
1493 | if (platform->isDirectRendering()) { |
1494 | support.append("yes\n" ); |
1495 | } else { |
1496 | support.append("no\n" ); |
1497 | } |
1498 | support.append("Requires strict binding: " ); |
1499 | if (!platform->isLooseBinding()) { |
1500 | support.append("yes\n" ); |
1501 | } else { |
1502 | support.append("no\n" ); |
1503 | } |
1504 | support.append("GLSL shaders: " ); |
1505 | if (platform->supports(GLSL)) { |
1506 | if (platform->supports(LimitedGLSL)) { |
1507 | support.append(" limited\n" ); |
1508 | } else { |
1509 | support.append(" yes\n" ); |
1510 | } |
1511 | } else { |
1512 | support.append(" no\n" ); |
1513 | } |
1514 | support.append("Texture NPOT support: " ); |
1515 | if (platform->supports(TextureNPOT)) { |
1516 | if (platform->supports(LimitedNPOT)) { |
1517 | support.append(" limited\n" ); |
1518 | } else { |
1519 | support.append(" yes\n" ); |
1520 | } |
1521 | } else { |
1522 | support.append(" no\n" ); |
1523 | } |
1524 | support.append("Virtual Machine: " ); |
1525 | if (platform->isVirtualMachine()) { |
1526 | support.append(" yes\n" ); |
1527 | } else { |
1528 | support.append(" no\n" ); |
1529 | } |
1530 | |
1531 | if (effects->compositingType() == OpenGL2Compositing) { |
1532 | support.append("OpenGL 2 Shaders are used\n" ); |
1533 | } else { |
1534 | support.append("OpenGL 2 Shaders are not used. Legacy OpenGL 1.x code path is used.\n" ); |
1535 | } |
1536 | support.append("Painting blocks for vertical retrace: " ); |
1537 | if (m_compositor->scene()->blocksForRetrace()) |
1538 | support.append(" yes\n" ); |
1539 | else |
1540 | support.append(" no\n" ); |
1541 | break; |
1542 | } |
1543 | case XRenderCompositing: |
1544 | support.append("Compositing Type: XRender\n" ); |
1545 | break; |
1546 | case NoCompositing: |
1547 | default: |
1548 | support.append("Something is really broken, neither OpenGL nor XRender is used" ); |
1549 | } |
1550 | support.append("\nLoaded Effects:\n" ); |
1551 | support.append( "---------------\n" ); |
1552 | foreach (const QString &effect, static_cast<EffectsHandlerImpl*>(effects)->loadedEffects()) { |
1553 | support.append(effect % '\n'); |
1554 | } |
1555 | support.append("\nCurrently Active Effects:\n" ); |
1556 | support.append( "-------------------------\n" ); |
1557 | foreach (const QString &effect, static_cast<EffectsHandlerImpl*>(effects)->activeEffects()) { |
1558 | support.append(effect % '\n'); |
1559 | } |
1560 | support.append("\nEffect Settings:\n" ); |
1561 | support.append( "----------------\n" ); |
1562 | foreach (const QString &effect, static_cast<EffectsHandlerImpl*>(effects)->loadedEffects()) { |
1563 | support.append(static_cast<EffectsHandlerImpl*>(effects)->supportInformation(effect)); |
1564 | support.append('\n'); |
1565 | } |
1566 | } else { |
1567 | support.append("Compositing is not active\n" ); |
1568 | } |
1569 | return support; |
1570 | } |
1571 | |
1572 | void Workspace::slotToggleCompositing() |
1573 | { |
1574 | if (m_compositor) { |
1575 | m_compositor->slotToggleCompositing(); |
1576 | } |
1577 | } |
1578 | |
1579 | } // namespace |
1580 | |
1581 | #include "workspace.moc" |
1582 | |