shod

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit db7cd5cd466c358680635614db9ac986f79823b1
parent fedf704fe4a231829232b0e94ef2c5be03f038f8
Author: phillbush <phillbush@cock.li>
Date:   Thu, 16 Sep 2021 12:06:25 -0300

big chungus

Diffstat:
MMakefile | 13+++++++++----
Mshod.1 | 324+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mshod.c | 247+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Ashodc.c | 794+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 1337 insertions(+), 41 deletions(-)

diff --git a/Makefile b/Makefile @@ -18,27 +18,32 @@ LDFLAGS = ${LIBS} CC = cc # files -PROGS = shod +PROGS = shod shodc SRCS = ${PROGS}.c OBJS = ${SRCS:.c=.o} -all: shod +all: ${PROGS} shod: shod.o ${CC} -o $@ shod.o ${LDFLAGS} shod.o: theme.xpm +shodc: shodc.o + ${CC} -o $@ shodc.o ${LDFLAGS} + .c.o: ${CC} ${CFLAGS} -c $< install: all install -D -m 755 shod ${DESTDIR}${PREFIX}/bin/shod - #install -D -m 644 shod.1 ${DESTDIR}${MANPREFIX}/man1/shod.1 + install -D -m 755 shodc ${DESTDIR}${PREFIX}/bin/shodc + install -D -m 644 shod.1 ${DESTDIR}${MANPREFIX}/man1/shod.1 uninstall: rm -f ${DESTDIR}/${PREFIX}/bin/shod - #rm -f ${DESTDIR}/${MANPREFIX}/man1/shod.1 + rm -f ${DESTDIR}/${PREFIX}/bin/shodc + rm -f ${DESTDIR}/${MANPREFIX}/man1/shod.1 clean: -rm ${OBJS} ${PROGS} diff --git a/shod.1 b/shod.1 @@ -13,8 +13,45 @@ .RB [ \-r .IR buttons ] .PP -.B shodc -.IR operation " [" options "] [" arg ...] +.B shodc close +.RI [ win_id ] +.br +.B shodc desks +.br +.B shodc focus +.RB [ \-clrtbpnLRTBPN ] +.RI [ win_id ] +.br +.B shodc geom +.RB [ \-X|\-x +.IR N ] +.RB [ \-Y|\-y +.IR N ] +.RB [ \-W|\-w +.IR N ] +.RB [ \-H|\-h +.IR N ] +.RI [ win_id ] +.br +.B shodc goto +.RB [ \-m +.IR mon_id ] +.I desk_id +.br +.B shodc list +.RB [ \-ls ] +.RI [ win_id ] +.br +.B shodc sendto +.RB [ \-m +.IR mon_id ] +.I desk_id +.RI [ win_id ] +.br +.B shodc state +.RB [ \-ATR ] +.RB [ \-abfMms ] +.RI [ win_id ] .SH DESCRIPTION .B shod is a multi\-monitor floating reparenting X11 window manager which supports tiled and tabbed containers. @@ -26,7 +63,8 @@ and by responding to client messages a controller called .IR shodc (1) to control shod). -.PP The options are as follows: +.PP +The options are as follows: .TP .BI \-f " buttons" Which mouse buttons are used to focus a window when clicking on it. @@ -77,8 +115,282 @@ is the remote controller for Its first argument must be an operation to be performed. The following arguments are the options for the operation (each operation accepts a different set of options). -The last arguments are the operation's arguments. -For a list of all operations, see the OPERATIONS section below. +The last arguments are the operation's arguments, such as a window ID or a desktop ID. +.PP +Known operations for +.B shodc +are listed below. +.SS Close Window +The +.B close +operation closes a window whose ID is provided as argument. +If no argument is provided, close the active window. +.SS List desktops +The +.B desks +operation lists the desktops, one per line. +.PP +If the line begins with an asterisk, the desktop is the focused one; +If the line begins with a hyphen, the desktop has an urgent window in it. +The number is the number of windows in the desktop. +.SS Focus Window +The +.B focus +operation focus a window whose ID is provided as argument. +If a option is provided, focus a window relative to the window provided as argument. +.PP +The options are as follows: +.TP +.B \-c +Cycle focus. This option only makes sense when using +.BR \-n , +.BR \-p , +.BR \-N , +or +.BR \-P . +.TP +.B \-l +Focus window in the closest container to the left of the container of the window provided as argument +(or the active window, if no argument is provided). +.TP +.B \-r +Focus window in the closest container to the right of the container of the window provided as argument +(or the active window, if no argument is provided). +.TP +.B \-t +Focus window in the closest container on the top of the container of the window provided as argument +(or the active window, if no argument is provided). +.TP +.B \-b +Focus window in the closest container on the bottom of the container of the window provided as argument +(or the active window, if no argument is provided). +.TP +.B \-p +Focus window in the previous container in the focus list +in relation to the container of the window provided as argument +(or the active window, if no argument is provided). +.TP +.B \-n +Focus window in the next container in the focus list +in relation to the container of the window provided as argument +(or the active window, if no argument is provided). +.TP +.B \-L +Focus window in the column to the left of the window provided as argument +(or the active window, if no argument is provided). +.TP +.B \-R +Focus window in the column to the right of the window provided as argument +(or the active window, if no argument is provided). +.TP +.B \-T +Focus window in the row above (on the top of) the window provided as argument +(or the active window, if no argument is provided). +.TP +.B \-B +Focus window in the row below (on the bottom of) the window provided as argument +(or the active window, if no argument is provided). +.TP +.B \-P +Focus window in the tab previous to the window provided as argument +(or the active window, if no argument is provided). +.TP +.B \-N +Focus window in the tab next to the window provided as argument +(or the active window, if no argument is provided). +.SS Set Geometry +The +.B geom +operation sets the geometry (position and size) of the container of the window whose ID is provided as argument. +If no argument is provided, sets the geometry of the container of the active window. +If no position (set by the options +.B \-X +or +.B \-x +and +.B \-Y +or +.BR \-y ) +is provided, move window to position 0,0 (top left corner). +.PP +The options are as follows: +.TP +.B \-X \fIN\fP +Relative X position. +Set the position on the X axis to N plus the current X position of the container. +.TP +.B \-x \fIN\fP +Absolute X position. +Set the position on the X axis to N. +.TP +.B \-Y \fIN\fP +Relative Y position. +Set the position on the Y axis to N plus the current Y position of the container. +.TP +.B \-y \fIN\fP +Absolute Y position. +Set the position on the Y axis to N. +.TP +.B \-W \fIN\fP +Relative width. +Set the width of the contianer to N plus its current width. +.TP +.B \-w \fIN\fP +Absolute width. +Set the width of the contianer to N. +.TP +.B \-H \fIN\fP +Relative height. +Set the height of the contianer to N plus its current height. +.TP +.B \-h \fIN\fP +Absolute height. +Set the height of the contianer to N. +.SS Go To Desktop +The +.B goto +operation goes to the desktop ID provided as argument. +Different of other window managers, shod counts desktop from 1; +So the first desktop is the desktop 1, not the desktop 0. +.PP +The options are as follows: +.TP +.B \-M \fImonitor\fP +Goes to a desktop on the provided monitor rather than on the currently focused monitor. +Monitors are counted from 1, not from 0. +.SS List windows +The +.B list +operation +lists windows, one entry per line. +If a window ID is provided as argument, list only this window. +.PP +The option are as follows: +.TP +.B \-l +Long list format. +More information on this format below. +.TP +.B \-s +Sort by stacking order. +.PP +If the +.B \-l +option is given, the following information (delimited by tabs) is displayed for each window: +window state, +window desktop, +window geometry (size and position), +ID of the container window is on, +ID of the row window is on, +ID of the window, +name of the window. +.PP +The state consists of a sequence of eight characters, +each one meaning a state for the container. +If a character is \- the state is not set or does not apply to the window. +.IP \(bu 2 +The first character is +.B d +to indicate that the window is a dialog. +.IP \(bu 2 +The second character is +.B y +to indicate that the window's container is sticky. +.IP \(bu 2 +The third character is +.B M +to indicate that the window's container is maximized. +.IP \(bu 2 +The fourth character is +.B m +to indicate that the window's container is minimized. +.IP \(bu 2 +The fifth character is +.B f +to indicate that the window's container is fullscreen. +.IP \(bu 2 +The sixth character is +.B a +to indicate that the window's container is above others, +or +.B b +to indicate that the window's container is below others. +.IP \(bu 2 +The seventh character is +.B u +to indicate that the window has the urgency hint set, +.B a +to indicate that the window demands attention, +or +.B U +to indicate that the window is both urgent and demands attention. +.IP \(bu 2 +The eighth and last character is +.B a +to indicate that the window is active, +.B f +to indicate that the window is focused, +or +.B A +to indicate that the window is both active and focused. +.SS Send To Desktop +The +.B sendto +operation sends to the desktop ID provided as first argument +the container of the window whose ID provided as second argument. +If no window ID is provided, sends the container of the active window to that desktop. +Different of other window managers, shod counts desktop from 1; +So the first desktop is the desktop 1, not the desktop 0. +.PP +The options are as follows: +.TP +.B \-M \fImonitor\fP +Sends to a desktop on the provided monitor rather than on the currently focused monitor. +Monitors are counted from 1, not from 0. +.SS Set Container State +The +.B state +operation +sets the state of the container of the window whose ID is provided as argument. +If no argument is provided, sets the state of the container of the active window. +.PP +The options are as follows: +.TP +.B \-a +Set state above. +Raise container above others. +.TP +.B \-b +Set state below. +Lower container below others. +.TP +.B \-f +Set state fullscreen. +Make container fullscreen. +.TP +.B \-M +Set state maximized. +Maximize container +.TP +.B \-m +Set state minimized. +Minimize container. +.TP +.B \-s +Set state sticky. +Stick container to the monitor. +.TP +.B \-A +Add (set) state. +Force state to be set. +.TP +.B \-T +Toggle state. +Set state if it is unset, or unset it if it is set. +.TP +.B \-R +Remove (unset) state. +Force state to be unset. .SH DESKTOP .PP .B shod @@ -266,8 +578,6 @@ to rearrange the size of columns and rows. When a container is moved from one monitor to another, that container moves from the desktop it is in to the focused desktop of the monitor it is moved to. -.SH OPERATIONS -TODO. .SH ENVIRONMENT The following environment variables affect the execution of .B shod diff --git a/shod.c b/shod.c @@ -30,6 +30,11 @@ #define DROPPIXELS 30 /* number of pixels from the border where a tab can be dropped in */ #define RESIZETIME 64 /* time to redraw containers during resizing */ +#define _SHOD_RELATIVE_X ((long)(1 << 16)) +#define _SHOD_RELATIVE_Y ((long)(1 << 17)) +#define _SHOD_RELATIVE_WIDTH ((long)(1 << 18)) +#define _SHOD_RELATIVE_HEIGHT ((long)(1 << 19)) + /* title bar buttons */ enum { BUTTON_NONE, @@ -37,7 +42,7 @@ enum { BUTTON_RIGHT }; -/* state flag */ +/* state action */ enum { REMOVE = 0, ADD = 1, @@ -175,6 +180,23 @@ enum { _NET_WM_MOVERESIZE_CANCEL = 11, /* cancel operation */ }; +/* focus relative direction */ +enum { + _SHOD_FOCUS_ABSOLUTE = 0, + _SHOD_FOCUS_LEFT_CONTAINER = 1, + _SHOD_FOCUS_RIGHT_CONTAINER = 2, + _SHOD_FOCUS_TOP_CONTAINER = 3, + _SHOD_FOCUS_BOTTOM_CONTAINER = 4, + _SHOD_FOCUS_PREVIOUS_CONTAINER = 5, + _SHOD_FOCUS_NEXT_CONTAINER = 6, + _SHOD_FOCUS_LEFT_WINDOW = 7, + _SHOD_FOCUS_RIGHT_WINDOW = 8, + _SHOD_FOCUS_TOP_WINDOW = 9, + _SHOD_FOCUS_BOTTOM_WINDOW = 10, + _SHOD_FOCUS_PREVIOUS_WINDOW = 11, + _SHOD_FOCUS_NEXT_WINDOW = 12, +}; + /* window eight sections (aka octants) */ enum Octant { C = 0, @@ -1039,6 +1061,89 @@ getnextfocused(struct Monitor *mon, struct Desktop *desk) return c; } +/* get next focused container in given direction from another */ +static struct Container * +getbydirection(struct Container *rel, int dir) +{ + struct Monitor *mon; + struct Desktop *desk; + struct Container *c, *ret; + + ret = NULL; + mon = rel->mon; + desk = rel->issticky ? rel->mon->seldesk : rel->desk; + for (c = wm.focuslist; c != NULL; c = c->fnext) { + if (c == rel || c->isminimized) + continue; + if (c->mon == mon && (c->issticky || c->desk == desk)) { + switch (dir) { + case _SHOD_FOCUS_LEFT_CONTAINER: + if (c->x <= rel->x && (ret == NULL || c->x > ret->x)) + ret = c; + break; + case _SHOD_FOCUS_RIGHT_CONTAINER: + if (c->x >= rel->x && (ret == NULL || c->x < ret->x)) + ret = c; + break; + case _SHOD_FOCUS_TOP_CONTAINER: + if (c->y <= rel->y && (ret == NULL || c->y > ret->y)) + ret = c; + break; + case _SHOD_FOCUS_BOTTOM_CONTAINER: + if (c->y >= rel->y && (ret == NULL || c->y < ret->y)) + ret = c; + break; + default: + return NULL; + } + } + } + return ret; +} + +/* get first focused container in the same monitor and desktop of rel */ +static struct Container * +getfirstfocused(struct Container *rel) +{ + struct Monitor *mon; + struct Desktop *desk; + struct Container *c; + + mon = rel->mon; + desk = rel->issticky ? rel->mon->seldesk : rel->desk; + for (c = wm.focuslist; c != NULL; c = c->fnext) { + if (c == rel) + return NULL; + if (c->isminimized) + continue; + if (c->mon == mon && (c->issticky || c->desk == desk)) { + return c; + } + } + return NULL; +} + +/* get last focused container in the same monitor and desktop of rel */ +static struct Container * +getlastfocused(struct Container *rel) +{ + struct Monitor *mon; + struct Desktop *desk; + struct Container *c, *ret; + + ret = NULL; + mon = rel->mon; + desk = rel->issticky ? rel->mon->seldesk : rel->desk; + for (c = rel; c != NULL; c = c->fnext) { + if (c->isminimized) + continue; + if (c != rel && c->mon == mon && (c->issticky || c->desk == desk)) { + ret = c; + } + } + return ret; +} + /* get pointer position within a container */ static enum Octant getoctant(struct Container *c, int x, int y) @@ -1092,11 +1197,24 @@ getdivisions(struct Container *c, struct Column **cdiv, struct Row **rdiv, int x /* get pointer to nth desktop in focused monitor */ static struct Desktop * -getdesk(long int n) +getdesk(long int n, long int m) { + struct Monitor *mon, *tmp; + long int i; + if (n < 0 || n >= config.ndesktops) return NULL; - return &wm.selmon->desks[n]; + if (m == 0) { + return &wm.selmon->desks[n]; + } else { + mon = NULL; + for (i = 0, tmp = wm.selmon; i < m && tmp != NULL; i++, tmp = tmp->next) + mon = tmp; + if (mon != NULL) { + return &mon->desks[n]; + } + } + return NULL; } /* check whether window was placed by the user */ @@ -2164,9 +2282,9 @@ containerconfigure(struct Container *c, unsigned int valuemask, XWindowChanges * c->nx = wc->x; if (valuemask & CWY) c->ny = wc->y; - if (valuemask & CWWidth) + if ((valuemask & CWWidth) && wc->width >= visual.center + 2 * visual.border) c->nw = wc->width; - if (valuemask & CWHeight) + if ((valuemask & CWHeight) && wc->height >= visual.center + 2 * visual.border) c->nh = wc->height; containercalccols(c, 1); containermoveresize(c); @@ -2879,8 +2997,6 @@ tabfocus(struct Tab *t, int gotodesk) wm.prevfocused = wm.focused; if (t == NULL) { wm.focused = NULL; - if (wm.prevfocused != NULL) - containerdecorate(wm.prevfocused, NULL, NULL, 1, 0); XSetInputFocus(dpy, wm.focuswin, RevertToParent, CurrentTime); ewmhsetactivewindow(None); } else { @@ -2904,8 +3020,6 @@ tabfocus(struct Tab *t, int gotodesk) } if (t->isurgent) tabclearurgency(t); - if (wm.prevfocused) - containerdecorate(wm.prevfocused, NULL, NULL, 1, 0); containeraddfocus(c); containerdecorate(c, NULL, NULL, 1, 0); containerminimize(c, 0, 0); @@ -2916,6 +3030,10 @@ tabfocus(struct Tab *t, int gotodesk) shodgroup(c); ewmhsetstate(c); } + if (wm.prevfocused != NULL) { + containerdecorate(wm.prevfocused, NULL, NULL, 1, 0); + ewmhsetstate(wm.prevfocused); + } } /* update tab title */ @@ -4314,6 +4432,7 @@ xeventclientmessage(XEvent *e) struct Monitor *mon; struct Desktop *prevdesk, *desk; struct Container *c; + struct Tab *t; struct Winres res; XClientMessageEvent *ev; XWindowChanges wc; @@ -4323,7 +4442,7 @@ xeventclientmessage(XEvent *e) ev = &e->xclient; res = getwin(ev->window); if (ev->message_type == atoms[_NET_CURRENT_DESKTOP]) { - deskfocus(getdesk(ev->data.l[0])); + deskfocus(getdesk(ev->data.l[0], ev->data.l[2])); } else if (ev->message_type == atoms[_NET_SHOWING_DESKTOP]) { if (ev->data.l[0]) { deskshow(1); @@ -4355,32 +4474,100 @@ xeventclientmessage(XEvent *e) } } } else if (ev->message_type == atoms[_NET_ACTIVE_WINDOW]) { - if (res.t == NULL) + if (res.t == NULL && wm.focused != NULL) { + res.c = wm.focused; + res.t = wm.focused->selcol->selrow->seltab; + } + c = NULL; + t = NULL; + switch (ev->data.l[3]) { + case _SHOD_FOCUS_LEFT_CONTAINER: + case _SHOD_FOCUS_RIGHT_CONTAINER: + case _SHOD_FOCUS_TOP_CONTAINER: + case _SHOD_FOCUS_BOTTOM_CONTAINER: + if (res.c != NULL && (c = getbydirection(res.c, ev->data.l[3])) != NULL) + t = c->selcol->selrow->seltab; + break; + case _SHOD_FOCUS_PREVIOUS_CONTAINER: + if (res.c != NULL && res.c->fprev != NULL) { + c = res.c->fprev; + t = c->selcol->selrow->seltab; + } else if (ev->data.l[4] && res.c != NULL && (c = getlastfocused(res.c)) != NULL) { + t = c->selcol->selrow->seltab; + } + break; + case _SHOD_FOCUS_NEXT_CONTAINER: + if (res.c != NULL && res.c->fnext != NULL) { + c = res.c->fnext; + t = c->selcol->selrow->seltab; + } else if (ev->data.l[4] && res.c != NULL && (c = getfirstfocused(res.c)) != NULL) { + t = c->selcol->selrow->seltab; + } + break; + case _SHOD_FOCUS_LEFT_WINDOW: + if (res.t != NULL && res.t->row->col->prev != NULL) { + c = res.c; + t = res.t->row->col->prev->selrow->seltab; + } + break; + case _SHOD_FOCUS_RIGHT_WINDOW: + if (res.t != NULL && res.t->row->col->next != NULL) { + c = res.c; + t = res.t->row->col->next->selrow->seltab; + } + break; + case _SHOD_FOCUS_TOP_WINDOW: + if (res.t != NULL && res.t->row->prev != NULL) { + c = res.c; + t = res.t->row->prev->seltab; + } + break; + case _SHOD_FOCUS_BOTTOM_WINDOW: + if (res.t != NULL && res.t->row->next != NULL) { + c = res.c; + t = res.t->row->next->seltab; + } + break; + case _SHOD_FOCUS_PREVIOUS_WINDOW: + c = res.c; + if (res.t != NULL && res.t->prev != NULL) { + t = res.t->prev; + } else if (ev->data.l[4] && res.t != NULL) { + for (t = res.t->row->tabs; t != NULL; t = t->next) { + if (t->next == NULL) { + break; + } + } + } + break; + case _SHOD_FOCUS_NEXT_WINDOW: + if (res.t != NULL && res.t->next != NULL) { + c = res.c; + t = res.t->next; + } else if (ev->data.l[4] && res.t != NULL) { + t = res.t->row->tabs; + } + break; + default: + c = res.c; + t = res.t; + break; + } + if (c == NULL || t == NULL) return; - containerraise(res.c); - tabfocus(res.t, 1); + containerraise(c); + tabfocus(t, 1); } else if (ev->message_type == atoms[_NET_CLOSE_WINDOW]) { windowclose(ev->window); } else if (ev->message_type == atoms[_NET_MOVERESIZE_WINDOW]) { value_mask = 0; if (res.c == NULL) return; - if (ev->data.l[0] & 1 << 8) { - wc.x = ev->data.l[1]; - value_mask |= CWX; - } - if (ev->data.l[0] & 1 << 9) { - wc.y = ev->data.l[2]; - value_mask |= CWY; - } - if (ev->data.l[0] & 1 << 10) { - wc.width = ev->data.l[3]; - value_mask |= CWWidth; - } - if (ev->data.l[0] & 1 << 11) { - wc.height = ev->data.l[4]; - value_mask |= CWHeight; - } + value_mask = CWX | CWY | CWWidth | CWHeight; + wc.x = (ev->data.l[0] & _SHOD_RELATIVE_X) ? res.c->x + res.c->b + ev->data.l[1] : ev->data.l[1]; + wc.y = (ev->data.l[0] & _SHOD_RELATIVE_Y) ? res.c->y + res.c->b + ev->data.l[2] : ev->data.l[2]; + wc.width = (ev->data.l[0] & _SHOD_RELATIVE_WIDTH) ? res.c->w + ev->data.l[3] - 2 * res.c->b : ev->data.l[3]; + wc.height = (ev->data.l[0] & _SHOD_RELATIVE_HEIGHT) ? res.c->h + ev->data.l[4] - 2 * res.c->b : ev->data.l[4]; if (res.d != NULL) { dialogconfigure(res.d, value_mask, &wc); } else { @@ -4392,7 +4579,7 @@ xeventclientmessage(XEvent *e) if (ev->data.l[0] == 0xFFFFFFFF) { containerstick(res.c, ADD); } else if (!res.c->isminimized) { - if ((desk = getdesk(ev->data.l[0])) == NULL || desk == res.c->desk) + if ((desk = getdesk(ev->data.l[0], ev->data.l[2])) == NULL || desk == res.c->desk) return; if (res.c->issticky) containerstick(res.c, REMOVE); diff --git a/shodc.c b/shodc.c @@ -0,0 +1,794 @@ +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Xutil.h> + +#define NAMEMAXLEN 128 +#define DIRECT_ACTION 2 +#define _SHOD_RELATIVE_X ((long)(1 << 16)) +#define _SHOD_RELATIVE_Y ((long)(1 << 17)) +#define _SHOD_RELATIVE_WIDTH ((long)(1 << 18)) +#define _SHOD_RELATIVE_HEIGHT ((long)(1 << 19)) + +/* state action */ +enum { + REMOVE = 0, + ADD = 1, + TOGGLE = 2 +}; + +/* long list char positions */ +enum { + LIST_DIALOG = 0, + LIST_STICKY = 1, + LIST_MAXIMIZED = 2, + LIST_MINIMIZED = 3, + LIST_FULLSCREEN = 4, + LIST_LAYER = 5, + LIST_URGENCY = 6, + LIST_ACTIVE = 7 +}; + +/* focus relative direction */ +enum Direction { + _SHOD_FOCUS_ABSOLUTE = 0, + _SHOD_FOCUS_LEFT_CONTAINER = 1, + _SHOD_FOCUS_RIGHT_CONTAINER = 2, + _SHOD_FOCUS_TOP_CONTAINER = 3, + _SHOD_FOCUS_BOTTOM_CONTAINER = 4, + _SHOD_FOCUS_PREVIOUS_CONTAINER = 5, + _SHOD_FOCUS_NEXT_CONTAINER = 6, + _SHOD_FOCUS_LEFT_WINDOW = 7, + _SHOD_FOCUS_RIGHT_WINDOW = 8, + _SHOD_FOCUS_TOP_WINDOW = 9, + _SHOD_FOCUS_BOTTOM_WINDOW = 10, + _SHOD_FOCUS_PREVIOUS_WINDOW = 11, + _SHOD_FOCUS_NEXT_WINDOW = 12, +}; + +/* atoms for the list operation */ +enum { + UTF8STRING, + _NET_WM_NAME, + _NET_WM_VISIBLE_NAME, + _NET_WM_DESKTOP, + _NET_NUMBER_OF_DESKTOPS, + _NET_DESKTOP_NAMES, + _NET_CLOSE_WINDOW, + _NET_MOVERESIZE_WINDOW, + _NET_CURRENT_DESKTOP, + _NET_ACTIVE_WINDOW, + _NET_CLIENT_LIST, + _NET_CLIENT_LIST_STACKING, + _NET_WM_STATE, + _NET_WM_STATE_STICKY, + _NET_WM_STATE_MAXIMIZED_VERT, + _NET_WM_STATE_MAXIMIZED_HORZ, + _NET_WM_STATE_HIDDEN, + _NET_WM_STATE_FULLSCREEN, + _NET_WM_STATE_ABOVE, + _NET_WM_STATE_BELOW, + _NET_WM_STATE_DEMANDS_ATTENTION, + _NET_WM_STATE_FOCUSED, + _SHOD_GROUP_TAB, + _SHOD_GROUP_CONTAINER, + ATOM_LAST +}; + +/* global variables */ +static Display *dpy; +static int screen; +static Window root; +static Window active; +static Atom atoms[ATOM_LAST]; + +/* show usage and exit */ +static void +usage(void) +{ + (void)fprintf(stderr, "usage: shodc close [WIN_ID]\n"); + (void)fprintf(stderr, " shodc desks\n"); + (void)fprintf(stderr, " shodc focus [-clrtbpnLRTBPN] [WIN_ID]\n"); + (void)fprintf(stderr, " shodc geom [-X|-x N] [-Y|-y N] [-W|-w N] [-H|-h N] [WIN_ID]\n"); + (void)fprintf(stderr, " shodc goto [-m MON_ID] DESK_ID\n"); + (void)fprintf(stderr, " shodc list [-ls] [WIN_ID]\n"); + (void)fprintf(stderr, " shodc sendto [-m MON_ID] DESK_ID [WIN_ID]\n"); + (void)fprintf(stderr, " shodc state [-ATR] [-abfMms] [WIN_ID]\n"); + exit(1); +} + +/* call calloc checking for errors */ +static void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + if ((p = calloc(nmemb, size)) == NULL) + err(1, "calloc"); + return p; +} + +/* call strndup checking for error */ +static char * +estrndup(const char *s, size_t maxlen) +{ + char *p; + + if ((p = strndup(s, maxlen)) == NULL) + err(1, "strndup"); + return p; +} + +/* send client message to root window */ +static void +clientmsg(Window win, Atom atom, unsigned long d0, unsigned long d1, unsigned long d2, unsigned long d3, unsigned long d4) +{ + XEvent ev; + long mask = SubstructureRedirectMask | SubstructureNotifyMask; + + ev.xclient.type = ClientMessage; + ev.xclient.serial = 0; + ev.xclient.send_event = True; + ev.xclient.message_type = atom; + ev.xclient.window = win; + ev.xclient.format = 32; + ev.xclient.data.l[0] = d0; + ev.xclient.data.l[1] = d1; + ev.xclient.data.l[2] = d2; + ev.xclient.data.l[3] = d3; + ev.xclient.data.l[4] = d4; + if (!XSendEvent(dpy, root, False, mask, &ev)) { + errx(1, "could not send event"); + } +} + +/* get active window */ +static Window +getactivewin(void) +{ + Window win; + unsigned char *list; + unsigned long len; + unsigned long dl; /* dummy variable */ + int di; /* dummy variable */ + Atom da; /* dummy variable */ + + list = NULL; + if (XGetWindowProperty(dpy, root, atoms[_NET_ACTIVE_WINDOW], 0L, 1024, False, XA_WINDOW, + &da, &di, &len, &dl, &list) != Success || list == NULL) + return None; + win = *(Window *)list; + XFree(list); + return win; +} + +/* get window from string */ +static Window +getwin(const char *s) +{ + return (Window)strtol(s, NULL, 0); +} + +/* get window property from root window */ +static unsigned long +getwinprop(Window win, Atom prop, Window **wins) +{ + unsigned char *list; + unsigned long len; + unsigned long dl; /* dummy variable */ + int di; /* dummy variable */ + Atom da; /* dummy variable */ + + list = NULL; + if (XGetWindowProperty(dpy, win, prop, 0L, 1024, False, XA_WINDOW, + &da, &di, &len, &dl, &list) != Success || list == NULL) { + *wins = NULL; + return 0; + } + *wins = (Window *)list; + return len; +} + +/* get array of windows */ +static unsigned long +getwins(Window **wins, int sflag) +{ + if (sflag) + return getwinprop(root, atoms[_NET_CLIENT_LIST_STACKING], wins); + else + return getwinprop(root, atoms[_NET_CLIENT_LIST], wins); +} + +/* get atom property from given window */ +static unsigned long +getatomprop(Window win, Atom prop, Atom **atoms) +{ + unsigned char *list; + unsigned long len; + unsigned long dl; /* dummy variable */ + int di; /* dummy variable */ + Atom da; /* dummy variable */ + + list = NULL; + if (XGetWindowProperty(dpy, win, prop, 0L, 1024, False, XA_ATOM, + &da, &di, &len, &dl, &list) != Success || list == NULL) { + *atoms = NULL; + return 0; + } + *atoms = (Atom *)list; + return len; +} + +/* get cardinal property from given window */ +static unsigned long +getcardprop(Window win, Atom prop) +{ + unsigned long ret; + unsigned char *u; + unsigned long dl; /* dummy variable */ + int di; /* dummy variable */ + Atom da; /* dummy variable */ + + u = NULL; + if (XGetWindowProperty(dpy, win, prop, 0L, 1L, False, XA_CARDINAL, + &da, &di, &dl, &dl, &u) != Success || u == NULL) { + return 0; + } + ret = *(unsigned long *)u; + XFree(u); + return ret; +} + +/* get array of desktop names, return size of array */ +static unsigned long +getdesknames(char **desknames) +{ + unsigned char *str; + unsigned long len; + unsigned long dl; /* dummy variable */ + int di; /* dummy variable */ + Atom da; /* dummy variable */ + + + if (XGetWindowProperty(dpy, root, atoms[_NET_DESKTOP_NAMES], 0, ~0, False, + UTF8STRING, &da, &di, &len, &dl, &str) == + Success && str) { + *desknames = (char *)str; + } else { + *desknames = NULL; + len = 0; + } + + return len; +} + +/* get window name */ +char * +getwinname(Window win) +{ + XTextProperty tprop; + char **list = NULL; + char *name = NULL; + unsigned char *p = NULL; + unsigned long size, dl; + int di; + Atom da; + + if (XGetWindowProperty(dpy, win, atoms[_NET_WM_NAME], 0L, 8L, False, UTF8STRING, + &da, &di, &size, &dl, &p) == Success && p) { + name = estrndup((char *)p, NAMEMAXLEN); + XFree(p); + } else if (XGetWMName(dpy, win, &tprop) && + XmbTextPropertyToTextList(dpy, &tprop, &list, &di) == Success && + di > 0 && list && *list) { + name = estrndup(*list, NAMEMAXLEN); + XFreeStringList(list); + XFree(tprop.value); + } + return name; +} + +/* intern atoms */ +static void +initatoms(void) +{ + char *atomnames[ATOM_LAST] = { + [UTF8STRING] = "UTF8STRING", + [_NET_WM_NAME] = "_NET_WM_NAME", + [_NET_WM_VISIBLE_NAME] = "_NET_WM_VISIBLE_NAME", + [_NET_WM_DESKTOP] = "_NET_WM_DESKTOP", + [_NET_NUMBER_OF_DESKTOPS] = "_NET_NUMBER_OF_DESKTOPS", + [_NET_DESKTOP_NAMES] = "_NET_DESKTOP_NAMES", + [_NET_CLOSE_WINDOW] = "_NET_CLOSE_WINDOW", + [_NET_MOVERESIZE_WINDOW] = "_NET_MOVERESIZE_WINDOW", + [_NET_CURRENT_DESKTOP] = "_NET_CURRENT_DESKTOP", + [_NET_ACTIVE_WINDOW] = "_NET_ACTIVE_WINDOW", + [_NET_CLIENT_LIST] = "_NET_CLIENT_LIST", + [_NET_CLIENT_LIST_STACKING] = "_NET_CLIENT_LIST_STACKING", + [_NET_WM_STATE] = "_NET_WM_STATE", + [_NET_WM_STATE_STICKY] = "_NET_WM_STATE_STICKY", + [_NET_WM_STATE_MAXIMIZED_VERT] = "_NET_WM_STATE_MAXIMIZED_VERT", + [_NET_WM_STATE_MAXIMIZED_HORZ] = "_NET_WM_STATE_MAXIMIZED_HORZ", + [_NET_WM_STATE_HIDDEN] = "_NET_WM_STATE_HIDDEN", + [_NET_WM_STATE_FULLSCREEN] = "_NET_WM_STATE_FULLSCREEN", + [_NET_WM_STATE_ABOVE] = "_NET_WM_STATE_ABOVE", + [_NET_WM_STATE_BELOW] = "_NET_WM_STATE_BELOW", + [_NET_WM_STATE_DEMANDS_ATTENTION] = "_NET_WM_STATE_DEMANDS_ATTENTION", + [_NET_WM_STATE_FOCUSED] = "_NET_WM_STATE_FOCUSED", + [_SHOD_GROUP_TAB] = "_SHOD_GROUP_TAB", + [_SHOD_GROUP_CONTAINER] = "_SHOD_GROUP_CONTAINER", + }; + + XInternAtoms(dpy, atomnames, ATOM_LAST, False, atoms); +} + +/* close window */ +static void +closewin(int argc, char *argv[]) +{ + Window win; + + win = None; + if (argc == 1) + win = active; + else if (argc == 2 && argv[1][0] == '-') + win = getwin(argv[1]); + else + usage(); + clientmsg(win, atoms[_NET_CLOSE_WINDOW], CurrentTime, DIRECT_ACTION, 0, 0, 0); +} + +/* focus window */ +static void +focuswin(int argc, char *argv[]) +{ + Window win; + enum Direction d; + int cycle, c; + + cycle = 0; + d = _SHOD_FOCUS_ABSOLUTE; + win = None; + while ((c = getopt(argc, argv, "clrtbpnLRTBPN")) != -1) { + switch (c) { + case 'c': + cycle = 1; + break; + case 'L': + d = _SHOD_FOCUS_LEFT_WINDOW; + break; + case 'R': + d = _SHOD_FOCUS_RIGHT_WINDOW; + break; + case 'T': + d = _SHOD_FOCUS_TOP_WINDOW; + break; + case 'B': + d = _SHOD_FOCUS_BOTTOM_WINDOW; + break; + case 'P': + d = _SHOD_FOCUS_PREVIOUS_WINDOW; + break; + case 'N': + d = _SHOD_FOCUS_NEXT_WINDOW; + break; + case 'l': + d = _SHOD_FOCUS_LEFT_CONTAINER; + break; + case 'r': + d = _SHOD_FOCUS_RIGHT_CONTAINER; + break; + case 't': + d = _SHOD_FOCUS_TOP_CONTAINER; + break; + case 'b': + d = _SHOD_FOCUS_BOTTOM_CONTAINER; + break; + case 'p': + d = _SHOD_FOCUS_PREVIOUS_CONTAINER; + break; + case 'n': + d = _SHOD_FOCUS_NEXT_CONTAINER; + break; + default: + usage(); + break; + } + } + argc -= optind; + argv += optind; + if (argc > 1) + usage(); + if (argc == 1) + win = getwin(argv[0]); + clientmsg(win, atoms[_NET_ACTIVE_WINDOW], DIRECT_ACTION, CurrentTime, 0, d, cycle); +} + +/* set container geometry */ +static void +setgeom(int argc, char *argv[]) +{ + Window win; + long x, y, w, h; + int rel; + int c; + + rel = 0; + x = y = w = h = 0; + while ((c = getopt(argc, argv, "XYWHxywh")) != -1) { + switch (c) { + case 'X': + rel |= _SHOD_RELATIVE_X; + x = strtol(optarg, NULL, 10); + break; + case 'Y': + rel |= _SHOD_RELATIVE_Y; + y = strtol(optarg, NULL, 10); + break; + case 'W': + rel |= _SHOD_RELATIVE_WIDTH; + w = strtol(optarg, NULL, 10); + break; + case 'H': + rel |= _SHOD_RELATIVE_HEIGHT; + h = strtol(optarg, NULL, 10); + break; + case 'x': + rel &= ~_SHOD_RELATIVE_X; + x = strtol(optarg, NULL, 10); + break; + case 'y': + rel &= ~_SHOD_RELATIVE_Y; + y = strtol(optarg, NULL, 10); + break; + case 'w': + rel &= ~_SHOD_RELATIVE_WIDTH; + w = strtol(optarg, NULL, 10); + break; + case 'h': + rel &= ~_SHOD_RELATIVE_HEIGHT; + h = strtol(optarg, NULL, 10); + break; + default: + usage(); + break; + } + } + argc -= optind; + argv += optind; + if (argc > 1) + usage(); + win = (argc == 1) ? getwin(argv[0]) : active; + clientmsg(win, atoms[_NET_MOVERESIZE_WINDOW], rel, x, y, w, h); +} + +/* go to desktop */ +static void +gotodesk(int argc, char *argv[]) +{ + long mon, desk; + int c; + + mon = 0; + while ((c = getopt(argc, argv, "m")) != -1) { + switch (c) { + case 'm': + mon = strtol(optarg, NULL, 0); + break; + default: + usage(); + break; + } + } + argc -= optind; + argv += optind; + if (argc != 1) + usage(); + desk = strtol(argv[0], NULL, 0) - 1; + clientmsg(None, atoms[_NET_CURRENT_DESKTOP], desk, CurrentTime, mon, 0, 0); +} + +/* list window type, states, properties, etc */ +static void +longlist(Window win) +{ + Atom *as; + int x, y; + unsigned int w, h, b, du; + int desk; + unsigned long i, natoms, l; + char state[] = "--------"; + char *name; + XWMHints *wmhints = NULL; + Window *list = NULL; + Window transfor = 0x0; + Window dw; + XID container, tab; + + container = tab = 0x0; + if ((wmhints = XGetWMHints(dpy, win)) != NULL) { + if (wmhints->flags & XUrgencyHint) + state[LIST_URGENCY] = 'u'; + XFree(wmhints); + } + if (getwinprop(win, XA_WM_TRANSIENT_FOR, &list) > 0) { + if (*list != None) { + transfor = *list; + state[LIST_DIALOG] = 'd'; + } + XFree(list); + } + if (getwinprop(win, atoms[_SHOD_GROUP_CONTAINER], &list) > 0) { + if (*list != None) { + container = *list; + } + XFree(list); + } + if (getwinprop(win, atoms[_SHOD_GROUP_TAB], &list) > 0) { + if (*list != None) { + tab = *list; + } + XFree(list); + } + if (win == active) + state[LIST_ACTIVE] = 'a'; + if ((natoms = getatomprop(win, atoms[_NET_WM_STATE], &as)) > 0) { + for (i = 0; i < natoms; i++) { + if (as[i] == atoms[_NET_WM_STATE_STICKY]) { + state[LIST_STICKY] = 'y'; + } else if (as[i] == atoms[_NET_WM_STATE_MAXIMIZED_VERT]) { + if (state[LIST_MAXIMIZED] == 'h') { + state[LIST_MAXIMIZED] = 'M'; + } else { + state[LIST_MAXIMIZED] = 'v'; + } + } else if (as[i] == atoms[_NET_WM_STATE_MAXIMIZED_HORZ]) { + if (state[LIST_MAXIMIZED] == 'v') { + state[LIST_MAXIMIZED] = 'M'; + } else { + state[LIST_MAXIMIZED] = 'h'; + } + } else if (as[i] == atoms[_NET_WM_STATE_HIDDEN]) { + state[LIST_MINIMIZED] = 'm'; + } else if (as[i] == atoms[_NET_WM_STATE_FULLSCREEN]) { + state[LIST_FULLSCREEN] = 'F'; + } else if (as[i] == atoms[_NET_WM_STATE_ABOVE]) { + state[LIST_LAYER] = 'a'; + } else if (as[i] == atoms[_NET_WM_STATE_BELOW]) { + state[LIST_LAYER] = 'b'; + } else if (as[i] == atoms[_NET_WM_STATE_DEMANDS_ATTENTION]) { + if (state[LIST_URGENCY] == 'u') { + state[LIST_URGENCY] = 'U'; + } else { + state[LIST_URGENCY] = 'a'; + } + } else if (as[i] == atoms[_NET_WM_STATE_FOCUSED]) { + if (state[LIST_ACTIVE] == 'a') { + state[LIST_ACTIVE] = 'A'; + } else { + state[LIST_ACTIVE] = 'f'; + } + } + } + XFree(as); + } + l = getcardprop(win, atoms[_NET_WM_DESKTOP]); + desk = (l == 0xFFFFFFFF) ? -1 : l; + + name = getwinname(win); + XGetGeometry(dpy, win, &dw, &x, &y, &w, &h, &b, &du); + XTranslateCoordinates(dpy, win, root, x, y, &x, &y, &dw); + + printf("%s\t%d\t%dx%d%+d%+d\t0x%08lx\t0x%08lx\t0x%08lx\t%s\n", state, desk, w, h, x, y, container, tab, win, name); + free(name); +} + +/* list desktops */ +static void +listdesks(int argc, char *argv[]) +{ + unsigned long i, nwins, desk, ndesks, curdesk, len, nameslen; + unsigned long *wdesk; + int *urgdesks; + Window *wins; + XWMHints *hints; + char *desknames; + + (void)argv; + if (argc != 1) + usage(); + + /* get variables */ + ndesks = getcardprop(root, atoms[_NET_NUMBER_OF_DESKTOPS]); + curdesk = getcardprop(root, atoms[_NET_CURRENT_DESKTOP]); + nameslen = getdesknames(&desknames); + wdesk = ecalloc(ndesks, sizeof *wdesk); + urgdesks = ecalloc(ndesks, sizeof *urgdesks); + nwins = getwinprop(root, atoms[_NET_CLIENT_LIST], &wins); + for (i = 0; i < nwins; i++) { + desk = getcardprop(wins[i], atoms[_NET_WM_DESKTOP]); + hints = XGetWMHints(dpy, wins[i]); + if (desk < ndesks) { + wdesk[desk]++; + if (hints && hints->flags & XUrgencyHint) { + urgdesks[desk] = 1; + } + } + XFree(hints); + } + XFree(wins); + + /* list desktops */ + for (len = i = 0; i < ndesks; i++) { + printf("%c%lu:%s\n", + (i == curdesk ? '*' : (urgdesks[i] ? '-' : ' ')), + wdesk[i], + (desknames && len < nameslen ? desknames+len : "")); + if (desknames && len < nameslen) + len += strlen(desknames + len) + 1; + } + XFree(desknames); + free(wdesk); +} + +/* list windows */ +static void +list(int argc, char *argv[]) +{ + Window *wins; + unsigned long nwins, i; + int lflag, sflag; + int c; + + lflag = sflag = 0; + while ((c = getopt(argc, argv, "dls")) != -1) { + switch (c) { + case 'l': + lflag = 1; + break; + case 's': + sflag = 1; + break; + default: + usage(); + break; + } + } + argc -= optind; + argv += optind; + if (argc > 1) + usage(); + nwins = getwins(&wins, sflag); + for (i = 0; i < nwins; i++) { + if (lflag) { + longlist(wins[i]); + } else { + printf("0x%08lx\n", wins[i]); + } + } + XFree(wins); +} + +/* send container to desktop */ +static void +sendto(int argc, char *argv[]) +{ + Window win; + long mon, desk; + int c; + + mon = 0; + while ((c = getopt(argc, argv, "m")) != -1) { + switch (c) { + case 'm': + mon = strtol(optarg, NULL, 0); + break; + default: + usage(); + break; + } + } + argc -= optind; + argv += optind; + if (argc > 2) + usage(); + desk = strtol(argv[0], NULL, 0) - 1; + win = (argc == 2) ? getwin(argv[1]) : active; + clientmsg(win, atoms[_NET_WM_DESKTOP], desk, DIRECT_ACTION, mon, 0, 0); +} + +/* set container state */ +static void +state(int argc, char *argv[]) +{ + Window win; + Atom state1, state2; + int action; + int c; + + action = TOGGLE; + state1 = state2 = None; + while ((c = getopt(argc, argv, "ATRabfMms")) != -1) { + switch (c) { + case 'A': + action = ADD; + break; + case 'T': + action = TOGGLE; + break; + case 'R': + action = REMOVE; + break; + case 'a': + state1 = atoms[_NET_WM_STATE_ABOVE]; + break; + case 'b': + state1 = atoms[_NET_WM_STATE_BELOW]; + break; + case 'f': + state1 = atoms[_NET_WM_STATE_FULLSCREEN]; + break; + case 'M': + state1 = atoms[_NET_WM_STATE_MAXIMIZED_VERT]; + state2 = atoms[_NET_WM_STATE_MAXIMIZED_HORZ]; + break; + case 'm': + state1 = atoms[_NET_WM_STATE_HIDDEN]; + break; + case 's': + state1 = atoms[_NET_WM_STATE_STICKY]; + break; + default: + usage(); + break; + } + } + argc -= optind; + argv += optind; + if (argc > 1) + usage(); + if (state1 == None) + return; + win = (argc == 1) ? getwin(argv[0]) : active; + clientmsg(win, atoms[_NET_WM_STATE], action, state1, state2, DIRECT_ACTION, 0); +} + +/* shodc: remote controller for shod */ +int +main(int argc, char *argv[]) +{ + if (argc < 2) + usage(); + + if ((dpy = XOpenDisplay(NULL)) == NULL) + errx(1, "could not open display"); + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + + initatoms(); + active = getactivewin(); + if (strcmp(argv[1], "close") == 0) + closewin(argc - 1, argv + 1); + else if (strcmp(argv[1], "desks") == 0) + listdesks(argc - 1, argv + 1); + else if (strcmp(argv[1], "focus") == 0) + focuswin(argc - 1, argv + 1); + else if (strcmp(argv[1], "geom") == 0) + setgeom(argc - 1, argv + 1); + else if (strcmp(argv[1], "goto") == 0) + gotodesk(argc - 1, argv + 1); + else if (strcmp(argv[1], "list") == 0) + list(argc - 1, argv + 1); + else if (strcmp(argv[1], "sendto") == 0) + sendto(argc - 1, argv + 1); + else if (strcmp(argv[1], "state") == 0) + state(argc - 1, argv + 1); + else + usage(); + + XCloseDisplay(dpy); + return 0; +}