xfwm: support for 2d pagers

Paramjit Oberoi param at cs.wisc.edu
Mon Sep 20 08:50:38 CEST 2004


xfwm4 currently does not support 2d pagers very well: i.e., say that
your desktop pager/switcher displays 8 virtual desktops in 2 rows, 4
columns.  It is natural in this situation to want to move the virtual
desktop up/down/left/right.  However, xfwm only supports moving to the
next/previous desktop.  (the xfce pager doesn't support multiple rows;
I am using xfwm with GNOME, as a replacement for metacity)

According to the NetWM specification, pagers can communicate the layout
of the virtual desktops using the _NET_DESKTOP_LAYOUT property.  This
property is described in detail here:
http://freedesktop.org/Standards/wm-spec/1.3/ar01s03.html#id2503159

Attached to this email is a patch that adds support for this to xfwm.
The patch is against the current CVS head (I think).  Here's a summary
of the changes:

Additions to keythemerc:
    up_workspace_key
    down_workspace_key
    left_workspace_key
    right_workspace_key
    move_window_up_workspace_key
    move_window_down_workspace_key
    move_window_left_workspace_key
    move_window_right_workspace_key

Eight new hotkeys for changing the virtual desktop, and for moving the
current window. You must add these to your keythemerc to try out this
patch.

Adidtional setting in xfwm4.xml:
    <option name="Xfwm/WrapLayout" type="int" value="0"/>

Optionally, a "wrap_layout" parameter can be specified in the theme defaults.

This setting controls if the up/down/left/right movement wraps around at
the boundaries of the pager.  I have not (yet) modified the settings
manager plugins to allow this to be set via the GUI.

Most of the additional code is self-explanatory - things like grabbing
hotkeys, reading settings, etc.  Here is a list of the non-trivial bits:

hints.c, getDesktopLayout()
  Get the _NET_DESKTOP_LAYOUT property from the root window and update
  internal data structures appropriately

workspaces.c, workspaceGetPosition()
  Translate the given workspace number to a X,Y position depending on
  the layout of virtual workspaces

workspaces.c, workspaceGetNumber()
  Translate the given X,Y position to the workspace number

workspaces.c, workspaceMove(int rowmod, int colmod, Client * c2)
  just like workspaceSwitch (int new_ws, Client * c2), except that
  instead of specifying the new workspace number you specify which
  direction to move in (rowmod is +/- 1 for down/up movement; colmod
  is +/- 1 for right/left movement).

I've been using this with xfwm-4.0.6 for about a month.  I just ported
it to the latest xfwm, and it seems to be working fine so far.  I will
be glad if this can be included in future releases of xfwm.

-param

-------------- next part --------------
Index: client.c
===================================================================
RCS file: /cvsroot/xfce/xfce-devel/xfwm4/src/client.c,v
retrieving revision 1.291
diff -u -r1.291 client.c
--- client.c	8 Oct 2003 21:43:24 -0000	1.291
+++ client.c	20 Sep 2004 06:09:44 -0000
@@ -1373,6 +1373,14 @@
     grabKey (dpy, &params.keys[KEY_SHORTCUT_8], c->window);
     grabKey (dpy, &params.keys[KEY_SHORTCUT_9], c->window);
     grabKey (dpy, &params.keys[KEY_SHORTCUT_10], c->window);
+    grabKey (dpy, &params.keys[KEY_UP_WORKSPACE], c->window);
+    grabKey (dpy, &params.keys[KEY_DOWN_WORKSPACE], c->window);
+    grabKey (dpy, &params.keys[KEY_LEFT_WORKSPACE], c->window);
+    grabKey (dpy, &params.keys[KEY_RIGHT_WORKSPACE], c->window);
+    grabKey (dpy, &params.keys[KEY_MOVE_UP_WORKSPACE], c->window);
+    grabKey (dpy, &params.keys[KEY_MOVE_DOWN_WORKSPACE], c->window);
+    grabKey (dpy, &params.keys[KEY_MOVE_LEFT_WORKSPACE], c->window);
+    grabKey (dpy, &params.keys[KEY_MOVE_RIGHT_WORKSPACE], c->window);
 }
 
 void
