]> code.delx.au - spectrwm/blobdiff - spectrwm.c
Support _NET_WM_WINDOW_TYPE_DESKTOP and _NET_WM_WINDOW_TYPE_DOCK
[spectrwm] / spectrwm.c
index 75df3739cd425e1d4512028f9f1bccf8fb0a5afc..6ba69d202913fe3ba8feb9b476a86fd1f4c0cd5e 100644 (file)
@@ -69,6 +69,7 @@
 #include <X11/Xcursor/Xcursor.h>
 #include <X11/Xft/Xft.h>
 #include <X11/Xlib-xcb.h>
+#include <xcb/xcb.h>
 #include <xcb/xcb_atom.h>
 #include <xcb/xcb_aux.h>
 #include <xcb/xcb_event.h>
@@ -534,6 +535,7 @@ struct workspace {
        struct ws_win           *focus;         /* may be NULL */
        struct ws_win           *focus_prev;    /* may be NULL */
        struct ws_win           *focus_pending; /* may be NULL */
+       struct ws_win           *focus_raise;           /* may be NULL */
        struct swm_region       *r;             /* may be NULL */
        struct swm_region       *old_r;         /* may be NULL */
        struct ws_win_list      winlist;        /* list of windows in ws */
@@ -707,6 +709,7 @@ enum {
        _NET_WM_STATE_SKIP_PAGER,
        _NET_WM_STATE_SKIP_TASKBAR,
        _NET_WM_WINDOW_TYPE,
+       _NET_WM_WINDOW_TYPE_DESKTOP,
        _NET_WM_WINDOW_TYPE_DIALOG,
        _NET_WM_WINDOW_TYPE_DOCK,
        _NET_WM_WINDOW_TYPE_NORMAL,
@@ -751,6 +754,7 @@ struct ewmh_hint {
     {"_NET_WM_STATE_SKIP_PAGER", XCB_ATOM_NONE},
     {"_NET_WM_STATE_SKIP_TASKBAR", XCB_ATOM_NONE},
     {"_NET_WM_WINDOW_TYPE", XCB_ATOM_NONE},
+    {"_NET_WM_WINDOW_TYPE_DESKTOP", XCB_ATOM_NONE},
     {"_NET_WM_WINDOW_TYPE_DIALOG", XCB_ATOM_NONE},
     {"_NET_WM_WINDOW_TYPE_DOCK", XCB_ATOM_NONE},
     {"_NET_WM_WINDOW_TYPE_NORMAL", XCB_ATOM_NONE},
@@ -823,6 +827,7 @@ enum actionid {
        FN_FOCUS_NEXT,
        FN_FOCUS_PREV,
        FN_FOCUS_URGENT,
+       FN_FULLSCREEN_TOGGLE,
        FN_MAXIMIZE_TOGGLE,
        FN_HEIGHT_GROW,
        FN_HEIGHT_SHRINK,
@@ -845,6 +850,8 @@ enum actionid {
        FN_MVRG_7,
        FN_MVRG_8,
        FN_MVRG_9,
+       KF_MVRG_NEXT,
+       KF_MVRG_PREV,
        FN_MVWS_1,
        FN_MVWS_2,
        FN_MVWS_3,
@@ -869,6 +876,7 @@ enum actionid {
        FN_MVWS_22,
        FN_NAME_WORKSPACE,
        FN_QUIT,
+       FN_RAISE,
        FN_RAISE_TOGGLE,
        FN_RESIZE,
        FN_RESIZE_CENTERED,
@@ -1028,6 +1036,7 @@ void       ewmh_apply_flags(struct ws_win *, uint32_t);
 void    ewmh_autoquirk(struct ws_win *);
 void    ewmh_get_desktop_names(void);
 void    ewmh_get_wm_state(struct ws_win *);
+int     ewmh_handle_special_types(xcb_window_t, struct swm_region *);
 void    ewmh_update_actions(struct ws_win *);
 void    ewmh_update_client_list(void);
 void    ewmh_update_current_desktop(void);
@@ -1057,6 +1066,7 @@ void       focusout(xcb_focus_out_event_t *);
 void    focusrg(struct binding *, struct swm_region *, union arg *);
 void    fontset_init(void);
 void    free_window(struct ws_win *);
+void    fullscreen_toggle(struct binding *, struct swm_region *, union arg *);
 xcb_atom_t get_atom_from_string(const char *);
 #ifdef SWM_DEBUG
 char   *get_atom_name(xcb_atom_t);
@@ -1132,6 +1142,7 @@ void       quirk_remove(struct quirk *);
 void    quirk_replace(struct quirk *, const char *, const char *, const char *,
             uint32_t, int);
 void    quit(struct binding *, struct swm_region *, union arg *);
+void    raise_focus(struct binding *, struct swm_region *, union arg *);
 void    raise_toggle(struct binding *, struct swm_region *, union arg *);
 void    raise_window(struct ws_win *);
 void    region_containment(struct ws_win *, struct swm_region *, int);
@@ -1154,6 +1165,7 @@ void       search_win(struct binding *, struct swm_region *, union arg *);
 void    search_win_cleanup(void);
 void    search_workspace(struct binding *, struct swm_region *, union arg *);
 void    send_to_rg(struct binding *, struct swm_region *, union arg *);
+void    send_to_rg_relative(struct binding *, struct swm_region *, union arg *);
 void    send_to_ws(struct binding *, struct swm_region *, union arg *);
 void    set_region(struct swm_region *);
 int     setautorun(const char *, const char *, int);
@@ -1500,6 +1512,51 @@ teardown_ewmh(void)
        }
 }
 
+int
+ewmh_handle_special_types(xcb_window_t id, struct swm_region *region)
+{
+       xcb_get_property_reply_t        *r;
+       xcb_get_property_cookie_t       c;
+       xcb_atom_t                      *type;
+       int                             i, n;
+       uint16_t                        configure_mask = 0;
+       uint32_t                        wa[2];
+
+       c = xcb_get_property(conn, 0, id,
+           ewmh[_NET_WM_WINDOW_TYPE].atom, XCB_ATOM_ATOM, 0, UINT32_MAX);
+       r = xcb_get_property_reply(conn, c, NULL);
+       if (r == NULL)
+               return 0;
+
+       type = xcb_get_property_value(r);
+       n = xcb_get_property_value_length(r) / sizeof(xcb_atom_t);
+
+       for (i = 0; i < n; i++) {
+               if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_DESKTOP].atom) {
+                       configure_mask = XCB_CONFIG_WINDOW_STACK_MODE |
+                               XCB_CONFIG_WINDOW_SIBLING;
+                       wa[0] = region->id;
+                       wa[1] = XCB_STACK_MODE_ABOVE;
+                       break;
+               }
+
+               if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_DOCK].atom) {
+                       configure_mask = XCB_CONFIG_WINDOW_STACK_MODE;
+                       wa[0] = XCB_STACK_MODE_ABOVE;
+                       break;
+               }
+       }
+       free(r);
+
+       if (configure_mask != 0) {
+               xcb_map_window(conn, id);
+               xcb_configure_window (conn, id, configure_mask, wa);
+               return 1;
+       }
+
+       return 0;
+}
+
 void
 ewmh_autoquirk(struct ws_win *win)
 {
@@ -1520,8 +1577,7 @@ ewmh_autoquirk(struct ws_win *win)
        for (i = 0; i < n; i++) {
                if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_NORMAL].atom)
                        break;
