Folks, just to drop you a quick note about Mandriva 2011 UI – if you enable Desktop effects, you’ll have a completely different UI experience when compared to the one with them disabled.

Consider this as my easter egg hint gift, as an excuse for the almost-a-day delay in releasing Mandriva 2011 RC1 version :) .

So, after some months of evaluation, my patch to kwin to enable quick workspace switching was rejected as well.

I am starting to suspect that I am the only person on the face of the Earth who actually uses this functionality. The metacity patch was rejected, the mutter patch seems to have gone to limbo (not a single developer bothered to reply to the feature request with patch over the last few months), and now it won’t enter kwin too. However, from the experience, the kde developer’s feedback was the best one, and it actually gave me some ideas on how to do this functionality without changing a single line of code within kwin.

The idea came from the fact that the kde sends and receives dbus messages for anything. So I took some time today, learned how to develop for udev in python, and also learned how to grab a message which knotify sends when a desktop switch occurs, save the last used workspace; then how to discover that a desktop switch is attempted via a keyboard shortcut, and finally trick kwin into thinking that a different workspace switch should be done.

Yes, it is a hack, which actually relies on parsing strings and regexps contained within DBUS parameters, and it has a huge and enormous overhead over plain metacity/mutter/kwin patches, but it works and does not required me to manually patch metacity or mutter or kwin for each and every release :) .

So this is the code. Just run this “daemon” in background and become a happy user of such functionality:

#!/usr/bin/python

import dbus
from dbus.mainloop.glib import DBusGMainLoop
import pygtk
pygtk.require('2.0')
import gtk
import traceback
import re

# This is a very dirty hack which will probably affect the karma and maybe
# even cause a global meltdown...
regex = re.compile('The global shortcut for Switch to Desktop (.*) was issued.')

lastdesktop=None
prevdesktop=None

def match(bus, msg):
    global lastdesktop, prevdesktop, newdesktop, curdesktop, kwin
    if msg.get_path() == '/Notify':
        args = msg.get_args_list()
        if args[0] == "globalshortcutpressed":
            val = regex.findall(args[4])
            if lastdesktop == None or prevdesktop == None:
                return
            if val:
                curdesktop = "%s" % (val[0])
                if lastdesktop == curdesktop:
                    global kwin
                    kwin.setCurrentDesktop(int(prevdesktop))
        elif len(args[0]) > 6:
            param = args[0][:7]
            if param == 'desktop':
                val = args[0][7:]
                newdesktop = "%s" % val
                if newdesktop == lastdesktop:
                    return
                else:
                    prevdesktop = "%s" % lastdesktop
                    lastdesktop = "%s" % val

if __name__ == "__main__":
    DBusGMainLoop(set_as_default=True)

    bus = dbus.SessionBus()
    bus2 = dbus.SessionBus()

    # ask dbus-daemon to receive all matching messages
    bus.add_match_string("member='event',interface='org.kde.KNotify'")

    # add a callback when receiving a message
    bus.add_message_filter(match)

    kwin = bus2.get_object('org.kde.kwin', '/KWin')

    try:
        gtk.main()
    except:
        traceback.print_exc()

A careful reader already noticed that I am using 2 message bus instances, parse strings by hand, use a regexp to extract the new workspace number for the switch, and use a gtk mainloop within a kde environment. And I am also doing a “cast” from dbus variables into a local representation.

Is it a hack? Yes. Is it a dirty hack? Yes. Is it an awesomely dirty and hackish and oh-my-god-I-dont-believe-it-works-kind of a hack? I certainly think so.

But it works :) .

So, after a take on Metacity , I just went ahead and implemented the same quick workspace switching feature for KDE (namely, kwin) – with great help from Nicolas Lécureuil (a.k.a. Neoclust) of course!

Basically, it works essentially like the same feature in xfwm4 – when you switch to a different workspace via a keyboard shortcut, and press the same shortcut again while on this workspace, it will bring you back to the previous one. So you can press ctrl-f2 to switch to from workspace 1 workspace 2, and when you press ctrl-f2 again kwin will recognize that you want switch back, to whatever workspace you were before, and will do it. An extremely handy quirk which I cannot live without anymore :) .

I have to assume that I have never ever coded anything for KDE until yesterday, but it turned out to be extremely simple. KDE has its flaws, its infrastructure with billions of libraries and processes everywhere is hard to understand, but I actually was surprised on how easy it was.

So, without further words, the patch for kdebase4-workspace which adds this functionality follows (sorry for the formatting, but kde has some very long lines of code..):