Index: events.c
===================================================================
RCS file: /cvsroot/xfce/xfce-devel/xfwm4/src/events.c,v
retrieving revision 1.174
diff -u -r1.174 events.c
--- events.c	8 Oct 2003 21:43:25 -0000	1.174
+++ events.c	20 Sep 2004 06:09:46 -0000
@@ -352,6 +352,18 @@
             case KEY_MOVE_PREV_WORKSPACE:
                 workspaceSwitch (workspace - 1, c);
                 break;
+            case KEY_MOVE_UP_WORKSPACE:
+                workspaceMove (-1, 0, c);
+                break;
+            case KEY_MOVE_DOWN_WORKSPACE:
+                workspaceMove (1, 0, c);
+                break;
+            case KEY_MOVE_LEFT_WORKSPACE:
+                workspaceMove (0, -1, c);
+                break;
+            case KEY_MOVE_RIGHT_WORKSPACE:
+                workspaceMove (0, 1, c);
+                break;
             case KEY_MOVE_WORKSPACE_1:
                 workspaceSwitch (0, c);
                 break;
@@ -405,6 +417,18 @@
         case KEY_PREV_WORKSPACE:
             workspaceSwitch (workspace - 1, NULL);
             break;
+        case KEY_UP_WORKSPACE:
+            workspaceMove(-1, 0, NULL);
+            break;
+        case KEY_DOWN_WORKSPACE:
+            workspaceMove(1, 0, NULL);
+            break;
+        case KEY_LEFT_WORKSPACE:
+            workspaceMove(0, -1, NULL);
+            break;
+        case KEY_RIGHT_WORKSPACE:
+            workspaceMove(0, 1, NULL);
+            break;
         case KEY_ADD_WORKSPACE:
             workspaceSetCount (params.workspace_count + 1);
             break;
@@ -1369,6 +1393,11 @@
         getGnomeDesktopMargins (dpy, screen, gnome_margins);
         workspaceUpdateArea (margins, gnome_margins);
     }
+    else if (ev->atom == net_desktop_layout)
+    {
+        TRACE ("root has received a net_desktop_layout notify");
+        getDesktopLayout(dpy, root, &params.desktop_layout);
+    }
 }
 
 static inline void
Index: hints.c
===================================================================
RCS file: /cvsroot/xfce/xfce-devel/xfwm4/src/hints.c,v
retrieving revision 1.56
diff -u -r1.56 hints.c
--- hints.c	1 Sep 2003 21:09:59 -0000	1.56
+++ hints.c	20 Sep 2004 06:09:46 -0000
@@ -77,6 +77,7 @@
 Atom net_current_desktop;
 Atom net_desktop_geometry;
 Atom net_desktop_viewport;
+Atom net_desktop_layout;
 Atom net_number_of_desktops;
 Atom net_startup_id;
 Atom net_supported;
@@ -325,6 +326,60 @@
 }
 
 void