-               if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_DOCK].atom ||
-                   type[i] == ewmh[_NET_WM_WINDOW_TYPE_TOOLBAR].atom ||
+               if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_TOOLBAR].atom ||
                    type[i] == ewmh[_NET_WM_WINDOW_TYPE_UTILITY].atom) {
                        win->quirks = SWM_Q_FLOAT | SWM_Q_ANYWHERE;
                        break;
@@ -2410,7 +2466,7 @@ bar_urgent(char *s, size_t sz)
                        strlcat(s, "- ", sz);
                }
        }
-       if(urgent_collapse && s[0])
+       if (urgent_collapse && s[0])
                s[strlen(s) - 1] = 0;
 }
 
@@ -3370,18 +3426,20 @@ update_win_stacking(struct ws_win *win)
 
        sibling = TAILQ_NEXT(win, stack_entry);
        if (sibling != NULL && (FLOATING(win) == FLOATING(sibling) ||
-           (win->ws->always_raise && win->ws->focus == win)))
+           (win->ws->always_raise && win->ws->focus == win))) {
                val[0] = sibling->frame;
-       else if (FLOATING(win) || (win->ws->always_raise &&
-           win->ws->focus == win))
+               val[1] = XCB_STACK_MODE_ABOVE;
+       } else if (FLOATING(win) || (win->ws->always_raise &&
+           win->ws->focus == win)) {
                val[0] = r->bar->id;
-       else
-               val[0] = r->id;
+               val[1] = XCB_STACK_MODE_ABOVE;
+       } else {
+               val[0] = r->bar->id;
+               val[1] = XCB_STACK_MODE_BELOW;
+       }
 
        DNPRINTF(SWM_D_EVENT, "update_win_stacking: win %#x (%#x), "
-           "sibling %#x\n", win->frame, win->id, val[0]);
-
-       val[1] = XCB_STACK_MODE_ABOVE;
+           "sibling %#x mode %#x\n", win->frame, win->id, val[0], val[1]);
 
        xcb_configure_window(conn, win->frame, XCB_CONFIG_WINDOW_SIBLING |
            XCB_CONFIG_WINDOW_STACK_MODE, val);