diff -p -up kdebase-workspace-4.5.65svn1165394/kwin/kcmkwin/kwindesktop/main.cpp.switchback kdebase-workspace-4.5.65svn1165394/kwin/kcmkwin/kwindesktop/main.cpp
--- kdebase-workspace-4.5.65svn1165394/kwin/kcmkwin/kwindesktop/main.cpp.switchback 2010-09-01 09:10:02.000000000 -0300
+++ kdebase-workspace-4.5.65svn1165394/kwin/kcmkwin/kwindesktop/main.cpp    2010-09-01 09:21:13.000000000 -0300
@@ -184,6 +184,7 @@ void KWinDesktopConfig::init()
     connect( m_ui->popupHideSpinBox, SIGNAL(valueChanged(int)), SLOT(changed()));
     connect( m_ui->desktopLayoutIndicatorCheckBox, SIGNAL(stateChanged(int)), SLOT(changed()));
     connect( m_ui->wrapAroundBox, SIGNAL(stateChanged(int)), SLOT(changed()));
+    connect( m_ui->switchBackBox, SIGNAL(stateChanged(int)), SLOT(changed()));
     connect( m_editor, SIGNAL(keyChange()), SLOT(changed()));
     connect( m_ui->allShortcutsCheckBox, SIGNAL(stateChanged(int)), SLOT(slotShowAllShortcuts()));
     connect( m_ui->effectComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changed()));
@@ -252,6 +253,8 @@ void KWinDesktopConfig::defaults()

     m_ui->wrapAroundBox->setChecked( true );

+    m_ui->switchBackBox->setChecked( false );
+
     m_editor->allDefault();

     emit changed(true);
@@ -285,6 +288,9 @@ void KWinDesktopConfig::load()
     KConfigGroup windowConfig( m_config, "Windows" );
     m_ui->wrapAroundBox->setChecked( windowConfig.readEntry<bool>( "RollOverDesktops", true ) );

+    // Quick switching back to previous desktop
+    m_ui->switchBackBox->setChecked( windowConfig.readEntry<bool>( "SwitchBackDesktops", false ) );
+
     // Effect for desktop switching
     // Set current option to "none" if no plugin is activated.
     KConfigGroup effectconfig( m_config, "Plugins" );
@@ -341,6 +347,9 @@ void KWinDesktopConfig::save()
     // Wrap Around on screen edge
     KConfigGroup windowConfig( m_config, "Windows" );
     windowConfig.writeEntry( "RollOverDesktops", m_ui->wrapAroundBox->isChecked() );
+    //
+    // Quickly back to previous desktop
+    windowConfig.writeEntry( "SwitchBackDesktops", m_ui->switchBackBox->isChecked() );

     // Effect desktop switching
     KConfigGroup effectconfig( m_config, "Plugins" );
diff -p -up kdebase-workspace-4.5.65svn1165394/kwin/kcmkwin/kwindesktop/main.ui.switchback kdebase-workspace-4.5.65svn1165394/kwin/kcmkwin/kwindesktop/main.ui
--- kdebase-workspace-4.5.65svn1165394/kwin/kcmkwin/kwindesktop/main.ui.switchback  2010-09-01 09:09:59.000000000 -0300
+++ kdebase-workspace-4.5.65svn1165394/kwin/kcmkwin/kwindesktop/main.ui 2010-09-01 09:59:02.000000000 -0300
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>572</width>
-    <height>310</height>
+    <height>334</height>
    </rect>
   </property>
   <layout class="QHBoxLayout" name="horizontalLayout">
@@ -141,6 +141,16 @@
          </property>
         </widget>
        </item>
+       <item>
+        <widget class="QCheckBox" name="switchBackBox">
+         <property name="whatsThis">
+          <string>Enable this option if you want to remember and recall previous desktop when switching via keyboard shortcuts. E.g., if you switched to desktop 2 by pressing its shortcut, pressing it again while on desktop 2 will bring you back to the previous desktop.</string>
+         </property>
+         <property name="text">
+          <string>Remember and recall previous desktop when switching via keyboard shortcuts</string>
+         </property>
+        </widget>
+       </item>
        <item>
         <widget class="QGroupBox" name="groupBox_3">
          <property name="title">
diff -p -up kdebase-workspace-4.5.65svn1165394/kwin/kwin.kcfg.switchback kdebase-workspace-4.5.65svn1165394/kwin/kwin.kcfg
--- kdebase-workspace-4.5.65svn1165394/kwin/kwin.kcfg.switchback    2010-09-01 09:10:56.000000000 -0300
+++ kdebase-workspace-4.5.65svn1165394/kwin/kwin.kcfg   2010-09-01 09:16:20.000000000 -0300
@@ -41,6 +41,7 @@
   <entry key="ShadeHover" type="Bool" />
   <entry key="GeometryTip" type="Bool" />
   <entry key="RollOverDesktops" type="Bool" />
+  <entry key="SwitchBackDesktops" type="Bool" />
   <entry key="FocusStealingPreventionLevel" type="Int" />
   <entry key="Placement" type="String" />
   <entry key="AutoRaise" type="Bool" />
