From 3765d5af5024b24a593f6f213ad2852ce9617fe9 Mon Sep 17 00:00:00 2001 From: afiw Date: Sun, 18 Jan 2026 19:20:14 +0100 Subject: [PATCH] Import OpenBSD 7.8 source --- Makefile | 24 ++ calmwm.c | 237 ++++++++++++ calmwm.h | 611 +++++++++++++++++++++++++++++++ client.c | 1049 +++++++++++++++++++++++++++++++++++++++++++++++++++++ conf.c | 710 ++++++++++++++++++++++++++++++++++++ cwm.1 | 294 +++++++++++++++ cwmrc.5 | 510 ++++++++++++++++++++++++++ group.c | 378 +++++++++++++++++++ kbfunc.c | 825 +++++++++++++++++++++++++++++++++++++++++ menu.c | 619 +++++++++++++++++++++++++++++++ parse.y | 639 ++++++++++++++++++++++++++++++++ screen.c | 307 ++++++++++++++++ search.c | 289 +++++++++++++++ util.c | 140 +++++++ xevents.c | 493 +++++++++++++++++++++++++ xmalloc.c | 110 ++++++ xutil.c | 568 +++++++++++++++++++++++++++++ 17 files changed, 7803 insertions(+) create mode 100644 Makefile create mode 100644 calmwm.c create mode 100644 calmwm.h create mode 100644 client.c create mode 100644 conf.c create mode 100644 cwm.1 create mode 100644 cwmrc.5 create mode 100644 group.c create mode 100644 kbfunc.c create mode 100644 menu.c create mode 100644 parse.y create mode 100644 screen.c create mode 100644 search.c create mode 100644 util.c create mode 100644 xevents.c create mode 100644 xmalloc.c create mode 100644 xutil.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3a47404 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +# $OpenBSD: Makefile,v 1.25 2017/07/14 17:23:38 okan Exp $ + +.include + +PROG= cwm + +SRCS= calmwm.c screen.c xmalloc.c client.c menu.c \ + search.c util.c xutil.c conf.c xevents.c group.c \ + kbfunc.c parse.y + +CPPFLAGS+= -I${X11BASE}/include -I${X11BASE}/include/freetype2 -I${.CURDIR} + +CFLAGS+= -Wall +YFLAGS= +LDADD+= -L${X11BASE}/lib -lXft -lXrender -lX11 -lxcb -lXau -lXdmcp \ + -lfontconfig -lexpat -lfreetype -lz -lXrandr -lXext + +MANDIR= ${X11BASE}/man/man +MAN= cwm.1 cwmrc.5 + +obj: _xenocara_obj + +.include +.include diff --git a/calmwm.c b/calmwm.c new file mode 100644 index 0000000..23f3b96 --- /dev/null +++ b/calmwm.c @@ -0,0 +1,237 @@ +/* + * calmwm - the calm window manager + * + * Copyright (c) 2004 Marius Aamodt Eriksen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $OpenBSD: calmwm.c,v 1.115 2022/02/26 15:19:18 okan Exp $ + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "calmwm.h" + +Display *X_Dpy; +Time Last_Event_Time = CurrentTime; +Atom cwmh[CWMH_NITEMS]; +Atom ewmh[EWMH_NITEMS]; +struct screen_q Screenq = TAILQ_HEAD_INITIALIZER(Screenq); +struct conf Conf; +volatile sig_atomic_t cwm_status; + +__dead void usage(void); +static void sighdlr(int); +static int x_errorhandler(Display *, XErrorEvent *); +static int x_init(const char *); +static void x_teardown(void); +static int x_wmerrorhandler(Display *, XErrorEvent *); + +int +main(int argc, char **argv) +{ + char *display_name = NULL; + char *fallback; + int ch, xfd, nflag = 0; + struct pollfd pfd[1]; + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + warnx("no locale support"); + mbtowc(NULL, NULL, MB_CUR_MAX); + + conf_init(&Conf); + + fallback = u_argv(argv); + Conf.wm_argv = u_argv(argv); + while ((ch = getopt(argc, argv, "c:d:nv")) != -1) { + switch (ch) { + case 'c': + free(Conf.conf_file); + Conf.conf_file = xstrdup(optarg); + break; + case 'd': + display_name = optarg; + break; + case 'n': + nflag = 1; + break; + case 'v': + Conf.debug++; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (signal(SIGCHLD, sighdlr) == SIG_ERR || + signal(SIGHUP, sighdlr) == SIG_ERR || + signal(SIGINT, sighdlr) == SIG_ERR || + signal(SIGTERM, sighdlr) == SIG_ERR) + err(1, "signal"); + + if (parse_config(Conf.conf_file, &Conf) == -1) { + warnx("error parsing config file"); + if (nflag) + return 1; + } + if (nflag) + return 0; + + xfd = x_init(display_name); + cwm_status = CWM_RUNNING; + + if (pledge("stdio rpath proc exec", NULL) == -1) + err(1, "pledge"); + + memset(&pfd, 0, sizeof(pfd)); + pfd[0].fd = xfd; + pfd[0].events = POLLIN; + while (cwm_status == CWM_RUNNING) { + xev_process(); + if (poll(pfd, 1, INFTIM) == -1) { + if (errno != EINTR) + warn("poll"); + } + } + x_teardown(); + if (cwm_status == CWM_EXEC_WM) { + u_exec(Conf.wm_argv); + warnx("'%s' failed to start, starting fallback", Conf.wm_argv); + u_exec(fallback); + } + + return 0; +} + +static int +x_init(const char *dpyname) +{ + int i; + + if ((X_Dpy = XOpenDisplay(dpyname)) == NULL) + errx(1, "unable to open display \"%s\"", XDisplayName(dpyname)); + + XSetErrorHandler(x_wmerrorhandler); + XSelectInput(X_Dpy, DefaultRootWindow(X_Dpy), SubstructureRedirectMask); + XSync(X_Dpy, False); + XSetErrorHandler(x_errorhandler); + + Conf.xrandr = XRRQueryExtension(X_Dpy, &Conf.xrandr_event_base, &i); + + xu_atom_init(); + conf_cursor(&Conf); + + for (i = 0; i < ScreenCount(X_Dpy); i++) + screen_init(i); + + return ConnectionNumber(X_Dpy); +} + +static void +x_teardown(void) +{ + struct screen_ctx *sc; + unsigned int i; + + conf_clear(&Conf); + + TAILQ_FOREACH(sc, &Screenq, entry) { + for (i = 0; i < CWM_COLOR_NITEMS; i++) + XftColorFree(X_Dpy, DefaultVisual(X_Dpy, sc->which), + DefaultColormap(X_Dpy, sc->which), + &sc->xftcolor[i]); + XftFontClose(X_Dpy, sc->xftfont); + XUngrabKey(X_Dpy, AnyKey, AnyModifier, sc->rootwin); + } + XUngrabPointer(X_Dpy, CurrentTime); + XUngrabKeyboard(X_Dpy, CurrentTime); + for (i = 0; i < CF_NITEMS; i++) + XFreeCursor(X_Dpy, Conf.cursor[i]); + XSync(X_Dpy, False); + XSetInputFocus(X_Dpy, PointerRoot, RevertToPointerRoot, CurrentTime); + XCloseDisplay(X_Dpy); +} + +static int +x_wmerrorhandler(Display *dpy, XErrorEvent *e) +{ + errx(1, "root window unavailable - perhaps another wm is running?"); + return 0; +} + +static int +x_errorhandler(Display *dpy, XErrorEvent *e) +{ +#ifdef DEBUG + char msg[80], number[80], req[80]; + + XGetErrorText(X_Dpy, e->error_code, msg, sizeof(msg)); + (void)snprintf(number, sizeof(number), "%d", e->request_code); + XGetErrorDatabaseText(X_Dpy, "XRequest", number, + "", req, sizeof(req)); + + warnx("%s(0x%x): %s", req, (unsigned int)e->resourceid, msg); +#endif + return 0; +} + +static void +sighdlr(int sig) +{ + pid_t pid; + int save_errno = errno, status; + + switch (sig) { + case SIGCHLD: + /* Collect dead children. */ + while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) > 0 || + (pid < 0 && errno == EINTR)) + ; + break; + case SIGHUP: + cwm_status = CWM_EXEC_WM; + break; + case SIGINT: + case SIGTERM: + cwm_status = CWM_QUIT; + break; + } + + errno = save_errno; +} + +__dead void +usage(void) +{ + extern char *__progname; + + (void)fprintf(stderr, "usage: %s [-nv] [-c file] [-d display]\n", + __progname); + exit(1); +} diff --git a/calmwm.h b/calmwm.h new file mode 100644 index 0000000..44fcccd --- /dev/null +++ b/calmwm.h @@ -0,0 +1,611 @@ +/* + * calmwm - the calm window manager + * + * Copyright (c) 2004 Marius Aamodt Eriksen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $OpenBSD: calmwm.h,v 1.380 2025/08/20 23:44:06 job Exp $ + */ + +#ifndef _CALMWM_H_ +#define _CALMWM_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_DEBUG0(...) log_debug(0, __func__, __VA_ARGS__) +#define LOG_DEBUG1(...) log_debug(1, __func__, __VA_ARGS__) +#define LOG_DEBUG2(...) log_debug(2, __func__, __VA_ARGS__) +#define LOG_DEBUG3(...) log_debug(3, __func__, __VA_ARGS__) + +#undef MIN +#undef MAX +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#define MAX(x, y) ((x) > (y) ? (x) : (y)) + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +#define BUTTONMASK (ButtonPressMask | ButtonReleaseMask) +#define MOUSEMASK (BUTTONMASK | PointerMotionMask) +#define IGNOREMODMASK (LockMask | Mod2Mask | 0x2000) + +/* direction/amount */ +#define CWM_UP 0x0001 +#define CWM_DOWN 0x0002 +#define CWM_LEFT 0x0004 +#define CWM_RIGHT 0x0008 +#define CWM_BIGAMOUNT 0x0010 +#define CWM_CENTER 0x0020 +#define CWM_UP_BIG (CWM_UP | CWM_BIGAMOUNT) +#define CWM_DOWN_BIG (CWM_DOWN | CWM_BIGAMOUNT) +#define CWM_LEFT_BIG (CWM_LEFT | CWM_BIGAMOUNT) +#define CWM_RIGHT_BIG (CWM_RIGHT | CWM_BIGAMOUNT) +#define CWM_UP_RIGHT (CWM_UP | CWM_RIGHT) +#define CWM_UP_LEFT (CWM_UP | CWM_LEFT) +#define CWM_DOWN_RIGHT (CWM_DOWN | CWM_RIGHT) +#define CWM_DOWN_LEFT (CWM_DOWN | CWM_LEFT) + +#define CWM_CYCLE_FORWARD 0x0001 +#define CWM_CYCLE_REVERSE 0x0002 +#define CWM_CYCLE_INGROUP 0x0004 +#define CWM_CYCLE_INCLASS 0x0008 + +enum cwm_status { + CWM_QUIT, + CWM_RUNNING, + CWM_EXEC_WM +}; +enum cursor_font { + CF_NORMAL, + CF_MOVE, + CF_RESIZE, + CF_QUESTION, + CF_NITEMS +}; +enum color { + CWM_COLOR_BORDER_ACTIVE, + CWM_COLOR_BORDER_INACTIVE, + CWM_COLOR_BORDER_URGENCY, + CWM_COLOR_BORDER_GROUP, + CWM_COLOR_BORDER_UNGROUP, + CWM_COLOR_MENU_FG, + CWM_COLOR_MENU_BG, + CWM_COLOR_MENU_FONT, + CWM_COLOR_MENU_FONT_SEL, + CWM_COLOR_NITEMS +}; + +struct geom { + int x; + int y; + int w; + int h; +}; +struct gap { + int top; + int bottom; + int left; + int right; +}; + +struct winname { + TAILQ_ENTRY(winname) entry; + char *name; +}; +TAILQ_HEAD(name_q, winname); +TAILQ_HEAD(ignore_q, winname); + +struct client_ctx { + TAILQ_ENTRY(client_ctx) entry; + struct screen_ctx *sc; + struct group_ctx *gc; + Window win; + Colormap colormap; + int bwidth; /* border width */ + int obwidth; /* original border width */ + struct geom geom, savegeom, fullgeom; + struct { + long flags; /* defined hints */ + int basew; /* desired width */ + int baseh; /* desired height */ + int minw; /* minimum width */ + int minh; /* minimum height */ + int maxw; /* maximum width */ + int maxh; /* maximum height */ + int incw; /* width increment progression */ + int inch; /* height increment progression */ + float mina; /* minimum aspect ratio */ + float maxa; /* maximum aspect ratio */ + } hint; + struct { + int x; /* x position */ + int y; /* y position */ + } ptr; + struct { + int h; /* height */ + int w; /* width */ + } dim; +#define CLIENT_HIDDEN 0x0001 +#define CLIENT_IGNORE 0x0002 +#define CLIENT_VMAXIMIZED 0x0004 +#define CLIENT_HMAXIMIZED 0x0008 +#define CLIENT_FREEZE 0x0010 +#define CLIENT_GROUP 0x0020 +#define CLIENT_UNGROUP 0x0040 +#define CLIENT_INPUT 0x0080 +#define CLIENT_WM_DELETE_WINDOW 0x0100 +#define CLIENT_WM_TAKE_FOCUS 0x0200 +#define CLIENT_URGENCY 0x0400 +#define CLIENT_FULLSCREEN 0x0800 +#define CLIENT_STICKY 0x1000 +#define CLIENT_ACTIVE 0x2000 +#define CLIENT_SKIP_PAGER 0x4000 +#define CLIENT_SKIP_TASKBAR 0x8000 + +#define CLIENT_SKIP_CYCLE (CLIENT_HIDDEN | CLIENT_IGNORE | \ + CLIENT_SKIP_TASKBAR | CLIENT_SKIP_PAGER) +#define CLIENT_HIGHLIGHT (CLIENT_GROUP | CLIENT_UNGROUP) +#define CLIENT_MAXFLAGS (CLIENT_VMAXIMIZED | CLIENT_HMAXIMIZED) +#define CLIENT_MAXIMIZED (CLIENT_VMAXIMIZED | CLIENT_HMAXIMIZED) + int flags; + int stackingorder; + struct name_q nameq; + char *name; + char *label; + char *res_class; /* class hint */ + char *res_name; /* class hint */ + int initial_state; /* wm hint */ +}; +TAILQ_HEAD(client_q, client_ctx); + +struct group_ctx { + TAILQ_ENTRY(group_ctx) entry; + struct screen_ctx *sc; + char *name; + int num; +}; +TAILQ_HEAD(group_q, group_ctx); + +struct autogroup { + TAILQ_ENTRY(autogroup) entry; + char *class; + char *name; + int num; +}; +TAILQ_HEAD(autogroup_q, autogroup); + +struct region_ctx { + TAILQ_ENTRY(region_ctx) entry; + int num; + struct geom view; /* viewable area */ + struct geom work; /* workable area, gap-applied */ +}; +TAILQ_HEAD(region_q, region_ctx); + +struct screen_ctx { + TAILQ_ENTRY(screen_ctx) entry; + int which; + Window rootwin; + int cycling; + int hideall; + int snapdist; + struct geom view; /* viewable area */ + struct geom work; /* workable area, gap-applied */ + struct gap gap; + struct client_q clientq; + struct region_q regionq; + struct group_q groupq; + struct group_ctx *group_active; + struct group_ctx *group_last; + Colormap colormap; + Visual *visual; + struct { + Window win; + XftDraw *xftdraw; + } prop; + XftColor xftcolor[CWM_COLOR_NITEMS]; + XftFont *xftfont; +}; +TAILQ_HEAD(screen_q, screen_ctx); + +struct cargs { + char *cmd; + int flag; + enum { + CWM_XEV_KEY, + CWM_XEV_BTN + } xev; +}; +enum context { + CWM_CONTEXT_NONE = 0, + CWM_CONTEXT_CC, + CWM_CONTEXT_SC +}; +struct bind_ctx { + TAILQ_ENTRY(bind_ctx) entry; + void (*callback)(void *, struct cargs *); + struct cargs *cargs; + enum context context; + unsigned int modmask; + union { + KeySym keysym; + unsigned int button; + } press; +}; +TAILQ_HEAD(keybind_q, bind_ctx); +TAILQ_HEAD(mousebind_q, bind_ctx); + +struct cmd_ctx { + TAILQ_ENTRY(cmd_ctx) entry; + char *name; + char *path; +}; +TAILQ_HEAD(cmd_q, cmd_ctx); +TAILQ_HEAD(wm_q, cmd_ctx); + +#define CWM_MENU_DUMMY 0x0001 +#define CWM_MENU_FILE 0x0002 +#define CWM_MENU_LIST 0x0004 +#define CWM_MENU_WINDOW_ALL 0x0008 +#define CWM_MENU_WINDOW_HIDDEN 0x0010 + +struct menu { + TAILQ_ENTRY(menu) entry; + TAILQ_ENTRY(menu) resultentry; +#define MENU_MAXENTRY 200 + char text[MENU_MAXENTRY + 1]; + char print[MENU_MAXENTRY + 1]; + void *ctx; + short dummy; + short abort; +}; +TAILQ_HEAD(menu_q, menu); + +struct conf { + struct keybind_q keybindq; + struct mousebind_q mousebindq; + struct autogroup_q autogroupq; + struct ignore_q ignoreq; + struct cmd_q cmdq; + struct wm_q wmq; + int ngroups; + int stickygroups; + int nameqlen; + int bwidth; + int mamount; + int snapdist; + int htile; + int vtile; + struct gap gap; + char *color[CWM_COLOR_NITEMS]; + char *font; + char *wmname; + Cursor cursor[CF_NITEMS]; + int xrandr; + int xrandr_event_base; + char *conf_file; + char *known_hosts; + char *wm_argv; + int debug; +}; + +/* MWM hints */ +struct mwm_hints { +#define MWM_HINTS_ELEMENTS 5L + +#define MWM_HINTS_FUNCTIONS (1L << 0) +#define MWM_HINTS_DECORATIONS (1L << 1) +#define MWM_HINTS_INPUT_MODE (1L << 2) +#define MWM_HINTS_STATUS (1L << 3) + unsigned long flags; + +#define MWM_FUNC_ALL (1L << 0) +#define MWM_FUNC_RESIZE (1L << 1) +#define MWM_FUNC_MOVE (1L << 2) +#define MWM_FUNC_MINIMIZE (1L << 3) +#define MWM_FUNC_MAXIMIZE (1L << 4) +#define MWM_FUNC_CLOSE (1L << 5) + unsigned long functions; + +#define MWM_DECOR_ALL (1L << 0) +#define MWM_DECOR_BORDER (1L << 1) +#define MWM_DECOR_RESIZEH (1L << 2) +#define MWM_DECOR_TITLE (1L << 3) +#define MWM_DECOR_MENU (1L << 4) +#define MWM_DECOR_MINIMIZE (1L << 5) +#define MWM_DECOR_MAXIMIZE (1L << 6) + unsigned long decorations; + +#define MWM_INPUT_MODELESS 0 +#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1 +#define MWM_INPUT_SYSTEM_MODAL 2 +#define MWM_INPUT_FULL_APPLICATION_MODAL 3 + long inputMode; + +#define MWM_TEAROFF_WINDOW (1L << 0) + unsigned long status; +}; + +enum cwmh { + WM_STATE, + WM_DELETE_WINDOW, + WM_TAKE_FOCUS, + WM_PROTOCOLS, + _MOTIF_WM_HINTS, + UTF8_STRING, + WM_CHANGE_STATE, + CWMH_NITEMS +}; +enum ewmh { + _NET_SUPPORTED, + _NET_SUPPORTING_WM_CHECK, + _NET_ACTIVE_WINDOW, + _NET_CLIENT_LIST, + _NET_CLIENT_LIST_STACKING, + _NET_NUMBER_OF_DESKTOPS, + _NET_CURRENT_DESKTOP, + _NET_DESKTOP_VIEWPORT, + _NET_DESKTOP_GEOMETRY, + _NET_VIRTUAL_ROOTS, + _NET_SHOWING_DESKTOP, + _NET_DESKTOP_NAMES, + _NET_WORKAREA, + _NET_WM_NAME, + _NET_WM_DESKTOP, + _NET_CLOSE_WINDOW, + _NET_WM_STATE, +#define _NET_WM_STATES_NITEMS 9 + _NET_WM_STATE_STICKY, + _NET_WM_STATE_MAXIMIZED_VERT, + _NET_WM_STATE_MAXIMIZED_HORZ, + _NET_WM_STATE_HIDDEN, + _NET_WM_STATE_FULLSCREEN, + _NET_WM_STATE_DEMANDS_ATTENTION, + _NET_WM_STATE_SKIP_PAGER, + _NET_WM_STATE_SKIP_TASKBAR, + _CWM_WM_STATE_FREEZE, + EWMH_NITEMS +}; +enum net_wm_state { + _NET_WM_STATE_REMOVE, + _NET_WM_STATE_ADD, + _NET_WM_STATE_TOGGLE +}; + +extern Display *X_Dpy; +extern Time Last_Event_Time; +extern Atom cwmh[CWMH_NITEMS]; +extern Atom ewmh[EWMH_NITEMS]; +extern struct screen_q Screenq; +extern struct conf Conf; + +void client_apply_sizehints(struct client_ctx *); +void client_close(struct client_ctx *); +void client_config(struct client_ctx *); +struct client_ctx *client_current(struct screen_ctx *); +void client_draw_border(struct client_ctx *); +struct client_ctx *client_find(Window); +void client_get_sizehints(struct client_ctx *); +void client_hide(struct client_ctx *); +void client_htile(struct client_ctx *); +int client_inbound(struct client_ctx *, int, int); +struct client_ctx *client_init(Window, struct screen_ctx *); +void client_lower(struct client_ctx *); +void client_move(struct client_ctx *); +void client_mtf(struct client_ctx *); +struct client_ctx *client_next(struct client_ctx *); +struct client_ctx *client_prev(struct client_ctx *); +void client_ptr_inbound(struct client_ctx *, int); +void client_ptr_save(struct client_ctx *); +void client_ptr_warp(struct client_ctx *); +void client_raise(struct client_ctx *); +void client_remove(struct client_ctx *); +void client_resize(struct client_ctx *, int); +void client_set_active(struct client_ctx *); +void client_set_name(struct client_ctx *); +void client_show(struct client_ctx *); +int client_snapcalc(int, int, int, int, int); +void client_toggle_hidden(struct client_ctx *); +void client_toggle_hmaximize(struct client_ctx *); +void client_toggle_fullscreen(struct client_ctx *); +void client_toggle_freeze(struct client_ctx *); +void client_toggle_maximize(struct client_ctx *); +void client_toggle_skip_pager(struct client_ctx *); +void client_toggle_skip_taskbar(struct client_ctx *); +void client_toggle_sticky(struct client_ctx *); +void client_toggle_vmaximize(struct client_ctx *); +void client_transient(struct client_ctx *); +void client_urgency(struct client_ctx *); +void client_vtile(struct client_ctx *); +void client_wm_hints(struct client_ctx *); + +void group_assign(struct group_ctx *, struct client_ctx *); +int group_autogroup(struct client_ctx *); +void group_cycle(struct screen_ctx *, int); +void group_hide(struct group_ctx *); +int group_holds_only_hidden(struct group_ctx *); +int group_holds_only_sticky(struct group_ctx *); +void group_init(struct screen_ctx *, int, const char *); +void group_movetogroup(struct client_ctx *, int); +void group_only(struct screen_ctx *, int); +void group_close(struct screen_ctx *, int); +int group_restore(struct client_ctx *); +void group_show(struct group_ctx *); +void group_toggle(struct screen_ctx *, int); +void group_toggle_all(struct screen_ctx *); +void group_toggle_membership(struct client_ctx *); +void group_update_names(struct screen_ctx *); + +void search_match_client(struct menu_q *, struct menu_q *, + char *); +void search_match_cmd(struct menu_q *, struct menu_q *, + char *); +void search_match_exec(struct menu_q *, struct menu_q *, + char *); +void search_match_group(struct menu_q *, struct menu_q *, + char *); +void search_match_path(struct menu_q *, struct menu_q *, + char *); +void search_match_text(struct menu_q *, struct menu_q *, + char *); +void search_match_wm(struct menu_q *, struct menu_q *, + char *); +void search_print_client(struct menu *, int); +void search_print_cmd(struct menu *, int); +void search_print_group(struct menu *, int); +void search_print_text(struct menu *, int); +void search_print_wm(struct menu *, int); + +struct region_ctx *region_find(struct screen_ctx *, int, int); +void screen_assert_clients_within(struct screen_ctx *); +struct geom screen_area(struct screen_ctx *, int, int, int); +struct screen_ctx *screen_find(Window); +void screen_init(int); +void screen_prop_win_create(struct screen_ctx *, Window); +void screen_prop_win_destroy(struct screen_ctx *); +void screen_prop_win_draw(struct screen_ctx *, + const char *, ...) + __attribute__((__format__ (printf, 2, 3))) + __attribute__((__nonnull__ (2))); +void screen_update_geometry(struct screen_ctx *); +void screen_updatestackingorder(struct screen_ctx *); + +void kbfunc_cwm_status(void *, struct cargs *); +void kbfunc_ptrmove(void *, struct cargs *); +void kbfunc_client_snap(void *, struct cargs *); +void kbfunc_client_move(void *, struct cargs *); +void kbfunc_client_resize(void *, struct cargs *); +void kbfunc_client_close(void *, struct cargs *); +void kbfunc_client_lower(void *, struct cargs *); +void kbfunc_client_raise(void *, struct cargs *); +void kbfunc_client_hide(void *, struct cargs *); +void kbfunc_client_toggle_freeze(void *, struct cargs *); +void kbfunc_client_toggle_sticky(void *, struct cargs *); +void kbfunc_client_toggle_fullscreen(void *, + struct cargs *); +void kbfunc_client_toggle_maximize(void *, struct cargs *); +void kbfunc_client_toggle_hmaximize(void *, struct cargs *); +void kbfunc_client_toggle_vmaximize(void *, struct cargs *); +void kbfunc_client_htile(void *, struct cargs *); +void kbfunc_client_vtile(void *, struct cargs *); +void kbfunc_client_cycle(void *, struct cargs *); +void kbfunc_client_toggle_group(void *, struct cargs *); +void kbfunc_client_movetogroup(void *, struct cargs *); +void kbfunc_group_toggle(void *, struct cargs *); +void kbfunc_group_only(void *, struct cargs *); +void kbfunc_group_last(void *, struct cargs *); +void kbfunc_group_close(void *, struct cargs *); +void kbfunc_group_cycle(void *, struct cargs *); +void kbfunc_group_toggle_all(void *, struct cargs *); +void kbfunc_menu_client(void *, struct cargs *); +void kbfunc_menu_cmd(void *, struct cargs *); +void kbfunc_menu_group(void *, struct cargs *); +void kbfunc_menu_wm(void *, struct cargs *); +void kbfunc_menu_exec(void *, struct cargs *); +void kbfunc_menu_ssh(void *, struct cargs *); +void kbfunc_client_menu_label(void *, struct cargs *); +void kbfunc_exec_cmd(void *, struct cargs *); +void kbfunc_exec_lock(void *, struct cargs *); +void kbfunc_exec_term(void *, struct cargs *); + +struct menu *menu_filter(struct screen_ctx *, struct menu_q *, + const char *, const char *, int, + void (*)(struct menu_q *, struct menu_q *, char *), + void (*)(struct menu *, int)); +void menuq_add(struct menu_q *, void *, const char *, ...) + __attribute__((__format__ (printf, 3, 4))); +void menuq_clear(struct menu_q *); + +int parse_config(const char *, struct conf *); + +void conf_autogroup(struct conf *, int, const char *, + const char *); +int conf_bind_key(struct conf *, const char *, + const char *); +int conf_bind_mouse(struct conf *, const char *, + const char *); +void conf_clear(struct conf *); +void conf_client(struct client_ctx *); +void conf_cmd_add(struct conf *, const char *, + const char *); +void conf_wm_add(struct conf *, const char *, + const char *); +void conf_cursor(struct conf *); +void conf_grab_kbd(Window); +void conf_grab_mouse(Window); +void conf_init(struct conf *); +void conf_ignore(struct conf *, const char *); +void conf_screen(struct screen_ctx *); +void conf_group(struct screen_ctx *); + +void xev_process(void); + +int xu_get_prop(Window, Atom, Atom, long, unsigned char **); +int xu_get_strprop(Window, Atom, char **); +void xu_ptr_get(Window, int *, int *); +void xu_ptr_set(Window, int, int); +void xu_get_wm_state(Window, long *); +void xu_set_wm_state(Window, long); +void xu_send_clientmsg(Window, Atom, Time); +void xu_xorcolor(XftColor, XftColor, XftColor *); + +void xu_atom_init(void); +void xu_ewmh_net_supported(struct screen_ctx *); +void xu_ewmh_net_supported_wm_check(struct screen_ctx *); +void xu_ewmh_net_desktop_geometry(struct screen_ctx *); +void xu_ewmh_net_desktop_viewport(struct screen_ctx *); +void xu_ewmh_net_workarea(struct screen_ctx *); +void xu_ewmh_net_client_list(struct screen_ctx *); +void xu_ewmh_net_client_list_stacking(struct screen_ctx *); +void xu_ewmh_net_active_window(struct screen_ctx *, Window); +void xu_ewmh_net_number_of_desktops(struct screen_ctx *); +void xu_ewmh_net_showing_desktop(struct screen_ctx *); +void xu_ewmh_net_virtual_roots(struct screen_ctx *); +void xu_ewmh_net_current_desktop(struct screen_ctx *); +void xu_ewmh_net_desktop_names(struct screen_ctx *); +int xu_ewmh_get_net_wm_desktop(struct client_ctx *, long *); +void xu_ewmh_set_net_wm_desktop(struct client_ctx *); +Atom *xu_ewmh_get_net_wm_state(struct client_ctx *, int *); +void xu_ewmh_handle_net_wm_state_msg(struct client_ctx *, + int, Atom, Atom); +void xu_ewmh_set_net_wm_state(struct client_ctx *); +void xu_ewmh_restore_net_wm_state(struct client_ctx *); + +char *u_argv(char * const *); +void u_exec(char *); +void u_spawn(char *); +void log_debug(int, const char *, const char *, ...) + __attribute__((__format__ (printf, 3, 4))) + __attribute__((__nonnull__ (3))); + +void *xcalloc(size_t, size_t); +void *xmalloc(size_t); +void *xreallocarray(void *, size_t, size_t); +char *xstrdup(const char *); +int xasprintf(char **, const char *, ...) + __attribute__((__format__ (printf, 2, 3))) + __attribute__((__nonnull__ (2))); +int xvasprintf(char **, const char *, va_list) + __attribute__((__nonnull__ (2))); + +#endif /* _CALMWM_H_ */ diff --git a/client.c b/client.c new file mode 100644 index 0000000..955b36a --- /dev/null +++ b/client.c @@ -0,0 +1,1049 @@ +/* + * calmwm - the calm window manager + * + * Copyright (c) 2004 Marius Aamodt Eriksen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $OpenBSD: client.c,v 1.267 2023/03/22 08:27:36 op Exp $ + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "calmwm.h" + +static void client_class_hint(struct client_ctx *); +static void client_placement(struct client_ctx *); +static void client_mwm_hints(struct client_ctx *); +static void client_wm_protocols(struct client_ctx *); + +struct client_ctx * +client_init(Window win, struct screen_ctx *sc) +{ + struct client_ctx *cc; + XWindowAttributes wattr; + int mapped; + long state; + + if (win == None) + return NULL; + if (!XGetWindowAttributes(X_Dpy, win, &wattr)) + return NULL; + + if (sc == NULL) { + if ((sc = screen_find(wattr.root)) == NULL) + return NULL; + mapped = 1; + } else { + if (wattr.override_redirect || wattr.map_state != IsViewable) + return NULL; + mapped = wattr.map_state != IsUnmapped; + } + + XGrabServer(X_Dpy); + + cc = xmalloc(sizeof(*cc)); + cc->sc = sc; + cc->win = win; + cc->name = NULL; + cc->label = NULL; + cc->gc = NULL; + cc->res_class = NULL; + cc->res_name = NULL; + cc->flags = 0; + cc->stackingorder = 0; + cc->initial_state = 0; + memset(&cc->hint, 0, sizeof(cc->hint)); + TAILQ_INIT(&cc->nameq); + + cc->geom.x = wattr.x; + cc->geom.y = wattr.y; + cc->geom.w = wattr.width; + cc->geom.h = wattr.height; + cc->colormap = wattr.colormap; + cc->obwidth = wattr.border_width; + cc->bwidth = Conf.bwidth; + + client_set_name(cc); + conf_client(cc); + + client_wm_hints(cc); + client_class_hint(cc); + client_wm_protocols(cc); + client_get_sizehints(cc); + client_transient(cc); + client_mwm_hints(cc); + + if ((cc->flags & CLIENT_IGNORE)) + cc->bwidth = 0; + cc->dim.w = (cc->geom.w - cc->hint.basew) / cc->hint.incw; + cc->dim.h = (cc->geom.h - cc->hint.baseh) / cc->hint.inch; + cc->ptr.x = cc->geom.w / 2; + cc->ptr.y = cc->geom.h / 2; + + if (wattr.map_state != IsViewable) { + client_placement(cc); + client_resize(cc, 0); + if (cc->initial_state) + xu_set_wm_state(cc->win, cc->initial_state); + } + + XSelectInput(X_Dpy, cc->win, + EnterWindowMask | PropertyChangeMask | KeyReleaseMask); + + XAddToSaveSet(X_Dpy, cc->win); + + /* Notify client of its configuration. */ + client_config(cc); + + TAILQ_INSERT_TAIL(&sc->clientq, cc, entry); + + xu_ewmh_net_client_list(sc); + xu_ewmh_net_client_list_stacking(sc); + xu_ewmh_restore_net_wm_state(cc); + + xu_get_wm_state(cc->win, &state); + if (state == IconicState) + client_hide(cc); + else + client_show(cc); + + if (mapped) { + if (cc->gc) { + group_movetogroup(cc, cc->gc->num); + goto out; + } + if (group_restore(cc)) + goto out; + if (group_autogroup(cc)) + goto out; + if (Conf.stickygroups) + group_assign(sc->group_active, cc); + else + group_assign(NULL, cc); + } +out: + XSync(X_Dpy, False); + XUngrabServer(X_Dpy); + + return cc; +} + +struct client_ctx * +client_current(struct screen_ctx *sc) +{ + struct screen_ctx *_sc; + struct client_ctx *cc; + + if (sc) { + TAILQ_FOREACH(cc, &sc->clientq, entry) { + if (cc->flags & CLIENT_ACTIVE) + return cc; + } + } else { + TAILQ_FOREACH(_sc, &Screenq, entry) { + TAILQ_FOREACH(cc, &_sc->clientq, entry) { + if (cc->flags & CLIENT_ACTIVE) + return cc; + } + } + } + return NULL; +} + +struct client_ctx * +client_find(Window win) +{ + struct screen_ctx *sc; + struct client_ctx *cc; + + TAILQ_FOREACH(sc, &Screenq, entry) { + TAILQ_FOREACH(cc, &sc->clientq, entry) { + if (cc->win == win) + return cc; + } + } + return NULL; +} + +struct client_ctx * +client_next(struct client_ctx *cc) +{ + struct screen_ctx *sc = cc->sc; + struct client_ctx *newcc; + + return(((newcc = TAILQ_NEXT(cc, entry)) != NULL) ? + newcc : TAILQ_FIRST(&sc->clientq)); +} + +struct client_ctx * +client_prev(struct client_ctx *cc) +{ + struct screen_ctx *sc = cc->sc; + struct client_ctx *newcc; + + return(((newcc = TAILQ_PREV(cc, client_q, entry)) != NULL) ? + newcc : TAILQ_LAST(&sc->clientq, client_q)); +} + +void +client_remove(struct client_ctx *cc) +{ + struct screen_ctx *sc = cc->sc; + struct winname *wn; + + TAILQ_REMOVE(&sc->clientq, cc, entry); + + xu_ewmh_net_client_list(sc); + xu_ewmh_net_client_list_stacking(sc); + + if (cc->flags & CLIENT_ACTIVE) + xu_ewmh_net_active_window(sc, None); + + while ((wn = TAILQ_FIRST(&cc->nameq)) != NULL) { + TAILQ_REMOVE(&cc->nameq, wn, entry); + free(wn->name); + free(wn); + } + + free(cc->name); + free(cc->label); + free(cc->res_class); + free(cc->res_name); + free(cc); +} + +void +client_set_active(struct client_ctx *cc) +{ + struct screen_ctx *sc = cc->sc; + struct client_ctx *oldcc; + + if (cc->flags & CLIENT_HIDDEN) + return; + + XInstallColormap(X_Dpy, cc->colormap); + + if ((cc->flags & CLIENT_INPUT) || + (!(cc->flags & CLIENT_WM_TAKE_FOCUS))) { + XSetInputFocus(X_Dpy, cc->win, + RevertToPointerRoot, CurrentTime); + } + if (cc->flags & CLIENT_WM_TAKE_FOCUS) + xu_send_clientmsg(cc->win, cwmh[WM_TAKE_FOCUS], Last_Event_Time); + + if ((oldcc = client_current(sc)) != NULL) { + oldcc->flags &= ~CLIENT_ACTIVE; + client_draw_border(oldcc); + } + + /* If we're in the middle of cycling, don't change the order. */ + if (!sc->cycling) + client_mtf(cc); + + cc->flags |= CLIENT_ACTIVE; + cc->flags &= ~CLIENT_URGENCY; + client_draw_border(cc); + conf_grab_mouse(cc->win); + xu_ewmh_net_active_window(sc, cc->win); +} + +void +client_toggle_freeze(struct client_ctx *cc) +{ + if (cc->flags & CLIENT_FULLSCREEN) + return; + + cc->flags ^= CLIENT_FREEZE; + xu_ewmh_set_net_wm_state(cc); +} + +void +client_toggle_hidden(struct client_ctx *cc) +{ + cc->flags ^= CLIENT_HIDDEN; + xu_ewmh_set_net_wm_state(cc); +} + +void +client_toggle_skip_pager(struct client_ctx *cc) +{ + cc->flags ^= CLIENT_SKIP_PAGER; + xu_ewmh_set_net_wm_state(cc); +} + +void +client_toggle_skip_taskbar(struct client_ctx *cc) +{ + cc->flags ^= CLIENT_SKIP_TASKBAR; + xu_ewmh_set_net_wm_state(cc); +} + +void +client_toggle_sticky(struct client_ctx *cc) +{ + cc->flags ^= CLIENT_STICKY; + xu_ewmh_set_net_wm_state(cc); +} + +void +client_toggle_fullscreen(struct client_ctx *cc) +{ + struct screen_ctx *sc = cc->sc; + struct geom area; + + if ((cc->flags & CLIENT_FREEZE) && + !(cc->flags & CLIENT_FULLSCREEN)) + return; + + if (cc->flags & CLIENT_FULLSCREEN) { + if (!(cc->flags & CLIENT_IGNORE)) + cc->bwidth = Conf.bwidth; + cc->geom = cc->fullgeom; + cc->flags &= ~(CLIENT_FULLSCREEN | CLIENT_FREEZE); + goto resize; + } + + cc->fullgeom = cc->geom; + + area = screen_area(sc, + cc->geom.x + cc->geom.w / 2, + cc->geom.y + cc->geom.h / 2, 0); + + cc->bwidth = 0; + cc->geom = area; + cc->flags |= (CLIENT_FULLSCREEN | CLIENT_FREEZE); + +resize: + client_resize(cc, 0); + xu_ewmh_set_net_wm_state(cc); + client_ptr_inbound(cc, 1); +} + +void +client_toggle_maximize(struct client_ctx *cc) +{ + struct screen_ctx *sc = cc->sc; + struct geom area; + + if (cc->flags & CLIENT_FREEZE) + return; + + if ((cc->flags & CLIENT_MAXFLAGS) == CLIENT_MAXIMIZED) { + cc->geom = cc->savegeom; + cc->flags &= ~CLIENT_MAXIMIZED; + goto resize; + } + + if (!(cc->flags & CLIENT_VMAXIMIZED)) { + cc->savegeom.h = cc->geom.h; + cc->savegeom.y = cc->geom.y; + } + + if (!(cc->flags & CLIENT_HMAXIMIZED)) { + cc->savegeom.w = cc->geom.w; + cc->savegeom.x = cc->geom.x; + } + + area = screen_area(sc, + cc->geom.x + cc->geom.w / 2, + cc->geom.y + cc->geom.h / 2, 1); + + cc->geom.x = area.x; + cc->geom.y = area.y; + cc->geom.w = area.w - (cc->bwidth * 2); + cc->geom.h = area.h - (cc->bwidth * 2); + cc->flags |= CLIENT_MAXIMIZED; + +resize: + client_resize(cc, 0); + xu_ewmh_set_net_wm_state(cc); + client_ptr_inbound(cc, 1); +} + +void +client_toggle_vmaximize(struct client_ctx *cc) +{ + struct screen_ctx *sc = cc->sc; + struct geom area; + + if (cc->flags & CLIENT_FREEZE) + return; + + if (cc->flags & CLIENT_VMAXIMIZED) { + cc->geom.y = cc->savegeom.y; + cc->geom.h = cc->savegeom.h; + cc->flags &= ~CLIENT_VMAXIMIZED; + goto resize; + } + + cc->savegeom.y = cc->geom.y; + cc->savegeom.h = cc->geom.h; + + area = screen_area(sc, + cc->geom.x + cc->geom.w / 2, + cc->geom.y + cc->geom.h / 2, 1); + + cc->geom.y = area.y; + cc->geom.h = area.h - (cc->bwidth * 2); + cc->flags |= CLIENT_VMAXIMIZED; + +resize: + client_resize(cc, 0); + xu_ewmh_set_net_wm_state(cc); + client_ptr_inbound(cc, 1); +} + +void +client_toggle_hmaximize(struct client_ctx *cc) +{ + struct screen_ctx *sc = cc->sc; + struct geom area; + + if (cc->flags & CLIENT_FREEZE) + return; + + if (cc->flags & CLIENT_HMAXIMIZED) { + cc->geom.x = cc->savegeom.x; + cc->geom.w = cc->savegeom.w; + cc->flags &= ~CLIENT_HMAXIMIZED; + goto resize; + } + + cc->savegeom.x = cc->geom.x; + cc->savegeom.w = cc->geom.w; + + area = screen_area(sc, + cc->geom.x + cc->geom.w / 2, + cc->geom.y + cc->geom.h / 2, 1); + + cc->geom.x = area.x; + cc->geom.w = area.w - (cc->bwidth * 2); + cc->flags |= CLIENT_HMAXIMIZED; + +resize: + client_resize(cc, 0); + xu_ewmh_set_net_wm_state(cc); + client_ptr_inbound(cc, 1); +} + +void +client_resize(struct client_ctx *cc, int reset) +{ + if (reset) { + cc->flags &= ~CLIENT_MAXIMIZED; + xu_ewmh_set_net_wm_state(cc); + } + + client_draw_border(cc); + + XMoveResizeWindow(X_Dpy, cc->win, cc->geom.x, + cc->geom.y, cc->geom.w, cc->geom.h); + cc->dim.w = (cc->geom.w - cc->hint.basew) / cc->hint.incw; + cc->dim.h = (cc->geom.h - cc->hint.baseh) / cc->hint.inch; + client_config(cc); +} + +void +client_move(struct client_ctx *cc) +{ + XMoveWindow(X_Dpy, cc->win, cc->geom.x, cc->geom.y); + client_config(cc); +} + +void +client_lower(struct client_ctx *cc) +{ + XLowerWindow(X_Dpy, cc->win); +} + +void +client_raise(struct client_ctx *cc) +{ + XRaiseWindow(X_Dpy, cc->win); +} + +void +client_config(struct client_ctx *cc) +{ + XConfigureEvent cn; + + (void)memset(&cn, 0, sizeof(cn)); + cn.type = ConfigureNotify; + cn.event = cc->win; + cn.window = cc->win; + cn.x = cc->geom.x; + cn.y = cc->geom.y; + cn.width = cc->geom.w; + cn.height = cc->geom.h; + cn.border_width = cc->bwidth; + cn.above = None; + cn.override_redirect = 0; + + XSendEvent(X_Dpy, cc->win, False, StructureNotifyMask, (XEvent *)&cn); +} + +void +client_ptr_inbound(struct client_ctx *cc, int getpos) +{ + if (getpos) + xu_ptr_get(cc->win, &cc->ptr.x, &cc->ptr.y); + + if (cc->ptr.x < 0) + cc->ptr.x = 0; + else if (cc->ptr.x > cc->geom.w - 1) + cc->ptr.x = cc->geom.w - 1; + if (cc->ptr.y < 0) + cc->ptr.y = 0; + else if (cc->ptr.y > cc->geom.h - 1) + cc->ptr.y = cc->geom.h - 1; + + client_ptr_warp(cc); +} + +void +client_ptr_warp(struct client_ctx *cc) +{ + xu_ptr_set(cc->win, cc->ptr.x, cc->ptr.y); +} + +void +client_ptr_save(struct client_ctx *cc) +{ + int x, y; + + xu_ptr_get(cc->win, &x, &y); + if (client_inbound(cc, x, y)) { + cc->ptr.x = x; + cc->ptr.y = y; + } else { + cc->ptr.x = cc->geom.w / 2; + cc->ptr.y = cc->geom.h / 2; + } +} + +void +client_hide(struct client_ctx *cc) +{ + XUnmapWindow(X_Dpy, cc->win); + + if (cc->flags & CLIENT_ACTIVE) { + cc->flags &= ~CLIENT_ACTIVE; + xu_ewmh_net_active_window(cc->sc, None); + } + cc->flags |= CLIENT_HIDDEN; + xu_set_wm_state(cc->win, IconicState); +} + +void +client_show(struct client_ctx *cc) +{ + XMapRaised(X_Dpy, cc->win); + + cc->flags &= ~CLIENT_HIDDEN; + xu_set_wm_state(cc->win, NormalState); + client_draw_border(cc); +} + +void +client_urgency(struct client_ctx *cc) +{ + if (!(cc->flags & CLIENT_ACTIVE)) + cc->flags |= CLIENT_URGENCY; +} + +void +client_draw_border(struct client_ctx *cc) +{ + struct screen_ctx *sc = cc->sc; + unsigned long pixel; + + if (cc->flags & CLIENT_ACTIVE) + switch (cc->flags & CLIENT_HIGHLIGHT) { + case CLIENT_GROUP: + pixel = sc->xftcolor[CWM_COLOR_BORDER_GROUP].pixel; + break; + case CLIENT_UNGROUP: + pixel = sc->xftcolor[CWM_COLOR_BORDER_UNGROUP].pixel; + break; + default: + pixel = sc->xftcolor[CWM_COLOR_BORDER_ACTIVE].pixel; + break; + } + else + pixel = sc->xftcolor[CWM_COLOR_BORDER_INACTIVE].pixel; + + if (cc->flags & CLIENT_URGENCY) + pixel = sc->xftcolor[CWM_COLOR_BORDER_URGENCY].pixel; + + XSetWindowBorderWidth(X_Dpy, cc->win, (unsigned int)cc->bwidth); + XSetWindowBorder(X_Dpy, cc->win, pixel | (0xffu << 24)); +} + +static void +client_class_hint(struct client_ctx *cc) +{ + XClassHint ch; + + if (XGetClassHint(X_Dpy, cc->win, &ch)) { + if (ch.res_class) { + cc->res_class = xstrdup(ch.res_class); + XFree(ch.res_class); + } + if (ch.res_name) { + cc->res_name = xstrdup(ch.res_name); + XFree(ch.res_name); + } + } +} + +static void +client_wm_protocols(struct client_ctx *cc) +{ + Atom *p; + int i, j; + + if (XGetWMProtocols(X_Dpy, cc->win, &p, &j)) { + for (i = 0; i < j; i++) { + if (p[i] == cwmh[WM_DELETE_WINDOW]) + cc->flags |= CLIENT_WM_DELETE_WINDOW; + else if (p[i] == cwmh[WM_TAKE_FOCUS]) + cc->flags |= CLIENT_WM_TAKE_FOCUS; + } + XFree(p); + } +} + +void +client_wm_hints(struct client_ctx *cc) +{ + XWMHints *wmh; + + if ((wmh = XGetWMHints(X_Dpy, cc->win)) != NULL) { + if ((wmh->flags & InputHint) && (wmh->input)) + cc->flags |= CLIENT_INPUT; + if ((wmh->flags & XUrgencyHint)) + client_urgency(cc); + if ((wmh->flags & StateHint)) + cc->initial_state = wmh->initial_state; + XFree(wmh); + } +} + +void +client_close(struct client_ctx *cc) +{ + if (cc->flags & CLIENT_WM_DELETE_WINDOW) + xu_send_clientmsg(cc->win, cwmh[WM_DELETE_WINDOW], CurrentTime); + else + XKillClient(X_Dpy, cc->win); +} + +void +client_set_name(struct client_ctx *cc) +{ + struct winname *wn, *wnnxt; + int i = 0; + + free(cc->name); + if (!xu_get_strprop(cc->win, ewmh[_NET_WM_NAME], &cc->name)) + if (!xu_get_strprop(cc->win, XA_WM_NAME, &cc->name)) + cc->name = xstrdup(""); + + TAILQ_FOREACH_SAFE(wn, &cc->nameq, entry, wnnxt) { + if (strcmp(wn->name, cc->name) == 0) { + TAILQ_REMOVE(&cc->nameq, wn, entry); + free(wn->name); + free(wn); + } + i++; + } + wn = xmalloc(sizeof(*wn)); + wn->name = xstrdup(cc->name); + TAILQ_INSERT_TAIL(&cc->nameq, wn, entry); + + /* Garbage collection. */ + if ((i + 1) > Conf.nameqlen) { + wn = TAILQ_FIRST(&cc->nameq); + TAILQ_REMOVE(&cc->nameq, wn, entry); + free(wn->name); + free(wn); + } +} + +static void +client_placement(struct client_ctx *cc) +{ + struct screen_ctx *sc = cc->sc; + + if (cc->hint.flags & (USPosition | PPosition)) { + if (cc->geom.x >= sc->view.w) + cc->geom.x = sc->view.w - cc->bwidth - 1; + if (cc->geom.x + cc->geom.w + cc->bwidth <= 0) + cc->geom.x = -(cc->geom.w + cc->bwidth - 1); + if (cc->geom.y >= sc->view.h) + cc->geom.x = sc->view.h - cc->bwidth - 1; + if (cc->geom.y + cc->geom.h + cc->bwidth <= 0) + cc->geom.y = -(cc->geom.h + cc->bwidth - 1); + if (cc->flags & CLIENT_IGNORE) { + if (((cc->obwidth * 2) + cc->geom.x + cc->geom.w) == sc->view.w) + cc->geom.x += cc->obwidth * 2; + if (((cc->obwidth * 2) + cc->geom.y + cc->geom.h) == sc->view.h) + cc->geom.y += cc->obwidth * 2; + } + } else { + struct geom area; + int xmouse, ymouse, xslack, yslack; + + xu_ptr_get(sc->rootwin, &xmouse, &ymouse); + area = screen_area(sc, xmouse, ymouse, 1); + + xmouse = MAX(MAX(xmouse, area.x) - cc->geom.w / 2, area.x); + ymouse = MAX(MAX(ymouse, area.y) - cc->geom.h / 2, area.y); + + xslack = area.x + area.w - cc->geom.w - cc->bwidth * 2; + yslack = area.y + area.h - cc->geom.h - cc->bwidth * 2; + + if (xslack >= area.x) { + cc->geom.x = MAX(MIN(xmouse, xslack), area.x); + } else { + cc->geom.x = area.x; + cc->geom.w = area.x + area.w; + } + if (yslack >= area.y) { + cc->geom.y = MAX(MIN(ymouse, yslack), area.y); + } else { + cc->geom.y = area.y; + cc->geom.h = area.y + area.h; + } + } +} + +void +client_mtf(struct client_ctx *cc) +{ + struct screen_ctx *sc = cc->sc; + + TAILQ_REMOVE(&sc->clientq, cc, entry); + TAILQ_INSERT_HEAD(&sc->clientq, cc, entry); +} + +void +client_get_sizehints(struct client_ctx *cc) +{ + long tmp; + XSizeHints size; + + if (!XGetWMNormalHints(X_Dpy, cc->win, &size, &tmp)) + size.flags = 0; + + cc->hint.flags = size.flags; + + if (size.flags & PBaseSize) { + cc->hint.basew = size.base_width; + cc->hint.baseh = size.base_height; + } else if (size.flags & PMinSize) { + cc->hint.basew = size.min_width; + cc->hint.baseh = size.min_height; + } + if (size.flags & PMinSize) { + cc->hint.minw = size.min_width; + cc->hint.minh = size.min_height; + } else if (size.flags & PBaseSize) { + cc->hint.minw = size.base_width; + cc->hint.minh = size.base_height; + } + if (size.flags & PMaxSize) { + cc->hint.maxw = size.max_width; + cc->hint.maxh = size.max_height; + } + if (size.flags & PResizeInc) { + cc->hint.incw = size.width_inc; + cc->hint.inch = size.height_inc; + } + cc->hint.incw = MAX(1, cc->hint.incw); + cc->hint.inch = MAX(1, cc->hint.inch); + cc->hint.minw = MAX(1, cc->hint.minw); + cc->hint.minh = MAX(1, cc->hint.minh); + + if (size.flags & PAspect) { + if (size.min_aspect.x > 0) + cc->hint.mina = (float)size.min_aspect.y / + size.min_aspect.x; + if (size.max_aspect.y > 0) + cc->hint.maxa = (float)size.max_aspect.x / + size.max_aspect.y; + } +} + +void +client_apply_sizehints(struct client_ctx *cc) +{ + Bool baseismin; + + baseismin = (cc->hint.basew == cc->hint.minw) && + (cc->hint.baseh == cc->hint.minh); + + /* temporarily remove base dimensions, ICCCM 4.1.2.3 */ + if (!baseismin) { + cc->geom.w -= cc->hint.basew; + cc->geom.h -= cc->hint.baseh; + } + + /* adjust for aspect limits */ + if (cc->hint.mina && cc->hint.maxa) { + if (cc->hint.maxa < (float)cc->geom.w / cc->geom.h) + cc->geom.w = cc->geom.h * cc->hint.maxa; + else if (cc->hint.mina < (float)cc->geom.h / cc->geom.w) + cc->geom.h = cc->geom.w * cc->hint.mina; + } + + /* remove base dimensions for increment */ + if (baseismin) { + cc->geom.w -= cc->hint.basew; + cc->geom.h -= cc->hint.baseh; + } + + /* adjust for increment value */ + cc->geom.w -= cc->geom.w % cc->hint.incw; + cc->geom.h -= cc->geom.h % cc->hint.inch; + + /* restore base dimensions */ + cc->geom.w += cc->hint.basew; + cc->geom.h += cc->hint.baseh; + + /* adjust for min width/height */ + cc->geom.w = MAX(cc->geom.w, cc->hint.minw); + cc->geom.h = MAX(cc->geom.h, cc->hint.minh); + + /* adjust for max width/height */ + if (cc->hint.maxw) + cc->geom.w = MIN(cc->geom.w, cc->hint.maxw); + if (cc->hint.maxh) + cc->geom.h = MIN(cc->geom.h, cc->hint.maxh); +} + +static void +client_mwm_hints(struct client_ctx *cc) +{ + struct mwm_hints *mwmh; + + if (xu_get_prop(cc->win, cwmh[_MOTIF_WM_HINTS], + cwmh[_MOTIF_WM_HINTS], MWM_HINTS_ELEMENTS, + (unsigned char **)&mwmh) <= 0) + return; + + if ((mwmh->flags & MWM_HINTS_DECORATIONS) && + !(mwmh->decorations & MWM_DECOR_ALL)) { + if (!(mwmh->decorations & MWM_DECOR_BORDER)) + cc->bwidth = 0; + } + XFree(mwmh); +} + +void +client_transient(struct client_ctx *cc) +{ + struct client_ctx *tc; + Window trans; + + if (XGetTransientForHint(X_Dpy, cc->win, &trans)) { + if ((tc = client_find(trans)) != NULL) { + if (tc->flags & CLIENT_IGNORE) { + cc->flags |= CLIENT_IGNORE; + cc->bwidth = tc->bwidth; + } + } + } +} + +int +client_inbound(struct client_ctx *cc, int x, int y) +{ + return(x < cc->geom.w && x >= 0 && + y < cc->geom.h && y >= 0); +} + +int +client_snapcalc(int n0, int n1, int e0, int e1, int snapdist) +{ + int s0, s1; + + s0 = s1 = 0; + + if (abs(e0 - n0) <= snapdist) + s0 = e0 - n0; + + if (abs(e1 - n1) <= snapdist) + s1 = e1 - n1; + + /* possible to snap in both directions */ + if (s0 != 0 && s1 != 0) + if (abs(s0) < abs(s1)) + return s0; + else + return s1; + else if (s0 != 0) + return s0; + else if (s1 != 0) + return s1; + else + return 0; +} + +void +client_htile(struct client_ctx *cc) +{ + struct client_ctx *ci; + struct screen_ctx *sc = cc->sc; + struct geom area; + int i, n, mh, x, w, h; + + i = n = 0; + area = screen_area(sc, + cc->geom.x + cc->geom.w / 2, + cc->geom.y + cc->geom.h / 2, 1); + + TAILQ_FOREACH(ci, &sc->clientq, entry) { + if (ci->gc != cc->gc) + continue; + if (ci->flags & CLIENT_HIDDEN || + ci->flags & CLIENT_IGNORE || (ci == cc) || + ci->geom.x < area.x || + ci->geom.x > (area.x + area.w) || + ci->geom.y < area.y || + ci->geom.y > (area.y + area.h)) + continue; + n++; + } + if (n == 0) + return; + + if (cc->flags & CLIENT_VMAXIMIZED || + cc->geom.h + (cc->bwidth * 2) >= area.h) + return; + + cc->flags &= ~CLIENT_HMAXIMIZED; + cc->geom.x = area.x; + cc->geom.y = area.y; + cc->geom.w = area.w - (cc->bwidth * 2); + if (Conf.htile > 0) + cc->geom.h = ((area.h - (cc->bwidth * 2)) * Conf.htile) / 100; + client_resize(cc, 1); + client_ptr_warp(cc); + + mh = cc->geom.h + (cc->bwidth * 2); + x = area.x; + w = area.w / n; + h = area.h - mh; + TAILQ_FOREACH(ci, &sc->clientq, entry) { + if (ci->gc != cc->gc) + continue; + if (ci->flags & CLIENT_HIDDEN || + ci->flags & CLIENT_IGNORE || (ci == cc) || + ci->geom.x < area.x || + ci->geom.x > (area.x + area.w) || + ci->geom.y < area.y || + ci->geom.y > (area.y + area.h)) + continue; + ci->bwidth = Conf.bwidth; + ci->geom.x = x; + ci->geom.y = area.y + mh; + ci->geom.w = w - (ci->bwidth * 2); + ci->geom.h = h - (ci->bwidth * 2); + if (i + 1 == n) + ci->geom.w = area.x + area.w - + ci->geom.x - (ci->bwidth * 2); + x += w; + i++; + client_resize(ci, 1); + } +} + +void +client_vtile(struct client_ctx *cc) +{ + struct client_ctx *ci; + struct screen_ctx *sc = cc->sc; + struct geom area; + int i, n, mw, y, w, h; + + i = n = 0; + area = screen_area(sc, + cc->geom.x + cc->geom.w / 2, + cc->geom.y + cc->geom.h / 2, 1); + + TAILQ_FOREACH(ci, &sc->clientq, entry) { + if (ci->gc != cc->gc) + continue; + if (ci->flags & CLIENT_HIDDEN || + ci->flags & CLIENT_IGNORE || (ci == cc) || + ci->geom.x < area.x || + ci->geom.x > (area.x + area.w) || + ci->geom.y < area.y || + ci->geom.y > (area.y + area.h)) + continue; + n++; + } + if (n == 0) + return; + + if (cc->flags & CLIENT_HMAXIMIZED || + cc->geom.w + (cc->bwidth * 2) >= area.w) + return; + + cc->flags &= ~CLIENT_VMAXIMIZED; + cc->geom.x = area.x; + cc->geom.y = area.y; + if (Conf.vtile > 0) + cc->geom.w = ((area.w - (cc->bwidth * 2)) * Conf.vtile) / 100; + cc->geom.h = area.h - (cc->bwidth * 2); + client_resize(cc, 1); + client_ptr_warp(cc); + + mw = cc->geom.w + (cc->bwidth * 2); + y = area.y; + h = area.h / n; + w = area.w - mw; + TAILQ_FOREACH(ci, &sc->clientq, entry) { + if (ci->gc != cc->gc) + continue; + if (ci->flags & CLIENT_HIDDEN || + ci->flags & CLIENT_IGNORE || (ci == cc) || + ci->geom.x < area.x || + ci->geom.x > (area.x + area.w) || + ci->geom.y < area.y || + ci->geom.y > (area.y + area.h)) + continue; + ci->bwidth = Conf.bwidth; + ci->geom.x = area.x + mw; + ci->geom.y = y; + ci->geom.w = w - (ci->bwidth * 2); + ci->geom.h = h - (ci->bwidth * 2); + if (i + 1 == n) + ci->geom.h = area.y + area.h - + ci->geom.y - (ci->bwidth * 2); + y += h; + i++; + client_resize(ci, 1); + } +} diff --git a/conf.c b/conf.c new file mode 100644 index 0000000..83a3d34 --- /dev/null +++ b/conf.c @@ -0,0 +1,710 @@ +/* + * calmwm - the calm window manager + * + * Copyright (c) 2004 Marius Aamodt Eriksen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $OpenBSD: conf.c,v 1.257 2025/08/20 23:44:06 job Exp $ + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "calmwm.h" + +static const char *conf_bind_mask(const char *, unsigned int *); +static void conf_unbind_key(struct conf *, struct bind_ctx *); +static void conf_unbind_mouse(struct conf *, struct bind_ctx *); + +static const struct { + int num; + const char *name; +} group_binds[] = { + { 0, "nogroup" }, + { 1, "one" }, + { 2, "two" }, + { 3, "three" }, + { 4, "four" }, + { 5, "five" }, + { 6, "six" }, + { 7, "seven" }, + { 8, "eight" }, + { 9, "nine" }, +}; +static int cursor_binds[] = { + XC_left_ptr, /* CF_NORMAL */ + XC_fleur, /* CF_MOVE */ + XC_bottom_right_corner, /* CF_RESIZE */ + XC_question_arrow, /* CF_QUESTION */ +}; +static const char *color_binds[] = { + "#CCCCCC", /* CWM_COLOR_BORDER_ACTIVE */ + "#666666", /* CWM_COLOR_BORDER_INACTIVE */ + "#FC8814", /* CWM_COLOR_BORDER_URGENCY */ + "blue", /* CWM_COLOR_BORDER_GROUP */ + "red", /* CWM_COLOR_BORDER_UNGROUP */ + "black", /* CWM_COLOR_MENU_FG */ + "white", /* CWM_COLOR_MENU_BG */ + "black", /* CWM_COLOR_MENU_FONT */ + "", /* CWM_COLOR_MENU_FONT_SEL */ +}; +static const struct { + const char *tag; + void (*handler)(void *, struct cargs *); + enum context context; + int flag; +} name_to_func[] = { +#define FUNC_CC(t, h, n) \ + #t, kbfunc_ ## h, CWM_CONTEXT_CC, n +#define FUNC_SC(t, h, n) \ + #t, kbfunc_ ## h, CWM_CONTEXT_SC, n + + { FUNC_CC(window-lower, client_lower, 0) }, + { FUNC_CC(window-raise, client_raise, 0) }, + { FUNC_CC(window-hide, client_hide, 0) }, + { FUNC_CC(window-close, client_close, 0) }, + { FUNC_CC(window-delete, client_close, 0) }, + { FUNC_CC(window-htile, client_htile, 0) }, + { FUNC_CC(window-vtile, client_vtile, 0) }, + { FUNC_CC(window-stick, client_toggle_sticky, 0) }, + { FUNC_CC(window-fullscreen, client_toggle_fullscreen, 0) }, + { FUNC_CC(window-maximize, client_toggle_maximize, 0) }, + { FUNC_CC(window-vmaximize, client_toggle_vmaximize, 0) }, + { FUNC_CC(window-hmaximize, client_toggle_hmaximize, 0) }, + { FUNC_CC(window-freeze, client_toggle_freeze, 0) }, + { FUNC_CC(window-group, client_toggle_group, 0) }, + { FUNC_CC(window-movetogroup-1, client_movetogroup, 1) }, + { FUNC_CC(window-movetogroup-2, client_movetogroup, 2) }, + { FUNC_CC(window-movetogroup-3, client_movetogroup, 3) }, + { FUNC_CC(window-movetogroup-4, client_movetogroup, 4) }, + { FUNC_CC(window-movetogroup-5, client_movetogroup, 5) }, + { FUNC_CC(window-movetogroup-6, client_movetogroup, 6) }, + { FUNC_CC(window-movetogroup-7, client_movetogroup, 7) }, + { FUNC_CC(window-movetogroup-8, client_movetogroup, 8) }, + { FUNC_CC(window-movetogroup-9, client_movetogroup, 9) }, + { FUNC_CC(window-snap-center, client_snap, (CWM_CENTER)) }, + { FUNC_CC(window-snap-up, client_snap, (CWM_UP)) }, + { FUNC_CC(window-snap-down, client_snap, (CWM_DOWN)) }, + { FUNC_CC(window-snap-right, client_snap, (CWM_RIGHT)) }, + { FUNC_CC(window-snap-left, client_snap, (CWM_LEFT)) }, + { FUNC_CC(window-snap-up-right, client_snap, (CWM_UP_RIGHT)) }, + { FUNC_CC(window-snap-up-left, client_snap, (CWM_UP_LEFT)) }, + { FUNC_CC(window-snap-down-right, client_snap, (CWM_DOWN_RIGHT)) }, + { FUNC_CC(window-snap-down-left, client_snap, (CWM_DOWN_LEFT)) }, + { FUNC_CC(window-move, client_move, 0) }, + { FUNC_CC(window-move-up, client_move, (CWM_UP)) }, + { FUNC_CC(window-move-down, client_move, (CWM_DOWN)) }, + { FUNC_CC(window-move-right, client_move, (CWM_RIGHT)) }, + { FUNC_CC(window-move-left, client_move, (CWM_LEFT)) }, + { FUNC_CC(window-move-up-big, client_move, (CWM_UP_BIG)) }, + { FUNC_CC(window-move-down-big, client_move, (CWM_DOWN_BIG)) }, + { FUNC_CC(window-move-right-big, client_move, (CWM_RIGHT_BIG)) }, + { FUNC_CC(window-move-left-big, client_move, (CWM_LEFT_BIG)) }, + { FUNC_CC(window-resize, client_resize, 0) }, + { FUNC_CC(window-resize-up, client_resize, (CWM_UP)) }, + { FUNC_CC(window-resize-down, client_resize, (CWM_DOWN)) }, + { FUNC_CC(window-resize-right, client_resize, (CWM_RIGHT)) }, + { FUNC_CC(window-resize-left, client_resize, (CWM_LEFT)) }, + { FUNC_CC(window-resize-up-big, client_resize, (CWM_UP_BIG)) }, + { FUNC_CC(window-resize-down-big, client_resize, (CWM_DOWN_BIG)) }, + { FUNC_CC(window-resize-right-big, client_resize, (CWM_RIGHT_BIG)) }, + { FUNC_CC(window-resize-left-big, client_resize, (CWM_LEFT_BIG)) }, + { FUNC_CC(window-menu-label, client_menu_label, 0) }, + + { FUNC_SC(window-cycle, client_cycle, (CWM_CYCLE_FORWARD)) }, + { FUNC_SC(window-rcycle, client_cycle, (CWM_CYCLE_REVERSE)) }, + { FUNC_SC(window-cycle-ingroup, client_cycle, + (CWM_CYCLE_FORWARD | CWM_CYCLE_INGROUP)) }, + { FUNC_SC(window-rcycle-ingroup, client_cycle, + (CWM_CYCLE_REVERSE | CWM_CYCLE_INGROUP)) }, + { FUNC_SC(window-cycle-inclass, client_cycle, + (CWM_CYCLE_FORWARD | CWM_CYCLE_INCLASS)) }, + { FUNC_SC(window-rcycle-inclass, client_cycle, + (CWM_CYCLE_REVERSE | CWM_CYCLE_INCLASS)) }, + + { FUNC_SC(group-cycle, group_cycle, (CWM_CYCLE_FORWARD)) }, + { FUNC_SC(group-rcycle, group_cycle, (CWM_CYCLE_REVERSE)) }, + { FUNC_SC(group-last, group_last, 0) }, + { FUNC_SC(group-toggle-all, group_toggle_all, 0) }, + { FUNC_SC(group-toggle-1, group_toggle, 1) }, + { FUNC_SC(group-toggle-2, group_toggle, 2) }, + { FUNC_SC(group-toggle-3, group_toggle, 3) }, + { FUNC_SC(group-toggle-4, group_toggle, 4) }, + { FUNC_SC(group-toggle-5, group_toggle, 5) }, + { FUNC_SC(group-toggle-6, group_toggle, 6) }, + { FUNC_SC(group-toggle-7, group_toggle, 7) }, + { FUNC_SC(group-toggle-8, group_toggle, 8) }, + { FUNC_SC(group-toggle-9, group_toggle, 9) }, + { FUNC_SC(group-only-1, group_only, 1) }, + { FUNC_SC(group-only-2, group_only, 2) }, + { FUNC_SC(group-only-3, group_only, 3) }, + { FUNC_SC(group-only-4, group_only, 4) }, + { FUNC_SC(group-only-5, group_only, 5) }, + { FUNC_SC(group-only-6, group_only, 6) }, + { FUNC_SC(group-only-7, group_only, 7) }, + { FUNC_SC(group-only-8, group_only, 8) }, + { FUNC_SC(group-only-9, group_only, 9) }, + { FUNC_SC(group-close-1, group_close, 1) }, + { FUNC_SC(group-close-2, group_close, 2) }, + { FUNC_SC(group-close-3, group_close, 3) }, + { FUNC_SC(group-close-4, group_close, 4) }, + { FUNC_SC(group-close-5, group_close, 5) }, + { FUNC_SC(group-close-6, group_close, 6) }, + { FUNC_SC(group-close-7, group_close, 7) }, + { FUNC_SC(group-close-8, group_close, 8) }, + { FUNC_SC(group-close-9, group_close, 9) }, + + { FUNC_SC(pointer-move-up, ptrmove, (CWM_UP)) }, + { FUNC_SC(pointer-move-down, ptrmove, (CWM_DOWN)) }, + { FUNC_SC(pointer-move-left, ptrmove, (CWM_LEFT)) }, + { FUNC_SC(pointer-move-right, ptrmove, (CWM_RIGHT)) }, + { FUNC_SC(pointer-move-up-big, ptrmove, (CWM_UP_BIG)) }, + { FUNC_SC(pointer-move-down-big, ptrmove, (CWM_DOWN_BIG)) }, + { FUNC_SC(pointer-move-left-big, ptrmove, (CWM_LEFT_BIG)) }, + { FUNC_SC(pointer-move-right-big, ptrmove, (CWM_RIGHT_BIG)) }, + + { FUNC_SC(menu-cmd, menu_cmd, 0) }, + { FUNC_SC(menu-group, menu_group, 0) }, + { FUNC_SC(menu-ssh, menu_ssh, 0) }, + { FUNC_SC(menu-window, menu_client, CWM_MENU_WINDOW_ALL) }, + { FUNC_SC(menu-window-hidden, menu_client, CWM_MENU_WINDOW_HIDDEN) }, + { FUNC_SC(menu-exec, menu_exec, 0) }, + { FUNC_SC(menu-exec-wm, menu_wm, 0) }, + + { FUNC_SC(terminal, exec_term, 0) }, + { FUNC_SC(lock, exec_lock, 0) }, + { FUNC_SC(restart, cwm_status, CWM_EXEC_WM) }, + { FUNC_SC(quit, cwm_status, CWM_QUIT) }, +}; +static unsigned int ignore_mods[] = { + 0, LockMask, Mod2Mask, Mod2Mask | LockMask +}; +static const struct { + const char ch; + int mask; +} bind_mods[] = { + { 'S', ShiftMask }, + { 'C', ControlMask }, + { 'M', Mod1Mask }, + { '4', Mod4Mask }, + { '5', Mod5Mask }, +}; +static const struct { + const char *key; + const char *func; +} key_binds[] = { + { "CM-Return", "terminal" }, + { "CM-Delete", "lock" }, + { "M-question", "menu-exec" }, + { "CM-w", "menu-exec-wm" }, + { "M-period", "menu-ssh" }, + { "M-Return", "window-hide" }, + { "M-Down", "window-lower" }, + { "M-Up", "window-raise" }, + { "M-slash", "menu-window" }, + { "C-slash", "menu-cmd" }, + { "M-Tab", "window-cycle" }, + { "MS-Tab", "window-rcycle" }, + { "M-grave", "window-cycle-inclass" }, + { "MS-grave", "window-rcycle-inclass" }, + { "CM-n", "window-menu-label" }, + { "CM-x", "window-close" }, + { "CM-a", "group-toggle-all" }, + { "CM-0", "group-toggle-all" }, + { "CM-1", "group-toggle-1" }, + { "CM-2", "group-toggle-2" }, + { "CM-3", "group-toggle-3" }, + { "CM-4", "group-toggle-4" }, + { "CM-5", "group-toggle-5" }, + { "CM-6", "group-toggle-6" }, + { "CM-7", "group-toggle-7" }, + { "CM-8", "group-toggle-8" }, + { "CM-9", "group-toggle-9" }, + { "M-Right", "group-cycle" }, + { "M-Left", "group-rcycle" }, + { "CM-g", "window-group" }, + { "CM-f", "window-fullscreen" }, + { "CM-m", "window-maximize" }, + { "CM-s", "window-stick" }, + { "CM-equal", "window-vmaximize" }, + { "CMS-equal", "window-hmaximize" }, + { "CMS-f", "window-freeze" }, + { "CMS-r", "restart" }, + { "CMS-q", "quit" }, + { "M-h", "window-move-left" }, + { "M-j", "window-move-down" }, + { "M-k", "window-move-up" }, + { "M-l", "window-move-right" }, + { "MS-h", "window-move-left-big" }, + { "MS-j", "window-move-down-big" }, + { "MS-k", "window-move-up-big" }, + { "MS-l", "window-move-right-big" }, + { "CM-h", "window-resize-left" }, + { "CM-j", "window-resize-down" }, + { "CM-k", "window-resize-up" }, + { "CM-l", "window-resize-right" }, + { "CMS-h", "window-resize-left-big" }, + { "CMS-j", "window-resize-down-big" }, + { "CMS-k", "window-resize-up-big" }, + { "CMS-l", "window-resize-right-big" }, +}, +mouse_binds[] = { + { "1", "menu-window" }, + { "2", "menu-group" }, + { "3", "menu-cmd" }, + { "M-1", "window-move" }, + { "CM-1", "window-group" }, + { "M-2", "window-resize" }, + { "M-3", "window-lower" }, + { "CMS-3", "window-hide" }, +}; + +void +conf_init(struct conf *c) +{ + const char *home; + struct passwd *pw; + unsigned int i; + + c->stickygroups = 0; + c->bwidth = 1; + c->mamount = 1; + c->htile = 50; + c->vtile = 50; + c->snapdist = 0; + c->ngroups = 0; + c->nameqlen = 5; + + TAILQ_INIT(&c->ignoreq); + TAILQ_INIT(&c->autogroupq); + TAILQ_INIT(&c->keybindq); + TAILQ_INIT(&c->mousebindq); + TAILQ_INIT(&c->cmdq); + TAILQ_INIT(&c->wmq); + + for (i = 0; i < nitems(key_binds); i++) + conf_bind_key(c, key_binds[i].key, key_binds[i].func); + + for (i = 0; i < nitems(mouse_binds); i++) + conf_bind_mouse(c, mouse_binds[i].key, mouse_binds[i].func); + + for (i = 0; i < nitems(color_binds); i++) + c->color[i] = xstrdup(color_binds[i]); + + conf_cmd_add(c, "lock", "xlock"); + conf_cmd_add(c, "term", "xterm"); + conf_wm_add(c, "cwm", "cwm"); + + c->font = xstrdup("sans-serif:pixelsize=14:bold"); + c->wmname = xstrdup("CWM"); + + home = getenv("HOME"); + if ((home == NULL) || (*home == '\0')) { + pw = getpwuid(getuid()); + if (pw != NULL && pw->pw_dir != NULL && *pw->pw_dir != '\0') + home = pw->pw_dir; + else + home = "/"; + } + xasprintf(&c->conf_file, "%s/%s", home, ".cwmrc"); + xasprintf(&c->known_hosts, "%s/%s", home, ".ssh/known_hosts"); +} + +void +conf_clear(struct conf *c) +{ + struct autogroup *ag; + struct bind_ctx *kb, *mb; + struct winname *wn; + struct cmd_ctx *cmd, *wm; + int i; + + while ((cmd = TAILQ_FIRST(&c->cmdq)) != NULL) { + TAILQ_REMOVE(&c->cmdq, cmd, entry); + free(cmd->name); + free(cmd->path); + free(cmd); + } + while ((wm = TAILQ_FIRST(&c->wmq)) != NULL) { + TAILQ_REMOVE(&c->wmq, wm, entry); + free(wm->name); + free(wm->path); + free(wm); + } + while ((kb = TAILQ_FIRST(&c->keybindq)) != NULL) { + TAILQ_REMOVE(&c->keybindq, kb, entry); + free(kb); + } + while ((ag = TAILQ_FIRST(&c->autogroupq)) != NULL) { + TAILQ_REMOVE(&c->autogroupq, ag, entry); + free(ag->class); + free(ag->name); + free(ag); + } + while ((wn = TAILQ_FIRST(&c->ignoreq)) != NULL) { + TAILQ_REMOVE(&c->ignoreq, wn, entry); + free(wn->name); + free(wn); + } + while ((mb = TAILQ_FIRST(&c->mousebindq)) != NULL) { + TAILQ_REMOVE(&c->mousebindq, mb, entry); + free(mb); + } + for (i = 0; i < CWM_COLOR_NITEMS; i++) + free(c->color[i]); + + free(c->conf_file); + free(c->known_hosts); + free(c->font); + free(c->wmname); +} + +void +conf_cmd_add(struct conf *c, const char *name, const char *path) +{ + struct cmd_ctx *cmd, *cmdtmp = NULL, *cmdnxt; + + cmd = xmalloc(sizeof(*cmd)); + cmd->name = xstrdup(name); + cmd->path = xstrdup(path); + + TAILQ_FOREACH_SAFE(cmdtmp, &c->cmdq, entry, cmdnxt) { + if (strcmp(cmdtmp->name, name) == 0) { + TAILQ_REMOVE(&c->cmdq, cmdtmp, entry); + free(cmdtmp->name); + free(cmdtmp->path); + free(cmdtmp); + } + } + TAILQ_INSERT_TAIL(&c->cmdq, cmd, entry); +} + +void +conf_wm_add(struct conf *c, const char *name, const char *path) +{ + struct cmd_ctx *wm, *wmtmp = NULL, *wmnxt; + + wm = xmalloc(sizeof(*wm)); + wm->name = xstrdup(name); + wm->path = xstrdup(path); + + TAILQ_FOREACH_SAFE(wmtmp, &c->cmdq, entry, wmnxt) { + if (strcmp(wmtmp->name, name) == 0) { + TAILQ_REMOVE(&c->wmq, wmtmp, entry); + free(wmtmp->name); + free(wmtmp->path); + free(wmtmp); + } + } + TAILQ_INSERT_TAIL(&c->wmq, wm, entry); +} + +void +conf_autogroup(struct conf *c, int num, const char *name, const char *class) +{ + struct autogroup *ag; + char *p; + + ag = xmalloc(sizeof(*ag)); + if ((p = strchr(class, ',')) == NULL) { + if (name == NULL) + ag->name = NULL; + else + ag->name = xstrdup(name); + + ag->class = xstrdup(class); + } else { + *(p++) = '\0'; + if (name == NULL) + ag->name = xstrdup(class); + else + ag->name = xstrdup(name); + + ag->class = xstrdup(p); + } + ag->num = num; + TAILQ_INSERT_TAIL(&c->autogroupq, ag, entry); +} + +void +conf_ignore(struct conf *c, const char *name) +{ + struct winname *wn; + + wn = xmalloc(sizeof(*wn)); + wn->name = xstrdup(name); + TAILQ_INSERT_TAIL(&c->ignoreq, wn, entry); +} + +void +conf_cursor(struct conf *c) +{ + unsigned int i; + + for (i = 0; i < nitems(cursor_binds); i++) + c->cursor[i] = XCreateFontCursor(X_Dpy, cursor_binds[i]); +} + +void +conf_client(struct client_ctx *cc) +{ + struct winname *wn; + + TAILQ_FOREACH(wn, &Conf.ignoreq, entry) { + if (strncasecmp(wn->name, cc->name, strlen(wn->name)) == 0) { + cc->flags |= CLIENT_IGNORE; + break; + } + } +} + +void +conf_screen(struct screen_ctx *sc) +{ + unsigned int i; + XftColor xc; + + sc->gap = Conf.gap; + sc->snapdist = Conf.snapdist; + + sc->xftfont = XftFontOpenXlfd(X_Dpy, sc->which, Conf.font); + if (sc->xftfont == NULL) { + sc->xftfont = XftFontOpenName(X_Dpy, sc->which, Conf.font); + if (sc->xftfont == NULL) + errx(1, "%s: XftFontOpenName: %s", __func__, Conf.font); + } + + for (i = 0; i < nitems(color_binds); i++) { + if (i == CWM_COLOR_MENU_FONT_SEL && *Conf.color[i] == '\0') { + xu_xorcolor(sc->xftcolor[CWM_COLOR_MENU_BG], + sc->xftcolor[CWM_COLOR_MENU_FG], &xc); + xu_xorcolor(sc->xftcolor[CWM_COLOR_MENU_FONT], xc, &xc); + if (!XftColorAllocValue(X_Dpy, sc->visual, sc->colormap, + &xc.color, &sc->xftcolor[CWM_COLOR_MENU_FONT_SEL])) + warnx("XftColorAllocValue: %s", Conf.color[i]); + break; + } + if (!XftColorAllocName(X_Dpy, sc->visual, sc->colormap, + Conf.color[i], &sc->xftcolor[i])) { + warnx("XftColorAllocName: %s", Conf.color[i]); + XftColorAllocName(X_Dpy, sc->visual, sc->colormap, + color_binds[i], &sc->xftcolor[i]); + } + } + + conf_grab_kbd(sc->rootwin); +} + +void +conf_group(struct screen_ctx *sc) +{ + unsigned int i; + + for (i = 0; i < nitems(group_binds); i++) { + group_init(sc, group_binds[i].num, group_binds[i].name); + Conf.ngroups++; + } +} + +static const char * +conf_bind_mask(const char *name, unsigned int *mask) +{ + char *dash; + const char *ch; + unsigned int i; + + *mask = 0; + if ((dash = strchr(name, '-')) == NULL) + return name; + for (i = 0; i < nitems(bind_mods); i++) { + if ((ch = strchr(name, bind_mods[i].ch)) != NULL && ch < dash) + *mask |= bind_mods[i].mask; + } + /* Skip past modifiers. */ + return (dash + 1); +} + +int +conf_bind_key(struct conf *c, const char *bind, const char *cmd) +{ + struct bind_ctx *kb; + struct cargs *cargs; + const char *key; + unsigned int i; + + if ((strcmp(bind, "all") == 0) && (cmd == NULL)) { + conf_unbind_key(c, NULL); + return 1; + } + kb = xmalloc(sizeof(*kb)); + key = conf_bind_mask(bind, &kb->modmask); + kb->press.keysym = XStringToKeysym(key); + if (kb->press.keysym == NoSymbol) { + warnx("unknown symbol: %s", key); + free(kb); + return 0; + } + conf_unbind_key(c, kb); + if (cmd == NULL) { + free(kb); + return 1; + } + cargs = xcalloc(1, sizeof(*cargs)); + for (i = 0; i < nitems(name_to_func); i++) { + if (strcmp(name_to_func[i].tag, cmd) != 0) + continue; + kb->callback = name_to_func[i].handler; + kb->context = name_to_func[i].context; + cargs->flag = name_to_func[i].flag; + goto out; + } + kb->callback = kbfunc_exec_cmd; + kb->context = CWM_CONTEXT_NONE; + cargs->flag = 0; + cargs->cmd = xstrdup(cmd); +out: + kb->cargs = cargs; + TAILQ_INSERT_TAIL(&c->keybindq, kb, entry); + return 1; +} + +static void +conf_unbind_key(struct conf *c, struct bind_ctx *unbind) +{ + struct bind_ctx *key = NULL, *keynxt; + + TAILQ_FOREACH_SAFE(key, &c->keybindq, entry, keynxt) { + if ((unbind == NULL) || + ((key->modmask == unbind->modmask) && + (key->press.keysym == unbind->press.keysym))) { + TAILQ_REMOVE(&c->keybindq, key, entry); + free(key->cargs->cmd); + free(key->cargs); + free(key); + } + } +} + +int +conf_bind_mouse(struct conf *c, const char *bind, const char *cmd) +{ + struct bind_ctx *mb; + struct cargs *cargs; + const char *button, *errstr; + unsigned int i; + + if ((strcmp(bind, "all") == 0) && (cmd == NULL)) { + conf_unbind_mouse(c, NULL); + return 1; + } + mb = xmalloc(sizeof(*mb)); + button = conf_bind_mask(bind, &mb->modmask); + mb->press.button = strtonum(button, Button1, Button5, &errstr); + if (errstr) { + warnx("button number is %s: %s", errstr, button); + free(mb); + return 0; + } + conf_unbind_mouse(c, mb); + if (cmd == NULL) { + free(mb); + return 1; + } + cargs = xcalloc(1, sizeof(*cargs)); + for (i = 0; i < nitems(name_to_func); i++) { + if (strcmp(name_to_func[i].tag, cmd) != 0) + continue; + mb->callback = name_to_func[i].handler; + mb->context = name_to_func[i].context; + cargs->flag = name_to_func[i].flag; + goto out; + } + mb->callback = kbfunc_exec_cmd; + mb->context = CWM_CONTEXT_NONE; + cargs->flag = 0; + cargs->cmd = xstrdup(cmd); +out: + mb->cargs = cargs; + TAILQ_INSERT_TAIL(&c->mousebindq, mb, entry); + return 1; +} + +static void +conf_unbind_mouse(struct conf *c, struct bind_ctx *unbind) +{ + struct bind_ctx *mb = NULL, *mbnxt; + + TAILQ_FOREACH_SAFE(mb, &c->mousebindq, entry, mbnxt) { + if ((unbind == NULL) || + ((mb->modmask == unbind->modmask) && + (mb->press.button == unbind->press.button))) { + TAILQ_REMOVE(&c->mousebindq, mb, entry); + free(mb->cargs->cmd); + free(mb->cargs); + free(mb); + } + } +} + +void +conf_grab_kbd(Window win) +{ + struct bind_ctx *kb; + KeyCode kc; + unsigned int i; + + XUngrabKey(X_Dpy, AnyKey, AnyModifier, win); + + TAILQ_FOREACH(kb, &Conf.keybindq, entry) { + kc = XKeysymToKeycode(X_Dpy, kb->press.keysym); + if (kc == 0) + continue; + if ((XkbKeycodeToKeysym(X_Dpy, kc, 0, 0) != kb->press.keysym) && + (XkbKeycodeToKeysym(X_Dpy, kc, 0, 1) == kb->press.keysym)) + kb->modmask |= ShiftMask; + + for (i = 0; i < nitems(ignore_mods); i++) + XGrabKey(X_Dpy, kc, (kb->modmask | ignore_mods[i]), win, + True, GrabModeAsync, GrabModeAsync); + } +} + +void +conf_grab_mouse(Window win) +{ + struct bind_ctx *mb; + unsigned int i; + + XUngrabButton(X_Dpy, AnyButton, AnyModifier, win); + + TAILQ_FOREACH(mb, &Conf.mousebindq, entry) { + if (mb->context != CWM_CONTEXT_CC) + continue; + for (i = 0; i < nitems(ignore_mods); i++) { + XGrabButton(X_Dpy, mb->press.button, + (mb->modmask | ignore_mods[i]), win, False, + BUTTONMASK, GrabModeAsync, GrabModeSync, + None, None); + } + } +} diff --git a/cwm.1 b/cwm.1 new file mode 100644 index 0000000..107e0b4 --- /dev/null +++ b/cwm.1 @@ -0,0 +1,294 @@ +.\" $OpenBSD: cwm.1,v 1.66 2023/07/20 14:39:34 okan Exp $ +.\" +.\" Copyright (c) 2004,2005 Marius Aamodt Eriksen +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: July 20 2023 $ +.Dt CWM 1 +.Os +.Sh NAME +.Nm cwm +.Nd a lightweight and efficient window manager for X11 +.Sh SYNOPSIS +.\" For a program: program [-abc] file ... +.Nm cwm +.Op Fl nv +.Op Fl c Ar file +.Op Fl d Ar display +.Sh DESCRIPTION +.Nm +is a window manager for X11 which contains many features that +concentrate on the efficiency and transparency of window management, +while maintaining the simplest and most pleasant aesthetic. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl c Ar file +Specify an alternative configuration file. +By default, +.Nm +loads +.Pa ~/.cwmrc , +if present. +Any error messages from lines in the configuration file will be sent to +.Em stderr ; +however, +.Nm +will continue to process the rest of the configuration file. +.It Fl d Ar display +Specify the display to use. +.It Fl n +Configtest mode. +Only check the configuration file for validity. +.It Fl v +Verbose mode. +Multiple +.Fl v +options increase the verbosity. +.El +.Pp +.Nm +actions are initiated either via key or mouse bindings. +The following notations are used throughout this page: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It Ic C +Control key. +.It Ic M +Meta key. +.It Ic S +Shift key. +.It Ic 4 +Mod4 (windows) key. +.It Ic M1 +Left mouse button. +.It Ic M2 +Middle mouse button. +.It Ic M3 +Right mouse button. +.El +.Pp +The default key bindings are: +.Pp +.Bl -tag -width "CM-EscapeXXXXX" -offset indent -compact +.It Ic CM-Return +Spawn a new terminal. +.It Ic CM-Delete +Lock the screen. +.It Ic M-Return +Hide current window. +.It Ic M-Down +Lower current window. +.It Ic M-Up +Raise current window. +.It Ic M-slash +Search for windows. +.It Ic C-slash +Search for applications. +.It Ic CM-n +Label current window. +.It Ic M-Tab +Cycle through currently visible windows. +.It Ic MS-Tab +Reverse cycle through currently visible windows. +.It Ic M-grave +Cycle through currently visible windows of the same window class. +.It Ic MS-grave +Reverse cycle through currently visible windows of the same window class. +.It Ic CM-x +Close current window. +.It Ic CM-[n] +Toggle visibility of group n, where n is 1-9. +.It Ic CM-a +Toggle visibility of all groups. +.It Ic CM-g +Toggle group membership of current window. +.It Ic M-Right +Cycle through active groups. +.It Ic M-Left +Reverse cycle through active groups. +.It Ic CMS-f +Toggle freezing geometry of current window. +.It Ic CM-s +Toggle stickiness of current window. +.It Ic CM-f +Toggle full-screen mode of current window. +.It Ic CM-m +Toggle maximization of current window. +.It Ic CM-equal +Toggle vertical maximization of current window. +.It Ic CMS-equal +Toggle horizontal maximization of current window. +.It Ic M-[hjkl] +Move window by a small amount. +.It Ic MS-[hjkl] +Move window by a large amount; see +.Xr cwmrc 5 . +.It Ic CM-[hjkl] +Resize window by a small amount. +.It Ic CMS-[hjkl] +Resize window by a large amount; see +.Xr cwmrc 5 . +.It Ic M-question +Spawn +.Dq exec program +dialog. +.It Ic M-period +Spawn +.Dq ssh to +dialog. +This parses +.Pa $HOME/.ssh/known_hosts +to provide host auto-completion. +.Xr ssh 1 +will be executed via the configured terminal emulator. +.It Ic CM-w +Spawn +.Dq exec WindowManager +menu, allowing a switch to another window manager. +.It Ic CMS-r +Restart. +.It Ic CMS-q +Quit. +.El +.Pp +The default mouse bindings are: +.Pp +.Bl -tag -width "CM-EscapeXXXXX" -offset indent -compact +.It Ic M-M1 +Move current window. +.It Ic CM-M1 +Toggle group membership of current window. +.It Ic M-M2 +Resize current window +.It Ic M-M3 +Lower current window. +.It Ic CMS-M3 +Hide current window. +.El +.Pp +The following key bindings may be used to navigate +search and exec dialogs: +.Pp +.Bl -tag -width "[Down] or C-s or M-j" -offset indent -compact +.It Ic [Return] +Select item. +.It Ic [Down], C-s No or Ic M-j +Next item. +.It Ic [Up], C-r No or Ic M-k +Previous item. +.It Ic [Backspace] No or Ic C-h +Backspace. +.It Ic C-u +Clear input. +.It Ic C-a +List all available items. +.It Ic [Esc] +Cancel. +.El +.Pp +.Nm +rereads its configuration file when it receives a hangup signal, +.Dv SIGHUP , +by executing itself with the name and arguments with which it was started. +This is equivalent to the +.Ar restart +function. +.Sh SEARCH +.Nm +features the ability to search for windows by their current title, +old titles, and by their label. +The priority for the search results are: label, current title, +old titles in reverse order, and finally window class name. +.Nm +keeps a history of the 5 previous titles of a window. +.Pp +When searching, the leftmost character of the result list may show a +flag: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It ! +Window is currently focused. +.It & +Window is hidden. +.El +.Sh APPLICATIONS +.Nm +manages a list of applications defined with the +.Cm command +configuration option. +.Sh GROUPS +.Nm +has the ability to group windows together, and use the groups to +perform operations on the entire group instead of just one window. +Together with the +.Pa sticky +option, this can be used to emulate virtual desktops. +.Pp +To edit groups, use the group selection commands to toggle membership +of a group. +A blue border will be shown briefly on windows added to the current group, +and a red border will be shown on those just removed. +.Sh MENUS +Menus are recalled by clicking the mouse on the root window: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It Ic M1 +Show list of currently defined windows. +Selecting an item will warp to that window, unhiding it if necessary. +.It Ic M2 +Show list of currently defined groups. +Selecting an item will hide/unhide that group. +.It Ic M3 +Show list of applications as defined in the configuration file. +Selecting an item will spawn that application. +.El +.Sh ENVIRONMENT +.Bl -tag -width "DISPLAYXXX" -compact +.It DISPLAY +.Nm +starts on this display unless the +.Fl d +option is given. +.El +.Sh FILES +.Bl -tag -width "~/.cwmrcXX" -compact +.It Pa ~/.cwmrc +Default +.Nm +configuration file. +.El +.Sh SEE ALSO +.Xr cwmrc 5 +.Sh HISTORY +.Nm +was originally inspired by evilwm, but was rewritten from scratch +due to limitations in the evilwm codebase. +The from-scratch rewrite borrowed some code from 9wm, however that code +has since been removed or rewritten. +.Pp +.Nm +first appeared in +.Ox 4.2 . +.Sh AUTHORS +.An -nosplit +.Nm +was developed by +.An Marius Aamodt Eriksen Aq marius@monkey.org +with contributions from +.An Andy Adamson Aq dros@monkey.org , +.An Niels Provos Aq provos@monkey.org , +and +.An Antti Nyk\(:anen Aq aon@iki.fi . +Ideas, discussion with many others. diff --git a/cwmrc.5 b/cwmrc.5 new file mode 100644 index 0000000..6329663 --- /dev/null +++ b/cwmrc.5 @@ -0,0 +1,510 @@ +.\" $OpenBSD: cwmrc.5,v 1.79 2025/08/20 23:44:06 job Exp $ +.\" +.\" Copyright (c) 2004,2005 Marius Aamodt Eriksen +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: August 20 2025 $ +.Dt CWMRC 5 +.Os +.Sh NAME +.Nm cwmrc +.Nd calm window manager configuration file +.Sh DESCRIPTION +This manual page describes the +.Xr cwm 1 +configuration file. +.Pp +The current line can be extended over multiple lines using a backslash +.Pq Sq \e . +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extend to the end of the current line. +Care should be taken when commenting out multi-line text: +the comment is effective until the end of the entire block. +.Pp +Arguments containing whitespace should be surrounded by double quotes +.Pq \&" . +.Pp +The following options are accepted: +.Bl -tag -width Ds +.It Ic autogroup Ar group Oo Ar windowname , Oc Ns Ar windowclass +Automatically add new windows to +.Ar group +if their class property matches +.Ar windowclass , +or if their name and class properties match +.Ar windowname +and +.Ar windowclass , +respectively. +The more specific last match wins. +.Ar group +is a number between 0 and 9. +If +.Ar group +is 0, matching windows will not be added to any group; this may be +used to override +.Dq sticky group mode . +.Pp +The name and class values, respectively, for existing windows +are both set in the WM_CLASS property and may be obtained using +.Xr xprop 1 . +.It Ic bind-key Ar key function +Bind or rebind key +.Ar key +to +.Ar function . +The modifier keys come first, followed by a +.Sq - , +then a keysym name, taken from +.Pa /usr/X11R6/include/X11/keysymdef.h . +.Pp +The following modifiers are recognised: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It Ic C +Control key. +.It Ic M +Meta key. +.It Ic S +Shift key. +.It Ic 4 +Mod4 (windows) key. +.It Ic 5 +Mod5 (AltGr) key. +.El +.Pp +The +.Ar function +may either be one from the +.Sx BIND FUNCTION LIST +(see below) or the command line that is to be executed. +.It Ic bind-mouse Ar button function +Bind or rebind button +.Ar button +to +.Ar function . +The modifier keys come first, followed by a +.Sq - , +then the button number. +.Pp +The same modifiers are recognised as for +.Ar key +in +.Nm bind-key . +.Pp +The following buttons are recognised: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It Ic 1 +Left mouse button. +.It Ic 2 +Middle mouse button. +.It Ic 3 +Right mouse button. +.It Ic 4 +Scroll up mouse button. +.It Ic 5 +Scroll down mouse button. +.El +.Pp +The +.Ar function +may be taken from the +.Sx BIND FUNCTION LIST +(see below) or the command line that is to be executed. +.It Ic borderwidth Ar pixels +Set the window border width to +.Ar pixels . +.It Ic color activeborder Ar color +Set the color of the active border. +.It Ic color font Ar color +Set menu font color. +.It Ic color selfont Ar color +Set font color for selected menu item. +.It Ic color groupborder Ar color +Set the color of the border while grouping a window. +.It Ic color inactiveborder Ar color +Set the color of the inactive border. +.It Ic color menubg Ar color +Set menu background color. +.It Ic color menufg Ar color +Set menu foreground color. +.It Ic color urgencyborder Ar color +Set the color of the border of a window indicating urgency. +.It Ic color ungroupborder Ar color +Set the color of the border while ungrouping a window. +.It Ic command Ar name path +Every +.Ar name +entry is shown in the application menu. +When selected, the defined +.Ar path +is executed with +.Xr execvp 3 . +.Pp +The +.Ar name +entries +.Nm term +and +.Nm lock +have a special meaning. +They point to the terminal and screen locking programs specified by +key bindings. +The defaults are +.Xr xterm 1 +and +.Xr xlock 1 , +respectively. +.It Ic fontname Ar font +Change the default +.Ar font +for +.Xr Xft 3 . +.It Ic gap Ar top bottom left right +Define a +.Dq gap +in pixels at the edge of the screen, so that when a +window is maximized it will not overlap this area. +This +.Dq gap +can be used for applications such as +.Xr xclock 1 , +where the user may wish to remain visible. +.It Ic htile Ar percent +Set the percentage of screen the master window should occupy +after calling +.Ic window-htile . +If set to 0, the horizontal size of the master window will +remain unchanged. +The default is 50. +.It Ic ignore Ar windowname +Ignore, and do not warp to, windows with the name +.Ar windowname +when drawing borders and cycling through windows. +.It Ic moveamount Ar pixels +Set a default size for the keyboard movement bindings, +in pixels. +The default is 1. +.It Ic snapdist Ar pixels +Minimum distance to snap-to adjacent edge, in pixels. +The default is 0. +.It Ic sticky Ic yes Ns \&| Ns Ic no +Toggle sticky group mode. +The default behavior for new windows is to not assign any group. +By enabling sticky group mode, +.Xr cwm 1 +will assign new windows to the currently selected group. +.It Ic unbind-key Ar key +Unbind function bound to +.Ar key . +A special +.Ar key +keyword +.Dq all +can be used to unbind all keys. +.It Ic unbind-mouse Ar button +Unbind function bound to +.Ar button . +A special +.Ar button +keyword +.Dq all +can be used to unbind all buttons. +.It Ic vtile Ar percent +Set the percentage of screen the master window should occupy +after calling +.Ic window-vtile . +If set to 0, the vertical size of the master window will +remain unchanged. +The default is 50. +.It Ic wm Ar name path +Every +.Ar name +entry is shown in the wm menu. +When selected, the window manager is replaced by +.Ar path . +.El +.Sh BIND FUNCTION LIST +.Bl -tag -width 23n -compact +.It restart +Restart the running +.Xr cwm 1 . +.It quit +Quit +.Xr cwm 1 . +.It terminal +Spawn a new terminal. +.It lock +Lock the screen. +.It menu-window +Launch window search menu. +.It menu-window-hidden +Launch hidden window search menu. +.It menu-cmd +Launch application search menu. +.It menu-group +Launch group search menu. +.It menu-exec +Launch +.Dq exec program +menu. +.It menu-exec-wm +Launch +.Dq exec WindowManager +menu. +.It menu-ssh +Launch +.Dq ssh +menu. +.It group-toggle-[n] +Toggle visibility of group n, where n is 1-9. +.It group-only-[n] +Show only group n, where n is 1-9, hiding other groups. +.It group-last +Show only the previously active group. +.It group-close-[n] +Close all windows in group n, where n is 1-9. +.It group-toggle-all +Toggle visibility of all groups. +.It window-group +Toggle group membership of current window. +.It window-movetogroup-[n] +Hide current window from display and move to group n, where n is 1-9. +.It group-cycle +Forward cycle through groups. +.It group-rcycle +Reverse cycle through groups. +.It window-cycle +Forward cycle through windows. +.It window-rcycle +Reverse cycle through windows. +.It window-cycle-ingroup +Forward cycle through windows in current group. +.It window-rcycle-ingroup +Reverse cycle through windows in current group. +.It window-cycle-inclass +Forward cycle through windows of the current window class. +.It window-rcycle-inclass +Reverse cycle through windows of the current window class. +.It window-close +Close current window. +.It window-hide +Hide current window. +.It window-lower +Lower current window. +.It window-raise +Raise current window. +.It window-menu-label +Label current window. +.It window-freeze +Freeze current window geometry. +.It window-stick +Stick current window to all groups (same as assigning to nogroup). +.It window-fullscreen +Full-screen current window (gap + border removed). +.It window-maximize +Maximize current window (gap + border honored). +.It window-vmaximize +Vertically maximize current window (gap + border honored). +.It window-hmaximize +Horizontally maximize current window (gap + border honored). +.It window-htile +Current window is placed at the top of the screen, maximized +horizontally and resized to +.Ar htile +(default half) of the vertical screen space. +Other windows in its group share remaining screen space. +.It window-vtile +Current window is placed on the left of the screen, maximized vertically +and resized to +.Ar vtile +(default half) of the horizontal screen space. +Other windows in its group share remaining screen space. +.It window-move +Move current window. +.It window-resize +Resize current window. +.It window-move-up +Move window +.Ar moveamount +pixels up. +.It window-move-down +Move window +.Ar moveamount +pixels down. +.It window-move-right +Move window +.Ar moveamount +pixels right. +.It window-move-left +Move window +.Ar moveamount +pixels left. +.It window-move-up-big +Move window 10 times +.Ar moveamount +pixels up. +.It window-move-down-big +Move window 10 times +.Ar moveamount +pixels down. +.It window-move-right-big +Move window 10 times +.Ar moveamount +pixels right. +.It window-move-left-big +Move window 10 times +.Ar moveamount +pixels left. +.It window-resize-up +Resize window +.Ar moveamount +pixels up. +.It window-resize-down +Resize window +.Ar moveamount +pixels down. +.It window-resize-right +Resize window +.Ar moveamount +pixels right. +.It window-resize-left +Resize window +.Ar moveamount +pixels left. +.It window-resize-up-big +Resize window 10 times +.Ar moveamount +pixels up. +.It window-resize-down-big +Resize window 10 times +.Ar moveamount +pixels down. +.It window-resize-right-big +Resize window 10 times +.Ar moveamount +pixels right. +.It window-resize-left-big +Resize window 10 times +.Ar moveamount +pixels left. +.It window-snap-center +Snap window to center. +.It window-snap-up +Snap window to top edge. +.It window-snap-down +Snap window to bottom edge. +.It window-snap-right +Snap window to right edge. +.It window-snap-left +Snap window to left edge. +.It window-snap-up-right +Snap window to top-right corner. +.It window-snap-up-left +Snap window to top-left corner. +.It window-snap-down-right +Snap window to bottom-right corner. +.It window-snap-down-left +Snap window to bottom-left corner. +.It pointer-move-up +Move pointer +.Ar moveamount +pixels up. +.It pointer-move-down +Move pointer +.Ar moveamount +pixels down. +.It pointer-move-right +Move pointer +.Ar moveamount +pixels right. +.It pointer-move-left +Move pointer +.Ar moveamount +pixels left. +.It pointer-move-up-big +Move pointer 10 times +.Ar moveamount +pixels up. +.It pointer-move-down-big +Move pointer 10 times +.Ar moveamount +pixels down. +.It pointer-move-right-big +Move pointer 10 times +.Ar moveamount +pixels right. +.It pointer-move-left-big +Move pointer 10 times +.Ar moveamount +pixels left. +.El +.Sh FILES +.Bl -tag -width "~/.cwmrcXXX" -compact +.It Pa ~/.cwmrc +Default +.Xr cwm 1 +configuration file. +.El +.Sh EXAMPLES +.Bd -literal +# Set default Xft(3) font +fontname "sans-serif:pixelsize=14:bold" + +# Turn on sticky-group mode +sticky yes + +# Any entry here is shown in the application menu +command firefox firefox +command xmms xmms +command top "xterm -e top" + +# Autogroup definitions +autogroup 3 "aterm,XTerm" +autogroup 3 "xterm,XTerm" + +# Ignore programs by that name by not drawing borders around them. +ignore XMMS +ignore xwi +ignore xapm +ignore xclock + +# Key bindings +bind-key CM-r window-menu-label +bind-key CS-Return "xterm -e top" +bind-key C4-equal window-vmaximize +bind-key C4S-equal window-hmaximize +bind-key M-1 group-only-1 +bind-key M-2 group-only-2 +bind-key M-3 group-only-3 +bind-key MS-1 window-movetogroup-1 +bind-key MS-2 window-movetogroup-2 +bind-key MS-3 window-movetogroup-3 +unbind-key 4-o +unbind-key CM-equal +unbind-key CMS-equal + +# Mouse bindings +bind-mouse M-2 window-lower +bind-mouse M-3 window-resize +.Ed +.Sh SEE ALSO +.Xr cwm 1 +.Sh HISTORY +The +.Nm +file format first appeared in +.Ox 4.4 . diff --git a/group.c b/group.c new file mode 100644 index 0000000..76c548d --- /dev/null +++ b/group.c @@ -0,0 +1,378 @@ +/* + * calmwm - the calm window manager + * + * Copyright (c) 2004 Andy Adamson + * Copyright (c) 2004,2005 Marius Aamodt Eriksen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $OpenBSD: group.c,v 1.138 2022/01/27 18:45:10 op Exp $ + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "calmwm.h" + +static struct group_ctx *group_next(struct group_ctx *); +static struct group_ctx *group_prev(struct group_ctx *); +static void group_restack(struct group_ctx *); +static void group_set_active(struct group_ctx *); + +void +group_assign(struct group_ctx *gc, struct client_ctx *cc) +{ + if ((gc != NULL) && (gc->num == 0)) + gc = NULL; + + cc->gc = gc; + + xu_ewmh_set_net_wm_desktop(cc); +} + +void +group_hide(struct group_ctx *gc) +{ + struct screen_ctx *sc = gc->sc; + struct client_ctx *cc; + + screen_updatestackingorder(gc->sc); + + TAILQ_FOREACH(cc, &sc->clientq, entry) { + if (cc->gc != gc) + continue; + if (!(cc->flags & CLIENT_STICKY) && + !(cc->flags & CLIENT_HIDDEN)) + client_hide(cc); + } +} + +void +group_show(struct group_ctx *gc) +{ + struct screen_ctx *sc = gc->sc; + struct client_ctx *cc; + + TAILQ_FOREACH(cc, &sc->clientq, entry) { + if (cc->gc != gc) + continue; + if (!(cc->flags & CLIENT_STICKY) && + (cc->flags & CLIENT_HIDDEN)) + client_show(cc); + } + group_restack(gc); + group_set_active(gc); +} + +static void +group_restack(struct group_ctx *gc) +{ + struct screen_ctx *sc = gc->sc; + struct client_ctx *cc; + Window *winlist; + int i, lastempty = -1; + int nwins = 0, highstack = 0; + + TAILQ_FOREACH(cc, &sc->clientq, entry) { + if (cc->gc != gc) + continue; + if (cc->stackingorder > highstack) + highstack = cc->stackingorder; + } + winlist = xreallocarray(NULL, (highstack + 1), sizeof(*winlist)); + + /* Invert the stacking order for XRestackWindows(). */ + TAILQ_FOREACH(cc, &sc->clientq, entry) { + if (cc->gc != gc) + continue; + winlist[highstack - cc->stackingorder] = cc->win; + nwins++; + } + + /* Un-sparseify */ + for (i = 0; i <= highstack; i++) { + if (!winlist[i] && lastempty == -1) + lastempty = i; + else if (winlist[i] && lastempty != -1) { + winlist[lastempty] = winlist[i]; + if (++lastempty == i) + lastempty = -1; + } + } + + XRestackWindows(X_Dpy, winlist, nwins); + free(winlist); +} + +void +group_init(struct screen_ctx *sc, int num, const char *name) +{ + struct group_ctx *gc; + + gc = xmalloc(sizeof(*gc)); + gc->sc = sc; + gc->name = xstrdup(name); + gc->num = num; + TAILQ_INSERT_TAIL(&sc->groupq, gc, entry); + + if (num == 1) + group_set_active(gc); +} + +void +group_set_active(struct group_ctx *gc) +{ + struct screen_ctx *sc = gc->sc; + + sc->group_active = gc; + + xu_ewmh_net_current_desktop(sc); +} + +void +group_movetogroup(struct client_ctx *cc, int idx) +{ + struct screen_ctx *sc = cc->sc; + struct group_ctx *gc; + + TAILQ_FOREACH(gc, &sc->groupq, entry) { + if (gc->num == idx) { + if (cc->gc == gc) + return; + if (gc->num != 0 && group_holds_only_hidden(gc)) + client_hide(cc); + group_assign(gc, cc); + } + } +} + +void +group_toggle_membership(struct client_ctx *cc) +{ + struct screen_ctx *sc = cc->sc; + struct group_ctx *gc = sc->group_active; + + if (cc->gc == gc) { + group_assign(NULL, cc); + cc->flags |= CLIENT_UNGROUP; + } else { + group_assign(gc, cc); + cc->flags |= CLIENT_GROUP; + } + client_draw_border(cc); +} + +int +group_holds_only_sticky(struct group_ctx *gc) +{ + struct screen_ctx *sc = gc->sc; + struct client_ctx *cc; + + TAILQ_FOREACH(cc, &sc->clientq, entry) { + if (cc->gc != gc) + continue; + if (!(cc->flags & CLIENT_STICKY)) + return 0; + } + return 1; +} + +int +group_holds_only_hidden(struct group_ctx *gc) +{ + struct screen_ctx *sc = gc->sc; + struct client_ctx *cc; + + TAILQ_FOREACH(cc, &sc->clientq, entry) { + if (cc->gc != gc) + continue; + if (!(cc->flags & (CLIENT_HIDDEN | CLIENT_STICKY))) + return 0; + } + return 1; +} + +void +group_only(struct screen_ctx *sc, int idx) +{ + struct group_ctx *gc; + + if (sc->group_last != sc->group_active) + sc->group_last = sc->group_active; + + TAILQ_FOREACH(gc, &sc->groupq, entry) { + if (gc->num == idx) + group_show(gc); + else + group_hide(gc); + } +} + +void +group_toggle(struct screen_ctx *sc, int idx) +{ + struct group_ctx *gc; + + TAILQ_FOREACH(gc, &sc->groupq, entry) { + if (gc->num == idx) { + if (group_holds_only_hidden(gc)) + group_show(gc); + else + group_hide(gc); + } + } +} + +void +group_toggle_all(struct screen_ctx *sc) +{ + struct group_ctx *gc; + + TAILQ_FOREACH(gc, &sc->groupq, entry) { + if (sc->hideall) + group_show(gc); + else + group_hide(gc); + } + sc->hideall = !sc->hideall; +} + +void +group_close(struct screen_ctx *sc, int idx) +{ + struct group_ctx *gc; + struct client_ctx *cc; + + TAILQ_FOREACH(gc, &sc->groupq, entry) { + if (gc->num == idx) { + TAILQ_FOREACH(cc, &sc->clientq, entry) { + if (cc->gc != gc) + continue; + client_close(cc); + } + } + } +} + +void +group_cycle(struct screen_ctx *sc, int flags) +{ + struct group_ctx *newgc, *oldgc, *showgroup = NULL; + + oldgc = sc->group_active; + + newgc = oldgc; + for (;;) { + newgc = (flags & CWM_CYCLE_REVERSE) ? group_prev(newgc) : + group_next(newgc); + + if (newgc == oldgc) + break; + + if (!group_holds_only_sticky(newgc) && showgroup == NULL) + showgroup = newgc; + else if (!group_holds_only_hidden(newgc)) + group_hide(newgc); + } + if (showgroup == NULL) + return; + + group_hide(oldgc); + + if (group_holds_only_hidden(showgroup)) + group_show(showgroup); + else + group_set_active(showgroup); +} + +static struct group_ctx * +group_next(struct group_ctx *gc) +{ + struct screen_ctx *sc = gc->sc; + struct group_ctx *newgc; + + return(((newgc = TAILQ_NEXT(gc, entry)) != NULL) ? + newgc : TAILQ_FIRST(&sc->groupq)); +} + +static struct group_ctx * +group_prev(struct group_ctx *gc) +{ + struct screen_ctx *sc = gc->sc; + struct group_ctx *newgc; + + return(((newgc = TAILQ_PREV(gc, group_q, entry)) != NULL) ? + newgc : TAILQ_LAST(&sc->groupq, group_q)); +} + +int +group_restore(struct client_ctx *cc) +{ + struct screen_ctx *sc = cc->sc; + struct group_ctx *gc; + int num; + long grpnum; + + if (!xu_ewmh_get_net_wm_desktop(cc, &grpnum)) + return 0; + + num = (grpnum == -1) ? 0 : grpnum; + num = MIN(num, (Conf.ngroups - 1)); + + TAILQ_FOREACH(gc, &sc->groupq, entry) { + if (gc->num == num) { + group_assign(gc, cc); + return 1; + } + } + return 0; +} + +int +group_autogroup(struct client_ctx *cc) +{ + struct screen_ctx *sc = cc->sc; + struct autogroup *ag; + struct group_ctx *gc; + int num = -1, both_match = 0; + + if (cc->res_class == NULL || cc->res_name == NULL) + return 0; + + TAILQ_FOREACH(ag, &Conf.autogroupq, entry) { + if (strcmp(ag->class, cc->res_class) == 0) { + if ((ag->name != NULL) && + (strcmp(ag->name, cc->res_name) == 0)) { + num = ag->num; + both_match = 1; + } else if (ag->name == NULL && !both_match) + num = ag->num; + } + } + + TAILQ_FOREACH(gc, &sc->groupq, entry) { + if (gc->num == num) { + group_assign(gc, cc); + return 1; + } + } + return 0; +} diff --git a/kbfunc.c b/kbfunc.c new file mode 100644 index 0000000..89c9b0e --- /dev/null +++ b/kbfunc.c @@ -0,0 +1,825 @@ +/* + * calmwm - the calm window manager + * + * Copyright (c) 2004 Martin Murray + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $OpenBSD: kbfunc.c,v 1.176 2025/08/20 23:44:06 job Exp $ + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "calmwm.h" + +#define HASH_MARKER "|1|" + +extern sig_atomic_t cwm_status; + +static void kbfunc_amount(int, int, int *, int *); +static void kbfunc_client_move_kb(void *, struct cargs *); +static void kbfunc_client_move_mb(void *, struct cargs *); +static void kbfunc_client_resize_kb(void *, struct cargs *); +static void kbfunc_client_resize_mb(void *, struct cargs *); + +void +kbfunc_cwm_status(void *ctx, struct cargs *cargs) +{ + cwm_status = cargs->flag; +} + +static void +kbfunc_amount(int flags, int amt, int *mx, int *my) +{ +#define CWM_FACTOR 10 + + if (flags & CWM_BIGAMOUNT) + amt *= CWM_FACTOR; + + switch (flags & (CWM_UP | CWM_DOWN | CWM_LEFT | CWM_RIGHT)) { + case CWM_UP: + *my -= amt; + break; + case CWM_DOWN: + *my += amt; + break; + case CWM_RIGHT: + *mx += amt; + break; + case CWM_LEFT: + *mx -= amt; + break; + } +} + +void +kbfunc_ptrmove(void *ctx, struct cargs *cargs) +{ + struct screen_ctx *sc = ctx; + int x, y; + int mx = 0, my = 0; + + kbfunc_amount(cargs->flag, Conf.mamount, &mx, &my); + + xu_ptr_get(sc->rootwin, &x, &y); + xu_ptr_set(sc->rootwin, x + mx, y + my); +} + +void +kbfunc_client_move(void *ctx, struct cargs *cargs) +{ + if (cargs->xev == CWM_XEV_BTN) + kbfunc_client_move_mb(ctx, cargs); + else + kbfunc_client_move_kb(ctx, cargs); +} + +void +kbfunc_client_resize(void *ctx, struct cargs *cargs) +{ + if (cargs->xev == CWM_XEV_BTN) + kbfunc_client_resize_mb(ctx, cargs); + else + kbfunc_client_resize_kb(ctx, cargs); +} + +static void +kbfunc_client_move_kb(void *ctx, struct cargs *cargs) +{ + struct client_ctx *cc = ctx; + struct screen_ctx *sc = cc->sc; + struct geom area; + int mx = 0, my = 0; + + if (cc->flags & CLIENT_FREEZE) + return; + + kbfunc_amount(cargs->flag, Conf.mamount, &mx, &my); + + cc->geom.x += mx; + if (cc->geom.x < -(cc->geom.w + cc->bwidth - 1)) + cc->geom.x = -(cc->geom.w + cc->bwidth - 1); + if (cc->geom.x > (sc->view.w - cc->bwidth - 1)) + cc->geom.x = sc->view.w - cc->bwidth - 1; + cc->geom.y += my; + if (cc->geom.y < -(cc->geom.h + cc->bwidth - 1)) + cc->geom.y = -(cc->geom.h + cc->bwidth - 1); + if (cc->geom.y > (sc->view.h - cc->bwidth - 1)) + cc->geom.y = sc->view.h - cc->bwidth - 1; + + area = screen_area(sc, + cc->geom.x + cc->geom.w / 2, + cc->geom.y + cc->geom.h / 2, 1); + cc->geom.x += client_snapcalc(cc->geom.x, + cc->geom.x + cc->geom.w + (cc->bwidth * 2), + area.x, area.x + area.w, sc->snapdist); + cc->geom.y += client_snapcalc(cc->geom.y, + cc->geom.y + cc->geom.h + (cc->bwidth * 2), + area.y, area.y + area.h, sc->snapdist); + + client_move(cc); + client_ptr_inbound(cc, 1); + XSync(X_Dpy, True); +} + +static void +kbfunc_client_move_mb(void *ctx, struct cargs *cargs) +{ + struct client_ctx *cc = ctx; + XEvent ev; + Time ltime = 0; + struct screen_ctx *sc = cc->sc; + struct geom area; + int move = 1; + + client_raise(cc); + + if (cc->flags & CLIENT_FREEZE) + return; + + client_ptr_inbound(cc, 1); + + if (XGrabPointer(X_Dpy, sc->rootwin, False, MOUSEMASK, + GrabModeAsync, GrabModeAsync, None, Conf.cursor[CF_MOVE], + CurrentTime) != GrabSuccess) + return; + + screen_prop_win_create(sc, cc->win); + screen_prop_win_draw(sc, "%+5d%+5d", cc->geom.x, cc->geom.y); + while (move) { + XMaskEvent(X_Dpy, MOUSEMASK, &ev); + switch (ev.type) { + case MotionNotify: + /* not more than 60 times / second */ + if ((ev.xmotion.time - ltime) <= (1000 / 60)) + continue; + ltime = ev.xmotion.time; + + cc->geom.x = ev.xmotion.x_root - cc->ptr.x - cc->bwidth; + cc->geom.y = ev.xmotion.y_root - cc->ptr.y - cc->bwidth; + + area = screen_area(sc, + cc->geom.x + cc->geom.w / 2, + cc->geom.y + cc->geom.h / 2, 1); + cc->geom.x += client_snapcalc(cc->geom.x, + cc->geom.x + cc->geom.w + (cc->bwidth * 2), + area.x, area.x + area.w, sc->snapdist); + cc->geom.y += client_snapcalc(cc->geom.y, + cc->geom.y + cc->geom.h + (cc->bwidth * 2), + area.y, area.y + area.h, sc->snapdist); + client_move(cc); + screen_prop_win_draw(sc, + "%+5d%+5d", cc->geom.x, cc->geom.y); + break; + case ButtonRelease: + move = 0; + break; + } + } + if (ltime) + client_move(cc); + screen_prop_win_destroy(sc); + XUngrabPointer(X_Dpy, CurrentTime); +} + +static void +kbfunc_client_resize_kb(void *ctx, struct cargs *cargs) +{ + struct client_ctx *cc = ctx; + int mx = 0, my = 0; + int amt = 1; + + if (cc->flags & CLIENT_FREEZE) + return; + + if (!(cc->hint.flags & PResizeInc)) + amt = Conf.mamount; + + kbfunc_amount(cargs->flag, amt, &mx, &my); + + if ((cc->geom.w += mx * cc->hint.incw) < cc->hint.minw) + cc->geom.w = cc->hint.minw; + if ((cc->geom.h += my * cc->hint.inch) < cc->hint.minh) + cc->geom.h = cc->hint.minh; + if (cc->geom.x + cc->geom.w + cc->bwidth - 1 < 0) + cc->geom.x = -(cc->geom.w + cc->bwidth - 1); + if (cc->geom.y + cc->geom.h + cc->bwidth - 1 < 0) + cc->geom.y = -(cc->geom.h + cc->bwidth - 1); + + client_resize(cc, 1); + client_ptr_inbound(cc, 1); + XSync(X_Dpy, True); +} + +static void +kbfunc_client_resize_mb(void *ctx, struct cargs *cargs) +{ + struct client_ctx *cc = ctx; + XEvent ev; + Time ltime = 0; + struct screen_ctx *sc = cc->sc; + int resize = 1; + + if (cc->flags & CLIENT_FREEZE) + return; + + client_raise(cc); + client_ptr_save(cc); + + xu_ptr_set(cc->win, cc->geom.w, cc->geom.h); + + if (XGrabPointer(X_Dpy, sc->rootwin, False, MOUSEMASK, + GrabModeAsync, GrabModeAsync, None, Conf.cursor[CF_RESIZE], + CurrentTime) != GrabSuccess) + return; + + screen_prop_win_create(sc, cc->win); + screen_prop_win_draw(sc, "%4d x %-4d", cc->dim.w, cc->dim.h); + while (resize) { + XMaskEvent(X_Dpy, MOUSEMASK, &ev); + switch (ev.type) { + case MotionNotify: + /* not more than 60 times / second */ + if ((ev.xmotion.time - ltime) <= (1000 / 60)) + continue; + ltime = ev.xmotion.time; + + cc->geom.w = ev.xmotion.x - cc->geom.x - cc->bwidth; + cc->geom.h = ev.xmotion.y - cc->geom.y - cc->bwidth; + client_apply_sizehints(cc); + client_resize(cc, 1); + screen_prop_win_draw(sc, + "%4d x %-4d", cc->dim.w, cc->dim.h); + break; + case ButtonRelease: + resize = 0; + break; + } + } + if (ltime) + client_resize(cc, 1); + screen_prop_win_destroy(sc); + XUngrabPointer(X_Dpy, CurrentTime); + + /* Make sure the pointer stays within the window. */ + client_ptr_inbound(cc, 0); +} + +void +kbfunc_client_snap(void *ctx, struct cargs *cargs) +{ + struct client_ctx *cc = ctx; + struct screen_ctx *sc = cc->sc; + struct geom area; + int flags; + + area = screen_area(sc, + cc->geom.x + cc->geom.w / 2, + cc->geom.y + cc->geom.h / 2, 1); + + flags = cargs->flag; + while (flags) { + if (flags & CWM_UP) { + cc->geom.y = area.y; + flags &= ~CWM_UP; + } + if (flags & CWM_LEFT) { + cc->geom.x = area.x; + flags &= ~CWM_LEFT; + } + if (flags & CWM_RIGHT) { + cc->geom.x = area.x + area.w - cc->geom.w - + (cc->bwidth * 2); + flags &= ~CWM_RIGHT; + } + if (flags & CWM_DOWN) { + cc->geom.y = area.y + area.h - cc->geom.h - + (cc->bwidth * 2); + flags &= ~CWM_DOWN; + } + if (flags & CWM_CENTER) { + cc->geom.x = area.x + + (area.w - cc->geom.w - cc->bwidth) / 2; + cc->geom.y = area.y + + (area.h - cc->geom.h - cc->bwidth) / 2; + flags &= ~CWM_CENTER; + } + } + client_move(cc); + client_ptr_inbound(cc, 1); +} + +void +kbfunc_client_close(void *ctx, struct cargs *cargs) +{ + client_close(ctx); +} + +void +kbfunc_client_lower(void *ctx, struct cargs *cargs) +{ + client_ptr_save(ctx); + client_lower(ctx); +} + +void +kbfunc_client_raise(void *ctx, struct cargs *cargs) +{ + client_raise(ctx); +} + +void +kbfunc_client_hide(void *ctx, struct cargs *cargs) +{ + client_hide(ctx); +} + +void +kbfunc_client_toggle_freeze(void *ctx, struct cargs *cargs) +{ + client_toggle_freeze(ctx); +} + +void +kbfunc_client_toggle_sticky(void *ctx, struct cargs *cargs) +{ + client_toggle_sticky(ctx); +} + +void +kbfunc_client_toggle_fullscreen(void *ctx, struct cargs *cargs) +{ + client_toggle_fullscreen(ctx); +} + +void +kbfunc_client_toggle_maximize(void *ctx, struct cargs *cargs) +{ + client_toggle_maximize(ctx); +} + +void +kbfunc_client_toggle_hmaximize(void *ctx, struct cargs *cargs) +{ + client_toggle_hmaximize(ctx); +} + +void +kbfunc_client_toggle_vmaximize(void *ctx, struct cargs *cargs) +{ + client_toggle_vmaximize(ctx); +} + +void +kbfunc_client_htile(void *ctx, struct cargs *cargs) +{ + client_htile(ctx); +} + +void +kbfunc_client_vtile(void *ctx, struct cargs *cargs) +{ + client_vtile(ctx); +} + +void +kbfunc_client_cycle(void *ctx, struct cargs *cargs) +{ + struct screen_ctx *sc = ctx; + struct client_ctx *newcc, *oldcc, *prevcc; + int again = 1, flags = cargs->flag; + + /* For X apps that ignore/steal events. */ + if (cargs->xev == CWM_XEV_KEY) + XGrabKeyboard(X_Dpy, sc->rootwin, True, + GrabModeAsync, GrabModeAsync, CurrentTime); + + if (TAILQ_EMPTY(&sc->clientq)) + return; + + prevcc = TAILQ_FIRST(&sc->clientq); + oldcc = client_current(sc); + if (oldcc == NULL) + oldcc = (flags & CWM_CYCLE_REVERSE) ? + TAILQ_LAST(&sc->clientq, client_q) : + TAILQ_FIRST(&sc->clientq); + + newcc = oldcc; + while (again) { + again = 0; + + newcc = (flags & CWM_CYCLE_REVERSE) ? client_prev(newcc) : + client_next(newcc); + + /* Only cycle visible and non-ignored windows. */ + if ((newcc->flags & (CLIENT_SKIP_CYCLE)) || + ((flags & CWM_CYCLE_INGROUP) && + (newcc->gc != oldcc->gc)) || + ((flags & CWM_CYCLE_INCLASS) && + strcmp(newcc->res_class, oldcc->res_class) != 0)) + again = 1; + + /* Is oldcc the only non-hidden window? */ + if (newcc == oldcc) { + if (again) + return; /* No windows visible. */ + break; + } + } + + /* Reset when cycling mod is released. XXX I hate this hack */ + sc->cycling = 1; + client_ptr_save(oldcc); + client_raise(prevcc); + client_raise(newcc); + if (!client_inbound(newcc, newcc->ptr.x, newcc->ptr.y)) { + newcc->ptr.x = newcc->geom.w / 2; + newcc->ptr.y = newcc->geom.h / 2; + } + + /* When no client is active, warp pointer to last active. */ + if (oldcc->flags & (CLIENT_ACTIVE)) + client_ptr_warp(newcc); + else if (oldcc->flags & (CLIENT_SKIP_CYCLE)) + client_ptr_warp(newcc); + else { + client_raise(oldcc); + client_ptr_warp(oldcc); + } +} + +void +kbfunc_client_toggle_group(void *ctx, struct cargs *cargs) +{ + struct client_ctx *cc = ctx; + + /* For X apps that ignore/steal events. */ + if (cargs->xev == CWM_XEV_KEY) + XGrabKeyboard(X_Dpy, cc->win, True, + GrabModeAsync, GrabModeAsync, CurrentTime); + + group_toggle_membership(cc); +} + +void +kbfunc_client_movetogroup(void *ctx, struct cargs *cargs) +{ + group_movetogroup(ctx, cargs->flag); +} + +void +kbfunc_group_only(void *ctx, struct cargs *cargs) +{ + group_only(ctx, cargs->flag); +} + +void +kbfunc_group_last(void *ctx, struct cargs *cargs) +{ + struct screen_ctx *sc = ctx; + + group_only(ctx, sc->group_last->num); +} + +void +kbfunc_group_toggle(void *ctx, struct cargs *cargs) +{ + group_toggle(ctx, cargs->flag); +} + +void +kbfunc_group_toggle_all(void *ctx, struct cargs *cargs) +{ + group_toggle_all(ctx); +} + +void +kbfunc_group_close(void *ctx, struct cargs *cargs) +{ + group_close(ctx, cargs->flag); +} + +void +kbfunc_group_cycle(void *ctx, struct cargs *cargs) +{ + group_cycle(ctx, cargs->flag); +} + +void +kbfunc_menu_client(void *ctx, struct cargs *cargs) +{ + struct screen_ctx *sc = ctx; + struct client_ctx *cc, *old_cc; + struct menu *mi; + struct menu_q menuq; + int mflags = 0; + + if (cargs->xev == CWM_XEV_BTN) + mflags |= CWM_MENU_LIST; + + TAILQ_INIT(&menuq); + TAILQ_FOREACH(cc, &sc->clientq, entry) { + if ((cargs->flag & CWM_MENU_WINDOW_ALL) || + (cc->flags & CLIENT_HIDDEN)) + menuq_add(&menuq, cc, NULL); + } + + if ((mi = menu_filter(sc, &menuq, "window", NULL, mflags, + search_match_client, search_print_client)) != NULL) { + cc = (struct client_ctx *)mi->ctx; + client_show(cc); + if ((old_cc = client_current(sc)) != NULL) + client_ptr_save(old_cc); + client_ptr_warp(cc); + } + + menuq_clear(&menuq); +} + +void +kbfunc_menu_cmd(void *ctx, struct cargs *cargs) +{ + struct screen_ctx *sc = ctx; + struct cmd_ctx *cmd; + struct menu *mi; + struct menu_q menuq; + int mflags = 0; + + if (cargs->xev == CWM_XEV_BTN) + mflags |= CWM_MENU_LIST; + + TAILQ_INIT(&menuq); + TAILQ_FOREACH(cmd, &Conf.cmdq, entry) { + if ((strcmp(cmd->name, "lock") == 0) || + (strcmp(cmd->name, "term") == 0)) + continue; + menuq_add(&menuq, cmd, NULL); + } + + if ((mi = menu_filter(sc, &menuq, "application", NULL, mflags, + search_match_cmd, search_print_cmd)) != NULL) { + cmd = (struct cmd_ctx *)mi->ctx; + u_spawn(cmd->path); + } + + menuq_clear(&menuq); +} + +void +kbfunc_menu_group(void *ctx, struct cargs *cargs) +{ + struct screen_ctx *sc = ctx; + struct group_ctx *gc; + struct menu *mi; + struct menu_q menuq; + int mflags = 0; + + if (cargs->xev == CWM_XEV_BTN) + mflags |= CWM_MENU_LIST; + + TAILQ_INIT(&menuq); + TAILQ_FOREACH(gc, &sc->groupq, entry) { + if (group_holds_only_sticky(gc)) + continue; + menuq_add(&menuq, gc, NULL); + } + + if ((mi = menu_filter(sc, &menuq, "group", NULL, mflags, + search_match_group, search_print_group)) != NULL) { + gc = (struct group_ctx *)mi->ctx; + (group_holds_only_hidden(gc)) ? + group_show(gc) : group_hide(gc); + } + + menuq_clear(&menuq); +} + +void +kbfunc_menu_wm(void *ctx, struct cargs *cargs) +{ + struct screen_ctx *sc = ctx; + struct cmd_ctx *wm; + struct menu *mi; + struct menu_q menuq; + int mflags = 0; + + if (cargs->xev == CWM_XEV_BTN) + mflags |= CWM_MENU_LIST; + + TAILQ_INIT(&menuq); + TAILQ_FOREACH(wm, &Conf.wmq, entry) + menuq_add(&menuq, wm, NULL); + + if ((mi = menu_filter(sc, &menuq, "wm", NULL, mflags, + search_match_wm, search_print_wm)) != NULL) { + wm = (struct cmd_ctx *)mi->ctx; + free(Conf.wm_argv); + Conf.wm_argv = xstrdup(wm->path); + cwm_status = CWM_EXEC_WM; + } + + menuq_clear(&menuq); +} + +void +kbfunc_menu_exec(void *ctx, struct cargs *cargs) +{ +#define NPATHS 256 + struct screen_ctx *sc = ctx; + char **ap, *paths[NPATHS], *path, *pathcpy; + char tpath[PATH_MAX]; + struct stat sb; + DIR *dirp; + struct dirent *dp; + struct menu *mi; + struct menu_q menuq; + int l, i; + int mflags = (CWM_MENU_DUMMY | CWM_MENU_FILE); + + TAILQ_INIT(&menuq); + + if ((path = getenv("PATH")) == NULL) + path = _PATH_DEFPATH; + pathcpy = path = xstrdup(path); + + for (ap = paths; ap < &paths[NPATHS - 1] && + (*ap = strsep(&pathcpy, ":")) != NULL;) { + if (**ap != '\0') + ap++; + } + *ap = NULL; + for (i = 0; i < NPATHS && paths[i] != NULL; i++) { + if ((dirp = opendir(paths[i])) == NULL) + continue; + + while ((dp = readdir(dirp)) != NULL) { + (void)memset(tpath, '\0', sizeof(tpath)); + l = snprintf(tpath, sizeof(tpath), "%s/%s", paths[i], + dp->d_name); + if (l == -1 || l >= sizeof(tpath)) + continue; + /* Skip everything but regular files and symlinks. */ + if (dp->d_type != DT_REG && dp->d_type != DT_LNK) { + /* lstat(2) in case d_type isn't supported. */ + if (lstat(tpath, &sb) == -1) + continue; + if (!S_ISREG(sb.st_mode) && + !S_ISLNK(sb.st_mode)) + continue; + } + if (access(tpath, X_OK) == 0) + menuq_add(&menuq, NULL, "%s", dp->d_name); + } + (void)closedir(dirp); + } + free(path); + + if ((mi = menu_filter(sc, &menuq, "exec", NULL, mflags, + search_match_exec, search_print_text)) != NULL) { + if (mi->text[0] == '\0') + goto out; + u_spawn(mi->text); + } +out: + if (mi != NULL && mi->dummy) + free(mi); + menuq_clear(&menuq); +} + +void +kbfunc_menu_ssh(void *ctx, struct cargs *cargs) +{ + struct screen_ctx *sc = ctx; + struct cmd_ctx *cmd; + struct menu *mi; + struct menu_q menuq; + FILE *fp; + char *buf, *lbuf, *p; + char hostbuf[HOST_NAME_MAX+1]; + char path[PATH_MAX]; + int l; + size_t len; + ssize_t slen; + int mflags = (CWM_MENU_DUMMY); + + TAILQ_FOREACH(cmd, &Conf.cmdq, entry) { + if (strcmp(cmd->name, "term") == 0) + break; + } + TAILQ_INIT(&menuq); + + if ((fp = fopen(Conf.known_hosts, "r")) == NULL) { + warn("%s: %s", __func__, Conf.known_hosts); + goto menu; + } + + lbuf = NULL; + len = 0; + while ((slen = getline(&lbuf, &len, fp)) != -1) { + buf = lbuf; + if (buf[slen - 1] == '\n') + buf[slen - 1] = '\0'; + + /* skip hashed hosts */ + if (strncmp(buf, HASH_MARKER, strlen(HASH_MARKER)) == 0) + continue; + for (p = buf; *p != ',' && *p != ' ' && p != buf + slen; p++) + ; + /* ignore badness */ + if (p - buf + 1 > sizeof(hostbuf)) + continue; + (void)strlcpy(hostbuf, buf, p - buf + 1); + menuq_add(&menuq, NULL, "%s", hostbuf); + } + free(lbuf); + if (ferror(fp)) + err(1, "%s", path); + (void)fclose(fp); +menu: + if ((mi = menu_filter(sc, &menuq, "ssh", NULL, mflags, + search_match_text, search_print_text)) != NULL) { + if (mi->text[0] == '\0') + goto out; + l = snprintf(path, sizeof(path), "%s -T '[ssh] %s' -e ssh %s", + cmd->path, mi->text, mi->text); + if (l == -1 || l >= sizeof(path)) + goto out; + u_spawn(path); + } +out: + if (mi != NULL && mi->dummy) + free(mi); + menuq_clear(&menuq); +} + +void +kbfunc_client_menu_label(void *ctx, struct cargs *cargs) +{ + struct client_ctx *cc = ctx; + struct menu *mi; + struct menu_q menuq; + int mflags = (CWM_MENU_DUMMY); + + TAILQ_INIT(&menuq); + + /* dummy is set, so this will always return */ + mi = menu_filter(cc->sc, &menuq, "label", cc->label, mflags, + search_match_text, search_print_text); + + if (!mi->abort) { + free(cc->label); + cc->label = xstrdup(mi->text); + } + free(mi); +} + +void +kbfunc_exec_cmd(void *ctx, struct cargs *cargs) +{ + u_spawn(cargs->cmd); +} + +void +kbfunc_exec_term(void *ctx, struct cargs *cargs) +{ + struct cmd_ctx *cmd; + + TAILQ_FOREACH(cmd, &Conf.cmdq, entry) { + if (strcmp(cmd->name, "term") == 0) + u_spawn(cmd->path); + } +} + +void +kbfunc_exec_lock(void *ctx, struct cargs *cargs) +{ + struct cmd_ctx *cmd; + + TAILQ_FOREACH(cmd, &Conf.cmdq, entry) { + if (strcmp(cmd->name, "lock") == 0) + u_spawn(cmd->path); + } +} diff --git a/menu.c b/menu.c new file mode 100644 index 0000000..b7524fc --- /dev/null +++ b/menu.c @@ -0,0 +1,619 @@ +/* + * calmwm - the calm window manager + * + * Copyright (c) 2008 Owain G. Ainsworth + * Copyright (c) 2004 Marius Aamodt Eriksen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $OpenBSD: menu.c,v 1.110 2022/10/15 16:06:07 okan Exp $ + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "calmwm.h" + +#define PROMPT_SCHAR "\xc2\xbb" +#define PROMPT_ECHAR "\xc2\xab" + +#define MENUMASK (MOUSEMASK | ButtonMotionMask | KeyPressMask | \ + ExposureMask) +#define MENUGRABMASK (MOUSEMASK | ButtonMotionMask | StructureNotifyMask) + +enum ctltype { + CTL_NONE = -1, + CTL_ERASEONE = 0, CTL_WIPE, CTL_UP, CTL_DOWN, CTL_RETURN, + CTL_TAB, CTL_ABORT, CTL_ALL +}; + +struct menu_ctx { + struct screen_ctx *sc; + Window win; + XftDraw *xftdraw; + struct geom geom; + char searchstr[MENU_MAXENTRY + 1]; + char dispstr[MENU_MAXENTRY*2 + 1]; + char promptstr[MENU_MAXENTRY + 1]; + int list; + int listing; + int changed; + int prev; + int entry; + int num; + int flags; + void (*match)(struct menu_q *, struct menu_q *, char *); + void (*print)(struct menu *, int); +}; +static struct menu *menu_handle_key(XEvent *, struct menu_ctx *, + struct menu_q *, struct menu_q *); +static void menu_handle_move(struct menu_ctx *, + struct menu_q *, int, int); +static struct menu *menu_handle_release(struct menu_ctx *, + struct menu_q *, int, int); +static void menu_draw(struct menu_ctx *, struct menu_q *, + struct menu_q *); +static void menu_draw_entry(struct menu_ctx *, struct menu_q *, + int, int); +static int menu_calc_entry(struct menu_ctx *, int, int); +static struct menu *menu_complete_path(struct menu_ctx *); +static int menu_keycode(XKeyEvent *, enum ctltype *, char *); + +struct menu * +menu_filter(struct screen_ctx *sc, struct menu_q *menuq, const char *prompt, + const char *initial, int flags, + void (*match)(struct menu_q *, struct menu_q *, char *), + void (*print)(struct menu *, int)) +{ + struct menu_ctx mc; + struct menu_q resultq; + struct menu *mi = NULL; + XEvent e; + Window focuswin; + int focusrevert, xsave, ysave, xcur, ycur; + + TAILQ_INIT(&resultq); + + xu_ptr_get(sc->rootwin, &xsave, &ysave); + + (void)memset(&mc, 0, sizeof(mc)); + mc.sc = sc; + mc.flags = flags; + mc.match = match; + mc.print = print; + mc.entry = mc.prev = -1; + mc.geom.x = xsave; + mc.geom.y = ysave; + + if (mc.flags & CWM_MENU_LIST) + mc.list = 1; + + (void)strlcpy(mc.promptstr, prompt, sizeof(mc.promptstr)); + if (initial != NULL) + (void)strlcpy(mc.searchstr, initial, sizeof(mc.searchstr)); + else + mc.searchstr[0] = '\0'; + + mc.win = XCreateSimpleWindow(X_Dpy, sc->rootwin, 0, 0, 1, 1, + Conf.bwidth, + sc->xftcolor[CWM_COLOR_MENU_FG].pixel, + sc->xftcolor[CWM_COLOR_MENU_BG].pixel); + mc.xftdraw = XftDrawCreate(X_Dpy, mc.win, + sc->visual, sc->colormap); + + XSelectInput(X_Dpy, mc.win, MENUMASK); + XMapRaised(X_Dpy, mc.win); + + if (XGrabPointer(X_Dpy, mc.win, False, MENUGRABMASK, + GrabModeAsync, GrabModeAsync, None, Conf.cursor[CF_QUESTION], + CurrentTime) != GrabSuccess) { + XftDrawDestroy(mc.xftdraw); + XDestroyWindow(X_Dpy, mc.win); + return NULL; + } + + XGetInputFocus(X_Dpy, &focuswin, &focusrevert); + XSetInputFocus(X_Dpy, mc.win, RevertToPointerRoot, CurrentTime); + + /* make sure keybindings don't remove keys from the menu stream */ + XGrabKeyboard(X_Dpy, mc.win, True, + GrabModeAsync, GrabModeAsync, CurrentTime); + + for (;;) { + mc.changed = 0; + + XWindowEvent(X_Dpy, mc.win, MENUMASK, &e); + + switch (e.type) { + case KeyPress: + if ((mi = menu_handle_key(&e, &mc, menuq, &resultq)) + != NULL) + goto out; + /* FALLTHROUGH */ + case Expose: + menu_draw(&mc, menuq, &resultq); + break; + case MotionNotify: + menu_handle_move(&mc, &resultq, + e.xbutton.x, e.xbutton.y); + break; + case ButtonRelease: + if ((mi = menu_handle_release(&mc, &resultq, + e.xbutton.x, e.xbutton.y)) != NULL) + goto out; + break; + default: + break; + } + } +out: + if ((mc.flags & CWM_MENU_DUMMY) == 0 && mi->dummy) { + /* no mouse based match */ + free(mi); + mi = NULL; + } + + XftDrawDestroy(mc.xftdraw); + XDestroyWindow(X_Dpy, mc.win); + + XSetInputFocus(X_Dpy, focuswin, focusrevert, CurrentTime); + /* restore if user didn't move */ + xu_ptr_get(sc->rootwin, &xcur, &ycur); + if (xcur == mc.geom.x && ycur == mc.geom.y) + xu_ptr_set(sc->rootwin, xsave, ysave); + + XUngrabPointer(X_Dpy, CurrentTime); + XUngrabKeyboard(X_Dpy, CurrentTime); + + return mi; +} + +static struct menu * +menu_complete_path(struct menu_ctx *mc) +{ + struct screen_ctx *sc = mc->sc; + struct menu *mi, *mr; + struct menu_q menuq; + int mflags = (CWM_MENU_DUMMY); + + mr = xcalloc(1, sizeof(*mr)); + + TAILQ_INIT(&menuq); + + if ((mi = menu_filter(sc, &menuq, mc->searchstr, NULL, mflags, + search_match_path, search_print_text)) != NULL) { + mr->abort = mi->abort; + mr->dummy = mi->dummy; + if (mi->text[0] != '\0') + snprintf(mr->text, sizeof(mr->text), "%s \"%s\"", + mc->searchstr, mi->text); + else if (!mr->abort) + strlcpy(mr->text, mc->searchstr, sizeof(mr->text)); + } + + menuq_clear(&menuq); + + return mr; +} + +static struct menu * +menu_handle_key(XEvent *e, struct menu_ctx *mc, struct menu_q *menuq, + struct menu_q *resultq) +{ + struct menu *mi; + enum ctltype ctl; + char chr[32]; + size_t len; + int clen, i; + wchar_t wc; + + if (menu_keycode(&e->xkey, &ctl, chr) < 0) + return NULL; + + switch (ctl) { + case CTL_ERASEONE: + if ((len = strlen(mc->searchstr)) > 0) { + clen = 1; + while (mbtowc(&wc, &mc->searchstr[len-clen], MB_CUR_MAX) == -1) + clen++; + for (i = 1; i <= clen; i++) + mc->searchstr[len - i] = '\0'; + mc->changed = 1; + } + break; + case CTL_UP: + mi = TAILQ_LAST(resultq, menu_q); + if (mi == NULL) + break; + + TAILQ_REMOVE(resultq, mi, resultentry); + TAILQ_INSERT_HEAD(resultq, mi, resultentry); + break; + case CTL_DOWN: + mi = TAILQ_FIRST(resultq); + if (mi == NULL) + break; + + TAILQ_REMOVE(resultq, mi, resultentry); + TAILQ_INSERT_TAIL(resultq, mi, resultentry); + break; + case CTL_RETURN: + /* + * Return whatever the cursor is currently on. Else + * even if dummy is zero, we need to return something. + */ + if ((mi = TAILQ_FIRST(resultq)) == NULL) { + mi = xmalloc(sizeof(*mi)); + (void)strlcpy(mi->text, + mc->searchstr, sizeof(mi->text)); + mi->dummy = 1; + } + mi->abort = 0; + return mi; + case CTL_WIPE: + mc->searchstr[0] = '\0'; + mc->changed = 1; + break; + case CTL_TAB: + if ((mi = TAILQ_FIRST(resultq)) != NULL) { + /* + * - We are in exec_path menu mode + * - It is equal to the input + * We got a command, launch the file menu + */ + if ((mc->flags & CWM_MENU_FILE) && + (strncmp(mc->searchstr, mi->text, + strlen(mi->text))) == 0) + return menu_complete_path(mc); + + /* + * Put common prefix of the results into searchstr + */ + (void)strlcpy(mc->searchstr, + mi->text, sizeof(mc->searchstr)); + while ((mi = TAILQ_NEXT(mi, resultentry)) != NULL) { + i = 0; + while (tolower(mc->searchstr[i]) == + tolower(mi->text[i])) + i++; + mc->searchstr[i] = '\0'; + } + mc->changed = 1; + } + break; + case CTL_ALL: + mc->list = !mc->list; + break; + case CTL_ABORT: + mi = xmalloc(sizeof(*mi)); + mi->text[0] = '\0'; + mi->dummy = 1; + mi->abort = 1; + return mi; + default: + break; + } + + if (chr[0] != '\0') { + mc->changed = 1; + (void)strlcat(mc->searchstr, chr, sizeof(mc->searchstr)); + } + + if (mc->changed) { + if (mc->searchstr[0] != '\0') + (*mc->match)(menuq, resultq, mc->searchstr); + } else if (!mc->list && mc->listing) { + TAILQ_INIT(resultq); + mc->listing = 0; + } + + return NULL; +} + +static void +menu_draw(struct menu_ctx *mc, struct menu_q *menuq, struct menu_q *resultq) +{ + struct screen_ctx *sc = mc->sc; + struct menu *mi; + struct geom area; + int n, xsave, ysave; + XGlyphInfo extents; + + if (mc->list) { + if (TAILQ_EMPTY(resultq)) { + /* Copy them all over. */ + TAILQ_FOREACH(mi, menuq, entry) + TAILQ_INSERT_TAIL(resultq, mi, resultentry); + + mc->listing = 1; + } else if (mc->changed) + mc->listing = 0; + } + + (void)snprintf(mc->dispstr, sizeof(mc->dispstr), "%s%s%s%s", + mc->promptstr, PROMPT_SCHAR, mc->searchstr, PROMPT_ECHAR); + XftTextExtentsUtf8(X_Dpy, sc->xftfont, + (const FcChar8*)mc->dispstr, strlen(mc->dispstr), &extents); + mc->geom.w = extents.xOff; + mc->geom.h = sc->xftfont->ascent + sc->xftfont->descent; + mc->num = 1; + + TAILQ_FOREACH(mi, resultq, resultentry) { + (*mc->print)(mi, mc->listing); + XftTextExtentsUtf8(X_Dpy, sc->xftfont, + (const FcChar8*)mi->print, + MIN(strlen(mi->print), MENU_MAXENTRY), &extents); + mc->geom.w = MAX(mc->geom.w, extents.xOff); + mc->geom.h += sc->xftfont->ascent + sc->xftfont->descent; + mc->num++; + } + + area = screen_area(sc, mc->geom.x, mc->geom.y, 1); + area.w += area.x - Conf.bwidth * 2; + area.h += area.y - Conf.bwidth * 2; + + xsave = mc->geom.x; + ysave = mc->geom.y; + + /* Never hide the top, or left side, of the menu. */ + if (mc->geom.x + mc->geom.w >= area.w) + mc->geom.x = area.w - mc->geom.w; + if (mc->geom.x < area.x) { + mc->geom.x = area.x; + mc->geom.w = MIN(mc->geom.w, (area.w - area.x)); + } + if (mc->geom.y + mc->geom.h >= area.h) + mc->geom.y = area.h - mc->geom.h; + if (mc->geom.y < area.y) { + mc->geom.y = area.y; + mc->geom.h = MIN(mc->geom.h, (area.h - area.y)); + } + + if (mc->geom.x != xsave || mc->geom.y != ysave) + xu_ptr_set(sc->rootwin, mc->geom.x, mc->geom.y); + + XClearWindow(X_Dpy, mc->win); + XMoveResizeWindow(X_Dpy, mc->win, mc->geom.x, mc->geom.y, + mc->geom.w, mc->geom.h); + + n = 1; + XftDrawStringUtf8(mc->xftdraw, + &sc->xftcolor[CWM_COLOR_MENU_FONT], sc->xftfont, + 0, sc->xftfont->ascent, + (const FcChar8*)mc->dispstr, strlen(mc->dispstr)); + + TAILQ_FOREACH(mi, resultq, resultentry) { + int y = n * (sc->xftfont->ascent + sc->xftfont->descent); + + /* Stop drawing when menu doesn't fit inside the screen. */ + if (mc->geom.y + y >= area.h) + break; + + XftDrawStringUtf8(mc->xftdraw, + &sc->xftcolor[CWM_COLOR_MENU_FONT], sc->xftfont, + 0, y + sc->xftfont->ascent, + (const FcChar8*)mi->print, strlen(mi->print)); + n++; + } + if (n > 1) + menu_draw_entry(mc, resultq, 1, 1); +} + +static void +menu_draw_entry(struct menu_ctx *mc, struct menu_q *resultq, + int entry, int active) +{ + struct screen_ctx *sc = mc->sc; + struct menu *mi; + int color, i = 1, y; + + TAILQ_FOREACH(mi, resultq, resultentry) + if (entry == i++) + break; + if (mi == NULL) + return; + + y = entry * (sc->xftfont->ascent + sc->xftfont->descent); + color = (active) ? CWM_COLOR_MENU_FG : CWM_COLOR_MENU_BG; + XftDrawRect(mc->xftdraw, &sc->xftcolor[color], 0, y, + mc->geom.w, sc->xftfont->ascent + sc->xftfont->descent); + color = (active) ? CWM_COLOR_MENU_FONT_SEL : CWM_COLOR_MENU_FONT; + XftDrawStringUtf8(mc->xftdraw, + &sc->xftcolor[color], sc->xftfont, 0, y + sc->xftfont->ascent, + (const FcChar8*)mi->print, strlen(mi->print)); +} + +static void +menu_handle_move(struct menu_ctx *mc, struct menu_q *resultq, int x, int y) +{ + mc->prev = mc->entry; + mc->entry = menu_calc_entry(mc, x, y); + + if (mc->prev == mc->entry) + return; + + if (mc->prev != -1) + menu_draw_entry(mc, resultq, mc->prev, 0); + if (mc->entry != -1) { + XChangeActivePointerGrab(X_Dpy, MENUGRABMASK, + Conf.cursor[CF_NORMAL], CurrentTime); + menu_draw_entry(mc, resultq, mc->entry, 1); + } +} + +static struct menu * +menu_handle_release(struct menu_ctx *mc, struct menu_q *resultq, int x, int y) +{ + struct menu *mi; + int entry, i = 1; + + entry = menu_calc_entry(mc, x, y); + + TAILQ_FOREACH(mi, resultq, resultentry) + if (entry == i++) + break; + if (mi == NULL) { + mi = xmalloc(sizeof(*mi)); + mi->text[0] = '\0'; + mi->dummy = 1; + } + return mi; +} + +static int +menu_calc_entry(struct menu_ctx *mc, int x, int y) +{ + struct screen_ctx *sc = mc->sc; + int entry; + + entry = y / (sc->xftfont->ascent + sc->xftfont->descent); + + /* in bounds? */ + if (x < 0 || x > mc->geom.w || y < 0 || + y > (sc->xftfont->ascent + sc->xftfont->descent) * mc->num || + entry < 0 || entry >= mc->num) + entry = -1; + + if (entry == 0) + entry = -1; + + return entry; +} + +static int +menu_keycode(XKeyEvent *ev, enum ctltype *ctl, char *chr) +{ + KeySym ks; + + *ctl = CTL_NONE; + chr[0] = '\0'; + + ks = XkbKeycodeToKeysym(X_Dpy, ev->keycode, 0, + (ev->state & ShiftMask) ? 1 : 0); + + /* Look for control characters. */ + switch (ks) { + case XK_BackSpace: + *ctl = CTL_ERASEONE; + break; + case XK_KP_Enter: + case XK_Return: + *ctl = CTL_RETURN; + break; + case XK_Tab: + *ctl = CTL_TAB; + break; + case XK_Up: + *ctl = CTL_UP; + break; + case XK_Down: + *ctl = CTL_DOWN; + break; + case XK_Escape: + *ctl = CTL_ABORT; + break; + } + + if (*ctl == CTL_NONE && (ev->state & ControlMask)) { + switch (ks) { + case XK_s: + case XK_S: + /* Emacs "next" */ + *ctl = CTL_DOWN; + break; + case XK_r: + case XK_R: + /* Emacs "previous" */ + *ctl = CTL_UP; + break; + case XK_u: + case XK_U: + *ctl = CTL_WIPE; + break; + case XK_h: + case XK_H: + *ctl = CTL_ERASEONE; + break; + case XK_a: + case XK_A: + *ctl = CTL_ALL; + break; + case XK_bracketleft: + *ctl = CTL_ABORT; + break; + } + } + + if (*ctl == CTL_NONE && (ev->state & Mod1Mask)) { + switch (ks) { + case XK_j: + case XK_J: + /* Vi "down" */ + *ctl = CTL_DOWN; + break; + case XK_k: + case XK_K: + /* Vi "up" */ + *ctl = CTL_UP; + break; + } + } + + if (*ctl != CTL_NONE) + return 0; + + if (XLookupString(ev, chr, 32, &ks, NULL) < 0) + return -1; + + return 0; +} + +void +menuq_add(struct menu_q *mq, void *ctx, const char *fmt, ...) +{ + va_list ap; + struct menu *mi; + + mi = xcalloc(1, sizeof(*mi)); + mi->ctx = ctx; + + va_start(ap, fmt); + if (fmt != NULL) + (void)vsnprintf(mi->text, sizeof(mi->text), fmt, ap); + else + mi->text[0] = '\0'; + va_end(ap); + + TAILQ_INSERT_TAIL(mq, mi, entry); +} + +void +menuq_clear(struct menu_q *mq) +{ + struct menu *mi; + + while ((mi = TAILQ_FIRST(mq)) != NULL) { + TAILQ_REMOVE(mq, mi, entry); + free(mi); + } +} diff --git a/parse.y b/parse.y new file mode 100644 index 0000000..7290dba --- /dev/null +++ b/parse.y @@ -0,0 +1,639 @@ +/* $OpenBSD: parse.y,v 1.75 2021/12/24 16:00:47 okan Exp $ */ + +/* + * Copyright (c) 2002, 2003, 2004 Henning Brauer + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +%{ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "calmwm.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *, FILE *); +int popfile(void); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(int); +int lungetc(int); +int findeol(void); + +static struct conf *conf; + +typedef struct { + union { + int64_t number; + char *string; + } v; + int lineno; +} YYSTYPE; + +%} + +%token BINDKEY UNBINDKEY BINDMOUSE UNBINDMOUSE +%token FONTNAME STICKY GAP +%token AUTOGROUP COMMAND IGNORE WM +%token YES NO BORDERWIDTH MOVEAMOUNT HTILE VTILE +%token COLOR SNAPDIST +%token ACTIVEBORDER INACTIVEBORDER URGENCYBORDER +%token GROUPBORDER UNGROUPBORDER +%token MENUBG MENUFG +%token FONTCOLOR FONTSELCOLOR +%token ERROR +%token STRING +%token NUMBER +%type yesno +%type string numberstring +%% + +grammar : /* empty */ + | grammar '\n' + | grammar main '\n' + | grammar color '\n' + | grammar error '\n' { file->errors++; } + ; + +string : string STRING { + if (asprintf(&$$, "%s %s", $1, $2) == -1) { + free($1); + free($2); + yyerror("string: asprintf"); + YYERROR; + } + free($1); + free($2); + } + | STRING + ; + +numberstring : NUMBER { + char *s; + if (asprintf(&s, "%lld", $1) == -1) { + yyerror("string: asprintf"); + YYERROR; + } + $$ = s; + } + | STRING + ; + +yesno : YES { $$ = 1; } + | NO { $$ = 0; } + ; + +main : FONTNAME STRING { + free(conf->font); + conf->font = $2; + } + | STICKY yesno { + conf->stickygroups = $2; + } + | BORDERWIDTH NUMBER { + if ($2 < 0 || $2 > INT_MAX) { + yyerror("invalid borderwidth"); + YYERROR; + } + conf->bwidth = $2; + } + | HTILE NUMBER { + if ($2 < 0 || $2 > 99) { + yyerror("invalid htile percent"); + YYERROR; + } + conf->htile = $2; + } + | VTILE NUMBER { + if ($2 < 0 || $2 > 99) { + yyerror("invalid vtile percent"); + YYERROR; + } + conf->vtile = $2; + } + | MOVEAMOUNT NUMBER { + if ($2 < 0 || $2 > INT_MAX) { + yyerror("invalid movemount"); + YYERROR; + } + conf->mamount = $2; + } + | SNAPDIST NUMBER { + if ($2 < 0 || $2 > INT_MAX) { + yyerror("invalid snapdist"); + YYERROR; + } + conf->snapdist = $2; + } + | COMMAND STRING string { + if (strlen($3) >= PATH_MAX) { + yyerror("%s command path too long", $2); + free($2); + free($3); + YYERROR; + } + conf_cmd_add(conf, $2, $3); + free($2); + free($3); + } + | WM STRING string { + if (strlen($3) >= PATH_MAX) { + yyerror("%s wm path too long", $2); + free($2); + free($3); + YYERROR; + } + conf_wm_add(conf, $2, $3); + free($2); + free($3); + } + | AUTOGROUP NUMBER STRING { + if ($2 < 0 || $2 > 9) { + yyerror("invalid autogroup"); + free($3); + YYERROR; + } + conf_autogroup(conf, $2, NULL, $3); + free($3); + } + | AUTOGROUP NUMBER STRING ',' STRING { + if ($2 < 0 || $2 > 9) { + yyerror("invalid autogroup"); + free($3); + free($5); + YYERROR; + } + conf_autogroup(conf, $2, $3, $5); + free($3); + free($5); + } + | IGNORE STRING { + conf_ignore(conf, $2); + free($2); + } + | GAP NUMBER NUMBER NUMBER NUMBER { + if ($2 < 0 || $2 > INT_MAX || + $3 < 0 || $3 > INT_MAX || + $4 < 0 || $4 > INT_MAX || + $5 < 0 || $5 > INT_MAX) { + yyerror("invalid gap"); + YYERROR; + } + conf->gap.top = $2; + conf->gap.bottom = $3; + conf->gap.left = $4; + conf->gap.right = $5; + } + | BINDKEY numberstring string { + if (!conf_bind_key(conf, $2, $3)) { + yyerror("invalid bind-key: %s %s", $2, $3); + free($2); + free($3); + YYERROR; + } + free($2); + free($3); + } + | UNBINDKEY numberstring { + if (!conf_bind_key(conf, $2, NULL)) { + yyerror("invalid unbind-key: %s", $2); + free($2); + YYERROR; + } + free($2); + } + | BINDMOUSE numberstring string { + if (!conf_bind_mouse(conf, $2, $3)) { + yyerror("invalid bind-mouse: %s %s", $2, $3); + free($2); + free($3); + YYERROR; + } + free($2); + free($3); + } + | UNBINDMOUSE numberstring { + if (!conf_bind_mouse(conf, $2, NULL)) { + yyerror("invalid unbind-mouse: %s", $2); + free($2); + YYERROR; + } + free($2); + } + ; + +color : COLOR colors + ; + +colors : ACTIVEBORDER STRING { + free(conf->color[CWM_COLOR_BORDER_ACTIVE]); + conf->color[CWM_COLOR_BORDER_ACTIVE] = $2; + } + | INACTIVEBORDER STRING { + free(conf->color[CWM_COLOR_BORDER_INACTIVE]); + conf->color[CWM_COLOR_BORDER_INACTIVE] = $2; + } + | URGENCYBORDER STRING { + free(conf->color[CWM_COLOR_BORDER_URGENCY]); + conf->color[CWM_COLOR_BORDER_URGENCY] = $2; + } + | GROUPBORDER STRING { + free(conf->color[CWM_COLOR_BORDER_GROUP]); + conf->color[CWM_COLOR_BORDER_GROUP] = $2; + } + | UNGROUPBORDER STRING { + free(conf->color[CWM_COLOR_BORDER_UNGROUP]); + conf->color[CWM_COLOR_BORDER_UNGROUP] = $2; + } + | MENUBG STRING { + free(conf->color[CWM_COLOR_MENU_BG]); + conf->color[CWM_COLOR_MENU_BG] = $2; + } + | MENUFG STRING { + free(conf->color[CWM_COLOR_MENU_FG]); + conf->color[CWM_COLOR_MENU_FG] = $2; + } + | FONTCOLOR STRING { + free(conf->color[CWM_COLOR_MENU_FONT]); + conf->color[CWM_COLOR_MENU_FONT] = $2; + } + | FONTSELCOLOR STRING { + free(conf->color[CWM_COLOR_MENU_FONT_SEL]); + conf->color[CWM_COLOR_MENU_FONT_SEL] = $2; + } + ; +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + + file->errors++; + va_start(ap, fmt); + fprintf(stderr, "%s:%d: ", file->name, yylval.lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "activeborder", ACTIVEBORDER}, + { "autogroup", AUTOGROUP}, + { "bind-key", BINDKEY}, + { "bind-mouse", BINDMOUSE}, + { "borderwidth", BORDERWIDTH}, + { "color", COLOR}, + { "command", COMMAND}, + { "font", FONTCOLOR}, + { "fontname", FONTNAME}, + { "gap", GAP}, + { "groupborder", GROUPBORDER}, + { "htile", HTILE}, + { "ignore", IGNORE}, + { "inactiveborder", INACTIVEBORDER}, + { "menubg", MENUBG}, + { "menufg", MENUFG}, + { "moveamount", MOVEAMOUNT}, + { "no", NO}, + { "selfont", FONTSELCOLOR}, + { "snapdist", SNAPDIST}, + { "sticky", STICKY}, + { "unbind-key", UNBINDKEY}, + { "unbind-mouse", UNBINDMOUSE}, + { "ungroupborder", UNGROUPBORDER}, + { "urgencyborder", URGENCYBORDER}, + { "vtile", VTILE}, + { "wm", WM}, + { "yes", YES} + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define MAXPUSHBACK 128 + +char *parsebuf; +int parseindex; +char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(int quotec) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = (unsigned char)parsebuf[parseindex++]; + if (c != '\0') + return (c); + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return ((unsigned char)pushback_buffer[--pushback_index]); + + if (quotec) { + if ((c = getc(file->stream)) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = getc(file->stream)) == '\\') { + next = getc(file->stream); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = getc(file->stream); + } + return (c); +} + +int +lungetc(int c) +{ + if (c == EOF) + return (EOF); + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return (c); + } + if (pushback_index + 1 >= MAXPUSHBACK) + return (EOF); + pushback_buffer[pushback_index++] = c; + return (c); +} + +int +findeol(void) +{ + int c; + + parsebuf = NULL; + + /* skip to either EOF or the first real EOL */ + while (1) { + if (pushback_index) + c = (unsigned char)pushback_buffer[--pushback_index]; + else + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + char buf[8096]; + char *p; + int quotec, next, c; + int token; + + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || next == ' ' || + next == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = c; + } + yylval.v.string = xstrdup(buf); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc((unsigned char)*--p); + c = (unsigned char)*--p; + if (c == '-') + return (c); + } + } + +/* Similar to other parse.y copies, but also allows '/' in strings */ +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '#' && x != ',')) + + if (isalnum(c) || c == ':' || c == '_' || c == '*' || c == '/') { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + yylval.v.string = xstrdup(buf); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +struct file * +pushfile(const char *name, FILE *stream) +{ + struct file *nfile; + + nfile = xcalloc(1, sizeof(struct file)); + nfile->name = xstrdup(name); + nfile->stream = stream; + nfile->lineno = 1; + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +int +parse_config(const char *filename, struct conf *xconf) +{ + FILE *stream; + int errors = 0; + + conf = xconf; + + stream = fopen(filename, "r"); + if (stream == NULL) { + if (errno == ENOENT) + return (0); + warn("%s", filename); + return (-1); + } + file = pushfile(filename, stream); + topfile = file; + + yyparse(); + errors = file->errors; + popfile(); + + return (errors ? -1 : 0); +} diff --git a/screen.c b/screen.c new file mode 100644 index 0000000..4173fe7 --- /dev/null +++ b/screen.c @@ -0,0 +1,307 @@ +/* + * calmwm - the calm window manager + * + * Copyright (c) 2004 Marius Aamodt Eriksen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $OpenBSD: screen.c,v 1.98 2022/01/27 18:45:10 op Exp $ + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "calmwm.h" + +static struct geom screen_apply_gap(struct screen_ctx *, struct geom); +static void screen_scan(struct screen_ctx *); + +void +screen_init(int which) +{ + struct screen_ctx *sc; + XSetWindowAttributes attr; + + sc = xmalloc(sizeof(*sc)); + + TAILQ_INIT(&sc->clientq); + TAILQ_INIT(&sc->regionq); + TAILQ_INIT(&sc->groupq); + + sc->which = which; + sc->rootwin = RootWindow(X_Dpy, sc->which); + sc->colormap = DefaultColormap(X_Dpy, sc->which); + sc->visual = DefaultVisual(X_Dpy, sc->which); + sc->cycling = 0; + sc->hideall = 0; + + conf_screen(sc); + + xu_ewmh_net_supported(sc); + xu_ewmh_net_supported_wm_check(sc); + + conf_group(sc); + sc->group_last = sc->group_active; + screen_update_geometry(sc); + + xu_ewmh_net_desktop_names(sc); + xu_ewmh_net_number_of_desktops(sc); + xu_ewmh_net_showing_desktop(sc); + xu_ewmh_net_virtual_roots(sc); + + attr.cursor = Conf.cursor[CF_NORMAL]; + attr.event_mask = SubstructureRedirectMask | SubstructureNotifyMask | + EnterWindowMask | PropertyChangeMask | ButtonPressMask; + XChangeWindowAttributes(X_Dpy, sc->rootwin, (CWEventMask | CWCursor), &attr); + + if (Conf.xrandr) + XRRSelectInput(X_Dpy, sc->rootwin, RRScreenChangeNotifyMask); + + screen_scan(sc); + screen_updatestackingorder(sc); + + TAILQ_INSERT_TAIL(&Screenq, sc, entry); + + XSync(X_Dpy, False); +} + +static void +screen_scan(struct screen_ctx *sc) +{ + struct client_ctx *cc, *active = NULL; + Window *wins, w0, w1, rwin, cwin; + unsigned int nwins, i, mask; + int rx, ry, wx, wy; + + XQueryPointer(X_Dpy, sc->rootwin, &rwin, &cwin, + &rx, &ry, &wx, &wy, &mask); + + if (XQueryTree(X_Dpy, sc->rootwin, &w0, &w1, &wins, &nwins)) { + for (i = 0; i < nwins; i++) { + if ((cc = client_init(wins[i], sc)) != NULL) + if (cc->win == cwin) + active = cc; + } + XFree(wins); + } + if (active) + client_set_active(active); +} + +struct screen_ctx * +screen_find(Window win) +{ + struct screen_ctx *sc; + + TAILQ_FOREACH(sc, &Screenq, entry) { + if (sc->rootwin == win) + return sc; + } + warnx("%s: failure win 0x%lx", __func__, win); + return NULL; +} + +void +screen_updatestackingorder(struct screen_ctx *sc) +{ + Window *wins, w0, w1; + struct client_ctx *cc; + unsigned int nwins, i, s; + + if (XQueryTree(X_Dpy, sc->rootwin, &w0, &w1, &wins, &nwins)) { + for (s = 0, i = 0; i < nwins; i++) { + /* Skip hidden windows */ + if ((cc = client_find(wins[i])) == NULL || + cc->flags & CLIENT_HIDDEN) + continue; + + cc->stackingorder = s++; + } + XFree(wins); + } +} + +struct region_ctx * +region_find(struct screen_ctx *sc, int x, int y) +{ + struct region_ctx *rc; + + TAILQ_FOREACH(rc, &sc->regionq, entry) { + if ((x >= rc->view.x) && (x < (rc->view.x + rc->view.w)) && + (y >= rc->view.y) && (y < (rc->view.y + rc->view.h))) { + break; + } + } + return rc; +} + +struct geom +screen_area(struct screen_ctx *sc, int x, int y, int apply_gap) +{ + struct region_ctx *rc; + struct geom area = sc->view; + + TAILQ_FOREACH(rc, &sc->regionq, entry) { + if ((x >= rc->view.x) && (x < (rc->view.x + rc->view.w)) && + (y >= rc->view.y) && (y < (rc->view.y + rc->view.h))) { + area = rc->view; + break; + } + } + if (apply_gap) + area = screen_apply_gap(sc, area); + return area; +} + +void +screen_update_geometry(struct screen_ctx *sc) +{ + struct region_ctx *rc; + + sc->view.x = 0; + sc->view.y = 0; + sc->view.w = DisplayWidth(X_Dpy, sc->which); + sc->view.h = DisplayHeight(X_Dpy, sc->which); + sc->work = screen_apply_gap(sc, sc->view); + + while ((rc = TAILQ_FIRST(&sc->regionq)) != NULL) { + TAILQ_REMOVE(&sc->regionq, rc, entry); + free(rc); + } + + if (Conf.xrandr) { + XRRScreenResources *sr; + XRRCrtcInfo *ci; + int i; + + sr = XRRGetScreenResources(X_Dpy, sc->rootwin); + for (i = 0, ci = NULL; i < sr->ncrtc; i++) { + ci = XRRGetCrtcInfo(X_Dpy, sr, sr->crtcs[i]); + if (ci == NULL) + continue; + if (ci->noutput == 0) { + XRRFreeCrtcInfo(ci); + continue; + } + + rc = xmalloc(sizeof(*rc)); + rc->num = i; + rc->view.x = ci->x; + rc->view.y = ci->y; + rc->view.w = ci->width; + rc->view.h = ci->height; + rc->work = screen_apply_gap(sc, rc->view); + TAILQ_INSERT_TAIL(&sc->regionq, rc, entry); + + XRRFreeCrtcInfo(ci); + } + XRRFreeScreenResources(sr); + } else { + rc = xmalloc(sizeof(*rc)); + rc->num = 0; + rc->view.x = 0; + rc->view.y = 0; + rc->view.w = DisplayWidth(X_Dpy, sc->which); + rc->view.h = DisplayHeight(X_Dpy, sc->which); + rc->work = screen_apply_gap(sc, rc->view); + TAILQ_INSERT_TAIL(&sc->regionq, rc, entry); + } + + xu_ewmh_net_desktop_geometry(sc); + xu_ewmh_net_desktop_viewport(sc); + xu_ewmh_net_workarea(sc); +} + +static struct geom +screen_apply_gap(struct screen_ctx *sc, struct geom geom) +{ + geom.x += sc->gap.left; + geom.y += sc->gap.top; + geom.w -= (sc->gap.left + sc->gap.right); + geom.h -= (sc->gap.top + sc->gap.bottom); + + return geom; +} + +/* Bring back clients which are beyond the screen. */ +void +screen_assert_clients_within(struct screen_ctx *sc) +{ + struct client_ctx *cc; + int top, left, right, bottom; + + TAILQ_FOREACH(cc, &sc->clientq, entry) { + if (cc->sc != sc) + continue; + top = cc->geom.y; + left = cc->geom.x; + right = cc->geom.x + cc->geom.w + (cc->bwidth * 2) - 1; + bottom = cc->geom.y + cc->geom.h + (cc->bwidth * 2) - 1; + if ((top > sc->view.h || left > sc->view.w) || + (bottom < 0 || right < 0)) { + cc->geom.x = sc->gap.left; + cc->geom.y = sc->gap.top; + client_move(cc); + } + } +} + +void +screen_prop_win_create(struct screen_ctx *sc, Window win) +{ + sc->prop.win = XCreateSimpleWindow(X_Dpy, win, 0, 0, 1, 1, 0, + sc->xftcolor[CWM_COLOR_MENU_BG].pixel, + sc->xftcolor[CWM_COLOR_MENU_BG].pixel); + sc->prop.xftdraw = XftDrawCreate(X_Dpy, sc->prop.win, + sc->visual, sc->colormap); + + XMapWindow(X_Dpy, sc->prop.win); +} + +void +screen_prop_win_destroy(struct screen_ctx *sc) +{ + XftDrawDestroy(sc->prop.xftdraw); + XDestroyWindow(X_Dpy, sc->prop.win); +} + +void +screen_prop_win_draw(struct screen_ctx *sc, const char *fmt, ...) +{ + va_list ap; + char *text; + XGlyphInfo extents; + + va_start(ap, fmt); + xvasprintf(&text, fmt, ap); + va_end(ap); + + XftTextExtentsUtf8(X_Dpy, sc->xftfont, (const FcChar8*)text, + strlen(text), &extents); + XResizeWindow(X_Dpy, sc->prop.win, extents.xOff, sc->xftfont->height); + XClearWindow(X_Dpy, sc->prop.win); + XftDrawStringUtf8(sc->prop.xftdraw, &sc->xftcolor[CWM_COLOR_MENU_FONT], + sc->xftfont, 0, sc->xftfont->ascent + 1, + (const FcChar8*)text, strlen(text)); + + free(text); +} diff --git a/search.c b/search.c new file mode 100644 index 0000000..25bb917 --- /dev/null +++ b/search.c @@ -0,0 +1,289 @@ +/* + * calmwm - the calm window manager + * + * Copyright (c) 2004 Marius Aamodt Eriksen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $OpenBSD: search.c,v 1.70 2020/02/27 14:56:39 okan Exp $ + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "calmwm.h" + +#define PATH_ANY 0x0001 +#define PATH_EXEC 0x0002 + +static void match_path_type(struct menu_q *, char *, int); +static int match_substr(char *, char *, int); + +static int +match_substr(char *sub, char *str, int zeroidx) +{ + size_t len, sublen; + unsigned int n, flen; + + if (sub == NULL || str == NULL) + return 0; + + len = strlen(str); + sublen = strlen(sub); + + if (sublen > len) + return 0; + + if (zeroidx) + flen = 0; + else + flen = len - sublen; + + for (n = 0; n <= flen; n++) + if (strncasecmp(sub, str + n, sublen) == 0) + return 1; + + return 0; +} + +void +search_match_client(struct menu_q *menuq, struct menu_q *resultq, char *search) +{ + struct menu *mi, *tierp[3], *before = NULL; + struct client_ctx *cc; + struct winname *wn; + + (void)memset(tierp, 0, sizeof(tierp)); + + TAILQ_INIT(resultq); + TAILQ_FOREACH(mi, menuq, entry) { + int tier = -1, t; + cc = (struct client_ctx *)mi->ctx; + + /* Match on label. */ + if (match_substr(search, cc->label, 0)) + tier = 0; + + /* Match on window name history, from present to past. */ + if (tier < 0) { + TAILQ_FOREACH_REVERSE(wn, &cc->nameq, name_q, entry) + if (match_substr(search, wn->name, 0)) { + tier = 1; + break; + } + } + + /* Match on window resource class. */ + if ((tier < 0) && match_substr(search, cc->res_class, 0)) + tier = 2; + + if (tier < 0) + continue; + + /* Current window is ranked down. */ + if ((tier < nitems(tierp) - 1) && (cc->flags & CLIENT_ACTIVE)) + tier++; + + /* Hidden window is ranked up. */ + if ((tier > 0) && (cc->flags & CLIENT_HIDDEN)) + tier--; + + /* + * If you have a tierp, insert after it, and make it + * the new tierp. If you don't have a tierp, find the + * first nonzero tierp above you, insert after it. + * Always make your current tierp the newly inserted + * entry. + */ + for (t = tier; t >= 0 && ((before = tierp[t]) == NULL); t--) + ; + + if (before == NULL) + TAILQ_INSERT_HEAD(resultq, mi, resultentry); + else + TAILQ_INSERT_AFTER(resultq, before, mi, resultentry); + + tierp[tier] = mi; + } +} + +void +search_match_cmd(struct menu_q *menuq, struct menu_q *resultq, char *search) +{ + struct menu *mi; + struct cmd_ctx *cmd; + + TAILQ_INIT(resultq); + TAILQ_FOREACH(mi, menuq, entry) { + cmd = (struct cmd_ctx *)mi->ctx; + if (match_substr(search, cmd->name, 0)) + TAILQ_INSERT_TAIL(resultq, mi, resultentry); + } +} + +void +search_match_group(struct menu_q *menuq, struct menu_q *resultq, char *search) +{ + struct menu *mi; + struct group_ctx *gc; + char *s; + + TAILQ_INIT(resultq); + TAILQ_FOREACH(mi, menuq, entry) { + gc = (struct group_ctx *)mi->ctx; + xasprintf(&s, "%d %s", gc->num, gc->name); + if (match_substr(search, s, 0)) + TAILQ_INSERT_TAIL(resultq, mi, resultentry); + free(s); + } +} + +static void +match_path_type(struct menu_q *resultq, char *search, int flag) +{ + struct menu *mi; + char *pattern; + glob_t g; + int i; + + xasprintf(&pattern, "%s*", search); + if (glob(pattern, GLOB_MARK, NULL, &g) != 0) + return; + for (i = 0; i < g.gl_pathc; i++) { + if ((flag & PATH_EXEC) && access(g.gl_pathv[i], X_OK)) + continue; + mi = xcalloc(1, sizeof(*mi)); + (void)strlcpy(mi->text, g.gl_pathv[i], sizeof(mi->text)); + TAILQ_INSERT_TAIL(resultq, mi, resultentry); + } + globfree(&g); + free(pattern); +} + +void +search_match_exec(struct menu_q *menuq, struct menu_q *resultq, char *search) +{ + struct menu *mi, *mj; + int r; + + TAILQ_INIT(resultq); + TAILQ_FOREACH(mi, menuq, entry) { + if (match_substr(search, mi->text, 1) == 0 && + fnmatch(search, mi->text, 0) == FNM_NOMATCH) + continue; + TAILQ_FOREACH(mj, resultq, resultentry) { + r = strcmp(mi->text, mj->text); + if (r < 0) + TAILQ_INSERT_BEFORE(mj, mi, resultentry); + if (r <= 0) + break; + } + if (mj == NULL) + TAILQ_INSERT_TAIL(resultq, mi, resultentry); + } + if (TAILQ_EMPTY(resultq)) + match_path_type(resultq, search, PATH_EXEC); +} + +void +search_match_path(struct menu_q *menuq, struct menu_q *resultq, char *search) +{ + TAILQ_INIT(resultq); + match_path_type(resultq, search, PATH_ANY); +} + +void +search_match_text(struct menu_q *menuq, struct menu_q *resultq, char *search) +{ + struct menu *mi; + + TAILQ_INIT(resultq); + TAILQ_FOREACH(mi, menuq, entry) { + if (match_substr(search, mi->text, 0)) + TAILQ_INSERT_TAIL(resultq, mi, resultentry); + } +} + +void +search_match_wm(struct menu_q *menuq, struct menu_q *resultq, char *search) +{ + struct menu *mi; + struct cmd_ctx *wm; + + TAILQ_INIT(resultq); + TAILQ_FOREACH(mi, menuq, entry) { + wm = (struct cmd_ctx *)mi->ctx; + if ((match_substr(search, wm->name, 0)) || + (match_substr(search, wm->path, 0))) + TAILQ_INSERT_TAIL(resultq, mi, resultentry); + } +} + +void +search_print_client(struct menu *mi, int listing) +{ + struct client_ctx *cc = (struct client_ctx *)mi->ctx; + char flag = ' '; + + if (cc->flags & CLIENT_ACTIVE) + flag = '!'; + else if (cc->flags & CLIENT_HIDDEN) + flag = '&'; + + (void)snprintf(mi->print, sizeof(mi->print), "(%d) %c[%s] %s", + (cc->gc) ? cc->gc->num : 0, flag, + (cc->label) ? cc->label : "", cc->name); +} + +void +search_print_cmd(struct menu *mi, int listing) +{ + struct cmd_ctx *cmd = (struct cmd_ctx *)mi->ctx; + + (void)snprintf(mi->print, sizeof(mi->print), "%s", cmd->name); +} + +void +search_print_group(struct menu *mi, int listing) +{ + struct group_ctx *gc = (struct group_ctx *)mi->ctx; + + (void)snprintf(mi->print, sizeof(mi->print), + (group_holds_only_hidden(gc)) ? "%d: [%s]" : "%d: %s", + gc->num, gc->name); +} + +void +search_print_text(struct menu *mi, int listing) +{ + (void)snprintf(mi->print, sizeof(mi->print), "%s", mi->text); +} + +void +search_print_wm(struct menu *mi, int listing) +{ + struct cmd_ctx *wm = (struct cmd_ctx *)mi->ctx; + + (void)snprintf(mi->print, sizeof(mi->print), "%s [%s]", + wm->name, wm->path); +} diff --git a/util.c b/util.c new file mode 100644 index 0000000..381d6ae --- /dev/null +++ b/util.c @@ -0,0 +1,140 @@ +/* + * calmwm - the calm window manager + * + * Copyright (c) 2004 Marius Aamodt Eriksen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $OpenBSD: util.c,v 1.26 2020/04/16 17:12:49 tobias Exp $ + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "calmwm.h" + +static void log_msg(const char *, va_list); + +void +u_spawn(char *argstr) +{ + switch (fork()) { + case 0: + u_exec(argstr); + exit(1); + case -1: + warn("fork"); + default: + break; + } +} + +void +u_exec(char *argstr) +{ +#define MAXARGLEN 20 + char *args[MAXARGLEN], **ap = args; + char **end = &args[MAXARGLEN - 2], *tmp; + char *s = argstr; + + while (ap < end && (*ap = strsep(&argstr, " \t")) != NULL) { + if (**ap == '\0') + continue; + ap++; + if (argstr != NULL) { + /* deal with quoted strings */ + switch(argstr[0]) { + case '"': + case '\'': + if ((tmp = strchr(argstr + 1, argstr[0])) + != NULL) { + *(tmp++) = '\0'; + *(ap++) = ++argstr; + argstr = tmp; + } + break; + default: + break; + } + } + } + *ap = NULL; + + (void)setsid(); + (void)execvp(args[0], args); + warn("%s", s); +} + +char * +u_argv(char * const *argv) +{ + size_t siz = 0; + int i; + char *p; + + if (argv == 0) + return NULL; + + for (i = 0; argv[i]; i++) + siz += strlen(argv[i]) + 1; + if (siz == 0) + return NULL; + + p = xmalloc(siz); + strlcpy(p, argv[0], siz); + for (i = 1; argv[i]; i++) { + strlcat(p, " ", siz); + strlcat(p, argv[i], siz); + } + return p; +} + +static void +log_msg(const char *msg, va_list ap) +{ + char *fmt; + + if (asprintf(&fmt, "%s\n", msg) == -1) { + vfprintf(stderr, msg, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, fmt, ap); + free(fmt); + } + fflush(stderr); +} + +void +log_debug(int level, const char *func, const char *msg, ...) +{ + char *fmt; + va_list ap; + + if (Conf.debug < level) + return; + + va_start(ap, msg); + xasprintf(&fmt, "debug%d: %s: %s", level, func, msg); + log_msg(fmt, ap); + free(fmt); + va_end(ap); +} diff --git a/xevents.c b/xevents.c new file mode 100644 index 0000000..8c6be52 --- /dev/null +++ b/xevents.c @@ -0,0 +1,493 @@ +/* + * calmwm - the calm window manager + * + * Copyright (c) 2004 Marius Aamodt Eriksen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $OpenBSD: xevents.c,v 1.150 2020/03/24 14:47:29 okan Exp $ + */ + +/* + * NOTE: + * It is the responsibility of the caller to deal with memory + * management of the xevent's. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "calmwm.h" + +static void xev_handle_maprequest(XEvent *); +static void xev_handle_unmapnotify(XEvent *); +static void xev_handle_destroynotify(XEvent *); +static void xev_handle_configurerequest(XEvent *); +static void xev_handle_propertynotify(XEvent *); +static void xev_handle_enternotify(XEvent *); +static void xev_handle_buttonpress(XEvent *); +static void xev_handle_buttonrelease(XEvent *); +static void xev_handle_keypress(XEvent *); +static void xev_handle_keyrelease(XEvent *); +static void xev_handle_clientmessage(XEvent *); +static void xev_handle_randr(XEvent *); +static void xev_handle_mappingnotify(XEvent *); +static void xev_handle_expose(XEvent *); + +void (*xev_handlers[LASTEvent])(XEvent *) = { + [MapRequest] = xev_handle_maprequest, + [UnmapNotify] = xev_handle_unmapnotify, + [DestroyNotify] = xev_handle_destroynotify, + [ConfigureRequest] = xev_handle_configurerequest, + [PropertyNotify] = xev_handle_propertynotify, + [EnterNotify] = xev_handle_enternotify, + [ButtonPress] = xev_handle_buttonpress, + [ButtonRelease] = xev_handle_buttonrelease, + [KeyPress] = xev_handle_keypress, + [KeyRelease] = xev_handle_keyrelease, + [ClientMessage] = xev_handle_clientmessage, + [MappingNotify] = xev_handle_mappingnotify, + [Expose] = xev_handle_expose, +}; + +static KeySym modkeys[] = { XK_Alt_L, XK_Alt_R, XK_Super_L, XK_Super_R, + XK_Control_L, XK_Control_R, XK_ISO_Level3_Shift }; + +static void +xev_handle_maprequest(XEvent *ee) +{ + XMapRequestEvent *e = &ee->xmaprequest; + struct screen_ctx *sc; + struct client_ctx *cc, *old_cc; + + LOG_DEBUG3("parent: 0x%lx window: 0x%lx", e->parent, e->window); + + if ((sc = screen_find(e->parent)) == NULL) + return; + + if ((old_cc = client_current(sc)) != NULL) + client_ptr_save(old_cc); + + if ((cc = client_find(e->window)) == NULL) + cc = client_init(e->window, NULL); + + if ((cc != NULL) && (!(cc->flags & CLIENT_IGNORE))) + client_ptr_warp(cc); +} + +static void +xev_handle_unmapnotify(XEvent *ee) +{ + XUnmapEvent *e = &ee->xunmap; + struct client_ctx *cc; + + LOG_DEBUG3("window: 0x%lx", e->window); + + if ((cc = client_find(e->window)) != NULL) { + if (e->send_event) { + xu_set_wm_state(cc->win, WithdrawnState); + } else { + if (!(cc->flags & CLIENT_HIDDEN)) + client_remove(cc); + } + } +} + +static void +xev_handle_destroynotify(XEvent *ee) +{ + XDestroyWindowEvent *e = &ee->xdestroywindow; + struct client_ctx *cc; + + LOG_DEBUG3("window: 0x%lx", e->window); + + if ((cc = client_find(e->window)) != NULL) + client_remove(cc); +} + +static void +xev_handle_configurerequest(XEvent *ee) +{ + XConfigureRequestEvent *e = &ee->xconfigurerequest; + struct client_ctx *cc; + struct screen_ctx *sc; + XWindowChanges wc; + + LOG_DEBUG3("window: 0x%lx", e->window); + + if ((cc = client_find(e->window)) != NULL) { + sc = cc->sc; + + if (e->value_mask & CWWidth) + cc->geom.w = e->width; + if (e->value_mask & CWHeight) + cc->geom.h = e->height; + if (e->value_mask & CWX) + cc->geom.x = e->x; + if (e->value_mask & CWY) + cc->geom.y = e->y; + if (e->value_mask & CWBorderWidth) + cc->bwidth = e->border_width; + if (e->value_mask & CWSibling) + wc.sibling = e->above; + if (e->value_mask & CWStackMode) + wc.stack_mode = e->detail; + + if (cc->geom.x == 0 && cc->geom.w >= sc->view.w) + cc->geom.x -= cc->bwidth; + + if (cc->geom.y == 0 && cc->geom.h >= sc->view.h) + cc->geom.y -= cc->bwidth; + + wc.x = cc->geom.x; + wc.y = cc->geom.y; + wc.width = cc->geom.w; + wc.height = cc->geom.h; + wc.border_width = cc->bwidth; + + XConfigureWindow(X_Dpy, cc->win, e->value_mask, &wc); + client_config(cc); + } else { + /* let it do what it wants, it'll be ours when we map it. */ + wc.x = e->x; + wc.y = e->y; + wc.width = e->width; + wc.height = e->height; + wc.border_width = e->border_width; + wc.stack_mode = Above; + e->value_mask &= ~CWStackMode; + + XConfigureWindow(X_Dpy, e->window, e->value_mask, &wc); + } +} + +static void +xev_handle_propertynotify(XEvent *ee) +{ + XPropertyEvent *e = &ee->xproperty; + struct screen_ctx *sc; + struct client_ctx *cc; + + LOG_DEBUG3("window: 0x%lx", e->window); + + if ((cc = client_find(e->window)) != NULL) { + switch (e->atom) { + case XA_WM_NORMAL_HINTS: + client_get_sizehints(cc); + break; + case XA_WM_NAME: + client_set_name(cc); + break; + case XA_WM_HINTS: + client_wm_hints(cc); + client_draw_border(cc); + break; + case XA_WM_TRANSIENT_FOR: + client_transient(cc); + client_draw_border(cc); + if (cc->gc) + group_movetogroup(cc, cc->gc->num); + break; + default: + if (e->atom == ewmh[_NET_WM_NAME]) + client_set_name(cc); + break; + } + } else { + if (e->atom == ewmh[_NET_DESKTOP_NAMES]) { + if ((sc = screen_find(e->window)) != NULL) + xu_ewmh_net_desktop_names(sc); + } + } +} + +static void +xev_handle_enternotify(XEvent *ee) +{ + XCrossingEvent *e = &ee->xcrossing; + struct client_ctx *cc; + + LOG_DEBUG3("window: 0x%lx", e->window); + + Last_Event_Time = e->time; + + if ((cc = client_find(e->window)) != NULL) + client_set_active(cc); +} + +static void +xev_handle_buttonpress(XEvent *ee) +{ + XButtonEvent *e = &ee->xbutton; + struct client_ctx *cc; + struct screen_ctx *sc; + struct bind_ctx *mb; + + LOG_DEBUG3("root: 0x%lx window: 0x%lx subwindow: 0x%lx", + e->root, e->window, e->subwindow); + + if ((sc = screen_find(e->root)) == NULL) + return; + + e->state &= ~IGNOREMODMASK; + + TAILQ_FOREACH(mb, &Conf.mousebindq, entry) { + if (e->button == mb->press.button && e->state == mb->modmask) + break; + } + if (mb == NULL) + return; + mb->cargs->xev = CWM_XEV_BTN; + switch (mb->context) { + case CWM_CONTEXT_CC: + if (((cc = client_find(e->window)) == NULL) && + ((cc = client_current(sc)) == NULL)) + return; + (*mb->callback)(cc, mb->cargs); + break; + case CWM_CONTEXT_SC: + (*mb->callback)(sc, mb->cargs); + break; + case CWM_CONTEXT_NONE: + (*mb->callback)(NULL, mb->cargs); + break; + } +} + +static void +xev_handle_buttonrelease(XEvent *ee) +{ + XButtonEvent *e = &ee->xbutton; + struct client_ctx *cc; + + LOG_DEBUG3("root: 0x%lx window: 0x%lx subwindow: 0x%lx", + e->root, e->window, e->subwindow); + + if ((cc = client_find(e->window)) != NULL) { + if (cc->flags & (CLIENT_ACTIVE | CLIENT_HIGHLIGHT)) { + cc->flags &= ~CLIENT_HIGHLIGHT; + client_draw_border(cc); + } + } +} + +static void +xev_handle_keypress(XEvent *ee) +{ + XKeyEvent *e = &ee->xkey; + struct client_ctx *cc; + struct screen_ctx *sc; + struct bind_ctx *kb; + KeySym keysym, skeysym; + unsigned int modshift; + + LOG_DEBUG3("root: 0x%lx window: 0x%lx subwindow: 0x%lx", + e->root, e->window, e->subwindow); + + if ((sc = screen_find(e->root)) == NULL) + return; + + keysym = XkbKeycodeToKeysym(X_Dpy, e->keycode, 0, 0); + skeysym = XkbKeycodeToKeysym(X_Dpy, e->keycode, 0, 1); + + e->state &= ~IGNOREMODMASK; + + TAILQ_FOREACH(kb, &Conf.keybindq, entry) { + if (keysym != kb->press.keysym && skeysym == kb->press.keysym) + modshift = ShiftMask; + else + modshift = 0; + + if ((kb->modmask | modshift) != e->state) + continue; + + if (kb->press.keysym == ((modshift == 0) ? keysym : skeysym)) + break; + } + if (kb == NULL) + return; + kb->cargs->xev = CWM_XEV_KEY; + switch (kb->context) { + case CWM_CONTEXT_CC: + if (((cc = client_find(e->subwindow)) == NULL) && + ((cc = client_current(sc)) == NULL)) + return; + (*kb->callback)(cc, kb->cargs); + break; + case CWM_CONTEXT_SC: + (*kb->callback)(sc, kb->cargs); + break; + case CWM_CONTEXT_NONE: + (*kb->callback)(NULL, kb->cargs); + break; + } +} + +/* + * This is only used for the modifier suppression detection. + */ +static void +xev_handle_keyrelease(XEvent *ee) +{ + XKeyEvent *e = &ee->xkey; + struct screen_ctx *sc; + struct client_ctx *cc; + KeySym keysym; + unsigned int i; + + LOG_DEBUG3("root: 0x%lx window: 0x%lx subwindow: 0x%lx", + e->root, e->window, e->subwindow); + + if ((sc = screen_find(e->root)) == NULL) + return; + + keysym = XkbKeycodeToKeysym(X_Dpy, e->keycode, 0, 0); + for (i = 0; i < nitems(modkeys); i++) { + if (keysym == modkeys[i]) { + if ((cc = client_current(sc)) != NULL) { + if (sc->cycling) { + sc->cycling = 0; + client_mtf(cc); + } + if (cc->flags & CLIENT_HIGHLIGHT) { + cc->flags &= ~CLIENT_HIGHLIGHT; + client_draw_border(cc); + } + } + XUngrabKeyboard(X_Dpy, CurrentTime); + break; + } + } +} + +static void +xev_handle_clientmessage(XEvent *ee) +{ + XClientMessageEvent *e = &ee->xclient; + struct client_ctx *cc, *old_cc; + struct screen_ctx *sc; + + LOG_DEBUG3("window: 0x%lx", e->window); + + if (e->message_type == cwmh[WM_CHANGE_STATE]) { + if ((cc = client_find(e->window)) != NULL) { + if (e->data.l[0] == IconicState) + client_hide(cc); + } + } else if (e->message_type == ewmh[_NET_CLOSE_WINDOW]) { + if ((cc = client_find(e->window)) != NULL) { + client_close(cc); + } + } else if (e->message_type == ewmh[_NET_ACTIVE_WINDOW]) { + if ((cc = client_find(e->window)) != NULL) { + if ((old_cc = client_current(NULL)) != NULL) + client_ptr_save(old_cc); + client_show(cc); + client_ptr_warp(cc); + } + } else if (e->message_type == ewmh[_NET_WM_DESKTOP]) { + if ((cc = client_find(e->window)) != NULL) { + /* + * The EWMH spec states that if the cardinal returned + * is 0xFFFFFFFF (-1) then the window should appear + * on all desktops, in our case, group 0. + */ + if (e->data.l[0] == (unsigned long)-1) + group_movetogroup(cc, 0); + else + if (e->data.l[0] >= 0 && + e->data.l[0] < Conf.ngroups) + group_movetogroup(cc, e->data.l[0]); + } + } else if (e->message_type == ewmh[_NET_WM_STATE]) { + if ((cc = client_find(e->window)) != NULL) { + xu_ewmh_handle_net_wm_state_msg(cc, + e->data.l[0], e->data.l[1], e->data.l[2]); + } + } else if (e->message_type == ewmh[_NET_CURRENT_DESKTOP]) { + if ((sc = screen_find(e->window)) != NULL) { + if (e->data.l[0] >= 0 && + e->data.l[0] < Conf.ngroups) + group_only(sc, e->data.l[0]); + } + } +} + +static void +xev_handle_randr(XEvent *ee) +{ + XRRScreenChangeNotifyEvent *e = (XRRScreenChangeNotifyEvent *)ee; + struct screen_ctx *sc; + + LOG_DEBUG3("size: %d/%d", e->width, e->height); + + if ((sc = screen_find(e->root)) == NULL) + return; + + XRRUpdateConfiguration(ee); + screen_update_geometry(sc); + screen_assert_clients_within(sc); +} + +/* + * Called when the keymap has changed. + * Ungrab all keys, reload keymap and then regrab + */ +static void +xev_handle_mappingnotify(XEvent *ee) +{ + XMappingEvent *e = &ee->xmapping; + struct screen_ctx *sc; + + LOG_DEBUG3("window: 0x%lx", e->window); + + XRefreshKeyboardMapping(e); + if (e->request == MappingKeyboard) { + TAILQ_FOREACH(sc, &Screenq, entry) + conf_grab_kbd(sc->rootwin); + } +} + +static void +xev_handle_expose(XEvent *ee) +{ + XExposeEvent *e = &ee->xexpose; + struct client_ctx *cc; + + LOG_DEBUG3("window: 0x%lx", e->window); + + if ((cc = client_find(e->window)) != NULL && e->count == 0) + client_draw_border(cc); +} + +void +xev_process(void) +{ + XEvent e; + + while (XPending(X_Dpy)) { + XNextEvent(X_Dpy, &e); + if ((e.type - Conf.xrandr_event_base) == RRScreenChangeNotify) + xev_handle_randr(&e); + else if ((e.type < LASTEvent) && (xev_handlers[e.type] != NULL)) + (*xev_handlers[e.type])(&e); + } +} diff --git a/xmalloc.c b/xmalloc.c new file mode 100644 index 0000000..f4bfee8 --- /dev/null +++ b/xmalloc.c @@ -0,0 +1,110 @@ +/* + * calmwm - the calm window manager + * + * Copyright (c) 2004 Marius Aamodt Eriksen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $OpenBSD: xmalloc.c,v 1.17 2020/02/27 14:56:39 okan Exp $ + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "calmwm.h" + +void * +xmalloc(size_t siz) +{ + void *p; + + if (siz == 0) + errx(1, "xmalloc: zero size"); + if ((p = malloc(siz)) == NULL) + err(1, "malloc"); + + return p; +} + +void * +xcalloc(size_t no, size_t siz) +{ + void *p; + + if (siz == 0 || no == 0) + errx(1, "xcalloc: zero size"); + if (SIZE_MAX / no < siz) + errx(1, "xcalloc: no * siz > SIZE_MAX"); + if ((p = calloc(no, siz)) == NULL) + err(1, "calloc"); + + return p; +} + +void * +xreallocarray(void *ptr, size_t nmemb, size_t size) +{ + void *p; + + p = reallocarray(ptr, nmemb, size); + if (p == NULL) + errx(1, "xreallocarray: out of memory (new_size %zu bytes)", + nmemb * size); + return p; +} + +char * +xstrdup(const char *str) +{ + char *p; + + if ((p = strdup(str)) == NULL) + err(1, "strdup"); + + return p; +} + +int +xasprintf(char **ret, const char *fmt, ...) +{ + va_list ap; + int i; + + va_start(ap, fmt); + i = xvasprintf(ret, fmt, ap); + va_end(ap); + + return i; +} + +int +xvasprintf(char **ret, const char *fmt, va_list ap) +{ + int i; + + i = vasprintf(ret, fmt, ap); + if (i == -1) + err(1, "vasprintf"); + + return i; +} diff --git a/xutil.c b/xutil.c new file mode 100644 index 0000000..8d92b50 --- /dev/null +++ b/xutil.c @@ -0,0 +1,568 @@ +/* + * calmwm - the calm window manager + * + * Copyright (c) 2004 Marius Aamodt Eriksen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $OpenBSD: xutil.c,v 1.113 2020/04/25 20:07:28 tobias Exp $ + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "calmwm.h" + +void +xu_ptr_get(Window win, int *x, int *y) +{ + Window w0, w1; + int tmp0, tmp1; + unsigned int tmp2; + + XQueryPointer(X_Dpy, win, &w0, &w1, &tmp0, &tmp1, x, y, &tmp2); +} + +void +xu_ptr_set(Window win, int x, int y) +{ + XWarpPointer(X_Dpy, None, win, 0, 0, 0, 0, x, y); +} + +int +xu_get_prop(Window win, Atom atm, Atom type, long len, unsigned char **p) +{ + Atom realtype; + unsigned long n, extra; + int format; + + if (XGetWindowProperty(X_Dpy, win, atm, 0L, len, False, type, + &realtype, &format, &n, &extra, p) != Success || *p == NULL) + return -1; + + if (n == 0) + XFree(*p); + + return n; +} + +int +xu_get_strprop(Window win, Atom atm, char **text) { + XTextProperty prop; + char **list; + int nitems = 0; + + *text = NULL; + + XGetTextProperty(X_Dpy, win, &prop, atm); + if (!prop.nitems) { + XFree(prop.value); + return 0; + } + + if (Xutf8TextPropertyToTextList(X_Dpy, &prop, &list, + &nitems) == Success && nitems > 0 && *list) { + if (nitems > 1) { + XTextProperty prop2; + if (Xutf8TextListToTextProperty(X_Dpy, list, nitems, + XUTF8StringStyle, &prop2) == Success) { + *text = xstrdup((const char *)prop2.value); + XFree(prop2.value); + } + } else { + *text = xstrdup(*list); + } + XFreeStringList(list); + } + XFree(prop.value); + + return nitems; +} + +void +xu_send_clientmsg(Window win, Atom proto, Time ts) +{ + XClientMessageEvent cm; + + (void)memset(&cm, 0, sizeof(cm)); + cm.type = ClientMessage; + cm.window = win; + cm.message_type = cwmh[WM_PROTOCOLS]; + cm.format = 32; + cm.data.l[0] = proto; + cm.data.l[1] = ts; + + XSendEvent(X_Dpy, win, False, NoEventMask, (XEvent *)&cm); +} + +void +xu_get_wm_state(Window win, long *state) +{ + long *p; + + *state = -1; + if (xu_get_prop(win, cwmh[WM_STATE], cwmh[WM_STATE], 2L, + (unsigned char **)&p) > 0) { + *state = *p; + XFree(p); + } +} + +void +xu_set_wm_state(Window win, long state) +{ + long data[] = { state, None }; + + XChangeProperty(X_Dpy, win, cwmh[WM_STATE], cwmh[WM_STATE], 32, + PropModeReplace, (unsigned char *)data, 2); +} +void +xu_xorcolor(XftColor a, XftColor b, XftColor *r) +{ + r->pixel = a.pixel ^ b.pixel; + r->color.red = a.color.red ^ b.color.red; + r->color.green = a.color.green ^ b.color.green; + r->color.blue = a.color.blue ^ b.color.blue; + r->color.alpha = 0xffff; +} + +void +xu_atom_init(void) +{ + char *cwmhints[] = { + "WM_STATE", + "WM_DELETE_WINDOW", + "WM_TAKE_FOCUS", + "WM_PROTOCOLS", + "_MOTIF_WM_HINTS", + "UTF8_STRING", + "WM_CHANGE_STATE", + }; + char *ewmhints[] = { + "_NET_SUPPORTED", + "_NET_SUPPORTING_WM_CHECK", + "_NET_ACTIVE_WINDOW", + "_NET_CLIENT_LIST", + "_NET_CLIENT_LIST_STACKING", + "_NET_NUMBER_OF_DESKTOPS", + "_NET_CURRENT_DESKTOP", + "_NET_DESKTOP_VIEWPORT", + "_NET_DESKTOP_GEOMETRY", + "_NET_VIRTUAL_ROOTS", + "_NET_SHOWING_DESKTOP", + "_NET_DESKTOP_NAMES", + "_NET_WORKAREA", + "_NET_WM_NAME", + "_NET_WM_DESKTOP", + "_NET_CLOSE_WINDOW", + "_NET_WM_STATE", + "_NET_WM_STATE_STICKY", + "_NET_WM_STATE_MAXIMIZED_VERT", + "_NET_WM_STATE_MAXIMIZED_HORZ", + "_NET_WM_STATE_HIDDEN", + "_NET_WM_STATE_FULLSCREEN", + "_NET_WM_STATE_DEMANDS_ATTENTION", + "_NET_WM_STATE_SKIP_PAGER", + "_NET_WM_STATE_SKIP_TASKBAR", + "_CWM_WM_STATE_FREEZE", + }; + + XInternAtoms(X_Dpy, cwmhints, nitems(cwmhints), False, cwmh); + XInternAtoms(X_Dpy, ewmhints, nitems(ewmhints), False, ewmh); +} + +/* Root Window Properties */ +void +xu_ewmh_net_supported(struct screen_ctx *sc) +{ + XChangeProperty(X_Dpy, sc->rootwin, ewmh[_NET_SUPPORTED], + XA_ATOM, 32, PropModeReplace, (unsigned char *)ewmh, EWMH_NITEMS); +} + +void +xu_ewmh_net_supported_wm_check(struct screen_ctx *sc) +{ + Window w; + + w = XCreateSimpleWindow(X_Dpy, sc->rootwin, -1, -1, 1, 1, 0, 0, 0); + XChangeProperty(X_Dpy, sc->rootwin, ewmh[_NET_SUPPORTING_WM_CHECK], + XA_WINDOW, 32, PropModeReplace, (unsigned char *)&w, 1); + XChangeProperty(X_Dpy, w, ewmh[_NET_SUPPORTING_WM_CHECK], + XA_WINDOW, 32, PropModeReplace, (unsigned char *)&w, 1); + XChangeProperty(X_Dpy, w, ewmh[_NET_WM_NAME], + cwmh[UTF8_STRING], 8, PropModeReplace, + (unsigned char *)Conf.wmname, strlen(Conf.wmname)); +} + +void +xu_ewmh_net_desktop_geometry(struct screen_ctx *sc) +{ + long geom[2] = { sc->view.w, sc->view.h }; + + XChangeProperty(X_Dpy, sc->rootwin, ewmh[_NET_DESKTOP_GEOMETRY], + XA_CARDINAL, 32, PropModeReplace, (unsigned char *)geom , 2); +} + +void +xu_ewmh_net_desktop_viewport(struct screen_ctx *sc) +{ + long viewports[2] = {0, 0}; + + /* We don't support large desktops, so this is (0, 0). */ + XChangeProperty(X_Dpy, sc->rootwin, ewmh[_NET_DESKTOP_VIEWPORT], + XA_CARDINAL, 32, PropModeReplace, (unsigned char *)viewports, 2); +} + +void +xu_ewmh_net_workarea(struct screen_ctx *sc) +{ + unsigned long *workarea; + int i, ngroups = Conf.ngroups; + + workarea = xreallocarray(NULL, ngroups * 4, sizeof(unsigned long)); + for (i = 0; i < ngroups; i++) { + workarea[4 * i + 0] = sc->work.x; + workarea[4 * i + 1] = sc->work.y; + workarea[4 * i + 2] = sc->work.w; + workarea[4 * i + 3] = sc->work.h; + } + XChangeProperty(X_Dpy, sc->rootwin, ewmh[_NET_WORKAREA], + XA_CARDINAL, 32, PropModeReplace, (unsigned char *)workarea, + ngroups * 4); + free(workarea); +} + +void +xu_ewmh_net_client_list(struct screen_ctx *sc) +{ + struct client_ctx *cc; + Window *winlist; + int i = 0, j = 0; + + TAILQ_FOREACH(cc, &sc->clientq, entry) + i++; + if (i == 0) + return; + + winlist = xreallocarray(NULL, i, sizeof(*winlist)); + TAILQ_FOREACH(cc, &sc->clientq, entry) + winlist[j++] = cc->win; + XChangeProperty(X_Dpy, sc->rootwin, ewmh[_NET_CLIENT_LIST], + XA_WINDOW, 32, PropModeReplace, (unsigned char *)winlist, i); + free(winlist); +} + +void +xu_ewmh_net_client_list_stacking(struct screen_ctx *sc) +{ + struct client_ctx *cc; + Window *winlist; + int i = 0, j; + + TAILQ_FOREACH(cc, &sc->clientq, entry) + i++; + if (i == 0) + return; + + j = i; + winlist = xreallocarray(NULL, i, sizeof(*winlist)); + TAILQ_FOREACH(cc, &sc->clientq, entry) + winlist[--j] = cc->win; + XChangeProperty(X_Dpy, sc->rootwin, ewmh[_NET_CLIENT_LIST_STACKING], + XA_WINDOW, 32, PropModeReplace, (unsigned char *)winlist, i); + free(winlist); +} + +void +xu_ewmh_net_active_window(struct screen_ctx *sc, Window w) +{ + XChangeProperty(X_Dpy, sc->rootwin, ewmh[_NET_ACTIVE_WINDOW], + XA_WINDOW, 32, PropModeReplace, (unsigned char *)&w, 1); +} + +void +xu_ewmh_net_number_of_desktops(struct screen_ctx *sc) +{ + long ndesks = Conf.ngroups; + + XChangeProperty(X_Dpy, sc->rootwin, ewmh[_NET_NUMBER_OF_DESKTOPS], + XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&ndesks, 1); +} + +void +xu_ewmh_net_showing_desktop(struct screen_ctx *sc) +{ + long zero = 0; + + /* We don't support `showing desktop' mode, so this is zero. + * Note that when we hide all groups, or when all groups are + * hidden we could technically set this later on. + */ + XChangeProperty(X_Dpy, sc->rootwin, ewmh[_NET_SHOWING_DESKTOP], + XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&zero, 1); +} + +void +xu_ewmh_net_virtual_roots(struct screen_ctx *sc) +{ + /* We don't support virtual roots, so delete if set by previous wm. */ + XDeleteProperty(X_Dpy, sc->rootwin, ewmh[_NET_VIRTUAL_ROOTS]); +} + +void +xu_ewmh_net_current_desktop(struct screen_ctx *sc) +{ + long num = sc->group_active->num; + + XChangeProperty(X_Dpy, sc->rootwin, ewmh[_NET_CURRENT_DESKTOP], + XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&num, 1); +} + +void +xu_ewmh_net_desktop_names(struct screen_ctx *sc) +{ + struct group_ctx *gc; + char *p, *q; + unsigned char *prop_ret; + int i = 0, j = 0, nstrings = 0, n = 0; + size_t len = 0, tlen, slen; + + /* Let group names be overwritten if _NET_DESKTOP_NAMES is set. */ + + if ((j = xu_get_prop(sc->rootwin, ewmh[_NET_DESKTOP_NAMES], + cwmh[UTF8_STRING], 0xffffff, (unsigned char **)&prop_ret)) > 0) { + prop_ret[j - 1] = '\0'; /* paranoia */ + while (i < j) { + if (prop_ret[i++] == '\0') + nstrings++; + } + } + + p = (char *)prop_ret; + while (n < nstrings) { + TAILQ_FOREACH(gc, &sc->groupq, entry) { + if (gc->num == n) { + free(gc->name); + gc->name = xstrdup(p); + p += strlen(p) + 1; + break; + } + } + n++; + } + if (prop_ret != NULL) + XFree(prop_ret); + + TAILQ_FOREACH(gc, &sc->groupq, entry) + len += strlen(gc->name) + 1; + q = p = xreallocarray(NULL, len, sizeof(*p)); + + tlen = len; + TAILQ_FOREACH(gc, &sc->groupq, entry) { + slen = strlen(gc->name) + 1; + (void)strlcpy(q, gc->name, tlen); + tlen -= slen; + q += slen; + } + + XChangeProperty(X_Dpy, sc->rootwin, ewmh[_NET_DESKTOP_NAMES], + cwmh[UTF8_STRING], 8, PropModeReplace, (unsigned char *)p, len); + free(p); +} + +/* Application Window Properties */ +int +xu_ewmh_get_net_wm_desktop(struct client_ctx *cc, long *n) +{ + long *p; + + if (xu_get_prop(cc->win, ewmh[_NET_WM_DESKTOP], XA_CARDINAL, 1L, + (unsigned char **)&p) <= 0) + return 0; + *n = *p; + XFree(p); + return 1; +} + +void +xu_ewmh_set_net_wm_desktop(struct client_ctx *cc) +{ + long num = 0xffffffff; + + if (cc->gc) + num = cc->gc->num; + + XChangeProperty(X_Dpy, cc->win, ewmh[_NET_WM_DESKTOP], + XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&num, 1); +} + +Atom * +xu_ewmh_get_net_wm_state(struct client_ctx *cc, int *n) +{ + Atom *state, *p = NULL; + + if ((*n = xu_get_prop(cc->win, ewmh[_NET_WM_STATE], XA_ATOM, 64L, + (unsigned char **)&p)) <= 0) + return NULL; + + state = xreallocarray(NULL, *n, sizeof(Atom)); + (void)memcpy(state, p, *n * sizeof(Atom)); + XFree((char *)p); + + return state; +} + +void +xu_ewmh_handle_net_wm_state_msg(struct client_ctx *cc, int action, + Atom first, Atom second) +{ + unsigned int i; + struct handlers { + Atom atom; + int flag; + void (*toggle)(struct client_ctx *); + } handlers[] = { + { _NET_WM_STATE_STICKY, + CLIENT_STICKY, + client_toggle_sticky }, + { _NET_WM_STATE_MAXIMIZED_VERT, + CLIENT_VMAXIMIZED, + client_toggle_vmaximize }, + { _NET_WM_STATE_MAXIMIZED_HORZ, + CLIENT_HMAXIMIZED, + client_toggle_hmaximize }, + { _NET_WM_STATE_HIDDEN, + CLIENT_HIDDEN, + client_toggle_hidden }, + { _NET_WM_STATE_FULLSCREEN, + CLIENT_FULLSCREEN, + client_toggle_fullscreen }, + { _NET_WM_STATE_DEMANDS_ATTENTION, + CLIENT_URGENCY, + client_urgency }, + { _NET_WM_STATE_SKIP_PAGER, + CLIENT_SKIP_PAGER, + client_toggle_skip_pager}, + { _NET_WM_STATE_SKIP_TASKBAR, + CLIENT_SKIP_TASKBAR, + client_toggle_skip_taskbar}, + { _CWM_WM_STATE_FREEZE, + CLIENT_FREEZE, + client_toggle_freeze }, + }; + + for (i = 0; i < nitems(handlers); i++) { + if (first != ewmh[handlers[i].atom] && + second != ewmh[handlers[i].atom]) + continue; + switch (action) { + case _NET_WM_STATE_ADD: + if (!(cc->flags & handlers[i].flag)) + handlers[i].toggle(cc); + break; + case _NET_WM_STATE_REMOVE: + if (cc->flags & handlers[i].flag) + handlers[i].toggle(cc); + break; + case _NET_WM_STATE_TOGGLE: + handlers[i].toggle(cc); + } + } +} + +void +xu_ewmh_restore_net_wm_state(struct client_ctx *cc) +{ + Atom *atoms; + int i, n; + + atoms = xu_ewmh_get_net_wm_state(cc, &n); + for (i = 0; i < n; i++) { + if (atoms[i] == ewmh[_NET_WM_STATE_STICKY]) + client_toggle_sticky(cc); + if (atoms[i] == ewmh[_NET_WM_STATE_MAXIMIZED_VERT]) + client_toggle_vmaximize(cc); + if (atoms[i] == ewmh[_NET_WM_STATE_MAXIMIZED_HORZ]) + client_toggle_hmaximize(cc); + if (atoms[i] == ewmh[_NET_WM_STATE_HIDDEN]) + client_toggle_hidden(cc); + if (atoms[i] == ewmh[_NET_WM_STATE_FULLSCREEN]) + client_toggle_fullscreen(cc); + if (atoms[i] == ewmh[_NET_WM_STATE_DEMANDS_ATTENTION]) + client_urgency(cc); + if (atoms[i] == ewmh[_NET_WM_STATE_SKIP_PAGER]) + client_toggle_skip_pager(cc); + if (atoms[i] == ewmh[_NET_WM_STATE_SKIP_TASKBAR]) + client_toggle_skip_taskbar(cc); + if (atoms[i] == ewmh[_CWM_WM_STATE_FREEZE]) + client_toggle_freeze(cc); + } + free(atoms); +} + +void +xu_ewmh_set_net_wm_state(struct client_ctx *cc) +{ + Atom *atoms, *oatoms; + int n, i, j; + + oatoms = xu_ewmh_get_net_wm_state(cc, &n); + atoms = xreallocarray(NULL, (n + _NET_WM_STATES_NITEMS), sizeof(Atom)); + for (i = j = 0; i < n; i++) { + if (oatoms[i] != ewmh[_NET_WM_STATE_STICKY] && + oatoms[i] != ewmh[_NET_WM_STATE_MAXIMIZED_VERT] && + oatoms[i] != ewmh[_NET_WM_STATE_MAXIMIZED_HORZ] && + oatoms[i] != ewmh[_NET_WM_STATE_HIDDEN] && + oatoms[i] != ewmh[_NET_WM_STATE_FULLSCREEN] && + oatoms[i] != ewmh[_NET_WM_STATE_DEMANDS_ATTENTION] && + oatoms[i] != ewmh[_NET_WM_STATE_SKIP_PAGER] && + oatoms[i] != ewmh[_NET_WM_STATE_SKIP_TASKBAR] && + oatoms[i] != ewmh[_CWM_WM_STATE_FREEZE]) + atoms[j++] = oatoms[i]; + } + free(oatoms); + if (cc->flags & CLIENT_STICKY) + atoms[j++] = ewmh[_NET_WM_STATE_STICKY]; + if (cc->flags & CLIENT_HIDDEN) + atoms[j++] = ewmh[_NET_WM_STATE_HIDDEN]; + if (cc->flags & CLIENT_FULLSCREEN) + atoms[j++] = ewmh[_NET_WM_STATE_FULLSCREEN]; + else { + if (cc->flags & CLIENT_VMAXIMIZED) + atoms[j++] = ewmh[_NET_WM_STATE_MAXIMIZED_VERT]; + if (cc->flags & CLIENT_HMAXIMIZED) + atoms[j++] = ewmh[_NET_WM_STATE_MAXIMIZED_HORZ]; + } + if (cc->flags & CLIENT_URGENCY) + atoms[j++] = ewmh[_NET_WM_STATE_DEMANDS_ATTENTION]; + if (cc->flags & CLIENT_SKIP_PAGER) + atoms[j++] = ewmh[_NET_WM_STATE_SKIP_PAGER]; + if (cc->flags & CLIENT_SKIP_TASKBAR) + atoms[j++] = ewmh[_NET_WM_STATE_SKIP_TASKBAR]; + if (cc->flags & CLIENT_FREEZE) + atoms[j++] = ewmh[_CWM_WM_STATE_FREEZE]; + if (j > 0) + XChangeProperty(X_Dpy, cc->win, ewmh[_NET_WM_STATE], + XA_ATOM, 32, PropModeReplace, (unsigned char *)atoms, j); + else + XDeleteProperty(X_Dpy, cc->win, ewmh[_NET_WM_STATE]); + free(atoms); +}