@@ -3704,7 +3762,16 @@ spawn(int ws_idx, union arg *args, bool close_fd)
 
        close(xcb_get_file_descriptor(conn));
 
-       setenv("LD_PRELOAD", SWM_LIB, 1);
+       if ((ret = getenv("LD_PRELOAD"))) {
+               if (asprintf(&ret, "%s:%s", SWM_LIB, ret) == -1) {
+                       warn("spawn: asprintf LD_PRELOAD");
+                       _exit(1);
+               }
+               setenv("LD_PRELOAD", ret, 1);
+               free(ret);
+       } else {
+               setenv("LD_PRELOAD", SWM_LIB, 1);
+       }
 
        if (asprintf(&ret, "%d", ws_idx) == -1) {
                warn("spawn: asprintf SWM_WS");
@@ -3770,6 +3837,8 @@ kill_refs(struct ws_win *win)
                                ws->focus_prev = NULL;
                        if (win == ws->focus_pending)
                                ws->focus_pending = NULL;
+                       if (win == ws->focus_raise)
+                               ws->focus_raise = NULL;
 
                        if (TRANS(win))
                                TAILQ_FOREACH(w, &ws->winlist, entry)
@@ -3855,6 +3924,9 @@ unfocus_win(struct ws_win *win)
        if (win->ws->focus == win) {
                win->ws->focus = NULL;
                win->ws->focus_prev = win;
+               if (win->ws->focus_raise == win && !FLOATING(win)) {
+                       update_win_stacking(win);
+               }
        }
 
        if (validate_win(win->ws->focus)) {
@@ -4122,6 +4194,10 @@ set_region(struct swm_region *r)
 
        r->s->r_focus = r;
 
+       /* Update the focus window frame on the now unfocused region. */
+       if (rf && rf->ws->focus)
+               draw_frame(rf->ws->focus);
+
        ewmh_update_current_desktop();
 }
 
@@ -4166,8 +4242,6 @@ switchws(struct binding *bp, struct swm_region *r, union arg *args)
        int                     wsid = args->id;
        bool                    unmap_old = false;
 
-       (void)bp;
-
        if (!(r && r->s))
                return;
 
@@ -4187,7 +4261,8 @@ switchws(struct binding *bp, struct swm_region *r, union arg *args)
                return;
 
        other_r = new_ws->r;
-       if (other_r && workspace_clamp) {
+       if (other_r && workspace_clamp &&
+           bp->action != FN_RG_MOVE_NEXT && bp->action != FN_RG_MOVE_PREV) {
                DNPRINTF(SWM_D_WS, "switchws: ws clamped.\n");
 
                if (warp_focus) {
@@ -5626,6 +5701,30 @@ send_to_ws(struct binding *bp, struct swm_region *r, union arg *args)
        focus_flush();
 }
 
+/* Transfer focused window to region-relative workspace and focus. */
+void
+send_to_rg_relative(struct binding *bp, struct swm_region *r, union arg *args)
+{
+       union arg               args_abs;
+       struct swm_region       *r_other;
+
+       if (args->id == 1) {
+               r_other = TAILQ_NEXT(r, entry);
+               if (r_other == NULL)
+                       r_other = TAILQ_FIRST(&r->s->rl);
+       } else {
+               r_other = TAILQ_PREV(r, swm_region_list, entry);
+               if (r_other == NULL)
+                       r_other = TAILQ_LAST(&r->s->rl, swm_region_list);
+       }
+
+       /* Map relative to absolute */
+       args_abs = *args;
+       args_abs.id = r_other->ws->idx;
+
+       send_to_ws(bp, r, &args_abs);
+}
+
 void
 win_to_ws(struct ws_win *win, int wsid, bool unfocus)
 {
@@ -5733,6 +5832,31 @@ pressbutton(struct binding *bp, struct swm_region *r, union arg *args)
            XCB_CURRENT_TIME, XCB_WINDOW_NONE, 0, 0, 0);
 }
 
+void
+raise_focus(struct binding *bp, struct swm_region *r, union arg *args)
+{
+       struct ws_win   *win;
+       uint32_t        val;
+
+       /* Suppress warning. */
+       (void)bp;
+       (void)args;
+
+       if (r == NULL || r->ws == NULL || r->ws->focus == NULL)
+               return;
+
+       win = r->ws->focus;
+       r->ws->focus_raise = win;
+       raise_window(win);
+
+       /* Temporarily override stacking order also in the stack */
+       if (!FLOATING(win)) {
+               val = XCB_STACK_MODE_ABOVE;
+               xcb_configure_window(conn, win->frame,
+                   XCB_CONFIG_WINDOW_STACK_MODE, &val);
+       }
+}
+
 void
 raise_toggle(struct binding *bp, struct swm_region *r, union arg *args)
 {
@@ -6115,7 +6239,7 @@ ewmh_update_desktop_names(void)
                        ++len;
                }
 
-               if((name_list = calloc(len, sizeof(char))) == NULL)
+               if ((name_list = calloc(len, sizeof(char))) == NULL)
                        err(1, "update_desktop_names: calloc: failed to "
                            "allocate memory.");
 
@@ -6493,6 +6617,32 @@ floating_toggle(struct binding *bp, struct swm_region *r, union arg *args)
        DNPRINTF(SWM_D_MISC, "floating_toggle: done\n");
 }
 
+void
+fullscreen_toggle(struct binding *bp, struct swm_region *r, union arg *args)
+{
+       struct ws_win           *w = r->ws->focus;
+
+       /* suppress unused warning since var is needed */
+       (void)bp;
+       (void)args;
+
+       if (w == NULL)
+               return;
+
+       DNPRINTF(SWM_D_MISC, "fullscreen_toggle: win %#x\n", w->id);
+
+       ewmh_apply_flags(w, w->ewmh_flags ^ EWMH_F_FULLSCREEN);
+       ewmh_update_wm_state(w);
+
+       stack(r);
+
+       if (w == w->ws->focus)
+               focus_win(w);
+
+       center_pointer(r);
+       focus_flush();
+       DNPRINTF(SWM_D_MISC, "fullscreen_toggle: done\n");
+}
 void
 region_containment(struct ws_win *win, struct swm_region *r, int opts)
 {
@@ -7279,6 +7429,7 @@ struct action {
        { "focus_next",         focus,          0, {.id = SWM_ARG_ID_FOCUSNEXT} },
        { "focus_prev",         focus,          0, {.id = SWM_ARG_ID_FOCUSPREV} },
        { "focus_urgent",       focus,          0, {.id = SWM_ARG_ID_FOCUSURGENT} },
+       { "fullscreen_toggle",  fullscreen_toggle, 0, {0} },
        { "maximize_toggle",    maximize_toggle,0, {0} },
        { "height_grow",        resize,         0, {.id = SWM_ARG_ID_HEIGHTGROW} },
        { "height_shrink",      resize,         0, {.id = SWM_ARG_ID_HEIGHTSHRINK} },
@@ -7301,6 +7452,8 @@ struct action {
        { "mvrg_7",             send_to_rg,     0, {.id = 6} },
        { "mvrg_8",             send_to_rg,     0, {.id = 7} },
        { "mvrg_9",             send_to_rg,     0, {.id = 8} },
+       { "mvrg_next",          send_to_rg_relative,    0, {.id = 1} },
+       { "mvrg_prev",          send_to_rg_relative,    0, {.id = -1} },
        { "mvws_1",             send_to_ws,     0, {.id = 0} },
        { "mvws_2",             send_to_ws,     0, {.id = 1} },
        { "mvws_3",             send_to_ws,     0, {.id = 2} },
@@ -7325,6 +7478,7 @@ struct action {
        { "mvws_22",            send_to_ws,     0, {.id = 21} },
        { "name_workspace",     name_workspace, 0, {0} },
        { "quit",               quit,           0, {0} },
+       { "raise",              raise_focus,    0, {0} },
        { "raise_toggle",       raise_toggle,   0, {0} },
        { "resize",             resize, FN_F_NOREPLAY, {.id = SWM_ARG_ID_DONTCENTER} },
        { "resize_centered",    resize, FN_F_NOREPLAY, {.id = SWM_ARG_ID_CENTER} },
@@ -8062,6 +8216,7 @@ setup_keybindings(void)
        BINDKEY(MODKEY,         XK_k,                   FN_FOCUS_PREV);
        BINDKEY(MODSHIFT,       XK_Tab,                 FN_FOCUS_PREV);
        BINDKEY(MODKEY,         XK_u,                   FN_FOCUS_URGENT);
+       BINDKEY(MODSHIFT,       XK_e,                   FN_FULLSCREEN_TOGGLE);
        BINDKEY(MODKEY,         XK_e,                   FN_MAXIMIZE_TOGGLE);
        BINDKEY(MODSHIFT,       XK_equal,               FN_HEIGHT_GROW);
        BINDKEY(MODSHIFT,       XK_minus,               FN_HEIGHT_SHRINK);
@@ -8107,6 +8262,7 @@ setup_keybindings(void)
        BINDKEY(MODSHIFT,       XK_F12,                 FN_MVWS_22);
        BINDKEY(MODSHIFT,       XK_slash,               FN_NAME_WORKSPACE);
        BINDKEY(MODSHIFT,       XK_q,                   FN_QUIT);
+       BINDKEY(MODKEY,         XK_r,                   FN_RAISE);
        BINDKEY(MODSHIFT,       XK_r,                   FN_RAISE_TOGGLE);
        BINDKEY(MODKEY,         XK_q,                   FN_RESTART);
        BINDKEY(MODKEY,         XK_KP_End,              FN_RG_1);
@@ -9765,6 +9921,16 @@ manage_window(xcb_window_t id, int spawn_pos, bool mapping)
                goto out;
        }
 
+       /* Figure out which region the window belongs to. */
+       r = root_to_region(gr->root, SWM_CK_ALL);
+
+       /* Handle special windows with special _NET_WM_WINDOW_TYPE */
+       if (ewmh_handle_special_types(id, r)) {
+               DNPRINTF(SWM_D_EVENT, "manage_window: "
+                    "unmanaged ewmh window type\n");
+               goto out;
+       }
+
        /* Create and initialize ws_win object. */
        if ((win = calloc(1, sizeof(struct ws_win))) == NULL)
                err(1, "manage_window: calloc: failed to allocate memory for "
@@ -9772,9 +9938,6 @@ manage_window(xcb_window_t id, int spawn_pos, bool mapping)
 
        win->id = id;
 
-       /* Figureout which region the window belongs to. */
-       r = root_to_region(gr->root, SWM_CK_ALL);
-
        /* Ignore window border if there is one. */
        WIDTH(win) = gr->width;
        HEIGHT(win) = gr->height;
@@ -10315,7 +10478,7 @@ get_stack_mode_name(uint8_t mode)
 {
        char    *name;
 
-       switch(mode) {
+       switch (mode) {
        case XCB_STACK_MODE_ABOVE:
                name = "Above";
                break;
@@ -10990,7 +11153,7 @@ reparentnotify(xcb_reparent_notify_event_t *e)
                if (win->state == SWM_WIN_STATE_REPARENTING) {
                        win->state = SWM_WIN_STATE_REPARENTED;
 
-                       if (win->ws->r)
+                       if (win->ws->r && !ICONIC(win))
                                map_window(win);
                        else
                                unmap_window(win);
@@ -11416,7 +11579,6 @@ scan_randr(int idx)
        int                                             ncrtc = 0;
 #endif /* SWM_XRR_HAS_CRTC */
        struct swm_region                               *r;
-       struct ws_win                                   *win;
        int                                             num_screens;
        xcb_randr_get_screen_resources_current_cookie_t src;
        xcb_randr_get_screen_resources_current_reply_t  *srr;
@@ -11491,13 +11653,8 @@ scan_randr(int idx)
                    screen->height_in_pixels);
 
 out:
-       /* Cleanup unused previously visible workspaces. */
+       /* The screen shouldn't focus on unused regions. */
        TAILQ_FOREACH(r, &screens[idx].orl, entry) {
-               TAILQ_FOREACH(win, &r->ws->winlist, entry)
-                       unmap_window(win);
-               r->ws->state = SWM_WS_STATE_HIDDEN;
-
-               /* The screen shouldn't focus on an unused region. */
                if (screens[idx].r_focus == r)
                        screens[idx].r_focus = NULL;
        }
@@ -11509,7 +11666,9 @@ void
 screenchange(xcb_randr_screen_change_notify_event_t *e)
 {
        struct swm_region               *r;
-       int                             i, num_screens;
+       struct workspace                *ws;
+       struct ws_win                   *win;
+       int                             i, j, num_screens;
 
        DNPRINTF(SWM_D_EVENT, "screenchange: root: %#x\n", e->root);
 
@@ -11542,6 +11701,16 @@ screenchange(xcb_randr_screen_change_notify_event_t *e)
                        focus_region(r);
        }
 
+       /* Cleanup unused previously visible workspaces. */
+       for (j = 0; j < workspace_limit; j++) {
+               ws = &screens[i].ws[j];
+               if (ws->r == NULL && ws->state != SWM_WS_STATE_HIDDEN) {
+                       TAILQ_FOREACH(win, &ws->winlist, entry)
+                               unmap_window(win);
+                       ws->state = SWM_WS_STATE_HIDDEN;
+               }
+       }
+
        focus_flush();
 
        /* Update workspace state and bar on all regions. */
@@ -11715,6 +11884,7 @@ setup_screens(void)
                        ws->focus = NULL;
                        ws->focus_prev = NULL;
                        ws->focus_pending = NULL;
+                       ws->focus_raise = NULL;
                        ws->r = NULL;
                        ws->old_r = NULL;
                        ws->state = SWM_WS_STATE_HIDDEN;