├── LICENSE ├── Makefile ├── README.md ├── client.c ├── coma.1 ├── coma.c ├── coma.h ├── config.c ├── frame.c ├── scripts ├── coma-cmd └── coma-remote └── wm.c /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Joris Vink 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # coma Makefile. 2 | 3 | CC?=cc 4 | COMA=coma 5 | DESTDIR?= 6 | PREFIX?=/usr/local 7 | INSTALL_DIR=$(PREFIX)/bin 8 | MAN_DIR?=$(PREFIX)/share/man 9 | 10 | SRC= coma.c client.c config.c frame.c wm.c 11 | OBJS= $(SRC:%.c=%.o) 12 | 13 | CFLAGS+=-Wall 14 | CFLAGS+=-Werror 15 | CFLAGS+=-Wstrict-prototypes 16 | CFLAGS+=-Wmissing-prototypes 17 | CFLAGS+=-Wmissing-declarations 18 | CFLAGS+=-Wshadow 19 | CFLAGS+=-Wpointer-arith 20 | CFLAGS+=-Wcast-qual 21 | CFLAGS+=-Wsign-compare 22 | CFLAGS+=-std=c99 23 | CFLAGS+=-pedantic 24 | 25 | CFLAGS+=`pkg-config --cflags x11 xft` 26 | LDFLAGS+=`pkg-config --libs x11 xft` 27 | 28 | all: $(COMA) 29 | 30 | install: $(COMA) 31 | mkdir -p $(DESTDIR)$(INSTALL_DIR) 32 | mkdir -p $(DESTDIR)$(MAN_DIR)/man1 33 | install -m 555 $(COMA) $(DESTDIR)$(INSTALL_DIR)/$(COMA) 34 | install -m 555 scripts/coma-* $(DESTDIR)$(INSTALL_DIR) 35 | install -m 644 coma.1 $(DESTDIR)$(MAN_DIR)/man1/coma.1 36 | 37 | $(COMA): $(OBJS) 38 | $(CC) $(OBJS) $(LDFLAGS) -o $(COMA) 39 | 40 | .c.o: 41 | $(CC) $(CFLAGS) -c $< -o $@ 42 | 43 | clean: 44 | rm -rf $(COMA) $(OBJS) 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | About 2 | ----- 3 | 4 | Coma is a minimalistic X11 Window Manager. 5 | 6 | It makes my life easier when hacking on my laptop as by default 7 | I will get my 2 frames containing 80 column terminals with a nice 8 | gap and layout. 9 | 10 | Read the included manual page for more information. 11 | 12 | License 13 | ------- 14 | Coma is licensed under the ISC license. 15 | 16 | Building 17 | -------- 18 | 19 | Coma should build fine on MacOS, Linux and OpenBSD. 20 | 21 | OpenBSD: 22 | ``` 23 | $ make 24 | $ doas make install 25 | ``` 26 | 27 | MacOS: 28 | ``` 29 | $ export PKG_CONFIG_PATH=/opt/X11/share/pkgconfig:/opt/X11/lib/pkgconfig 30 | $ make 31 | $ sudo make install 32 | ``` 33 | 34 | Linux (requires libbsd): 35 | ``` 36 | $ env CFLAGS=-D_GNU_SOURCE LDFLAGS=-lbsd make 37 | $ sudo make install 38 | ``` 39 | 40 | Shell setup 41 | ----------- 42 | 43 | Coma ties in directly with your xterms and running shell. With the help 44 | of your shell you can update coma on the hostname, pwd and commands 45 | running in the clients. 46 | 47 | Here's a snippet from my .zshrc that can get you started: 48 | 49 | ``` 50 | precmd() { 51 | print -Pn "\e]0;%M;%d;zsh\a" 52 | } 53 | 54 | preexec() { 55 | cmd=`echo $1 | cut -f1 -d' '` 56 | if [ "$cmd" = "vi" ] || [ "$cmd" = $EDITOR ]; then 57 | cmd=`echo $1 | cut -f2 -d' '` 58 | fi 59 | 60 | print -Pn "\e]0;%M;%d;$cmd\a" 61 | } 62 | ``` 63 | 64 | Coma expects the information from the shell in the following format: 65 | 66 | hostname;directory;running command 67 | 68 | If your environment is configured like the above Coma will be able to 69 | execute commands on remote hosts transparently via prefix-e as it will 70 | auto detect what host you are currently on and execute the coma-remote 71 | script to connect to it before executing the command given. 72 | 73 | Key bindings 74 | ------------ 75 | All key bindings are changable via the config file (see coma.1). 76 | 77 | C-t = prefix 78 | 79 | prefix-c = new xterm 80 | 81 | prefix-e = open command execution input 82 | 83 | prefix-Space = toggle popup area 84 | 85 | prefix-colon = coma internal command prompt 86 | 87 | prefix-p = previous window 88 | 89 | prefix-n = next window 90 | 91 | prefix-r = restart Coma 92 | 93 | prefix-h = move to frame to the left of current frame 94 | 95 | prefix-l = move to frame to the right of current frame 96 | 97 | prefix-k = kill client in current frame 98 | 99 | prefix-i = move the active client to the left 100 | 101 | prefix-o = move the active client to the right 102 | 103 | prefix-s = split frame 104 | 105 | prefix-q = client quick selection list 106 | 107 | prefix-m = merge frame back together 108 | 109 | prefix-f = toggle focus between split frames 110 | 111 | prefix-z = zoom/unzoom current frame to cover all frames 112 | 113 | Screenshots 114 | ----------- 115 | 116 | ![coma](https://coma.one/wm/screenshots/coma2.png?raw=true) 117 | -------------------------------------------------------------------------------- /client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Joris Vink 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #include "coma.h" 23 | 24 | struct client_list clients; 25 | static u_int32_t client_id = 1; 26 | struct client *client_active = NULL; 27 | 28 | void 29 | coma_client_init(void) 30 | { 31 | TAILQ_INIT(&clients); 32 | } 33 | 34 | void 35 | coma_client_create(Window window) 36 | { 37 | XWindowAttributes attr; 38 | struct frame *frame; 39 | struct client *client; 40 | u_int32_t frame_id, pos, visible; 41 | 42 | XGetWindowAttributes(dpy, window, &attr); 43 | 44 | if (coma_wm_property_read(window, atom_frame_id, &frame_id) == -1) { 45 | frame_id = 0; 46 | frame = frame_active; 47 | } else { 48 | if ((frame = coma_frame_lookup(frame_id)) == NULL) 49 | frame = frame_active; 50 | } 51 | 52 | if (coma_wm_property_read(window, atom_client_visible, &visible) == -1) 53 | visible = 0; 54 | 55 | if (client_discovery == 0) 56 | visible = 1; 57 | 58 | coma_log("window 0x%08x - visible=%d - frame:%u", 59 | window, visible, frame_id); 60 | 61 | client = coma_calloc(1, sizeof(*client)); 62 | TAILQ_INSERT_TAIL(&clients, client, glist); 63 | 64 | if (coma_wm_property_read(window, atom_client_pos, &pos) == 0) 65 | client->pos = pos; 66 | 67 | if (frame->focus != NULL) { 68 | TAILQ_INSERT_BEFORE(frame->focus, client, list); 69 | } else { 70 | TAILQ_INSERT_HEAD(&frame->clients, client, list); 71 | } 72 | 73 | if (frame == frame_popup && frame != frame_active) 74 | visible = 0; 75 | 76 | if (client_active == NULL) 77 | client_active = client; 78 | 79 | client->x = attr.x; 80 | client->y = attr.y; 81 | client->w = attr.width; 82 | client->h = attr.height; 83 | 84 | client->frame = frame; 85 | client->window = window; 86 | client->id = client_id++; 87 | client->bw = frame_border; 88 | 89 | coma_client_update_title(client); 90 | 91 | XSelectInput(dpy, client->window, 92 | StructureNotifyMask | PropertyChangeMask | FocusChangeMask); 93 | 94 | XAddToSaveSet(dpy, client->window); 95 | XSetWindowBorderWidth(dpy, client->window, client->bw); 96 | 97 | coma_wm_register_prefix(client->window); 98 | coma_client_adjust(client); 99 | 100 | if (visible) { 101 | coma_client_map(client); 102 | coma_client_warp_pointer(client); 103 | } else { 104 | coma_client_hide(client); 105 | } 106 | 107 | if (client_discovery == 0) { 108 | coma_frame_bar_update(frame); 109 | XSync(dpy, False); 110 | } 111 | } 112 | 113 | void 114 | coma_client_kill_active(void) 115 | { 116 | if (client_active == NULL) 117 | return; 118 | 119 | XKillClient(dpy, client_active->window); 120 | } 121 | 122 | struct client * 123 | coma_client_find(Window window) 124 | { 125 | return (coma_frame_find_client(window)); 126 | } 127 | 128 | void 129 | coma_client_destroy(struct client *client) 130 | { 131 | struct client *next; 132 | struct frame *frame; 133 | int was_active; 134 | 135 | frame = client->frame; 136 | 137 | if (client_active != NULL && client_active->id == client->id) { 138 | was_active = 1; 139 | client_active = NULL; 140 | } else { 141 | was_active = 0; 142 | } 143 | 144 | if (frame->focus != NULL && frame->focus->id == client->id) 145 | frame->focus = NULL; 146 | 147 | next = TAILQ_NEXT(client, list); 148 | TAILQ_REMOVE(&clients, client, glist); 149 | TAILQ_REMOVE(&frame->clients, client, list); 150 | 151 | if (client->status) 152 | free(client->status); 153 | 154 | free(client); 155 | 156 | coma_frame_bar_update(frame); 157 | 158 | if (was_active == 0) 159 | return; 160 | 161 | if (frame_active == frame_popup) { 162 | if (TAILQ_EMPTY(&frame_popup->clients)) { 163 | coma_frame_popup_toggle(); 164 | return; 165 | } 166 | } 167 | 168 | if (next == NULL) { 169 | if ((next = TAILQ_FIRST(&frame->clients)) == NULL) { 170 | if (frame->split != NULL) 171 | coma_frame_merge(); 172 | } 173 | } 174 | 175 | if (next == NULL) { 176 | coma_frame_select_any(); 177 | } else { 178 | coma_client_focus(next); 179 | coma_frame_bar_update(frame); 180 | } 181 | } 182 | 183 | void 184 | coma_client_adjust(struct client *client) 185 | { 186 | client->w = client->frame->w; 187 | client->h = client->frame->h; 188 | client->x = client->frame->x; 189 | client->y = client->frame->y; 190 | 191 | coma_client_send_configure(client); 192 | 193 | coma_wm_property_write(client->window, 194 | atom_frame_id, client->frame->id); 195 | } 196 | 197 | void 198 | coma_client_map(struct client *client) 199 | { 200 | XMapWindow(dpy, client->window); 201 | coma_client_focus(client); 202 | coma_wm_property_write(client->window, atom_client_visible, 1); 203 | } 204 | 205 | void 206 | coma_client_hide(struct client *client) 207 | { 208 | if (!(client->flags & COMA_CLIENT_HIDDEN)) { 209 | client->flags |= COMA_CLIENT_HIDDEN; 210 | XUnmapWindow(dpy, client->window); 211 | coma_wm_property_write(client->window, atom_client_visible, 0); 212 | } 213 | } 214 | 215 | void 216 | coma_client_unhide(struct client *client) 217 | { 218 | if (client->flags & COMA_CLIENT_HIDDEN) { 219 | client->flags &= ~COMA_CLIENT_HIDDEN; 220 | coma_client_map(client); 221 | } 222 | } 223 | 224 | void 225 | coma_client_warp_pointer(struct client *client) 226 | { 227 | XWarpPointer(dpy, None, client->window, 0, 0, 0, 0, 228 | client->w / 2, client->h / 2); 229 | } 230 | 231 | void 232 | coma_client_focus(struct client *client) 233 | { 234 | XftColor *color; 235 | 236 | if (client->flags & COMA_CLIENT_HIDDEN) { 237 | XMapWindow(dpy, client->window); 238 | client->flags &= ~COMA_CLIENT_HIDDEN; 239 | } 240 | 241 | XRaiseWindow(dpy, client->window); 242 | XSetInputFocus(dpy, client->window, RevertToPointerRoot, CurrentTime); 243 | 244 | color = coma_wm_color("client-active"); 245 | XSetWindowBorder(dpy, client->window, color->pixel); 246 | 247 | if (client_active != NULL && client_active->id != client->id) { 248 | color = coma_wm_color("client-inactive"); 249 | XSetWindowBorder(dpy, client_active->window, color->pixel); 250 | } 251 | 252 | client_active = client; 253 | client->frame->focus = client; 254 | 255 | if (client_discovery == 0) { 256 | coma_frame_bar_update(client->frame); 257 | coma_wm_property_write(DefaultRootWindow(dpy), 258 | atom_client_act, client->window); 259 | XSync(dpy, True); 260 | } 261 | } 262 | 263 | void 264 | coma_client_send_configure(struct client *client) 265 | { 266 | XConfigureEvent cfg; 267 | 268 | memset(&cfg, 0, sizeof(cfg)); 269 | 270 | cfg.type = ConfigureNotify; 271 | cfg.event = client->window; 272 | cfg.window = client->window; 273 | 274 | cfg.x = client->x; 275 | cfg.y = client->y; 276 | cfg.width = client->w; 277 | cfg.height = client->h; 278 | cfg.border_width = client->bw; 279 | 280 | XMoveResizeWindow(dpy, client->window, 281 | client->x, client->y, client->w, client->h); 282 | 283 | XSendEvent(dpy, client->window, False, 284 | StructureNotifyMask, (XEvent *)&cfg); 285 | } 286 | 287 | void 288 | coma_client_update_title(struct client *client) 289 | { 290 | int n, len; 291 | char *name, *args[4], pwd[PATH_MAX]; 292 | 293 | if (!XFetchName(dpy, client->window, &name)) 294 | return; 295 | 296 | free(client->pwd); 297 | free(client->status); 298 | 299 | client->pwd = NULL; 300 | client->cmd = NULL; 301 | client->host = NULL; 302 | 303 | if ((client->status = strdup(name)) == NULL) 304 | fatal("strdup"); 305 | 306 | if ((n = coma_split_string(client->status, ";", args, 4)) < 2) { 307 | client->cmd = args[0]; 308 | return; 309 | } 310 | 311 | if (args[1] != NULL && !strncmp(args[1], homedir, strlen(homedir))) { 312 | len = snprintf(pwd, sizeof(pwd), "~%s", 313 | args[1] + strlen(homedir)); 314 | if (len != -1 && (size_t)len < sizeof(pwd)) 315 | client->pwd = strdup(pwd); 316 | } 317 | 318 | if (client->pwd == NULL) { 319 | if ((client->pwd = strdup(args[1])) == NULL) 320 | fatal("strdup failed"); 321 | } 322 | 323 | client->host = args[0]; 324 | 325 | if (n == 3) 326 | client->cmd = args[2]; 327 | 328 | XFree(name); 329 | } 330 | -------------------------------------------------------------------------------- /coma.1: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright (c) 2019 Joris Vink 3 | .\" 4 | .\" Permission to use, copy, modify, and distribute this software for any 5 | .\" purpose with or without fee is hereby granted, provided that the above 6 | .\" copyright notice and this permission notice appear in all copies. 7 | .\" 8 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | .\"/ 16 | .Dd 17 | .Dt COMA 1 18 | .Os 19 | .Sh NAME 20 | .Nm coma 21 | .Nd a keyboard driven, tiling window manager 22 | .Sh SYNOPSIS 23 | .Nm 24 | .Op Fl c 25 | .Ar config 26 | .Sh DESCRIPTION 27 | .Nm 28 | is a keyboard driven tiling window manager. By default the window manager 29 | will attempt to create 484 pixel frames on your screen. This is wide enough 30 | to fit 80-column xterms inside of them when using the default 'fixed' font. 31 | .Pp 32 | These defaults can be overwritten using the configuration file. 33 | .Sh CONFIGURATION 34 | The configuration file by default exists in 35 | .An $HOME/.comarc 36 | but can be given 37 | on the command line with the 38 | .Op Fl c 39 | flag. 40 | .Pp 41 | The following options can be configured: 42 | .Bl -tag -width Ds 43 | .It Ic font (default: fixed:pixelsize=13:style=bold) 44 | The font to be used by coma for status bars and input. 45 | .It Ic frame-layout (default: default) 46 | Change frame layout. options: small-large, default. 47 | The gap between frames and from the top and bottom of the screen. 48 | .It Ic frame-gap (default: 10) 49 | The gap between frames and from the top and bottom of the screen. 50 | .It Ic frame-bar (default: 20) 51 | The height of the bar at the bottom of the frames. 52 | .It Ic frame-count (default: auto). 53 | The number of frames to create (by default 54 | .Nm 55 | will create frames until they no longer fit on-screen). 56 | .It Ic frame-width (default: 484) 57 | The width of each frame. 58 | .It Ic frame-height (default: fill) 59 | The height of each frame. 60 | .It Ic frame-offset (default: 0) 61 | The X offset where you want things to be created from. 62 | .It Ic bind 63 | Bind the given key to the action specified. (see key bindings below). 64 | If the action is prefixed with cmd: the keybinding will execute that command 65 | via the coma-run internal instead. 66 | .Pp 67 | Example: bind frame-next p 68 | .It Ic color 69 | Set the color for the specified type (see colors below). 70 | .Pp 71 | Example: color client-active "#55007a" 72 | .It Ic prefix (default: C-t) 73 | Configure the prefix key. The format of this key is in the form of MOD-KEY. 74 | .Pp 75 | Allowed MODs: C (Control), S (Shift), M (mod1), M2 (mod2), M3 (mod3), M4 (mod4). 76 | .Sh KEY BINDINGS 77 | All key bindings start with the prefix key which by default is 78 | .Ic C\-t . 79 | The prefix key can be configured in the 80 | .An configuration file . 81 | .Pp 82 | These are the default key bindings for all actions: 83 | .Bl -tag -width Ds 84 | .It Ic C\-t c (coma-terminal) 85 | Start a new xterm in the current frame 86 | .It Ic C\-t h (frame-prev) 87 | Move to the next frame 88 | .It Ic C\-t l (frame-next) 89 | Move to the previous frame 90 | .It Ic C\-t space (frame-popup) 91 | Open and close the popup frame 92 | .It Ic C\-t z (frame-zoom) 93 | Zoom/unzoom the current frame 94 | .It Ic C\-t s (frame-split) 95 | Split the current frame vertically (can be done only once) 96 | .It Ic C\-t m (frame-merge) 97 | Merge a vertically split frame back together into one 98 | .It Ic C\-t f (frame-split-next) 99 | Toggle between the upper and lower part of a vertical split 100 | .It Ic C\-t i (frame-move-client-left) 101 | Move the active client to the left 102 | .It Ic C\-t o (frame-move-client-right) 103 | Move the active client to the right 104 | .It Ic C\-t r (coma-restart) 105 | Restart coma 106 | .It Ic C\-t q (coma-client-list) 107 | Display client quick selection list 108 | .It Ic C\-t k (client-kill) 109 | Kill the active client (no warning will be presented) 110 | .It Ic C\-t p (client-prev) 111 | Swap to the previous client in the frame 112 | .It Ic C\-t n (client-next) 113 | Swap to the next client in the frame 114 | .It Ic C\-t e (coma-run) 115 | Opens an input window and runs the given command when RETURN is hit 116 | .It Ic C\-t colon (coma-command) 117 | Runs an internal WM command 118 | .El 119 | .Sh COLORS 120 | Colors must be in RGB format prefixed with a pound character and 121 | quoted in double quotes: 122 | .Pp 123 | color client-active "#ffffff" 124 | .Pp 125 | The following colors can be configured: 126 | .Bl -tag -width Ds 127 | .It Ic client-active 128 | The border color for the current active client. 129 | .It Ic client-inactive 130 | The border color for inactive clients. 131 | .It Ic frame-bar 132 | The color for the frame bars. 133 | .It Ic frame-bar-directory 134 | The directory path of the active client. 135 | .It Ic frame-bar-client-active 136 | The text color for the active client in the frame bar. 137 | .It Ic frame-bar-client-inactive 138 | The text color for the inactive client in the frame bar. 139 | .Sh AUTHORS 140 | .Nm 141 | was written by 142 | .An Joris Vink 143 | -------------------------------------------------------------------------------- /coma.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Joris Vink 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "coma.h" 31 | 32 | static void coma_signal(int); 33 | static void coma_log_init(void); 34 | 35 | char myhost[256]; 36 | int restart = 0; 37 | volatile sig_atomic_t sig_recv = -1; 38 | char *homedir = NULL; 39 | char *terminal = NULL; 40 | 41 | static FILE *logfp = NULL; 42 | static char **cargv = NULL; 43 | 44 | static void 45 | usage(void) 46 | { 47 | printf("Help for coma %s\n", COMA_VERSION); 48 | printf("\n"); 49 | printf("-c\tconfiguration file ($HOME/.comarc by default)\n"); 50 | printf("\n"); 51 | printf("Mail bugs and patches to joris@coders.se\n"); 52 | printf("\n"); 53 | exit(1); 54 | } 55 | 56 | int 57 | main(int argc, char *argv[]) 58 | { 59 | struct sigaction sa; 60 | int ch; 61 | struct passwd *pw; 62 | const char *config; 63 | char *layout; 64 | 65 | cargv = argv; 66 | layout = NULL; 67 | config = NULL; 68 | 69 | if ((pw = getpwuid(getuid())) == NULL) 70 | fatal("who are you?"); 71 | 72 | if ((homedir = strdup(pw->pw_dir)) == NULL) 73 | fatal("strdup failed"); 74 | 75 | if (chdir(homedir) == -1) 76 | fatal("chdir(%s): %s", homedir, errno_s); 77 | 78 | coma_log_init(); 79 | coma_wm_init(); 80 | 81 | while ((ch = getopt(argc, argv, "c:hl:")) != -1) { 82 | switch (ch) { 83 | case 'c': 84 | config = optarg; 85 | break; 86 | case 'l': 87 | layout = optarg; 88 | break; 89 | case 'h': 90 | default: 91 | usage(); 92 | break; 93 | } 94 | } 95 | 96 | if ((terminal = strdup(COMA_TERMINAL)) == NULL) 97 | fatal("strdup"); 98 | 99 | coma_frame_init(); 100 | coma_config_parse(config); 101 | 102 | if (layout != NULL) 103 | coma_frame_layout(layout); 104 | 105 | memset(&sa, 0, sizeof(sa)); 106 | sa.sa_handler = coma_signal; 107 | 108 | if (sigfillset(&sa.sa_mask) == -1) 109 | fatal("sigfillset: %s", errno_s); 110 | 111 | if (sigaction(SIGINT, &sa, NULL) == -1) 112 | fatal("sigaction: %s", errno_s); 113 | if (sigaction(SIGHUP, &sa, NULL) == -1) 114 | fatal("sigaction: %s", errno_s); 115 | if (sigaction(SIGQUIT, &sa, NULL) == -1) 116 | fatal("sigaction: %s", errno_s); 117 | if (sigaction(SIGTERM, &sa, NULL) == -1) 118 | fatal("sigaction: %s", errno_s); 119 | if (sigaction(SIGCHLD, &sa, NULL) == -1) 120 | fatal("sigaction: %s", errno_s); 121 | 122 | if (gethostname(myhost, sizeof(myhost)) == -1) 123 | fatal("gethostname: %s", errno_s); 124 | 125 | coma_client_init(); 126 | coma_wm_setup(); 127 | coma_wm_run(); 128 | 129 | if (restart) { 130 | execvp(cargv[0], cargv); 131 | fatal("failed to restart process: %s", errno_s); 132 | } 133 | 134 | return (0); 135 | } 136 | 137 | void 138 | coma_reap(void) 139 | { 140 | pid_t pid; 141 | int status; 142 | 143 | for (;;) { 144 | if ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) == -1) { 145 | if (errno == ECHILD) 146 | return; 147 | if (errno == EINTR) 148 | continue; 149 | fprintf(stderr, "waitpid: %s\n", errno_s); 150 | return; 151 | } 152 | 153 | if (pid == 0) 154 | return; 155 | } 156 | } 157 | 158 | void 159 | coma_spawn_terminal(void) 160 | { 161 | char *argv[2]; 162 | 163 | argv[0] = terminal; 164 | argv[1] = NULL; 165 | 166 | coma_execute(argv); 167 | } 168 | 169 | void 170 | coma_execute(char **argv) 171 | { 172 | pid_t pid; 173 | const char *pwd; 174 | 175 | pid = fork(); 176 | 177 | switch (pid) { 178 | case -1: 179 | fprintf(stderr, "failed to spawn terminal: %s\n", errno_s); 180 | return; 181 | case 0: 182 | if (frame_active->focus) 183 | pwd = frame_active->focus->pwd; 184 | else 185 | pwd = NULL; 186 | 187 | if (pwd != NULL && chdir(pwd) == -1) 188 | fprintf(stderr, "chdir: %s\n", errno_s); 189 | 190 | (void)setsid(); 191 | execvp(argv[0], argv); 192 | fprintf(stderr, "failed to start '%s': %s\n", argv[0], errno_s); 193 | exit(1); 194 | break; 195 | default: 196 | break; 197 | } 198 | } 199 | 200 | void * 201 | coma_malloc(size_t len) 202 | { 203 | void *ptr; 204 | 205 | if ((ptr = malloc(len)) == NULL) 206 | fatal("malloc: %s", errno_s); 207 | 208 | return (ptr); 209 | } 210 | 211 | void * 212 | coma_calloc(size_t memb, size_t len) 213 | { 214 | void *ptr; 215 | 216 | if (SIZE_MAX / memb < len) 217 | fatal("coma_calloc(): memb * len > SIZE_MAX"); 218 | 219 | if ((ptr = calloc(memb, len)) == NULL) 220 | fatal("calloc: %s", errno_s); 221 | 222 | return (ptr); 223 | } 224 | 225 | int 226 | coma_split_arguments(char *args, char **argv, size_t elm) 227 | { 228 | size_t idx; 229 | int count; 230 | char *p, *line, *end; 231 | 232 | if (elm <= 1) 233 | fatal("not enough elements (%zu)", elm); 234 | 235 | idx = 0; 236 | count = 0; 237 | line = args; 238 | 239 | for (p = line; *p != '\0'; p++) { 240 | if (idx >= elm - 1) 241 | break; 242 | 243 | if (*p == ' ') { 244 | *p = '\0'; 245 | if (*line != '\0') { 246 | argv[idx++] = line; 247 | count++; 248 | } 249 | line = p + 1; 250 | continue; 251 | } 252 | 253 | if (*p != '"') 254 | continue; 255 | 256 | line = p + 1; 257 | if ((end = strchr(line, '"')) == NULL) 258 | break; 259 | 260 | *end = '\0'; 261 | argv[idx++] = line; 262 | count++; 263 | line = end + 1; 264 | 265 | while (isspace(*(unsigned char *)line)) 266 | line++; 267 | 268 | p = line; 269 | } 270 | 271 | if (idx < elm - 1 && *line != '\0') { 272 | argv[idx++] = line; 273 | count++; 274 | } 275 | 276 | argv[idx] = NULL; 277 | 278 | return (count); 279 | } 280 | 281 | int 282 | coma_split_string(char *input, const char *delim, char **out, size_t ele) 283 | { 284 | int count; 285 | char **ap; 286 | 287 | if (ele == 0) 288 | return (0); 289 | 290 | count = 0; 291 | for (ap = out; ap < &out[ele - 1] && 292 | (*ap = strsep(&input, delim)) != NULL;) { 293 | if (**ap != '\0') { 294 | ap++; 295 | count++; 296 | } 297 | } 298 | 299 | *ap = NULL; 300 | return (count); 301 | } 302 | 303 | void 304 | coma_log(const char *fmt, ...) 305 | { 306 | va_list args; 307 | 308 | if (logfp == NULL) 309 | return; 310 | 311 | va_start(args, fmt); 312 | vfprintf(logfp, fmt, args); 313 | va_end(args); 314 | 315 | fprintf(logfp, "\n"); 316 | fflush(logfp); 317 | } 318 | 319 | char * 320 | coma_program_path(void) 321 | { 322 | return (cargv[0]); 323 | } 324 | 325 | void 326 | fatal(const char *fmt, ...) 327 | { 328 | va_list args; 329 | 330 | fprintf(stderr, "error: "); 331 | 332 | va_start(args, fmt); 333 | vfprintf(stderr, fmt, args); 334 | 335 | if (logfp != NULL) { 336 | fprintf(logfp, "FATAL: "); 337 | vfprintf(logfp, fmt, args); 338 | fprintf(logfp, "\n"); 339 | fflush(logfp); 340 | } 341 | 342 | va_end(args); 343 | 344 | fprintf(stderr, "\n"); 345 | exit(1); 346 | } 347 | 348 | static void 349 | coma_log_init(void) 350 | { 351 | if ((logfp = fopen(COMA_LOG_FILE, "a")) == NULL) 352 | fatal("failed to open logfile: %s", strerror(errno)); 353 | 354 | coma_log("coma %s starting", COMA_VERSION); 355 | } 356 | 357 | static void 358 | coma_signal(int sig) 359 | { 360 | sig_recv = sig; 361 | } 362 | -------------------------------------------------------------------------------- /coma.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Joris Vink 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef __H_COMA_H 18 | #define __H_COMA_H 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #define errno_s strerror(errno) 31 | 32 | #define COMA_VERSION "1.2" 33 | #define COMA_WM_FONT "fixed:pixelsize=13:style=bold" 34 | 35 | #define COMA_TERMINAL "xterm" 36 | 37 | #define COMA_ACTION_PREFIX "cmd:" 38 | #define COMA_ACTION_SHELL_PREFIX "cmd_shell:" 39 | #define COMA_ACTION_NOHOLD_PREFIX "cmd_nohold:" 40 | 41 | #define COMA_ACTION_PREFIX_LEN (sizeof(COMA_ACTION_PREFIX) - 1) 42 | #define COMA_ACTION_SHELL_PREFIX_LEN (sizeof(COMA_ACTION_SHELL_PREFIX) - 1) 43 | #define COMA_ACTION_NOHOLD_PREFIX_LEN (sizeof(COMA_ACTION_NOHOLD_PREFIX) - 1) 44 | 45 | #define COMA_LOG_FILE ".coma.log" 46 | #define COMA_MOD_KEY ControlMask 47 | #define COMA_PREFIX_KEY XK_t 48 | 49 | #define COMA_FRAME_LAYOUT_DEFAULT 1 50 | #define COMA_FRAME_LAYOUT_SMALL_LARGE 2 51 | #define COMA_FRAME_LAYOUT_SMALL_DUAL 3 52 | 53 | #define COMA_FRAME_BORDER 5 54 | #define COMA_FRAME_GAP 20 55 | #define COMA_FRAME_BAR 35 56 | #define COMA_FRAME_WIDTH 484 57 | 58 | #define COMA_SHELL_ARGV 64 59 | 60 | struct frame; 61 | 62 | #define COMA_CLIENT_HIDDEN 0x0001 63 | 64 | struct client { 65 | u_int32_t id; 66 | u_int32_t pos; 67 | u_int32_t prev; 68 | u_int32_t flags; 69 | 70 | Window window; 71 | struct frame *frame; 72 | 73 | char *tag; 74 | char *cmd; 75 | char *pwd; 76 | char *host; 77 | char *status; 78 | 79 | u_int16_t w; 80 | u_int16_t h; 81 | u_int16_t x; 82 | u_int16_t y; 83 | u_int16_t bw; 84 | 85 | u_int16_t fbo; 86 | u_int16_t fbw; 87 | 88 | TAILQ_ENTRY(client) list; 89 | TAILQ_ENTRY(client) glist; 90 | }; 91 | 92 | TAILQ_HEAD(client_list, client); 93 | 94 | #define COMA_FRAME_INLIST 0x0001 95 | #define COMA_FRAME_ZOOMED 0x0002 96 | 97 | struct frame { 98 | u_int32_t id; 99 | int flags; 100 | int screen; 101 | 102 | Window bar; 103 | Visual *visual; 104 | Colormap colormap; 105 | XftDraw *xft_draw; 106 | 107 | u_int16_t w; 108 | u_int16_t h; 109 | u_int16_t x; 110 | u_int16_t y; 111 | 112 | u_int16_t orig_w; 113 | u_int16_t orig_h; 114 | u_int16_t orig_x; 115 | u_int16_t orig_y; 116 | 117 | struct client *focus; 118 | struct client_list clients; 119 | struct frame *split; 120 | 121 | TAILQ_ENTRY(frame) list; 122 | }; 123 | 124 | TAILQ_HEAD(frame_list, frame); 125 | 126 | extern Display *dpy; 127 | extern XftFont *font; 128 | extern struct client_list clients; 129 | extern int restart; 130 | extern char *homedir; 131 | extern char myhost[256]; 132 | extern unsigned int prefix_mod; 133 | extern KeySym prefix_key; 134 | extern char *terminal; 135 | extern char *font_name; 136 | extern int frame_count; 137 | extern int frame_layout; 138 | extern u_int16_t frame_gap; 139 | extern u_int16_t frame_bar; 140 | extern u_int16_t frame_width; 141 | extern u_int16_t frame_height; 142 | extern int frame_offset; 143 | extern u_int16_t frame_border; 144 | extern u_int16_t screen_width; 145 | extern u_int16_t screen_height; 146 | extern struct frame *frame_popup; 147 | extern struct frame *frame_active; 148 | extern struct client *client_active; 149 | extern int client_discovery; 150 | extern volatile sig_atomic_t sig_recv; 151 | 152 | extern Atom atom_frame_id; 153 | extern Atom atom_client_pos; 154 | extern Atom atom_client_act; 155 | extern Atom atom_net_wm_pid; 156 | extern Atom atom_client_visible; 157 | 158 | void fatal(const char *, ...); 159 | void coma_log(const char *, ...); 160 | 161 | void coma_reap(void); 162 | void coma_command(char *); 163 | void coma_execute(char **); 164 | char *coma_program_path(void); 165 | void coma_spawn_terminal(void); 166 | void coma_config_parse(const char *); 167 | int coma_split_arguments(char *, char **, size_t); 168 | int coma_split_string(char *, const char *, char **, size_t); 169 | 170 | void *coma_malloc(size_t); 171 | void *coma_calloc(size_t, size_t); 172 | 173 | void coma_wm_run(void); 174 | void coma_wm_init(void); 175 | void coma_wm_setup(void); 176 | XftColor *coma_wm_color(const char *); 177 | void coma_wm_register_prefix(Window); 178 | int coma_wm_register_action(const char *, KeySym); 179 | void coma_wm_property_write(Window, Atom, u_int32_t); 180 | int coma_wm_property_read(Window, Atom, u_int32_t *); 181 | int coma_wm_register_color(const char *, const char *); 182 | 183 | struct frame *coma_frame_lookup(u_int32_t); 184 | 185 | void coma_frame_init(void); 186 | void coma_frame_prev(void); 187 | void coma_frame_next(void); 188 | void coma_frame_zoom(void); 189 | void coma_frame_setup(void); 190 | void coma_frame_split(void); 191 | void coma_frame_merge(void); 192 | void coma_frame_cleanup(void); 193 | void coma_frame_bar_sort(void); 194 | void coma_frame_popup_show(void); 195 | void coma_frame_popup_hide(void); 196 | void coma_frame_split_next(void); 197 | void coma_frame_select_any(void); 198 | void coma_frame_client_prev(void); 199 | void coma_frame_client_next(void); 200 | void coma_frame_bars_create(void); 201 | void coma_frame_bars_update(void); 202 | void coma_frame_popup_toggle(void); 203 | void coma_frame_update_titles(void); 204 | void coma_frame_layout(const char *); 205 | void coma_frame_select_id(u_int32_t); 206 | void coma_frame_client_move_left(void); 207 | void coma_frame_client_move_right(void); 208 | void coma_frame_register(struct frame *); 209 | void coma_frame_focus(struct frame *, int); 210 | void coma_frame_bar_update(struct frame *); 211 | void coma_frame_bar_click(Window, u_int16_t); 212 | void coma_frame_mouseover(u_int16_t, u_int16_t); 213 | struct frame *coma_frame_create(u_int16_t, u_int16_t, u_int16_t, u_int16_t); 214 | 215 | void coma_client_init(void); 216 | void coma_client_create(Window); 217 | void coma_client_kill_active(void); 218 | void coma_client_map(struct client *); 219 | void coma_client_hide(struct client *); 220 | void coma_client_focus(struct client *); 221 | void coma_client_unhide(struct client *); 222 | void coma_client_adjust(struct client *); 223 | void coma_client_destroy(struct client *); 224 | void coma_client_update_title(struct client *); 225 | void coma_client_warp_pointer(struct client *); 226 | void coma_client_send_configure(struct client *); 227 | 228 | struct client *coma_client_find(Window); 229 | struct client *coma_frame_find_client(Window); 230 | 231 | #endif 232 | -------------------------------------------------------------------------------- /config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Joris Vink 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "coma.h" 29 | 30 | static void config_bind(int, char **); 31 | static void config_font(int, char **); 32 | static void config_color(int, char **); 33 | static void config_prefix(int, char **); 34 | static void config_terminal(int, char **); 35 | static void config_screen_height(int, char **); 36 | 37 | static void config_frame_gap(int, char **); 38 | static void config_frame_bar(int, char **); 39 | static void config_frame_count(int, char **); 40 | static void config_frame_width(int, char **); 41 | static void config_frame_height(int, char **); 42 | static void config_frame_offset(int, char **); 43 | static void config_frame_border(int, char **); 44 | static void config_frame_layout(int, char **); 45 | static void config_frame_create(int, char **); 46 | 47 | static void config_parse(const char *); 48 | static void config_fatal(const char *, const char *, ...); 49 | 50 | static char *config_read_line(FILE *, char *, size_t); 51 | static long long config_strtonum(const char *, const char *, int, 52 | long long, long long); 53 | 54 | struct { 55 | const char *name; 56 | int args; 57 | void (*cb)(int, char **); 58 | } keywords[] = { 59 | { "font", 1, config_font }, 60 | { "bind", 2, config_bind }, 61 | { "color", 2, config_color }, 62 | { "prefix", 1, config_prefix }, 63 | { "terminal", 1, config_terminal }, 64 | { "screen-height", 1, config_screen_height }, 65 | 66 | { "frame-gap", 1, config_frame_gap }, 67 | { "frame-bar", 1, config_frame_bar }, 68 | { "frame-count", 1, config_frame_count }, 69 | { "frame-width", 1, config_frame_width }, 70 | { "frame-height", 1, config_frame_height }, 71 | { "frame-offset", 1, config_frame_offset }, 72 | { "frame-border", 1, config_frame_border }, 73 | { "frame-layout", 1, config_frame_layout }, 74 | { "frame-create", 4, config_frame_create }, 75 | 76 | { NULL, 0, NULL } 77 | }; 78 | 79 | struct { 80 | const char *mod; 81 | unsigned int mask; 82 | } modmasks[] = { 83 | { "C", ControlMask }, 84 | { "S", ShiftMask }, 85 | { "M", Mod1Mask }, 86 | { "M2", Mod2Mask }, 87 | { "M3", Mod3Mask }, 88 | { "M4", Mod4Mask }, 89 | { NULL, 0 } 90 | }; 91 | 92 | static int config_line = 1; 93 | 94 | void 95 | coma_config_parse(const char *cpath) 96 | { 97 | int len; 98 | struct passwd *pw; 99 | char path[PATH_MAX]; 100 | 101 | if (cpath == NULL) { 102 | if ((pw = getpwuid(getuid())) == NULL) 103 | fatal("getpwuid(): %s", errno_s); 104 | 105 | len = snprintf(path, sizeof(path), "%s/.comarc", pw->pw_dir); 106 | if (len == -1 || (size_t)len >= sizeof(path)) 107 | fatal("failed to create path to config file"); 108 | 109 | cpath = path; 110 | } 111 | 112 | config_parse(cpath); 113 | } 114 | 115 | static void 116 | config_parse(const char *path) 117 | { 118 | FILE *fp; 119 | int i, argc; 120 | char *line, buf[128], *argv[16]; 121 | 122 | if ((fp = fopen(path, "r")) == NULL) 123 | return; 124 | 125 | while ((line = config_read_line(fp, buf, sizeof(buf))) != NULL) { 126 | argc = coma_split_string(line, " ", argv, 16); 127 | 128 | if (argc < 2) { 129 | config_line++; 130 | continue; 131 | } 132 | 133 | for (i = 0; keywords[i].name != NULL; i++) { 134 | if (!strcmp(argv[0], keywords[i].name)) { 135 | coma_log("got '%s' with %d", argv[0], argc - 1); 136 | if (argc - 1 != keywords[i].args) { 137 | config_fatal(argv[0], 138 | "requires %d args, got %d", 139 | keywords[i].args, argc - 1); 140 | } else { 141 | keywords[i].cb(argc, argv); 142 | } 143 | } 144 | } 145 | 146 | config_line++; 147 | } 148 | 149 | (void)fclose(fp); 150 | } 151 | 152 | static void 153 | config_fatal(const char *kw, const char *fmt, ...) 154 | { 155 | va_list args; 156 | 157 | fprintf(stderr, "config error on line %d for keyword '%s': ", 158 | config_line, kw); 159 | 160 | va_start(args, fmt); 161 | vfprintf(stderr, fmt, args); 162 | va_end(args); 163 | 164 | fprintf(stderr, "\n"); 165 | 166 | exit(1); 167 | } 168 | 169 | static void 170 | config_bind(int argc, char **argv) 171 | { 172 | KeySym sym; 173 | 174 | if ((sym = XStringToKeysym(argv[2])) == NoSymbol) 175 | config_fatal(argv[0], "invalid key '%s'", argv[2]); 176 | 177 | if (coma_wm_register_action(argv[1], sym) == -1) 178 | config_fatal(argv[0], "unknown action '%s'", argv[1]); 179 | } 180 | 181 | static void 182 | config_font(int argc, char **argv) 183 | { 184 | free(font_name); 185 | 186 | if ((font_name = strdup(argv[1])) == NULL) 187 | fatal("strdup"); 188 | } 189 | 190 | static void 191 | config_color(int argc, char **argv) 192 | { 193 | int valid; 194 | char *color, *p, *c; 195 | 196 | if (*argv[2] != '"') 197 | config_fatal(argv[0], "missing beginning '\"'"); 198 | 199 | color = argv[2] + 1; 200 | 201 | if ((p = strchr(color, '"')) == NULL) 202 | config_fatal(argv[0], "missing ending '\"'"); 203 | *p = '\0'; 204 | 205 | if (*color != '#') 206 | config_fatal(argv[0], "missing '#' in rgb color '%s'", color); 207 | 208 | if (strlen(color) != 7) 209 | config_fatal(argv[0], "invalid rgb color '%s'", color); 210 | 211 | for (c = color + 1; *c != '\0'; c++) { 212 | valid = 0; 213 | if (!isdigit(*(unsigned char *)c)) { 214 | valid |= (*c >= 'a' && *c <= 'f'); 215 | valid |= (*c >= 'A' && *c <= 'F'); 216 | if (valid == 0) { 217 | config_fatal(argv[0], 218 | "invalid rgb color '%s'", color); 219 | } 220 | } 221 | } 222 | 223 | if (coma_wm_register_color(argv[1], color) == -1) 224 | config_fatal(argv[0], "unknown color '%s'", argv[1]); 225 | } 226 | 227 | static void 228 | config_prefix(int argc, char **argv) 229 | { 230 | int i; 231 | char *mod, *key; 232 | 233 | mod = argv[1]; 234 | 235 | if ((key = strchr(mod, '-')) == NULL) 236 | config_fatal(argv[0], "missing '-' in prefix key"); 237 | 238 | *(key)++ = '\0'; 239 | 240 | if (*mod == '\0') 241 | config_fatal(argv[0], "missing mod value before '-'"); 242 | 243 | if (*key == '\0') 244 | config_fatal(argv[0], "missing key value after '-'"); 245 | 246 | if ((prefix_key = XStringToKeysym(key)) == NoSymbol) 247 | config_fatal(argv[0], "invalid key '%s'", key); 248 | 249 | for (i = 0; modmasks[i].mod != NULL; i++) { 250 | if (!strcmp(mod, modmasks[i].mod)) { 251 | prefix_mod = modmasks[i].mask; 252 | break; 253 | } 254 | } 255 | 256 | if (modmasks[i].mask == 0) 257 | config_fatal(argv[0], "invalid mod key '%s'", mod); 258 | } 259 | 260 | static void 261 | config_terminal(int argc, char **argv) 262 | { 263 | free(terminal); 264 | 265 | if ((terminal = strdup(argv[1])) == NULL) 266 | fatal("strdup"); 267 | } 268 | 269 | static void 270 | config_screen_height(int argc, char **argv) 271 | { 272 | screen_height = config_strtonum(argv[0], argv[1], 10, 1, USHRT_MAX); 273 | } 274 | 275 | static void 276 | config_frame_bar(int argc, char **argv) 277 | { 278 | frame_bar = config_strtonum(argv[0], argv[1], 10, 0, USHRT_MAX); 279 | } 280 | 281 | static void 282 | config_frame_gap(int argc, char **argv) 283 | { 284 | frame_gap = config_strtonum(argv[0], argv[1], 10, 0, USHRT_MAX); 285 | } 286 | 287 | static void 288 | config_frame_count(int argc, char **argv) 289 | { 290 | frame_count = config_strtonum(argv[0], argv[1], 10, 1, INT_MAX); 291 | } 292 | 293 | static void 294 | config_frame_width(int argc, char **argv) 295 | { 296 | frame_width = config_strtonum(argv[0], argv[1], 10, 1, USHRT_MAX); 297 | } 298 | 299 | static void 300 | config_frame_height(int argc, char **argv) 301 | { 302 | frame_height = config_strtonum(argv[0], argv[1], 10, 1, USHRT_MAX); 303 | } 304 | 305 | static void 306 | config_frame_offset(int argc, char **argv) 307 | { 308 | frame_offset = config_strtonum(argv[0], argv[1], 10, 0, USHRT_MAX); 309 | } 310 | 311 | static void 312 | config_frame_border(int argc, char **argv) 313 | { 314 | frame_border = config_strtonum(argv[0], argv[1], 10, 0, USHRT_MAX); 315 | } 316 | 317 | static void 318 | config_frame_layout(int argc, char **argv) 319 | { 320 | coma_frame_layout(argv[1]); 321 | } 322 | 323 | static void 324 | config_frame_create(int argc, char **argv) 325 | { 326 | struct frame *frame; 327 | u_int16_t x, y, w, h; 328 | 329 | x = config_strtonum(argv[0], argv[1], 10, 0, USHRT_MAX); 330 | y = config_strtonum(argv[0], argv[2], 10, 0, USHRT_MAX); 331 | w = config_strtonum(argv[0], argv[3], 10, 0, USHRT_MAX); 332 | h = config_strtonum(argv[0], argv[4], 10, 0, USHRT_MAX); 333 | 334 | frame = coma_frame_create(w, h, x, y); 335 | coma_frame_register(frame); 336 | } 337 | 338 | static char * 339 | config_read_line(FILE *fp, char *in, size_t len) 340 | { 341 | char *p, *t; 342 | 343 | if (fgets(in, len, fp) == NULL) 344 | return (NULL); 345 | 346 | p = in; 347 | in[strcspn(in, "\n")] = '\0'; 348 | 349 | while (isspace(*(unsigned char *)p)) 350 | p++; 351 | 352 | if (p[0] == '#' || p[0] == '\0') { 353 | p[0] = '\0'; 354 | return (p); 355 | } 356 | 357 | for (t = p; *t != '\0'; t++) { 358 | if (*t == '\t') 359 | *t = ' '; 360 | } 361 | 362 | return (p); 363 | } 364 | 365 | static long long 366 | config_strtonum(const char *kw, const char *str, int base, 367 | long long min, long long max) 368 | { 369 | long long l; 370 | char *ep; 371 | 372 | if (min > max) 373 | config_fatal(kw, "min > max"); 374 | 375 | errno = 0; 376 | l = strtoll(str, &ep, base); 377 | if (errno != 0 || str == ep || *ep != '\0') 378 | config_fatal(kw, "'%s' is not a valid integer", str); 379 | 380 | if (l < min) 381 | config_fatal(kw, "'%s' is too low", str); 382 | 383 | if (l > max) 384 | config_fatal(kw, "'%s' is too high", str); 385 | 386 | return (l); 387 | } 388 | -------------------------------------------------------------------------------- /frame.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Joris Vink 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #if defined(__linux__) 25 | #include 26 | #endif 27 | 28 | #include "coma.h" 29 | 30 | #define CLIENT_MOVE_LEFT 1 31 | #define CLIENT_MOVE_RIGHT 2 32 | 33 | #define LARGE_SINGLE_WINDOW 0 34 | #define LARGE_DUAL_WINDOWS 1 35 | 36 | static void frame_layout_default(void); 37 | static void frame_layout_small_large(int); 38 | static void frame_bar_sort(struct frame *); 39 | static void frame_bar_create(struct frame *); 40 | 41 | static void frame_client_move(int); 42 | static struct frame *frame_find_left(void); 43 | static struct frame *frame_find_right(void); 44 | 45 | static struct frame_list frames; 46 | static u_int32_t frame_id = 1; 47 | static u_int16_t zoom_width = 0; 48 | 49 | int frame_count = -1; 50 | int frame_offset = -1; 51 | u_int16_t frame_height = 0; 52 | u_int16_t frame_y_offset = 0; 53 | struct frame *frame_popup = NULL; 54 | struct frame *frame_active = NULL; 55 | u_int16_t frame_gap = COMA_FRAME_GAP; 56 | u_int16_t frame_bar = COMA_FRAME_BAR; 57 | u_int16_t frame_width = COMA_FRAME_WIDTH; 58 | u_int16_t frame_border = COMA_FRAME_BORDER; 59 | int frame_layout = COMA_FRAME_LAYOUT_DEFAULT; 60 | 61 | void 62 | coma_frame_init(void) 63 | { 64 | TAILQ_INIT(&frames); 65 | } 66 | 67 | void 68 | coma_frame_setup(void) 69 | { 70 | switch (frame_layout) { 71 | case COMA_FRAME_LAYOUT_DEFAULT: 72 | frame_layout_default(); 73 | break; 74 | case COMA_FRAME_LAYOUT_SMALL_LARGE: 75 | frame_layout_small_large(LARGE_SINGLE_WINDOW); 76 | break; 77 | case COMA_FRAME_LAYOUT_SMALL_DUAL: 78 | frame_layout_small_large(LARGE_DUAL_WINDOWS); 79 | break; 80 | default: 81 | fatal("unknown frame layout %d", frame_layout); 82 | } 83 | 84 | frame_popup->id = UINT_MAX; 85 | frame_active = TAILQ_FIRST(&frames); 86 | 87 | coma_log("frame active is %u", frame_active->id); 88 | } 89 | 90 | void 91 | coma_frame_layout(const char *mode) 92 | { 93 | if (!strcmp(mode, "default")) { 94 | frame_layout = COMA_FRAME_LAYOUT_DEFAULT; 95 | } else if (!strcmp(mode, "small-large")) { 96 | frame_layout = COMA_FRAME_LAYOUT_SMALL_LARGE; 97 | } else if (!strcmp(mode, "small-dual")) { 98 | frame_layout = COMA_FRAME_LAYOUT_SMALL_DUAL; 99 | } else { 100 | fatal("unknown frame-layout '%s'", mode); 101 | } 102 | } 103 | 104 | void 105 | coma_frame_cleanup(void) 106 | { 107 | struct frame *frame, *next; 108 | 109 | for (frame = TAILQ_FIRST(&frames); frame != NULL; frame = next) { 110 | next = TAILQ_NEXT(frame, list); 111 | TAILQ_REMOVE(&frames, frame, list); 112 | XDestroyWindow(dpy, frame->bar); 113 | XftDrawDestroy(frame->xft_draw); 114 | free(frame); 115 | } 116 | 117 | XDestroyWindow(dpy, frame_popup->bar); 118 | XftDrawDestroy(frame_popup->xft_draw); 119 | free(frame_popup); 120 | } 121 | 122 | void 123 | coma_frame_popup_toggle(void) 124 | { 125 | if (frame_active == frame_popup) 126 | coma_frame_popup_hide(); 127 | else 128 | coma_frame_popup_show(); 129 | } 130 | 131 | void 132 | coma_frame_popup_hide(void) 133 | { 134 | struct client *client; 135 | 136 | TAILQ_FOREACH(client, &frame_popup->clients, list) 137 | coma_client_hide(client); 138 | 139 | if (frame_popup->split != NULL) { 140 | TAILQ_FOREACH(client, 141 | &frame_popup->split->clients, list) { 142 | coma_client_hide(client); 143 | } 144 | } 145 | 146 | coma_frame_select_any(); 147 | 148 | XUnmapWindow(dpy, frame_popup->bar); 149 | if (frame_popup->split != NULL) 150 | XUnmapWindow(dpy, frame_popup->split->bar); 151 | } 152 | 153 | void 154 | coma_frame_popup_show(void) 155 | { 156 | struct client *client, *focus; 157 | 158 | if (frame_active->flags & COMA_FRAME_ZOOMED) 159 | return; 160 | 161 | focus = frame_popup->focus; 162 | frame_active = frame_popup; 163 | 164 | TAILQ_FOREACH(client, &frame_popup->clients, list) 165 | coma_client_unhide(client); 166 | 167 | if (frame_popup->split != NULL) { 168 | TAILQ_FOREACH(client, 169 | &frame_popup->split->clients, list) { 170 | coma_client_unhide(client); 171 | } 172 | } 173 | 174 | XMapRaised(dpy, frame_popup->bar); 175 | coma_frame_bar_update(frame_popup); 176 | 177 | if (frame_popup->split != NULL) { 178 | XMapRaised(dpy, frame_popup->split->bar); 179 | coma_frame_bar_update(frame_popup->split); 180 | } 181 | 182 | if (focus != NULL) 183 | coma_client_focus(focus); 184 | } 185 | 186 | void 187 | coma_frame_next(void) 188 | { 189 | struct frame *next; 190 | 191 | if (!(frame_active->flags & COMA_FRAME_INLIST) || 192 | frame_active->flags & COMA_FRAME_ZOOMED) 193 | return; 194 | 195 | if ((next = frame_find_right()) != NULL) 196 | coma_frame_focus(next, 1); 197 | } 198 | 199 | void 200 | coma_frame_prev(void) 201 | { 202 | struct frame *prev; 203 | 204 | if (!(frame_active->flags & COMA_FRAME_INLIST) || 205 | frame_active->flags & COMA_FRAME_ZOOMED) 206 | return; 207 | 208 | if ((prev = frame_find_left()) != NULL) 209 | coma_frame_focus(prev, 1); 210 | } 211 | 212 | void 213 | coma_frame_client_next(void) 214 | { 215 | struct client *next; 216 | 217 | if (frame_active->focus == NULL) 218 | return; 219 | 220 | next = TAILQ_PREV(frame_active->focus, client_list, list); 221 | if (next == NULL) 222 | next = TAILQ_LAST(&frame_active->clients, client_list); 223 | 224 | if (next != NULL) { 225 | coma_client_focus(next); 226 | coma_client_warp_pointer(next); 227 | coma_frame_bar_update(frame_active); 228 | } 229 | } 230 | 231 | void 232 | coma_frame_client_prev(void) 233 | { 234 | struct client *prev; 235 | 236 | if (frame_active->focus == NULL) 237 | return; 238 | 239 | if ((prev = TAILQ_NEXT(frame_active->focus, list)) == NULL) 240 | prev = TAILQ_FIRST(&frame_active->clients); 241 | 242 | if (prev != NULL) { 243 | coma_client_focus(prev); 244 | coma_client_warp_pointer(prev); 245 | coma_frame_bar_update(frame_active); 246 | } 247 | } 248 | 249 | void 250 | coma_frame_client_move_left(void) 251 | { 252 | frame_client_move(CLIENT_MOVE_LEFT); 253 | } 254 | 255 | void 256 | coma_frame_client_move_right(void) 257 | { 258 | frame_client_move(CLIENT_MOVE_RIGHT); 259 | } 260 | 261 | void 262 | coma_frame_split(void) 263 | { 264 | struct frame *frame; 265 | struct client *client; 266 | u_int16_t height, used, y; 267 | 268 | if (frame_active == frame_popup) 269 | return; 270 | 271 | if (frame_active->split != NULL) 272 | return; 273 | 274 | height = frame_border + frame_active->h + frame_border + frame_bar; 275 | used = (frame_border * 4) + (frame_bar * 2) + frame_gap; 276 | height = (height - used) / 2; 277 | 278 | y = frame_active->y + frame_border + height + frame_border + 279 | frame_bar + frame_gap; 280 | 281 | frame = coma_frame_create(frame_active->w, height, frame_active->x, y); 282 | 283 | if (frame_active->flags & COMA_FRAME_INLIST) 284 | TAILQ_INSERT_TAIL(&frames, frame, list); 285 | 286 | frame->split = frame_active; 287 | frame->flags = frame_active->flags; 288 | 289 | frame_active->split = frame; 290 | frame_active->h = height; 291 | 292 | frame_active->orig_h = frame_active->h; 293 | 294 | frame_bar_create(frame_active); 295 | frame_bar_create(frame); 296 | 297 | TAILQ_FOREACH(client, &frame_active->clients, list) 298 | coma_client_adjust(client); 299 | 300 | coma_frame_bar_update(frame); 301 | coma_frame_bar_update(frame_active); 302 | 303 | frame_active = frame; 304 | coma_spawn_terminal(); 305 | } 306 | 307 | void 308 | coma_frame_merge(void) 309 | { 310 | struct frame *survives, *dies; 311 | struct client *client, *focus, *next; 312 | 313 | if (frame_active->split == NULL) 314 | return; 315 | 316 | if (frame_active->y < frame_active->split->y) { 317 | survives = frame_active; 318 | dies = frame_active->split; 319 | } else { 320 | dies = frame_active; 321 | survives = frame_active->split; 322 | } 323 | 324 | focus = dies->focus; 325 | 326 | for (client = TAILQ_FIRST(&dies->clients); 327 | client != NULL; client = next) { 328 | next = TAILQ_NEXT(client, list); 329 | TAILQ_REMOVE(&dies->clients, client, list); 330 | 331 | client->frame = survives; 332 | TAILQ_INSERT_TAIL(&survives->clients, client, list); 333 | } 334 | 335 | if (dies->flags & COMA_FRAME_INLIST) 336 | TAILQ_REMOVE(&frames, dies, list); 337 | 338 | XDestroyWindow(dpy, dies->bar); 339 | XftDrawDestroy(dies->xft_draw); 340 | free(dies); 341 | 342 | survives->split = NULL; 343 | survives->h = frame_height; 344 | survives->orig_h = survives->h; 345 | 346 | frame_active = survives; 347 | 348 | TAILQ_FOREACH(client, &frame_active->clients, list) 349 | coma_client_adjust(client); 350 | 351 | if (focus != NULL) { 352 | coma_client_focus(focus); 353 | coma_client_warp_pointer(focus); 354 | } 355 | 356 | frame_bar_create(frame_active); 357 | coma_frame_bar_update(frame_active); 358 | } 359 | 360 | void 361 | coma_frame_split_next(void) 362 | { 363 | if (frame_active->split == NULL) 364 | return; 365 | 366 | coma_frame_focus(frame_active->split, 1); 367 | } 368 | 369 | void 370 | coma_frame_select_any(void) 371 | { 372 | struct frame *frame; 373 | 374 | frame = NULL; 375 | 376 | TAILQ_FOREACH(frame, &frames, list) { 377 | if (TAILQ_EMPTY(&frame->clients)) 378 | continue; 379 | break; 380 | } 381 | 382 | if (frame == NULL) 383 | frame = TAILQ_FIRST(&frames); 384 | 385 | coma_frame_focus(frame, 1); 386 | } 387 | 388 | void 389 | coma_frame_select_id(u_int32_t id) 390 | { 391 | struct frame *frame; 392 | 393 | TAILQ_FOREACH(frame, &frames, list) { 394 | if (frame->id == id) { 395 | coma_frame_focus(frame, 1); 396 | return; 397 | } 398 | } 399 | 400 | if (frame_popup->id == id) 401 | coma_frame_popup_show(); 402 | } 403 | 404 | void 405 | coma_frame_mouseover(u_int16_t x, u_int16_t y) 406 | { 407 | struct client *client, *prev; 408 | struct frame *frame, *prev_frame; 409 | 410 | if (frame_active == frame_popup) 411 | return; 412 | 413 | if (frame_active->flags & COMA_FRAME_ZOOMED) 414 | return; 415 | 416 | frame = NULL; 417 | client = NULL; 418 | prev_frame = frame_active; 419 | prev = frame_active->focus; 420 | 421 | TAILQ_FOREACH(frame, &frames, list) { 422 | if (x >= frame->x && x <= frame->x + frame->w && 423 | y >= frame->y && y <= frame->y + frame->h) 424 | break; 425 | } 426 | 427 | if (frame == NULL) 428 | return; 429 | 430 | frame_active = frame; 431 | if (frame_active->focus != NULL) 432 | client = frame_active->focus; 433 | else 434 | client = TAILQ_FIRST(&frame_active->clients); 435 | 436 | if (client != NULL && prev != client) 437 | coma_client_focus(client); 438 | 439 | if (prev_frame) 440 | coma_frame_bar_update(prev_frame); 441 | 442 | coma_frame_bar_update(frame_active); 443 | } 444 | 445 | struct client * 446 | coma_frame_find_client(Window window) 447 | { 448 | struct client *client; 449 | 450 | TAILQ_FOREACH(client, &clients, glist) { 451 | if (client->window == window) 452 | return (client); 453 | } 454 | 455 | return (NULL); 456 | } 457 | 458 | void 459 | coma_frame_zoom(void) 460 | { 461 | struct client *client; 462 | 463 | if (frame_active->focus == NULL) 464 | return; 465 | 466 | if (frame_active == frame_popup) 467 | return; 468 | 469 | if (frame_active->flags & COMA_FRAME_ZOOMED) { 470 | frame_active->w = frame_active->orig_w; 471 | frame_active->h = frame_active->orig_h; 472 | frame_active->x = frame_active->orig_x; 473 | frame_active->y = frame_active->orig_y; 474 | frame_active->flags &= ~COMA_FRAME_ZOOMED; 475 | } else { 476 | frame_active->w = zoom_width; 477 | frame_active->h = frame_height; 478 | frame_active->x = frame_offset; 479 | frame_active->y = frame_y_offset; 480 | frame_active->flags |= COMA_FRAME_ZOOMED; 481 | } 482 | 483 | TAILQ_FOREACH(client, &frame_active->clients, list) { 484 | coma_client_hide(client); 485 | coma_client_adjust(client); 486 | } 487 | 488 | coma_client_unhide(frame_active->focus); 489 | 490 | frame_bar_create(frame_active); 491 | coma_frame_bar_update(frame_active); 492 | } 493 | 494 | void 495 | coma_frame_bar_sort(void) 496 | { 497 | struct frame *frame; 498 | 499 | TAILQ_FOREACH(frame, &frames, list) 500 | frame_bar_sort(frame); 501 | 502 | frame_bar_sort(frame_popup); 503 | coma_frame_bars_update(); 504 | } 505 | 506 | void 507 | coma_frame_bars_create(void) 508 | { 509 | struct frame *frame; 510 | 511 | TAILQ_FOREACH(frame, &frames, list) 512 | frame_bar_create(frame); 513 | 514 | frame_bar_create(frame_popup); 515 | XUnmapWindow(dpy, frame_popup->bar); 516 | 517 | coma_frame_bars_update(); 518 | } 519 | 520 | void 521 | coma_frame_bars_update(void) 522 | { 523 | struct frame *frame; 524 | 525 | TAILQ_FOREACH(frame, &frames, list) 526 | coma_frame_bar_update(frame); 527 | 528 | coma_frame_bar_update(frame_popup); 529 | } 530 | 531 | void 532 | coma_frame_bar_update(struct frame *frame) 533 | { 534 | XGlyphInfo gi; 535 | u_int32_t pos; 536 | size_t slen; 537 | u_int16_t offset; 538 | struct client *client; 539 | int len, idx; 540 | char buf[64], status[256]; 541 | XftColor *bar_active, *bar_inactive; 542 | XftColor *active, *inactive, *color, *dir; 543 | 544 | /* Can be called before bars are setup. */ 545 | if (frame->bar == None) 546 | return; 547 | 548 | pos = 1; 549 | TAILQ_FOREACH_REVERSE(client, &frame->clients, client_list, list) { 550 | client->pos = pos++; 551 | if (client->pos != client->prev) { 552 | coma_wm_property_write(client->window, 553 | atom_client_pos, client->pos); 554 | client->prev = client->pos; 555 | } 556 | } 557 | 558 | idx = 0; 559 | offset = 5; 560 | buf[0] = '\0'; 561 | 562 | dir = coma_wm_color("frame-bar-directory"); 563 | active = coma_wm_color("frame-bar-client-active"); 564 | inactive = coma_wm_color("frame-bar-client-inactive"); 565 | 566 | bar_active = coma_wm_color("frame-bar"); 567 | bar_inactive = coma_wm_color("frame-bar-inactive"); 568 | 569 | if (frame_active == frame) { 570 | color = coma_wm_color("client-active"); 571 | XSetWindowBorder(dpy, frame->bar, color->pixel); 572 | XSetWindowBackground(dpy, frame->bar, bar_active->pixel); 573 | } else { 574 | dir = inactive; 575 | active = inactive; 576 | color = coma_wm_color("client-inactive"); 577 | XSetWindowBorder(dpy, frame->bar, color->pixel); 578 | XSetWindowBackground(dpy, frame->bar, bar_inactive->pixel); 579 | } 580 | 581 | XClearWindow(dpy, frame->bar); 582 | 583 | if (frame == frame_popup) { 584 | (void)strlcpy(buf, "[popup bar]", sizeof(buf)); 585 | slen = strlen(buf); 586 | XftTextExtentsUtf8(dpy, font, (const FcChar8 *)buf, slen, &gi); 587 | XftDrawStringUtf8(frame->xft_draw, active, font, 588 | offset, 30, (const FcChar8 *)buf, slen); 589 | offset += gi.width + 4; 590 | } 591 | 592 | if (frame->focus != NULL) 593 | client = frame->focus; 594 | else 595 | client = NULL; 596 | 597 | if (client != NULL && client->pwd != NULL) { 598 | if (client->host) { 599 | len = snprintf(status, sizeof(status), "%s - %s", 600 | client->host, client->pwd); 601 | } else { 602 | len = snprintf(status, sizeof(status), "%s", 603 | client->pwd); 604 | } 605 | 606 | if (len == -1 || (size_t)len >= sizeof(status)) 607 | (void)strlcpy(buf, "[error]", sizeof(buf)); 608 | 609 | XftDrawStringUtf8(frame->xft_draw, dir, font, 610 | 5, 15, (const FcChar8 *)status, len); 611 | } 612 | 613 | TAILQ_FOREACH_REVERSE(client, &frame->clients, client_list, list) { 614 | if (client->tag) { 615 | len = snprintf(buf, sizeof(buf), "[%s]", client->tag); 616 | } else if (client->cmd) { 617 | len = snprintf(buf, sizeof(buf), "[%s]", client->cmd); 618 | } else if (client->host) { 619 | len = snprintf(buf, sizeof(buf), "[%s]", client->host); 620 | } else { 621 | len = snprintf(buf, sizeof(buf), "[%u]", idx); 622 | } 623 | 624 | if (len == -1 || (size_t)len >= sizeof(buf)) 625 | (void)strlcpy(buf, "[?]", sizeof(buf)); 626 | 627 | idx++; 628 | 629 | if (client == frame->focus) 630 | color = active; 631 | else 632 | color = inactive; 633 | 634 | slen = strlen(buf); 635 | XftTextExtentsUtf8(dpy, font, (const FcChar8 *)buf, slen, &gi); 636 | 637 | XftDrawStringUtf8(frame->xft_draw, color, font, 638 | offset, 30, (const FcChar8 *)buf, slen); 639 | 640 | client->fbo = offset; 641 | client->fbw = gi.width; 642 | 643 | offset += gi.width + 4; 644 | } 645 | } 646 | 647 | void 648 | coma_frame_bar_click(Window bar, u_int16_t offset) 649 | { 650 | struct frame *frame; 651 | struct client *client; 652 | 653 | frame = NULL; 654 | TAILQ_FOREACH(frame, &frames, list) { 655 | if (frame->bar == bar) 656 | break; 657 | } 658 | 659 | if (frame == NULL) 660 | return; 661 | 662 | client = NULL; 663 | 664 | TAILQ_FOREACH(client, &frame->clients, list) { 665 | if (offset >= client->fbo && 666 | offset <= client->fbo + client->fbw) 667 | break; 668 | } 669 | 670 | if (client != NULL) { 671 | frame->focus = client; 672 | coma_frame_focus(frame, 0); 673 | coma_frame_bar_update(frame); 674 | } 675 | } 676 | 677 | void 678 | coma_frame_update_titles(void) 679 | { 680 | struct frame *frame; 681 | struct client *client; 682 | 683 | TAILQ_FOREACH(frame, &frames, list) { 684 | TAILQ_FOREACH(client, &frame->clients, list) 685 | coma_client_update_title(client); 686 | coma_frame_bar_update(frame); 687 | } 688 | 689 | TAILQ_FOREACH(client, &frame_popup->clients, list) 690 | coma_client_update_title(client); 691 | 692 | coma_frame_bar_update(frame_popup); 693 | 694 | if (frame_popup->split != NULL) { 695 | TAILQ_FOREACH(client, &frame_popup->split->clients, list) 696 | coma_client_update_title(client); 697 | coma_frame_bar_update(frame_popup->split); 698 | } 699 | } 700 | 701 | static void 702 | frame_layout_default(void) 703 | { 704 | struct frame *frame; 705 | u_int16_t i, count, width, offset, x; 706 | 707 | count = 0; 708 | 709 | if (frame_offset != -1) 710 | width = screen_width - frame_offset; 711 | else 712 | width = screen_width; 713 | 714 | if (frame_height == 0) { 715 | frame_y_offset = frame_gap; 716 | frame_height = screen_height - (frame_gap * 2) - frame_bar - 717 | (frame_border * 2); 718 | } else { 719 | frame_y_offset = (screen_height - 720 | (frame_bar + frame_height + (frame_border * 2))) / 2; 721 | } 722 | 723 | while (width > frame_width) { 724 | if (frame_count != -1 && count == frame_count) 725 | break; 726 | count++; 727 | width -= frame_width; 728 | } 729 | 730 | if (frame_offset != -1) { 731 | offset = frame_offset; 732 | } else { 733 | offset = width / 2; 734 | } 735 | 736 | if (offset > (frame_gap * count)) 737 | offset -= frame_gap; 738 | 739 | x = offset; 740 | zoom_width = 0; 741 | 742 | for (i = 0; i < count; i++) { 743 | frame = coma_frame_create(frame_width, 744 | frame_height, offset, frame_y_offset); 745 | coma_frame_register(frame); 746 | offset += frame_width + frame_gap + (frame_border * 2); 747 | zoom_width += frame_width + frame_gap + (frame_border * 2); 748 | } 749 | 750 | if (frame_offset != -1) { 751 | width = screen_width / 2; 752 | if (frame_offset < width) { 753 | i = frame_offset; 754 | width = screen_width; 755 | offset = frame_offset - (i / 2); 756 | width -= i; 757 | } else { 758 | i = frame_offset - width; 759 | offset = width + (i / 2); 760 | width -= i * 2; 761 | } 762 | } else { 763 | offset = frame_gap; 764 | width = screen_width - (frame_gap * 2) - (frame_border * 2); 765 | } 766 | 767 | frame_offset = x; 768 | zoom_width -= frame_gap + (frame_border * 2); 769 | 770 | frame_popup = coma_frame_create(zoom_width, 771 | frame_height, x, frame_y_offset); 772 | } 773 | 774 | struct frame * 775 | coma_frame_lookup(u_int32_t id) 776 | { 777 | struct frame *frame; 778 | 779 | if (frame_popup->id == id) 780 | return (frame_popup); 781 | 782 | TAILQ_FOREACH(frame, &frames, list) { 783 | if (frame->id == id) 784 | return (frame); 785 | } 786 | 787 | return (NULL); 788 | } 789 | 790 | void 791 | coma_frame_focus(struct frame *frame, int warp) 792 | { 793 | struct frame *prev; 794 | struct client *client; 795 | 796 | prev = frame_active; 797 | frame_active = frame; 798 | 799 | if ((client = frame->focus) == NULL) 800 | client = TAILQ_FIRST(&frame->clients); 801 | 802 | if (client != NULL) { 803 | coma_client_focus(client); 804 | if (warp) 805 | coma_client_warp_pointer(client); 806 | } 807 | 808 | coma_frame_bar_update(prev); 809 | coma_frame_bar_update(frame_active); 810 | } 811 | 812 | struct frame * 813 | coma_frame_create(u_int16_t width, u_int16_t height, u_int16_t x, u_int16_t y) 814 | { 815 | struct frame *frame; 816 | 817 | frame = coma_calloc(1, sizeof(*frame)); 818 | 819 | frame->bar = None; 820 | frame->id = frame_id++; 821 | 822 | frame->x = x; 823 | frame->y = y; 824 | frame->orig_x = x; 825 | frame->orig_y = y; 826 | 827 | frame->w = width; 828 | frame->h = height; 829 | frame->orig_w = width; 830 | frame->orig_h = height; 831 | 832 | frame->screen = DefaultScreen(dpy); 833 | frame->visual = DefaultVisual(dpy, frame->screen); 834 | frame->colormap = DefaultColormap(dpy, frame->screen); 835 | 836 | TAILQ_INIT(&frame->clients); 837 | 838 | return (frame); 839 | } 840 | 841 | void 842 | coma_frame_register(struct frame *frame) 843 | { 844 | frame->flags = COMA_FRAME_INLIST; 845 | TAILQ_INSERT_TAIL(&frames, frame, list); 846 | } 847 | 848 | static void 849 | frame_layout_small_large(int dual) 850 | { 851 | struct frame *frame; 852 | u_int16_t offset, width; 853 | 854 | zoom_width = 0; 855 | 856 | if (frame_offset == -1) { 857 | offset = frame_gap; 858 | frame_offset = offset; 859 | } else { 860 | offset = frame_offset; 861 | } 862 | 863 | frame_y_offset = frame_gap; 864 | frame_height = screen_height - (frame_gap * 2) - frame_bar - 865 | (frame_border * 2); 866 | 867 | /* Small frame on the left hand-side. */ 868 | frame = coma_frame_create(frame_width, frame_height, 869 | offset, frame_y_offset); 870 | coma_frame_register(frame); 871 | offset += frame_width + frame_gap + (frame_border * 2); 872 | 873 | /* Rest of the screen covered by large/dual frame(s). */ 874 | if (dual) { 875 | width = ((screen_width - offset - frame_gap) / 2) - 876 | frame_border; 877 | } else { 878 | width = screen_width - offset - frame_gap - (frame_border * 2); 879 | } 880 | 881 | frame = coma_frame_create(width, frame_height, offset, frame_y_offset); 882 | coma_frame_register(frame); 883 | 884 | if (dual) { 885 | offset += width; 886 | frame = coma_frame_create(width, frame_height, offset, 887 | frame_y_offset); 888 | coma_frame_register(frame); 889 | } 890 | 891 | /* Popup covers entire screen. */ 892 | frame_popup = coma_frame_create(screen_width - (frame_border * 2) - 893 | (frame_gap * 2), frame_height, frame_gap, frame_y_offset); 894 | 895 | zoom_width = screen_width - (frame_gap * 2); 896 | } 897 | 898 | static void 899 | frame_bar_create(struct frame *frame) 900 | { 901 | XftColor *color; 902 | u_int16_t y_offset; 903 | 904 | if (frame->bar != None) { 905 | XDestroyWindow(dpy, frame->bar); 906 | XftDrawDestroy(frame->xft_draw); 907 | } 908 | 909 | y_offset = frame->y + frame->h + (frame_border * 2); 910 | color = coma_wm_color("frame-bar"); 911 | 912 | frame->bar = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 913 | frame->x, y_offset + (frame_gap / 2), frame->w, 914 | frame_bar, 0, WhitePixel(dpy, frame->screen), color->pixel); 915 | 916 | XSetWindowBorderWidth(dpy, frame->bar, frame_border); 917 | 918 | XSelectInput(dpy, frame->bar, ButtonReleaseMask); 919 | 920 | if ((frame->xft_draw = XftDrawCreate(dpy, 921 | frame->bar, frame->visual, frame->colormap)) == NULL) 922 | fatal("XftDrawCreate failed"); 923 | 924 | XMapWindow(dpy, frame->bar); 925 | } 926 | 927 | static void 928 | frame_bar_sort(struct frame *frame) 929 | { 930 | struct client *c1, *c2, *next; 931 | 932 | for (c1 = TAILQ_FIRST(&frame->clients); c1 != NULL; c1 = next) { 933 | next = TAILQ_NEXT(c1, list); 934 | TAILQ_FOREACH(c2, &frame->clients, list) { 935 | if (c1->pos < c2->pos) { 936 | TAILQ_REMOVE(&frame->clients, c1, list); 937 | TAILQ_INSERT_AFTER(&frame->clients, 938 | c2, c1, list); 939 | break; 940 | } 941 | } 942 | } 943 | } 944 | 945 | static struct frame * 946 | frame_find_left(void) 947 | { 948 | struct frame *frame, *candidate; 949 | 950 | candidate = NULL; 951 | 952 | TAILQ_FOREACH_REVERSE(frame, &frames, frame_list, list) { 953 | if (frame->x < frame_active->x) { 954 | if (candidate == NULL) 955 | candidate = frame; 956 | if (frame->y == frame_active->y) 957 | return (frame); 958 | } 959 | } 960 | 961 | return (candidate); 962 | } 963 | 964 | static struct frame * 965 | frame_find_right(void) 966 | { 967 | struct frame *frame, *candidate; 968 | 969 | candidate = NULL; 970 | 971 | TAILQ_FOREACH(frame, &frames, list) { 972 | if (frame->x > frame_active->x) { 973 | if (candidate == NULL) 974 | candidate = frame; 975 | if (frame->y == frame_active->y) 976 | return (frame); 977 | } 978 | } 979 | 980 | return (candidate); 981 | } 982 | 983 | static void 984 | frame_client_move(int which) 985 | { 986 | struct client *client; 987 | struct frame *other, *prev; 988 | 989 | if (!(frame_active->flags & COMA_FRAME_INLIST)) 990 | return; 991 | 992 | if (TAILQ_EMPTY(&frame_active->clients)) 993 | return; 994 | 995 | prev = frame_active; 996 | 997 | switch (which) { 998 | case CLIENT_MOVE_LEFT: 999 | other = frame_find_left(); 1000 | break; 1001 | case CLIENT_MOVE_RIGHT: 1002 | other = frame_find_right(); 1003 | break; 1004 | default: 1005 | other = NULL; 1006 | break; 1007 | } 1008 | 1009 | if (other == NULL) 1010 | return; 1011 | 1012 | client = frame_active->focus; 1013 | frame_active->focus = TAILQ_NEXT(client, list); 1014 | 1015 | if (frame_active->focus == NULL) 1016 | frame_active->focus = TAILQ_FIRST(&frame_active->clients); 1017 | 1018 | if (frame_active->focus) 1019 | coma_client_focus(frame_active->focus); 1020 | 1021 | TAILQ_REMOVE(&frame_active->clients, client, list); 1022 | TAILQ_INSERT_HEAD(&other->clients, client, list); 1023 | 1024 | client->frame = other; 1025 | client->x = other->x; 1026 | 1027 | coma_client_adjust(client); 1028 | 1029 | frame_active = other; 1030 | coma_client_focus(client); 1031 | coma_client_warp_pointer(client); 1032 | 1033 | coma_frame_bar_update(prev); 1034 | coma_frame_bar_update(frame_active); 1035 | } 1036 | -------------------------------------------------------------------------------- /scripts/coma-cmd: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | print -Pn "\e]0;%m;%d;$1\a" 4 | 5 | $@ 6 | -------------------------------------------------------------------------------- /scripts/coma-remote: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | ssh $1 "zsh -c \"cd $2 && ${@:3}\"" 4 | -------------------------------------------------------------------------------- /wm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Joris Vink 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #if defined(__linux__) 31 | #include 32 | #endif 33 | 34 | #include "coma.h" 35 | 36 | static void wm_run(void); 37 | static void wm_command(void); 38 | static void wm_restart(void); 39 | static void wm_teardown(void); 40 | static void wm_screen_init(void); 41 | static void wm_client_list(void); 42 | static void wm_layout_swap(void); 43 | static void wm_query_atoms(void); 44 | static Atom wm_atom(const char *); 45 | static void wm_run_command(char *, int); 46 | static void wm_run_shell_command(char *); 47 | static int wm_input(char *, size_t, void (*autocomplete)(char *, size_t)); 48 | 49 | static void wm_client_check(Window); 50 | static void wm_handle_prefix(XKeyEvent *); 51 | static void wm_mouse_click(XButtonEvent *); 52 | static void wm_mouse_motion(XMotionEvent *); 53 | 54 | static void wm_window_map(XMapRequestEvent *); 55 | static void wm_window_destroy(XDestroyWindowEvent *); 56 | static void wm_window_configure(XConfigureRequestEvent *); 57 | 58 | static int wm_error(Display *, XErrorEvent *); 59 | static int wm_error_active(Display *, XErrorEvent *); 60 | 61 | Display *dpy = NULL; 62 | XftFont *font = NULL; 63 | u_int16_t screen_width = 0; 64 | u_int16_t screen_height = 0; 65 | int client_discovery = 0; 66 | 67 | Atom atom_frame_id = None; 68 | Atom atom_client_pos = None; 69 | Atom atom_client_act = None; 70 | Atom atom_net_wm_pid = None; 71 | Atom atom_client_visible = None; 72 | 73 | char *font_name = NULL; 74 | unsigned int prefix_mod = COMA_MOD_KEY; 75 | KeySym prefix_key = COMA_PREFIX_KEY; 76 | 77 | static Window key_input = None; 78 | static Window cmd_input = None; 79 | static Window clients_win = None; 80 | 81 | static XftDraw *cmd_xft = NULL; 82 | static XftDraw *clients_xft = NULL; 83 | 84 | struct { 85 | const char *name; 86 | const char *rgb; 87 | int allocated; 88 | XftColor color; 89 | } xft_colors[] = { 90 | { "client-active", "#000000", 0, { 0 }}, 91 | { "client-inactive", "#d0cdb3", 0, { 0 }}, 92 | { "frame-bar", "#fff2d0", 0, { 0 }}, 93 | { "frame-bar-inactive", "#d0cdb3", 0, { 0 }}, 94 | { "frame-bar-directory", "#000000", 0, { 0 }}, 95 | { "frame-bar-client-active", "#000000", 0, { 0 }}, 96 | { "frame-bar-client-inactive", "#888888", 0, { 0 }}, 97 | { "command-input", "#ffffff", 0, { 0 }}, 98 | { "command-bar", "#000000", 0, { 0 }}, 99 | { "command-border", "#55007a", 0, { 0 }}, 100 | { NULL, NULL, 0, { 0 }}, 101 | }; 102 | 103 | struct uaction { 104 | KeySym sym; 105 | char *action; 106 | int hold; 107 | int shell; 108 | LIST_ENTRY(uaction) list; 109 | }; 110 | 111 | static LIST_HEAD(, uaction) uactions; 112 | 113 | struct { 114 | const char *name; 115 | KeySym sym; 116 | void (*cb)(void); 117 | } actions[] = { 118 | { "frame-prev", XK_h, coma_frame_prev }, 119 | { "frame-next", XK_l, coma_frame_next }, 120 | { "frame-popup", XK_space, coma_frame_popup_toggle }, 121 | 122 | { "frame-zoom", XK_z, coma_frame_zoom }, 123 | { "frame-split", XK_s, coma_frame_split }, 124 | { "frame-merge", XK_m, coma_frame_merge }, 125 | { "frame-split-next", XK_f, coma_frame_split_next }, 126 | { "frame-layout-swap", XK_w, wm_layout_swap }, 127 | 128 | { "frame-move-client-left", XK_i, coma_frame_client_move_left }, 129 | { "frame-move-client-right", XK_o, coma_frame_client_move_right }, 130 | 131 | { "coma-restart", XK_r, wm_restart }, 132 | { "coma-terminal", XK_c, coma_spawn_terminal }, 133 | 134 | { "client-kill", XK_k, coma_client_kill_active }, 135 | { "client-prev", XK_p, coma_frame_client_prev }, 136 | { "client-next", XK_n, coma_frame_client_next }, 137 | 138 | { "coma-run", XK_e, wm_run }, 139 | { "coma-command", XK_colon, wm_command }, 140 | { "coma-client-list", XK_q, wm_client_list }, 141 | 142 | { NULL, 0, NULL } 143 | }; 144 | 145 | void 146 | coma_wm_init(void) 147 | { 148 | if ((dpy = XOpenDisplay(NULL)) == NULL) 149 | fatal("failed to open display"); 150 | 151 | if ((font_name = strdup(COMA_WM_FONT)) == NULL) 152 | fatal("strdup"); 153 | 154 | LIST_INIT(&uactions); 155 | } 156 | 157 | void 158 | coma_wm_setup(void) 159 | { 160 | XSetErrorHandler(wm_error_active); 161 | XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask); 162 | XSync(dpy, True); 163 | 164 | XSetErrorHandler(wm_error); 165 | 166 | wm_query_atoms(); 167 | wm_screen_init(); 168 | } 169 | 170 | void 171 | coma_wm_run(void) 172 | { 173 | XEvent evt; 174 | struct pollfd pfd[1]; 175 | int running, ret; 176 | 177 | running = 1; 178 | restart = 0; 179 | 180 | while (running) { 181 | if (sig_recv != -1) { 182 | switch (sig_recv) { 183 | case SIGQUIT: 184 | case SIGINT: 185 | running = 0; 186 | continue; 187 | case SIGHUP: 188 | running = 0; 189 | restart = 1; 190 | continue; 191 | case SIGCHLD: 192 | coma_reap(); 193 | break; 194 | default: 195 | break; 196 | } 197 | sig_recv = -1; 198 | } 199 | 200 | pfd[0].fd = ConnectionNumber(dpy); 201 | pfd[0].events = POLLIN; 202 | 203 | ret = poll(pfd, 1, 500); 204 | if (ret == -1) { 205 | if (errno == EINTR) 206 | continue; 207 | fatal("poll: %s", errno_s); 208 | } 209 | 210 | coma_frame_update_titles(); 211 | 212 | if (ret == 0 || !(pfd[0].revents & POLLIN)) 213 | continue; 214 | 215 | while (XPending(dpy)) { 216 | XNextEvent(dpy, &evt); 217 | 218 | switch (evt.type) { 219 | case ButtonRelease: 220 | wm_mouse_click(&evt.xbutton); 221 | break; 222 | case MotionNotify: 223 | wm_mouse_motion(&evt.xmotion); 224 | break; 225 | case DestroyNotify: 226 | wm_window_destroy(&evt.xdestroywindow); 227 | break; 228 | case ConfigureRequest: 229 | wm_window_configure(&evt.xconfigurerequest); 230 | break; 231 | case MapRequest: 232 | wm_window_map(&evt.xmaprequest); 233 | break; 234 | case KeyPress: 235 | wm_handle_prefix(&evt.xkey); 236 | break; 237 | } 238 | 239 | XSync(dpy, False); 240 | } 241 | 242 | } 243 | 244 | wm_teardown(); 245 | } 246 | 247 | XftColor * 248 | coma_wm_color(const char *name) 249 | { 250 | int i; 251 | 252 | for (i = 0; xft_colors[i].name != NULL; i++) { 253 | if (!strcmp(name, xft_colors[i].name)) 254 | return (&xft_colors[i].color); 255 | } 256 | 257 | return (&xft_colors[0].color); 258 | } 259 | 260 | void 261 | coma_wm_register_prefix(Window win) 262 | { 263 | KeyCode c; 264 | 265 | XUngrabKey(dpy, AnyKey, AnyModifier, win); 266 | 267 | c = XKeysymToKeycode(dpy, prefix_key); 268 | XGrabKey(dpy, c, prefix_mod, win, True, GrabModeAsync, GrabModeAsync); 269 | } 270 | 271 | int 272 | coma_wm_register_action(const char *action, KeySym sym) 273 | { 274 | int i; 275 | struct uaction *ua; 276 | 277 | if (!strncmp(COMA_ACTION_PREFIX, action, COMA_ACTION_PREFIX_LEN)) { 278 | ua = coma_calloc(1, sizeof(*ua)); 279 | ua->sym = sym; 280 | ua->hold = 1; 281 | ua->action = strdup(action + COMA_ACTION_PREFIX_LEN); 282 | if (ua->action == NULL) 283 | fatal("strdup"); 284 | LIST_INSERT_HEAD(&uactions, ua, list); 285 | return (0); 286 | } 287 | 288 | if (!strncmp(COMA_ACTION_NOHOLD_PREFIX, 289 | action, COMA_ACTION_NOHOLD_PREFIX_LEN)) { 290 | ua = coma_calloc(1, sizeof(*ua)); 291 | ua->sym = sym; 292 | ua->action = strdup(action + COMA_ACTION_NOHOLD_PREFIX_LEN); 293 | if (ua->action == NULL) 294 | fatal("strdup"); 295 | LIST_INSERT_HEAD(&uactions, ua, list); 296 | return (0); 297 | } 298 | 299 | if (!strncmp(COMA_ACTION_SHELL_PREFIX, 300 | action, COMA_ACTION_SHELL_PREFIX_LEN)) { 301 | ua = coma_calloc(1, sizeof(*ua)); 302 | ua->sym = sym; 303 | ua->shell = 1; 304 | ua->action = strdup(action + COMA_ACTION_SHELL_PREFIX_LEN); 305 | if (ua->action == NULL) 306 | fatal("strdup"); 307 | LIST_INSERT_HEAD(&uactions, ua, list); 308 | return (0); 309 | } 310 | 311 | for (i = 0; actions[i].name != NULL; i++) { 312 | if (!strcmp(actions[i].name, action)) { 313 | actions[i].sym = sym; 314 | return (0); 315 | } 316 | } 317 | 318 | return (-1); 319 | } 320 | 321 | int 322 | coma_wm_register_color(const char *name, const char *rgb) 323 | { 324 | Visual *visual; 325 | Colormap colormap; 326 | int screen, i; 327 | 328 | for (i = 0; xft_colors[i].name != NULL; i++) { 329 | if (!strcmp(name, xft_colors[i].name)) 330 | break; 331 | } 332 | 333 | if (xft_colors[i].name == NULL) 334 | return (-1); 335 | 336 | screen = DefaultScreen(dpy); 337 | visual = DefaultVisual(dpy, screen); 338 | colormap = DefaultColormap(dpy, screen); 339 | 340 | if (xft_colors[i].allocated) 341 | XftColorFree(dpy, visual, colormap, &xft_colors[i].color); 342 | 343 | XftColorAllocName(dpy, visual, colormap, rgb, &xft_colors[i].color); 344 | xft_colors[i].allocated = 1; 345 | 346 | return (0); 347 | } 348 | 349 | void 350 | coma_wm_property_write(Window win, Atom prop, u_int32_t value) 351 | { 352 | (void)XChangeProperty(dpy, win, prop, XA_INTEGER, 32, 353 | PropModeReplace, (unsigned char *)&value, 1); 354 | 355 | coma_log(">>> win 0x%08x prop 0x%08x = %u", win, prop, value); 356 | } 357 | 358 | int 359 | coma_wm_property_read(Window win, Atom prop, u_int32_t *val) 360 | { 361 | int ret; 362 | Atom type; 363 | unsigned char *data; 364 | int format; 365 | unsigned long nitems, bytes; 366 | 367 | ret = XGetWindowProperty(dpy, win, prop, 0, 32, False, AnyPropertyType, 368 | &type, &format, &nitems, &bytes, &data); 369 | 370 | if (ret != Success) { 371 | coma_log("! prop=0x%08x win=0x%08x bad prop", prop, win); 372 | return (-1); 373 | } 374 | 375 | if (type != XA_INTEGER && type != XA_CARDINAL) { 376 | coma_log("! prop=0x%08x win=0x%08x type=0x%08x bad type", 377 | prop, win, type); 378 | return (-1); 379 | } 380 | 381 | if (nitems != 1) { 382 | coma_log("! prop=0x%08x win=0x%08x bad nitems %d", 383 | prop, win, nitems); 384 | return (-1); 385 | } 386 | 387 | memcpy(val, data, sizeof(*val)); 388 | XFree(data); 389 | 390 | return (0); 391 | } 392 | 393 | static void 394 | wm_restart(void) 395 | { 396 | restart = 1; 397 | sig_recv = SIGQUIT; 398 | } 399 | 400 | static void 401 | wm_teardown(void) 402 | { 403 | struct uaction *ua; 404 | 405 | while ((ua = LIST_FIRST(&uactions)) != NULL) { 406 | LIST_REMOVE(ua, list); 407 | free(ua->action); 408 | free(ua); 409 | } 410 | 411 | coma_frame_cleanup(); 412 | 413 | XftFontClose(dpy, font); 414 | XftDrawDestroy(cmd_xft); 415 | XftDrawDestroy(clients_xft); 416 | 417 | XDestroyWindow(dpy, key_input); 418 | XDestroyWindow(dpy, cmd_input); 419 | XDestroyWindow(dpy, clients_win); 420 | 421 | XUngrabKeyboard(dpy, CurrentTime); 422 | XSync(dpy, True); 423 | XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); 424 | XCloseDisplay(dpy); 425 | } 426 | 427 | static void 428 | wm_screen_init(void) 429 | { 430 | u_int32_t id; 431 | int screen; 432 | struct client *client; 433 | Visual *visual; 434 | Colormap colormap; 435 | XftColor *bg, *border; 436 | unsigned int windows, idx; 437 | Window root, wr, wp, *childwin; 438 | 439 | screen = DefaultScreen(dpy); 440 | root = DefaultRootWindow(dpy); 441 | 442 | visual = DefaultVisual(dpy, screen); 443 | colormap = DefaultColormap(dpy, screen); 444 | screen_width = DisplayWidth(dpy, screen); 445 | 446 | if (screen_height == 0) 447 | screen_height = DisplayHeight(dpy, screen); 448 | 449 | if ((font = XftFontOpenName(dpy, screen, font_name)) == NULL) { 450 | coma_log("failed to open %s, falling back to default", 451 | font_name); 452 | if ((font = XftFontOpenName(dpy, screen, COMA_WM_FONT)) == NULL) 453 | fatal("failed to open %s", COMA_WM_FONT); 454 | } 455 | 456 | for (idx = 0; xft_colors[idx].name != NULL; idx++) { 457 | if (xft_colors[idx].allocated == 0) { 458 | XftColorAllocName(dpy, visual, colormap, 459 | xft_colors[idx].rgb, &xft_colors[idx].color); 460 | } 461 | } 462 | 463 | XSelectInput(dpy, root, 464 | SubstructureRedirectMask | SubstructureNotifyMask | 465 | EnterWindowMask | LeaveWindowMask | KeyPressMask | 466 | PointerMotionMask); 467 | 468 | coma_frame_setup(); 469 | coma_wm_register_prefix(root); 470 | coma_frame_bars_create(); 471 | 472 | client_discovery = 1; 473 | 474 | if (XQueryTree(dpy, root, &wr, &wp, &childwin, &windows)) { 475 | for (idx = 0; idx < windows; idx++) 476 | wm_client_check(childwin[idx]); 477 | XFree(childwin); 478 | } 479 | 480 | coma_frame_bar_sort(); 481 | 482 | key_input = XCreateSimpleWindow(dpy, root, 483 | 0, 0, 1, 1, 0, WhitePixel(dpy, screen), BlackPixel(dpy, screen)); 484 | XSelectInput(dpy, key_input, KeyPressMask); 485 | XMapWindow(dpy, key_input); 486 | 487 | bg = coma_wm_color("command-bar"); 488 | border = coma_wm_color("command-border"); 489 | 490 | cmd_input = XCreateSimpleWindow(dpy, root, 491 | (screen_width / 2) - 200, (screen_height / 2) - 50, 400, 492 | COMA_FRAME_BAR, 2, border->pixel, bg->pixel); 493 | 494 | if ((cmd_xft = XftDrawCreate(dpy, cmd_input, visual, colormap)) == NULL) 495 | fatal("XftDrawCreate failed"); 496 | 497 | if (frame_offset == -1) { 498 | clients_win = XCreateSimpleWindow(dpy, root, 499 | (screen_width / 2) - 220, (screen_height / 2) - 205, 500 | 400, 400, 2, border->pixel, bg->pixel); 501 | } else { 502 | clients_win = XCreateSimpleWindow(dpy, root, 503 | frame_offset + ((screen_width - frame_offset) / 2) - 220, 504 | (screen_height / 2) - 205, 505 | 400, 400, 2, border->pixel, bg->pixel); 506 | } 507 | 508 | if ((clients_xft = XftDrawCreate(dpy, 509 | clients_win, visual, colormap)) == NULL) 510 | fatal("XftDrawCreate failed"); 511 | 512 | if (coma_wm_property_read(root, atom_client_act, &id) == 0) { 513 | coma_log("client 0x%08x was active", id); 514 | if ((client = coma_client_find(id)) != NULL) { 515 | coma_client_focus(client); 516 | coma_frame_focus(client->frame, 1); 517 | if (client->frame == frame_popup) 518 | coma_frame_popup_show(); 519 | coma_frame_bar_update(client->frame); 520 | } 521 | } 522 | 523 | client_discovery = 0; 524 | XSync(dpy, True); 525 | } 526 | 527 | static void 528 | wm_query_atoms(void) 529 | { 530 | atom_net_wm_pid = wm_atom("_NET_WM_PID"); 531 | atom_frame_id = wm_atom("_COMA_WM_FRAME_ID"); 532 | atom_client_pos = wm_atom("_COMA_WM_CLIENT_POS"); 533 | atom_client_act = wm_atom("_COMA_WM_CLIENT_ACT"); 534 | atom_client_visible = wm_atom("_COMA_WM_CLIENT_VISIBLE"); 535 | 536 | coma_log("_NET_WM_PID Atom = 0x%08x", atom_net_wm_pid); 537 | coma_log("_COMA_WM_FRAME_ID Atom = 0x%08x", atom_frame_id); 538 | coma_log("_COMA_WM_CLIENT_POS Atom = 0x%08x", atom_client_pos); 539 | coma_log("_COMA_WM_CLIENT_ACT Atom = 0x%08x", atom_client_act); 540 | coma_log("_COMA_WM_CLIENT_VISIBLE Atom = 0x%08x", atom_client_visible); 541 | } 542 | 543 | static Atom 544 | wm_atom(const char *name) 545 | { 546 | Atom prop; 547 | 548 | if ((prop = XInternAtom(dpy, name, False)) == None) 549 | fatal("failed to query Atom '%s'", name); 550 | 551 | return (prop); 552 | } 553 | 554 | static void 555 | wm_run(void) 556 | { 557 | char cmd[2048]; 558 | 559 | if (wm_input(cmd, sizeof(cmd), NULL) == -1) 560 | return; 561 | 562 | wm_run_command(cmd, 1); 563 | } 564 | 565 | static void 566 | wm_command(void) 567 | { 568 | char cmd[32], *argv[32]; 569 | 570 | if (wm_input(cmd, sizeof(cmd), NULL) == -1) 571 | return; 572 | 573 | if (coma_split_arguments(cmd, argv, 32)) { 574 | if (!strcmp(argv[0], "tag") && argv[1] != NULL) { 575 | if (client_active == NULL) 576 | return; 577 | 578 | free(client_active->tag); 579 | if ((client_active->tag = strdup(argv[1])) == NULL) 580 | fatal("strdup"); 581 | 582 | coma_frame_bar_update(frame_active); 583 | } else if (!strcmp(argv[0], "untag")) { 584 | free(client_active->tag); 585 | client_active->tag = NULL; 586 | } 587 | } 588 | } 589 | 590 | static void 591 | wm_run_command(char *cmd, int hold) 592 | { 593 | int off, title, local; 594 | char *argv[COMA_SHELL_ARGV]; 595 | 596 | off = 0; 597 | local = 1; 598 | title = -1; 599 | 600 | argv[off++] = terminal; 601 | 602 | if (hold) 603 | argv[off++] = "-hold"; 604 | else 605 | argv[off++] = "+hold"; 606 | 607 | if (client_active != NULL && client_active->host != NULL) { 608 | if (strcmp(myhost, client_active->host)) { 609 | argv[off++] = "-T"; 610 | title = off; 611 | argv[off++] = client_active->host; 612 | } 613 | } 614 | 615 | argv[off++] = "-e"; 616 | 617 | if (client_active != NULL && client_active->host != NULL) { 618 | if (strcmp(myhost, client_active->host)) { 619 | argv[off++] = "coma-remote"; 620 | argv[off++] = client_active->host; 621 | if (client_active->pwd) 622 | argv[off++] = client_active->pwd; 623 | local = 0; 624 | } 625 | } 626 | 627 | if (local) 628 | argv[off++] = "coma-cmd"; 629 | 630 | if (coma_split_arguments(cmd, argv + off, COMA_SHELL_ARGV - off)) { 631 | if (!strcmp(argv[off], "vi") || !strcmp(argv[off], "vim")) 632 | argv[1] = "+hold"; 633 | 634 | if (title != -1) 635 | argv[title] = argv[off]; 636 | 637 | coma_execute(argv); 638 | } 639 | } 640 | 641 | static void 642 | wm_run_shell_command(char *cmd) 643 | { 644 | char *argv[COMA_SHELL_ARGV]; 645 | 646 | if (coma_split_arguments(cmd, argv, COMA_SHELL_ARGV)) 647 | coma_execute(argv); 648 | } 649 | 650 | static int 651 | wm_input(char *cmd, size_t len, void (*autocomplete)(char *, size_t)) 652 | { 653 | XEvent evt; 654 | KeySym sym; 655 | char c[2]; 656 | size_t clen; 657 | Window focus; 658 | XftColor *color; 659 | int revert; 660 | struct client *client; 661 | 662 | memset(cmd, 0, len); 663 | 664 | XSelectInput(dpy, cmd_input, KeyPressMask); 665 | XMapWindow(dpy, cmd_input); 666 | XRaiseWindow(dpy, cmd_input); 667 | 668 | client = client_active; 669 | XGetInputFocus(dpy, &focus, &revert); 670 | XSetInputFocus(dpy, cmd_input, RevertToNone, CurrentTime); 671 | 672 | color = coma_wm_color("command-input"); 673 | 674 | for (;;) { 675 | clen = strlen(cmd); 676 | 677 | XClearWindow(dpy, cmd_input); 678 | 679 | if (clen > 0) { 680 | XftDrawStringUtf8(cmd_xft, color, font, 681 | 5, 15, (const FcChar8 *)cmd, clen); 682 | } 683 | 684 | XMaskEvent(dpy, KeyPressMask, &evt); 685 | sym = XkbKeycodeToKeysym(dpy, evt.xkey.keycode, 0, 686 | (evt.xkey.state & ShiftMask)); 687 | 688 | if (sym == XK_Shift_L || sym == XK_Shift_R) 689 | continue; 690 | 691 | if (sym == XK_BackSpace) { 692 | if (clen > 0) 693 | cmd[clen - 1] = '\0'; 694 | continue; 695 | } 696 | 697 | if (sym == XK_Tab) { 698 | if (autocomplete != NULL) 699 | autocomplete(cmd, len); 700 | continue; 701 | } 702 | 703 | if (sym == XK_Escape || sym == XK_Return) 704 | break; 705 | 706 | c[0] = sym; 707 | c[1] = '\0'; 708 | 709 | if (strlcat(cmd, c, len) >= len) 710 | continue; 711 | } 712 | 713 | XUnmapWindow(dpy, cmd_input); 714 | 715 | if (client == client_active) 716 | XSetInputFocus(dpy, focus, RevertToPointerRoot, CurrentTime); 717 | 718 | if (clen > 1 && sym == XK_Return) 719 | return (0); 720 | 721 | return (-1); 722 | } 723 | 724 | static void 725 | wm_client_list(void) 726 | { 727 | XEvent evt; 728 | KeySym sym; 729 | Window focus; 730 | struct frame *prev; 731 | XftColor *color; 732 | char c, buf[128]; 733 | struct client *client, *cl, *list[16]; 734 | int revert, y, idx, len, limit; 735 | 736 | color = coma_wm_color("command-input"); 737 | 738 | XSelectInput(dpy, clients_win, KeyPressMask); 739 | XMapWindow(dpy, clients_win); 740 | XRaiseWindow(dpy, clients_win); 741 | 742 | client = client_active; 743 | XGetInputFocus(dpy, &focus, &revert); 744 | XSetInputFocus(dpy, clients_win, RevertToNone, CurrentTime); 745 | 746 | XClearWindow(dpy, clients_win); 747 | 748 | y = 20; 749 | idx = 0; 750 | 751 | TAILQ_FOREACH(cl, &clients, glist) { 752 | if (idx > 15) 753 | break; 754 | 755 | if (idx < 10) 756 | c = '0' + idx; 757 | else 758 | c = 'a' + (idx - 10); 759 | 760 | if (cl->tag) { 761 | len = snprintf(buf, sizeof(buf), 762 | "#%c [%s] [%s]", c, cl->tag, cl->host); 763 | } else if (cl->cmd) { 764 | len = snprintf(buf, sizeof(buf), 765 | "#%c [%s] [%s]", c, cl->cmd, cl->host); 766 | } else { 767 | len = snprintf(buf, sizeof(buf), 768 | "#%c [%s]", c, cl->host); 769 | } 770 | 771 | if (len == -1 || (size_t)len >= sizeof(buf)) 772 | len = snprintf(buf, sizeof(buf), "#%c [unknown]", c); 773 | 774 | if (len == -1 || (size_t)len >= sizeof(buf)) 775 | fatal("failed to construct client list buffer"); 776 | 777 | XftDrawStringUtf8(clients_xft, color, font, 778 | 5, y, (const FcChar8 *)buf, len); 779 | 780 | y += 15; 781 | 782 | list[idx++] = cl; 783 | } 784 | 785 | limit = idx; 786 | idx = -1; 787 | 788 | for (;;) { 789 | XMaskEvent(dpy, KeyPressMask, &evt); 790 | 791 | if (evt.type != KeyPress) 792 | continue; 793 | 794 | sym = XkbKeycodeToKeysym(dpy, evt.xkey.keycode, 0, 795 | (evt.xkey.state & ShiftMask)); 796 | 797 | if (sym == XK_Escape) 798 | break; 799 | 800 | if (sym >= XK_0 && sym <= XK_9) { 801 | idx = sym - XK_0; 802 | if (idx < limit) 803 | break; 804 | } 805 | 806 | if (sym >= XK_a && sym <= XK_f) { 807 | idx = (sym - XK_a) + 10; 808 | if (idx < limit) 809 | break; 810 | } 811 | } 812 | 813 | XUnmapWindow(dpy, clients_win); 814 | 815 | if (idx != -1 && idx < limit) { 816 | prev = frame_active; 817 | frame_active = list[idx]->frame; 818 | 819 | if (frame_active == frame_popup && prev != frame_popup) 820 | coma_frame_popup_show(); 821 | 822 | if (frame_active != frame_popup && prev == frame_popup) { 823 | coma_frame_popup_hide(); 824 | frame_active = list[idx]->frame; 825 | } 826 | 827 | coma_client_focus(list[idx]); 828 | coma_client_warp_pointer(list[idx]); 829 | } 830 | 831 | if (client == client_active) 832 | XSetInputFocus(dpy, focus, RevertToPointerRoot, CurrentTime); 833 | } 834 | 835 | static void 836 | wm_layout_swap(void) 837 | { 838 | XEvent evt; 839 | KeySym sym; 840 | char *argv[4], *layout; 841 | 842 | for (;;) { 843 | XMaskEvent(dpy, KeyPressMask, &evt); 844 | 845 | if (evt.type != KeyPress) 846 | continue; 847 | 848 | sym = XkbKeycodeToKeysym(dpy, evt.xkey.keycode, 0, 849 | (evt.xkey.state & ShiftMask)); 850 | 851 | if (sym == XK_Escape) 852 | break; 853 | 854 | switch (sym) { 855 | case XK_1: 856 | layout = "default"; 857 | break; 858 | case XK_2: 859 | layout = "small-large"; 860 | break; 861 | case XK_3: 862 | layout = "small-dual"; 863 | break; 864 | default: 865 | return; 866 | } 867 | 868 | coma_log("swapping to layout *%s'", layout); 869 | argv[0] = coma_program_path(); 870 | argv[1] = "-l"; 871 | argv[2] = layout; 872 | argv[3] = NULL; 873 | 874 | execvp(argv[0], argv); 875 | fatal("failed to execute %s: %s", argv[0], errno_s); 876 | } 877 | } 878 | 879 | static void 880 | wm_client_check(Window window) 881 | { 882 | u_int32_t pid; 883 | 884 | if (coma_wm_property_read(window, atom_net_wm_pid, &pid) == -1) { 885 | coma_log("ignoring window 0x%08x", window); 886 | return; 887 | } 888 | 889 | coma_log("discovered window 0x%08x with pid %u", window, pid); 890 | coma_client_create(window); 891 | } 892 | 893 | static void 894 | wm_window_destroy(XDestroyWindowEvent *evt) 895 | { 896 | struct client *client; 897 | 898 | if (evt->window == key_input) 899 | return; 900 | 901 | if ((client = coma_client_find(evt->window)) == NULL) 902 | return; 903 | 904 | coma_client_destroy(client); 905 | } 906 | 907 | static void 908 | wm_handle_prefix(XKeyEvent *prefix) 909 | { 910 | XEvent evt; 911 | struct uaction *ua; 912 | KeySym sym; 913 | Window focus; 914 | struct frame *frame; 915 | struct client *client; 916 | int revert, i; 917 | 918 | client = client_active; 919 | XGetInputFocus(dpy, &focus, &revert); 920 | 921 | sym = XkbKeycodeToKeysym(dpy, prefix->keycode, 0, 0); 922 | 923 | if (sym != prefix_key) 924 | return; 925 | 926 | XSetInputFocus(dpy, key_input, RevertToNone, CurrentTime); 927 | 928 | for (;;) { 929 | XMaskEvent(dpy, KeyPressMask, &evt); 930 | 931 | if (evt.type != KeyPress) 932 | goto out; 933 | 934 | sym = XkbKeycodeToKeysym(dpy, evt.xkey.keycode, 0, 935 | (evt.xkey.state & ShiftMask)); 936 | 937 | if (sym == XK_Shift_L || sym == XK_Shift_R) 938 | continue; 939 | 940 | if (sym >= XK_0 && sym <= XK_9) { 941 | sym -= XK_0; 942 | frame = coma_frame_lookup(sym); 943 | if (frame != NULL) { 944 | coma_frame_focus(frame, 1); 945 | goto out; 946 | } 947 | } 948 | 949 | break; 950 | } 951 | 952 | for (i = 0; actions[i].name != NULL; i++) { 953 | if (actions[i].sym == sym) { 954 | actions[i].cb(); 955 | break; 956 | } 957 | } 958 | 959 | if (actions[i].name == NULL) { 960 | LIST_FOREACH(ua, &uactions, list) { 961 | if (ua->sym == sym) { 962 | if (ua->shell) 963 | wm_run_shell_command(ua->action); 964 | else 965 | wm_run_command(ua->action, ua->hold); 966 | break; 967 | } 968 | } 969 | } 970 | 971 | out: 972 | if (client == client_active) 973 | XSetInputFocus(dpy, focus, RevertToPointerRoot, CurrentTime); 974 | } 975 | 976 | static void 977 | wm_mouse_click(XButtonEvent *evt) 978 | { 979 | coma_frame_bar_click(evt->window, evt->x); 980 | } 981 | 982 | static void 983 | wm_mouse_motion(XMotionEvent *evt) 984 | { 985 | static Time last = 0; 986 | 987 | if ((evt->time - last) <= (1000 / 60)) 988 | return; 989 | 990 | last = evt->time; 991 | coma_frame_mouseover(evt->x, evt->y); 992 | } 993 | 994 | static void 995 | wm_window_map(XMapRequestEvent *evt) 996 | { 997 | struct client *client; 998 | 999 | if ((client = coma_client_find(evt->window)) == NULL) 1000 | coma_client_create(evt->window); 1001 | } 1002 | 1003 | static void 1004 | wm_window_configure(XConfigureRequestEvent *evt) 1005 | { 1006 | XWindowChanges cfg; 1007 | struct client *client; 1008 | 1009 | memset(&cfg, 0, sizeof(cfg)); 1010 | 1011 | if ((client = coma_client_find(evt->window)) != NULL) { 1012 | if (evt->value_mask & CWBorderWidth) 1013 | client->bw = evt->border_width; 1014 | 1015 | if (evt->value_mask & CWWidth) 1016 | client->w = evt->width; 1017 | 1018 | if (evt->value_mask & CWHeight) 1019 | client->h = evt->height; 1020 | 1021 | if (evt->value_mask & CWX) 1022 | client->x = evt->x; 1023 | 1024 | if (evt->value_mask & CWY) 1025 | client->y = evt->y; 1026 | 1027 | cfg.x = client->frame->x; 1028 | cfg.y = client->frame->y; 1029 | cfg.width = client->frame->w; 1030 | cfg.height = client->frame->h; 1031 | cfg.border_width = client->bw; 1032 | 1033 | XConfigureWindow(dpy, evt->window, evt->value_mask, &cfg); 1034 | coma_client_adjust(client); 1035 | coma_client_send_configure(client); 1036 | } else { 1037 | cfg.x = evt->x; 1038 | cfg.y = evt->y; 1039 | cfg.stack_mode = Above; 1040 | cfg.width = evt->width; 1041 | cfg.height = evt->height; 1042 | evt->value_mask &= ~CWStackMode; 1043 | cfg.border_width = evt->border_width; 1044 | XConfigureWindow(dpy, evt->window, evt->value_mask, &cfg); 1045 | } 1046 | } 1047 | 1048 | static int 1049 | wm_error(Display *edpy, XErrorEvent *error) 1050 | { 1051 | int len; 1052 | char msg[128], req[128], num[32]; 1053 | 1054 | len = snprintf(num, sizeof(num), "%d", error->request_code); 1055 | if (len == -1 || (size_t)len >= sizeof(num)) 1056 | fatal("overflow creating num '%d' buf", error->request_code); 1057 | 1058 | XGetErrorText(dpy, error->error_code, msg, sizeof(msg)); 1059 | XGetErrorDatabaseText(dpy, "XRequest", num, "", 1060 | req, sizeof(req)); 1061 | 1062 | coma_log("%s: %s", req, msg); 1063 | 1064 | return (0); 1065 | } 1066 | 1067 | static int 1068 | wm_error_active(Display *edpy, XErrorEvent *error) 1069 | { 1070 | fatal("another wm is already running"); 1071 | return (0); 1072 | } 1073 | --------------------------------------------------------------------------------