+getDesktopLayout (Display * dpy, Window root, NetWmDesktopLayout * layout)
+{
+    Atom real_type;
+    int real_format;
+    gboolean success = FALSE;
+    unsigned long items_read, items_left;
+    unsigned long orientation, cols, rows, start;
+    unsigned long *data = NULL;
+
+    TRACE ("entering getDesktopLayout");
+
+    if ((XGetWindowProperty (dpy, root, net_desktop_layout,
+                0L, 4L, FALSE, XA_CARDINAL,
+                &real_type, &real_format, &items_read, &items_left,
+                (unsigned char **) &data) == Success) && (items_read >= 3))
+    {
+        do
+        {
+            orientation = data[0];
+            cols = data[1];
+            rows = data[2];
+            start = (items_read >= 4) ? data[3] : NET_WM_TOPLEFT;
+
+            if (orientation > NET_WM_ORIENTATION_VERT) break;
+            if (start > NET_WM_BOTTOMLEFT) break;
+            if ((rows == 0) && (cols == 0)) break;
+
+            if (rows == 0)
+                rows = (params.workspace_count-1) / cols + 1;
+
+            if (cols == 0)
+                cols = (params.workspace_count-1) / rows + 1;
+
+            layout->orientation = orientation;
+            layout->cols = cols;
+            layout->rows = rows;
+            layout->start = start;
+            success = TRUE;
+        } while (0);
+
+        XFree (data);
+    }
+
+    if (!success)
+    {
+        /* Assume HORZ, TOPLEFT, one row by default */
+        layout->orientation = NET_WM_ORIENTATION_HORZ;
+        layout->cols = params.workspace_count;
+        layout->rows = 1;
+        layout->start = NET_WM_TOPLEFT;
+    }
+}
+
+void
 getGnomeDesktopMargins (Display * dpy, int screen, int * m)
 {
     Atom real_type;
@@ -380,6 +435,7 @@
     net_current_desktop = XInternAtom (dpy, "_NET_CURRENT_DESKTOP", FALSE);
     net_desktop_geometry = XInternAtom (dpy, "_NET_DESKTOP_GEOMETRY", FALSE);
     net_desktop_viewport = XInternAtom (dpy, "_NET_DESKTOP_VIEWPORT", FALSE);
+    net_desktop_layout = XInternAtom (dpy, "_NET_DESKTOP_LAYOUT", FALSE);
     net_number_of_desktops =
         XInternAtom (dpy, "_NET_NUMBER_OF_DESKTOPS", FALSE);
     net_startup_id = XInternAtom (dpy, "_NET_STARTUP_ID", FALSE);
@@ -458,6 +514,7 @@
     atoms[i++] = net_current_desktop;
     atoms[i++] = net_desktop_geometry;
     atoms[i++] = net_desktop_viewport;
+    atoms[i++] = net_desktop_layout;
     atoms[i++] = net_number_of_desktops;
     atoms[i++] = net_supported;
     atoms[i++] = net_supporting_wm_check;
Index: hints.h
===================================================================
RCS file: /cvsroot/xfce/xfce-devel/xfwm4/src/hints.h,v
retrieving revision 1.33
diff -u -r1.33 hints.h
--- hints.h	31 Aug 2003 22:46:13 -0000	1.33
+++ hints.h	20 Sep 2004 06:09:46 -0000
@@ -95,6 +95,23 @@
 #define NET_WM_STATE_ADD                        1
 #define NET_WM_STATE_TOGGLE                     2
 
+#define NET_WM_ORIENTATION_HORZ                 0
+#define NET_WM_ORIENTATION_VERT                 1
+
+#define NET_WM_TOPLEFT                          0
+#define NET_WM_TOPRIGHT                         1
+#define NET_WM_BOTTOMRIGHT                      2
+#define NET_WM_BOTTOMLEFT                       3
+
+typedef struct
+{
+    unsigned long orientation;
+    unsigned long start;
+    unsigned long rows;
+    unsigned long cols;
+}
+NetWmDesktopLayout;
+
 typedef struct
 {
     unsigned long flags;
@@ -133,6 +150,7 @@
 extern Atom net_current_desktop;
 extern Atom net_desktop_geometry;
 extern Atom net_desktop_viewport;
+extern Atom net_desktop_layout;
 extern Atom net_number_of_desktops;
 extern Atom net_startup_id;
 extern Atom net_supported;
@@ -185,6 +203,7 @@
 void initGnomeHints (Display *);
 gboolean getHint (Display *, Window, Atom, long *);
 void setHint (Display *, Window, Atom, long);
+void getDesktopLayout (Display *, Window, NetWmDesktopLayout *);
 void getGnomeDesktopMargins (Display *, int, int *);
 void setGnomeProtocols (Display *, int, Window);
 void initNetHints (Display * dpy);
Index: settings.c
===================================================================
RCS file: /cvsroot/xfce/xfce-devel/xfwm4/src/settings.c,v
retrieving revision 1.81
diff -u -r1.81 settings.c
--- settings.c	9 Oct 2003 21:42:31 -0000	1.81
+++ settings.c	20 Sep 2004 06:09:47 -0000
@@ -152,6 +152,10 @@
                         params.wrap_workspaces = setting->data.v_int;
                         placeSidewalks (params.wrap_workspaces);
                     }
+                    else if (!strcmp (name, "Xfwm/WrapLayout"))
+                    {
+                        params.wrap_layout = setting->data.v_int;
+                    }
                     else if (!strcmp (name, "Xfwm/WrapWindows"))
                     {
                         params.wrap_windows = setting->data.v_int;
@@ -391,6 +395,12 @@
                 rc);
             mcs_setting_free (setting);
         }
