commit 3765d5af5024b24a593f6f213ad2852ce9617fe9 Author: afiw Date: Sun Jan 18 19:20:14 2026 +0100 Import OpenBSD 7.8 source 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); +}