diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | LICENSE | 2 | ||||
-rw-r--r-- | Makefile | 31 | ||||
-rw-r--r-- | NEWS | 33 | ||||
-rw-r--r-- | TODO | 145 | ||||
-rw-r--r-- | WISHLIST | 2 | ||||
-rw-r--r-- | config.h | 20 | ||||
-rw-r--r-- | hidden.c | 253 | ||||
-rw-r--r-- | hidden.man | 19 | ||||
-rw-r--r-- | mcmenu.c | 517 | ||||
-rw-r--r-- | mcwm.c | 478 | ||||
-rw-r--r-- | mcwm.man | 155 | ||||
-rwxr-xr-x | scripts/9icon | 18 | ||||
-rwxr-xr-x | scripts/mcicon | 6 | ||||
-rwxr-xr-x | scripts/mcmenu | 11 |
15 files changed, 1462 insertions, 232 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eba3c85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*~ +*.o +mcwm +hidden @@ -1,4 +1,4 @@ -Copyright (c) 2010,2011 Michael Cardell Widerkrantz <mc@hack.org> +Copyright (c) 2010,2011,2012 Michael Cardell Widerkrantz <mc@hack.org> Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -1,27 +1,29 @@ -VERSION=20110818 +VERSION=20130209-2 DIST=mcwm-$(VERSION) -SRC=mcwm.c list.c config.h events.h list.h -DISTFILES=LICENSE Makefile NEWS README TODO WISHLIST mcwm.man $(SRC) +SRC=mcwm.c list.c config.h events.h list.h hidden.c +DISTFILES=LICENSE Makefile NEWS README TODO WISHLIST mcwm.man hidden.man scripts $(SRC) -CC=gcc -CFLAGS=-g -std=c99 -Wall -Wextra -O2 -I/usr/local/include #-DDEBUG #-DDMALLOC -LDFLAGS=-L/usr/local/lib -lxcb -lxcb-randr -lxcb-keysyms -lxcb-icccm \ - -lxcb-atom #-ldmalloc +CFLAGS+=-g -std=c99 -Wall -Wextra -I/usr/local/include #-DDEBUG #-DDMALLOC +LDFLAGS+=-L/usr/local/lib -lxcb -lxcb-randr -lxcb-keysyms -lxcb-icccm \ + -lxcb-util #-ldmalloc RM=/bin/rm PREFIX=$(DESTDIR) -TARGETS=mcwm +TARGETS=mcwm hidden OBJS=mcwm.o list.o all: $(TARGETS) mcwm: $(OBJS) - $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) + $(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) -o $@ + +hidden: hidden.c + $(CC) $(CFLAGS) hidden.c $(LDFLAGS) -o $@ mcwm-static: $(OBJS) - $(CC) -o $@ $(OBJS) -static $(CFLAGS) $(LDFLAGS) \ - -lxcb-property -lxcb-event -lXau -lXdmcp + $(CC) $(OBJS) -static $(CFLAGS) $(LDFLAGS) \ + -lXau -lXdmcp -o $@ mcwm.o: mcwm.c events.h list.h config.h Makefile @@ -31,14 +33,19 @@ install: $(TARGETS) install -m 755 mcwm $(PREFIX)/bin install -m 755 -d $(PREFIX)/man/man1/ install -m 644 mcwm.man $(PREFIX)/man/man1/mcwm.1 + install -m 755 hidden $(PREFIX)/bin + install -m 644 hidden.man $(PREFIX)/man/man1/hidden.1 +uninstall: deinstall deinstall: $(RM) $(PREFIX)/bin/mcwm $(RM) $(PREFIX)/man/man1/mcwm.1 + $(RM) $(PREFIX)/bin/hidden + $(RM) $(PREFIX)/man/man1/hidden.1 $(DIST).tar.bz2: mkdir $(DIST) - cp $(DISTFILES) $(DIST)/ + cp -r $(DISTFILES) $(DIST)/ tar cf $(DIST).tar --exclude .git $(DIST) bzip2 -9 $(DIST).tar $(RM) -rf $(DIST) @@ -2,6 +2,39 @@ User visible changes +2013-02-08 + + * New keys: MODKEY + c,v moves to previous or next workspace. Thanks + to Per Cederqvist. + + * New key: Shift-TAB moves backwards in on screen window list. + Thanks to Per Cederqvist. + +2012-03-05 + + * New key: MODKEY + i hides windows. + + * New program: hidden. Lists hidden windows. + +2012-02-15 + + * Added option -i to allow icons. + + * Added scripts: mcmenu (configurable start meny) and 9icon (to map + iconified windows). + +2012-02-07 + + * Check for socket errors. Should prevent busy loops. + +2011-11-22 + + * Now optionally starts programs when MODKEY + mouse buttons are + pressed on root window. See config.h for configuration and the + manual page for an example. + + Also simplified starting programs. + 2011-11-17 * In a ConfigureRequest we no longer obey window movements not @@ -1,20 +1,105 @@ -*- text -*- -* When a window tries to map itself outside the physical screens, - always map it to the screen closest to the coordinates it asked for - instead of always mapping on the first screen in the list. +* IconicState + + Currently we set IconicState only on windows we hide and not when we + unmap them to switch to another desktop. ICCCM and EWMH says we + should set IconicState in both situations. + + We should also set _NET_WM_STATE_HIDDEN when hiding windows. + + hidden.c should list windows with _NET_WM_STATE_HIDDEN and, + optionally, all with IconicState. + +* When unhiding a window we want it to re-appear in the same position. + How? + + Setting XCB_ICCCM_SIZE_HINT_US_POSITION before hiding would probably + work without adding more code, but does ICCCM allow that? + + PPosition? + +* Feature: Handle several screens (DISPLAY=0.x) in classical X. + + This means we will have several root windows. + + setup = xcb_get_setup(conn); + screens = xcb_setup_roots_length(setup); + + returns the number of screens available. + + We can walk over them with xcb_setup_roots_iterator(setup). + +* Feature: Xinerama support. Needed when XRANDR above 1.1 not + supported, for instance with Nvidia cards in Twinview configuration. + +* Bug: Ignore other modifiers, such as NumLock and CapsLock. + + We can use something like this to find the modifier mask for + NumLock: + + xcb_keycode_t *num_lock; + num_lock = xcb_key_symbols_get_keycode(symbols, XK_Num_Lock); + + and get an array of keycodes finished by XCB_NO_SYMBOL. + + then compare the keycodes in the array with the keycodes all the + modifier masks give. See getmodkeys(). + +* Extended Window Manager Hints (EWMH) + + - Use the new xcb-ewmh for the EWMH hints. + + I suggest listing least these in _NET_SUPPORTED (* marks + implemented): + + _NET_NUMBER_OF_DESKTOPS, _NET_WM_DESKTOP*, _NET_CURRENT_DESKTOP, + _NET_WM_STATE, _NET_WM_STATE_STICKY, + _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_HIDDEN, + _NET_WM_STATE_FULLSCREEN, _NET_ACTIVE_WINDOW + + _NET_NUMBER_OF_DESKTOPS can be set to the constant WORKSPACES. + + _NET_CURRENT_DESKTOP is just curws. Set the hint when starting and + when changing workspaces. + + We *may* want to support a message to the root window that *sets* + _NET_CURRENT_DESKTOP and then switch to it. + + We might want to support _NET_WM_WINDOW_TYPE_DESKTOP as well. + +* Bug: We grab MODKEY all the time! We can grab it only when we start + tabbing instead and release it when tabbing is complete. + +* Key to move to previous and next workspace. David Jacobs has a patch + for mod4+shift + I/O. + + https://gist.github.com/1478365 + +* Key to move windows to other workspaces, perhaps mod4+Shift + 0..9 + and mod4+shift + I/O. + +* Full-screen windows that are resized from client requests should be + kept full-screen. + +* When moving windows between monitors, try to place the window on + roughly the same place on a new monitor, if possible. + +* When a window tries to map itself outside the monitors always map it + on the monitor closest to the coordinates it asked for instead of + always mapping on the first monitor in the list. * Feature: We need to continue dragging for move and resize until *both* MODKEY and mouse button has been released. Will be much nicer with trackball. -* Move to first window (or middle of screen) on another screen - with MODKEY + . and , instead of moving windows to new screen. - Shifted these keys will move window to new screen? +* Move to first window (or middle of monitor) on another monitor with + MODKEY + . and , instead of moving windows to new monitor. Shifted + these keys will move window to new monitor? * Handle new modes on physical outputs. What do we have to do? -* A separate workspace list for every physical output. +* A separate workspace list for every monitor. * Allow hexadecimal colour values on command line. @@ -42,27 +127,23 @@ - A window might be on one, *several* or all virtual screens. - We already have a way of fixing a window on all screens (Mod2-f), - but we need a way of saying "stick on this workspace". Perhaps - something like Mod2-a <n>, where <n> is 1--9 for virtual screens. - Better ways? Note that this seems to be mildly incompatible with - the EWMH _NET_WM_DESKTOP hint we're currently using: We will only - be able to save one of the desktops used. - -* Hide windows - - Instead of iconifying, hide them à la 9wm. Even if we use a key to - hide them, this probably means we have to have a menu to get them - back. Perhaps use an external program somehow? Needs to talk to mcwm - anyway. Unix socket? + We already have a way of fixing a window on all screens + (MODKEY-f), but we need a way of saying "stick on this workspace". + Perhaps something like MODKEY-a <n>, where <n> is 1--9 for virtual + screens. Better ways? Note that this seems to be mildly + incompatible with the EWMH _NET_WM_DESKTOP hint we're currently + using: We will only be able to save one of the desktops used. * Menu - We might need a menu for hidden windows (see above). Since I'm - probably implementing menu windows anyway, perhaps I should add a - menu with basic window functions, like 9wm and twm. This way, one - might use the window manager without keyboard, if necessary. Not - much work if I have to do the menu anyway... But also chords? + We might need a menu for hidden windows. Since I'm probably + implementing menu windows anyway, perhaps I should add a menu with + basic window functions, like 9wm and twm. This way, one might use + the window manager without keyboard, if necessary. Not much work if + I have to do the menu anyway... But also chords? + + On the other hand, see hidden.c and forthcoming external menu + program. * Chords @@ -88,8 +169,6 @@ * Configuration file. -* Start configurable programs when clicking on root window. - * Handle Urgency hint Some windows might need attention and marks this with an urgency @@ -97,13 +176,6 @@ the BEL character). Do we want to handle it? How do we tell the user? Can this be done with some stand-alone program instead? -* Support sensible portions of ICCCM and EWMH - - EWMH hints that tells applications about active workspace and - focused window... - - And a few others. - * Code cleaning - Obivous cleanup: The event switch is way too big. @@ -111,9 +183,10 @@ - The states are known everywhere. A tight state machine would be nicer. - - Dispatch table for key bindings instead of keysym->enum->case? + - Dispatch table with function pointers for key bindings instead of + keysym->enum->case? - - Use bitfields instead of extra lists for virtual screens? + - Use bitfields instead of extra lists for workspaces? * Resize behaviour @@ -43,7 +43,7 @@ v Know about physical screens dynamically (RandR). - Snap to border and screen edge, which favours the edge. -- Mouse buttons on root window should start some configurable +v Mouse buttons on root window should start some configurable programs. - Feedback window for size when resizing. @@ -33,6 +33,20 @@ #define TERMINAL "urxvt" /* + * Do we allow windows to be iconified? Set to true if you want this + * behaviour to be default. Can also be set by calling mcwm with -i. + */ +#define ALLOWICONS false + +/* + * Start these programs when pressing MOUSEMODKEY and mouse buttons on + * root window. + */ +#define MOUSE1 "" +#define MOUSE2 "" +#define MOUSE3 "mcmenu" + +/* * Default colour on border for focused windows. Can be set from * command line with "-f colour". */ @@ -49,7 +63,7 @@ /* * Keysym codes for window operations. Look in X11/keysymdefs.h for - * actual symbols. + * actual symbols. Use XK_VoidSymbol to disable a function. */ #define USERKEY_FIX XK_F #define USERKEY_MOVE_LEFT XK_H @@ -61,6 +75,7 @@ #define USERKEY_TERMINAL XK_Return #define USERKEY_MAX XK_X #define USERKEY_CHANGE XK_Tab +#define USERKEY_BACKCHANGE XK_VoidSymbol #define USERKEY_WS1 XK_1 #define USERKEY_WS2 XK_2 #define USERKEY_WS3 XK_3 @@ -71,6 +86,8 @@ #define USERKEY_WS8 XK_8 #define USERKEY_WS9 XK_9 #define USERKEY_WS10 XK_0 +#define USERKEY_PREVWS XK_C +#define USERKEY_NEXTWS XK_V #define USERKEY_TOPLEFT XK_Y #define USERKEY_TOPRIGHT XK_U #define USERKEY_BOTLEFT XK_B @@ -78,3 +95,4 @@ #define USERKEY_DELETE XK_End #define USERKEY_PREVSCREEN XK_comma #define USERKEY_NEXTSCREEN XK_period +#define USERKEY_ICONIFY XK_I diff --git a/hidden.c b/hidden.c new file mode 100644 index 0000000..f2df679 --- /dev/null +++ b/hidden.c @@ -0,0 +1,253 @@ +/* + * hidden - A small program to listen all windows with WM_STATE set to + * Iconic. + * + * Copyright (c) 2012 Michael Cardell Widerkrantz, mc at the domain + * hack.org. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <getopt.h> +#include <xcb/xcb.h> +#include <xcb/xcb_icccm.h> + +xcb_connection_t *conn; +xcb_screen_t *screen; + +xcb_atom_t wm_state; +xcb_atom_t wm_icon_name; + +bool printcommand = false; + +static uint32_t get_wm_state(xcb_drawable_t win); +static int findhidden(void); +static void init(void); +static void cleanup(void); +static xcb_atom_t getatom(char *atom_name); +static void printhelp(void); + +uint32_t get_wm_state(xcb_drawable_t win) +{ + xcb_get_property_reply_t *reply; + xcb_get_property_cookie_t cookie; + uint32_t *statep; + uint32_t state = 0; + + cookie = xcb_get_property(conn, false, win, wm_state, wm_state, 0, + sizeof (int32_t)); + + reply = xcb_get_property_reply(conn, cookie, NULL); + if (NULL == reply) + { + fprintf(stderr, "mcwm: Couldn't get properties for win %d\n", win); + return -1; + } + + /* Length is 0 if we didn't find it. */ + if (0 == xcb_get_property_value_length(reply)) + { + goto bad; + } + + statep = xcb_get_property_value(reply); + state = *statep; + +bad: + free(reply); + return state; +} + +/* + * List all hidden windows. + * + */ +int findhidden(void) +{ + xcb_query_tree_reply_t *reply; + int i; + int len; + xcb_window_t *children; + xcb_get_window_attributes_reply_t *attr; + uint32_t state; + xcb_get_property_cookie_t cookie; + xcb_icccm_get_text_property_reply_t prop; + xcb_generic_error_t *error; + + /* Get all children. */ + reply = xcb_query_tree_reply(conn, + xcb_query_tree(conn, screen->root), 0); + if (NULL == reply) + { + return -1; + } + + len = xcb_query_tree_children_length(reply); + children = xcb_query_tree_children(reply); + + /* List all hidden windows on this root. */ + for (i = 0; i < len; i ++) + { + attr = xcb_get_window_attributes_reply( + conn, xcb_get_window_attributes(conn, children[i]), NULL); + + if (!attr) + { + fprintf(stderr, "Couldn't get attributes for window %d.", + children[i]); + continue; + } + + /* + * Don't bother windows in override redirect mode. + * + * This mode means they wouldn't have been reported to us + * with a MapRequest if we had been running, so in the + * normal case we wouldn't have seen them. + */ + if (!attr->override_redirect) + { + state = get_wm_state(children[i]); + if (state == XCB_ICCCM_WM_STATE_ICONIC) + { + /* + * Example names: + * + * _NET_WM_ICON_NAME(UTF8_STRING) = 0x75, 0x72, 0x78, + * 0x76, 0x74 WM_ICON_NAME(STRING) = "urxvt" + * _NET_WM_NAME(UTF8_STRING) = 0x75, 0x72, 0x78, 0x76, + * 0x74 WM_NAME(STRING) = "urxvt" + */ + cookie = xcb_icccm_get_wm_icon_name(conn, children[i]); + xcb_icccm_get_wm_icon_name_reply(conn, cookie, &prop, &error); + + prop.name[prop.name_len] = '\0'; + if (printcommand) + { + /* FIXME: Need to escape : in prop.name. */ + printf("'%s':'xdotool windowmap 0x%x windowraise 0x%x'\n", + prop.name, children[i], children[i]); + } + else + { + puts(prop.name); + } + } + } /* if not override redirect */ + + free(attr); + } /* for */ + + free(reply); + + return 0; +} + +void init(void) +{ + int scrno; + xcb_screen_iterator_t iter; + + conn = xcb_connect(NULL, &scrno); + if (!conn) + { + fprintf(stderr, "can't connect to an X server\n"); + exit(1); + } + + iter = xcb_setup_roots_iterator(xcb_get_setup(conn)); + + for (int i = 0; i < scrno; ++i) + { + xcb_screen_next(&iter); + } + + screen = iter.data; + + if (!screen) + { + fprintf(stderr, "can't get the current screen\n"); + xcb_disconnect(conn); + exit(1); + } +} + +void cleanup(void) +{ + xcb_disconnect(conn); +} + +/* + * Get a defined atom from the X server. + */ +xcb_atom_t getatom(char *atom_name) +{ + xcb_intern_atom_cookie_t atom_cookie; + xcb_atom_t atom; + xcb_intern_atom_reply_t *rep; + + atom_cookie = xcb_intern_atom(conn, 0, strlen(atom_name), atom_name); + rep = xcb_intern_atom_reply(conn, atom_cookie, NULL); + if (NULL != rep) + { + atom = rep->atom; + free(rep); + return atom; + } + + /* + * XXX Note that we return 0 as an atom if anything goes wrong. + * Might become interesting. + */ + return 0; +} + +void printhelp(void) +{ + printf("hidden: Usage: hidden [-c]\n"); + printf(" -c print 9menu/xdotool compatible output.\n"); +} + +int main(int argc, char **argv) +{ + int ch; /* Option character */ + + while (1) + { + ch = getopt(argc, argv, "c"); + if (-1 == ch) + { + /* No more options, break out of while loop. */ + break; + } + switch (ch) + { + case 'c': + printcommand = true; + break; + + default: + printhelp(); + exit(0); + } /* switch ch */ + } /* while 1 */ + + init(); + wm_state = getatom("WM_STATE"); + findhidden(); + cleanup(); + exit(0); +} diff --git a/hidden.man b/hidden.man new file mode 100644 index 0000000..2a4edeb --- /dev/null +++ b/hidden.man @@ -0,0 +1,19 @@ +.TH hidden 1 "Mar 09, 2012" "" "" +.SH NAME +hidden \- list iconified windows +.SH SYNOPSIS +.B hidden +[ +.B \-c +] + +.SH DESCRIPTION +.B hidden\fP lists all windows on an X server with WM_STATE Iconic. +.SH OPTIONS +.PP +\-c prints a command suitable to get the window back again. + +.SH ENVIRONMENT +.B hidden\fP obeys the $DISPLAY variable. +.SH AUTHOR +Michael Cardell Widerkrantz <mc@hack.org>. diff --git a/mcmenu.c b/mcmenu.c new file mode 100644 index 0000000..a8fdac8 --- /dev/null +++ b/mcmenu.c @@ -0,0 +1,517 @@ +/* + * mcmenu - small menu program using only XCB. + * + * MC, mc at the domain hack.org + * http://hack.org/mc/ + * + * Copyright (c) 2010, 2011, 2012 Michael Cardell Widerkrantz, mc at + * the domain hack.org. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/select.h> + +#include <X11/keysym.h> + +#include <xcb/xcb.h> +#include <xcb/xcb_keysyms.h> + +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +/* + * FIXME calculate height and width. + * How? We get font size *after* opening a window! Resize afterwards? + * + * Can I ask a font about information before using setfont()? I need + * pixel sizes. + * + */ +#define WIDTH 100 +#define HEIGHT 200 + +xcb_connection_t *conn; +xcb_screen_t *screen; + +int sigcode; /* Signal code. Non-zero if we've been + * interruped by a signal. */ +xcb_keycode_t keycode_j; +xcb_keycode_t keycode_k; +xcb_keycode_t keycode_ret; + +struct font +{ + char *name; + /* width, height. */ +}; + +struct window +{ + xcb_window_t id; + xcb_gc_t curfontc; +}; + +static uint32_t getcolor(const char *); +static int setfont(struct window *, const char *, const char *, const char *); +static void printat(struct window *, int16_t, int16_t, const char *); +static struct window *window(int16_t, int16_t, uint16_t, uint16_t); +static void init(void); +static void cleanup(void); +static xcb_keycode_t keysymtokeycode(xcb_keysym_t, xcb_key_symbols_t *); +static void grabkeys(struct window *); +static void printrows(struct window *, char *[], int, int); +static void invertrow(struct window *, char *[], int, int); +static void normalrow(struct window *, char *[], int, int); +static int keypress(xcb_key_press_event_t *, struct window *, char *[], int, + int, int); +static void redraw(struct window *, char *[], int, int, int); + +/* + * Get the pixel values of a named colour colstr. + * + * Returns pixel values. + */ +uint32_t getcolor(const char *colstr) +{ + xcb_alloc_named_color_reply_t *col_reply; + xcb_colormap_t colormap; + xcb_generic_error_t *error; + xcb_alloc_named_color_cookie_t colcookie; + + colormap = screen->default_colormap; + + colcookie = xcb_alloc_named_color(conn, colormap, strlen(colstr), colstr); + + col_reply = xcb_alloc_named_color_reply(conn, colcookie, &error); + if (NULL != error) + { + fprintf(stderr, "mcwm: Couldn't get pixel value for colour %s. " + "Exiting.\n", colstr); + + xcb_disconnect(conn); + exit(1); + } + + return col_reply->pixel; +} + +int setfont(struct window *win, const char *fontname, const char *fg, + const char *bg) +{ + uint32_t fgcol; /* Focused border colour. */ + uint32_t bgcol; /* Focused border colour. */ + xcb_font_t font; + xcb_gcontext_t gc; + uint32_t mask; + uint32_t values[3]; + + fgcol = getcolor(fg); + bgcol = getcolor(bg); + + font = xcb_generate_id(conn); + xcb_open_font(conn, font, strlen(fontname), fontname); + + gc = xcb_generate_id(conn); + mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; + values[0] = fgcol; + values[1] = bgcol; + values[2] = font; + xcb_void_cookie_t cookie = xcb_create_gc_checked(conn, gc, win->id, mask, + values); + + xcb_generic_error_t *error = xcb_request_check(conn, cookie); + if (error) + { + fprintf(stderr, "ERROR: can't print text : %d\n", error->error_code); + xcb_disconnect(conn); + exit(-1); + } + + xcb_close_font(conn, font); + + win->curfontc = gc; + + return 0; +} + +void printat(struct window *win, int16_t x, int16_t y, const char *text) +{ + xcb_void_cookie_t cookie; + xcb_generic_error_t *error; + + cookie = xcb_image_text_8_checked(conn, strlen(text), + win->id, win->curfontc, x, y, text); + + error = xcb_request_check(conn, cookie); + + if (error) + { + fprintf(stderr, "ERROR: can't print text : %d\n", error->error_code); + xcb_disconnect(conn); + exit(-1); + } + + xcb_flush(conn); +} + +struct window *window(int16_t x, int16_t y, uint16_t width, uint16_t height) +{ + struct window *win; + xcb_void_cookie_t cookie; + uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; + uint32_t values[2]; + + win = malloc(sizeof (struct window)); + if (NULL == win) + { + perror("malloc"); + return NULL; + } + + values[0] = screen->black_pixel; + values[1] = XCB_EVENT_MASK_KEY_RELEASE | + XCB_EVENT_MASK_BUTTON_PRESS | + XCB_EVENT_MASK_EXPOSURE | + XCB_EVENT_MASK_POINTER_MOTION; + + win->id = xcb_generate_id(conn); + + cookie = xcb_create_window(conn, screen->root_depth, win->id, screen->root, + x, y, + width, height, + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, + screen->root_visual, + mask, values); + + xcb_map_window(conn, win->id); + + xcb_flush(conn); + + return win; +} + +void init(void) +{ + int scrno; + xcb_screen_iterator_t iter; + + conn = xcb_connect(NULL, &scrno); + if (!conn) + { + fprintf(stderr, "can't connect to an X server\n"); + exit(1); + } + + iter = xcb_setup_roots_iterator(xcb_get_setup(conn)); + + for (int i = 0; i < scrno; ++i) + { + xcb_screen_next(&iter); + } + + screen = iter.data; + + if (!screen) + { + fprintf(stderr, "can't get the current screen\n"); + xcb_disconnect(conn); + exit(1); + } +} + +void cleanup(void) +{ + xcb_disconnect(conn); +} + +/* + * Get a keycode from a keysym. + * + * Returns keycode value. + */ +xcb_keycode_t keysymtokeycode(xcb_keysym_t keysym, xcb_key_symbols_t *keysyms) +{ + xcb_keycode_t *keyp; + xcb_keycode_t key; + + /* We only use the first keysymbol, even if there are more. */ + keyp = xcb_key_symbols_get_keycode(keysyms, keysym); + if (NULL == keyp) + { + fprintf(stderr, "mcmenu: Couldn't look up key. Exiting.\n"); + exit(1); + return 0; + } + + key = *keyp; + free(keyp); + + return key; +} + +void grabkeys(struct window *win) +{ + xcb_key_symbols_t *keysyms; + + /* Get all the keysymbols. */ + keysyms = xcb_key_symbols_alloc(conn); + + keycode_j = keysymtokeycode(XK_J, keysyms); + keycode_k = keysymtokeycode(XK_K, keysyms); + keycode_ret = keysymtokeycode(XK_Return, keysyms); + + xcb_grab_key(conn, 1, win->id, XCB_MOD_MASK_ANY, + keycode_j, + XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + + xcb_grab_key(conn, 1, win->id, XCB_MOD_MASK_ANY, + keycode_k, + XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + + xcb_grab_key(conn, 1, win->id, XCB_MOD_MASK_ANY, + keycode_ret, + XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + + xcb_flush(conn); + + /* Get rid of the key symbols table. */ + xcb_key_symbols_free(keysyms); +} + +/* + * Print some rows of text in window win, stopping at max + * automatically moving fontheight pixels down at a time. + */ +void printrows(struct window *win, char *rows[], int max, int fontheight) +{ + int i; + int y; + + if (0 != setfont(win, "6x13", "white", "black")) + { + fprintf(stderr, "Couldn't set new font.\n"); + exit(1); + } + + for (i = 0, y = fontheight; i < max; i ++, y += fontheight) + { + printat(win, 1, y, rows[i]); + } +} + +/* + * Invert row row (counting from 1). + */ +void invertrow(struct window *win, char *rows[], int row, int fontheight) +{ + int y = fontheight * row; + + if (0 != setfont(win, "6x13", "black", "white")) + { + fprintf(stderr, "Couldn't set new font.\n"); + exit(1); + } + + /* TODO Pad to window width. */ + printat(win, 1, y, rows[row - 1]); +} + +void normalrow(struct window *win, char *rows[], int row, int fontheight) +{ + int y = fontheight * row; + + if (0 != setfont(win, "6x13", "white", "black")) + { + fprintf(stderr, "Couldn't set new font.\n"); + exit(1); + } + + /* TODO Pad to window width. */ + printat(win, 1, y, rows[row - 1]); +} + +int keypress(xcb_key_press_event_t *ev, struct window *win, char *menu[], + int currow, int max, int height) +{ + int oldrow; + + oldrow = currow; + + if (ev->detail == keycode_j) + { + printf("DOWN!\n"); + currow ++; + } + else if (ev->detail == keycode_k) + { + printf("UP!\n"); + currow --; + } + else if (ev->detail == keycode_ret) + { + printf("Choice: %s\n", menu[currow - 1]); + cleanup(); + exit(0); + } + else + { + printf("Unknown key pressed.\n"); + } + + if (currow < 1) + { + currow = max; + } + else if (currow > max) + { + currow = 1; + } + + invertrow(win, menu, currow, height); + + if (currow != oldrow) + { + normalrow(win, menu, oldrow, height); + } + + return currow; +} + +void redraw(struct window *win, char *menu[], int max, int cur, int height) +{ + printrows(win, menu, max, height); + invertrow(win, menu, cur, height); +} + +int main(void) +{ + struct window *win; + char *menu[] = + { + "foo", + "gurka", + "sallad" + }; + int currow = 1; + int maxrow = 3; + char *font = "6x13"; + int fontheight = 13; + xcb_generic_event_t *ev; + int fd; /* Our X file descriptor */ + fd_set in; /* For select */ + int found; /* Ditto. */ + + init(); + + /* + * This creates a new window. I might want to have a window + * created already after init(). The default window, where the + * program was started. Of course, this won't work in X but might + * work in another window systems. + */ + win = window(1, 1, WIDTH, HEIGHT); + if (NULL == win) + { + fprintf(stderr, "Couldn't create window.\n"); + exit(1); + } + + if (0 != setfont(win, font, "white", "black")) + { + fprintf(stderr, "Couldn't set new font.\n"); + exit(1); + } + + grabkeys(win); + + redraw(win, menu, maxrow, currow, fontheight); + + /* Get the file descriptor so we can do select() on it. */ + fd = xcb_get_file_descriptor(conn); + + for (sigcode = 0; 0 == sigcode;) + { + /* Prepare for select(). */ + FD_ZERO(&in); + FD_SET(fd, &in); + + /* + * Check for events, again and again. When poll returns NULL, + * we block on select() until the event file descriptor gets + * readable again. + */ + ev = xcb_poll_for_event(conn); + if (NULL == ev) + { + printf("xcb_poll_for_event() returned NULL.\n"); + + /* Check if we have an unrecoverably error. */ + if (xcb_connection_has_error(conn)) + { + cleanup(); + exit(1); + } + + found = select(fd + 1, &in, NULL, NULL, NULL); + if (-1 == found) + { + if (EINTR == errno) + { + /* We received a signal. Break out of loop. */ + break; + } + else + { + /* Something was seriously wrong with select(). */ + fprintf(stderr, "mcwm: select failed."); + cleanup(); + exit(1); + } + } + else + { + /* We found more events. Goto start of loop. */ + continue; + } + } + + switch (ev->response_type & ~0x80) + { + /* TODO Add mouse events and mouse button click. */ + + case XCB_KEY_PRESS: + { + xcb_key_press_event_t *e = (xcb_key_press_event_t *)ev; + + printf("Key %d pressed\n", e->detail); + + currow = keypress(e, win, menu, currow, maxrow, fontheight); + } + break; + + case XCB_EXPOSE: + redraw(win, menu, maxrow, currow, fontheight); + break; + + default: + printf("Unknown event.\n"); + + } /* switch */ + } /* for */ + + cleanup(); + exit(0); +} @@ -7,8 +7,8 @@ * MC, mc at the domain hack.org * http://hack.org/mc/ * - * Copyright (c) 2010,2011 Michael Cardell Widerkrantz, mc at the - * domain hack.org. + * Copyright (c) 2010, 2011, 2012 Michael Cardell Widerkrantz, mc at + * the domain hack.org. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -45,6 +45,9 @@ #include <X11/keysym.h> +#include <xcb/xproto.h> +#include <xcb/xcb_util.h> + #ifdef DEBUG #include "events.h" #endif @@ -106,6 +109,7 @@ typedef enum { KEY_RET, KEY_X, KEY_TAB, + KEY_BACKTAB, KEY_1, KEY_2, KEY_3, @@ -123,10 +127,12 @@ typedef enum { KEY_END, KEY_PREVSCR, KEY_NEXTSCR, + KEY_ICONIFY, + KEY_PREVWS, + KEY_NEXTWS, KEY_MAX } key_enum_t; - struct monitor { xcb_randr_output_t id; @@ -234,6 +240,7 @@ struct keys { USERKEY_TERMINAL, 0 }, { USERKEY_MAX, 0 }, { USERKEY_CHANGE, 0 }, + { USERKEY_BACKCHANGE, 0 }, { USERKEY_WS1, 0 }, { USERKEY_WS2, 0 }, { USERKEY_WS3, 0 }, @@ -250,7 +257,10 @@ struct keys { USERKEY_BOTRIGHT, 0 }, { USERKEY_DELETE, 0 }, { USERKEY_PREVSCREEN, 0 }, - { USERKEY_NEXTSCREEN, 0 }, + { USERKEY_NEXTSCREEN, 0 }, + { USERKEY_ICONIFY, 0 }, + { USERKEY_PREVWS, 0 }, + { USERKEY_NEXTWS, 0 }, }; /* All keycodes generating our MODKEY mask. */ @@ -272,6 +282,7 @@ struct conf uint32_t focuscol; /* Focused border colour. */ uint32_t unfocuscol; /* Unfocused border colour. */ uint32_t fixedcol; /* Fixed windows border colour. */ + bool allowicons; /* Allow windows to be unmapped. */ } conf; xcb_atom_t atom_desktop; /* @@ -281,6 +292,8 @@ xcb_atom_t atom_desktop; /* */ xcb_atom_t wm_delete_window; /* WM_DELETE_WINDOW event to close windows. */ +xcb_atom_t wm_change_state; +xcb_atom_t wm_state; xcb_atom_t wm_protocols; /* WM_PROTOCOLS. */ @@ -323,10 +336,10 @@ static void raiseorlower(struct client *client); static void movelim(struct client *client); static void movewindow(xcb_drawable_t win, uint16_t x, uint16_t y); static struct client *findclient(xcb_drawable_t win); -static void focusnext(void); +static void focusnext(bool reverse); static void setunfocus(xcb_drawable_t win); static void setfocus(struct client *client); -static int start_terminal(void); +static int start(char *program); static void resizelim(struct client *client); static void moveresize(xcb_drawable_t win, uint16_t x, uint16_t y, uint16_t width, uint16_t height); @@ -339,6 +352,7 @@ static void setborders(struct client *client, int width); static void unmax(struct client *client); static void maximize(struct client *client); static void maxvert(struct client *client); +static void hide(struct client *client); static bool getpointer(xcb_drawable_t win, int16_t *x, int16_t *y); static bool getgeom(xcb_drawable_t win, int16_t *x, int16_t *y, uint16_t *width, uint16_t *height); @@ -355,6 +369,7 @@ static void configurerequest(xcb_configure_request_event_t *e); static void events(void); static void printhelp(void); static void sigcatch(int sig); +static xcb_atom_t getatom(char *atom_name); /* Function bodies. */ @@ -372,7 +387,6 @@ static void sigcatch(int sig); */ void finishtabbing(void) { - mode = 0; if (NULL != lastfocuswin) @@ -503,7 +517,7 @@ void setwmdesktop(xcb_drawable_t win, uint32_t ws) PDEBUG("Changing _NET_WM_DESKTOP on window %d to %d\n", win, ws); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win, - atom_desktop, CARDINAL, 32, 1, + atom_desktop, XCB_ATOM_CARDINAL, 32, 1, &ws); } @@ -521,8 +535,9 @@ int32_t getwmdesktop(xcb_drawable_t win) uint32_t *wsp; uint32_t ws; - cookie = xcb_get_any_property(conn, false, win, atom_desktop, - sizeof (int32_t)); + cookie = xcb_get_property(conn, false, win, atom_desktop, + XCB_GET_PROPERTY_TYPE_ANY, 0, + sizeof (int32_t)); reply = xcb_get_property_reply(conn, cookie, NULL); if (NULL == reply) @@ -737,9 +752,7 @@ uint32_t getcolor(const char *colstr) xcb_alloc_named_color_cookie_t colcookie; colormap = screen->default_colormap; - colcookie = xcb_alloc_named_color(conn, colormap, strlen(colstr), colstr); - col_reply = xcb_alloc_named_color_reply(conn, colcookie, &error); if (NULL != error) { @@ -826,7 +839,6 @@ void forgetwin(xcb_window_t win) } free(item->data); - delitem(&winlist, item); return; @@ -925,7 +937,8 @@ void fitonscreen(struct client *client) willmove = true; willresize = true; } - else if (client->x + client->width + conf.borderwidth * 2 > mon_x + mon_width) + else if (client->x + client->width + conf.borderwidth * 2 + > mon_x + mon_width) { client->x = mon_x + mon_width - (client->width + conf.borderwidth * 2); willmove = true; @@ -938,10 +951,12 @@ void fitonscreen(struct client *client) willmove = true; willresize = true; } - else if (client->y + client->height + conf.borderwidth * 2 > mon_y + mon_height) + else if (client->y + client->height + conf.borderwidth * 2 + > mon_y + mon_height) { - client->y = mon_y + mon_height - (client->height + conf.borderwidth * 2); - willmove = true; + client->y = mon_y + mon_height - (client->height + conf.borderwidth + * 2); + willmove = true; } if (willmove) @@ -963,8 +978,6 @@ void fitonscreen(struct client *client) */ void newwin(xcb_window_t win) { - int16_t pointx; - int16_t pointy; struct client *client; if (NULL != findclient(win)) @@ -978,14 +991,6 @@ void newwin(xcb_window_t win) return; } - /* Get pointer position so we can move the window to the cursor. */ - if (!getpointer(screen->root, &pointx, &pointy)) - { - PDEBUG("Failed to get pointer coords!\n"); - pointx = 0; - pointy = 0; - } - /* * Set up stuff, like borders, add the window to the client list, * et cetera. @@ -1002,12 +1007,23 @@ void newwin(xcb_window_t win) /* * If the client doesn't say the user specified the coordinates - * for the window we store it where our pointer is instead. + * for the window we map it where our pointer is instead. */ if (!client->usercoord) { + int16_t pointx; + int16_t pointy; PDEBUG("Coordinates not set by user. Using pointer: %d,%d.\n", pointx, pointy); + + /* Get pointer position so we can move the window to the cursor. */ + if (!getpointer(screen->root, &pointx, &pointy)) + { + PDEBUG("Failed to get pointer coords!\n"); + pointx = 0; + pointy = 0; + } + client->x = pointx; client->y = pointy; @@ -1021,7 +1037,7 @@ void newwin(xcb_window_t win) /* Find the physical output this window will be on if RANDR is active. */ if (-1 != randrbase) { - client->monitor = findmonbycoord(pointx, pointy); + client->monitor = findmonbycoord(client->x, client->y); if (NULL == client->monitor) { /* @@ -1040,6 +1056,11 @@ void newwin(xcb_window_t win) /* Show window on screen. */ xcb_map_window(conn, client->id); + /* Declare window normal. */ + long data[] = { XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE }; + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->id, + wm_state, wm_state, 32, 2, data); + /* * Move cursor into the middle of the window so we don't lose the * pointer to another window. @@ -1136,8 +1157,8 @@ struct client *setupwin(xcb_window_t win) /* * Get the window's incremental size step, if any. */ - if (!xcb_get_wm_normal_hints_reply( - conn, xcb_get_wm_normal_hints_unchecked( + if (!xcb_icccm_get_wm_normal_hints_reply( + conn, xcb_icccm_get_wm_normal_hints_unchecked( conn, win), &hints, NULL)) { PDEBUG("Couldn't get size hints.\n"); @@ -1147,25 +1168,25 @@ struct client *setupwin(xcb_window_t win) * The user specified the position coordinates. Remember that so * we can use geometry later. */ - if (hints.flags & XCB_SIZE_HINT_US_POSITION) + if (hints.flags & XCB_ICCCM_SIZE_HINT_US_POSITION) { client->usercoord = true; } - if (hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) + if (hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) { client->min_width = hints.min_width; client->min_height = hints.min_height; } - if (hints.flags & XCB_SIZE_HINT_P_MAX_SIZE) + if (hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) { client->max_width = hints.max_width; client->max_height = hints.max_height; } - if (hints.flags & XCB_SIZE_HINT_P_RESIZE_INC) + if (hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC) { client->width_inc = hints.width_inc; client->height_inc = hints.height_inc; @@ -1174,7 +1195,7 @@ struct client *setupwin(xcb_window_t win) client->height_inc); } - if (hints.flags & XCB_SIZE_HINT_BASE_SIZE) + if (hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) { client->base_width = hints.base_width; client->base_height = hints.base_height; @@ -1252,6 +1273,12 @@ int setupkeys(void) /* Now grab the rest of the keys with the MODKEY modifier. */ for (i = KEY_F; i < KEY_MAX; i ++) { + if (XK_VoidSymbol == keys[i].keysym) + { + keys[i].keycode = 0; + continue; + } + keys[i].keycode = keysymtokeycode(keys[i].keysym, keysyms); if (0 == keys[i].keycode) { @@ -1441,7 +1468,7 @@ int setuprandr(void) extension = xcb_get_extension_data(conn, &xcb_randr_id); if (!extension->present) { - printf("No RANDR.\n"); + PDEBUG("No RANDR extension.\n"); return -1; } else @@ -1866,7 +1893,8 @@ void movelim(struct client *client) if (client->y + client->height > mon_y + mon_height - conf.borderwidth * 2) { - client->y = (mon_y + mon_height - conf.borderwidth * 2) - client->height; + client->y = (mon_y + mon_height - conf.borderwidth * 2) + - client->height; } movewindow(client->id, client->x, client->y); @@ -1890,11 +1918,10 @@ void movewindow(xcb_drawable_t win, uint16_t x, uint16_t y) | XCB_CONFIG_WINDOW_Y, values); xcb_flush(conn); - } /* Change focus to next in window ring. */ -void focusnext(void) +void focusnext(bool reverse) { struct client *client = NULL; @@ -1938,25 +1965,54 @@ void focusnext(void) } else { - if (NULL == focuswin->wsitem[curws]->next) + if (reverse) { - /* - * We were at the end of list. Focusing on first window in - * list unless we were already there. - */ - if (focuswin->wsitem[curws] != wslist[curws]->data) + if (NULL == focuswin->wsitem[curws]->prev) { - PDEBUG("End of list. Focusing first in list: %p\n", - wslist[curws]); - client = wslist[curws]->data; + /* + * We were at the head of list. Focusing on last + * window in list unless we were already there. + */ + struct item *last = wslist[curws]; + while (NULL != last->next) + last = last->next; + if (focuswin->wsitem[curws] != last->data) + { + PDEBUG("Beginning of list. Focusing last in list: %p\n", + last); + client = last->data; + } + } + else + { + /* Otherwise, focus the next in list. */ + PDEBUG("Tabbing. Focusing next: %p.\n", + focuswin->wsitem[curws]->prev); + client = focuswin->wsitem[curws]->prev->data; } } else { - /* Otherwise, focus the next in list. */ - PDEBUG("Tabbing. Focusing next: %p.\n", - focuswin->wsitem[curws]->next); - client = focuswin->wsitem[curws]->next->data; + if (NULL == focuswin->wsitem[curws]->next) + { + /* + * We were at the end of list. Focusing on first window in + * list unless we were already there. + */ + if (focuswin->wsitem[curws] != wslist[curws]->data) + { + PDEBUG("End of list. Focusing first in list: %p\n", + wslist[curws]); + client = wslist[curws]->data; + } + } + else + { + /* Otherwise, focus the next in list. */ + PDEBUG("Tabbing. Focusing next: %p.\n", + focuswin->wsitem[curws]->next); + client = focuswin->wsitem[curws]->next->data; + } } } /* if NULL focuswin */ @@ -2085,15 +2141,10 @@ void setfocus(struct client *client) focuswin = client; } -/* - * Start a program specified in conf.terminal. - * - * Returns 0 on success. - */ -int start_terminal(void) +int start(char *program) { pid_t pid; - + pid = fork(); if (-1 == pid) { @@ -2102,10 +2153,10 @@ int start_terminal(void) } else if (0 == pid) { - pid_t termpid; - - /* In our first child. */ + char *argv[2]; + /* In the child. */ + /* * Make this process a new process leader, otherwise the * terminal will die when the wm dies. Also, this makes any @@ -2117,39 +2168,15 @@ int start_terminal(void) exit(1); } - /* - * Fork again for the terminal process. This way, the wm won't - * know anything about it. - */ - termpid = fork(); - if (-1 == termpid) + argv[0] = program; + argv[1] = NULL; + + if (-1 == execvp(program, argv)) { - perror("fork"); + perror("execve"); exit(1); } - else if (0 == termpid) - { - char *argv[2]; - - /* In the second child, now starting terminal. */ - - argv[0] = conf.terminal; - argv[1] = NULL; - - if (-1 == execvp(conf.terminal, argv)) - { - perror("execve"); - exit(1); - } - } /* second child */ - - /* Exit our first child so the wm can pick up and continue. */ exit(0); - } /* first child */ - else - { - /* Wait for the first forked process to exit. */ - waitpid(pid, NULL, 0); } return 0; @@ -2189,14 +2216,16 @@ void resizelim(struct client *client) client->width = client->min_width; } - if (client->x + client->width + conf.borderwidth * 2 > mon_x + mon_width) + if (client->x + client->width + conf.borderwidth * 2 > mon_x + mon_width) { - client->width = mon_width - ((client->x - mon_x) + conf.borderwidth * 2); + client->width = mon_width - ((client->x - mon_x) + conf.borderwidth + * 2); } if (client->y + client->height + conf.borderwidth * 2 > mon_y + mon_height) { - client->height = mon_height - ((client->y - mon_y) + conf.borderwidth * 2); + client->height = mon_height - ((client->y - mon_y) + conf.borderwidth + * 2); } resize(client->id, client->width, client->height); @@ -2419,8 +2448,9 @@ void movestep(struct client *client, char direction) * If the pointer was inside the window to begin with, move * pointer back to where it was, relative to the window. */ - if (start_x > 0 - conf.borderwidth && start_x < client->width + conf.borderwidth - && start_y > 0 - conf.borderwidth && start_y < client->height + conf.borderwidth ) + if (start_x > 0 - conf.borderwidth && start_x < client->width + + conf.borderwidth && start_y > 0 - conf.borderwidth && start_y + < client->height + conf.borderwidth) { xcb_warp_pointer(conn, XCB_NONE, client->id, 0, 0, 0, 0, start_x, start_y); @@ -2632,13 +2662,28 @@ void maxvert(struct client *client) client->vertmaxed = true; } +void hide(struct client *client) +{ + long data[] = { XCB_ICCCM_WM_STATE_ICONIC, XCB_NONE }; + + /* + * Unmap window and declare iconic. + * + * Unmapping will generate an UnmapNotify event so we can forget + * about the window later. + */ + xcb_unmap_window(conn, client->id); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->id, + wm_state, wm_state, 32, 2, data); + xcb_flush(conn); +} + bool getpointer(xcb_drawable_t win, int16_t *x, int16_t *y) { xcb_query_pointer_reply_t *pointer; - pointer = xcb_query_pointer_reply( - conn, xcb_query_pointer(conn, win), 0); - + pointer + = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, win), 0); if (NULL == pointer) { return false; @@ -2657,9 +2702,8 @@ bool getgeom(xcb_drawable_t win, int16_t *x, int16_t *y, uint16_t *width, { xcb_get_geometry_reply_t *geom; - geom = xcb_get_geometry_reply(conn, - xcb_get_geometry(conn, win), NULL); - + geom + = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL); if (NULL == geom) { return false; @@ -2758,7 +2802,6 @@ void topright(void) xcb_flush(conn); } - void botleft(void) { int16_t pointx; @@ -2793,10 +2836,9 @@ void botleft(void) } focuswin->x = mon_x; - focuswin->y = mon_y + mon_height - (focuswin->height + conf.borderwidth * 2); - + focuswin->y = mon_y + mon_height - (focuswin->height + conf.borderwidth + * 2); movewindow(focuswin->id, focuswin->x, focuswin->y); - xcb_warp_pointer(conn, XCB_NONE, focuswin->id, 0, 0, 0, 0, pointx, pointy); xcb_flush(conn); @@ -2839,11 +2881,9 @@ void botright(void) } focuswin->x = mon_x + mon_width - (focuswin->width + conf.borderwidth * 2); - - focuswin->y = mon_y + mon_height - (focuswin->height + conf.borderwidth * 2); - + focuswin->y = mon_y + mon_height - (focuswin->height + conf.borderwidth + * 2); movewindow(focuswin->id, focuswin->x, focuswin->y); - xcb_warp_pointer(conn, XCB_NONE, focuswin->id, 0, 0, 0, 0, pointx, pointy); xcb_flush(conn); @@ -2852,7 +2892,7 @@ void botright(void) void deletewin(void) { xcb_get_property_cookie_t cookie; - xcb_get_wm_protocols_reply_t protocols; + xcb_icccm_get_wm_protocols_reply_t protocols; bool use_delete = false; uint32_t i; @@ -2862,14 +2902,20 @@ void deletewin(void) } /* Check if WM_DELETE is supported. */ - cookie = xcb_get_wm_protocols_unchecked(conn, focuswin->id, wm_protocols); - if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) == 1) { + cookie = xcb_icccm_get_wm_protocols_unchecked(conn, focuswin->id, + wm_protocols); + if (xcb_icccm_get_wm_protocols_reply(conn, cookie, &protocols, NULL) == 1) + { for (i = 0; i < protocols.atoms_len; i++) + { if (protocols.atoms[i] == wm_delete_window) + { use_delete = true; + } + } } - xcb_get_wm_protocols_reply_wipe(&protocols); + xcb_icccm_get_wm_protocols_reply_wipe(&protocols); if (use_delete) { @@ -2954,9 +3000,10 @@ void handle_keypress(xcb_key_press_event_t *ev) for (key = KEY_MAX, i = KEY_F; i < KEY_MAX; i ++) { - if (ev->detail == keys[i].keycode) + if (ev->detail == keys[i].keycode && 0 != keys[i].keycode) { key = i; + break; } } if (key == KEY_MAX) @@ -2973,7 +3020,7 @@ void handle_keypress(xcb_key_press_event_t *ev) return; } - if (MCWM_TABBING == mode && key != KEY_TAB) + if (MCWM_TABBING == mode && key != KEY_TAB && key != KEY_BACKTAB) { /* First finish tabbing around. Then deal with the next key. */ finishtabbing(); @@ -3000,6 +3047,10 @@ void handle_keypress(xcb_key_press_event_t *ev) resizestep(focuswin, 'l'); break; + case KEY_TAB: /* shifted tab counts as backtab */ + focusnext(true); + break; + default: /* Ignore other shifted keys. */ break; @@ -3010,7 +3061,7 @@ void handle_keypress(xcb_key_press_event_t *ev) switch (key) { case KEY_RET: /* return */ - start_terminal(); + start(conf.terminal); break; case KEY_F: /* f */ @@ -3034,7 +3085,11 @@ void handle_keypress(xcb_key_press_event_t *ev) break; case KEY_TAB: /* tab */ - focusnext(); + focusnext(false); + break; + + case KEY_BACKTAB: /* backtab */ + focusnext(true); break; case KEY_M: /* m */ @@ -3117,6 +3172,28 @@ void handle_keypress(xcb_key_press_event_t *ev) nextscreen(); break; + case KEY_ICONIFY: + if (conf.allowicons) + { + hide(focuswin); + } + break; + + case KEY_PREVWS: + if (curws > 0) + { + changeworkspace(curws - 1); + } + else + { + changeworkspace(WORKSPACES - 1); + } + break; + + case KEY_NEXTWS: + changeworkspace((curws + 1) % WORKSPACES); + break; + default: /* Ignore other keys. */ break; @@ -3373,17 +3450,29 @@ void events(void) FD_SET(fd, &in); /* - * Check for events, again and again. When poll returns NULL, - * we block on select() until the event file descriptor gets - * readable again. + * Check for events, again and again. When poll returns NULL + * (and it does that a lot), we block on select() until the + * event file descriptor gets readable again. * * We do it this way instead of xcb_wait_for_event() since - * select() will return if we we're interrupted by a signal. - * We like that. + * select() will return if we were interrupted by a signal. We + * like that. */ ev = xcb_poll_for_event(conn); if (NULL == ev) { + PDEBUG("xcb_poll_for_event() returned NULL.\n"); + + /* + * Check if we have an unrecoverable connection error, + * like a disconnected X server. + */ + if (xcb_connection_has_error(conn)) + { + cleanup(0); + exit(1); + } + found = select(fd + 1, &in, NULL, NULL, NULL); if (-1 == found) { @@ -3486,6 +3575,32 @@ void events(void) e->detail, (long)e->event, e->child, e->event_x, e->event_y); + if (0 == e->child) + { + /* Mouse click on root window. Start programs? */ + + switch (e->detail) + { + case 1: /* Mouse button one. */ + start(MOUSE1); + break; + + case 2: /* Middle mouse button. */ + start(MOUSE2); + break; + + case 3: /* Mouse button three. */ + start(MOUSE3); + break; + + default: + break; + } /* switch */ + + /* Break out of event switch. */ + break; + } + /* * If we don't have any currently focused window, we can't * do anything. We don't want to do anything if the mouse @@ -3498,11 +3613,6 @@ void events(void) } /* - * XXX if 0 == e->child, we're on the root window. Do - * something on the root when mouse buttons are pressed? - */ - - /* * If middle button was pressed, raise window or lower * it if it was already on top. */ @@ -3877,6 +3987,30 @@ void events(void) configurerequest((xcb_configure_request_event_t *) ev); break; + case XCB_CLIENT_MESSAGE: + { + xcb_client_message_event_t *e + = (xcb_client_message_event_t *)ev; + + if (conf.allowicons) + { + if (e->type == wm_change_state + && e->format == 32 + && e->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) + { + long data[] = { XCB_ICCCM_WM_STATE_ICONIC, XCB_NONE }; + + /* Unmap window and declare iconic. */ + + xcb_unmap_window(conn, e->window); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, e->window, + wm_state, wm_state, 32, 2, data); + xcb_flush(conn); + } + } /* if */ + } + break; + case XCB_CIRCULATE_REQUEST: { xcb_circulate_request_event_t *e @@ -3990,6 +4124,31 @@ void sigcatch(int sig) sigcode = sig; } +/* + * Get a defined atom from the X server. + */ +xcb_atom_t getatom(char *atom_name) +{ + xcb_intern_atom_cookie_t atom_cookie; + xcb_atom_t atom; + xcb_intern_atom_reply_t *rep; + + atom_cookie = xcb_intern_atom(conn, 0, strlen(atom_name), atom_name); + rep = xcb_intern_atom_reply(conn, atom_cookie, NULL); + if (NULL != rep) + { + atom = rep->atom; + free(rep); + return atom; + } + + /* + * XXX Note that we return 0 as an atom if anything goes wrong. + * Might become interesting. + */ + return 0; +} + int main(int argc, char **argv) { uint32_t mask = 0; @@ -4001,9 +4160,18 @@ int main(int argc, char **argv) char *focuscol; char *unfocuscol; char *fixedcol; - + int scrno; + xcb_screen_iterator_t iter; + /* Install signal handlers. */ + /* We ignore child exists. Don't create zombies. */ + if (SIG_ERR == signal(SIGCHLD, SIG_IGN)) + { + perror("mcwm: signal"); + exit(1); + } + if (SIG_ERR == signal(SIGINT, sigcatch)) { perror("mcwm: signal"); @@ -4020,13 +4188,14 @@ int main(int argc, char **argv) conf.borderwidth = BORDERWIDTH; conf.terminal = TERMINAL; + conf.allowicons = ALLOWICONS; focuscol = FOCUSCOL; unfocuscol = UNFOCUSCOL; fixedcol = FIXEDCOL; while (1) { - ch = getopt(argc, argv, "b:t:f:u:x:"); + ch = getopt(argc, argv, "b:it:f:u:x:"); if (-1 == ch) { @@ -4041,6 +4210,10 @@ int main(int argc, char **argv) conf.borderwidth = atoi(optarg); break; + case 'i': + conf.allowicons = true; + break; + case 't': conf.terminal = optarg; break; @@ -4062,16 +4235,33 @@ int main(int argc, char **argv) exit(0); } /* switch */ } - - conn = xcb_connect(NULL, NULL); + + /* + * Use $DISPLAY. After connecting scrno will contain the value of + * the display's screen. + */ + conn = xcb_connect(NULL, &scrno); if (xcb_connection_has_error(conn)) { perror("xcb_connect"); exit(1); } - - /* Get the first screen */ - screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; + + /* Find our screen. */ + iter = xcb_setup_roots_iterator(xcb_get_setup(conn)); + for (int i = 0; i < scrno; ++ i) + { + xcb_screen_next(&iter); + } + + screen = iter.data; + if (!screen) + { + fprintf (stderr, "mcwm: Can't get the current screen. Exiting.\n"); + xcb_disconnect(conn); + exit(1); + } + root = screen->root; PDEBUG("Screen size: %dx%d\nRoot window: %d\n", screen->width_in_pixels, @@ -4083,17 +4273,20 @@ int main(int argc, char **argv) conf.fixedcol = getcolor(fixedcol); /* Get some atoms. */ - atom_desktop = xcb_atom_get(conn, "_NET_WM_DESKTOP"); - wm_delete_window = xcb_atom_get(conn, "WM_DELETE_WINDOW"); - wm_protocols = xcb_atom_get(conn, "WM_PROTOCOLS"); - + atom_desktop = getatom("_NET_WM_DESKTOP"); + wm_delete_window = getatom("WM_DELETE_WINDOW"); + wm_change_state = getatom("WM_CHANGE_STATE"); + wm_state = getatom("WM_STATE"); + wm_protocols = getatom("WM_PROTOCOLS"); + /* Check for RANDR extension and configure. */ randrbase = setuprandr(); /* Loop over all clients and set up stuff. */ if (0 != setupscreen()) { - fprintf(stderr, "Failed to initialize windows. Exiting.\n"); + fprintf(stderr, "mcwm: Failed to initialize windows. Exiting.\n"); + xcb_disconnect(conn); exit(1); } @@ -4101,6 +4294,7 @@ int main(int argc, char **argv) if (0 != setupkeys()) { fprintf(stderr, "mcwm: Couldn't set up keycodes. Exiting."); + xcb_disconnect(conn); exit(1); } @@ -4111,7 +4305,7 @@ int main(int argc, char **argv) XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, 1 /* left mouse button */, MOUSEMODKEY); - + xcb_grab_button(conn, 0, root, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, @@ -1,11 +1,14 @@ -.TH mcwm 1 "Nov 07, 2011" "" "" +.TH mcwm 1 "Apr 30, 2012" "" "" .SH NAME mcwm \- MC's Window Manager for X11. .SH SYNOPSIS .B mcwm [ .B \-b -] width +width ] +[ +.B \-i +] [ .B \-t .I terminal-program @@ -27,6 +30,12 @@ mcwm \- MC's Window Manager for X11. .PP \-b width sets border width to this many pixels. .PP +\-i turns on icons/hidden windows. +.B Please note +that there is no way from mcwm to get a hidden window back! You have +to use an external program such as a panel or the mcicon or 9icon +scripts (see below) to get the window mapped again. +.PP \-t urxvt will start urxvt when MODKEY + Return is pressed. Change to your prefered terminal program or something else entirely. .PP @@ -65,44 +74,57 @@ Note that the mouse cursor needs to be inside the window you want to move, raise/lower or resize even if it currently has the focus. This is a feature, not a bug. .PP -Mod4 + key: +Mod4 + key on focused window: .RS .IP \(bu 2 .B r -raise or lower (toggles) +raise or lower (toggles). .IP \(bu 2 .B x -maximize (toggles) +maximize (toggles). .IP \(bu 2 .B m -maximize vertically (toggles) +maximize vertically (toggles). +.IP \(bu 2 +.B H +resize left. +.IP \(bu 2 +.B J +resize down. +.IP \(bu 2 +.B K +resize up. +.IP \(bu 2 +.B L +resize right. .IP \(bu 2 .B h -move left +move left. .IP \(bu 2 .B j -move down +move down. .IP \(bu 2 .B k -move up +move up. .IP \(bu 2 .B l -move right +move right. .IP \(bu 2 -.B H -resize left +.B y +move to upper left corner of monitor. .IP \(bu 2 -.B J -resize down +.B u +move to upper right corner of monitor. .IP \(bu 2 -.B K -resize up +.B b +move to lower left corner of monitor. .IP \(bu 2 -.B L -resize right +.B n +move to lower right corner of monitor. .IP \(bu 2 .B Return -start terminal +start terminal or whatever program you have configured with -t or in +the config.h. .IP \(bu 2 .B Tab go to next window in the current workspace window ring. If you release @@ -110,35 +132,41 @@ MODKEY or press another command key mcwm will change focus to the new window. A new press of MODKEY + Tab will bring you back to the window where you last had focus. .IP \(bu 2 -.B f -fix window so it is visible on all workspaces. Toggles. Press again to -unfix window. Also used to move windows between workspaces: First fix -the window, change to the workspace you want, then unfix the window on -the new workspace. -.IP \(bu 2 -.B y -move to upper left corner of physical screen. -.IP \(bu 2 -.B u -move to upper right corner of physical screen.. +.B Shift-Tab +go to previous window in the current workspace window ring. This is +most useful while you are tabbing: if you accidentally pressed Tab one +time too many, you can move back by pressing Shift + TAB (all the +while holding down the MODKEY). .IP \(bu 2 -.B b -move to lower left corner of physical screen.. +.B f +fix window so it is visible on all workspaces (toggles). Note that +this is also used to move windows between workspaces: First fix the +window, change to the workspace you want, then unfix the window on the +new workspace. .IP \(bu 2 -.B n -move to lower right corner of physical screen.. +.B i +iconify (or hide) window from the display. Only usable when mcwm has +been started with -i. Currently there is no way to get a hidden window +back. You have to use an external program such as a panel or the +mcicon or 9icon script in the mcwm distribution. .IP \(bu 2 .B 0\-9 go to workspace n, 0-9. .IP \(bu 2 +.B c +go to previous workspace. +.IP \(bu 2 +.B v +go to next workspace. +.IP \(bu 2 .B End -close focused window. +close window. .IP \(bu 2 .B , -move window to previous physical screen. +move window to previous monitor. .IP \(bu 2 .B . -move window to next physical screen. +move window to next monitor. .RE .PP Note that all functions activated from the keyboard work on the @@ -147,8 +175,12 @@ cursor. Of course, changing workspaces has nothing to do with the focused window. .PP If you don't like the default key bindings, border width, et cetera, -look in the config.h file, change and recompile. -.PP +look in the config.h file, change and recompile. In the config.h file +you can also define mouse button actions on the root window. By +default button 3 starts the command mcmenu. You can write your own +mcmenu by using, for instance, 9menu, dmenu or ratmenu. +.SH ENVIRONMENT +.B mcwm\fP obeys the $DISPLAY variable. .SH STARTING Typically the window manager is started from a script, either run by .B startx(1) @@ -174,10 +206,55 @@ xrdb \-load ~/.Xresources # Start window manager in the background. If it dies, X still lives. mcwm & +# If you want to allow windows to be hidden, use this instead: +# mcwm -i & + # Start a terminal in the foreground. If this dies, X dies. exec urxvt .fi .in -4 .sp +.SH SCRIPTS +You may want to define a menu program for use with mcwm (see +config.h). In the source distribution you can find an example as +mcmenu (the default menu program in config.h) in the scripts +directory. +.PP +Christian Neukirchen wrote a little script you can use to get +iconified windows mapped again if you are running mcwm in allow icons +mode (-i). You need awk, xdotool, xprop and xwininfo installed. You +can find the script as scripts/9icon. +.PP +Inspired by Christian's work I wrote a small program, hidden(1), which +is included with mcwm. You can use hidden(1) with the -c option +together with 9menu. See scripts/mcicon for an example. +.PP +You might also be interested in the following shell function that +might come in handy to give your terminal emulators good titles before +hiding them. +.sp +.in +4 +.nf +# Set the title and icon name of an xterm or clone. +function title +{ + # icon name + echo -e '\\033]1;'$1'\\007' + # title + echo -e '\\033]2;'$1'\\007' +} +.fi +.in -4 +.sp +Use it like this: +.sp +.in +4 +.nf +% title 'really descriptive title' +.fi +.in -4 +.sp +.SH SEE ALSO +.B hidden(1) .SH AUTHOR Michael Cardell Widerkrantz <mc@hack.org>. diff --git a/scripts/9icon b/scripts/9icon new file mode 100755 index 0000000..73edc6e --- /dev/null +++ b/scripts/9icon @@ -0,0 +1,18 @@ +#! /bin/sh + +# 9icon - show 9menu of hidden windows for mapping +# Originally by Christian Neukirchen <chneukirchen@gmail.com> and +# slightly changed by MC. + +IFS=" +" + +for win in $(xwininfo -root -children | awk '$1~/0x/ && $2~/"/ {print $1}'); do + xprop -id $win WM_NAME WM_STATE | + awk -F'"' -v win=$win ' + /^WM_NAME/ { name=$2 } + /window state: Iconic/ { + print "'\''" name "'\''" "'\''" ":xdotool windowmap " win " windowraise " win "'\''" + } + ' +done | xargs 9menu -popup -label 9icon -bg grey20 -fg grey80 -font fixed diff --git a/scripts/mcicon b/scripts/mcicon new file mode 100755 index 0000000..f002a55 --- /dev/null +++ b/scripts/mcicon @@ -0,0 +1,6 @@ +#! /bin/sh + +# mcicon - list iconified windows in menu and map the chosen one. +# Needs hidden (distributed with mcwm), 9menu and xdotool. + +hidden -c | xargs 9menu -popup -label 9icon -bg grey20 -fg grey80 -font 9x15 diff --git a/scripts/mcmenu b/scripts/mcmenu new file mode 100755 index 0000000..6d32940 --- /dev/null +++ b/scripts/mcmenu @@ -0,0 +1,11 @@ +#! /bin/sh +exec 9menu -bg grey20 -fg grey80 -popup \ + 'hide:xdotool selectwindow windowminimize' \ + 'cpu:urxvt -e ssh cpu.example.org' \ + ':' \ + 'VGA On: xrandr --output VGA --on' \ + 'VGA Off: xrandr --output VGA --off' \ + ':' \ + 'HHKB: xkbcomp -I$HOME/conf/xkb $HOME/conf/xkb/hhkb.xkb $DISPLAY' \ + ':' \ + 'close:' |