+        if (mcs_client_get_setting (client, "Xfwm/WrapLayout", CHANNEL1,
+                &setting) == MCS_SUCCESS)
+        {
+            setBooleanValueFromInt ("wrap_layout", setting->data.v_int, rc);
+            mcs_setting_free (setting);
+        }
         if (mcs_client_get_setting (client, "Xfwm/WrapWindows", CHANNEL1,
                 &setting) == MCS_SUCCESS)
         {
@@ -916,12 +926,28 @@
         getValue ("raise_window_key", rc));
     parseKeyString (dpy, &params.keys[KEY_LOWER_WINDOW],
         getValue ("lower_window_key", rc));
+    parseKeyString (dpy, &params.keys[KEY_UP_WORKSPACE],
+        getValue ("up_workspace_key", rc));
+    parseKeyString (dpy, &params.keys[KEY_DOWN_WORKSPACE],
+        getValue ("down_workspace_key", rc));
+    parseKeyString (dpy, &params.keys[KEY_LEFT_WORKSPACE],
+        getValue ("left_workspace_key", rc));
+    parseKeyString (dpy, &params.keys[KEY_RIGHT_WORKSPACE],
+        getValue ("right_workspace_key", rc));
+    parseKeyString (dpy, &params.keys[KEY_MOVE_UP_WORKSPACE],
+        getValue ("move_window_up_workspace_key", rc));
+    parseKeyString (dpy, &params.keys[KEY_MOVE_DOWN_WORKSPACE],
+        getValue ("move_window_down_workspace_key", rc));
+    parseKeyString (dpy, &params.keys[KEY_MOVE_LEFT_WORKSPACE],
+        getValue ("move_window_left_workspace_key", rc));
+    parseKeyString (dpy, &params.keys[KEY_MOVE_RIGHT_WORKSPACE],
+        getValue ("move_window_right_workspace_key", rc));
     ungrabKeys (dpy, gnome_win);
     grabKey (dpy, &params.keys[KEY_CYCLE_WINDOWS], gnome_win);
     grabKey (dpy, &params.keys[KEY_NEXT_WORKSPACE], gnome_win);
     grabKey (dpy, &params.keys[KEY_PREV_WORKSPACE], gnome_win);
     grabKey (dpy, &params.keys[KEY_ADD_WORKSPACE], gnome_win);
-    grabKey (dpy, &params.keys[KEY_NEXT_WORKSPACE], gnome_win);
+    grabKey (dpy, &params.keys[KEY_DEL_WORKSPACE], gnome_win);
     grabKey (dpy, &params.keys[KEY_WORKSPACE_1], gnome_win);
     grabKey (dpy, &params.keys[KEY_WORKSPACE_2], gnome_win);
     grabKey (dpy, &params.keys[KEY_WORKSPACE_3], gnome_win);
@@ -941,6 +967,10 @@
     grabKey (dpy, &params.keys[KEY_SHORTCUT_8], gnome_win);
     grabKey (dpy, &params.keys[KEY_SHORTCUT_9], gnome_win);
     grabKey (dpy, &params.keys[KEY_SHORTCUT_10], gnome_win);
+    grabKey (dpy, &params.keys[KEY_UP_WORKSPACE], gnome_win);
+    grabKey (dpy, &params.keys[KEY_DOWN_WORKSPACE], gnome_win);
+    grabKey (dpy, &params.keys[KEY_LEFT_WORKSPACE], gnome_win);
+    grabKey (dpy, &params.keys[KEY_RIGHT_WORKSPACE], gnome_win);
 
     return TRUE;
 }
@@ -1006,6 +1036,7 @@
         {"workspace_count", NULL, TRUE},
         {"wrap_windows", NULL, TRUE},
         {"wrap_workspaces", NULL, TRUE},
+        {"wrap_layout", NULL, TRUE},
         {"wrap_resistance", NULL, TRUE},
         /* Keys */
         {"add_workspace_key", NULL, TRUE},
@@ -1068,6 +1099,14 @@
         {"shortcut_8_exec", NULL, FALSE},
         {"shortcut_9_exec", NULL, FALSE},
         {"shortcut_10_exec", NULL, FALSE},
