tewmh.c - glazier - window management experiments
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
 (DIR) README
 (DIR) LICENSE
       ---
       tewmh.c (10775B)
       ---
            1 #include <signal.h>
            2 #include <stdio.h>
            3 #include <stdlib.h>
            4 #include <string.h>
            5 #include <xcb/xcb.h>
            6 #include <xcb/xcb_cursor.h>
            7 #include <xcb/randr.h>
            8 
            9 #include "arg.h"
           10 #include "wm.h"
           11 
           12 #define LEN(x) (sizeof(x)/sizeof(x[0]))
           13 
           14 struct xatom {
           15         char *name;
           16         xcb_atom_t atom;
           17 };
           18 
           19 struct xgeom {
           20         int x, y, w, h, b;
           21 };
           22 
           23 enum EWMH_TYPES {
           24         IGNORE,
           25         NORMAL,
           26         POPUP,
           27 };
           28 
           29 enum {
           30         _NET_SUPPORTED,
           31         _NET_CLIENT_LIST,
           32         _NET_CLIENT_LIST_STACKING,
           33         _NET_SUPPORTING_WM_CHECK,
           34         _NET_ACTIVE_WINDOW,
           35         _NET_WM_STATE,
           36         _NET_WM_STATE_FULLSCREEN,
           37         _NET_WM_WINDOW_TYPE,
           38         _NET_WM_WINDOW_TYPE_DESKTOP,
           39         _NET_WM_WINDOW_TYPE_DOCK,
           40         _NET_WM_WINDOW_TYPE_TOOLBAR,
           41         _NET_WM_WINDOW_TYPE_MENU,
           42         _NET_WM_WINDOW_TYPE_UTILITY,
           43         _NET_WM_WINDOW_TYPE_SPLASH,
           44         _NET_WM_WINDOW_TYPE_DIALOG,
           45         _NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
           46         _NET_WM_WINDOW_TYPE_POPUP_MENU,
           47         _NET_WM_WINDOW_TYPE_TOOLTIP,
           48         _NET_WM_WINDOW_TYPE_NOTIFICATION,
           49         _NET_WM_WINDOW_TYPE_COMBO,
           50         _NET_WM_WINDOW_TYPE_DND,
           51         _NET_WM_WINDOW_TYPE_NORMAL,
           52 };
           53 
           54 static void usage(const char *);
           55 static int ewmh_init();
           56 static int ewmh_wipe();
           57 static int ewmh_supported();
           58 static int ewmh_supportingwmcheck();
           59 static int ewmh_activewindow(xcb_window_t);
           60 static int ewmh_clientlist();
           61 static int ewmh_type(xcb_window_t);
           62 static int ewmh_message(xcb_client_message_event_t *);
           63 static int ewmh_fullscreen(xcb_window_t, int);
           64 
           65 xcb_connection_t *conn;
           66 xcb_screen_t     *scrn;
           67 xcb_window_t      ewmhwid; /* _NET_SUPPORTING_WM_CHECK target window */
           68 
           69 struct xatom ewmh[] = {
           70         [_NET_SUPPORTED]                    = { .name = "_NET_SUPPORTED"                    },
           71         [_NET_CLIENT_LIST]                  = { .name = "_NET_CLIENT_LIST"                  },
           72         [_NET_CLIENT_LIST_STACKING]         = { .name = "_NET_CLIENT_LIST_STACKING"         },
           73         [_NET_SUPPORTING_WM_CHECK]          = { .name = "_NET_SUPPORTING_WM_CHECK"          },
           74         [_NET_ACTIVE_WINDOW]                = { .name = "_NET_ACTIVE_WINDOW"                },
           75         [_NET_WM_STATE]                     = { .name = "_NET_WM_STATE"                     },
           76         [_NET_WM_STATE_FULLSCREEN]          = { .name = "_NET_WM_STATE_FULLSCREEN"          },
           77         [_NET_WM_WINDOW_TYPE]               = { .name = "_NET_WM_WINDOW_TYPE"               },
           78         [_NET_WM_WINDOW_TYPE_DESKTOP]       = { .name = "_NET_WM_WINDOW_TYPE_DESKTOP"       },
           79         [_NET_WM_WINDOW_TYPE_DOCK]          = { .name = "_NET_WM_WINDOW_TYPE_DOCK"          },
           80         [_NET_WM_WINDOW_TYPE_TOOLBAR]       = { .name = "_NET_WM_WINDOW_TYPE_TOOLBAR"       },
           81         [_NET_WM_WINDOW_TYPE_MENU]          = { .name = "_NET_WM_WINDOW_TYPE_MENU"          },
           82         [_NET_WM_WINDOW_TYPE_UTILITY]       = { .name = "_NET_WM_WINDOW_TYPE_UTILITY"       },
           83         [_NET_WM_WINDOW_TYPE_SPLASH]        = { .name = "_NET_WM_WINDOW_TYPE_SPLASH"        },
           84         [_NET_WM_WINDOW_TYPE_DIALOG]        = { .name = "_NET_WM_WINDOW_TYPE_DIALOG"        },
           85         [_NET_WM_WINDOW_TYPE_DROPDOWN_MENU] = { .name = "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU" },
           86         [_NET_WM_WINDOW_TYPE_POPUP_MENU]    = { .name = "_NET_WM_WINDOW_TYPE_POPUP_MENU"    },
           87         [_NET_WM_WINDOW_TYPE_TOOLTIP]       = { .name = "_NET_WM_WINDOW_TYPE_TOOLTIP"       },
           88         [_NET_WM_WINDOW_TYPE_NOTIFICATION]  = { .name = "_NET_WM_WINDOW_TYPE_NOTIFICATION"  },
           89         [_NET_WM_WINDOW_TYPE_COMBO]         = { .name = "_NET_WM_WINDOW_TYPE_COMBO"         },
           90         [_NET_WM_WINDOW_TYPE_DND]           = { .name = "_NET_WM_WINDOW_TYPE_DND"           },
           91         [_NET_WM_WINDOW_TYPE_NORMAL]        = { .name = "_NET_WM_WINDOW_TYPE_NORMAL"        },
           92 };
           93 
           94 void
           95 usage(const char *name)
           96 {
           97         fprintf(stderr, "usage: %s [-h]\n", name);
           98 }
           99 
          100 void
          101 cleanup()
          102 {
          103         printf("cleaning up\n");
          104         ewmh_wipe();
          105         wm_kill_xcb();
          106 }
          107 
          108 int
          109 ewmh_init()
          110 {
          111         uint32_t i, n;
          112         xcb_window_t *w;
          113 
          114         for (i = 0; i < LEN(ewmh); i++)
          115                 ewmh[i].atom = wm_add_atom(ewmh[i].name, strlen(ewmh[i].name));
          116 
          117         /* monitor focus events on existing windows */
          118         n = wm_get_windows(scrn->root, &w);
          119         for (i = 0; i < n; i++)
          120                 wm_reg_window_event(w[i], XCB_EVENT_MASK_FOCUS_CHANGE);
          121 
          122         ewmh_supported();
          123         ewmh_supportingwmcheck();
          124         ewmh_clientlist();
          125 
          126         return 0;
          127 }
          128 
          129 int
          130 ewmh_wipe()
          131 {
          132         xcb_delete_property(conn, scrn->root, ewmh[_NET_SUPPORTED].atom);
          133         xcb_delete_property(conn, scrn->root, ewmh[_NET_CLIENT_LIST].atom);
          134         xcb_delete_property(conn, scrn->root, ewmh[_NET_CLIENT_LIST_STACKING].atom);
          135         xcb_delete_property(conn, scrn->root, ewmh[_NET_ACTIVE_WINDOW].atom);
          136         xcb_delete_property(conn, scrn->root, ewmh[_NET_SUPPORTING_WM_CHECK].atom);
          137         xcb_destroy_window(conn, ewmhwid);
          138 
          139         xcb_flush(conn);
          140 
          141         return 0;
          142 }
          143 
          144 int
          145 ewmh_supported()
          146 {
          147         uint32_t i;
          148         xcb_atom_t supported[LEN(ewmh)];
          149 
          150         for (i = 0; i < LEN(ewmh); i++)
          151                 supported[i] = ewmh[i].atom;
          152 
          153         return wm_set_atom(scrn->root, ewmh[_NET_SUPPORTED].atom, XCB_ATOM_ATOM, i, &supported);
          154 }
          155 
          156 int
          157 ewmh_supportingwmcheck()
          158 {
          159         int val = 1;
          160 
          161         ewmhwid = xcb_generate_id(conn);
          162 
          163         /* dummyest window ever. */
          164         xcb_create_window(conn,
          165                 XCB_COPY_FROM_PARENT, ewmhwid, scrn->root,
          166                 0, 0, 1, 1, 0,                     /* x, y, w, h, border */
          167                 XCB_WINDOW_CLASS_INPUT_ONLY,       /* no need for output */
          168                 scrn->root_visual,                 /* visual */
          169                 XCB_CW_OVERRIDE_REDIRECT, &val);   /* have the WM ignore us */
          170 
          171         wm_set_atom(scrn->root, ewmh[_NET_SUPPORTING_WM_CHECK].atom, XCB_ATOM_WINDOW, 1, &ewmhwid);
          172         wm_set_atom(ewmhwid, ewmh[_NET_SUPPORTING_WM_CHECK].atom, XCB_ATOM_WINDOW, 1, &ewmhwid);
          173 
          174         return 0;
          175 }
          176 
          177 int
          178 ewmh_activewindow(xcb_window_t wid)
          179 {
          180         wm_set_atom(scrn->root, ewmh[_NET_ACTIVE_WINDOW].atom, XCB_ATOM_WINDOW, 1, &wid);
          181         return 0;
          182 }
          183 
          184 int
          185 ewmh_clientlist()
          186 {
          187         uint32_t i, c, n;
          188         xcb_window_t *w, *l;
          189 
          190         n = wm_get_windows(scrn->root, &w);
          191 
          192         l = calloc(n, sizeof(*w));
          193 
          194         for (i=0, c=0; i<n; i++) {
          195                 if (ewmh_type(w[i]) != NORMAL)
          196                         xcb_change_window_attributes(conn, w[i], XCB_CW_OVERRIDE_REDIRECT, &(int){1});
          197 
          198                 if (wm_is_listable(w[i], 0))
          199                         l[c++] = w[i];
          200         }
          201 
          202         free(w);
          203 
          204         wm_set_atom(scrn->root, ewmh[_NET_CLIENT_LIST].atom, XCB_ATOM_WINDOW, c, l);
          205         wm_set_atom(scrn->root, ewmh[_NET_CLIENT_LIST_STACKING].atom, XCB_ATOM_WINDOW, c, l);
          206 
          207         free(l);
          208 
          209         return 0;
          210 }
          211 
          212 int
          213 ewmh_type(xcb_window_t window)
          214 {
          215         unsigned long n;
          216         int type = NORMAL; /* treat non-ewmh as normal windows */
          217         xcb_atom_t *atoms;
          218 
          219         atoms = wm_get_atom(window, ewmh[_NET_WM_WINDOW_TYPE].atom, XCB_ATOM_ATOM, &n);
          220 
          221         if (!atoms)
          222                 return NORMAL;
          223 
          224         /*
          225          * as per the EWMH spec, when multiple types are
          226          * applicable, they must be listed from the most to least
          227          * important.
          228          * To do so, we cycle through them in reverse order, changing
          229          * the window type everytime a known type is encountered.
          230          * Some toolkits like to use toolkit-specific atoms as their
          231          * first value for more appropriate categorization. This function
          232          * only deals with standard EWMH atoms.
          233          */
          234         while (n --> 0) {
          235                 if (atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_DESKTOP].atom
          236                  || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_DOCK].atom
          237                  || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_TOOLBAR].atom
          238                  || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_POPUP_MENU].atom
          239                  || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_COMBO].atom
          240                  || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_DND].atom)
          241                         type = IGNORE;
          242 
          243                 if (atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_MENU].atom
          244                  || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_SPLASH].atom
          245                  || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_DROPDOWN_MENU].atom
          246                  || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_TOOLTIP].atom
          247                  || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_NOTIFICATION].atom)
          248                         type = POPUP;
          249 
          250                 if (atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_DIALOG].atom
          251                  || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_UTILITY].atom
          252                  || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_NORMAL].atom)
          253                         type = NORMAL;
          254         }
          255 
          256         return type;
          257 }
          258 
          259 int
          260 ewmh_message(xcb_client_message_event_t *ev)
          261 {
          262         /* ignore all other messages */
          263         if (ev->type != ewmh[_NET_WM_STATE].atom)
          264                 return -1;
          265 
          266         if (ev->data.data32[1] == ewmh[_NET_WM_STATE_FULLSCREEN].atom
          267          || ev->data.data32[2] == ewmh[_NET_WM_STATE_FULLSCREEN].atom) {
          268                 ewmh_fullscreen(ev->window, ev->data.data32[0]);
          269                 return 0;
          270         }
          271 
          272         return 1;
          273 }
          274 
          275 int
          276 ewmh_fullscreen(xcb_window_t wid, int state)
          277 {
          278         size_t n;
          279         int isfullscreen;
          280         xcb_atom_t *atom, original_size;
          281         xcb_randr_monitor_info_t *m;
          282         struct xgeom g, *origin;
          283 
          284         atom = wm_get_atom(wid, ewmh[_NET_WM_STATE].atom, XCB_ATOM_ATOM, &n);
          285         original_size = wm_add_atom("ORIGINAL_SIZE", strlen("ORIGINAL_SIZE"));
          286 
          287         isfullscreen = (atom && *atom == ewmh[_NET_WM_STATE_FULLSCREEN].atom);
          288 
          289         switch (state) {
          290         case -1:
          291                 return isfullscreen;
          292                 break; /* NOTREACHED */
          293 
          294         case 0: /* _NET_WM_STATE_REMOVE */
          295                 wm_set_atom(wid, ewmh[_NET_WM_STATE].atom, XCB_ATOM_ATOM, 0, NULL);
          296                 origin = wm_get_atom(wid, original_size, XCB_ATOM_CARDINAL, &n);
          297                 if (!origin || n < 5)
          298                         return -1;
          299 
          300                 wm_set_border(origin->b, -1, wid);
          301                 wm_teleport(wid, origin->x, origin->y, origin->w, origin->h);
          302                 xcb_delete_property(conn, wid, original_size);
          303                 break;
          304 
          305         case 1: /* _NET_WM_STATE_ADD */
          306                 /* save current window geometry */
          307                 g.x = wm_get_attribute(wid, ATTR_X);
          308                 g.y = wm_get_attribute(wid, ATTR_Y);
          309                 g.w = wm_get_attribute(wid, ATTR_W);
          310                 g.h = wm_get_attribute(wid, ATTR_H);
          311                 g.b = wm_get_attribute(wid, ATTR_B);
          312                 wm_set_atom(wid, original_size, XCB_ATOM_CARDINAL, 5, &g);
          313 
          314                 m = wm_get_monitor(wm_find_monitor(g.x, g.y));
          315                 if (!m)
          316                         return -1;
          317 
          318                 /* move window fullscreen */
          319                 wm_set_border(0, -1, wid);
          320                 wm_teleport(wid, m->x, m->y, m->width, m->height);
          321                 wm_restack(wid, XCB_STACK_MODE_ABOVE);
          322                 wm_set_atom(wid, ewmh[_NET_WM_STATE].atom, XCB_ATOM_ATOM, 1, &ewmh[_NET_WM_STATE_FULLSCREEN].atom);
          323                 free(m);
          324                 break;
          325 
          326         case 2: /* _NET_WM_STATE_TOGGLE */
          327                 printf("0x%08x !fullscreen\n", wid);
          328                 ewmh_fullscreen(wid, !isfullscreen);
          329                 break;
          330         }
          331 
          332         return 0;
          333 }
          334 
          335 int
          336 main (int argc, char *argv[])
          337 {
          338         int mask;
          339         char *argv0;
          340         xcb_generic_event_t *ev = NULL;
          341 
          342         ARGBEGIN {
          343         case 'h':
          344                 usage(argv0);
          345                 return 0;
          346                 break; /* NOTREACHED */
          347         default:
          348                 usage(argv0);
          349                 return -1;
          350                 break; /* NOTREACHED */
          351         } ARGEND;
          352 
          353         wm_init_xcb();
          354         wm_get_screen();
          355         ewmh_init();
          356 
          357         signal(SIGINT,  cleanup);
          358         signal(SIGTERM, cleanup);
          359 
          360         /* needed to get notified of windows creation */
          361         mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
          362 
          363         if (!wm_reg_window_event(scrn->root, mask))
          364                 return -1;
          365 
          366         for (;;) {
          367                 xcb_flush(conn);
          368                 ev = xcb_wait_for_event(conn);
          369                 if (!ev)
          370                         break;
          371 
          372                 switch (ev->response_type & ~0x80) {
          373 
          374                 case XCB_CREATE_NOTIFY:
          375                         wm_reg_window_event(((xcb_create_notify_event_t *)ev)->window, XCB_EVENT_MASK_FOCUS_CHANGE);
          376                         /* FALLTHROUGH */
          377                 case XCB_DESTROY_NOTIFY:
          378                         ewmh_clientlist();
          379                         break;
          380 
          381                 case XCB_CLIENT_MESSAGE:
          382                         ewmh_message((xcb_client_message_event_t *)ev);
          383                         break;
          384 
          385                 case XCB_FOCUS_IN:
          386                         ewmh_activewindow(((xcb_focus_in_event_t *)ev)->event);
          387                         break;
          388                 case XCB_MAP_NOTIFY:
          389                         if (ewmh_type(((xcb_map_notify_event_t *)ev)->window) == POPUP)
          390                                 wm_restack(((xcb_map_notify_event_t *)ev)->window, XCB_STACK_MODE_ABOVE);
          391                 }
          392                 free(ev);
          393         }
          394 
          395         return -1;
          396