diff -p -up kdebase-workspace-4.5.65svn1165394/kwin/options.cpp.switchback kdebase-workspace-4.5.65svn1165394/kwin/options.cpp
--- kdebase-workspace-4.5.65svn1165394/kwin/options.cpp.switchback  2010-09-01 09:12:09.000000000 -0300
+++ kdebase-workspace-4.5.65svn1165394/kwin/options.cpp 2010-09-01 09:26:40.000000000 -0300
@@ -87,6 +87,8 @@ unsigned long Options::updateSettings()

     rollOverDesktops = config.readEntry("RollOverDesktops", true);

+    switchBackDesktops = config.readEntry("SwitchBackDesktops", false);
+
     legacyFullscreenSupport = config.readEntry( "LegacyFullscreenSupport", false );

 //    focusStealingPreventionLevel = config.readEntry( "FocusStealingPreventionLevel", 2 );
diff -p -up kdebase-workspace-4.5.65svn1165394/kwin/options.h.switchback kdebase-workspace-4.5.65svn1165394/kwin/options.h
--- kdebase-workspace-4.5.65svn1165394/kwin/options.h.switchback    2010-09-01 09:12:07.000000000 -0300
+++ kdebase-workspace-4.5.65svn1165394/kwin/options.h   2010-09-01 09:22:14.000000000 -0300
@@ -215,6 +215,11 @@ class Options : public KDecorationOption
          */
         bool rollOverDesktops;

+        /**
+         * whether or not quick switching back to previous desktop is allowed via keyboard shortcuts
+         */
+        bool switchBackDesktops;
+
         // 0 - 4 , see Workspace::allowClientActivation()
         int focusStealingPreventionLevel;

diff -p -up kdebase-workspace-4.5.65svn1165394/kwin/workspace.cpp.switchback kdebase-workspace-4.5.65svn1165394/kwin/workspace.cpp
--- kdebase-workspace-4.5.65svn1165394/kwin/workspace.cpp.switchback    2010-05-20 08:42:10.000000000 -0300
+++ kdebase-workspace-4.5.65svn1165394/kwin/workspace.cpp   2010-09-01 10:10:02.000000000 -0300
@@ -95,6 +95,7 @@ Workspace::Workspace( bool restore )
     , desktopGridSize_( 1, 2 ) // Default to two rows
     , desktopGrid_( new int[2] )
     , currentDesktop_( 0 )
+    , prevDesktop_( 0 )
     , desktopLayoutDynamicity_( false )
     , tilingEnabled_( false )
     // Unsorted
@@ -1403,6 +1404,15 @@ bool Workspace::setCurrentDesktop( int n
     StackingUpdatesBlocker blocker( this );

     int old_desktop = currentDesktop();
+
+    // Eugeni: are we trying to switch back to previous desktop?
+    if (options->switchBackDesktops && (old_desktop == new_desktop ) && (prevDesktop() > 0) )
+        {
+        // go back to previous desktop
+        new_desktop = prevDesktop();
+        kDebug(1212) << "Switching back to " << new_desktop;
+        }
+
     if (new_desktop != currentDesktop() )
         {
         ++block_showing_desktop;
@@ -1413,6 +1423,7 @@ bool Workspace::setCurrentDesktop( int n
         ObscuringWindows obs_wins;

         currentDesktop_ = new_desktop; // Change the desktop (so that Client::updateVisibility() works)
+        prevDesktop_ = old_desktop;

         for( ClientList::ConstIterator it = stacking_order.constBegin();
             it != stacking_order.constEnd();
diff -p -up kdebase-workspace-4.5.65svn1165394/kwin/workspace.h.switchback kdebase-workspace-4.5.65svn1165394/kwin/workspace.h
--- kdebase-workspace-4.5.65svn1165394/kwin/workspace.h.switchback  2010-08-11 07:08:13.000000000 -0300
+++ kdebase-workspace-4.5.65svn1165394/kwin/workspace.h 2010-08-31 23:34:01.000000000 -0300
@@ -245,6 +245,10 @@ class Workspace : public QObject, public
          */
         int currentDesktop() const;
         /**
+         * @returns The ID of the previous desktop.
+         */
+        int prevDesktop() const;
+        /**
          * Set the current desktop to @a current.
          * @returns True on success, false otherwise.
          */
@@ -314,6 +318,7 @@ class Workspace : public QObject, public
         QSize desktopGridSize_;
         int* desktopGrid_;
         int currentDesktop_;
+        int prevDesktop_;
         QString activity_;
         bool desktopLayoutDynamicity_;

@@ -1142,6 +1147,11 @@ inline int Workspace::currentDesktop() c
     return currentDesktop_;
     }

+inline int Workspace::prevDesktop() const
+    {
+    return prevDesktop_;
+    }
+
 inline int Workspace::desktopAtCoords( QPoint coords ) const
     {
     return desktopGrid_[coords.y() * desktopGridSize_.width() + coords.x()];
© 2012 Eugeni's blog Suffusion theme by Sayontan Sinha