Removing decorations in Metacity

I switched to gnome and I miss one wonderful feature of kwin. By combining always on top and remove decoration, kwin allow to transform any window into a kind of post-it embedded into an other window. For example I use it to add an xterm in Netbeans. I don’t know any other window manager than kwin being able to hide decoration with a hot key.

From Devil’s pie source code I found that it was possible to remove decoration using the _MOTIF_WM_HINTS window property. It first tried it with the xprop command, but I had some trouble to handle the maximized window case. Then I found the Ralf Neupert wmctrl patch. I used it as inspiration to write my wmctrl patch, which toggle decoration and ensure that maximized window spread on the whole screen. See the patch bellow.

Metacity have a built-in support for binding custom action to shortcut. In gconf-editor:

  • /apps/metacity/keybinding_commands/command_1 = wmctrl -r :ACTIVE: -B
  • /apps/metacity/global_keybindings/run_command_1 = <mod4>H

A first implementation in shell script using the xprop command. It doesn’t handle the maximized window case.

#! /bin/sh -x
winid=$(xprop -root | awk '/_NET_ACTIVE_WINDOW/ {print $5; exit;}')
current=$(xprop -id $winid | awk '/_MOTIF_WM_HINTS/ { sub(/,/,"",$5); print $5 }')
if [ -z $current ]; then
    current=0;
else
    current=$((1-$current))
fi

xprop -id $winid -f _MOTIF_WM_HINTS 32c -set _MOTIF_WM_HINTS "0x2, 0x0, $current, 0x0, 0x0"

This is a patch to wmctrl, adapted from Ralf one. It ensure that window keep it’s maximized status:

--- /tmp/wmctrl-1.07/main.c	2009-05-22 19:58:19.000000000 +0200
+++ main.c	2009-04-12 16:51:40.000000000 +0200
@@ -264,7 +264,7 @@
         }
     }
    
-    while ((opt = getopt(argc, argv, "FGVvhlupidmxa:r:s:c:t:w:k:o:n:g:e:b:N:I:T:R:")) != -1) {
+    while ((opt = getopt(argc, argv, "BFGVvhlupidmxa:r:s:c:t:w:k:o:n:g:e:b:N:I:T:R:")) != -1) {
         missing_option = 0;
         switch (opt) {
             case 'F':
@@ -356,7 +356,7 @@
             ret = wm_info(disp);
             break;
         case 'a': case 'c': case 'R': 
-        case 't': case 'e': case 'b': case 'N': case 'I': case 'T':
+	case 't': case 'e': case 'b': case 'N': case 'I': case 'T': case 'B':
             if (! options.param_window) {
                 fputs("No window was specified.\n", stderr);
                 return EXIT_FAILURE;
@@ -874,6 +874,83 @@
     }
 }/*}}}*/
 
+static int switch_decoration (Display *disp, Window win) {
+    #define PROP_MOTIF_WM_HINTS_ELEMENTS 5
+    #define MWM_HINTS_DECORATIONS (1L << 1)
+    struct {
+        unsigned long flags;
+        unsigned long functions;
+        unsigned long decorations;
+        long inputMode;
+        unsigned long status;
+    } hints = {0,};
+
+    Atom mA = XInternAtom(disp, "_MOTIF_WM_HINTS", False);
+    Atom sA = XInternAtom(disp, "_NET_WM_STATE", False);
+    //Atom maximizedHorzAtom = XInternAtom(disp, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
+    Atom maximizedVertAtom = XInternAtom(disp, "_NET_WM_STATE_MAXIMIZED_VERT", False);
+    Atom actual_type_return;
+    int actual_format_return;
+    int max=0;
+    int i;
+    unsigned long nitems_return;
+    unsigned long bytes_after_return;
+    unsigned long *prop_return;
+    int x, y, junkx, junky;
+    unsigned int wwidth, wheight, bw, depth;
+    Window junkroot;
+
+    //check if the window is maxmized
+    XGetWindowProperty(disp, win, sA, 0, PROP_MOTIF_WM_HINTS_ELEMENTS, 0,
+        AnyPropertyType, &actual_type_return, &actual_format_return,
+        &nitems_return, &bytes_after_return, &prop_return);
+
+    for(i = 0; i < nitems_return; i++)
+    {
+        if(prop_return[i] == maximizedVertAtom)
+            max = 1;
+    }
+
+    //get the window size
+    XGetGeometry (disp, win, &junkroot, &junkx, &junky,
+                      &wwidth, &wheight, &bw, &depth);
+    XTranslateCoordinates (disp, win, junkroot, junkx, junky,
+                           &x, &y, &junkroot);
+    wwidth += junkx;
+    wheight += junky;
+
+    //check the decoration stats
+    XGetWindowProperty(disp, win, mA, 0, PROP_MOTIF_WM_HINTS_ELEMENTS, 0, mA,
+        &actual_type_return, &actual_format_return, &nitems_return,
+        &bytes_after_return, &prop_return);
+
+    if(nitems_return >= 3)
+        hints.decorations = 1 ^ prop_return[2];
+    else
+        hints.decorations = 0;
+   
+    //fix the window size so it does'nt move after removing the border
+    if(!max)
+    {
+        if(!hints.decorations)
+            XMoveWindow(disp, win, x+junkx, y+junky);
+        else
+            XMoveWindow(disp, win, x-junkx, y-junky);
+    }
+
+    // show/hide borders
+    hints.flags = MWM_HINTS_DECORATIONS;
+    XChangeProperty(disp, win, mA, mA, 32, PropModeReplace,
+        (unsigned char *)&hints, PROP_MOTIF_WM_HINTS_ELEMENTS);
+
+    //Ensure the window is still maximized
+    if(max)
+        XResizeWindow(disp, win, wwidth, wheight);
+
+    activate_window (disp, win, 0);
+    return EXIT_SUCCESS;
+}
+
 static int action_window (Display *disp, Window win, char mode) {/*{{{*/
     p_verbose("Using window: 0x%.8lx\n", win);
     switch (mode) {
@@ -909,7 +986,8 @@
         case 'N': case 'I': case 'T':
             window_set_title(disp, win, options.param, mode);
             return EXIT_SUCCESS;
-
+        case 'B':
+	    return switch_decoration(disp, win);
         default:
             fprintf(stderr, "Unknown action: '%c'\n", mode);
             return EXIT_FAILURE;

#gnome, #metacity, #wmctrl