Created
April 16, 2012 19:29
-
-
Save Cloudef/2400943 to your computer and use it in GitHub Desktop.
monsterwm pandora (vi mode and systray)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* see LICENSE for copyright and license */ | |
#ifndef CONFIG_H | |
#define CONFIG_H | |
#include <X11/XF86keysym.h> | |
/* handheld mode? | |
* Optimizes interface to small screens */ | |
#define HANDHELD 1 | |
/* handheld mode options */ | |
#if HANDHELD | |
# define SYSTRAY_ICON_SIZE 82 /* size of systray icons */ | |
#endif | |
/** buttons **/ | |
#define MOD1 Mod1Mask /* ALT key */ | |
#define MOD4 Mod4Mask /* Super/Windows key */ | |
#define CONTROL ControlMask /* Control key */ | |
#define SHIFT ShiftMask /* Shift key */ | |
/** generic settings **/ | |
#define MASTER_SIZE 0.52 | |
#define SHOW_PANEL True /* show panel by default on exec */ | |
#define TOP_PANEL True /* False mean panel is on bottom */ | |
#define PANEL_HEIGHT 18 /* 0 for no space for panel, thus no panel */ | |
#define DEFAULT_MODE TILE /* TILE MONOCLE BSTACK GRID */ | |
#define ATTACH_ASIDE True /* False means new window is master */ | |
#define FOLLOW_MOUSE True /* Focus the window the mouse just entered */ | |
#define FOLLOW_WINDOW True /* Follow the window when moved to a different desktop */ | |
#define CLICK_TO_FOCUS True /* Focus an unfocused window when clicked */ | |
#define BORDER_WIDTH 2 /* window border width */ | |
#define FOCUS "#ff950e" /* focused window border color */ | |
#define UNFOCUS "#444444" /* unfocused window border color */ | |
#define DESKTOPS 4 /* number of desktops - edit DESKTOPCHANGE keys to suit */ | |
#define DEFAULT_DESKTOP 0 /* the desktop to focus on exec */ | |
#define MINWSZ 50 /* minimum window size in pixels */ | |
static const int systray_enable = 1; /* enable systray? */ | |
static const int systray_spacing = 2; /* spacing beetwen icons */ | |
/* open applications to specified desktop with specified mode. | |
* if desktop is negative, then current is assumed */ | |
static const AppRule rules[] = { \ | |
/* class desktop follow float */ | |
{ "mplayer", 3, True, False }, | |
{ "mplayer2", 3, True, False }, | |
{ "Gimp", 2, True, False }, | |
{ "dwb", 0, True, False }, | |
}; | |
/* helper for spawning shell commands */ | |
#define SHCMD(cmd) {.com = (const char*[]){"/bin/sh", "-c", cmd, NULL}} | |
/** commands **/ | |
static const char *termcmd[] = { "urxvt", NULL }; | |
static const char *menucmd[] = { "dmenu_run", NULL }; | |
#define DESKTOPCHANGE(K,N) \ | |
{ MOD_VI, K, change_desktop, {.i = N}}, \ | |
{ MOD_VI|SHIFT, K, client_to_desktop, {.i = N}}, | |
/** Shortcuts **/ | |
static key keys[] = { | |
/* modifier key function argument */ | |
{ MOD1, XK_b, togglepanel, {NULL}}, | |
{ MOD_VI, XK_BackSpace, focusurgent, {NULL}}, | |
{ MOD_VI, XK_q, killclient, {NULL}}, | |
{ MOD_VI, XK_Right, next_win, {NULL}}, | |
{ MOD_VI, XK_Left, prev_win, {NULL}}, | |
{ MOD_VI, XK_9, resize_master, {.i = -10}}, /* decrease */ | |
{ MOD_VI, XK_0, resize_master, {.i = +10}}, /* increase */ | |
{ MOD_VI|SHIFT, XK_9, resize_stack, {.i = -10}}, /* shrink */ | |
{ MOD_VI|SHIFT, XK_0, resize_stack, {.i = +10}}, /* grow */ | |
{ MOD_VI|SHIFT, XK_Left, rotate, {.i = -1}}, | |
{ MOD_VI|SHIFT, XK_Right, rotate, {.i = +1}}, | |
{ MOD_VI, XK_space, last_desktop, {NULL}}, | |
{ MOD_VI, XK_Return, swap_master, {NULL}}, | |
{ MOD_VI, XK_Down, move_down, {NULL}}, | |
{ MOD_VI, XK_Up, move_up, {NULL}}, | |
{ MOD_VI, XK_t, switch_mode, {.i = TILE}}, | |
{ MOD_VI, XK_m, switch_mode, {.i = MONOCLE}}, | |
{ MOD_VI, XK_b, switch_mode, {.i = BSTACK}}, | |
{ MOD_VI, XK_g, switch_mode, {.i = GRID}}, | |
{ MOD_VI|CONTROL, XK_q, quit, {.i = 2}}, /* quit with exit value 1 */ | |
{ MOD_VI|SHIFT, XK_Return, spawn, {.com = termcmd}}, | |
{ 0, XF86XK_MenuKB, togglevi, {NULL}}, | |
{ MOD_VI, XK_s, toggletray, {NULL}}, | |
{ MOD_VI, XK_r, spawn, {.com = menucmd}}, | |
DESKTOPCHANGE( XK_1, 0) | |
DESKTOPCHANGE( XK_2, 1) | |
DESKTOPCHANGE( XK_3, 2) | |
DESKTOPCHANGE( XK_4, 3) | |
}; | |
static Button buttons[] = { | |
{ SHIFT, Button1, mousemotion, {.i = MOVE}}, | |
{ SHIFT, Button3, mousemotion, {.i = RESIZE}}, | |
}; | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* see license for copyright and license */ | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <err.h> | |
#include <stdarg.h> | |
#include <unistd.h> | |
#include <string.h> | |
#include <signal.h> | |
#include <sys/wait.h> | |
#include <X11/Xutil.h> | |
#include <X11/XKBlib.h> | |
#include <X11/Xproto.h> | |
#include <X11/Xatom.h> | |
#define LENGTH(x) (sizeof(x)/sizeof(*x)) | |
#define CLEANMASK(mask) (mask & ~(numlockmask | LockMask)) | |
#define BUTTONMASK ButtonPressMask|ButtonReleaseMask | |
#define ISFFT(c) (c->isfullscrn || c->isfloating || c->istransient) | |
#define USAGE "usage: monsterwm [-h] [-v]" | |
enum { RESIZE, MOVE }; | |
enum { TILE, MONOCLE, BSTACK, GRID, MODES }; | |
enum { WM_PROTOCOLS, WM_DELETE_WINDOW, WM_COUNT }; | |
enum { NET_SUPPORTED, NET_FULLSCREEN, NET_WM_STATE, NET_ACTIVE, NET_WM_NAME, NET_SYSTRAY, NET_WINDOW_TYPE, NET_COUNT }; | |
/* argument structure to be passed to function by config.h | |
* com - a command to run | |
* i - an integer to indicate different states | |
*/ | |
typedef union { | |
const char** com; | |
const int i; | |
} Arg; | |
/* a key struct represents a combination of | |
* mod - a modifier mask | |
* keysym - and the key pressed | |
* func - the function to be triggered because of the above combo | |
* arg - the argument to the function | |
*/ | |
typedef struct { | |
unsigned int mod; | |
KeySym keysym; | |
void (*func)(const Arg *); | |
const Arg arg; | |
} key; | |
/* a button struct represents a combination of | |
* mask - a modifier mask | |
* button - and the mouse button pressed | |
* func - the function to be triggered because of the above combo | |
* arg - the argument to the function | |
*/ | |
typedef struct { | |
unsigned int mask, button; | |
void (*func)(const Arg *); | |
const Arg arg; | |
} Button; | |
/* a client is a wrapper to a window that additionally | |
* holds some properties for that window | |
* | |
* next - the client after this one, or NULL if the current is the last client | |
* isurgent - set when the window received an urgent hint | |
* istransient - set when the window is transient | |
* isfullscrn - set when the window is fullscreen | |
* isfloating - set when the window is floating | |
* win - the window this client is representing | |
* | |
* istransient is separate from isfloating as floating window can be reset | |
* to their tiling positions, while the transients will always be floating | |
*/ | |
typedef struct client { | |
struct client *next; | |
Bool isurgent, istransient, isfullscrn, isfloating; | |
Window win; | |
} client; | |
/* properties of each desktop | |
* master_size - the size of the master window | |
* mode - the desktop's tiling layout mode | |
* growth - growth factor of the first stack window | |
* head - the start of the client list | |
* current - the currently highlighted window | |
* prevfocus - the client that previously had focus | |
* showpanel - the visibility status of the panel | |
*/ | |
typedef struct { | |
int mode, growth; | |
float master_size; | |
client *head, *current, *prevfocus; | |
Bool showpanel; | |
} desktop; | |
/* define behavior of certain applications | |
* configured in config.h | |
* class - the class or name of the instance | |
* desktop - what desktop it should be spawned at | |
* follow - whether to change desktop focus to the specified desktop | |
*/ | |
typedef struct { | |
const char *class; | |
const int desktop; | |
const Bool follow, floating; | |
} AppRule; | |
/* function prototypes sorted alphabetically */ | |
static client* addwindow(Window w); | |
static void buttonpress(XEvent *e); | |
static void change_desktop(const Arg *arg); | |
static void cleanup(void); | |
static void client_to_desktop(const Arg *arg); | |
static void clientmessage(XEvent *e); | |
static void configurerequest(XEvent *e); | |
static void deletewindow(Window w); | |
static void desktopinfo(void); | |
static void destroynotify(XEvent *e); | |
static void enternotify(XEvent *e); | |
static void focusurgent(); | |
static unsigned long getcolor(const char* color); | |
static void grabbuttons(client *c); | |
static void grabkeys(void); | |
static void grid(int h, int y); | |
static void keypress(XEvent *e); | |
static void killclient(); | |
static void last_desktop(); | |
static void maprequest(XEvent *e); | |
static void monocle(int h, int y); | |
static void move_down(); | |
static void move_up(); | |
static void mousemotion(const Arg *arg); | |
static void next_win(); | |
static client* prev_client(client *c); | |
static void prev_win(); | |
static void propertynotify(XEvent *e); | |
static void quit(const Arg *arg); | |
static void removeclient(client *c); | |
static void resize_master(const Arg *arg); | |
static void resize_stack(const Arg *arg); | |
static void rotate(const Arg *arg); | |
static void rotate_filled(const Arg *arg); | |
static void run(void); | |
static void save_desktop(int i); | |
static void select_desktop(int i); | |
static void setfullscreen(client *c, Bool fullscrn); | |
static void setup(void); | |
static void sigchld(); | |
static void spawn(const Arg *arg); | |
static void stack(int h, int y); | |
static void swap_master(); | |
static void switch_mode(const Arg *arg); | |
static void tile(void); | |
static void togglepanel(); | |
static void update_current(client *c); | |
static void unmapnotify(XEvent *e); | |
static client* wintoclient(Window w); | |
static int xerror(Display *dis, XErrorEvent *ee); | |
static int xerrorstart(); | |
/* vi mode patch declares */ | |
#define MOD_VI (1<<9) | |
static int VI_MODE = 0; | |
/* vi mode functions */ | |
static void togglevi(); | |
/* system tray */ | |
typedef struct systray { | |
Window win; | |
XRectangle geo; | |
struct systray *next, *prev; | |
} systray; | |
systray *trayicons; | |
Window traywin; | |
static int TRAY_TOGGLE = 0; | |
#define XEMBED_MAPPED (1 << 0) | |
#define XEMBED_WINDOW_ACTIVATE 1 | |
#define XEMBED_WINDOW_DEACTIVATE 2 | |
/* system tray functions */ | |
static void toggletray(); | |
static Bool systray_acquire(void); | |
static void systray_add(Window win); | |
static void systray_del(systray *s); | |
static void systray_freeicons(void); | |
static systray* systray_find(Window win); | |
static int systray_get_width(void); | |
static void systray_update(void); | |
static void systray_state(systray *s); | |
#include "config.h" | |
static Bool running = True, showpanel = SHOW_PANEL; | |
static int previous_desktop = 0, current_desktop = 0, retval = 0; | |
static int screen, wh, ww, mode = DEFAULT_MODE, master_size = 0, growth = 0; | |
static int (*xerrorxlib)(Display *, XErrorEvent *); | |
static unsigned int numlockmask = 0, win_unfocus, win_focus; | |
static Display *dis; | |
static Window root; | |
static client *head, *prevfocus, *current; | |
static Atom wmatoms[WM_COUNT], netatoms[NET_COUNT]; | |
static desktop desktops[DESKTOPS]; | |
/* events array - on new event, call the appropriate handling function */ | |
static void (*events[LASTEvent])(XEvent *e) = { | |
[KeyPress] = keypress, [EnterNotify] = enternotify, | |
[MapRequest] = maprequest, [ClientMessage] = clientmessage, | |
[ButtonPress] = buttonpress, [DestroyNotify] = destroynotify, | |
[UnmapNotify] = unmapnotify, [PropertyNotify] = propertynotify, | |
[ConfigureRequest] = configurerequest, | |
}; | |
/* layout array - given the current layout mode, tile the windows | |
* h (or hh) - avaible height that windows have to expand | |
* y (or cy) - offset from top to place the windows (reserved by the panel) */ | |
static void (*layout[MODES])(int h, int y) = { | |
[TILE] = stack, [BSTACK] = stack, [GRID] = grid, [MONOCLE] = monocle, | |
}; | |
/* create a new client and add the new window | |
* window should notify of property change events */ | |
client* addwindow(Window w) { | |
client *c, *t = prev_client(head); | |
if (!(c = (client *)calloc(1, sizeof(client)))) err(EXIT_FAILURE, "cannot allocate client"); | |
if (!head) head = c; | |
else if (!ATTACH_ASIDE) { c->next = head; head = c; } | |
else if (t) t->next = c; else head->next = c; | |
XSelectInput(dis, (c->win = w), PropertyChangeMask|(FOLLOW_MOUSE?EnterWindowMask:0)); | |
return c; | |
} | |
/* on the press of a button check to see if there's a binded function to call */ | |
void buttonpress(XEvent *e) { | |
client *c = wintoclient(e->xbutton.window); | |
if (VI_MODE) togglevi(); /* if button pressed, go to standard mode */ | |
if (!c) return; | |
if (CLICK_TO_FOCUS && current != c && e->xbutton.button == Button1) update_current(c); | |
for (unsigned int i=0; i<LENGTH(buttons); i++) | |
if (buttons[i].func && buttons[i].button == e->xbutton.button && | |
CLEANMASK(buttons[i].mask) == CLEANMASK(e->xbutton.state)) { | |
if (current != c) update_current(c); | |
buttons[i].func(&(buttons[i].arg)); | |
} | |
} | |
/* focus another desktop | |
* | |
* to avoid flickering | |
* first map the new windows | |
* first the current window and then all other | |
* then unmap the old windows | |
* first all others then the current */ | |
void change_desktop(const Arg *arg) { | |
if (arg->i == current_desktop) return; | |
previous_desktop = current_desktop; | |
select_desktop(arg->i); | |
if (current) XMapWindow(dis, current->win); | |
for (client *c=head; c; c=c->next) XMapWindow(dis, c->win); | |
select_desktop(previous_desktop); | |
for (client *c=head; c; c=c->next) if (c != current) XUnmapWindow(dis, c->win); | |
if (current) XUnmapWindow(dis, current->win); | |
select_desktop(arg->i); | |
tile(); update_current(current); | |
desktopinfo(); | |
} | |
/* remove all windows in all desktops by sending a delete message */ | |
void cleanup(void) { | |
Window root_return, parent_return, *children; | |
unsigned int nchildren; | |
XUngrabKey(dis, AnyKey, AnyModifier, root); | |
XQueryTree(dis, root, &root_return, &parent_return, &children, &nchildren); | |
for (unsigned int i = 0; i<nchildren; i++) deletewindow(children[i]); | |
if (children) XFree(children); | |
XSync(dis, False); | |
XSetInputFocus(dis, PointerRoot, RevertToPointerRoot, CurrentTime); | |
} | |
/* move a client to another desktop | |
* | |
* remove the current client from the current desktop's client list | |
* and add it as last client of the new desktop's client list */ | |
void client_to_desktop(const Arg *arg) { | |
if (!current || arg->i == current_desktop) return; | |
int cd = current_desktop; | |
client *p = prev_client(current), *c = current; | |
select_desktop(arg->i); | |
client *l = prev_client(head); | |
update_current(l ? (l->next = c):head ? (head->next = c):(head = c)); | |
select_desktop(cd); | |
if (c == head || !p) head = c->next; else p->next = c->next; | |
c->next = NULL; | |
XUnmapWindow(dis, c->win); | |
update_current(prevfocus); | |
if (FOLLOW_WINDOW) change_desktop(arg); else tile(); | |
desktopinfo(); | |
} | |
/* To change the state of a mapped window, a client MUST | |
* send a _NET_WM_STATE client message to the root window | |
* message_type must be _NET_WM_STATE | |
* data.l[0] is the action to be taken | |
* data.l[1] is the property to alter three actions: | |
* - remove/unset _NET_WM_STATE_REMOVE=0 | |
* - add/set _NET_WM_STATE_ADD=1, | |
* - toggle _NET_WM_STATE_TOGGLE=2 | |
* | |
* check if window requested fullscreen or activation */ | |
void clientmessage(XEvent *e) { | |
client *t = NULL, *c = wintoclient(e->xclient.window); | |
if (!c) { | |
if (e->xclient.window == traywin && | |
e->xclient.data.l[1] == 0) { | |
systray_add(e->xclient.data.l[2]); | |
systray_update(); | |
return; | |
} | |
} | |
if (c && e->xclient.message_type == netatoms[NET_WM_STATE] | |
&& ((unsigned)e->xclient.data.l[1] == netatoms[NET_FULLSCREEN] | |
|| (unsigned)e->xclient.data.l[2] == netatoms[NET_FULLSCREEN])) | |
setfullscreen(c, (e->xclient.data.l[0] == 1 || (e->xclient.data.l[0] == 2 && !c->isfullscrn))); | |
else if (c && e->xclient.message_type == netatoms[NET_ACTIVE]) for (t=head; t && t!=c; t=t->next); | |
if (t) update_current(c); | |
tile(); | |
} | |
/* a configure request means that the window requested changes in its geometry | |
* state. if the window is fullscreen discard and fill the screen else set the | |
* appropriate values as requested, and tile the window again so that it fills | |
* the gaps that otherwise could have been created */ | |
void configurerequest(XEvent *e) { | |
XConfigureRequestEvent *ev = &e->xconfigurerequest; | |
client *c = wintoclient(ev->window); | |
if (!c || !c->isfullscrn) { | |
XConfigureWindow(dis, ev->window, ev->value_mask, &(XWindowChanges){ev->x, | |
ev->y, ev->width, ev->height, ev->border_width, ev->above, ev->detail}); | |
XSync(dis, False); | |
} else setfullscreen(c, True); | |
tile(); | |
} | |
/* close the window */ | |
void deletewindow(Window w) { | |
XEvent ev; | |
ev.type = ClientMessage; | |
ev.xclient.window = w; | |
ev.xclient.message_type = wmatoms[WM_PROTOCOLS]; | |
ev.xclient.format = 32; | |
ev.xclient.data.l[0] = wmatoms[WM_DELETE_WINDOW]; | |
ev.xclient.data.l[1] = CurrentTime; | |
XSendEvent(dis, w, False, NoEventMask, &ev); | |
} | |
/* output info about the desktops on standard output stream | |
* | |
* the info is a list of ':' separated values for each desktop | |
* desktop to desktop info is separated by ' ' single spaces | |
* the info values are | |
* the desktop number/id | |
* the desktop's client count | |
* the desktop's tiling layout mode/id | |
* whether the desktop is the current focused (1) or not (0) | |
* whether any client in that desktop has received an urgent hint | |
* | |
* once the info is collected, immediately flush the stream */ | |
void desktopinfo(void) { | |
Bool urgent = False; | |
int cd = current_desktop, n=0, d=0; | |
for (client *c; d<DESKTOPS; d++) { | |
for (select_desktop(d), c=head, n=0, urgent=False; c; c=c->next, ++n) if (c->isurgent) urgent = True; | |
fprintf(stdout, "%d:%d:%d:%d:%d:%d%c", d, n, mode, current_desktop == cd, urgent, VI_MODE, d+1==DESKTOPS?'\n':' '); | |
} | |
fflush(stdout); | |
if (cd != d-1) select_desktop(cd); | |
} | |
/* a destroy notification is received when a window is being closed | |
* on receival, remove the appropriate client that held that window */ | |
void destroynotify(XEvent *e) { | |
systray *s; | |
client *c = wintoclient(e->xdestroywindow.window); | |
if ((s = systray_find(e->xdestroywindow.window))) { | |
systray_del(s); | |
systray_update(); | |
return; | |
} | |
if (c) removeclient(c); | |
desktopinfo(); | |
} | |
/* when the mouse enters a window's borders | |
* the window, if notifying of such events (EnterWindowMask) | |
* will notify the wm and will get focus */ | |
void enternotify(XEvent *e) { | |
if (!FOLLOW_MOUSE) return; | |
client *c = wintoclient(e->xcrossing.window); | |
if (c && e->xcrossing.mode == NotifyNormal | |
&& e->xcrossing.detail != NotifyInferior) update_current(c); | |
} | |
/* find and focus the client which received | |
* the urgent hint in the current desktop */ | |
void focusurgent(void) { | |
client *c; | |
int cd = current_desktop, d = 0; | |
for (c=head; c && !c->isurgent; c=c->next); | |
if (c) { update_current(c); return; } | |
else for (Bool f=False; d<DESKTOPS && !f; d++) for (select_desktop(d), c=head; c && !(f=c->isurgent); c=c->next); | |
select_desktop(cd); | |
if (c) { change_desktop(&(Arg){.i = --d}); update_current(c); } | |
} | |
/* get a pixel with the requested color | |
* to fill some window area - borders */ | |
unsigned long getcolor(const char* color) { | |
XColor c; Colormap map = DefaultColormap(dis, screen); | |
if (!XAllocNamedColor(dis, map, color, &c, &c)) err(EXIT_FAILURE, "cannot allocate color"); | |
return c.pixel; | |
} | |
/* set the given client to listen to button events (presses / releases) */ | |
void grabbuttons(client *c) { | |
unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; | |
for (unsigned int b=0; b<LENGTH(buttons); b++) | |
for (unsigned int m=0; m<LENGTH(modifiers); m++) | |
XGrabButton(dis, buttons[b].button, buttons[b].mask|modifiers[m], c->win, | |
False, BUTTONMASK, GrabModeAsync, GrabModeAsync, None, None); | |
} | |
/* the wm should listen to key presses */ | |
void grabkeys(void) { | |
KeyCode code; | |
/* grab everything on vi mode */ | |
if (!VI_MODE) XUngrabKey(dis, AnyKey, AnyModifier, root); | |
else XGrabKey(dis, AnyKey, AnyModifier, root, True, GrabModeAsync, GrabModeAsync); | |
unsigned int mod = 0; | |
unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; | |
for (unsigned int k=0; k<LENGTH(keys); k++) | |
if ((code = XKeysymToKeycode(dis, keys[k].keysym))) | |
for (unsigned int m=0; m<LENGTH(modifiers); m++) { | |
if (VI_MODE || !(keys[k].mod & MOD_VI)) { | |
mod = (keys[k].mod&MOD_VI)?keys[k].mod&~MOD_VI:keys[k].mod; | |
XGrabKey(dis, code, mod|modifiers[m], root, True, GrabModeAsync, GrabModeAsync); | |
} | |
} | |
} | |
/* arrange windows in a grid */ | |
void grid(int hh, int cy) { | |
int n = 0, cols = 0, cn = 0, rn = 0, i = -1; | |
for (client *c = head; c; c=c->next) if (!ISFFT(c)) ++n; | |
for (cols=0; cols <= n/2; cols++) if (cols*cols >= n) break; /* emulate square root */ | |
if (n == 0) return; else if (n == 5) cols = 2; | |
int rows = n/cols, ch = hh - BORDER_WIDTH, cw = (ww - BORDER_WIDTH)/(cols?cols:1); | |
for (client *c=head; c; c=c->next) { | |
if (ISFFT(c)) continue; else ++i; | |
if (i/rows + 1 > cols - n%cols) rows = n/cols + 1; | |
XMoveResizeWindow(dis, c->win, cn*cw, cy + rn*ch/rows, cw - BORDER_WIDTH, ch/rows - BORDER_WIDTH); | |
if (++rn >= rows) { rn = 0; cn++; } | |
} | |
} | |
/* on the press of a key check to see if there's a binded function to call */ | |
void keypress(XEvent *e) { | |
KeySym keysym = XkbKeycodeToKeysym(dis, e->xkey.keycode, 0, 0); | |
unsigned int mod = 0; | |
for (unsigned int i=0; i<LENGTH(keys); i++) { | |
mod = (keys[i].mod&MOD_VI)?keys[i].mod&~MOD_VI:keys[i].mod; | |
if (keysym == keys[i].keysym && CLEANMASK(mod) == CLEANMASK(e->xkey.state) | |
&& keys[i].func) keys[i].func(&keys[i].arg); | |
} | |
} | |
/* explicitly kill a client - close the highlighted window | |
* send a delete message and remove the client */ | |
void killclient(void) { | |
if (!current) return; | |
Atom *prot; int n = -1; | |
if (XGetWMProtocols(dis, current->win, &prot, &n)) while(!--n<0 && prot[n] != wmatoms[WM_DELETE_WINDOW]); | |
if (n < 0) XKillClient(dis, current->win); else deletewindow(current->win); | |
removeclient(current); | |
} | |
/* focus the previously focused desktop */ | |
void last_desktop(void) { | |
change_desktop(&(Arg){.i = previous_desktop}); | |
} | |
/* a map request is received when a window wants to display itself | |
* if the window has override_redirect flag set then it should not be handled | |
* by the wm. if the window already has a client then there is nothing to do. | |
* | |
* get the window class and name instance and try to match against an app rule. | |
* create a client for the window, that client will always be current. | |
* check for transient state, and fullscreen state and the appropriate values. | |
* if the desktop in which the window was spawned is the current desktop then | |
* display the window, else, if set, focus the new desktop. */ | |
void maprequest(XEvent *e) { | |
static XWindowAttributes wa; Window w; | |
int di; unsigned long dl; unsigned char *state = NULL; Atom da; | |
/* respect _NET_WM_WINDOW_TYPE */ | |
if (XGetWindowProperty(dis, e->xmaprequest.window, netatoms[NET_WINDOW_TYPE], 0L, 1L, | |
False, XA_ATOM, &da, &di, &dl, &dl, &state) == Success && state) { | |
memcpy(&da, state, sizeof(Atom)); | |
if (state) XFree(state); | |
if (da && da == XInternAtom(dis, "_NET_WM_WINDOW_TYPE_NOTIFICATION", False)) { | |
XMapWindow(dis, e->xmaprequest.window); | |
return; | |
} | |
} | |
if (XGetWindowAttributes(dis, e->xmaprequest.window, &wa) && wa.override_redirect) return; | |
if (wintoclient(e->xmaprequest.window)) return; | |
Bool follow = False, floating = False; | |
int cd = current_desktop, newdsk = current_desktop; | |
XClassHint ch = {0, 0}; | |
if (XGetClassHint(dis, e->xmaprequest.window, &ch)) | |
for (unsigned int i=0; i<LENGTH(rules); i++) | |
if (strstr(ch.res_class, rules[i].class) || strstr(ch.res_name, rules[i].class)) { | |
follow = rules[i].follow; | |
newdsk = (rules[i].desktop < 0) ? current_desktop:rules[i].desktop; | |
floating = rules[i].floating; | |
break; | |
} | |
if (ch.res_class) XFree(ch.res_class); | |
if (ch.res_name) XFree(ch.res_name); | |
if (cd != newdsk) select_desktop(newdsk); | |
client *c = addwindow(e->xmaprequest.window); | |
c->istransient = XGetTransientForHint(dis, c->win, &w); | |
c->isfloating = floating || c->istransient; | |
if (XGetWindowProperty(dis, c->win, netatoms[NET_WM_STATE], 0L, sizeof da, | |
False, XA_ATOM, &da, &di, &dl, &dl, &state) == Success && state) | |
setfullscreen(c, (*(Atom *)state == netatoms[NET_FULLSCREEN])); | |
if (state) XFree(state); | |
if (cd != newdsk) select_desktop(cd); | |
if (cd == newdsk) { tile(); XMapWindow(dis, c->win); update_current(c); } | |
else if (follow) { change_desktop(&(Arg){.i = newdsk}); update_current(c); } | |
grabbuttons(c); | |
desktopinfo(); | |
} | |
/* grab the pointer and get it's current position | |
* all pointer movement events will be reported until it's ungrabbed | |
* until the mouse button has not been released, | |
* grab the interesting events - button press/release and pointer motion | |
* and on on pointer movement resize or move the window under the curson. | |
* if the received event is a map request or a configure request call the | |
* appropriate handler, and stop listening for other events. | |
* Ungrab the poitner and event handling is passed back to run() function. | |
* Once a window has been moved or resized, it's marked as floating. */ | |
void mousemotion(const Arg *arg) { | |
if (!current) return; | |
static XWindowAttributes wa; | |
XGetWindowAttributes(dis, current->win, &wa); | |
if (XGrabPointer(dis, root, False, BUTTONMASK|PointerMotionMask, GrabModeAsync, | |
GrabModeAsync, None, None, CurrentTime) != GrabSuccess) return; | |
int x, y, z, xw, yh; unsigned int v; Window w; | |
//XWarpPointer(dis, None, current->win, 0, 0, 0, 0, wa.width, wa.height); | |
XQueryPointer(dis, root, &w, &w, &x, &y, &z, &z, &v); | |
if (current->isfullscrn) setfullscreen(current, False); | |
if (!current->isfloating) current->isfloating = True; | |
tile(); update_current(current); | |
XEvent ev; | |
do { | |
XMaskEvent(dis, BUTTONMASK|PointerMotionMask|SubstructureRedirectMask, &ev); | |
switch (ev.type) { | |
case ConfigureRequest: case MapRequest: | |
events[ev.type](&ev); | |
break; | |
case MotionNotify: | |
xw = (arg->i == MOVE ? wa.x:wa.width) + ev.xmotion.x - x; | |
yh = (arg->i == MOVE ? wa.y:wa.height) + ev.xmotion.y - y; | |
if (arg->i == RESIZE) XResizeWindow(dis, current->win, | |
xw>MINWSZ ? xw:wa.width, yh>MINWSZ ? yh:wa.height); | |
else if (arg->i == MOVE) XMoveWindow(dis, current->win, xw, yh); | |
break; | |
} | |
} while(ev.type != ButtonRelease); | |
XUngrabPointer(dis, CurrentTime); | |
} | |
/* each window should cover all the available screen space */ | |
void monocle(int hh, int cy) { | |
for (client *c=head; c; c=c->next) if (!ISFFT(c)) XMoveResizeWindow(dis, c->win, 0, cy, ww, hh); | |
} | |
/* move the current client, to current->next | |
* and current->next to current client's position */ | |
void move_down(void) { | |
/* p is previous, c is current, n is next, if current is head n is last */ | |
client *p = NULL, *n = (current->next) ? current->next:head; | |
if (!(p = prev_client(current))) return; | |
/* | |
* if c is head, swapping with n should update head to n | |
* [c]->[n]->.. ==> [n]->[c]->.. | |
* ^head ^head | |
* | |
* else there is a previous client and p->next should be what's after c | |
* ..->[p]->[c]->[n]->.. ==> ..->[p]->[n]->[c]->.. | |
*/ | |
if (current == head) head = n; else p->next = current->next; | |
/* | |
* if c is the last client, c will be the current head | |
* [n]->..->[p]->[c]->NULL ==> [c]->[n]->..->[p]->NULL | |
* ^head ^head | |
* else c will take the place of n, so c-next will be n->next | |
* ..->[p]->[c]->[n]->.. ==> ..->[p]->[n]->[c]->.. | |
*/ | |
current->next = (current->next) ? n->next:n; | |
/* | |
* if c was swapped with n then they now point to the same ->next. n->next should be c | |
* ..->[p]->[c]->[n]->.. ==> ..->[p]->[n]->.. ==> ..->[p]->[n]->[c]->.. | |
* [c]-^ | |
* | |
* else c is the last client and n is head, | |
* so c will be move to be head, no need to update n->next | |
* [n]->..->[p]->[c]->NULL ==> [c]->[n]->..->[p]->NULL | |
* ^head ^head | |
*/ | |
if (current->next == n->next) n->next = current; else head = current; | |
tile(); | |
} | |
/* move the current client, to the previous from current and | |
* the previous from current to current client's position */ | |
void move_up(void) { | |
client *pp = NULL, *p; | |
/* p is previous from current or last if current is head */ | |
if (!(p = prev_client(current))) return; | |
/* pp is previous from p, or null if current is head and thus p is last */ | |
if (p->next) for (pp=head; pp && pp->next != p; pp=pp->next); | |
/* | |
* if p has a previous client then the next client should be current (current is c) | |
* ..->[pp]->[p]->[c]->.. ==> ..->[pp]->[c]->[p]->.. | |
* | |
* if p doesn't have a previous client, then p might be head, so head must change to c | |
* [p]->[c]->.. ==> [c]->[p]->.. | |
* ^head ^head | |
* if p is not head, then c is head (and p is last), so the new head is next of c | |
* [c]->[n]->..->[p]->NULL ==> [n]->..->[p]->[c]->NULL | |
* ^head ^last ^head ^last | |
*/ | |
if (pp) pp->next = current; else head = (current == head) ? current->next:current; | |
/* | |
* next of p should be next of c | |
* ..->[pp]->[p]->[c]->[n]->.. ==> ..->[pp]->[c]->[p]->[n]->.. | |
* except if c was head (now c->next is head), so next of p should be c | |
* [c]->[n]->..->[p]->NULL ==> [n]->..->[p]->[c]->NULL | |
* ^head ^last ^head ^last | |
*/ | |
p->next = (current->next == head) ? current:current->next; | |
/* | |
* next of c should be p | |
* ..->[pp]->[p]->[c]->[n]->.. ==> ..->[pp]->[c]->[p]->[n]->.. | |
* except if c was head (now c->next is head), so c is must be last | |
* [c]->[n]->..->[p]->NULL ==> [n]->..->[p]->[c]->NULL | |
* ^head ^last ^head ^last | |
*/ | |
current->next = (current->next == head) ? NULL:p; | |
tile(); | |
} | |
/* cyclic focus the next window | |
* if the window is the last on stack, focus head */ | |
void next_win(void) { | |
if (!current || !head->next) return; | |
update_current(current->next ? current->next:head); | |
} | |
/* get the previous client from the given | |
* if no such client, return NULL */ | |
client* prev_client(client *c) { | |
if (!c || !head->next) return NULL; | |
client *p; for (p=head; p->next && p->next != c; p=p->next); | |
return p; | |
} | |
/* cyclic focus the previous window | |
* if the window is the head, focus the last stack window */ | |
void prev_win(void) { | |
if (!current || !head->next) return; | |
update_current(prev_client(prevfocus = current)); | |
} | |
/* property notify is called when one of the window's properties | |
* is changed, such as an urgent hint is received */ | |
void propertynotify(XEvent *e) { | |
systray *s; | |
client *c = wintoclient(e->xproperty.window); | |
if ((s = systray_find(e->xproperty.window))) { | |
systray_state(s); | |
systray_update(); | |
} | |
if (!c || e->xproperty.atom != XA_WM_HINTS) return; | |
XWMHints *wmh = XGetWMHints(dis, c->win); | |
c->isurgent = c != current && wmh && (wmh->flags & XUrgencyHint); | |
XFree(wmh); | |
desktopinfo(); | |
} | |
/* to quit just stop receiving and processing events | |
* run() is stopped and control is back to main() */ | |
void quit(const Arg *arg) { | |
retval = arg->i; | |
running = False; | |
} | |
/* remove the specified client | |
* | |
* note, the removing client can be on any desktop, | |
* we must return back to the current focused desktop. | |
* if c was the previously focused, prevfocus must be updated | |
* else if c was the current one, current must be updated. */ | |
void removeclient(client *c) { | |
client **p = NULL; | |
int nd = 0, cd = current_desktop; | |
for (Bool found = False; nd<DESKTOPS && !found; nd++) | |
for (select_desktop(nd), p = &head; *p && !(found = *p == c); p = &(*p)->next); | |
*p = c->next; | |
if (c == prevfocus) prevfocus = prev_client(current); | |
if (c == current || !head->next) update_current(prevfocus); | |
free(c); c = NULL; | |
if (cd == nd -1) tile(); else select_desktop(cd); | |
} | |
/* resize the master window - check for boundary size limits | |
* the size of a window can't be less than MINWSZ | |
*/ | |
void resize_master(const Arg *arg) { | |
int msz = (mode == BSTACK ? wh:ww) * MASTER_SIZE + master_size + arg->i; | |
if (msz < MINWSZ || (mode == BSTACK ? wh:ww) - msz < MINWSZ) return; | |
master_size += arg->i; | |
tile(); | |
} | |
/* resize the first stack window - no boundary checks */ | |
void resize_stack(const Arg *arg) { | |
growth += arg->i; | |
tile(); | |
} | |
/* jump and focus the next or previous desktop */ | |
void rotate(const Arg *arg) { | |
change_desktop(&(Arg){.i = (DESKTOPS + current_desktop + arg->i) % DESKTOPS}); | |
} | |
/* jump and focus the next or previous desktop that has clients */ | |
void rotate_filled(const Arg *arg) { | |
int n = arg->i; | |
while (n < DESKTOPS && !desktops[(DESKTOPS + current_desktop + n) % DESKTOPS].head) (n += arg->i); | |
change_desktop(&(Arg){.i = (DESKTOPS + current_desktop + n) % DESKTOPS}); | |
} | |
/* main event loop - on receival of an event call the appropriate event handler */ | |
void run(void) { | |
XEvent ev; | |
while(running && !XNextEvent(dis, &ev)) if (events[ev.type]) events[ev.type](&ev); | |
} | |
/* save specified desktop's properties */ | |
void save_desktop(int i) { | |
if (i < 0 || i >= DESKTOPS) return; | |
desktops[i].master_size = master_size; | |
desktops[i].mode = mode; | |
desktops[i].growth = growth; | |
desktops[i].head = head; | |
desktops[i].current = current; | |
desktops[i].showpanel = showpanel; | |
desktops[i].prevfocus = prevfocus; | |
} | |
/* set the specified desktop's properties */ | |
void select_desktop(int i) { | |
if (i < 0 || i >= DESKTOPS) return; | |
save_desktop(current_desktop); | |
master_size = desktops[i].master_size; | |
mode = desktops[i].mode; | |
growth = desktops[i].growth; | |
head = desktops[i].head; | |
current = desktops[i].current; | |
showpanel = desktops[i].showpanel; | |
prevfocus = desktops[i].prevfocus; | |
current_desktop = i; | |
} | |
/* set or unset fullscreen state of client */ | |
void setfullscreen(client *c, Bool fullscrn) { | |
if (fullscrn != c->isfullscrn) XChangeProperty(dis, c->win, | |
netatoms[NET_WM_STATE], XA_ATOM, 32, PropModeReplace, (unsigned char*) | |
((c->isfullscrn = fullscrn) ? &netatoms[NET_FULLSCREEN]:0), fullscrn); | |
if (fullscrn) XMoveResizeWindow(dis, c->win, 0, 0, ww, wh + PANEL_HEIGHT); | |
XConfigureWindow(dis, c->win, CWBorderWidth, &(XWindowChanges){0,0,0,0,fullscrn?0:BORDER_WIDTH,0,0}); | |
} | |
/* set initial values | |
* root window - screen height/width - atoms - xerror handler | |
* set masks for reporting events handled by the wm | |
* and propagate the suported net atoms */ | |
void setup(void) { | |
sigchld(); | |
screen = DefaultScreen(dis); | |
root = RootWindow(dis, screen); | |
ww = XDisplayWidth(dis, screen); | |
wh = XDisplayHeight(dis, screen) - PANEL_HEIGHT; | |
for (unsigned int i=0; i<DESKTOPS; i++) save_desktop(i); | |
win_focus = getcolor(FOCUS); | |
win_unfocus = getcolor(UNFOCUS); | |
XModifierKeymap *modmap = XGetModifierMapping(dis); | |
for (int k=0; k<8; k++) for (int j=0; j<modmap->max_keypermod; j++) | |
if (modmap->modifiermap[modmap->max_keypermod*k + j] == XKeysymToKeycode(dis, XK_Num_Lock)) | |
numlockmask = (1 << k); | |
XFreeModifiermap(modmap); | |
/* set up atoms for dialog/notification windows */ | |
wmatoms[WM_PROTOCOLS] = XInternAtom(dis, "WM_PROTOCOLS", False); | |
wmatoms[WM_DELETE_WINDOW] = XInternAtom(dis, "WM_DELETE_WINDOW", False); | |
netatoms[NET_SUPPORTED] = XInternAtom(dis, "_NET_SUPPORTED", False); | |
netatoms[NET_WM_STATE] = XInternAtom(dis, "_NET_WM_STATE", False); | |
netatoms[NET_ACTIVE] = XInternAtom(dis, "_NET_ACTIVE_WINDOW", False); | |
netatoms[NET_FULLSCREEN] = XInternAtom(dis, "_NET_WM_STATE_FULLSCREEN", False); | |
netatoms[NET_WM_NAME] = XInternAtom(dis, "_NET_WM_NAME", False); | |
netatoms[NET_SYSTRAY] = XInternAtom(dis, "_NET_SYSTEM_TRAY_S0", False); | |
netatoms[NET_WINDOW_TYPE] = XInternAtom(dis, "_NET_WM_WINDOW_TYPE", False); | |
/* check if another window manager is running */ | |
xerrorxlib = XSetErrorHandler(xerrorstart); | |
XSelectInput(dis, DefaultRootWindow(dis), SubstructureRedirectMask|ButtonPressMask| | |
SubstructureNotifyMask|PropertyChangeMask); | |
XSync(dis, False); | |
XSetErrorHandler(xerror); | |
XSync(dis, False); | |
XChangeProperty(dis, root, netatoms[NET_SUPPORTED], XA_ATOM, 32, | |
PropModeReplace, (unsigned char *)netatoms, NET_COUNT); | |
grabkeys(); | |
change_desktop(&(Arg){.i = DEFAULT_DESKTOP}); | |
/* get systray */ | |
systray_acquire(); | |
} | |
void sigchld() { | |
if (signal(SIGCHLD, sigchld) == SIG_ERR) | |
err(EXIT_FAILURE, "cannot install SIGCHLD handler"); | |
while(0 < waitpid(-1, NULL, WNOHANG)); | |
} | |
/* execute a command */ | |
void spawn(const Arg *arg) { | |
if (fork()) return; | |
if (dis) close(ConnectionNumber(dis)); | |
setsid(); | |
freopen("/dev/null", "w", stdout); freopen("/dev/null", "w", stderr); | |
execvp((char*)arg->com[0], (char**)arg->com); | |
err(EXIT_SUCCESS, "execvp %s", (char *)arg->com[0]); | |
} | |
/* arrange windows in normal or bottom stack tile */ | |
void stack(int hh, int cy) { | |
client *c = NULL, *t = NULL; Bool b = mode == BSTACK; | |
int n = 0, d = 0, z = b ? ww:hh, ma = (mode == BSTACK ? wh:ww) * MASTER_SIZE + master_size; | |
/* count stack windows and grab first non-floating, non-fullscreen window */ | |
for (t = head; t; t=t->next) if (!ISFFT(t)) { if (c) ++n; else c = t; } | |
/* if there is only one window, it should cover the available screen space | |
* if there is only one stack window (n == 1) then we don't care about growth | |
* if more than one stack windows (n > 1) on screen then adjustments may be needed | |
* - d is the num of pixels than remain when spliting | |
* the available width/height to the number of windows | |
* - z is the clients' height/width | |
* | |
* ---------- -. --------------------. | |
* | |----| --|--> growth `}--> first client will get (z+d) height/width | |
* | | | | | | |
* | |----| }--> screen height - hh --' | |
* | | | }-|--> client height - z :: 2 stack clients on tile mode ..looks like a spaceship | |
* ---------- -' :: piece of aart by c00kiemon5ter o.O om nom nom nom nom | |
* | |
* what we do is, remove the growth from the screen height : (z - growth) | |
* and then divide that space with the windows on the stack : (z - growth)/n | |
* so all windows have equal height/width (z) : | |
* growth is left out and will later be added to the first's client height/width | |
* before that, there will be cases when the num of windows is not perfectly | |
* divided with then available screen height/width (ie 100px scr. height, and 3 windows) | |
* so we get that remaining space and merge growth to it (d) : (z - growth) % n + growth | |
* finally we know each client's height, and how many pixels should be added to | |
* the first stack window so that it satisfies growth, and doesn't create gaps | |
* on the bottom of the screen. */ | |
if (!c) return; else if (!n) { | |
XMoveResizeWindow(dis, c->win, 0, cy, ww - 2*BORDER_WIDTH, hh - 2*BORDER_WIDTH); | |
return; | |
} else if (n > 1) { d = (z - growth)%n + growth; z = (z - growth)/n; } | |
/* tile the first non-floating, non-fullscreen window to cover the master area */ | |
if (b) XMoveResizeWindow(dis, c->win, 0, cy, ww - 2*BORDER_WIDTH, ma - BORDER_WIDTH); | |
else XMoveResizeWindow(dis, c->win, 0, cy, ma - BORDER_WIDTH, hh - 2*BORDER_WIDTH); | |
/* tile the next non-floating, non-fullscreen (first) stack window with growth|d */ | |
for (c=c->next; c && ISFFT(c); c=c->next); | |
int cx = b ? 0:ma, cw = (b ? hh:ww) - 2*BORDER_WIDTH - ma, ch = z - BORDER_WIDTH; | |
if (b) XMoveResizeWindow(dis, c->win, cx, cy += ma, ch - BORDER_WIDTH + d, cw); | |
else XMoveResizeWindow(dis, c->win, cx, cy, cw, ch - BORDER_WIDTH + d); | |
/* tile the rest of the non-floating, non-fullscreen stack windows */ | |
for (b?(cx+=ch+d):(cy+=ch+d), c=c->next; c; c=c->next) { | |
if (ISFFT(c)) continue; | |
if (b) { XMoveResizeWindow(dis, c->win, cx, cy, ch, cw); cx += z; } | |
else { XMoveResizeWindow(dis, c->win, cx, cy, cw, ch); cy += z; } | |
} | |
} | |
/* swap master window with current or | |
* if current is head swap with next | |
* if current is not head, then head | |
* is behind us, so move_up until we | |
* are the head */ | |
void swap_master(void) { | |
if (!current || !head->next) return; | |
if (current == head) move_down(); | |
else while (current != head) move_up(); | |
update_current(head); | |
} | |
/* switch the tiling mode and reset all floating windows */ | |
void switch_mode(const Arg *arg) { | |
if (mode == arg->i) for (client *c=head; c; c=c->next) c->isfloating = False; | |
mode = arg->i; | |
tile(); update_current(current); | |
desktopinfo(); | |
} | |
/* tile all windows of current desktop - call the handler tiling function */ | |
void tile(void) { | |
if (!head) return; /* nothing to arange */ | |
layout[head->next ? mode:MONOCLE](wh + (showpanel ? 0:PANEL_HEIGHT), | |
(TOP_PANEL && showpanel ? PANEL_HEIGHT:0)); | |
} | |
/* toggle visibility state of the panel */ | |
void togglepanel(void) { | |
showpanel = !showpanel; | |
tile(); | |
} | |
/* windows that request to unmap should lose their | |
* client, so no invisible windows exist on screen */ | |
void unmapnotify(XEvent *e) { | |
client *c = wintoclient(e->xunmap.window); | |
if (c && e->xunmap.send_event) removeclient(c); | |
desktopinfo(); | |
} | |
/* highlight borders and set active window and input focus | |
* if given current is NULL then delete the active window property | |
* | |
* stack order by client properties, top to bottom: | |
* - current when floating or transient | |
* - floating or trancient windows | |
* - current when tiled | |
* - current when fullscreen | |
* - fullscreen windows | |
* - tiled windows | |
* | |
* a window should have borders in any case, except if | |
* - the window is the only window on screen | |
* - the window is fullscreen | |
* - the mode is MONOCLE and the window is not floating or transient */ | |
void update_current(client *c) { | |
if (!head) { | |
XDeleteProperty(dis, root, netatoms[NET_ACTIVE]); | |
current = prevfocus = NULL; | |
return; | |
} else if (c == prevfocus) { prevfocus = prev_client(current = prevfocus ? prevfocus:head); | |
} else if (c != current) { prevfocus = current; current = c; } | |
/* num of n:all fl:fullscreen ft:floating/transient windows */ | |
int n = 0, fl = 0, ft = 0; | |
for (c = head; c; c = c->next, ++n) if (ISFFT(c)) { fl++; if (!c->isfullscrn) ft++; } | |
Window w[n]; | |
w[(current->isfloating||current->istransient) ? 0:ft] = current->win; | |
for (fl += !ISFFT(current) ? 1:0, c = head; c; c = c->next) { | |
XSetWindowBorder(dis, c->win, c == current ? win_focus:win_unfocus); | |
XSetWindowBorderWidth(dis, c->win, (!head->next || c->isfullscrn | |
|| (mode == MONOCLE && !ISFFT(c))) ? 0:BORDER_WIDTH); | |
if (c != current) w[c->isfullscrn ? --fl:ISFFT(c) ? --ft:--n] = c->win; | |
} | |
XRestackWindows(dis, w, LENGTH(w)); | |
XSetInputFocus(dis, current->win, RevertToPointerRoot, CurrentTime); | |
XChangeProperty(dis, root, netatoms[NET_ACTIVE], XA_WINDOW, 32, | |
PropModeReplace, (unsigned char *)¤t->win, 1); | |
if (CLICK_TO_FOCUS && !VI_MODE) XUngrabButton(dis, Button1, None, current->win); | |
#if HANDHELD | |
XRaiseWindow(dis, traywin); /* make systray always top */ | |
#endif | |
XSync(dis, False); | |
} | |
/* find to which client the given window belongs to */ | |
client* wintoclient(Window w) { | |
client *c = NULL; | |
int d = 0, cd = current_desktop; | |
for (Bool found = False; d<DESKTOPS && !found; ++d) | |
for (select_desktop(d), c=head; c && !(found = (w == c->win)); c=c->next); | |
if (cd != d-1) select_desktop(cd); | |
return c; | |
} | |
/* There's no way to check accesses to destroyed windows, thus those cases are | |
* ignored (especially on UnmapNotify's). Other types of errors call Xlibs | |
* default error handler, which may call exit through xerrorlib. */ | |
int xerror(Display *dis, XErrorEvent *ee) { | |
if (ee->error_code == BadWindow || (ee->error_code == BadAccess && ee->request_code == X_GrabKey) | |
|| (ee->error_code == BadMatch && (ee->request_code == X_SetInputFocus | |
|| ee->request_code == X_ConfigureWindow)) | |
|| (ee->error_code == BadDrawable && (ee->request_code == X_PolyFillRectangle | |
|| ee->request_code == X_CopyArea || ee->request_code == X_PolySegment | |
|| ee->request_code == X_PolyText8))) return 0; | |
fprintf(stderr, "error: xerror: request code: %d, error code: %d\n", ee->request_code, ee->error_code); | |
return xerrorxlib(dis, ee); | |
} | |
int xerrorstart(void) { | |
err(EXIT_FAILURE, "another window manager is already running"); | |
} | |
int main(int argc, char *argv[]) { | |
if (argc == 2 && argv[1][0] == '-') switch (argv[1][1]) { | |
case 'v': errx(EXIT_SUCCESS, "%s - by c00kiemon5ter >:3 omnomnomnom", VERSION); | |
case 'h': errx(EXIT_SUCCESS, "%s", USAGE); | |
default: errx(EXIT_FAILURE, "%s", USAGE); | |
} else if (argc != 1) errx(EXIT_FAILURE, "%s", USAGE); | |
if (!(dis = XOpenDisplay(NULL))) errx(EXIT_FAILURE, "cannot open display"); | |
setup(); | |
desktopinfo(); /* zero out every desktop on (re)start */ | |
run(); | |
cleanup(); | |
XCloseDisplay(dis); | |
return retval; | |
} | |
void togglevi() | |
{ | |
VI_MODE = !VI_MODE; | |
grabkeys(); | |
if (current && CLICK_TO_FOCUS) { | |
if (VI_MODE) XGrabButton(dis, Button1, None, current->win, True, | |
ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None); | |
else XUngrabButton(dis, Button1, None, current->win); | |
} | |
desktopinfo(); | |
} | |
void toggletray() | |
{ | |
if ((TRAY_TOGGLE = !TRAY_TOGGLE)) | |
XMapWindow(dis, traywin); | |
else | |
XUnmapWindow(dis, traywin); | |
} | |
static Bool gettextprop(Window w, Atom atom, char *text, unsigned int size) { | |
char **list = NULL; | |
int n; | |
XTextProperty name; | |
if(!text || size == 0) | |
return False; | |
text[0] = '\0'; | |
XGetTextProperty(dis, w, &name, atom); | |
if (!name.nitems) | |
return False; | |
if (name.encoding == XA_STRING) | |
strncpy(text, (char *)name.value, size - 1); | |
else { | |
if (XmbTextPropertyToTextList(dis, &name, &list, &n) >= Success && n > 0 && *list) { | |
strncpy(text, *list, size - 1); | |
XFreeStringList(list); | |
} | |
} | |
text[size - 1] = '\0'; | |
XFree(name.value); | |
return True; | |
} | |
static void setwinstate(Window win, long state) | |
{ | |
long data[] = {state, None}; | |
XChangeProperty(dis, win, XInternAtom(dis, "WM_STATE", False), | |
XInternAtom(dis, "WM_STATE", False), 32, PropModeReplace, (unsigned char*)data, 2); | |
return; | |
} | |
static long ewmh_get_xembed_state(Window win) | |
{ | |
Atom rf; | |
int f; | |
unsigned long n, il; | |
long ret = 0; | |
unsigned char *data = NULL; | |
if (XGetWindowProperty(dis, win, XInternAtom(dis, "_XEMBED_INFO", False), 0L, 2, False, | |
XInternAtom(dis, "_XEMBED_INFO", False), &rf, &f, &n, &il, &data) != Success) | |
return 0; | |
if (rf == XInternAtom(dis, "_XEMBED_INFO", False) && n == 2) | |
ret = (long)data[1]; | |
if (n && data) | |
XFree(data); | |
return ret; | |
} | |
Bool systray_acquire(void) { | |
XSetWindowAttributes wattr; | |
XClientMessageEvent e; | |
if (!systray_enable || traywin) return False; | |
if (XGetSelectionOwner(dis, netatoms[NET_SYSTRAY]) != None) { | |
fprintf(stderr, "Can't initialize system tray: owned by another process\n"); | |
return False; | |
} | |
/* init traywin window */ | |
wattr.event_mask = ButtonPressMask | ExposureMask; | |
wattr.override_redirect = True; | |
wattr.background_pixmap = ParentRelative; | |
traywin = XCreateWindow(dis, root, 0, 0, 1, 1, 0, DefaultDepth(dis, screen), | |
CopyFromParent, DefaultVisual(dis, screen), | |
CWOverrideRedirect|CWBackPixmap|CWEventMask|CWBackPixel, &wattr); | |
XSelectInput(dis, traywin, KeyPressMask | ButtonPressMask); | |
XUnmapWindow(dis, traywin); | |
XSetSelectionOwner(dis, netatoms[NET_SYSTRAY], traywin, CurrentTime); | |
if (XGetSelectionOwner(dis, netatoms[NET_SYSTRAY]) != traywin) { | |
systray_freeicons(); | |
fprintf(stderr, "System tray: can't get systray manager\n"); | |
return False; | |
} | |
/* send acquire message */ | |
e.type = ClientMessage; | |
e.message_type = XInternAtom(dis, "MANAGER", False); | |
e.window = root; | |
e.format = 32; | |
e.data.l[0] = CurrentTime; | |
e.data.l[1] = netatoms[NET_SYSTRAY]; | |
e.data.l[2] = traywin; | |
e.data.l[3] = 0; | |
e.data.l[4] = 0; | |
XSendEvent(dis, root, False, StructureNotifyMask, (XEvent*)&e); | |
XSync(dis, False); | |
return True; | |
} | |
void systray_add(Window win) { | |
systray *s; | |
char name[256]; | |
if (!systray_enable) return; | |
if (systray_find(win)) return; /* don't add same window again */ | |
if (!gettextprop(win, netatoms[NET_WM_NAME], name, sizeof name)) | |
gettextprop(win, XA_WM_NAME, name, sizeof name); | |
if (name[0] == '\0') | |
return; | |
s = calloc(1, sizeof(systray)); | |
s->win = win; | |
#if HANDHELD | |
s->geo.height = SYSTRAY_ICON_SIZE; | |
s->geo.width = SYSTRAY_ICON_SIZE; | |
#else | |
s->geo.height = PANEL_HEIGHT; | |
s->geo.width = PANEL_HEIGHT; | |
#endif | |
XSelectInput(dis, s->win, StructureNotifyMask | PropertyChangeMask| EnterWindowMask | FocusChangeMask); | |
XReparentWindow(dis, s->win, traywin, 0, 0); | |
/* attach */ | |
if (trayicons) trayicons->prev = s; | |
s->next = trayicons; | |
trayicons = s; | |
return; | |
} | |
void systray_del(systray *s) { | |
systray **ss; | |
if (!systray_enable) return; | |
for (ss = &trayicons; *ss && *ss != s; ss = &(*ss)->next); | |
*ss = s->next; | |
if (s) free(s); | |
} | |
void systray_freeicons(void) { | |
systray *i, *next; | |
if (!systray_enable) return; | |
for (i = trayicons; i; i = next) { | |
XUnmapWindow(dis, i->win); | |
XReparentWindow(dis, i->win, root, 0, 0); | |
next = i->next; if (i) free(i); | |
} | |
XSetSelectionOwner(dis, netatoms[NET_SYSTRAY], None, CurrentTime); | |
XDestroyWindow(dis, traywin); | |
XSync(dis, False); | |
return; | |
} | |
systray* systray_find(Window win) { | |
systray *i; | |
if (!systray_enable) return NULL; | |
for (i = trayicons; i; i = i->next) | |
if (i->win == win) return i; | |
return NULL; | |
} | |
int systray_get_width(void) { | |
int w = 0; | |
systray *i; | |
if (!systray_enable) return 0; | |
for (i = trayicons; i; i = i->next) | |
w += i->geo.width + systray_spacing; | |
return w; | |
} | |
void systray_update(void) { | |
systray *i; | |
int x = 1, pos = ww, pos_y, height; | |
if (!systray_enable) return; | |
#if HANDHELD | |
pos_y = wh - SYSTRAY_ICON_SIZE; /* we make systray toggleable */ | |
height = SYSTRAY_ICON_SIZE; | |
#else | |
if (TOP_PANEL) pos_y = 0; | |
else pos_y = wh - PANEL_HEIGHT; | |
height = PANEL_HEIGHT; | |
#endif | |
if (!trayicons) { | |
pos -= 1; | |
XMoveResizeWindow(dis, traywin, pos, 0, 1, 1); | |
return; | |
} | |
for (i = trayicons; i; i = i->next) { | |
XMapWindow(dis, i->win); | |
XMoveResizeWindow(dis, i->win, (i->geo.x = x), 0, i->geo.width, i->geo.height); | |
x += i->geo.width + systray_spacing; | |
} | |
pos -= x; | |
XMoveResizeWindow(dis, traywin, pos, pos_y, x, height); | |
XSync(dis, False); | |
return; | |
} | |
void systray_state(systray *s) | |
{ | |
long flags; | |
int code = 0; | |
XClientMessageEvent e; | |
if (!(flags = ewmh_get_xembed_state(s->win)) || !systray_enable) | |
return; | |
if (flags & XEMBED_MAPPED) { | |
code = XEMBED_WINDOW_ACTIVATE; | |
XMapRaised(dis, s->win); | |
setwinstate(s->win, NormalState); | |
} else { | |
code = XEMBED_WINDOW_DEACTIVATE; | |
XUnmapWindow(dis, s->win); | |
setwinstate(s->win, WithdrawnState); | |
} | |
/* send embed message */ | |
e.type = ClientMessage; | |
e.message_type = XInternAtom(dis, "_XEMBED", False); | |
e.window = s->win; | |
e.format = 32; | |
e.data.l[0] = CurrentTime; | |
e.data.l[1] = code; | |
e.data.l[2] = 0; | |
e.data.l[3] = 0; | |
e.data.l[4] = 0; | |
XSendEvent(dis, s->win, False, StructureNotifyMask, (XEvent*)&e); | |
XSync(dis, False); | |
return; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment