commit daa8175

wf  ·  2026-04-04 08:49:42 +0000 UTC
parent 7a39712
More IPC commands, clean up code, add documentation
11 files changed,  +678, -79
M howl.c
M ipc.c
M log.c
+0, -1
1@@ -1,3 +1,2 @@
2 howl
3 howlc
4-test
+8, -2
 1@@ -22,13 +22,19 @@ $(howlc): $(howlc_src)
 2 	@echo "Building howlc..."
 3 	$(CC) $(CFLAGS) $(LDFLAGS) -o $(howlc) $(howlc_src) $(LDLIBS)
 4 
 5-install: $(howl) $(howlc)
 6+install: $(howl) $(howlc) install-man
 7 	install -Dm755 $(howl)  $(DESTDIR)$(PREFIX)/bin/$(howl)
 8 	install -Dm755 $(howlc) $(DESTDIR)$(PREFIX)/bin/$(howlc)
 9 
10-uninstall:
11+uninstall: uninstall-man
12 	rm -f $(DESTDIR)$(PREFIX)/bin/$(howl)
13 	rm -f $(DESTDIR)$(PREFIX)/bin/$(howlc)
14 
15+install-man:
16+	install -Dm644 doc/howlc.1 $(DESTDIR)$(PREFIX)/share/man/man1/howlc.1
17+
18+uninstall-man:
19+	rm -f $(DESTDIR)$(PREFIX)/share/man/man1/howlc.1
20+
21 clean:
22 	rm -f $(howl) $(howlc)
+13, -26
 1@@ -1,35 +1,22 @@
 2 howl
 3 ====
 4 
 5-howl is a floating wayland compositor written in neuswc[^1] that implements an IPC interface, allowing for scriptability and advanced usage. howl's code is largely based on that of tohu[^2]'s and berry[^3]'s.
 6-to build howl, use any POSIX-compliant make. the repo provides a Makefile compatible with most implementations, including bmake.
 7+howl is a small wayland compositor written with the neuswc library.
 8 
 9-dependencies
10-------------
11+It is controlled through an external client, which allows for scriptability.
12 
13- - neuswc[^1]
14- - everything that neuswc depends on
15+See the `howlc` manual page for more information on usage.
16 
17-todo
18-----
19+To do
20+-----
21 
22- - titlebars(?)
23- - unbinding keys
24- - allow for more scriptability, assign pseudorandom IDs to windows etc.
25- - documentation
26+ * Expose compositor internals through the client
27+ * Eyecandy: titlebars and double borders
28+ * Clean up the codebase
29 
30-acknowledgements
31-----------------
32+Credits
33+-------
34 
35-the name howl doesn't carry any meaning, aside from being a nice coincidence that its last 2 letters are 'wl'.
36-howl is currently in a moreso experimental state rather than being a full-fledged compositor. the end goal is to develop an alternative to berrywm, my favorite x11 window manager, for wayland. however, the structural differences and my lack of competence do not allow it to be as high-quality as berry, though i am working towards making it at least comparable!
37-
38-obviously, i would like to thank the following:
39- - neulibs developers for creating neuswc/neuwld
40- - shrub900/uint23 for their amazing work on tohu/wsxwm respectively
41- - jlervin for their amazing work on berrywm
42- - many, many others
43-
44-[^1]: <https://git.sr.ht/~shrub900/neuswc>
45-[^2]: <https://git.sr.ht/~shrub900/tohu>
46-[^3]: <https://github.com/JLErvin/berry>
47+ * neuswc authors
48+ * uint23, shrub: For developing wsxwm / tohu
49+ * JLErvin: For developing berry, which is the inspiration and reference for this project
+22, -17
 1@@ -17,23 +17,28 @@ struct command {
 2 };
 3 
 4 static const struct command commands[] = {
 5-	{ "move",     cmd_move,     2, false },
 6-	{ "resize",   cmd_resize,   2, false },
 7-	{ "teleport", cmd_teleport, 4, false },
 8-	{ "center",   cmd_center,   0, false },
 9-	{ "hide",     cmd_hide,     0, false },
10-	{ "show",     cmd_show,     0, false },
11-	{ "close",    cmd_close,    0, false },
12-	{ "geometry", cmd_geometry, 0, false },
13-	{ "pid",      cmd_pid,      0, false },
14-	{ "title",    cmd_title,    0, false },
15-	{ "appid",    cmd_app_id,   0, false },
16-
17-	{ "bind",          cmd_bind,          2, false },
18-	{ "modkey",        cmd_modkey,        1, true  },
19-	{ "focus_color",   cmd_focus_color,   1, true  },
20-	{ "unfocus_color", cmd_unfocus_color, 1, true  },
21-	{ "border_width",  cmd_border_width,  1, true  },
22+	{ "move",            cmd_move,           2, false },
23+	{ "move_absolute",   cmd_move_absolute,  2, false },
24+	{ "resize",          cmd_resize,         2, false },
25+	{ "resize_absolute", cmd_resize,         2, false },
26+	{ "teleport",        cmd_teleport,       4, false },
27+	{ "center",          cmd_center,         0, false },
28+	{ "fullscreen",      cmd_fullscreen,     0, false },
29+	{ "hide",            cmd_hide,           0, false },
30+	{ "show",            cmd_show,           0, false },
31+	{ "close",           cmd_close,          0, false },
32+	{ "workspace",       cmd_workspace,      1, false },
33+	{ "move_workspace",  cmd_move_workspace, 1, false },
34+	{ "geometry",        cmd_geometry,       0, false },
35+	{ "pid",             cmd_pid,            0, false },
36+	{ "title",           cmd_title,          0, false },
37+	{ "appid",           cmd_app_id,         0, false },
38+	{ "bind",            cmd_bind,           2, false },
39+	{ "modkey",          cmd_modkey,         1, true  },
40+	{ "focus_color",     cmd_focus_color,    1, true  },
41+	{ "unfocus_color",   cmd_unfocus_color,  1, true  },
42+	{ "border_width",    cmd_border_width,   1, true  },
43+	{ "quit",            cmd_quit,           0, false }
44 };
45 
46 static void
+206, -0
  1@@ -0,0 +1,206 @@
  2+.\" generated with Ronn-NG/v0.10.1
  3+.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
  4+.TH "HOWLC" "1" "April 2026" ""
  5+.SH "NAME"
  6+\fBhowlc\fR \- client to control the howl compositor
  7+.SH "SYNOPSIS"
  8+\fBhowlc\fR \fIcmd\fR [\fIargs\|\.\|\.\|\.\fR]
  9+.SH "DESCRIPTION"
 10+\fBhowlc\fR is a client that controls the howl compositor\.
 11+.SH "COMMANDS"
 12+.TP
 13+\fBmove\fR, \fBmove_absolute\fR
 14+Will move the window relative to either its current position or to the top left corner of the screen\. Arguments are in the form of pairs of x and y integer coordinates\.
 15+.IP
 16+Example:
 17+.IP "" 4
 18+.nf
 19+$ howlc move 20 \-40           # Shift the window 20px forward and 40px up relative to its position
 20+$ howlc move_absolute 300 300 # Move the window to absolute coordinates [300, 300]
 21+.fi
 22+.IP "" 0
 23+
 24+.TP
 25+\fBresize\fR, \fBresize_absolute\fR
 26+Will resize the window relative to either its current position or the top left corner of the screen\. Arguments are in the form of pairs of w and h integers\.
 27+.IP
 28+Example:
 29+.IP "" 4
 30+.nf
 31+$ howlc resize 20 \-40           # Grow the window 20px forward and shrink it 40px up relative to its size
 32+$ howlc resize_absolute 300 300 # Resize the window to the absolute size [300, 300]
 33+.fi
 34+.IP "" 0
 35+
 36+.TP
 37+\fBteleport\fR
 38+Will "teleport" (move and resize) the window to the coordinates specified, in the format of x, y, w and h\.
 39+.IP
 40+Example:
 41+.IP "" 4
 42+.nf
 43+$ howlc teleport 50 25 200 250 # Teleport the window to [50, 25] and resize it to [200, 250]
 44+.fi
 45+.IP "" 0
 46+
 47+.TP
 48+\fBcenter\fR
 49+Will position the window in the center of the screen\.
 50+.IP
 51+Example:
 52+.IP "" 4
 53+.nf
 54+$ howlc center # Move the window to the center of the screen
 55+.fi
 56+.IP "" 0
 57+
 58+.TP
 59+\fBfullscreen\fR
 60+Will resize the window to the screen size\.
 61+.IP
 62+Example:
 63+.IP "" 4
 64+.nf
 65+$ howlc fullscreen # Put the window in fullscreen mode
 66+.fi
 67+.IP "" 0
 68+
 69+.TP
 70+\fBhide\fR, \fBshow\fR
 71+Hide/show the current window\. Currently these don't have much use\.
 72+.IP
 73+Example:
 74+.IP "" 4
 75+.nf
 76+$ howlc hide # Hide (unmap) the current window
 77+$ howlc show # And now show (map) it
 78+.fi
 79+.IP "" 0
 80+
 81+.TP
 82+\fBclose\fR
 83+Closes the window\.
 84+.IP
 85+Example:
 86+.IP "" 4
 87+.nf
 88+$ howlc close # Close the current window
 89+.fi
 90+.IP "" 0
 91+
 92+.TP
 93+\fBworkspace\fR, \fBmove_workspace\fR
 94+Switch, or move the current window, to the specified workspace\. Argument is a single integer representing the workspace number\.
 95+.IP
 96+Example:
 97+.IP "" 4
 98+.nf
 99+$ howlc workspace 3      # Switch to the 3rd workspace
100+$ howlc move_workspace 6 # Move the current window to the 6th workspace
101+.fi
102+.IP "" 0
103+
104+.TP
105+\fBget_geometry\fR, \fBget_pid\fR, \fBget_title\fR, \fBget_app_id\fR
106+Print the current window's geometry (in x, y, w and h), process ID, title or app ID\. These are practically non\-operational right now\.
107+.IP
108+Example:
109+.IP "" 4
110+.nf
111+$ howlc get_geometry # Get the current window's geometry
112+327 148 620 620
113+$ howlc get_pid      # Get the current window's process ID
114+12345
115+$ howlc get_title    # Get the current window's title
116+Example
117+$ howlc get_app_id   # Get the current window's app ID
118+example
119+.fi
120+.IP "" 0
121+
122+.TP
123+\fBbind\fR
124+Create a key binding\. Arguments are in the form of a list of keys and the command to execute, see the BINDINGS section for more info\.
125+.IP
126+Example:
127+.IP "" 4
128+.nf
129+$ howlc bind mod+shift+g "example_command" # Bind modkey+Shift+g to execute "example_command"
130+$ howlc bind win+4 "howlc workspace 4"     # Bind Win+4 to switch to the 4th workspace
131+.fi
132+.IP "" 0
133+
134+.TP
135+\fBmodkey\fR
136+Configure the modifier key\. This can be then used as an alias in keybindings, like so: \fBmod+key\fR\. See the BINDINGS section for allowed values\.
137+.IP
138+Example:
139+.IP "" 4
140+.nf
141+$ howlc modkey alt      # Configure the modifier key to Alt
142+$ howlc modkey win+ctrl # Configure the modifier key to Win+Ctrl
143+.fi
144+.IP "" 0
145+
146+.TP
147+\fBfocus_color\fR, \fBunfocus_color\fR
148+Set the focused and unfocused colors of the window borders\. Arguments are in the format of #RRGGBB, where the leading pound sign is optional\. Note that in the format of #RRGGBB, the argument needs to be quoted, since the pound sign (#) is a special one in most shells\.
149+.IP
150+Example:
151+.IP "" 4
152+.nf
153+$ howlc focus_color "#f03937" # Set the focused color to a shade of red
154+$ howlc unfocus_color eeeeee  # Set the unfocused color to a bright white
155+.fi
156+.IP "" 0
157+
158+.TP
159+\fBborder_width\fR
160+Set the window's border width\. Argument is a single integer representing the new border width\.
161+.IP
162+Examples:
163+.IP "" 4
164+.nf
165+$ howlc border_width 4 # Set the border width to 4px
166+$ howlc border_width 0 # Set the border width to 0px, essentially disabling borders
167+.fi
168+.IP "" 0
169+
170+.TP
171+\fBquit\fR
172+Quit the compositor\.
173+.IP
174+Example:
175+.IP "" 4
176+.nf
177+$ howlc quit # Terminate the Wayland display, cleanup and exit
178+.fi
179+.IP "" 0
180+
181+.SH "BINDINGS"
182+The format for keybindings is the following:
183+.P
184+Each key/modifier in the binding string is separated by a plus (+) sign\. There can be multiple modifiers, but only one key\. The accepted modifiers are:
185+.IP "\(bu" 4
186+\fBmod\fR: Corresponds to the modifier key
187+.IP "\(bu" 4
188+\fBalt\fR: The Alt key
189+.IP "\(bu" 4
190+\fBwin\fR: The Win key, sometimes the logo
191+.IP "\(bu" 4
192+\fBctrl\fR: The Ctrl key
193+.IP "\(bu" 4
194+\fBshift\fR: The Shift key
195+.IP "\(bu" 4
196+\fBany\fR: Any, (and) no modifiers
197+.IP "" 0
198+.P
199+The accepted key strings are as specified in \fB<xkbcommon/xkbcommon\-keysyms\.h>\fR\.
200+.SH "BUGS"
201+The \fBget_*\fR functions are useless since they print the needed information to the compositor's controlling terminal\.
202+.P
203+The manual does not specify the names for modifiers on other platforms\.
204+.SH "COPYRIGHT"
205+\fBhowl\fR and \fBhowlc\fR are (C) wf 2026 \fIhttps://codeberg\.org/wf\fR\.
206+.SH "SEE ALSO"
207+The README\.
+185, -0
  1@@ -0,0 +1,185 @@
  2+howlc(1) - client to control the howl compositor
  3+================================================
  4+
  5+## SYNOPSIS
  6+
  7+`howlc` *cmd* [*args...*]
  8+
  9+## DESCRIPTION
 10+
 11+**howlc** is a client that controls the howl compositor.
 12+
 13+## COMMANDS
 14+
 15+ * `move`, `move_absolute`:
 16+   Will move the window relative to either its current position or to the top
 17+   left corner of the screen. Arguments are in the form of pairs of x and y
 18+   integer coordinates.
 19+
 20+   Example:
 21+
 22+   ```
 23+   $ howlc move 20 -40           # Shift the window 20px forward and 40px up relative to its position
 24+   $ howlc move_absolute 300 300 # Move the window to absolute coordinates [300, 300]
 25+   ```
 26+
 27+
 28+ * `resize`, `resize_absolute`:
 29+   Will resize the window relative to either its current position or the top
 30+   left corner of the screen. Arguments are in the form of pairs of w and h
 31+   integers.
 32+
 33+   Example:
 34+   ```
 35+   $ howlc resize 20 -40           # Grow the window 20px forward and shrink it 40px up relative to its size
 36+   $ howlc resize_absolute 300 300 # Resize the window to the absolute size [300, 300]
 37+   ```
 38+
 39+ * `teleport`:
 40+   Will "teleport" (move and resize) the window to the coordinates specified, in
 41+   the format of x, y, w and h.
 42+
 43+   Example:
 44+   ```
 45+   $ howlc teleport 50 25 200 250 # Teleport the window to [50, 25] and resize it to [200, 250]
 46+   ```
 47+
 48+ * `center`:
 49+   Will position the window in the center of the screen.
 50+
 51+   Example:
 52+   ```
 53+   $ howlc center # Move the window to the center of the screen
 54+   ```
 55+
 56+ * `fullscreen`:
 57+   Will resize the window to the screen size.
 58+
 59+   Example:
 60+   ```
 61+   $ howlc fullscreen # Put the window in fullscreen mode
 62+   ```
 63+
 64+ * `hide`, `show`:
 65+   Hide/show the current window. Currently these don't have much use.
 66+
 67+   Example:
 68+   ```
 69+   $ howlc hide # Hide (unmap) the current window
 70+   $ howlc show # And now show (map) it
 71+   ```
 72+
 73+ * `close`:
 74+   Closes the window.
 75+
 76+   Example:
 77+   ```
 78+   $ howlc close # Close the current window
 79+   ```
 80+
 81+ * `workspace`, `move_workspace`:
 82+   Switch, or move the current window, to the specified workspace. Argument is a single
 83+   integer representing the workspace number.
 84+
 85+   Example:
 86+   ```
 87+   $ howlc workspace 3      # Switch to the 3rd workspace
 88+   $ howlc move_workspace 6 # Move the current window to the 6th workspace
 89+   ```
 90+
 91+ * `get_geometry`, `get_pid`, `get_title`, `get_app_id`:
 92+   Print the current window's geometry (in x, y, w and h), process ID, title or app ID.
 93+   These are practically non-operational right now.
 94+
 95+   Example:
 96+   ```
 97+   $ howlc get_geometry # Get the current window's geometry
 98+   327 148 620 620
 99+   $ howlc get_pid      # Get the current window's process ID
100+   12345
101+   $ howlc get_title    # Get the current window's title
102+   Example
103+   $ howlc get_app_id   # Get the current window's app ID
104+   example
105+   ```
106+
107+ * `bind`:
108+   Create a key binding. Arguments are in the form of a list of keys and the command to execute,
109+   see the BINDINGS section for more info.
110+
111+   Example:
112+   ```
113+   $ howlc bind mod+shift+g "example_command" # Bind modkey+Shift+g to execute "example_command"
114+   $ howlc bind win+4 "howlc workspace 4"     # Bind Win+4 to switch to the 4th workspace
115+   ```
116+
117+ * `modkey`:
118+   Configure the modifier key. This can be then used as an alias in keybindings, like so: `mod+key`.
119+   See the BINDINGS section for allowed values.
120+
121+   Example:
122+   ```
123+   $ howlc modkey alt      # Configure the modifier key to Alt
124+   $ howlc modkey win+ctrl # Configure the modifier key to Win+Ctrl
125+   ```
126+
127+ * `focus_color`, `unfocus_color`:
128+   Set the focused and unfocused colors of the window borders. Arguments are in the format of #RRGGBB,
129+   where the leading pound sign is optional. Note that in the format of #RRGGBB, the argument needs to
130+   be quoted, since the pound sign (#) is a special one in most shells.
131+
132+   Example:
133+   ```
134+   $ howlc focus_color "#f03937" # Set the focused color to a shade of red
135+   $ howlc unfocus_color eeeeee  # Set the unfocused color to a bright white
136+   ```
137+
138+ * `border_width`:
139+   Set the window's border width. Argument is a single integer representing the new border width.
140+
141+   Examples:
142+   ```
143+   $ howlc border_width 4 # Set the border width to 4px
144+   $ howlc border_width 0 # Set the border width to 0px, essentially disabling borders
145+   ```
146+
147+ * `quit`:
148+   Quit the compositor.
149+
150+   Example:
151+   ```
152+   $ howlc quit # Terminate the Wayland display, cleanup and exit
153+   ```
154+
155+## BINDINGS
156+
157+The format for keybindings is the following:
158+
159+Each key/modifier in the binding string is separated by a plus (+) sign. There can be multiple modifiers,
160+but only one key. The accepted modifiers are:
161+
162+ * `mod`: Corresponds to the modifier key
163+ * `alt`: The Alt key
164+ * `win`: The Win key, sometimes the logo
165+ * `ctrl`: The Ctrl key
166+ * `shift`: The Shift key
167+ * `any`: Any, (and) no modifiers
168+
169+The accepted key strings are as specified in `<xkbcommon/xkbcommon-keysyms.h>`.
170+
171+## BUGS
172+
173+The `get_*` functions are useless since they print the needed information to the compositor's controlling
174+terminal.
175+
176+The manual does not specify the names for modifiers on other platforms.
177+
178+There is no way to unbind keys. This is an swc problem, but still deserves to be mentioned here.
179+
180+## COPYRIGHT
181+
182+`howl` and `howlc` are (C) wf 2026 <https://codeberg.org/wf>.
183+
184+## SEE ALSO
185+
186+The README.
M howl.c
+159, -24
  1@@ -4,8 +4,6 @@
  2 #include <errno.h>
  3 #include <signal.h>
  4 #include <unistd.h>
  5-#include <fcntl.h>
  6-#include <getopt.h>
  7 #include <sys/socket.h>
  8 #include <sys/un.h>
  9 
 10@@ -22,19 +20,27 @@ static void on_win_destroy(void *);
 11 static void on_win_entered(void *);
 12 static void on_scr_destroy(void *);
 13 static bool is_ws_client(const struct client *, const struct screen *);
 14+static void mouse_move(void *, uint32_t, uint32_t, uint32_t);
 15+static void mouse_resize(void *, uint32_t, uint32_t, uint32_t);
 16 
 17 extern void ipc_move(char **);
 18+extern void ipc_move_absolute(char **);
 19 extern void ipc_resize(char **);
 20+extern void ipc_resize_absolute(char **);
 21 extern void ipc_teleport(char **);
 22 extern void ipc_center(char **);
 23+extern void ipc_fullscreen(char **);
 24 extern void ipc_hide(char **);
 25 extern void ipc_show(char **);
 26 extern void ipc_close(char **);
 27+extern void ipc_workspace(char **);
 28+extern void ipc_move_workspace(char **);
 29 extern void ipc_get_geometry(char **);
 30 extern void ipc_get_pid(char **);
 31 extern void ipc_get_title(char **);
 32 extern void ipc_get_app_id(char **);
 33 extern void ipc_bind(char **);
 34+extern void ipc_quit(char **);
 35 extern void ipc_config(char **);
 36 
 37 bool have_config = true;
 38@@ -57,19 +63,25 @@ static struct swc_screen_handler scr_handler = {
 39 
 40 typedef void (*cmd_handler_t)(char **);
 41 static const cmd_handler_t cmd_handler [cmd_last] = {
 42-	[cmd_move]     = ipc_move,
 43-	[cmd_resize]   = ipc_resize,
 44-	[cmd_teleport] = ipc_teleport,
 45-	[cmd_center]   = ipc_center,
 46-	[cmd_hide]     = ipc_hide,
 47-	[cmd_show]     = ipc_show,
 48-	[cmd_close]    = ipc_close,
 49-	[cmd_geometry] = ipc_get_geometry,
 50-	[cmd_pid]      = ipc_get_pid,
 51-	[cmd_title]    = ipc_get_title,
 52-	[cmd_app_id]   = ipc_get_app_id,
 53-	[cmd_bind]     = ipc_bind,
 54-	[cmd_config]   = ipc_config
 55+	[cmd_move]            = ipc_move,
 56+	[cmd_move_absolute]   = ipc_move,
 57+	[cmd_resize]          = ipc_resize,
 58+	[cmd_resize_absolute] = ipc_resize,
 59+	[cmd_teleport]        = ipc_teleport,
 60+	[cmd_center]          = ipc_center,
 61+	[cmd_fullscreen]      = ipc_fullscreen,
 62+	[cmd_hide]            = ipc_hide,
 63+	[cmd_show]            = ipc_show,
 64+	[cmd_close]           = ipc_close,
 65+	[cmd_workspace]       = ipc_workspace,
 66+	[cmd_move_workspace]  = ipc_move_workspace,
 67+	[cmd_geometry]        = ipc_get_geometry,
 68+	[cmd_pid]             = ipc_get_pid,
 69+	[cmd_title]           = ipc_get_title,
 70+	[cmd_app_id]          = ipc_get_app_id,
 71+	[cmd_bind]            = ipc_bind,
 72+	[cmd_quit]            = ipc_quit,
 73+	[cmd_config]          = ipc_config
 74 };
 75 
 76 static int
 77@@ -130,7 +142,8 @@ ipc_handler(int fd, uint32_t mask, void *data) {
 78 
 79 				int cmd = atoi(argv[0]);
 80 				void *fn = cmd_handler[cmd];
 81-				if (!fn) _wrn("no such command #%s", argv[0]);
 82+				/* TODO: this is hard to debug */
 83+				if (!fn) _wrn("no such command #%d", cmd);
 84 	
 85 				cmd_handler[cmd](argv);
 86 			}
 87@@ -212,9 +225,10 @@ setup(void) {
 88 	wm.scr         = NULL;
 89 	wm.grab.active = false;
 90 	wm.grab.resize = false;
 91-	wm.grab.c      = NULL;
 92+	wm.grab.client = NULL;
 93 	wm.ws          = 1;
 94 
 95+	config.modkey        = SWC_MOD_LOGO;
 96 	config.focus_color   = 0xFFE16C87;
 97 	config.unfocus_color = 0xFF444059;
 98 	config.border_width  = 2;
 99@@ -249,6 +263,22 @@ setup(void) {
100 
101 	if (have_config) load_config(conf_path);
102 
103+	/* FIXME: these don't get enough time to inherit the modkey that the user configured */
104+	swc_add_binding(
105+		SWC_BINDING_BUTTON,
106+		config.modkey,
107+		0x110, /* left mouse button */
108+		mouse_move,
109+		NULL
110+	);
111+	swc_add_binding(
112+		SWC_BINDING_BUTTON,
113+		config.modkey,
114+		0x111, /* right mouse button */
115+		mouse_resize,
116+		NULL
117+	);
118+
119 	signal(SIGINT,  sig_handler);
120 	signal(SIGTERM, sig_handler);
121 	signal(SIGQUIT, sig_handler);
122@@ -256,10 +286,9 @@ setup(void) {
123 	free(conf_path);
124 }
125 
126-static void
127+void
128 cleanup(void) {
129-	swc_finalize();
130-	wl_display_destroy(wm.dpy);
131+	wl_display_terminate(wm.dpy);
132 	close(sfd);
133 	unlink(SOCK_PATH);
134 }
135@@ -300,6 +329,112 @@ is_ws_client(const struct client *c, const struct screen *s) {
136 	return c && c->ws == wm.ws && (!s || c->scr == s);
137 }
138 
139+static void
140+sync_windows(void) {
141+	struct client *c;
142+
143+	wl_list_for_each(c, &wm.clients, link) {
144+		if (c->ws == wm.ws)
145+			swc_window_show(c->win);
146+		else
147+			swc_window_hide(c->win);
148+	}
149+}
150+
151+void
152+ws_go_to(uint8_t ws) {
153+	struct client *c;
154+
155+	if (ws < 1 || ws > 9 || ws == wm.ws)
156+		return;
157+
158+	wm.ws = ws;
159+	sync_windows();
160+
161+	c = first_client(wm.scr);
162+	if (!c) c = first_client(NULL);
163+	focus(c);
164+}
165+
166+void
167+ws_move_to(uint8_t ws) {
168+	struct client *c, *next;
169+
170+	if (!wm.cur || ws < 1 || ws > 9) return;
171+	c = wm.cur;
172+	if (c->ws == ws)
173+		return;
174+
175+	c->ws = ws;
176+	if (c->ws == wm.ws)
177+		swc_window_show(c->win);
178+	else
179+		swc_window_hide(c->win);
180+
181+	next = first_client(wm.scr);
182+	if (!next) next = first_client(NULL);
183+	focus(next);
184+}
185+
186+static void
187+mouse_move(void *data, uint32_t time, uint32_t value, uint32_t state) {
188+	UNUSED(data);
189+	UNUSED(time);
190+	UNUSED(value);
191+
192+	if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
193+		if (!wm.cur)
194+			return;
195+
196+		_inf("I am moving with my mouse");
197+
198+		wm.grab.active = true;
199+		wm.grab.resize = false;
200+		wm.grab.client = wm.cur;
201+
202+		swc_window_begin_move(wm.grab.client->win);
203+	} else {
204+		if (!wm.grab.active || wm.grab.resize || !wm.grab.client)
205+			return;
206+
207+		_inf("I am no longer moving with my mouse.");
208+
209+		swc_window_end_move(wm.grab.client->win);
210+		wm.grab.active = false;
211+		wm.grab.client = NULL;
212+	}
213+}
214+
215+static void
216+mouse_resize(void *data, uint32_t time, uint32_t value, uint32_t state) {
217+	UNUSED(data);
218+	UNUSED(time);
219+	UNUSED(value);
220+
221+	if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
222+		if (!wm.cur)
223+			return;
224+
225+		wm.grab.active = true;
226+		wm.grab.resize = true;
227+		wm.grab.client = wm.cur;
228+
229+		swc_window_begin_resize(
230+			wm.grab.client->win,
231+			SWC_WINDOW_EDGE_RIGHT | SWC_WINDOW_EDGE_BOTTOM
232+		);
233+	} else {
234+		if (!wm.grab.active || !wm.grab.resize || !wm.grab.client)
235+			return;
236+
237+		swc_window_end_resize(wm.grab.client->win);
238+
239+		wm.grab.active = false;
240+		wm.grab.resize = false;
241+		wm.grab.client = NULL;
242+	}
243+}
244+
245 static void
246 new_screen(struct swc_screen *scr) {
247 	struct screen *s;
248@@ -332,8 +467,8 @@ new_window(struct swc_window *win) {
249 
250 	c->win        = win;
251 	c->scr        = wm.scr;
252-	c->visible    = 0;
253-	c->fullscreen = 0;
254+	c->visible    = false;
255+	c->fullscreen = false;
256 	c->ws         = wm.ws;
257 	c->x          = 0;
258 	c->y          = 0;
259@@ -370,9 +505,9 @@ on_win_destroy(void *data) {
260 	struct client *c = data, *next;
261 
262 	if (!c) return;
263-	if (wm.grab.active && wm.grab.c == c) {
264+	if (wm.grab.active && wm.grab.client == c) {
265 		wm.grab.active = false;
266-		wm.grab.c = NULL;
267+		wm.grab.client = NULL;
268 	}
269 
270 	wl_list_remove(&c->link);
+6, -0
 1@@ -3,12 +3,17 @@
 2 
 3 enum cmd {
 4 	cmd_move,
 5+	cmd_move_absolute,
 6 	cmd_resize,
 7+	cmd_resize_absolute,
 8 	cmd_teleport,
 9 	cmd_center,
10+	cmd_fullscreen,
11 	cmd_hide,
12 	cmd_show,
13 	cmd_close,
14+	cmd_workspace,
15+	cmd_move_workspace,
16 	cmd_geometry,
17 	cmd_pid,
18 	cmd_title,
19@@ -18,6 +23,7 @@ enum cmd {
20 	cmd_focus_color,
21 	cmd_unfocus_color,
22 	cmd_border_width,
23+	cmd_quit,
24 	cmd_config,
25 	cmd_last
26 };
+1, -8
 1@@ -7,13 +7,6 @@
 2 #include <wayland-server.h>
 3 #include <swc.h>
 4 
 5-union arg {
 6-	int        i;
 7-	uint32_t   ui;
 8-	float      f;
 9-	const void *v;
10-};
11-
12 struct config {
13 	uint32_t  modkey;
14 	uint32_t  focus_color;
15@@ -43,7 +36,7 @@ struct screen {
16 
17 struct grab {
18 	bool           active, resize;
19-	struct client  *c;
20+	struct client  *client;
21 };
22 
23 struct wm {
M ipc.c
+75, -1
  1@@ -38,7 +38,7 @@ fn_bind(char *s) {
  2 	char *tok = strtok(s, "+");
  3 	while (tok != NULL) {
  4 		uint32_t tmp = 0;
  5-	
  6+
  7 		if (strcmp(tok, "mod") == 0) tmp = config.modkey;
  8 		else if (strcmp(tok, "alt") == 0) tmp = SWC_MOD_ALT;
  9 		else if (strcmp(tok, "win") == 0) tmp = SWC_MOD_LOGO;
 10@@ -65,6 +65,9 @@ fn_bind(char *s) {
 11 }
 12 
 13 extern void bind_handler(void *, uint32_t, uint32_t, uint32_t);
 14+extern void ws_go_to(int8_t);
 15+extern void ws_move_to(int8_t);
 16+extern void cleanup(void);
 17 
 18 /*********/
 19 
 20@@ -84,6 +87,14 @@ ipc_move(char **arg) {
 21 	swc_window_set_position(wm.cur->win, wm.cur->x + atoi(arg[1]), wm.cur->y + atoi(arg[2]));
 22 }
 23 
 24+void
 25+ipc_move_absolute(char **arg) {
 26+	if (arg[1] == NULL || arg[2] == NULL || !wm.cur)
 27+		return;
 28+
 29+	swc_window_set_position(wm.cur->win, atoi(arg[1]), atoi(arg[2]));
 30+}
 31+
 32 void
 33 ipc_resize(char **arg) {
 34 	if (arg[1] == NULL || arg[2] == NULL || !wm.cur) return;
 35@@ -99,6 +110,14 @@ ipc_resize(char **arg) {
 36 	swc_window_set_size(wm.cur->win, wm.cur->width + strtoul(arg[1], NULL, 0), wm.cur->height + strtoul(arg[2], NULL, 0));
 37 }
 38 
 39+void
 40+ipc_resize_absolute(char **arg) {
 41+	if (arg[1] == NULL || arg[2] == NULL || !wm.cur)
 42+		return;
 43+
 44+	swc_window_set_size(wm.cur->win, strtoul(arg[1], NULL, 0), strtoul(arg[2], NULL, 0));
 45+}
 46+
 47 void
 48 ipc_teleport(char **arg) {
 49 	if (arg[1] == NULL || arg[2] == NULL ||
 50@@ -135,6 +154,41 @@ ipc_center(char **arg) {
 51 	);
 52 }
 53 
 54+void
 55+ipc_fullscreen(char **arg) {
 56+	UNUSED(arg);
 57+	if (!wm.cur) return;
 58+
 59+	struct swc_rectangle geom;
 60+
 61+	if (wm.cur->fullscreen) {
 62+		wm.cur->fullscreen = false;
 63+		swc_window_set_stacked(wm.cur->win);
 64+
 65+		if (wm.cur->width > 0 && wm.cur->height > 0) {
 66+			geom.x = wm.cur->x;
 67+			geom.y = wm.cur->y;
 68+			geom.width = wm.cur->width;
 69+			geom.height = wm.cur->height;
 70+			swc_window_set_geometry(wm.cur->win, &geom);
 71+		}
 72+		return;
 73+	}
 74+
 75+	if (!wm.cur->scr || !wm.cur->scr->scr)
 76+		return;
 77+
 78+	if (swc_window_get_geometry(wm.cur->win, &geom)) {
 79+		wm.cur->x = geom.x;
 80+		wm.cur->y = geom.y;
 81+		wm.cur->width = geom.width;
 82+		wm.cur->height = geom.height;
 83+	}
 84+
 85+	wm.cur->fullscreen = true;
 86+	swc_window_set_fullscreen(wm.cur->win, wm.scr->scr);
 87+}
 88+
 89 void
 90 ipc_hide(char **arg) {
 91 	UNUSED(arg);
 92@@ -159,6 +213,20 @@ ipc_close(char **arg) {
 93 	swc_window_close(wm.cur->win);
 94 }
 95 
 96+void
 97+ipc_workspace(char **arg) {
 98+	if (arg[1] == NULL) return;
 99+
100+	ws_go_to(atoi(arg[1]));
101+}
102+
103+void
104+ipc_move_workspace(char **arg) {
105+	if (arg[1] == NULL || !wm.cur) return;
106+
107+	ws_move_to(atoi(arg[1]));
108+}
109+
110 /*********/
111 
112 void
113@@ -267,3 +335,9 @@ ipc_config(char **arg) {
114 			);
115 	}
116 }
117+
118+void
119+ipc_quit(char **arg) {
120+	UNUSED(arg);
121+	cleanup();
122+}
M log.c
+3, -0
 1@@ -4,6 +4,8 @@
 2 
 3 #include "howl.h"
 4 
 5+extern void cleanup();
 6+
 7 void
 8 _inf(const char *msg, ...) {
 9 	va_list list;
10@@ -45,5 +47,6 @@ _err(int ret, const char *msg, ...) {
11 	fputc('\n', stderr);
12 	fflush(stderr);
13 
14+	cleanup();
15 	exit(ret);
16 }