+        {"up_workspace_key", NULL, TRUE},
+        {"down_workspace_key", NULL, TRUE},
+        {"left_workspace_key", NULL, TRUE},
+        {"right_workspace_key", NULL, TRUE},
+        {"move_window_up_workspace_key", NULL, TRUE},
+        {"move_window_down_workspace_key", NULL, TRUE},
+        {"move_window_left_workspace_key", NULL, TRUE},
+        {"move_window_right_workspace_key", NULL, TRUE},
         {"raise_window_key", NULL, TRUE},
         {"lower_window_key", NULL, TRUE},
         {NULL, NULL, FALSE}
@@ -1159,6 +1198,8 @@
 
     params.wrap_workspaces =
         !g_ascii_strcasecmp ("true", getValue ("wrap_workspaces", rc));
+    params.wrap_layout =
+        !g_ascii_strcasecmp ("true", getValue ("wrap_layout", rc));
     params.wrap_windows =
         !g_ascii_strcasecmp ("true", getValue ("wrap_windows", rc));
     params.wrap_resistance = abs (TOINT (getValue ("wrap_resistance", rc)));
@@ -1295,6 +1336,8 @@
         workspaceSetCount (val);
     }
 
+    getDesktopLayout(dpy, root, &params.desktop_layout);
+
     if (!loadSettings ())
     {
         return FALSE;
Index: settings.h
===================================================================
RCS file: /cvsroot/xfce/xfce-devel/xfwm4/src/settings.h,v
retrieving revision 1.32
diff -u -r1.32 settings.h
--- settings.h	30 Sep 2003 00:43:22 -0000	1.32
+++ settings.h	20 Sep 2004 06:09:47 -0000
@@ -29,6 +29,7 @@
 #include <gdk/gdk.h>
 #include "keyboard.h"
 #include "mypixmap.h"
+#include "hints.h"
 
 #define CORNER_TOP_LEFT                 0
 #define CORNER_TOP_RIGHT                1
@@ -113,7 +114,15 @@
 #define KEY_SHORTCUT_10                 49
 #define KEY_LOWER_WINDOW		50
 #define KEY_RAISE_WINDOW		51
-#define KEY_COUNT                       52
+#define KEY_UP_WORKSPACE                52
+#define KEY_DOWN_WORKSPACE              53
+#define KEY_LEFT_WORKSPACE              54
+#define KEY_RIGHT_WORKSPACE             55
+#define KEY_MOVE_UP_WORKSPACE           56
+#define KEY_MOVE_DOWN_WORKSPACE         57
+#define KEY_MOVE_LEFT_WORKSPACE         58
+#define KEY_MOVE_RIGHT_WORKSPACE        59
+#define KEY_COUNT                       60
 #define NB_KEY_SHORTCUTS                10
 
 #define ALIGN_LEFT                      0
@@ -162,6 +171,7 @@
     int title_alignment;
     int title_horizontal_offset;
     int workspace_count;
+    NetWmDesktopLayout desktop_layout;
     int wrap_resistance;
     gboolean title_shadow[2];
     gboolean cycle_minimum;
@@ -179,6 +189,7 @@
     gboolean title_vertical_offset_active;
     gboolean title_vertical_offset_inactive;
     gboolean wrap_workspaces;
+    gboolean wrap_layout;
     gboolean wrap_windows;
     GC box_gc;
     GdkGC *black_gc;
Index: workspaces.c
===================================================================
RCS file: /cvsroot/xfce/xfce-devel/xfwm4/src/workspaces.c,v
retrieving revision 1.55
diff -u -r1.55 workspaces.c
--- workspaces.c	31 Aug 2003 22:46:13 -0000	1.55
+++ workspaces.c	20 Sep 2004 06:09:47 -0000
@@ -36,6 +36,149 @@
 #include "hints.h"
 
 void
+workspaceGetPosition (int n, int * row, int * col)
+{
+    NetWmDesktopLayout * l = &params.desktop_layout;
+    int major_length, minor_length, tmp;
+
+    if (l->orientation == NET_WM_ORIENTATION_HORZ)
+    {
+        major_length = l->cols;
+        minor_length = l->rows;
+    }
+    else
+    {
+        major_length = l->rows;
+        minor_length = l->cols;
+    }
+
+    *row = n / major_length;
+    *col = n % major_length;
+
+    switch (l->start)
+    {
+        case NET_WM_TOPRIGHT:
+            *col = major_length - *col - 1;
+            break;
+        case NET_WM_BOTTOMLEFT:
+            *row = minor_length - *row - 1;
+            break;
+        case NET_WM_BOTTOMRIGHT:
+            *col = major_length - *col - 1;
+            *row = minor_length - *row - 1;
+            break;
+    }
+
+    if (l->orientation == NET_WM_ORIENTATION_VERT)
+    {
+        tmp = *row;
+        *row = *col;
+        *col = tmp;
+        if ((l->start == NET_WM_TOPRIGHT) || (l->start == NET_WM_BOTTOMLEFT))
+        {
+            *row = l->rows - *row - 1;
+            *col = l->cols - *col - 1;
+        }
+    }
+}
+
+int
+workspaceGetNumber (int row, int col)
+{
+    NetWmDesktopLayout * l = &params.desktop_layout;
+    int major_length, minor_length, n, tmp;
+
+    if (l->orientation == NET_WM_ORIENTATION_HORZ)
+    {
+        major_length = l->cols;
+        minor_length = l->rows;
+    }
+    else
+    {
+        major_length = l->rows;
+        minor_length = l->cols;
+    }
+
+    if (l->orientation == NET_WM_ORIENTATION_VERT)
+    {
+        tmp = row;
+        row = col;
+        col = tmp;
+        if ((l->start == NET_WM_TOPRIGHT) || (l->start == NET_WM_BOTTOMLEFT))
+        {
+            row = minor_length - row - 1;
+            col = major_length - col - 1;
+        }
+    }
+
+    switch (l->start)
+    {
+        case NET_WM_TOPRIGHT:
+            col = major_length - col - 1;
+            break;
+        case NET_WM_BOTTOMLEFT:
+            row = minor_length - row - 1;
+            break;
+        case NET_WM_BOTTOMRIGHT:
+            col = major_length - col - 1;
+            row = minor_length - row - 1;
+            break;
+    }
+
+    n = row*major_length + col;
+    return n;
+}
+
+int
+modify_with_wrap (int value, int by, int limit, gboolean wrap)
+{
+    if (by >= limit) by = limit - 1;
+    value += by;
+    if (value >= limit)
+    {
+        if (!wrap) value = limit - 1;
+        else value = value % limit;
+    }
+    else if (value < 0)
+    {
+        if (!wrap) value = 0;
+        else value = (value+limit) % limit;
+    }
+    return value;
+}
+
+void
+workspaceMove (int rowmod, int colmod, Client * c2)
+{
+    int row, col, newrow, newcol, n;
+
+    workspaceGetPosition(workspace, &row, &col);
+    newrow = modify_with_wrap(row, rowmod, params.desktop_layout.rows, params.wrap_layout);
+    newcol = modify_with_wrap(col, colmod, params.desktop_layout.cols, params.wrap_layout);
+    n = workspaceGetNumber(newrow, newcol);
+
+    if (n < params.workspace_count)
+    {
+        workspaceSwitch(n, c2);
+    }
+    else if (params.wrap_layout)
+    {
+        if (colmod < 0) n = params.workspace_count - 1;
+        else
+        {
+            if (colmod > 0) newcol = 0;
+            else if (rowmod > 0) newrow = 0;
+            else if (rowmod < 0) newrow--;
+            else g_return_if_fail(FALSE);
+
+            n = workspaceGetNumber(newrow, newcol);
+        }
+        g_return_if_fail(n < params.workspace_count);
+        workspaceSwitch(n, c2);
+    }
+}
+
+void
 workspaceSwitch (int new_ws, Client * c2)
 {
     Client *c, *new_focus = NULL;
Index: workspaces.h
===================================================================
RCS file: /cvsroot/xfce/xfce-devel/xfwm4/src/workspaces.h,v
retrieving revision 1.13
diff -u -r1.13 workspaces.h
--- workspaces.h	31 Aug 2003 22:46:13 -0000	1.13
+++ workspaces.h	20 Sep 2004 06:09:47 -0000
@@ -30,6 +30,7 @@
 #include <X11/Xmd.h>
 #include "client.h"
 
+void workspaceMove (int, int, Client *);
 void workspaceSwitch (int, Client *);
 void workspaceSetCount (int);
 void workspaceGetArea (int *, int *, Client *);


More information about the Xfce4-dev mailing list