Tuesday, March 03, 2015

XCB Minimal Window Manager

In my quest for a good minimal window manager, I have tried almost everything available for linux, including projects on https://github.com     I havent found anything yet that works exactly as I want, but I enjoy hacking on the code to see if I can make them work more to my liking (or work at all).   This is a recent attempt to hack some public domain xcb code to meet my needs.   After my modifications it is only 134 sloc (single lines of code) and does the basic window manager functions I use.   Here is the code:

#include <stdlib.h>
#include <sys/wait.h>
#include <xcb/xcb.h>
#ifdef COMPOSITE
#include <xcb/composite.h>
#endif
int main(int argc,char**argv){
    static const uint32_t di[]={0,0,1440,900},cwa=XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT|XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY|XCB_EVENT_MASK_STRUCTURE_NOTIFY;
    xcb_connection_t*d=xcb_connect(0,0);
    void*p;
    int32_t*x,*y,*tx=0,mx,my,rt=xcb_setup_roots_iterator(xcb_get_setup(d)).data->root,cs[255],*cz=cs+1;
    uint8_t mz,mZ;
    xcb_change_window_attributes(d,rt,XCB_CW_EVENT_MASK,&cwa);
    xcb_grab_key(d,1,rt,0,133,XCB_GRAB_MODE_ASYNC,XCB_GRAB_MODE_ASYNC);
    xcb_grab_button(d,1,rt,XCB_EVENT_MASK_BUTTON_PRESS,XCB_GRAB_MODE_ASYNC,XCB_GRAB_MODE_ASYNC,XCB_NONE,XCB_NONE,XCB_GRAB_ANY,XCB_MOD_MASK_4);
    #ifdef COMPOSITE
    xcb_composite_redirect_subwindows(d,rt,XCB_COMPOSITE_REDIRECT_AUTOMATIC);
    #endif
    xcb_generic_event_t*e=0;
main:xcb_flush(d);
    waitpid(-1,0,WNOHANG);
    noflush:x=y=cz-1;
again:free(e);
    switch((e=xcb_wait_for_event(d))->response_type&127){
    case XCB_BUTTON_PRESS:
        for(;x>cs;x--)
            if(*x==((xcb_button_press_event_t*)e)->child){
                if(((xcb_key_press_event_t*)e)->detail==2)goto pocus;
            case XCB_KEY_PRESS:
                mz=128|((xcb_key_press_event_t*)e)->detail;
                my=((xcb_key_press_event_t*)e)->state;
                goto*(cz==cs+1?&&kcode:&&stack);
            }
        goto noflush;
    case XCB_KEY_RELEASE:
        if(((xcb_key_press_event_t*)e)->detail!=133||!tx)default:goto again;
        xt:x=tx;
        tx=0;
        goto stack;
    case XCB_CONFIGURE_REQUEST:;
        uint32_t c[7];
        p=c;
        for(mz=0;mz<5;mz++)
            if(((xcb_configure_request_event_t*)e)->value_mask&1<<mz){*(uint32_t*)p=*(int16_t*)(((void*)e)+16+mz*2);p+=4;}
        if(((xcb_configure_request_event_t*)e)->value_mask&XCB_CONFIG_WINDOW_SIBLING){*(uint32_t*)p=((xcb_configure_request_event_t*)e)->sibling;p+=4;}
        if(mz=((xcb_configure_request_event_t*)e)->value_mask&XCB_CONFIG_WINDOW_STACK_MODE)*(uint32_t*)p=((xcb_configure_request_event_t*)e)->stack_mode;
        xcb_configure_window(d,((xcb_configure_request_event_t*)e)->window,((xcb_configure_request_event_t*)e)->value_mask,c);
        if(mz){
            p=xcb_query_tree_reply(d,xcb_query_tree_unchecked(d,rt),0);
            int32_t*cl=p+32+((xcb_query_tree_reply_t*)p)->children_len*4;
            for(y=p+32;y<cl;y++){
                for(x=cs+1;x<cz;x++)
                    if(*x==*y)goto nono;
                *y=0;
                nono:;
            }
            x=cs;
            for(y=p+32;y<cl;y++)
                if(*y)*++x=*y;
            free(p);
            goto pocus;
        }else goto main;
    case XCB_MAP_REQUEST:
        p=xcb_get_window_attributes_reply(d,xcb_get_window_attributes_unchecked(d,((xcb_map_request_event_t*)e)->window),0);
        if(((xcb_get_window_attributes_reply_t*)p)->override_redirect)goto freeflush;
        free(p);
        for(;x>cs;x--)
            if(*x==((xcb_map_request_event_t*)e)->window)goto noflush;
        xcb_map_window(d,*cz++=((xcb_map_request_event_t*)e)->window);
        goto hocus;
    if(0)case XCB_MOTION_NOTIFY:xcb_configure_window(d,*x,mZ?XCB_CONFIG_WINDOW_WIDTH|XCB_CONFIG_WINDOW_HEIGHT:XCB_CONFIG_WINDOW_X|XCB_CONFIG_WINDOW_Y,(int32_t[]){mZ&&((xcb_motion_notify_event_t*)e)->root_x<=mx?:((xcb_motion_notify_event_t*)e)->root_x-mx,mZ&&((xcb_motion_notify_event_t*)e)->root_y<=my?:((xcb_motion_notify_event_t*)e)->root_y-my});
    else case XCB_BUTTON_RELEASE:xcb_ungrab_pointer(d,XCB_CURRENT_TIME);
    goto main;
    case XCB_UNMAP_NOTIFY:unmap:goto*(x==cs?&&noflush:*x==((xcb_unmap_notify_event_t*)e)->window&&--cz>cs+1?&&stack:(x--,&&unmap));
    }
mvsz:p=xcb_grab_pointer_reply(d,xcb_grab_pointer_unchecked(d,0,rt,XCB_EVENT_MASK_BUTTON_RELEASE|XCB_EVENT_MASK_POINTER_MOTION,XCB_GRAB_MODE_ASYNC,XCB_GRAB_MODE_ASYNC,XCB_NONE,XCB_NONE,XCB_CURRENT_TIME),0);
    if(((xcb_grab_pointer_reply_t*)p)->status!=XCB_GRAB_STATUS_SUCCESS)goto freeflush;
    free(p);
    p=xcb_get_geometry_reply(d,xcb_get_geometry_unchecked(d,*y),0);
    mx=((xcb_get_geometry_reply_t*)p)->x;
    my=((xcb_get_geometry_reply_t*)p)->y;
    free(p);
    if(mZ=mz!=1)goto noflush;
    p=xcb_query_pointer_reply(d,xcb_query_pointer_unchecked(d,rt),0);
    mx=((xcb_query_pointer_reply_t*)p)->root_x-mx;
    my=((xcb_query_pointer_reply_t*)p)->root_y-my;
freeflush:free(p);
    goto noflush;
stack:mx=*x;
    for(;x!=y;x+=x<y?:-1)*x=x[x<y?:-1];
    *x=mx;
hocus:x=cz-1;
    xcb_configure_window(d,*x,XCB_CONFIG_WINDOW_STACK_MODE,di);
pocus:xcb_set_input_focus(d,XCB_INPUT_FOCUS_POINTER_ROOT,*x,XCB_CURRENT_TIME);
    if(!(mz&128))goto main;
kcode:switch(mz&=127){
    case 1:case 3:goto mvsz;
    case 23:case 49:
        if(cz-cs<3)goto main;
        y=tx;
        tx=mz==23?(y!=cs+1?(y?:x)-1:x):!y||y==x?cs+1:y+1;
        if(y&&y<cz-1)xcb_configure_window(d,*y,XCB_CONFIG_WINDOW_SIBLING|XCB_CONFIG_WINDOW_STACK_MODE,(uint32_t[]){y[mz==23?:-1],mz==23});
        xcb_configure_window(d,*tx,XCB_CONFIG_WINDOW_STACK_MODE,di);
        goto main;
    case 25:goto*(p="firefox&",&&cmd);
    case 26:goto*(p="thunar&",&&cmd);
    case 27:goto*(p="dmenu_run&",&&cmd);
    case 28:goto*(p="urxvt&",&&cmd);
    case 38:goto*(p="xfce4-mixer&",&&cmd);
    case 39:goto*(p="slock&",&&cmd);
    case 53:full:
        if(cz>cs+1)xcb_configure_window(d,*y,XCB_CONFIG_WINDOW_X|XCB_CONFIG_WINDOW_Y|XCB_CONFIG_WINDOW_WIDTH|XCB_CONFIG_WINDOW_HEIGHT,di);
        goto main;
    case 24:shut:
        if(cz==cs+1)goto main;
        if(tx)goto*(mz|=128,&&xt);
        p=xcb_intern_atom_reply(d,xcb_intern_atom_unchecked(d,0,12,"WM_PROTOCOLS"),0);
        mx=((xcb_intern_atom_reply_t*)p)->atom;
        free(p);
        p=xcb_intern_atom_reply(d,xcb_intern_atom_unchecked(d,0,16,"WM_DELETE_WINDOW"),0);
        my=((xcb_intern_atom_reply_t*)p)->atom;
        free(p);
        p=xcb_get_property_reply(d,xcb_get_property_unchecked(d,0,*y,mx,XCB_ATOM_ATOM,0,-1),0);
        xcb_send_event(d,0,*y,XCB_EVENT_MASK_NO_EVENT,(void*)(xcb_client_message_event_t[]){{.response_type=XCB_CLIENT_MESSAGE,.window=*y,.type=mx,.format=32,.data.data32={my,XCB_CURRENT_TIME}}});
        for(mx=(((xcb_get_property_reply_t*)p)->value_len<<2)-4;mx>=0&&*(uint32_t*)(p+sizeof(xcb_get_property_reply_t)+mx)!=my;mx-=4);
        if(mx==-1)killit:xcb_kill_client(d,*y);
        free(p);
        goto main;
    case 9:goto*(cz==cs+1?&&main:(p=0,&&killit));
    case 54:p="urxvt +sb -fn xft:monospace-16 -geometry 32x2+500+500 -e sh -c 'date;sleep 5'&";
    cmd:system(p);
    default:goto main;
    }
}


AND here is what it does:

source code is configured to use the following keys:

WinKey - keycode 133 - Hold windows key and then press any of the following keys:
ESC - 9 - kills client window
q - 24 - asks to close client window normally
w - 25 - launches firefox
e - 26 - launches thunar
r - 27 - launches dmenu_run
t - 28 - lauches urxvt
a - 38 - launches xfce4-mixer
s - 39 - launches slock
x - 53 - maximizes window
c - 54 - shows time for 5 seconds
Tab - 23 - cycles forward
grave (~) - 49 cycles backward

WinKey + mouse button1 moves window
WinKey + mouse button3 resizes window

To compile from a terminal use this command:
gcc -o kcwm kcwm.c -std=gnu99 -lxcb

This is all I need from a window manager and it accomplishes all this with under 2 Meg of ram usage. It also closes windows gracefully, Allows proper memory usage, and allows firefox menus to show.