commit db7cd5cd466c358680635614db9ac986f79823b1
parent fedf704fe4a231829232b0e94ef2c5be03f038f8
Author: phillbush <phillbush@cock.li>
Date: Thu, 16 Sep 2021 12:06:25 -0300
big chungus
Diffstat:
M | Makefile | | | 13 | +++++++++---- |
M | shod.1 | | | 324 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
M | shod.c | | | 247 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- |
A | shodc.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;
+}