/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ //#define QT_CLEAN_NAMESPACE #include "workspace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "client.h" #include "composite.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "atoms.h" #include "placement.h" #include "notifications.h" #include "outline.h" #include "group.h" #include "rules.h" #include "dbusinterface.h" #include "unmanaged.h" #include "deleted.h" #include "effects.h" #include "overlaywindow.h" #include "useractions.h" #include #include #ifdef KWIN_BUILD_SCRIPTING #include "scripting/scripting.h" #endif #include #include #include #include #include #include #include #include #include namespace KWin { extern int screen_number; extern bool is_multihead; static const int KWIN_MAX_NUMBER_DESKTOPS = 20; #ifdef KWIN_BUILD_KAPPMENU static const char *KDED_SERVICE = "org.kde.kded"; static const char *KDED_APPMENU_PATH = "/modules/appmenu"; static const char *KDED_INTERFACE = "org.kde.kded"; #endif Workspace* Workspace::_self = 0; //----------------------------------------------------------------------------- // Rikkus: This class is too complex. It needs splitting further. // It's a nightmare to understand, especially with so few comments :( // // Matthias: Feel free to ask me questions about it. Feel free to add // comments. I dissagree that further splittings makes it easier. 2500 // lines are not too much. It's the task that is complex, not the // code. //----------------------------------------------------------------------------- Workspace::Workspace(bool restore) : QObject(0) // Desktop layout , desktopCount_(0) // This is an invalid state , desktopGridSize_(1, 2) // Default to two rows , desktopGrid_(new int[2]) , currentDesktop_(0) #ifdef KWIN_BUILD_SCREENEDGES , m_screenEdgeOrientation(0) #endif , m_compositor(NULL) // Unsorted , active_popup(NULL) , active_popup_client(NULL) , temporaryRulesMessages("_KDE_NET_WM_TEMPORARY_RULES", NULL, false) , rules_updates_disabled(false) , active_client(0) , last_active_client(0) , most_recently_raised(0) , movingClient(0) , pending_take_activity(NULL) , active_screen(0) , delayfocus_client(0) , force_restacking(false) , x_stacking_dirty(true) , showing_desktop(false) , block_showing_desktop(0) , was_user_interaction(false) , session_saving(false) , block_focus(0) #ifdef KWIN_BUILD_TABBOX , tab_box(0) #endif , m_userActionsMenu(new UserActionsMenu(this)) , keys(0) , client_keys(NULL) , disable_shortcuts_keys(NULL) , client_keys_dialog(NULL) , client_keys_client(NULL) , global_shortcuts_disabled(false) , global_shortcuts_disabled_for_client(false) , workspaceInit(true) , startup(0) , set_active_client_recursion(0) , block_stacking_updates(0) , forced_global_mouse_grab(false) , m_scripting(NULL) { // If KWin was already running it saved its configuration after loosing the selection -> Reread QFuture reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration); #ifdef KWIN_BUILD_KAPPMENU QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.connect(KDED_SERVICE, KDED_APPMENU_PATH, KDED_INTERFACE, "showRequest", this, SLOT(slotShowRequest(qulonglong))); dbus.connect(KDED_SERVICE, KDED_APPMENU_PATH, KDED_INTERFACE, "menuAvailable", this, SLOT(slotMenuAvailable(qulonglong))); dbus.connect(KDED_SERVICE, KDED_APPMENU_PATH, KDED_INTERFACE, "menuHidden", this, SLOT(slotMenuHidden(qulonglong))); dbus.connect(KDED_SERVICE, KDED_APPMENU_PATH, KDED_INTERFACE, "clearMenus", this, SLOT(slotClearMenus())); #endif // Initialize desktop grid array desktopGrid_[0] = 0; desktopGrid_[1] = 0; _self = this; // first initialize the extensions Extensions::init(); // PluginMgr needs access to the config file, so we need to wait for it for finishing reparseConfigFuture.waitForFinished(); options->loadConfig(); options->loadCompositingConfig(false); mgr = new PluginMgr; QX11Info info; default_colormap = DefaultColormap(display(), info.screen()); installed_colormap = default_colormap; connect(&temporaryRulesMessages, SIGNAL(gotMessage(QString)), this, SLOT(gotTemporaryRulesMessage(QString))); connect(&rulesUpdatedTimer, SIGNAL(timeout()), this, SLOT(writeWindowRules())); updateXTime(); // Needed for proper initialization of user_time in Client ctor delayFocusTimer = 0; if (restore) loadSessionInfo(); loadWindowRules(); // Call this before XSelectInput() on the root window startup = new KStartupInfo( KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this); // Select windowmanager privileges XSelectInput(display(), rootWindow(), KeyPressMask | PropertyChangeMask | ColormapChangeMask | SubstructureRedirectMask | SubstructureNotifyMask | FocusChangeMask | // For NotifyDetailNone ExposureMask ); #ifdef KWIN_BUILD_TABBOX // need to create the tabbox before compositing scene is setup tab_box = new TabBox::TabBox(this); #endif m_compositor = Compositor::createCompositor(this); connect(this, SIGNAL(currentDesktopChanged(int,KWin::Client*)), m_compositor, SLOT(addRepaintFull())); connect(m_compositor, SIGNAL(compositingToggled(bool)), SLOT(slotCompositingToggled())); new DBusInterface(this); // Compatibility long data = 1; XChangeProperty( display(), rootWindow(), atoms->kwin_running, atoms->kwin_running, 32, PropModeAppend, (unsigned char*)(&data), 1 ); client_keys = new KActionCollection(this); m_outline = new Outline(); initShortcuts(); init(); connect(QApplication::desktop(), SIGNAL(screenCountChanged(int)), &screenChangedTimer, SLOT(start())); connect(QApplication::desktop(), SIGNAL(resized(int)), &screenChangedTimer, SLOT(start())); #ifdef KWIN_BUILD_ACTIVITIES connect(&activityController_, SIGNAL(currentActivityChanged(QString)), SLOT(updateCurrentActivity(QString))); connect(&activityController_, SIGNAL(activityRemoved(QString)), SLOT(slotActivityRemoved(QString))); connect(&activityController_, SIGNAL(activityRemoved(QString)), SIGNAL(activityRemoved(QString))); connect(&activityController_, SIGNAL(activityAdded(QString)), SLOT(slotActivityAdded(QString))); connect(&activityController_, SIGNAL(activityAdded(QString)), SIGNAL(activityAdded(QString))); connect(&activityController_, SIGNAL(currentActivityChanged(QString)), SIGNAL(currentActivityChanged(QString))); #endif connect(&screenChangedTimer, SIGNAL(timeout()), SLOT(screenChangeTimeout())); screenChangedTimer.setSingleShot(true); screenChangedTimer.setInterval(100); } void Workspace::screenChangeTimeout() { kDebug() << "It is time to call desktopResized"; desktopResized(); } void Workspace::init() { #ifdef KWIN_BUILD_SCREENEDGES m_screenEdge.init(); #endif // Not used yet //topDock = 0L; //maximizedWindowCounter = 0; supportWindow = new QWidget(NULL, Qt::X11BypassWindowManagerHint); XLowerWindow(display(), supportWindow->winId()); // See usage in layers.cpp XSetWindowAttributes attr; attr.override_redirect = 1; null_focus_window = XCreateWindow(display(), rootWindow(), -1, -1, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWOverrideRedirect, &attr); XMapWindow(display(), null_focus_window); unsigned long protocols[5] = { NET::Supported | NET::SupportingWMCheck | NET::ClientList | NET::ClientListStacking | NET::DesktopGeometry | NET::NumberOfDesktops | NET::CurrentDesktop | NET::ActiveWindow | NET::WorkArea | NET::CloseWindow | NET::DesktopNames | NET::WMName | NET::WMVisibleName | NET::WMDesktop | NET::WMWindowType | NET::WMState | NET::WMStrut | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMMoveResize | NET::WMFrameExtents | NET::WMPing , NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::UtilityMask | NET::SplashMask | // No compositing window types here unless we support them also as managed window types 0 , NET::Modal | //NET::Sticky | // Large desktops not supported (and probably never will be) NET::MaxVert | NET::MaxHoriz | NET::Shaded | NET::SkipTaskbar | NET::KeepAbove | //NET::StaysOnTop | // The same like KeepAbove NET::SkipPager | NET::Hidden | NET::FullScreen | NET::KeepBelow | NET::DemandsAttention | 0 , NET::WM2UserTime | NET::WM2StartupId | NET::WM2AllowedActions | NET::WM2RestackWindow | NET::WM2MoveResizeWindow | NET::WM2ExtendedStrut | NET::WM2KDETemporaryRules | NET::WM2ShowingDesktop | NET::WM2DesktopLayout | NET::WM2FullPlacement | NET::WM2FullscreenMonitors | NET::WM2KDEShadow | 0 , NET::ActionMove | NET::ActionResize | NET::ActionMinimize | NET::ActionShade | //NET::ActionStick | // Sticky state is not supported NET::ActionMaxVert | NET::ActionMaxHoriz | NET::ActionFullScreen | NET::ActionChangeDesktop | NET::ActionClose | 0 , }; if (hasDecorationPlugin() && mgr->factory()->supports(AbilityExtendIntoClientArea)) protocols[ NETRootInfo::PROTOCOLS2 ] |= NET::WM2FrameOverlap; QX11Info info; rootInfo = new RootInfo(this, display(), supportWindow->winId(), "KWin", protocols, 5, info.screen()); // Create an entry with empty activity name, it will be used if activities are not supported. Otherwise, it will be removed. m_desktopFocusChain = m_activitiesDesktopFocusChain.insert(QString(), QVector(numberOfDesktops())); // Now we know how many desktops we'll have, thus we initialize the positioning object initPositioning = new Placement(this); loadDesktopSettings(); updateDesktopLayout(); // Extra NETRootInfo instance in Client mode is needed to get the values of the properties NETRootInfo client_info(display(), NET::ActiveWindow | NET::CurrentDesktop); int initial_desktop; if (!kapp->isSessionRestored()) initial_desktop = client_info.currentDesktop(); else { KConfigGroup group(kapp->sessionConfig(), "Session"); initial_desktop = group.readEntry("desktop", 1); } if (!setCurrentDesktop(initial_desktop)) setCurrentDesktop(1); #ifdef KWIN_BUILD_ACTIVITIES updateActivityList(false, true); #endif reconfigureTimer.setSingleShot(true); updateToolWindowsTimer.setSingleShot(true); connect(&reconfigureTimer, SIGNAL(timeout()), this, SLOT(slotReconfigure())); connect(&updateToolWindowsTimer, SIGNAL(timeout()), this, SLOT(slotUpdateToolWindows())); connect(KGlobalSettings::self(), SIGNAL(appearanceChanged()), this, SLOT(reconfigure())); connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), this, SLOT(slotSettingsChanged(int))); connect(KGlobalSettings::self(), SIGNAL(blockShortcuts(int)), this, SLOT(slotBlockShortcuts(int))); active_client = NULL; rootInfo->setActiveWindow(None); focusToNull(); if (!kapp->isSessionRestored()) ++block_focus; // Because it will be set below { // Begin updates blocker block StackingUpdatesBlocker blocker(this); bool fixoffset = KCmdLineArgs::parsedArgs()->getOption("crashes").toInt() > 0; xcb_connection_t *conn = connection(); xcb_query_tree_reply_t *tree = xcb_query_tree_reply(conn, xcb_query_tree_unchecked(conn, rootWindow()), 0); xcb_window_t *wins = xcb_query_tree_children(tree); QVector attr_cookies; QVector geom_cookies; attr_cookies.reserve(tree->children_len); geom_cookies.reserve(tree->children_len); // Request the attributes and geometries of all toplevel windows for (int i = 0; i < tree->children_len; i++) { attr_cookies << xcb_get_window_attributes(conn, wins[i]); geom_cookies << xcb_get_geometry(conn, wins[i]); } // Get the replies for (int i = 0; i < tree->children_len; i++) { ScopedCPointer attr = xcb_get_window_attributes_reply(conn, attr_cookies[i], 0); ScopedCPointer geometry = xcb_get_geometry_reply(conn, geom_cookies[i], 0); if (!attr || !geometry) continue; if (attr->override_redirect) { if (attr->map_state == XCB_MAP_STATE_VIEWABLE && attr->_class != XCB_WINDOW_CLASS_INPUT_ONLY) // ### This will request the attributes again createUnmanaged(wins[i]); } else if (attr->map_state != XCB_MAP_STATE_UNMAPPED) { if (fixoffset) fixPositionAfterCrash(wins[i], geometry); // ### This will request the attributes again createClient(wins[i], true); } } free(tree); // Propagate clients, will really happen at the end of the updates blocker block updateStackingOrder(true); saveOldScreenSizes(); updateClientArea(); // NETWM spec says we have to set it to (0,0) if we don't support it NETPoint* viewports = new NETPoint[numberOfDesktops()]; rootInfo->setDesktopViewport(numberOfDesktops(), *viewports); delete[] viewports; QRect geom; for (int i = 0; i < QApplication::desktop()->screenCount(); i++) { geom |= QApplication::desktop()->screenGeometry(i); } NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo->setDesktopGeometry(-1, desktop_geometry); setShowingDesktop(false); } // End updates blocker block Client* new_active_client = NULL; if (!kapp->isSessionRestored()) { --block_focus; new_active_client = findClient(WindowMatchPredicate(client_info.activeWindow())); } if (new_active_client == NULL && activeClient() == NULL && should_get_focus.count() == 0) { // No client activated in manage() if (new_active_client == NULL) new_active_client = topClientOnDesktop(currentDesktop(), -1); if (new_active_client == NULL && !desktops.isEmpty()) new_active_client = findDesktop(true, currentDesktop()); } if (new_active_client != NULL) activateClient(new_active_client); #ifdef KWIN_BUILD_SCRIPTING m_scripting = new Scripting(this); #endif // SELI TODO: This won't work with unreasonable focus policies, // and maybe in rare cases also if the selected client doesn't // want focus workspaceInit = false; // broadcast that Workspace is ready, but first process all events. QMetaObject::invokeMethod(this, "workspaceInitialized", Qt::QueuedConnection); // TODO: ungrabXServer() } Workspace::~Workspace() { delete m_compositor; m_compositor = NULL; blockStackingUpdates(true); // TODO: grabXServer(); // Use stacking_order, so that kwin --replace keeps stacking order for (ToplevelList::iterator it = stacking_order.begin(), end = stacking_order.end(); it != end; ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } // Only release the window c->releaseWindow(true); // No removeClient() is called, it does more than just removing. // However, remove from some lists to e.g. prevent performTransiencyCheck() // from crashing. clients.removeAll(c); desktops.removeAll(c); } for (UnmanagedList::iterator it = unmanaged.begin(), end = unmanaged.end(); it != end; ++it) (*it)->release(true); delete m_outline; XDeleteProperty(display(), rootWindow(), atoms->kwin_running); writeWindowRules(); KGlobal::config()->sync(); delete rootInfo; delete supportWindow; delete mgr; delete startup; delete initPositioning; delete client_keys_dialog; while (!rules.isEmpty()) { delete rules.front(); rules.pop_front(); } foreach (SessionInfo * s, session) delete s; XDestroyWindow(display(), null_focus_window); // TODO: ungrabXServer(); delete[] desktopGrid_; _self = 0; } Client* Workspace::createClient(Window w, bool is_mapped) { StackingUpdatesBlocker blocker(this); Client* c = new Client(this); connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); connect(c, SIGNAL(activeChanged()), m_compositor, SLOT(checkUnredirect())); connect(c, SIGNAL(fullScreenChanged()), m_compositor, SLOT(checkUnredirect())); connect(c, SIGNAL(geometryChanged()), m_compositor, SLOT(checkUnredirect())); connect(c, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), m_compositor, SLOT(checkUnredirect())); connect(c, SIGNAL(blockingCompositingChanged(KWin::Client*)), m_compositor, SLOT(updateCompositeBlocking(KWin::Client*))); if (!c->manage(w, is_mapped)) { Client::deleteClient(c, Allowed); return NULL; } addClient(c, Allowed); return c; } Unmanaged* Workspace::createUnmanaged(Window w) { if (m_compositor && m_compositor->checkForOverlayWindow(w)) return NULL; Unmanaged* c = new Unmanaged(this); if (!c->track(w)) { Unmanaged::deleteUnmanaged(c, Allowed); return NULL; } connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); addUnmanaged(c, Allowed); emit unmanagedAdded(c); return c; } void Workspace::addClient(Client* c, allowed_t) { Group* grp = findGroup(c->window()); KWindowInfo info = KWindowSystem::windowInfo(c->window(), -1U, NET::WM2WindowClass); emit clientAdded(c); if (grp != NULL) grp->gotLeader(c); if (c->isDesktop()) { desktops.append(c); if (active_client == NULL && should_get_focus.isEmpty() && c->isOnCurrentDesktop()) requestFocus(c); // TODO: Make sure desktop is active after startup if there's no other window active } else { updateFocusChains(c, FocusChainUpdate); // Add to focus chain if not already there clients.append(c); } if (!unconstrained_stacking_order.contains(c)) unconstrained_stacking_order.append(c); // Raise if it hasn't got any stacking position yet if (!stacking_order.contains(c)) // It'll be updated later, and updateToolWindows() requires stacking_order.append(c); // c to be in stacking_order x_stacking_dirty = true; updateClientArea(); // This cannot be in manage(), because the client got added only now updateClientLayer(c); if (c->isDesktop()) { raiseClient(c); // If there's no active client, make this desktop the active one if (activeClient() == NULL && should_get_focus.count() == 0) activateClient(findDesktop(true, currentDesktop())); } c->checkActiveModal(); checkTransients(c->window()); // SELI TODO: Does this really belong here? updateStackingOrder(true); // Propagate new client if (c->isUtility() || c->isMenu() || c->isToolbar()) updateToolWindows(true); checkNonExistentClients(); #ifdef KWIN_BUILD_TABBOX if (tabBox()->isDisplayed()) tab_box->reset(true); #endif #ifdef KWIN_BUILD_KAPPMENU if (m_windowsMenu.removeOne(c->window())) c->setAppMenuAvailable(); #endif } void Workspace::addUnmanaged(Unmanaged* c, allowed_t) { unmanaged.append(c); x_stacking_dirty = true; } /** * Destroys the client \a c */ void Workspace::removeClient(Client* c, allowed_t) { emit clientRemoved(c); if (c == active_popup_client) closeActivePopup(); if (m_userActionsMenu->isMenuClient(c)) { m_userActionsMenu->close(); } c->untab(QRect(), true); if (client_keys_client == c) setupWindowShortcutDone(false); if (!c->shortcut().isEmpty()) { c->setShortcut(QString()); // Remove from client_keys clientShortcutUpdated(c); // Needed, since this is otherwise delayed by setShortcut() and wouldn't run } if (c->isDialog()) Notify::raise(Notify::TransDelete); if (c->isNormalWindow()) Notify::raise(Notify::Delete); #ifdef KWIN_BUILD_TABBOX if (tabBox()->isDisplayed() && tabBox()->currentClient() == c) tab_box->nextPrev(true); #endif Q_ASSERT(clients.contains(c) || desktops.contains(c)); // TODO: if marked client is removed, notify the marked list clients.removeAll(c); desktops.removeAll(c); x_stacking_dirty = true; for (int i = 1; i <= numberOfDesktops(); ++i) focus_chain[i].removeAll(c); global_focus_chain.removeAll(c); attention_chain.removeAll(c); showing_desktop_clients.removeAll(c); Group* group = findGroup(c->window()); if (group != NULL) group->lostLeader(); if (c == most_recently_raised) most_recently_raised = 0; should_get_focus.removeAll(c); Q_ASSERT(c != active_client); if (c == last_active_client) last_active_client = 0; if (c == pending_take_activity) pending_take_activity = NULL; if (c == delayfocus_client) cancelDelayFocus(); updateStackingOrder(true); if (m_compositor) { m_compositor->updateCompositeBlocking(); } #ifdef KWIN_BUILD_TABBOX if (tabBox()->isDisplayed()) tab_box->reset(true); #endif updateClientArea(); } void Workspace::removeUnmanaged(Unmanaged* c, allowed_t) { assert(unmanaged.contains(c)); unmanaged.removeAll(c); x_stacking_dirty = true; } void Workspace::addDeleted(Deleted* c, Toplevel *orig, allowed_t) { assert(!deleted.contains(c)); deleted.append(c); const int unconstraintedIndex = unconstrained_stacking_order.indexOf(orig); if (unconstraintedIndex != -1) { unconstrained_stacking_order.replace(unconstraintedIndex, c); } else { unconstrained_stacking_order.append(c); } const int index = stacking_order.indexOf(orig); if (index != -1) { stacking_order.replace(index, c); } else { stacking_order.append(c); } x_stacking_dirty = true; connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); } void Workspace::removeDeleted(Deleted* c, allowed_t) { assert(deleted.contains(c)); emit deletedRemoved(c); deleted.removeAll(c); unconstrained_stacking_order.removeAll(c); stacking_order.removeAll(c); x_stacking_dirty = true; } void Workspace::updateFocusChains(Client* c, FocusChainChange change) { if (!c->wantsTabFocus()) { // Doesn't want tab focus, remove for (int i = 1; i <= numberOfDesktops(); ++i) focus_chain[i].removeAll(c); global_focus_chain.removeAll(c); return; } if (c->desktop() == NET::OnAllDesktops) { // Now on all desktops, add it to focus_chains it is not already in for (int i = 1; i <= numberOfDesktops(); i++) { // Making first/last works only on current desktop, don't affect all desktops if (i == currentDesktop() && (change == FocusChainMakeFirst || change == FocusChainMakeLast)) { focus_chain[i].removeAll(c); if (change == FocusChainMakeFirst) focus_chain[i].append(c); else focus_chain[i].prepend(c); } else if (!focus_chain[i].contains(c)) { // Add it after the active one if (active_client != NULL && active_client != c && !focus_chain[i].isEmpty() && focus_chain[i].last() == active_client) focus_chain[i].insert(focus_chain[i].size() - 1, c); else focus_chain[i].append(c); // Otherwise add as the first one } } } else { // Now only on desktop, remove it anywhere else for (int i = 1; i <= numberOfDesktops(); i++) { if (i == c->desktop()) { if (change == FocusChainMakeFirst) { focus_chain[i].removeAll(c); focus_chain[i].append(c); } else if (change == FocusChainMakeLast) { focus_chain[i].removeAll(c); focus_chain[i].prepend(c); } else if (!focus_chain[i].contains(c)) { // Add it after the active one if (active_client != NULL && active_client != c && !focus_chain[i].isEmpty() && focus_chain[i].last() == active_client) focus_chain[i].insert(focus_chain[i].size() - 1, c); else focus_chain[i].append(c); // Otherwise add as the first one } } else focus_chain[i].removeAll(c); } } if (change == FocusChainMakeFirst) { global_focus_chain.removeAll(c); global_focus_chain.append(c); } else if (change == FocusChainMakeLast) { global_focus_chain.removeAll(c); global_focus_chain.prepend(c); } else if (!global_focus_chain.contains(c)) { // Add it after the active one if (active_client != NULL && active_client != c && !global_focus_chain.isEmpty() && global_focus_chain.last() == active_client) global_focus_chain.insert(global_focus_chain.size() - 1, c); else global_focus_chain.append(c); // Otherwise add as the first one } } void Workspace::updateToolWindows(bool also_hide) { // TODO: What if Client's transiency/group changes? should this be called too? (I'm paranoid, am I not?) if (!options->isHideUtilityWindowsForInactive()) { for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) if (!(*it)->tabGroup() || (*it)->tabGroup()->current() == *it) (*it)->hideClient(false); return; } const Group* group = NULL; const Client* client = active_client; // Go up in transiency hiearchy, if the top is found, only tool transients for the top mainwindow // will be shown; if a group transient is group, all tools in the group will be shown while (client != NULL) { if (!client->isTransient()) break; if (client->groupTransient()) { group = client->group(); break; } client = client->transientFor(); } // Use stacking order only to reduce flicker, it doesn't matter if block_stacking_updates == 0, // I.e. if it's not up to date // SELI TODO: But maybe it should - what if a new client has been added that's not in stacking order yet? ClientList to_show, to_hide; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (c->isUtility() || c->isMenu() || c->isToolbar()) { bool show = true; if (!c->isTransient()) { if (c->group()->members().count() == 1) // Has its own group, keep always visible show = true; else if (client != NULL && c->group() == client->group()) show = true; else show = false; } else { if (group != NULL && c->group() == group) show = true; else if (client != NULL && client->hasTransient(c, true)) show = true; else show = false; } if (!show && also_hide) { const ClientList mainclients = c->mainClients(); // Don't hide utility windows which are standalone(?) or // have e.g. kicker as mainwindow if (mainclients.isEmpty()) show = true; for (ClientList::ConstIterator it2 = mainclients.constBegin(); it2 != mainclients.constEnd(); ++it2) { if (c->isSpecialWindow()) show = true; } if (!show) to_hide.append(c); } if (show) to_show.append(c); } } // First show new ones, then hide for (int i = to_show.size() - 1; i >= 0; --i) // From topmost // TODO: Since this is in stacking order, the order of taskbar entries changes :( to_show.at(i)->hideClient(false); if (also_hide) { for (ClientList::ConstIterator it = to_hide.constBegin(); it != to_hide.constEnd(); ++it) // From bottommost (*it)->hideClient(true); updateToolWindowsTimer.stop(); } else // setActiveClient() is after called with NULL client, quickly followed // by setting a new client, which would result in flickering resetUpdateToolWindowsTimer(); } void Workspace::resetUpdateToolWindowsTimer() { updateToolWindowsTimer.start(200); } void Workspace::slotUpdateToolWindows() { updateToolWindows(true); } /** * Updates the current colormap according to the currently active client */ void Workspace::updateColormap() { Colormap cmap = default_colormap; if (activeClient() && activeClient()->colormap() != None) cmap = activeClient()->colormap(); if (cmap != installed_colormap) { XInstallColormap(display(), cmap); installed_colormap = cmap; } } void Workspace::slotReloadConfig() { reconfigure(); } #ifdef KWIN_BUILD_KAPPMENU void Workspace::slotShowRequest(qulonglong wid) { if (Client *c = findClient(WindowMatchPredicate(wid))) c->emitShowRequest(); } void Workspace::slotMenuAvailable(qulonglong wid) { if (Client *c = findClient(WindowMatchPredicate(wid))) c->setAppMenuAvailable(); else m_windowsMenu.append(wid); } void Workspace::slotMenuHidden(qulonglong wid) { if (Client *c = findClient(WindowMatchPredicate(wid))) c->emitMenuHidden(); } void Workspace::slotClearMenus() { foreach (Client *c, clients) { c->setAppMenuUnavailable(); } } #endif void Workspace::reconfigure() { reconfigureTimer.start(200); } /** * This D-Bus call is used by the compositing kcm. Since the reconfigure() * D-Bus call delays the actual reconfiguring, it is not possible to immediately * call compositingActive(). Therefore the kcm will instead call this to ensure * the reconfiguring has already happened. */ bool Workspace::waitForCompositingSetup() { if (reconfigureTimer.isActive()) { reconfigureTimer.stop(); slotReconfigure(); } if (m_compositor) { return m_compositor->isActive(); } return false; } void Workspace::slotSettingsChanged(int category) { kDebug(1212) << "Workspace::slotSettingsChanged()"; if (category == KGlobalSettings::SETTINGS_SHORTCUTS) m_userActionsMenu->discard(); } /** * Reread settings */ KWIN_PROCEDURE(CheckBorderSizesProcedure, Client, cl->checkBorderSizes(true)); void Workspace::slotReconfigure() { kDebug(1212) << "Workspace::slotReconfigure()"; reconfigureTimer.stop(); #ifdef KWIN_BUILD_SCREENEDGES m_screenEdge.reserveActions(false); if (options->electricBorders() == Options::ElectricAlways) m_screenEdge.reserveDesktopSwitching(false, m_screenEdgeOrientation); #endif bool borderlessMaximizedWindows = options->borderlessMaximizedWindows(); KGlobal::config()->reparseConfiguration(); unsigned long changed = options->updateSettings(); emit configChanged(); m_userActionsMenu->discard(); updateToolWindows(true); if (hasDecorationPlugin() && mgr->reset(changed)) { // Decorations need to be recreated // This actually seems to make things worse now //QWidget curtain; //curtain.setBackgroundMode( NoBackground ); //curtain.setGeometry( Kephal::ScreenUtils::desktopGeometry() ); //curtain.show(); for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->updateDecoration(true, true); // If the new decoration doesn't supports tabs then ungroup clients if (!decorationSupportsTabbing()) { foreach (Client * c, clients) c->untab(); } mgr->destroyPreviousPlugin(); } else { forEachClient(CheckBorderSizesProcedure()); foreach (Client * c, clients) c->triggerDecorationRepaint(); } #ifdef KWIN_BUILD_SCREENEDGES m_screenEdge.reserveActions(true); if (options->electricBorders() == Options::ElectricAlways) { QSize desktopMatrix = rootInfo->desktopLayoutColumnsRows(); m_screenEdgeOrientation = 0; if (desktopMatrix.width() > 1) m_screenEdgeOrientation |= Qt::Horizontal; if (desktopMatrix.height() > 1) m_screenEdgeOrientation |= Qt::Vertical; m_screenEdge.reserveDesktopSwitching(true, m_screenEdgeOrientation); } m_screenEdge.update(); #endif loadWindowRules(); for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) { (*it)->setupWindowRules(true); (*it)->applyWindowRules(); discardUsedWindowRules(*it, false); } if (borderlessMaximizedWindows != options->borderlessMaximizedWindows() && !options->borderlessMaximizedWindows()) { // in case borderless maximized windows option changed and new option // is to have borders, we need to unset the borders for all maximized windows for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) { if ((*it)->maximizeMode() == MaximizeFull) (*it)->checkNoBorder(); } } if (hasDecorationPlugin()) { rootInfo->setSupported(NET::WM2FrameOverlap, mgr->factory()->supports(AbilityExtendIntoClientArea)); } else { rootInfo->setSupported(NET::WM2FrameOverlap, false); } } static bool _loading_desktop_settings = false; void Workspace::loadDesktopSettings() { _loading_desktop_settings = true; KSharedConfig::Ptr c = KGlobal::config(); QString groupname; if (screen_number == 0) groupname = "Desktops"; else groupname.sprintf("Desktops-screen-%d", screen_number); KConfigGroup group(c, groupname); const int n = group.readEntry("Number", 1); setNumberOfDesktops(n); for (int i = 1; i <= n; i++) { QString s = group.readEntry(QString("Name_%1").arg(i), i18n("Desktop %1", i)); rootInfo->setDesktopName(i, s.toUtf8().data()); m_desktopFocusChain.value()[i-1] = i; } int rows = group.readEntry("Rows", 2); rows = qBound(1, rows, n); // avoid weird cases like having 3 rows for 4 desktops, where the last row is unused int columns = n / rows; if (n % rows > 0) { columns++; } rootInfo->setDesktopLayout(NET::OrientationHorizontal, columns, rows, NET::DesktopLayoutCornerTopLeft); rootInfo->activate(); _loading_desktop_settings = false; } void Workspace::saveDesktopSettings() { if (_loading_desktop_settings) return; KSharedConfig::Ptr c = KGlobal::config(); QString groupname; if (screen_number == 0) groupname = "Desktops"; else groupname.sprintf("Desktops-screen-%d", screen_number); KConfigGroup group(c, groupname); group.writeEntry("Number", numberOfDesktops()); for (int i = 1; i <= numberOfDesktops(); i++) { QString s = desktopName(i); QString defaultvalue = i18n("Desktop %1", i); if (s.isEmpty()) { s = defaultvalue; rootInfo->setDesktopName(i, s.toUtf8().data()); } if (s != defaultvalue) { group.writeEntry(QString("Name_%1").arg(i), s); } else { QString currentvalue = group.readEntry(QString("Name_%1").arg(i), QString()); if (currentvalue != defaultvalue) group.writeEntry(QString("Name_%1").arg(i), ""); } } // Save to disk group.sync(); } /** * Avoids managing a window with title \a title */ void Workspace::doNotManage(const QString& title) { doNotManageList.append(title); } /** * Hack for java applets */ bool Workspace::isNotManaged(const QString& title) { for (QStringList::Iterator it = doNotManageList.begin(); it != doNotManageList.end(); ++it) { QRegExp r((*it)); if (r.indexIn(title) != -1) { doNotManageList.erase(it); return true; } } return false; } /** * During virt. desktop switching, desktop areas covered by windows that are * going to be hidden are first obscured by new windows with no background * ( i.e. transparent ) placed right below the windows. These invisible windows * are removed after the switch is complete. * Reduces desktop ( wallpaper ) repaints during desktop switching */ class ObscuringWindows { public: ~ObscuringWindows(); void create(Client* c); private: QList obscuring_windows; static QList* cached; static unsigned int max_cache_size; }; QList* ObscuringWindows::cached = 0; unsigned int ObscuringWindows::max_cache_size = 0; void ObscuringWindows::create(Client* c) { if (cached == 0) cached = new QList; Window obs_win; XWindowChanges chngs; int mask = CWSibling | CWStackMode; if (cached->count() > 0) { cached->removeAll(obs_win = cached->first()); chngs.x = c->x(); chngs.y = c->y(); chngs.width = c->width(); chngs.height = c->height(); mask |= CWX | CWY | CWWidth | CWHeight; } else { XSetWindowAttributes a; a.background_pixmap = None; a.override_redirect = True; obs_win = XCreateWindow(display(), rootWindow(), c->x(), c->y(), c->width(), c->height(), 0, CopyFromParent, InputOutput, CopyFromParent, CWBackPixmap | CWOverrideRedirect, &a); } chngs.sibling = c->frameId(); chngs.stack_mode = Below; XConfigureWindow(display(), obs_win, mask, &chngs); XMapWindow(display(), obs_win); obscuring_windows.append(obs_win); } ObscuringWindows::~ObscuringWindows() { max_cache_size = qMax(int(max_cache_size), obscuring_windows.count() + 4) - 1; for (QList::ConstIterator it = obscuring_windows.constBegin(); it != obscuring_windows.constEnd(); ++it) { XUnmapWindow(display(), *it); if (cached->count() < int(max_cache_size)) cached->prepend(*it); else XDestroyWindow(display(), *it); } } /** * Sets the current desktop to \a new_desktop * * Shows/Hides windows according to the stacking order and finally * propages the new desktop to the world */ bool Workspace::setCurrentDesktop(int new_desktop) { if (new_desktop < 1 || new_desktop > numberOfDesktops()) return false; closeActivePopup(); ++block_focus; // TODO: Q_ASSERT( block_stacking_updates == 0 ); // Make sure stacking_order is up to date StackingUpdatesBlocker blocker(this); int old_desktop = currentDesktop(); int old_active_screen = activeScreen(); if (new_desktop != currentDesktop()) { ++block_showing_desktop; // Optimized Desktop switching: unmapping done from back to front // mapping done from front to back => less exposure events Notify::raise((Notify::Event)(Notify::DesktopChange + new_desktop)); ObscuringWindows obs_wins; currentDesktop_ = new_desktop; // Change the desktop (so that Client::updateVisibility() works) for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (!c->isOnDesktop(new_desktop) && c != movingClient && c->isOnCurrentActivity()) { if (c->isShown(true) && c->isOnDesktop(old_desktop) && !compositing()) obs_wins.create(c); (c)->updateVisibility(); } } // Now propagate the change, after hiding, before showing rootInfo->setCurrentDesktop(currentDesktop()); if (movingClient && !movingClient->isOnDesktop(new_desktop)) { movingClient->setDesktop(new_desktop); } for (int i = stacking_order.size() - 1; i >= 0 ; --i) { Client *c = qobject_cast(stacking_order.at(i)); if (!c) { continue; } if (c->isOnDesktop(new_desktop) && c->isOnCurrentActivity()) c->updateVisibility(); } --block_showing_desktop; if (showingDesktop()) // Do this only after desktop change to avoid flicker resetShowingDesktop(false); } // Restore the focus on this desktop --block_focus; Client* c = 0; if (options->focusPolicyIsReasonable()) { // Search in focus chain if (movingClient != NULL && active_client == movingClient && focus_chain[currentDesktop()].contains(active_client) && active_client->isShown(true) && active_client->isOnCurrentDesktop()) c = active_client; // The requestFocus below will fail, as the client is already active // from actiavtion.cpp if (!c && options->isNextFocusPrefersMouse()) { ToplevelList::const_iterator it = stackingOrder().constEnd(); while (it != stackingOrder().constBegin()) { Client *client = qobject_cast(*(--it)); if (!client) { continue; } if (!(client->isShown(false) && client->isOnDesktop(new_desktop) && client->isOnCurrentActivity() && client->isOnScreen(activeScreen()))) continue; if (client->geometry().contains(QCursor::pos())) { if (!client->isDesktop()) c = client; break; // unconditional break - we do not pass the focus to some client below an unusable one } } } if (!c) { for (int i = focus_chain[currentDesktop()].size() - 1; i >= 0; --i) { Client* tmp = focus_chain[currentDesktop()].at(i); if (tmp->isShown(false) && tmp->isOnCurrentActivity() && ( !options->isSeparateScreenFocus() || tmp->screen() == old_active_screen )) { c = tmp; break; } } } } // If "unreasonable focus policy" and active_client is on_all_desktops and // under mouse (Hence == old_active_client), conserve focus. // (Thanks to Volker Schatz ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop()) c = active_client; if (c == NULL && !desktops.isEmpty()) c = findDesktop(true, currentDesktop()); if (c != active_client) setActiveClient(NULL, Allowed); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, currentDesktop())); else focusToNull(); // Update focus chain: // If input: chain = { 1, 2, 3, 4 } and currentDesktop() = 3, // Output: chain = { 3, 1, 2, 4 }. //kDebug(1212) << QString("Switching to desktop #%1, at focus_chain index %2\n") // .arg(currentDesktop()).arg(desktop_focus_chain.find( currentDesktop() )); QVector &chain = m_desktopFocusChain.value(); for (int i = chain.indexOf(currentDesktop()); i > 0; --i) chain[i] = chain[i-1]; chain[0] = currentDesktop(); //QString s = "desktop_focus_chain[] = { "; //for ( uint i = 0; i < desktop_focus_chain.size(); i++ ) // s += QString::number( desktop_focus_chain[i] ) + ", "; //kDebug( 1212 ) << s << "}\n"; emit currentDesktopChanged(old_desktop, movingClient); return true; } int Workspace::maxNumberOfDesktops() const { return KWIN_MAX_NUMBER_DESKTOPS; } #ifdef KWIN_BUILD_ACTIVITIES //BEGIN threaded activity list fetching typedef QPair AssignedList; typedef QPair CurrentAndList; static AssignedList fetchActivityList(KActivities::Controller *controller, QStringList *target, bool running) // could be member function, but actually it's much simpler this way { return AssignedList(target, running ? controller->listActivities(KActivities::Info::Running) : controller->listActivities()); } static CurrentAndList fetchActivityListAndCurrent(KActivities::Controller *controller) { QStringList l = controller->listActivities(); QString c = controller->currentActivity(); return CurrentAndList(c, l); } void Workspace::updateActivityList(bool running, bool updateCurrent, QObject *target, QString slot) { if (updateCurrent) { QFutureWatcher* watcher = new QFutureWatcher; connect( watcher, SIGNAL(finished()), SLOT(handleActivityReply()) ); if (!slot.isEmpty()) { watcher->setProperty("activityControllerCallback", slot); // "activity reply trigger" watcher->setProperty("activityControllerCallbackTarget", qVariantFromValue((void*)target)); } watcher->setFuture(QtConcurrent::run(fetchActivityListAndCurrent, &activityController_ )); } else { QFutureWatcher* watcher = new QFutureWatcher; connect(watcher, SIGNAL(finished()), SLOT(handleActivityReply())); if (!slot.isEmpty()) { watcher->setProperty("activityControllerCallback", slot); // "activity reply trigger" watcher->setProperty("activityControllerCallbackTarget", qVariantFromValue((void*)target)); } QStringList *target = running ? &openActivities_ : &allActivities_; watcher->setFuture(QtConcurrent::run(fetchActivityList, &activityController_, target, running)); } } void Workspace::handleActivityReply() { QObject *watcherObject = 0; if (QFutureWatcher* watcher = dynamic_cast< QFutureWatcher* >(sender())) { *(watcher->result().first) = watcher->result().second; // cool trick, ehh? :-) watcherObject = watcher; } if (!watcherObject) { if (QFutureWatcher* watcher = dynamic_cast< QFutureWatcher* >(sender())) { allActivities_ = watcher->result().second; updateCurrentActivity(watcher->result().first); watcherObject = watcher; } } if (watcherObject) { QString slot = watcherObject->property("activityControllerCallback").toString(); QObject *target = static_cast(watcherObject->property("activityControllerCallbackTarget").value()); watcherObject->deleteLater(); // has done it's job if (!slot.isEmpty()) QMetaObject::invokeMethod(target, slot.toAscii().data(), Qt::DirectConnection); } } //END threaded activity list fetching #else // make gcc happy - stupd moc cannot handle preproc defs so we MUST define void Workspace::handleActivityReply() {} #endif // KWIN_BUILD_ACTIVITIES /** * Updates the current activity when it changes * do *not* call this directly; it does not set the activity. * * Shows/Hides windows according to the stacking order */ void Workspace::updateCurrentActivity(const QString &new_activity) { //closeActivePopup(); ++block_focus; // TODO: Q_ASSERT( block_stacking_updates == 0 ); // Make sure stacking_order is up to date StackingUpdatesBlocker blocker(this); if (new_activity != activity_) { ++block_showing_desktop; //FIXME should I be using that? // Optimized Desktop switching: unmapping done from back to front // mapping done from front to back => less exposure events //Notify::raise((Notify::Event) (Notify::DesktopChange+new_desktop)); ObscuringWindows obs_wins; QString old_activity = activity_; activity_ = new_activity; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (!c->isOnActivity(new_activity) && c != movingClient && c->isOnCurrentDesktop()) { if (c->isShown(true) && c->isOnActivity(old_activity) && !compositing()) obs_wins.create(c); c->updateVisibility(); } } // Now propagate the change, after hiding, before showing //rootInfo->setCurrentDesktop( currentDesktop() ); /* TODO someday enable dragging windows to other activities if ( movingClient && !movingClient->isOnDesktop( new_desktop )) { movingClient->setDesktop( new_desktop ); */ for (int i = stacking_order.size() - 1; i >= 0 ; --i) { Client *c = qobject_cast(stacking_order.at(i)); if (!c) { continue; } if (c->isOnActivity(new_activity)) c->updateVisibility(); } --block_showing_desktop; //FIXME not sure if I should do this either if (showingDesktop()) // Do this only after desktop change to avoid flicker resetShowingDesktop(false); } // Restore the focus on this desktop --block_focus; Client* c = 0; //FIXME below here is a lot of focuschain stuff, probably all wrong now if (options->focusPolicyIsReasonable()) { // Search in focus chain if (movingClient != NULL && active_client == movingClient && focus_chain[currentDesktop()].contains(active_client) && active_client->isShown(true) && active_client->isOnCurrentDesktop()) c = active_client; // The requestFocus below will fail, as the client is already active if (!c) { for (int i = focus_chain[currentDesktop()].size() - 1; i >= 0; --i) { if (focus_chain[currentDesktop()].at(i)->isShown(false) && focus_chain[currentDesktop()].at(i)->isOnCurrentActivity()) { c = focus_chain[currentDesktop()].at(i); break; } } } } // If "unreasonable focus policy" and active_client is on_all_desktops and // under mouse (Hence == old_active_client), conserve focus. // (Thanks to Volker Schatz ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop() && active_client->isOnCurrentActivity()) c = active_client; if (c == NULL && !desktops.isEmpty()) c = findDesktop(true, currentDesktop()); if (c != active_client) setActiveClient(NULL, Allowed); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, currentDesktop())); else focusToNull(); // Update focus chain: #ifdef KWIN_BUILD_ACTIVITIES // Replace initial dummy with actual activity, preserving the current chain. if (m_desktopFocusChain.key().isNull()) { QVector val(m_desktopFocusChain.value()); m_activitiesDesktopFocusChain.erase(m_desktopFocusChain); m_desktopFocusChain = m_activitiesDesktopFocusChain.insert(activity_, val); } else { m_desktopFocusChain = m_activitiesDesktopFocusChain.find(activity_); if (m_desktopFocusChain == m_activitiesDesktopFocusChain.end()) { m_desktopFocusChain = m_activitiesDesktopFocusChain.insert(activity_, QVector(numberOfDesktops())); for (int i = 0; i < numberOfDesktops(); ++i) { m_desktopFocusChain.value()[i] = i + 1; } } } #endif // Not for the very first time, only if something changed and there are more than 1 desktops //if ( effects != NULL && old_desktop != 0 && old_desktop != new_desktop ) // static_cast( effects )->desktopChanged( old_desktop ); if (compositing() && m_compositor) m_compositor->addRepaintFull(); } /** * updates clients when an activity is destroyed. * this ensures that a client does not get 'lost' if the only activity it's on is removed. */ void Workspace::slotActivityRemoved(const QString &activity) { allActivities_.removeOne(activity); foreach (Toplevel * toplevel, stacking_order) { if (Client *client = qobject_cast(toplevel)) { client->setOnActivity(activity, false); } } //toss out any session data for it KConfigGroup cg(KGlobal::config(), QString("SubSession: ") + activity); cg.deleteGroup(); } void Workspace::slotActivityAdded(const QString &activity) { allActivities_ << activity; } /** * Called only from D-Bus */ void Workspace::nextDesktop() { int desktop = currentDesktop() + 1; setCurrentDesktop(desktop > numberOfDesktops() ? 1 : desktop); } /** * Called only from D-Bus */ void Workspace::previousDesktop() { int desktop = currentDesktop() - 1; setCurrentDesktop(desktop > 0 ? desktop : numberOfDesktops()); } /** * Sets the number of virtual desktops to \a n */ void Workspace::setNumberOfDesktops(int n) { if (n > KWIN_MAX_NUMBER_DESKTOPS) n = KWIN_MAX_NUMBER_DESKTOPS; if (n < 1 || n == numberOfDesktops()) return; int old_number_of_desktops = numberOfDesktops(); desktopCount_ = n; initPositioning->reinitCascading(0); updateDesktopLayout(); // Make sure the layout is still valid if (currentDesktop() > n) setCurrentDesktop(n); // move all windows that would be hidden to the last visible desktop if (old_number_of_desktops > numberOfDesktops()) { for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { if (!(*it)->isOnAllDesktops() && (*it)->desktop() > numberOfDesktops()) sendClientToDesktop(*it, numberOfDesktops(), true); // TODO: Tile should have a method allClients, push them into other tiles } } rootInfo->setNumberOfDesktops(n); NETPoint* viewports = new NETPoint[n]; rootInfo->setDesktopViewport(n, *viewports); delete[] viewports; // Make it +1, so that it can be accessed as [1..numberofdesktops] focus_chain.resize(n + 1); workarea.clear(); workarea.resize(n + 1); restrictedmovearea.clear(); restrictedmovearea.resize(n + 1); screenarea.clear(); updateClientArea(true); // Resize the desktop focus chain. for (DesktopFocusChains::iterator it = m_activitiesDesktopFocusChain.begin(), end = m_activitiesDesktopFocusChain.end(); it != end; ++it) { QVector &chain = it.value(); chain.resize(n); // We do not destroy the chain in case new desktops are added; if (n >= old_number_of_desktops) { for (int i = old_number_of_desktops; i < n; ++i) chain[i] = i + 1; // But when desktops are removed, we may have to modify the chain a bit, // otherwise invalid desktops may show up. } else { for (int i = 0; i < chain.size(); ++i) chain[i] = qMin(chain[i], n); } } saveDesktopSettings(); emit numberDesktopsChanged(old_number_of_desktops); } /** * Sends client \a c to desktop \a desk. * * Takes care of transients as well. */ void Workspace::sendClientToDesktop(Client* c, int desk, bool dont_activate) { if ((desk < 1 && desk != NET::OnAllDesktops) || desk > numberOfDesktops()) return; int old_desktop = c->desktop(); bool was_on_desktop = c->isOnDesktop(desk) || c->isOnAllDesktops(); c->setDesktop(desk); if (c->desktop() != desk) // No change or desktop forced return; desk = c->desktop(); // Client did range checking emit desktopPresenceChanged(c, old_desktop); if (c->isOnDesktop(currentDesktop())) { if (c->wantsTabFocus() && options->focusPolicyIsReasonable() && !was_on_desktop && // for stickyness changes !dont_activate) requestFocus(c); else restackClientUnderActive(c); } else raiseClient(c); c->checkWorkspacePosition( QRect(), old_desktop ); ClientList transients_stacking_order = ensureStackingOrder(c->transients()); for (ClientList::ConstIterator it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) sendClientToDesktop(*it, desk, dont_activate); updateClientArea(); } /** * Adds/removes client \a c to/from \a activity. * * Takes care of transients as well. */ void Workspace::toggleClientOnActivity(Client* c, const QString &activity, bool dont_activate) { //int old_desktop = c->desktop(); bool was_on_activity = c->isOnActivity(activity); bool was_on_all = c->isOnAllActivities(); //note: all activities === no activities bool enable = was_on_all || !was_on_activity; c->setOnActivity(activity, enable); if (c->isOnActivity(activity) == was_on_activity && c->isOnAllActivities() == was_on_all) // No change return; if (c->isOnCurrentActivity()) { if (c->wantsTabFocus() && options->focusPolicyIsReasonable() && !was_on_activity && // for stickyness changes //FIXME not sure if the line above refers to the correct activity !dont_activate) requestFocus(c); else restackClientUnderActive(c); } else raiseClient(c); //notifyWindowDesktopChanged( c, old_desktop ); ClientList transients_stacking_order = ensureStackingOrder(c->transients()); for (ClientList::ConstIterator it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) toggleClientOnActivity(*it, activity, dont_activate); updateClientArea(); } int Workspace::numScreens() const { return QApplication::desktop()->screenCount(); } int Workspace::activeScreen() const { if (!options->isActiveMouseScreen()) { if (activeClient() != NULL && !activeClient()->isOnScreen(active_screen)) return activeClient()->screen(); return active_screen; } return QApplication::desktop()->screenNumber(cursorPos()); } /** * Check whether a client moved completely out of what's considered the active screen, * if yes, set a new active screen. */ void Workspace::checkActiveScreen(const Client* c) { if (!c->isActive()) return; if (!c->isOnScreen(active_screen)) active_screen = c->screen(); } /** * Called e.g. when a user clicks on a window, set active screen to be the screen * where the click occurred */ void Workspace::setActiveScreenMouse(const QPoint& mousepos) { active_screen = QApplication::desktop()->screenNumber(mousepos); } QRect Workspace::screenGeometry(int screen) const { return QApplication::desktop()->screenGeometry(screen); } int Workspace::screenNumber(const QPoint& pos) const { return QApplication::desktop()->screenNumber(pos); } void Workspace::sendClientToScreen(Client* c, int screen) { screen = c->rules()->checkScreen(screen); if (c->screen() == screen) // Don't use isOnScreen(), that's true even when only partially return; GeometryUpdatesBlocker blocker(c); QRect old_sarea = clientArea(MaximizeArea, c); QRect sarea = clientArea(MaximizeArea, screen, c->desktop()); QRect oldgeom = c->geometry(); QRect geom = c->geometry(); // move the window to have the same relative position to the center of the screen // (i.e. one near the middle of the right edge will also end up near the middle of the right edge) geom.moveCenter( QPoint(( geom.center().x() - old_sarea.center().x()) * sarea.width() / old_sarea.width() + sarea.center().x(), ( geom.center().y() - old_sarea.center().y()) * sarea.height() / old_sarea.height() + sarea.center().y())); c->setGeometry( geom ); // If the window was inside the old screen area, explicitly make sure its inside also the new screen area. // Calling checkWorkspacePosition() should ensure that, but when moving to a small screen the window could // be big enough to overlap outside of the new screen area, making struts from other screens come into effect, // which could alter the resulting geometry. if( old_sarea.contains( oldgeom )) c->keepInArea( sarea ); c->checkWorkspacePosition( oldgeom ); ClientList transients_stacking_order = ensureStackingOrder(c->transients()); for (ClientList::ConstIterator it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) sendClientToScreen(*it, screen); if (c->isActive()) active_screen = screen; } void Workspace::killWindowId(Window window_to_kill) { if (window_to_kill == None) return; Window window = window_to_kill; Client* client = NULL; for (;;) { client = findClient(FrameIdMatchPredicate(window)); if (client != NULL) break; // Found the client Window parent, root; Window* children; unsigned int children_count; XQueryTree(display(), window, &root, &parent, &children, &children_count); if (children != NULL) XFree(children); if (window == root) // We didn't find the client, probably an override-redirect window break; window = parent; // Go up } if (client != NULL) client->killWindow(); else XKillClient(display(), window_to_kill); } void Workspace::sendPingToWindow(Window window, Time timestamp) { rootInfo->sendPing(window, timestamp); } void Workspace::sendTakeActivity(Client* c, Time timestamp, long flags) { rootInfo->takeActivity(c->window(), timestamp, flags); pending_take_activity = c; } /** * Delayed focus functions */ void Workspace::delayFocus() { requestFocus(delayfocus_client); cancelDelayFocus(); } void Workspace::requestDelayFocus(Client* c) { delayfocus_client = c; delete delayFocusTimer; delayFocusTimer = new QTimer(this); connect(delayFocusTimer, SIGNAL(timeout()), this, SLOT(delayFocus())); delayFocusTimer->setSingleShot(true); delayFocusTimer->start(options->delayFocusInterval()); } void Workspace::cancelDelayFocus() { delete delayFocusTimer; delayFocusTimer = 0; } KDecoration* Workspace::createDecoration(KDecorationBridge* bridge) { if (!hasDecorationPlugin()) { return NULL; } return mgr->createDecoration(bridge); } /** * Returns a list of all colors (KDecorationDefines::ColorType) the current * decoration supports */ QList Workspace::decorationSupportedColors() const { QList ret; if (!hasDecorationPlugin()) { return ret; } KDecorationFactory* factory = mgr->factory(); for (Ability ab = ABILITYCOLOR_FIRST; ab < ABILITYCOLOR_END; ab = static_cast(ab + 1)) if (factory->supports(ab)) ret << ab; return ret; } QString Workspace::desktopName(int desk) const { return QString::fromUtf8(rootInfo->desktopName(desk)); } bool Workspace::checkStartupNotification(Window w, KStartupInfoId& id, KStartupInfoData& data) { return startup->checkStartup(w, id, data) == KStartupInfo::Match; } /** * Puts the focus on a dummy window * Just using XSetInputFocus() with None would block keyboard input */ void Workspace::focusToNull() { XSetInputFocus(display(), null_focus_window, RevertToPointerRoot, xTime()); } void Workspace::setShowingDesktop(bool showing) { rootInfo->setShowingDesktop(showing); showing_desktop = showing; ++block_showing_desktop; if (showing_desktop) { showing_desktop_clients.clear(); ++block_focus; ToplevelList cls = stackingOrder(); // Find them first, then minimize, otherwise transients may get minimized with the window // they're transient for for (ToplevelList::ConstIterator it = cls.constBegin(); it != cls.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (c->isOnCurrentActivity() && c->isOnCurrentDesktop() && c->isShown(true) && !c->isSpecialWindow()) showing_desktop_clients.prepend(c); // Topmost first to reduce flicker } for (ClientList::ConstIterator it = showing_desktop_clients.constBegin(); it != showing_desktop_clients.constEnd(); ++it) (*it)->minimize(); --block_focus; if (Client* desk = findDesktop(true, currentDesktop())) requestFocus(desk); } else { for (ClientList::ConstIterator it = showing_desktop_clients.constBegin(); it != showing_desktop_clients.constEnd(); ++it) (*it)->unminimize(); if (showing_desktop_clients.count() > 0) requestFocus(showing_desktop_clients.first()); showing_desktop_clients.clear(); } --block_showing_desktop; } /** * Following Kicker's behavior: * Changing a virtual desktop resets the state and shows the windows again. * Unminimizing a window resets the state but keeps the windows hidden (except * the one that was unminimized). * A new window resets the state and shows the windows again, with the new window * being active. Due to popular demand (#67406) by people who apparently * don't see a difference between "show desktop" and "minimize all", this is not * true if "showDesktopIsMinimizeAll" is set in kwinrc. In such case showing * a new window resets the state but doesn't show windows. */ void Workspace::resetShowingDesktop(bool keep_hidden) { if (block_showing_desktop > 0) return; rootInfo->setShowingDesktop(false); showing_desktop = false; ++block_showing_desktop; if (!keep_hidden) { for (ClientList::ConstIterator it = showing_desktop_clients.constBegin(); it != showing_desktop_clients.constEnd(); ++it) (*it)->unminimize(); } showing_desktop_clients.clear(); --block_showing_desktop; } /** * Activating/deactivating this feature works like this: * When nothing is active, and the shortcut is pressed, global shortcuts are disabled * (using global_shortcuts_disabled) * When a window that has disabling forced is activated, global shortcuts are disabled. * (using global_shortcuts_disabled_for_client) * When a shortcut is pressed and global shortcuts are disabled (either by a shortcut * or for a client), they are enabled again. */ void Workspace::slotDisableGlobalShortcuts() { if (global_shortcuts_disabled || global_shortcuts_disabled_for_client) disableGlobalShortcuts(false); else disableGlobalShortcuts(true); } static bool pending_dfc = false; void Workspace::disableGlobalShortcutsForClient(bool disable) { if (global_shortcuts_disabled_for_client == disable) return; if (!global_shortcuts_disabled) { if (disable) pending_dfc = true; KGlobalSettings::self()->emitChange(KGlobalSettings::BlockShortcuts, disable); // KWin will get the kipc message too } } void Workspace::disableGlobalShortcuts(bool disable) { KGlobalSettings::self()->emitChange(KGlobalSettings::BlockShortcuts, disable); // KWin will get the kipc message too } void Workspace::slotBlockShortcuts(int data) { if (pending_dfc && data) { global_shortcuts_disabled_for_client = true; pending_dfc = false; } else { global_shortcuts_disabled = data; global_shortcuts_disabled_for_client = false; } // Update also Alt+LMB actions etc. for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->updateMouseGrab(); } // Optimized version of QCursor::pos() that tries to avoid X roundtrips // by updating the value only when the X timestamp changes. static QPoint last_cursor_pos; static int last_buttons = 0; static Time last_cursor_timestamp = CurrentTime; static QTimer* last_cursor_timer; QPoint Workspace::cursorPos() const { if (last_cursor_timestamp == CurrentTime || last_cursor_timestamp != QX11Info::appTime()) { last_cursor_timestamp = QX11Info::appTime(); Window root; Window child; int root_x, root_y, win_x, win_y; uint state; XQueryPointer(display(), rootWindow(), &root, &child, &root_x, &root_y, &win_x, &win_y, &state); last_cursor_pos = QPoint(root_x, root_y); last_buttons = state; if (last_cursor_timer == NULL) { Workspace* ws = const_cast(this); last_cursor_timer = new QTimer(ws); last_cursor_timer->setSingleShot(true); connect(last_cursor_timer, SIGNAL(timeout()), ws, SLOT(resetCursorPosTime())); } last_cursor_timer->start(0); } return last_cursor_pos; } /** * Because of QTimer's and the impossibility to get events for all mouse * movements (at least I haven't figured out how) the position needs * to be also refetched after each return to the event loop. */ void Workspace::resetCursorPosTime() { last_cursor_timestamp = CurrentTime; } void Workspace::checkCursorPos() { QPoint last = last_cursor_pos; int lastb = last_buttons; cursorPos(); // Update if needed if (last != last_cursor_pos || lastb != last_buttons) { emit mouseChanged(last_cursor_pos, last, x11ToQtMouseButtons(last_buttons), x11ToQtMouseButtons(lastb), x11ToQtKeyboardModifiers(last_buttons), x11ToQtKeyboardModifiers(lastb)); } } Outline* Workspace::outline() { return m_outline; } #ifdef KWIN_BUILD_SCREENEDGES ScreenEdge* Workspace::screenEdge() { return &m_screenEdge; } #endif bool Workspace::hasTabBox() const { #ifdef KWIN_BUILD_TABBOX return (tab_box != NULL); #else return false; #endif } #ifdef KWIN_BUILD_TABBOX TabBox::TabBox* Workspace::tabBox() const { return tab_box; } #endif QString Workspace::supportInformation() const { QString support; support.append(ki18nc("Introductory text shown in the support information.", "KWin Support Information:\n" "The following information should be used when requesting support on e.g. http://forum.kde.org.\n" "It provides information about the currently running instance, which options are used,\n" "what OpenGL driver and which effects are running.\n" "Please post the information provided underneath this introductory text to a paste bin service\n" "like http://paste.kde.org instead of pasting into support threads.\n").toString()); support.append("\n==========================\n\n"); // all following strings are intended for support. They need to be pasted to e.g forums.kde.org // it is expected that the support will happen in English language or that the people providing // help understand English. Because of that all texts are not translated support.append("Version\n"); support.append("=======\n"); support.append("KWin version: "); support.append(KWIN_VERSION_STRING); support.append('\n'); support.append("KDE SC version (runtime): "); support.append(KDE::versionString()); support.append('\n'); support.append("KDE SC version (compile): "); support.append(KDE_VERSION_STRING); support.append('\n'); support.append("Qt Version: "); support.append(qVersion()); support.append("\n\n"); support.append("Options\n"); support.append("=======\n"); const QMetaObject *metaOptions = options->metaObject(); for (int i=0; ipropertyCount(); ++i) { const QMetaProperty property = metaOptions->property(i); if (QLatin1String(property.name()) == "objectName") { continue; } support.append(QLatin1String(property.name()) % ": " % options->property(property.name()).toString() % '\n'); } support.append("\nScreens\n"); support.append( "=======\n"); support.append("Multi-Head: "); if (is_multihead) { support.append("yes\n"); support.append(QString("Head: %1\n").arg(screen_number)); } else { support.append("no\n"); } support.append(QString("Number of Screens: %1\n").arg(QApplication::desktop()->screenCount())); for (int i=0; iscreenCount(); ++i) { const QRect geo = QApplication::desktop()->screenGeometry(i); support.append(QString("Screen %1 Geometry: %2,%3,%4x%5\n") .arg(i) .arg(geo.x()) .arg(geo.y()) .arg(geo.width()) .arg(geo.height())); } support.append("\nCompositing\n"); support.append( "===========\n"); support.append("Qt Graphics System: "); if (Extensions::nonNativePixmaps()) { support.append("raster\n"); } else { support.append("native\n"); } if (effects) { support.append("Compositing is active\n"); switch (effects->compositingType()) { case OpenGL1Compositing: case OpenGL2Compositing: case OpenGLCompositing: { #ifdef KWIN_HAVE_OPENGLES support.append("Compositing Type: OpenGL ES 2.0\n"); #else support.append("Compositing Type: OpenGL\n"); #endif GLPlatform *platform = GLPlatform::instance(); support.append("OpenGL vendor string: " % platform->glVendorString() % '\n'); support.append("OpenGL renderer string: " % platform->glRendererString() % '\n'); support.append("OpenGL version string: " % platform->glVersionString() % '\n'); if (platform->supports(LimitedGLSL)) support.append("OpenGL shading language version string: " % platform->glShadingLanguageVersionString() % '\n'); support.append("Driver: " % GLPlatform::driverToString(platform->driver()) % '\n'); if (!platform->isMesaDriver()) support.append("Driver version: " % GLPlatform::versionToString(platform->driverVersion()) % '\n'); support.append("GPU class: " % GLPlatform::chipClassToString(platform->chipClass()) % '\n'); support.append("OpenGL version: " % GLPlatform::versionToString(platform->glVersion()) % '\n'); if (platform->supports(LimitedGLSL)) support.append("GLSL version: " % GLPlatform::versionToString(platform->glslVersion()) % '\n'); if (platform->isMesaDriver()) support.append("Mesa version: " % GLPlatform::versionToString(platform->mesaVersion()) % '\n'); if (platform->serverVersion() > 0) support.append("X server version: " % GLPlatform::versionToString(platform->serverVersion()) % '\n'); if (platform->kernelVersion() > 0) support.append("Linux kernel version: " % GLPlatform::versionToString(platform->kernelVersion()) % '\n'); support.append("Direct rendering: "); if (platform->isDirectRendering()) { support.append("yes\n"); } else { support.append("no\n"); } support.append("Requires strict binding: "); if (!platform->isLooseBinding()) { support.append("yes\n"); } else { support.append("no\n"); } support.append("GLSL shaders: "); if (platform->supports(GLSL)) { if (platform->supports(LimitedGLSL)) { support.append(" limited\n"); } else { support.append(" yes\n"); } } else { support.append(" no\n"); } support.append("Texture NPOT support: "); if (platform->supports(TextureNPOT)) { if (platform->supports(LimitedNPOT)) { support.append(" limited\n"); } else { support.append(" yes\n"); } } else { support.append(" no\n"); } support.append("Virtual Machine: "); if (platform->isVirtualMachine()) { support.append(" yes\n"); } else { support.append(" no\n"); } if (effects->compositingType() == OpenGL2Compositing) { support.append("OpenGL 2 Shaders are used\n"); } else { support.append("OpenGL 2 Shaders are not used. Legacy OpenGL 1.x code path is used.\n"); } break; } case XRenderCompositing: support.append("Compositing Type: XRender\n"); break; case NoCompositing: default: support.append("Something is really broken, neither OpenGL nor XRender is used"); } support.append("\nLoaded Effects:\n"); support.append( "---------------\n"); foreach (const QString &effect, static_cast(effects)->loadedEffects()) { support.append(effect % '\n'); } support.append("\nCurrently Active Effects:\n"); support.append( "-------------------------\n"); foreach (const QString &effect, static_cast(effects)->activeEffects()) { support.append(effect % '\n'); } support.append("\nEffect Settings:\n"); support.append( "----------------\n"); foreach (const QString &effect, static_cast(effects)->loadedEffects()) { support.append(static_cast(effects)->supportInformation(effect)); support.append('\n'); } } else { support.append("Compositing is not active\n"); } return support; } void Workspace::slotCompositingToggled() { // notify decorations that composition state has changed if (hasDecorationPlugin()) { KDecorationFactory* factory = mgr->factory(); factory->reset(SettingCompositing); } } void Workspace::slotToggleCompositing() { if (m_compositor) { m_compositor->slotToggleCompositing(); } } } // namespace #include "workspace.moc"