mirror of
https://github.com/vale981/spectrwm
synced 2025-03-05 18:01:37 -05:00

Change validation behavior for default 'optional' applications. Overriding the binding will no longer cause validation. Validation will only occur when the program call is overridden. Add details in spectrwm.conf on how to disable the default programs. Improve man page PROGRAMS section. Added details on default program validation and how to disable/override.
9482 lines
230 KiB
C
9482 lines
230 KiB
C
/*
|
|
* Copyright (c) 2009-2012 Marco Peereboom <marco@peereboom.us>
|
|
* Copyright (c) 2009-2011 Ryan McBride <mcbride@countersiege.com>
|
|
* Copyright (c) 2009 Darrin Chandler <dwchandler@stilyagin.com>
|
|
* Copyright (c) 2009 Pierre-Yves Ritschard <pyr@spootnik.org>
|
|
* Copyright (c) 2010 Tuukka Kataja <stuge@xor.fi>
|
|
* Copyright (c) 2011 Jason L. Wright <jason@thought.net>
|
|
* Copyright (c) 2011-2012 Reginald Kennedy <rk@rejii.com>
|
|
* Copyright (c) 2011-2012 Lawrence Teo <lteo@lteo.net>
|
|
* Copyright (c) 2011-2012 Tiago Cunha <tcunha@gmx.com>
|
|
* Copyright (c) 2012 David Hill <dhill@mindcry.org>
|
|
*
|
|
* 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.
|
|
*/
|
|
/*
|
|
* Much code and ideas taken from dwm under the following license:
|
|
* MIT/X Consortium License
|
|
*
|
|
* 2006-2008 Anselm R Garbe <garbeam at gmail dot com>
|
|
* 2006-2007 Sander van Dijk <a dot h dot vandijk at gmail dot com>
|
|
* 2006-2007 Jukka Salmi <jukka at salmi dot ch>
|
|
* 2007 Premysl Hruby <dfenze at gmail dot com>
|
|
* 2007 Szabolcs Nagy <nszabolcs at gmail dot com>
|
|
* 2007 Christof Musik <christof at sendfax dot de>
|
|
* 2007-2008 Enno Gottox Boland <gottox at s01 dot de>
|
|
* 2007-2008 Peter Hartlich <sgkkr at hartlich dot com>
|
|
* 2008 Martin Hurton <martin dot hurton at gmail dot com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
/* kernel includes */
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/param.h>
|
|
#include <sys/select.h>
|
|
#if defined(__linux__)
|
|
#include "tree.h"
|
|
#elif defined(__OpenBSD__)
|
|
#include <sys/tree.h>
|
|
#elif defined(__FreeBSD__)
|
|
#include <sys/tree.h>
|
|
#else
|
|
#include "tree.h"
|
|
#endif
|
|
|
|
/* /usr/includes */
|
|
#include <ctype.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <locale.h>
|
|
#include <paths.h>
|
|
#include <pwd.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <util.h>
|
|
#include <X11/cursorfont.h>
|
|
#include <X11/extensions/Xrandr.h>
|
|
#include <X11/Xcursor/Xcursor.h>
|
|
#include <X11/Xft/Xft.h>
|
|
#include <X11/Xlib-xcb.h>
|
|
#include <xcb/xcb_atom.h>
|
|
#include <xcb/xcb_aux.h>
|
|
#include <xcb/xcb_event.h>
|
|
#include <xcb/xcb_icccm.h>
|
|
#include <xcb/xcb_keysyms.h>
|
|
#include <xcb/xtest.h>
|
|
#include <xcb/randr.h>
|
|
|
|
/* local includes */
|
|
#include "version.h"
|
|
#ifdef __OSX__
|
|
#include <osx.h>
|
|
#endif
|
|
|
|
#ifdef SPECTRWM_BUILDSTR
|
|
static const char *buildstr = SPECTRWM_BUILDSTR;
|
|
#else
|
|
static const char *buildstr = SPECTRWM_VERSION;
|
|
#endif
|
|
|
|
#if !defined(__CYGWIN__) /* cygwin chokes on xrandr stuff */
|
|
# if RANDR_MAJOR < 1
|
|
# error XRandR versions less than 1.0 are not supported
|
|
#endif
|
|
|
|
# if RANDR_MAJOR >= 1
|
|
# if RANDR_MINOR >= 2
|
|
# define SWM_XRR_HAS_CRTC
|
|
# endif
|
|
# endif
|
|
#endif /* __CYGWIN__ */
|
|
|
|
#ifndef XCB_ICCCM_NUM_WM_HINTS_ELEMENTS
|
|
#define XCB_ICCCM_SIZE_HINT_P_MIN_SIZE XCB_SIZE_HINT_P_MIN_SIZE
|
|
#define XCB_ICCCM_SIZE_HINT_P_MAX_SIZE XCB_SIZE_HINT_P_MAX_SIZE
|
|
#define XCB_ICCCM_SIZE_HINT_P_RESIZE_INC XCB_SIZE_HINT_P_RESIZE_INC
|
|
#define XCB_ICCCM_WM_HINT_INPUT XCB_WM_HINT_INPUT
|
|
#define XCB_ICCCM_WM_HINT_X_URGENCY XCB_WM_HINT_X_URGENCY
|
|
#define XCB_ICCCM_WM_STATE_ICONIC XCB_WM_STATE_ICONIC
|
|
#define XCB_ICCCM_WM_STATE_WITHDRAWN XCB_WM_STATE_WITHDRAWN
|
|
#define XCB_ICCCM_WM_STATE_NORMAL XCB_WM_STATE_NORMAL
|
|
#define xcb_icccm_get_text_property_reply_t xcb_get_text_property_reply_t
|
|
#define xcb_icccm_get_text_property_reply_wipe xcb_get_text_property_reply_wipe
|
|
#define xcb_icccm_get_wm_class xcb_get_wm_class
|
|
#define xcb_icccm_get_wm_class_reply xcb_get_wm_class_reply
|
|
#define xcb_icccm_get_wm_class_reply_t xcb_get_wm_class_reply_t
|
|
#define xcb_icccm_get_wm_class_reply_wipe xcb_get_wm_class_reply_wipe
|
|
#define xcb_icccm_get_wm_hints xcb_get_wm_hints
|
|
#define xcb_icccm_get_wm_hints_reply xcb_get_wm_hints_reply
|
|
#define xcb_icccm_get_wm_name xcb_get_wm_name
|
|
#define xcb_icccm_get_wm_name_reply xcb_get_wm_name_reply
|
|
#define xcb_icccm_get_wm_normal_hints xcb_get_wm_normal_hints
|
|
#define xcb_icccm_get_wm_normal_hints_reply xcb_get_wm_normal_hints_reply
|
|
#define xcb_icccm_get_wm_protocols xcb_get_wm_protocols
|
|
#define xcb_icccm_get_wm_protocols_reply xcb_get_wm_protocols_reply
|
|
#define xcb_icccm_get_wm_protocols_reply_t xcb_get_wm_protocols_reply_t
|
|
#define xcb_icccm_get_wm_protocols_reply_wipe xcb_get_wm_protocols_reply_wipe
|
|
#define xcb_icccm_get_wm_transient_for xcb_get_wm_transient_for
|
|
#define xcb_icccm_get_wm_transient_for_reply xcb_get_wm_transient_for_reply
|
|
#define xcb_icccm_wm_hints_t xcb_wm_hints_t
|
|
#endif
|
|
|
|
/*#define SWM_DEBUG*/
|
|
#ifdef SWM_DEBUG
|
|
#define DPRINTF(x...) do { \
|
|
if (swm_debug) \
|
|
fprintf(stderr, x); \
|
|
} while (0)
|
|
#define DNPRINTF(n,x...) do { \
|
|
if (swm_debug & n) { \
|
|
fprintf(stderr, "%ld ", (long)(time(NULL) - time_started)); \
|
|
fprintf(stderr, x); \
|
|
} \
|
|
} while (0)
|
|
#define SWM_D_MISC 0x0001
|
|
#define SWM_D_EVENT 0x0002
|
|
#define SWM_D_WS 0x0004
|
|
#define SWM_D_FOCUS 0x0008
|
|
#define SWM_D_MOVE 0x0010
|
|
#define SWM_D_STACK 0x0020
|
|
#define SWM_D_MOUSE 0x0040
|
|
#define SWM_D_PROP 0x0080
|
|
#define SWM_D_CLASS 0x0100
|
|
#define SWM_D_KEY 0x0200
|
|
#define SWM_D_QUIRK 0x0400
|
|
#define SWM_D_SPAWN 0x0800
|
|
#define SWM_D_EVENTQ 0x1000
|
|
#define SWM_D_CONF 0x2000
|
|
#define SWM_D_BAR 0x4000
|
|
#define SWM_D_INIT 0x8000
|
|
|
|
u_int32_t swm_debug = 0
|
|
| SWM_D_MISC
|
|
| SWM_D_EVENT
|
|
| SWM_D_WS
|
|
| SWM_D_FOCUS
|
|
| SWM_D_MOVE
|
|
| SWM_D_STACK
|
|
| SWM_D_MOUSE
|
|
| SWM_D_PROP
|
|
| SWM_D_CLASS
|
|
| SWM_D_KEY
|
|
| SWM_D_QUIRK
|
|
| SWM_D_SPAWN
|
|
| SWM_D_EVENTQ
|
|
| SWM_D_CONF
|
|
| SWM_D_BAR
|
|
| SWM_D_INIT
|
|
;
|
|
#else
|
|
#define DPRINTF(x...)
|
|
#define DNPRINTF(n,x...)
|
|
#endif
|
|
|
|
/* convert 8-bit to 16-bit */
|
|
#define RGB_8_TO_16(col) ((col) << 8) + (col)
|
|
|
|
#define PIXEL_TO_XRENDERCOLOR(px, xrc) \
|
|
xrc.red = RGB_8_TO_16((px) >> 16 & 0xff); \
|
|
xrc.green = RGB_8_TO_16((px) >> 8 & 0xff); \
|
|
xrc.blue = RGB_8_TO_16((px) & 0xff); \
|
|
xrc.alpha = 0xffff;
|
|
|
|
#define LENGTH(x) (int)(sizeof (x) / sizeof (x)[0])
|
|
#define MODKEY XCB_MOD_MASK_1
|
|
#define CLEANMASK(mask) ((mask) & ~(numlockmask | XCB_MOD_MASK_LOCK))
|
|
#define BUTTONMASK (XCB_EVENT_MASK_BUTTON_PRESS | \
|
|
XCB_EVENT_MASK_BUTTON_RELEASE)
|
|
#define MOUSEMASK (BUTTONMASK|XCB_EVENT_MASK_POINTER_MOTION)
|
|
#define SWM_PROPLEN (16)
|
|
#define SWM_FUNCNAME_LEN (32)
|
|
#define SWM_KEYS_LEN (255)
|
|
#define SWM_QUIRK_LEN (64)
|
|
#define X(r) (r)->g.x
|
|
#define Y(r) (r)->g.y
|
|
#define WIDTH(r) (r)->g.w
|
|
#define HEIGHT(r) (r)->g.h
|
|
#define BORDER(w) ((w)->bordered ? border_width : 0)
|
|
#define MAX_X(r) ((r)->g.x + (r)->g.w)
|
|
#define MAX_Y(r) ((r)->g.y + (r)->g.h)
|
|
#define SH_MIN(w) (w)->sh.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE
|
|
#define SH_MIN_W(w) (w)->sh.min_width
|
|
#define SH_MIN_H(w) (w)->sh.min_height
|
|
#define SH_MAX(w) (w)->sh.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE
|
|
#define SH_MAX_W(w) (w)->sh.max_width
|
|
#define SH_MAX_H(w) (w)->sh.max_height
|
|
#define SH_INC(w) (w)->sh.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC
|
|
#define SH_INC_W(w) (w)->sh.width_inc
|
|
#define SH_INC_H(w) (w)->sh.height_inc
|
|
#define SWM_MAX_FONT_STEPS (3)
|
|
#define WINID(w) ((w) ? (w)->id : 0)
|
|
#define YESNO(x) ((x) ? "yes" : "no")
|
|
|
|
#define SWM_FOCUS_DEFAULT (0)
|
|
#define SWM_FOCUS_FOLLOW (1)
|
|
#define SWM_FOCUS_MANUAL (2)
|
|
|
|
#define SWM_CK_NONE 0
|
|
#define SWM_CK_ALL 0xf
|
|
#define SWM_CK_FOCUS 0x1
|
|
#define SWM_CK_POINTER 0x2
|
|
#define SWM_CK_FALLBACK 0x4
|
|
#define SWM_CK_REGION 0x8
|
|
|
|
#define SWM_CONF_DEFAULT (0)
|
|
#define SWM_CONF_KEYMAPPING (1)
|
|
|
|
#ifndef SWM_LIB
|
|
#define SWM_LIB "/usr/local/lib/libswmhack.so"
|
|
#endif
|
|
|
|
char **start_argv;
|
|
xcb_atom_t a_state;
|
|
xcb_atom_t a_prot;
|
|
xcb_atom_t a_delete;
|
|
xcb_atom_t a_takefocus;
|
|
xcb_atom_t a_wmname;
|
|
xcb_atom_t a_netwmname;
|
|
xcb_atom_t a_utf8_string;
|
|
xcb_atom_t a_string;
|
|
xcb_atom_t a_swm_iconic;
|
|
xcb_atom_t a_swm_ws;
|
|
volatile sig_atomic_t running = 1;
|
|
volatile sig_atomic_t restart_wm = 0;
|
|
xcb_timestamp_t last_event_time = 0;
|
|
int outputs = 0;
|
|
int other_wm;
|
|
int xrandr_support;
|
|
int xrandr_eventbase;
|
|
unsigned int numlockmask = 0;
|
|
|
|
Display *display;
|
|
xcb_connection_t *conn;
|
|
xcb_key_symbols_t *syms;
|
|
|
|
int cycle_empty = 0;
|
|
int cycle_visible = 0;
|
|
int term_width = 0;
|
|
int font_adjusted = 0;
|
|
unsigned int mod_key = MODKEY;
|
|
|
|
/* dmenu search */
|
|
struct swm_region *search_r;
|
|
int select_list_pipe[2];
|
|
int select_resp_pipe[2];
|
|
pid_t searchpid;
|
|
volatile sig_atomic_t search_resp;
|
|
int search_resp_action;
|
|
|
|
struct search_window {
|
|
TAILQ_ENTRY(search_window) entry;
|
|
int idx;
|
|
struct ws_win *win;
|
|
xcb_gcontext_t gc;
|
|
xcb_window_t indicator;
|
|
};
|
|
TAILQ_HEAD(search_winlist, search_window);
|
|
struct search_winlist search_wl;
|
|
|
|
/* search actions */
|
|
enum {
|
|
SWM_SEARCH_NONE,
|
|
SWM_SEARCH_UNICONIFY,
|
|
SWM_SEARCH_NAME_WORKSPACE,
|
|
SWM_SEARCH_SEARCH_WORKSPACE,
|
|
SWM_SEARCH_SEARCH_WINDOW
|
|
};
|
|
|
|
#define SWM_STACK_TOP (0)
|
|
#define SWM_STACK_BOTTOM (1)
|
|
#define SWM_STACK_ABOVE (2)
|
|
#define SWM_STACK_BELOW (3)
|
|
|
|
/* dialog windows */
|
|
double dialog_ratio = 0.6;
|
|
/* status bar */
|
|
#define SWM_BAR_MAX (256)
|
|
#define SWM_BAR_JUSTIFY_LEFT (0)
|
|
#define SWM_BAR_JUSTIFY_CENTER (1)
|
|
#define SWM_BAR_JUSTIFY_RIGHT (2)
|
|
#define SWM_BAR_OFFSET (4)
|
|
#define SWM_BAR_FONTS "-*-terminus-medium-*-*-*-12-*-*-*-*-*-*-*," \
|
|
"-*-profont-*-*-*-*-12-*-*-*-*-*-*-*," \
|
|
"-*-times-medium-r-*-*-12-*-*-*-*-*-*-*," \
|
|
"-misc-fixed-medium-r-*-*-12-*-*-*-*-*-*-*," \
|
|
"-*-*-*-r-*-*-*-*-*-*-*-*-*-*"
|
|
|
|
#ifdef X_HAVE_UTF8_STRING
|
|
#define DRAWSTRING(x...) Xutf8DrawString(x)
|
|
#else
|
|
#define DRAWSTRING(x...) XmbDrawString(x)
|
|
#endif
|
|
|
|
char *bar_argv[] = { NULL, NULL };
|
|
int bar_pipe[2];
|
|
char bar_ext[SWM_BAR_MAX];
|
|
char bar_ext_buf[SWM_BAR_MAX];
|
|
char bar_vertext[SWM_BAR_MAX];
|
|
int bar_version = 0;
|
|
int bar_enabled = 1;
|
|
int bar_border_width = 1;
|
|
int bar_at_bottom = 0;
|
|
int bar_extra = 0;
|
|
int bar_verbose = 1;
|
|
int bar_height = 0;
|
|
int bar_justify = SWM_BAR_JUSTIFY_LEFT;
|
|
char *bar_format = NULL;
|
|
int stack_enabled = 1;
|
|
int clock_enabled = 1;
|
|
int urgent_enabled = 0;
|
|
char *clock_format = NULL;
|
|
int title_name_enabled = 0;
|
|
int title_class_enabled = 0;
|
|
int window_name_enabled = 0;
|
|
int focus_mode = SWM_FOCUS_DEFAULT;
|
|
int focus_close = SWM_STACK_BELOW;
|
|
int focus_close_wrap = 1;
|
|
int focus_default = SWM_STACK_TOP;
|
|
int spawn_position = SWM_STACK_TOP;
|
|
int disable_border = 0;
|
|
int border_width = 1;
|
|
int region_padding = 0;
|
|
int tile_gap = 0;
|
|
int verbose_layout = 0;
|
|
time_t time_started;
|
|
pid_t bar_pid;
|
|
XFontSet bar_fs;
|
|
XFontSetExtents *bar_fs_extents;
|
|
XftFont *bar_font;
|
|
int bar_font_legacy = 1;
|
|
char *bar_fonts;
|
|
XftColor bar_font_color;
|
|
struct passwd *pwd;
|
|
char *startup_exception;
|
|
unsigned int nr_exceptions = 0;
|
|
|
|
/* layout manager data */
|
|
struct swm_geometry {
|
|
int x;
|
|
int y;
|
|
int w;
|
|
int h;
|
|
};
|
|
|
|
struct swm_screen;
|
|
struct workspace;
|
|
|
|
struct swm_bar {
|
|
xcb_window_t id;
|
|
xcb_pixmap_t buffer;
|
|
struct swm_geometry g;
|
|
};
|
|
|
|
/* virtual "screens" */
|
|
struct swm_region {
|
|
TAILQ_ENTRY(swm_region) entry;
|
|
xcb_window_t id;
|
|
struct swm_geometry g;
|
|
struct workspace *ws; /* current workspace on this region */
|
|
struct workspace *ws_prior; /* prior workspace on this region */
|
|
struct swm_screen *s; /* screen idx */
|
|
struct swm_bar *bar;
|
|
};
|
|
TAILQ_HEAD(swm_region_list, swm_region);
|
|
|
|
struct ws_win {
|
|
TAILQ_ENTRY(ws_win) entry;
|
|
xcb_window_t id;
|
|
xcb_window_t transient;
|
|
struct ws_win *focus_child; /* focus on child transient */
|
|
struct swm_geometry g; /* current geometry */
|
|
struct swm_geometry g_float; /* region coordinates */
|
|
int g_floatvalid; /* g_float geometry validity */
|
|
int floatmaxed; /* whether maxed by max_stack */
|
|
int floating;
|
|
int manual;
|
|
int32_t mapped;
|
|
int32_t iconic;
|
|
int bordered;
|
|
unsigned int ewmh_flags;
|
|
int font_size_boundary[SWM_MAX_FONT_STEPS];
|
|
int font_steps;
|
|
int last_inc;
|
|
int can_delete;
|
|
int take_focus;
|
|
int java;
|
|
unsigned long quirks;
|
|
struct workspace *ws; /* always valid */
|
|
struct swm_screen *s; /* always valid, never changes */
|
|
xcb_get_geometry_reply_t *wa;
|
|
xcb_size_hints_t sh;
|
|
xcb_icccm_get_wm_class_reply_t ch;
|
|
xcb_icccm_wm_hints_t hints;
|
|
};
|
|
TAILQ_HEAD(ws_win_list, ws_win);
|
|
|
|
/* pid goo */
|
|
struct pid_e {
|
|
TAILQ_ENTRY(pid_e) entry;
|
|
pid_t pid;
|
|
int ws;
|
|
};
|
|
TAILQ_HEAD(pid_list, pid_e);
|
|
struct pid_list pidlist = TAILQ_HEAD_INITIALIZER(pidlist);
|
|
|
|
/* layout handlers */
|
|
void stack(void);
|
|
void vertical_config(struct workspace *, int);
|
|
void vertical_stack(struct workspace *, struct swm_geometry *);
|
|
void horizontal_config(struct workspace *, int);
|
|
void horizontal_stack(struct workspace *, struct swm_geometry *);
|
|
void max_stack(struct workspace *, struct swm_geometry *);
|
|
void plain_stacker(struct workspace *);
|
|
void fancy_stacker(struct workspace *);
|
|
|
|
struct layout {
|
|
void (*l_stack)(struct workspace *, struct swm_geometry *);
|
|
void (*l_config)(struct workspace *, int);
|
|
u_int32_t flags;
|
|
#define SWM_L_FOCUSPREV (1<<0)
|
|
#define SWM_L_MAPONFOCUS (1<<1)
|
|
void (*l_string)(struct workspace *);
|
|
} layouts[] = {
|
|
/* stack, configure */
|
|
{ vertical_stack, vertical_config, 0, plain_stacker },
|
|
{ horizontal_stack, horizontal_config, 0, plain_stacker },
|
|
{ max_stack, NULL,
|
|
SWM_L_MAPONFOCUS | SWM_L_FOCUSPREV, plain_stacker },
|
|
{ NULL, NULL, 0, NULL },
|
|
};
|
|
|
|
/* position of max_stack mode in the layouts array, index into layouts! */
|
|
#define SWM_V_STACK (0)
|
|
#define SWM_H_STACK (1)
|
|
#define SWM_MAX_STACK (2)
|
|
|
|
#define SWM_H_SLICE (32)
|
|
#define SWM_V_SLICE (32)
|
|
|
|
/* define work spaces */
|
|
struct workspace {
|
|
int idx; /* workspace index */
|
|
char *name; /* workspace name */
|
|
int always_raise; /* raise windows on focus */
|
|
int bar_enabled; /* bar visibility */
|
|
struct layout *cur_layout; /* current layout handlers */
|
|
struct ws_win *focus; /* may be NULL */
|
|
struct ws_win *focus_prev; /* may be NULL */
|
|
struct ws_win *focus_pending; /* may be NULL */
|
|
struct swm_region *r; /* may be NULL */
|
|
struct swm_region *old_r; /* may be NULL */
|
|
struct ws_win_list winlist; /* list of windows in ws */
|
|
struct ws_win_list unmanagedlist; /* list of dead windows in ws */
|
|
char stacker[10]; /* display stacker and layout */
|
|
|
|
/* stacker state */
|
|
struct {
|
|
int horizontal_msize;
|
|
int horizontal_mwin;
|
|
int horizontal_stacks;
|
|
int horizontal_flip;
|
|
int vertical_msize;
|
|
int vertical_mwin;
|
|
int vertical_stacks;
|
|
int vertical_flip;
|
|
} l_state;
|
|
};
|
|
|
|
enum {
|
|
SWM_S_COLOR_BAR,
|
|
SWM_S_COLOR_BAR_BORDER,
|
|
SWM_S_COLOR_BAR_BORDER_UNFOCUS,
|
|
SWM_S_COLOR_BAR_FONT,
|
|
SWM_S_COLOR_FOCUS,
|
|
SWM_S_COLOR_UNFOCUS,
|
|
SWM_S_COLOR_MAX
|
|
};
|
|
|
|
/* physical screen mapping */
|
|
#define SWM_WS_MAX (22) /* hard limit */
|
|
int workspace_limit = 10; /* soft limit */
|
|
|
|
struct swm_screen {
|
|
int idx; /* screen index */
|
|
struct swm_region_list rl; /* list of regions on this screen */
|
|
struct swm_region_list orl; /* list of old regions */
|
|
xcb_window_t root;
|
|
struct workspace ws[SWM_WS_MAX];
|
|
struct swm_region *r_focus;
|
|
|
|
/* colors */
|
|
struct {
|
|
uint32_t pixel;
|
|
char *name;
|
|
} c[SWM_S_COLOR_MAX];
|
|
|
|
xcb_gcontext_t bar_gc;
|
|
GC bar_gc_legacy;
|
|
};
|
|
struct swm_screen *screens;
|
|
|
|
/* args to functions */
|
|
union arg {
|
|
int id;
|
|
#define SWM_ARG_ID_FOCUSNEXT (0)
|
|
#define SWM_ARG_ID_FOCUSPREV (1)
|
|
#define SWM_ARG_ID_FOCUSMAIN (2)
|
|
#define SWM_ARG_ID_SWAPNEXT (10)
|
|
#define SWM_ARG_ID_SWAPPREV (11)
|
|
#define SWM_ARG_ID_SWAPMAIN (12)
|
|
#define SWM_ARG_ID_MOVELAST (13)
|
|
#define SWM_ARG_ID_MASTERSHRINK (20)
|
|
#define SWM_ARG_ID_MASTERGROW (21)
|
|
#define SWM_ARG_ID_MASTERADD (22)
|
|
#define SWM_ARG_ID_MASTERDEL (23)
|
|
#define SWM_ARG_ID_FLIPLAYOUT (24)
|
|
#define SWM_ARG_ID_STACKRESET (30)
|
|
#define SWM_ARG_ID_STACKINIT (31)
|
|
#define SWM_ARG_ID_CYCLEWS_UP (40)
|
|
#define SWM_ARG_ID_CYCLEWS_DOWN (41)
|
|
#define SWM_ARG_ID_CYCLERG_UP (42)
|
|
#define SWM_ARG_ID_CYCLERG_DOWN (43)
|
|
#define SWM_ARG_ID_CYCLEWS_UP_ALL (44)
|
|
#define SWM_ARG_ID_CYCLEWS_DOWN_ALL (45)
|
|
#define SWM_ARG_ID_STACKINC (50)
|
|
#define SWM_ARG_ID_STACKDEC (51)
|
|
#define SWM_ARG_ID_SS_ALL (60)
|
|
#define SWM_ARG_ID_SS_WINDOW (61)
|
|
#define SWM_ARG_ID_DONTCENTER (70)
|
|
#define SWM_ARG_ID_CENTER (71)
|
|
#define SWM_ARG_ID_KILLWINDOW (80)
|
|
#define SWM_ARG_ID_DELETEWINDOW (81)
|
|
#define SWM_ARG_ID_WIDTHGROW (90)
|
|
#define SWM_ARG_ID_WIDTHSHRINK (91)
|
|
#define SWM_ARG_ID_HEIGHTGROW (92)
|
|
#define SWM_ARG_ID_HEIGHTSHRINK (93)
|
|
#define SWM_ARG_ID_MOVEUP (100)
|
|
#define SWM_ARG_ID_MOVEDOWN (101)
|
|
#define SWM_ARG_ID_MOVELEFT (102)
|
|
#define SWM_ARG_ID_MOVERIGHT (103)
|
|
#define SWM_ARG_ID_BAR_TOGGLE (110)
|
|
#define SWM_ARG_ID_BAR_TOGGLE_WS (111)
|
|
char **argv;
|
|
};
|
|
|
|
/* quirks */
|
|
struct quirk {
|
|
TAILQ_ENTRY(quirk) entry;
|
|
char *class;
|
|
char *name;
|
|
unsigned long quirk;
|
|
#define SWM_Q_FLOAT (1<<0) /* float this window */
|
|
#define SWM_Q_TRANSSZ (1<<1) /* transiend window size too small */
|
|
#define SWM_Q_ANYWHERE (1<<2) /* don't position this window */
|
|
#define SWM_Q_XTERM_FONTADJ (1<<3) /* adjust xterm fonts when resizing */
|
|
#define SWM_Q_FULLSCREEN (1<<4) /* remove border */
|
|
#define SWM_Q_FOCUSPREV (1<<5) /* focus on caller */
|
|
};
|
|
TAILQ_HEAD(quirk_list, quirk);
|
|
struct quirk_list quirks = TAILQ_HEAD_INITIALIZER(quirks);
|
|
|
|
/*
|
|
* Supported EWMH hints should be added to
|
|
* both the enum and the ewmh array
|
|
*/
|
|
enum {
|
|
_NET_ACTIVE_WINDOW,
|
|
_NET_CLOSE_WINDOW,
|
|
_NET_MOVERESIZE_WINDOW,
|
|
_NET_WM_ACTION_ABOVE,
|
|
_NET_WM_ACTION_CLOSE,
|
|
_NET_WM_ACTION_FULLSCREEN,
|
|
_NET_WM_ACTION_MOVE,
|
|
_NET_WM_ACTION_RESIZE,
|
|
_NET_WM_ALLOWED_ACTIONS,
|
|
_NET_WM_NAME,
|
|
_NET_WM_STATE,
|
|
_NET_WM_STATE_ABOVE,
|
|
_NET_WM_STATE_FULLSCREEN,
|
|
_NET_WM_STATE_HIDDEN,
|
|
_NET_WM_STATE_MAXIMIZED_HORZ,
|
|
_NET_WM_STATE_MAXIMIZED_VERT,
|
|
_NET_WM_STATE_SKIP_PAGER,
|
|
_NET_WM_STATE_SKIP_TASKBAR,
|
|
_NET_WM_WINDOW_TYPE,
|
|
_NET_WM_WINDOW_TYPE_DIALOG,
|
|
_NET_WM_WINDOW_TYPE_DOCK,
|
|
_NET_WM_WINDOW_TYPE_NORMAL,
|
|
_NET_WM_WINDOW_TYPE_SPLASH,
|
|
_NET_WM_WINDOW_TYPE_TOOLBAR,
|
|
_NET_WM_WINDOW_TYPE_UTILITY,
|
|
_SWM_WM_STATE_MANUAL,
|
|
SWM_EWMH_HINT_MAX
|
|
};
|
|
|
|
struct ewmh_hint {
|
|
char *name;
|
|
xcb_atom_t atom;
|
|
} ewmh[SWM_EWMH_HINT_MAX] = {
|
|
/* must be in same order as in the enum */
|
|
{"_NET_ACTIVE_WINDOW", XCB_ATOM_NONE},
|
|
{"_NET_CLOSE_WINDOW", XCB_ATOM_NONE},
|
|
{"_NET_MOVERESIZE_WINDOW", XCB_ATOM_NONE},
|
|
{"_NET_WM_ACTION_ABOVE", XCB_ATOM_NONE},
|
|
{"_NET_WM_ACTION_CLOSE", XCB_ATOM_NONE},
|
|
{"_NET_WM_ACTION_FULLSCREEN", XCB_ATOM_NONE},
|
|
{"_NET_WM_ACTION_MOVE", XCB_ATOM_NONE},
|
|
{"_NET_WM_ACTION_RESIZE", XCB_ATOM_NONE},
|
|
{"_NET_WM_ALLOWED_ACTIONS", XCB_ATOM_NONE},
|
|
{"_NET_WM_NAME", XCB_ATOM_NONE},
|
|
{"_NET_WM_STATE", XCB_ATOM_NONE},
|
|
{"_NET_WM_STATE_ABOVE", XCB_ATOM_NONE},
|
|
{"_NET_WM_STATE_FULLSCREEN", XCB_ATOM_NONE},
|
|
{"_NET_WM_STATE_HIDDEN", XCB_ATOM_NONE},
|
|
{"_NET_WM_STATE_MAXIMIZED_HORZ", XCB_ATOM_NONE},
|
|
{"_NET_WM_STATE_MAXIMIZED_VERT", XCB_ATOM_NONE},
|
|
{"_NET_WM_STATE_SKIP_PAGER", XCB_ATOM_NONE},
|
|
{"_NET_WM_STATE_SKIP_TASKBAR", XCB_ATOM_NONE},
|
|
{"_NET_WM_WINDOW_TYPE", XCB_ATOM_NONE},
|
|
{"_NET_WM_WINDOW_TYPE_DIALOG", XCB_ATOM_NONE},
|
|
{"_NET_WM_WINDOW_TYPE_DOCK", XCB_ATOM_NONE},
|
|
{"_NET_WM_WINDOW_TYPE_NORMAL", XCB_ATOM_NONE},
|
|
{"_NET_WM_WINDOW_TYPE_SPLASH", XCB_ATOM_NONE},
|
|
{"_NET_WM_WINDOW_TYPE_TOOLBAR", XCB_ATOM_NONE},
|
|
{"_NET_WM_WINDOW_TYPE_UTILITY", XCB_ATOM_NONE},
|
|
{"_SWM_WM_STATE_MANUAL", XCB_ATOM_NONE},
|
|
};
|
|
|
|
/* Cursors */
|
|
enum {
|
|
XC_FLEUR,
|
|
XC_LEFT_PTR,
|
|
XC_BOTTOM_LEFT_CORNER,
|
|
XC_BOTTOM_RIGHT_CORNER,
|
|
XC_SIZING,
|
|
XC_TOP_LEFT_CORNER,
|
|
XC_TOP_RIGHT_CORNER,
|
|
XC_MAX
|
|
};
|
|
|
|
struct cursors {
|
|
char *name; /* Name used by Xcursor .*/
|
|
uint8_t cf_char; /* cursorfont index. */
|
|
xcb_cursor_t cid;
|
|
} cursors[XC_MAX] = {
|
|
{"fleur", XC_fleur, XCB_CURSOR_NONE},
|
|
{"left_ptr", XC_left_ptr, XCB_CURSOR_NONE},
|
|
{"bottom_left_corner", XC_bottom_left_corner, XCB_CURSOR_NONE},
|
|
{"bottom_right_corner", XC_bottom_right_corner, XCB_CURSOR_NONE},
|
|
{"sizing", XC_sizing, XCB_CURSOR_NONE},
|
|
{"top_left_corner", XC_top_left_corner, XCB_CURSOR_NONE},
|
|
{"top_right_corner", XC_top_right_corner, XCB_CURSOR_NONE},
|
|
};
|
|
|
|
#define SWM_SPAWN_OPTIONAL 0x1
|
|
|
|
/* spawn */
|
|
struct spawn_prog {
|
|
TAILQ_ENTRY(spawn_prog) entry;
|
|
char *name;
|
|
int argc;
|
|
char **argv;
|
|
int flags;
|
|
};
|
|
TAILQ_HEAD(spawn_list, spawn_prog);
|
|
struct spawn_list spawns = TAILQ_HEAD_INITIALIZER(spawns);
|
|
|
|
/* user/key callable function IDs */
|
|
enum keyfuncid {
|
|
KF_BAR_TOGGLE,
|
|
KF_BAR_TOGGLE_WS,
|
|
KF_BUTTON2,
|
|
KF_CYCLE_LAYOUT,
|
|
KF_FLIP_LAYOUT,
|
|
KF_FLOAT_TOGGLE,
|
|
KF_FOCUS_MAIN,
|
|
KF_FOCUS_NEXT,
|
|
KF_FOCUS_PREV,
|
|
KF_HEIGHT_GROW,
|
|
KF_HEIGHT_SHRINK,
|
|
KF_ICONIFY,
|
|
KF_MASTER_SHRINK,
|
|
KF_MASTER_GROW,
|
|
KF_MASTER_ADD,
|
|
KF_MASTER_DEL,
|
|
KF_MOVE_DOWN,
|
|
KF_MOVE_LEFT,
|
|
KF_MOVE_RIGHT,
|
|
KF_MOVE_UP,
|
|
KF_MVRG_1,
|
|
KF_MVRG_2,
|
|
KF_MVRG_3,
|
|
KF_MVRG_4,
|
|
KF_MVRG_5,
|
|
KF_MVRG_6,
|
|
KF_MVRG_7,
|
|
KF_MVRG_8,
|
|
KF_MVRG_9,
|
|
KF_MVWS_1,
|
|
KF_MVWS_2,
|
|
KF_MVWS_3,
|
|
KF_MVWS_4,
|
|
KF_MVWS_5,
|
|
KF_MVWS_6,
|
|
KF_MVWS_7,
|
|
KF_MVWS_8,
|
|
KF_MVWS_9,
|
|
KF_MVWS_10,
|
|
KF_MVWS_11,
|
|
KF_MVWS_12,
|
|
KF_MVWS_13,
|
|
KF_MVWS_14,
|
|
KF_MVWS_15,
|
|
KF_MVWS_16,
|
|
KF_MVWS_17,
|
|
KF_MVWS_18,
|
|
KF_MVWS_19,
|
|
KF_MVWS_20,
|
|
KF_MVWS_21,
|
|
KF_MVWS_22,
|
|
KF_NAME_WORKSPACE,
|
|
KF_QUIT,
|
|
KF_RAISE_TOGGLE,
|
|
KF_RESTART,
|
|
KF_RG_1,
|
|
KF_RG_2,
|
|
KF_RG_3,
|
|
KF_RG_4,
|
|
KF_RG_5,
|
|
KF_RG_6,
|
|
KF_RG_7,
|
|
KF_RG_8,
|
|
KF_RG_9,
|
|
KF_RG_NEXT,
|
|
KF_RG_PREV,
|
|
KF_SCREEN_NEXT,
|
|
KF_SCREEN_PREV,
|
|
KF_SEARCH_WIN,
|
|
KF_SEARCH_WORKSPACE,
|
|
KF_SPAWN_CUSTOM,
|
|
KF_STACK_INC,
|
|
KF_STACK_DEC,
|
|
KF_STACK_RESET,
|
|
KF_SWAP_MAIN,
|
|
KF_SWAP_NEXT,
|
|
KF_SWAP_PREV,
|
|
KF_UNICONIFY,
|
|
KF_VERSION,
|
|
KF_WIDTH_GROW,
|
|
KF_WIDTH_SHRINK,
|
|
KF_WIND_DEL,
|
|
KF_WIND_KILL,
|
|
KF_WS_1,
|
|
KF_WS_2,
|
|
KF_WS_3,
|
|
KF_WS_4,
|
|
KF_WS_5,
|
|
KF_WS_6,
|
|
KF_WS_7,
|
|
KF_WS_8,
|
|
KF_WS_9,
|
|
KF_WS_10,
|
|
KF_WS_11,
|
|
KF_WS_12,
|
|
KF_WS_13,
|
|
KF_WS_14,
|
|
KF_WS_15,
|
|
KF_WS_16,
|
|
KF_WS_17,
|
|
KF_WS_18,
|
|
KF_WS_19,
|
|
KF_WS_20,
|
|
KF_WS_21,
|
|
KF_WS_22,
|
|
KF_WS_NEXT,
|
|
KF_WS_NEXT_ALL,
|
|
KF_WS_PREV,
|
|
KF_WS_PREV_ALL,
|
|
KF_WS_PRIOR,
|
|
KF_DUMPWINS, /* MUST BE LAST */
|
|
KF_INVALID
|
|
};
|
|
|
|
struct key {
|
|
RB_ENTRY(key) entry;
|
|
unsigned int mod;
|
|
KeySym keysym;
|
|
enum keyfuncid funcid;
|
|
char *spawn_name;
|
|
};
|
|
RB_HEAD(key_tree, key);
|
|
|
|
/* function prototypes */
|
|
void adjust_font(struct ws_win *);
|
|
void bar_class_name(char *, size_t, struct swm_region *);
|
|
void bar_class_title_name(char *, size_t, struct swm_region *);
|
|
void bar_cleanup(struct swm_region *);
|
|
void bar_extra_setup(void);
|
|
void bar_extra_stop(void);
|
|
int bar_extra_update(void);
|
|
void bar_fmt(const char *, char *, struct swm_region *, size_t);
|
|
void bar_fmt_expand(char *, size_t);
|
|
void bar_draw(void);
|
|
void bar_print(struct swm_region *, const char *);
|
|
void bar_print_legacy(struct swm_region *, const char *);
|
|
void bar_replace(char *, char *, struct swm_region *, size_t);
|
|
void bar_replace_pad(char *, int *, size_t);
|
|
char *bar_replace_seq(char *, char *, struct swm_region *, size_t *, size_t);
|
|
void bar_setup(struct swm_region *);
|
|
void bar_title_name(char *, size_t, struct swm_region *);
|
|
void bar_toggle(struct swm_region *, union arg *);
|
|
void bar_urgent(char *, size_t);
|
|
void bar_window_float(char *, size_t, struct swm_region *);
|
|
void bar_window_name(char *, size_t, struct swm_region *);
|
|
void bar_workspace_name(char *, size_t, struct swm_region *);
|
|
void buttonpress(xcb_button_press_event_t *);
|
|
void check_conn(void);
|
|
void clear_keys(void);
|
|
void clientmessage(xcb_client_message_event_t *);
|
|
void client_msg(struct ws_win *, xcb_atom_t, xcb_timestamp_t);
|
|
int conf_load(const char *, int);
|
|
void configurenotify(xcb_configure_notify_event_t *);
|
|
void configurerequest(xcb_configure_request_event_t *);
|
|
void config_win(struct ws_win *, xcb_configure_request_event_t *);
|
|
void constrain_window(struct ws_win *, struct swm_region *, int);
|
|
int count_win(struct workspace *, int);
|
|
void cursors_cleanup(void);
|
|
void cursors_load(void);
|
|
void custom_region(char *);
|
|
void cyclerg(struct swm_region *, union arg *);
|
|
void cyclews(struct swm_region *, union arg *);
|
|
void cycle_layout(struct swm_region *, union arg *);
|
|
void destroynotify(xcb_destroy_notify_event_t *);
|
|
void dumpwins(struct swm_region *, union arg *);
|
|
int enable_wm(void);
|
|
void enternotify(xcb_enter_notify_event_t *);
|
|
void event_drain(uint8_t);
|
|
void event_error(xcb_generic_error_t *);
|
|
void event_handle(xcb_generic_event_t *);
|
|
void ewmh_autoquirk(struct ws_win *);
|
|
void ewmh_get_win_state(struct ws_win *);
|
|
int ewmh_set_win_fullscreen(struct ws_win *, int);
|
|
void ewmh_update_actions(struct ws_win *);
|
|
void ewmh_update_win_state(struct ws_win *, xcb_atom_t, long);
|
|
char *expand_tilde(const char *);
|
|
void expose(xcb_expose_event_t *);
|
|
void fake_keypress(struct ws_win *, xcb_keysym_t, uint16_t);
|
|
struct pid_e *find_pid(pid_t);
|
|
struct ws_win *find_unmanaged_window(xcb_window_t);
|
|
struct ws_win *find_window(xcb_window_t);
|
|
void floating_toggle(struct swm_region *, union arg *);
|
|
int floating_toggle_win(struct ws_win *);
|
|
void focus(struct swm_region *, union arg *);
|
|
#ifdef SWM_DEBUG
|
|
void focusin(xcb_focus_in_event_t *);
|
|
void focusout(xcb_focus_out_event_t *);
|
|
#endif
|
|
void focus_flush(void);
|
|
void focus_region(struct swm_region *);
|
|
void focusrg(struct swm_region *, union arg *);
|
|
void focus_win(struct ws_win *);
|
|
void fontset_init(void);
|
|
void free_window(struct ws_win *);
|
|
xcb_atom_t get_atom_from_string(const char *);
|
|
#ifdef SWM_DEBUG
|
|
char *get_atom_name(xcb_atom_t);
|
|
#endif
|
|
struct ws_win *get_focus_magic(struct ws_win *);
|
|
struct ws_win *get_focus_prev(struct ws_win *);
|
|
#ifdef SWM_DEBUG
|
|
char *get_notify_detail_label(uint8_t);
|
|
char *get_notify_mode_label(uint8_t);
|
|
#endif
|
|
struct ws_win *get_pointer_win(xcb_window_t);
|
|
struct ws_win *get_region_focus(struct swm_region *);
|
|
int get_region_index(struct swm_region *);
|
|
xcb_screen_t *get_screen(int);
|
|
#ifdef SWM_DEBUG
|
|
char *get_stack_mode_name(uint8_t);
|
|
#endif
|
|
int32_t get_swm_iconic(struct ws_win *);
|
|
char *get_win_name(xcb_window_t);
|
|
int get_ws_idx(xcb_window_t);
|
|
uint32_t getstate(xcb_window_t);
|
|
void grabbuttons(struct ws_win *);
|
|
void grabkeys(void);
|
|
void grab_windows(void);
|
|
void iconify(struct swm_region *, union arg *);
|
|
int isxlfd(char *);
|
|
void keypress(xcb_key_press_event_t *);
|
|
int key_cmp(struct key *, struct key *);
|
|
void key_insert(unsigned int, KeySym, enum keyfuncid, const char *);
|
|
struct key *key_lookup(unsigned int, KeySym);
|
|
void key_remove(struct key *);
|
|
void key_replace(struct key *, unsigned int, KeySym, enum keyfuncid,
|
|
const char *);
|
|
void kill_bar_extra_atexit(void);
|
|
void kill_refs(struct ws_win *);
|
|
#ifdef SWM_DEBUG
|
|
void leavenotify(xcb_leave_notify_event_t *);
|
|
#endif
|
|
void load_float_geom(struct ws_win *, struct swm_region *);
|
|
struct ws_win *manage_window(xcb_window_t, uint16_t);
|
|
void map_window(struct ws_win *);
|
|
void mapnotify(xcb_map_notify_event_t *);
|
|
void mappingnotify(xcb_mapping_notify_event_t *);
|
|
void maprequest(xcb_map_request_event_t *);
|
|
void motionnotify(xcb_motion_notify_event_t *);
|
|
void move(struct ws_win *, union arg *);
|
|
void move_step(struct swm_region *, union arg *);
|
|
uint32_t name_to_pixel(const char *);
|
|
void name_workspace(struct swm_region *, union arg *);
|
|
void new_region(struct swm_screen *, int, int, int, int);
|
|
int parsekeys(char *, unsigned int, unsigned int *, KeySym *);
|
|
int parsequirks(char *, unsigned long *);
|
|
int parse_rgb(const char *, uint16_t *, uint16_t *, uint16_t *);
|
|
void pressbutton(struct swm_region *, union arg *);
|
|
void priorws(struct swm_region *, union arg *);
|
|
#ifdef SWM_DEBUG
|
|
void print_win_geom(xcb_window_t);
|
|
#endif
|
|
void propertynotify(xcb_property_notify_event_t *);
|
|
void quirk_insert(const char *, const char *, unsigned long);
|
|
void quirk_remove(struct quirk *);
|
|
void quirk_replace(struct quirk *, const char *, const char *,
|
|
unsigned long);
|
|
void quit(struct swm_region *, union arg *);
|
|
void raise_toggle(struct swm_region *, union arg *);
|
|
void resize(struct ws_win *, union arg *);
|
|
void resize_step(struct swm_region *, union arg *);
|
|
void restart(struct swm_region *, union arg *);
|
|
struct swm_region *root_to_region(xcb_window_t, int);
|
|
void screenchange(xcb_randr_screen_change_notify_event_t *);
|
|
void scan_xrandr(int);
|
|
void search_do_resp(void);
|
|
void search_resp_name_workspace(const char *, unsigned long);
|
|
void search_resp_search_window(const char *);
|
|
void search_resp_search_workspace(const char *);
|
|
void search_resp_uniconify(const char *, unsigned long);
|
|
void search_win(struct swm_region *, union arg *);
|
|
void search_win_cleanup(void);
|
|
void search_workspace(struct swm_region *, union arg *);
|
|
void send_to_rg(struct swm_region *, union arg *);
|
|
void send_to_ws(struct swm_region *, union arg *);
|
|
void set_region(struct swm_region *);
|
|
int setautorun(char *, char *, int);
|
|
int setconfbinding(char *, char *, int);
|
|
int setconfcolor(char *, char *, int);
|
|
int setconfmodkey(char *, char *, int);
|
|
int setconfquirk(char *, char *, int);
|
|
int setconfregion(char *, char *, int);
|
|
int setconfspawn(char *, char *, int);
|
|
int setconfvalue(char *, char *, int);
|
|
void setkeybinding(unsigned int, KeySym, enum keyfuncid, const char *);
|
|
int setkeymapping(char *, char *, int);
|
|
int setlayout(char *, char *, int);
|
|
void setquirk(const char *, const char *, unsigned long);
|
|
void setscreencolor(char *, int, int);
|
|
void setspawn(const char *, const char *, int);
|
|
void setup_ewmh(void);
|
|
void setup_globals(void);
|
|
void setup_keys(void);
|
|
void setup_quirks(void);
|
|
void setup_screens(void);
|
|
void setup_spawn(void);
|
|
void set_child_transient(struct ws_win *, xcb_window_t *);
|
|
void set_swm_iconic(struct ws_win *, int);
|
|
void set_win_state(struct ws_win *, uint16_t);
|
|
void shutdown_cleanup(void);
|
|
void sighdlr(int);
|
|
void socket_setnonblock(int);
|
|
void sort_windows(struct ws_win_list *);
|
|
void spawn(int, union arg *, int);
|
|
void spawn_custom(struct swm_region *, union arg *, const char *);
|
|
int spawn_expand(struct swm_region *, union arg *, const char *, char ***);
|
|
void spawn_insert(const char *, const char *, int);
|
|
void spawn_remove(struct spawn_prog *);
|
|
void spawn_replace(struct spawn_prog *, const char *, const char *, int);
|
|
void spawn_select(struct swm_region *, union arg *, const char *, int *);
|
|
void stack_config(struct swm_region *, union arg *);
|
|
void stack_floater(struct ws_win *, struct swm_region *);
|
|
void stack_master(struct workspace *, struct swm_geometry *, int, int);
|
|
void store_float_geom(struct ws_win *, struct swm_region *);
|
|
char *strdupsafe(const char *);
|
|
void swapwin(struct swm_region *, union arg *);
|
|
void switchws(struct swm_region *, union arg *);
|
|
void teardown_ewmh(void);
|
|
void unfocus_win(struct ws_win *);
|
|
void uniconify(struct swm_region *, union arg *);
|
|
void unmanage_window(struct ws_win *);
|
|
void unmapnotify(xcb_unmap_notify_event_t *);
|
|
void unmap_all(void);
|
|
void unmap_window(struct ws_win *);
|
|
void updatenumlockmask(void);
|
|
void update_modkey(unsigned int);
|
|
void update_window(struct ws_win *);
|
|
void validate_spawns(void);
|
|
int validate_win(struct ws_win *);
|
|
int validate_ws(struct workspace *);
|
|
/*void visibilitynotify(xcb_visibility_notify_event_t *);*/
|
|
void version(struct swm_region *, union arg *);
|
|
pid_t window_get_pid(xcb_window_t);
|
|
void wkill(struct swm_region *, union arg *);
|
|
void workaround(void);
|
|
void xft_init(struct swm_region *);
|
|
void _add_startup_exception(const char *, va_list);
|
|
void add_startup_exception(const char *, ...);
|
|
|
|
RB_PROTOTYPE(key_tree, key, entry, key_cmp);
|
|
RB_GENERATE(key_tree, key, entry, key_cmp);
|
|
struct key_tree keys;
|
|
|
|
void
|
|
cursors_load(void)
|
|
{
|
|
xcb_font_t cf = XCB_NONE;
|
|
int i;
|
|
|
|
for (i = 0; i < LENGTH(cursors); ++i) {
|
|
/* try to load Xcursor first. */
|
|
cursors[i].cid = XcursorLibraryLoadCursor(display,
|
|
cursors[i].name);
|
|
|
|
/* fallback to cursorfont. */
|
|
if (cursors[i].cid == XCB_CURSOR_NONE) {
|
|
if (cf == XCB_NONE) {
|
|
cf = xcb_generate_id(conn);
|
|
xcb_open_font(conn, cf, strlen("cursor"),
|
|
"cursor");
|
|
}
|
|
|
|
cursors[i].cid = xcb_generate_id(conn);
|
|
xcb_create_glyph_cursor(conn, cursors[i].cid, cf, cf,
|
|
cursors[i].cf_char, cursors[i].cf_char + 1, 0, 0, 0,
|
|
0xffff, 0xffff, 0xffff);
|
|
|
|
}
|
|
}
|
|
|
|
if (cf != XCB_NONE)
|
|
xcb_close_font(conn, cf);
|
|
}
|
|
|
|
void
|
|
cursors_cleanup(void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < LENGTH(cursors); ++i)
|
|
xcb_free_cursor(conn, cursors[i].cid);
|
|
}
|
|
|
|
char *
|
|
expand_tilde(const char *s)
|
|
{
|
|
struct passwd *ppwd;
|
|
int i;
|
|
long max;
|
|
char *user;
|
|
const char *sc = s;
|
|
char *result;
|
|
|
|
if (s == NULL)
|
|
errx(1, "expand_tilde: NULL string.");
|
|
|
|
if (s[0] != '~') {
|
|
result = strdup(sc);
|
|
goto out;
|
|
}
|
|
|
|
++s;
|
|
|
|
if ((max = sysconf(_SC_LOGIN_NAME_MAX)) == -1)
|
|
errx(1, "expand_tilde: sysconf");
|
|
|
|
if ((user = calloc(1, max + 1)) == NULL)
|
|
errx(1, "expand_tilde: calloc");
|
|
|
|
for (i = 0; s[i] != '/' && s[i] != '\0'; ++i)
|
|
user[i] = s[i];
|
|
user[i] = '\0';
|
|
s = &s[i];
|
|
|
|
ppwd = strlen(user) == 0 ? getpwuid(getuid()) : getpwnam(user);
|
|
free(user);
|
|
|
|
if (ppwd == NULL)
|
|
result = strdup(sc);
|
|
else
|
|
if (asprintf(&result, "%s%s", ppwd->pw_dir, s) == -1)
|
|
result = NULL;
|
|
out:
|
|
if (result == NULL)
|
|
errx(1, "expand_tilde: failed to allocate memory.");
|
|
|
|
return result;
|
|
}
|
|
|
|
int
|
|
parse_rgb(const char *rgb, uint16_t *rr, uint16_t *gg, uint16_t *bb)
|
|
{
|
|
unsigned int tmpr, tmpg, tmpb;
|
|
|
|
if (sscanf(rgb, "rgb:%x/%x/%x", &tmpr, &tmpg, &tmpb) != 3)
|
|
return (-1);
|
|
|
|
*rr = RGB_8_TO_16(tmpr);
|
|
*gg = RGB_8_TO_16(tmpg);
|
|
*bb = RGB_8_TO_16(tmpb);
|
|
|
|
return (0);
|
|
}
|
|
|
|
xcb_screen_t *
|
|
get_screen(int screen)
|
|
{
|
|
const xcb_setup_t *r;
|
|
xcb_screen_iterator_t iter;
|
|
|
|
if ((r = xcb_get_setup(conn)) == NULL) {
|
|
DNPRINTF(SWM_D_MISC, "get_screen: xcb_get_setup\n");
|
|
check_conn();
|
|
}
|
|
|
|
iter = xcb_setup_roots_iterator(r);
|
|
for (; iter.rem; --screen, xcb_screen_next(&iter))
|
|
if (screen == 0)
|
|
return (iter.data);
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
int
|
|
get_region_index(struct swm_region *r)
|
|
{
|
|
struct swm_region *rr;
|
|
int ridx = 0;
|
|
|
|
if (r == NULL)
|
|
return -1;
|
|
|
|
TAILQ_FOREACH(rr, &r->s->rl, entry) {
|
|
if (rr == r)
|
|
break;
|
|
++ridx;
|
|
}
|
|
|
|
if (rr == NULL)
|
|
return -1;
|
|
|
|
return ridx;
|
|
}
|
|
|
|
void
|
|
focus_flush(void)
|
|
{
|
|
if (focus_mode == SWM_FOCUS_DEFAULT)
|
|
event_drain(XCB_ENTER_NOTIFY);
|
|
else
|
|
xcb_flush(conn);
|
|
}
|
|
|
|
xcb_atom_t
|
|
get_atom_from_string(const char *str)
|
|
{
|
|
xcb_intern_atom_cookie_t c;
|
|
xcb_intern_atom_reply_t *r;
|
|
xcb_atom_t atom;
|
|
|
|
c = xcb_intern_atom(conn, 0, strlen(str), str);
|
|
r = xcb_intern_atom_reply(conn, c, NULL);
|
|
if (r) {
|
|
atom = r->atom;
|
|
free(r);
|
|
|
|
return (atom);
|
|
}
|
|
|
|
return (XCB_ATOM_NONE);
|
|
}
|
|
|
|
void
|
|
set_swm_iconic(struct ws_win *win, int newv)
|
|
{
|
|
int32_t v = newv;
|
|
|
|
win->iconic = newv;
|
|
|
|
if (newv)
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->id,
|
|
a_swm_iconic, XCB_ATOM_INTEGER, 32, 1, &v);
|
|
else
|
|
xcb_delete_property(conn, win->id, a_swm_iconic);
|
|
}
|
|
|
|
int32_t
|
|
get_swm_iconic(struct ws_win *win)
|
|
{
|
|
int32_t v = 0;
|
|
xcb_get_property_reply_t *pr = NULL;
|
|
|
|
pr = xcb_get_property_reply(conn,
|
|
xcb_get_property(conn, 0, win->id, a_swm_iconic,
|
|
XCB_ATOM_INTEGER, 0, 1), NULL);
|
|
if (!pr)
|
|
goto out;
|
|
if (pr->type != XCB_ATOM_INTEGER || pr->format != 32)
|
|
goto out;
|
|
v = *((int32_t *)xcb_get_property_value(pr));
|
|
out:
|
|
if (pr)
|
|
free(pr);
|
|
return (v);
|
|
}
|
|
|
|
void
|
|
setup_ewmh(void)
|
|
{
|
|
xcb_atom_t sup_list;
|
|
int i, j, num_screens;
|
|
|
|
sup_list = get_atom_from_string("_NET_SUPPORTED");
|
|
|
|
for (i = 0; i < LENGTH(ewmh); i++)
|
|
ewmh[i].atom = get_atom_from_string(ewmh[i].name);
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++) {
|
|
/* Support check window will be created by workaround(). */
|
|
|
|
/* Report supported atoms */
|
|
xcb_delete_property(conn, screens[i].root, sup_list);
|
|
for (j = 0; j < LENGTH(ewmh); j++)
|
|
xcb_change_property(conn, XCB_PROP_MODE_APPEND,
|
|
screens[i].root, sup_list, XCB_ATOM_ATOM, 32, 1,
|
|
&ewmh[j].atom);
|
|
}
|
|
}
|
|
|
|
void
|
|
teardown_ewmh(void)
|
|
{
|
|
int i, num_screens;
|
|
xcb_atom_t sup_check, sup_list;
|
|
xcb_window_t id;
|
|
xcb_get_property_cookie_t pc;
|
|
xcb_get_property_reply_t *pr;
|
|
|
|
sup_check = get_atom_from_string("_NET_SUPPORTING_WM_CHECK");
|
|
sup_list = get_atom_from_string("_NET_SUPPORTED");
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
|
|
for (i = 0; i < num_screens; i++) {
|
|
/* Get the support check window and destroy it */
|
|
pc = xcb_get_property(conn, 0, screens[i].root, sup_check,
|
|
XCB_ATOM_WINDOW, 0, 1);
|
|
pr = xcb_get_property_reply(conn, pc, NULL);
|
|
if (!pr)
|
|
continue;
|
|
if (pr->format == sup_check) {
|
|
id = *((xcb_window_t *)xcb_get_property_value(pr));
|
|
|
|
xcb_destroy_window(conn, id);
|
|
xcb_delete_property(conn, screens[i].root, sup_check);
|
|
xcb_delete_property(conn, screens[i].root, sup_list);
|
|
}
|
|
free(pr);
|
|
}
|
|
}
|
|
|
|
void
|
|
ewmh_autoquirk(struct ws_win *win)
|
|
{
|
|
uint32_t i, n;
|
|
xcb_atom_t *type;
|
|
xcb_get_property_cookie_t c;
|
|
xcb_get_property_reply_t *r;
|
|
|
|
c = xcb_get_property(conn, 0, win->id,
|
|
ewmh[_NET_WM_WINDOW_TYPE].atom, XCB_ATOM_ATOM, 0, UINT32_MAX);
|
|
r = xcb_get_property_reply(conn, c, NULL);
|
|
if (!r)
|
|
return;
|
|
|
|
n = xcb_get_property_value_length(r);
|
|
type = xcb_get_property_value(r);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_NORMAL].atom)
|
|
break;
|
|
if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_DOCK].atom ||
|
|
type[i] == ewmh[_NET_WM_WINDOW_TYPE_TOOLBAR].atom ||
|
|
type[i] == ewmh[_NET_WM_WINDOW_TYPE_UTILITY].atom) {
|
|
win->floating = 1;
|
|
win->quirks = SWM_Q_FLOAT | SWM_Q_ANYWHERE;
|
|
break;
|
|
}
|
|
if (type[i] == ewmh[_NET_WM_WINDOW_TYPE_SPLASH].atom ||
|
|
type[i] == ewmh[_NET_WM_WINDOW_TYPE_DIALOG].atom) {
|
|
win->floating = 1;
|
|
win->quirks = SWM_Q_FLOAT;
|
|
break;
|
|
}
|
|
}
|
|
free(r);
|
|
}
|
|
|
|
#define SWM_EWMH_ACTION_COUNT_MAX (6)
|
|
#define EWMH_F_FULLSCREEN (1<<0)
|
|
#define EWMH_F_ABOVE (1<<1)
|
|
#define EWMH_F_HIDDEN (1<<2)
|
|
#define EWMH_F_SKIP_PAGER (1<<3)
|
|
#define EWMH_F_SKIP_TASKBAR (1<<4)
|
|
#define SWM_F_MANUAL (1<<5)
|
|
|
|
int
|
|
ewmh_set_win_fullscreen(struct ws_win *win, int fs)
|
|
{
|
|
if (!win->ws->r)
|
|
return (0);
|
|
|
|
if (!win->floating)
|
|
return (0);
|
|
|
|
DNPRINTF(SWM_D_MISC, "ewmh_set_win_fullscreen: window: 0x%x, "
|
|
"fullscreen %s\n", win->id, YESNO(fs));
|
|
|
|
if (fs) {
|
|
if (!win->g_floatvalid)
|
|
store_float_geom(win, win->ws->r);
|
|
|
|
win->g = win->ws->r->g;
|
|
win->bordered = 0;
|
|
} else {
|
|
load_float_geom(win, win->ws->r);
|
|
}
|
|
|
|
return (1);
|
|
}
|
|
|
|
void
|
|
ewmh_update_actions(struct ws_win *win)
|
|
{
|
|
xcb_atom_t actions[SWM_EWMH_ACTION_COUNT_MAX];
|
|
int n = 0;
|
|
|
|
if (win == NULL)
|
|
return;
|
|
|
|
actions[n++] = ewmh[_NET_WM_ACTION_CLOSE].atom;
|
|
|
|
if (win->floating) {
|
|
actions[n++] = ewmh[_NET_WM_ACTION_MOVE].atom;
|
|
actions[n++] = ewmh[_NET_WM_ACTION_RESIZE].atom;
|
|
actions[n++] = ewmh[_NET_WM_ACTION_ABOVE].atom;
|
|
}
|
|
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->id,
|
|
ewmh[_NET_WM_ALLOWED_ACTIONS].atom, XCB_ATOM_ATOM, 32, 1, actions);
|
|
}
|
|
|
|
#define _NET_WM_STATE_REMOVE 0 /* remove/unset property */
|
|
#define _NET_WM_STATE_ADD 1 /* add/set property */
|
|
#define _NET_WM_STATE_TOGGLE 2 /* toggle property */
|
|
|
|
void
|
|
ewmh_update_win_state(struct ws_win *win, xcb_atom_t state, long action)
|
|
{
|
|
unsigned int mask = 0;
|
|
unsigned int changed = 0;
|
|
unsigned int orig_flags;
|
|
|
|
if (win == NULL)
|
|
return;
|
|
|
|
DNPRINTF(SWM_D_PROP, "ewmh_update_win_state: window: 0x%x, state: %ld, "
|
|
"action: %ld\n", win->id, (unsigned long)state, action);
|
|
|
|
if (state == ewmh[_NET_WM_STATE_FULLSCREEN].atom)
|
|
mask = EWMH_F_FULLSCREEN;
|
|
else if (state == ewmh[_NET_WM_STATE_ABOVE].atom)
|
|
mask = EWMH_F_ABOVE;
|
|
else if (state == ewmh[_SWM_WM_STATE_MANUAL].atom)
|
|
mask = SWM_F_MANUAL;
|
|
else if (state == ewmh[_NET_WM_STATE_SKIP_PAGER].atom)
|
|
mask = EWMH_F_SKIP_PAGER;
|
|
else if (state == ewmh[_NET_WM_STATE_SKIP_TASKBAR].atom)
|
|
mask = EWMH_F_SKIP_TASKBAR;
|
|
|
|
orig_flags = win->ewmh_flags;
|
|
|
|
switch (action) {
|
|
case _NET_WM_STATE_REMOVE:
|
|
win->ewmh_flags &= ~mask;
|
|
break;
|
|
case _NET_WM_STATE_ADD:
|
|
win->ewmh_flags |= mask;
|
|
break;
|
|
case _NET_WM_STATE_TOGGLE:
|
|
win->ewmh_flags ^= mask;
|
|
break;
|
|
}
|
|
|
|
changed = (win->ewmh_flags & mask) ^ (orig_flags & mask) ? 1 : 0;
|
|
|
|
if (state == ewmh[_NET_WM_STATE_ABOVE].atom) {
|
|
if (changed && !floating_toggle_win(win))
|
|
win->ewmh_flags = orig_flags; /* revert */
|
|
} else if (state == ewmh[_SWM_WM_STATE_MANUAL].atom) {
|
|
if (changed)
|
|
win->manual = (win->ewmh_flags & SWM_F_MANUAL) != 0;
|
|
} else if (state == ewmh[_NET_WM_STATE_FULLSCREEN].atom) {
|
|
if (changed && !ewmh_set_win_fullscreen(win,
|
|
win->ewmh_flags & EWMH_F_FULLSCREEN))
|
|
win->ewmh_flags = orig_flags; /* revert */
|
|
}
|
|
|
|
xcb_delete_property(conn, win->id, ewmh[_NET_WM_STATE].atom);
|
|
|
|
if (win->ewmh_flags & EWMH_F_FULLSCREEN)
|
|
xcb_change_property(conn, XCB_PROP_MODE_APPEND, win->id,
|
|
ewmh[_NET_WM_STATE].atom, XCB_ATOM_ATOM, 32, 1,
|
|
&ewmh[_NET_WM_STATE_FULLSCREEN].atom);
|
|
else if (win->ewmh_flags & EWMH_F_SKIP_PAGER)
|
|
xcb_change_property(conn, XCB_PROP_MODE_APPEND, win->id,
|
|
ewmh[_NET_WM_STATE].atom, XCB_ATOM_ATOM, 32, 1,
|
|
&ewmh[_NET_WM_STATE_SKIP_PAGER].atom);
|
|
else if (win->ewmh_flags & EWMH_F_SKIP_TASKBAR)
|
|
xcb_change_property(conn, XCB_PROP_MODE_APPEND, win->id,
|
|
ewmh[_NET_WM_STATE].atom, XCB_ATOM_ATOM, 32, 1,
|
|
&ewmh[_NET_WM_STATE_SKIP_TASKBAR].atom);
|
|
else if (win->ewmh_flags & EWMH_F_ABOVE)
|
|
xcb_change_property(conn, XCB_PROP_MODE_APPEND, win->id,
|
|
ewmh[_NET_WM_STATE].atom, XCB_ATOM_ATOM, 32, 1,
|
|
&ewmh[_NET_WM_STATE_ABOVE].atom);
|
|
else if (win->ewmh_flags & SWM_F_MANUAL)
|
|
xcb_change_property(conn, XCB_PROP_MODE_APPEND, win->id,
|
|
ewmh[_NET_WM_STATE].atom, XCB_ATOM_ATOM, 32, 1,
|
|
&ewmh[_SWM_WM_STATE_MANUAL].atom);
|
|
}
|
|
|
|
void
|
|
ewmh_get_win_state(struct ws_win *win)
|
|
{
|
|
xcb_atom_t *states;
|
|
xcb_get_property_cookie_t c;
|
|
xcb_get_property_reply_t *r;
|
|
int i, n;
|
|
|
|
if (win == NULL)
|
|
return;
|
|
|
|
win->ewmh_flags = 0;
|
|
if (win->floating)
|
|
win->ewmh_flags |= EWMH_F_ABOVE;
|
|
if (win->manual)
|
|
win->ewmh_flags |= SWM_F_MANUAL;
|
|
|
|
c = xcb_get_property(conn, 0, win->id, ewmh[_NET_WM_STATE].atom,
|
|
XCB_ATOM_ATOM, 0, UINT32_MAX);
|
|
r = xcb_get_property_reply(conn, c, NULL);
|
|
if (!r)
|
|
return;
|
|
|
|
states = xcb_get_property_value(r);
|
|
n = xcb_get_property_value_length(r);
|
|
|
|
for (i = 0; i < n; i++)
|
|
ewmh_update_win_state(win, states[i], _NET_WM_STATE_ADD);
|
|
|
|
free(r);
|
|
}
|
|
|
|
/* events */
|
|
#ifdef SWM_DEBUG
|
|
void
|
|
dumpwins(struct swm_region *r, union arg *args)
|
|
{
|
|
struct ws_win *win;
|
|
uint32_t state;
|
|
xcb_get_window_attributes_cookie_t c;
|
|
xcb_get_window_attributes_reply_t *wa;
|
|
|
|
/* suppress unused warning since var is needed */
|
|
(void)args;
|
|
|
|
if (r->ws == NULL) {
|
|
warnx("dumpwins: invalid workspace");
|
|
return;
|
|
}
|
|
|
|
warnx("=== managed window list ws %02d ===", r->ws->idx);
|
|
TAILQ_FOREACH(win, &r->ws->winlist, entry) {
|
|
state = getstate(win->id);
|
|
c = xcb_get_window_attributes(conn, win->id);
|
|
wa = xcb_get_window_attributes_reply(conn, c, NULL);
|
|
if (wa) {
|
|
warnx("window: 0x%x, map_state: %d, state: %u, "
|
|
"transient: 0x%x", win->id, wa->map_state,
|
|
state, win->transient);
|
|
free(wa);
|
|
} else
|
|
warnx("window: 0x%x, failed xcb_get_window_attributes",
|
|
win->id);
|
|
}
|
|
|
|
warnx("===== unmanaged window list =====");
|
|
TAILQ_FOREACH(win, &r->ws->unmanagedlist, entry) {
|
|
state = getstate(win->id);
|
|
c = xcb_get_window_attributes(conn, win->id);
|
|
wa = xcb_get_window_attributes_reply(conn, c, NULL);
|
|
if (wa) {
|
|
warnx("window: 0x%x, map_state: %d, state: %u, "
|
|
"transient: 0x%x", win->id, wa->map_state,
|
|
state, win->transient);
|
|
free(wa);
|
|
} else
|
|
warnx("window: 0x%x, failed xcb_get_window_attributes",
|
|
win->id);
|
|
}
|
|
|
|
warnx("=================================");
|
|
}
|
|
#else
|
|
void
|
|
dumpwins(struct swm_region *r, union arg *s)
|
|
{
|
|
(void)r;
|
|
(void)s;
|
|
}
|
|
#endif /* SWM_DEBUG */
|
|
|
|
void
|
|
sighdlr(int sig)
|
|
{
|
|
int saved_errno, status;
|
|
pid_t pid;
|
|
|
|
saved_errno = errno;
|
|
|
|
switch (sig) {
|
|
case SIGCHLD:
|
|
while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
|
|
if (pid == -1) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
#ifdef SWM_DEBUG
|
|
if (errno != ECHILD)
|
|
warn("sighdlr: waitpid");
|
|
#endif /* SWM_DEBUG */
|
|
break;
|
|
}
|
|
if (pid == searchpid)
|
|
search_resp = 1;
|
|
|
|
#ifdef SWM_DEBUG
|
|
if (WIFEXITED(status)) {
|
|
if (WEXITSTATUS(status) != 0)
|
|
warnx("sighdlr: child exit status: %d",
|
|
WEXITSTATUS(status));
|
|
} else
|
|
warnx("sighdlr: child is terminated "
|
|
"abnormally");
|
|
#endif /* SWM_DEBUG */
|
|
}
|
|
break;
|
|
|
|
case SIGHUP:
|
|
restart_wm = 1;
|
|
break;
|
|
case SIGINT:
|
|
case SIGTERM:
|
|
case SIGQUIT:
|
|
running = 0;
|
|
break;
|
|
}
|
|
|
|
errno = saved_errno;
|
|
}
|
|
|
|
struct pid_e *
|
|
find_pid(pid_t pid)
|
|
{
|
|
struct pid_e *p = NULL;
|
|
|
|
DNPRINTF(SWM_D_MISC, "find_pid: %d\n", pid);
|
|
|
|
if (pid == 0)
|
|
return (NULL);
|
|
|
|
TAILQ_FOREACH(p, &pidlist, entry) {
|
|
if (p->pid == pid)
|
|
return (p);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
uint32_t
|
|
name_to_pixel(const char *colorname)
|
|
{
|
|
uint32_t result = 0;
|
|
char cname[32] = "#";
|
|
xcb_screen_t *screen;
|
|
xcb_colormap_t cmap;
|
|
xcb_alloc_color_reply_t *cr;
|
|
xcb_alloc_named_color_reply_t *nr;
|
|
uint16_t rr, gg, bb;
|
|
|
|
screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
|
|
cmap = screen->default_colormap;
|
|
|
|
/* color is in format rgb://rr/gg/bb */
|
|
if (strncmp(colorname, "rgb:", 4) == 0) {
|
|
if (parse_rgb(colorname, &rr, &gg, &bb) == -1)
|
|
warnx("could not parse rgb %s", colorname);
|
|
else {
|
|
cr = xcb_alloc_color_reply(conn,
|
|
xcb_alloc_color(conn, cmap, rr, gg, bb),
|
|
NULL);
|
|
if (cr) {
|
|
result = cr->pixel;
|
|
free(cr);
|
|
} else
|
|
warnx("color '%s' not found", colorname);
|
|
}
|
|
} else {
|
|
nr = xcb_alloc_named_color_reply(conn,
|
|
xcb_alloc_named_color(conn, cmap, strlen(colorname),
|
|
colorname), NULL);
|
|
if (!nr) {
|
|
strlcat(cname, colorname + 2, sizeof cname - 1);
|
|
nr = xcb_alloc_named_color_reply(conn,
|
|
xcb_alloc_named_color(conn, cmap, strlen(cname),
|
|
cname), NULL);
|
|
}
|
|
if (nr) {
|
|
result = nr->pixel;
|
|
free(nr);
|
|
} else
|
|
warnx("color '%s' not found", colorname);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
void
|
|
setscreencolor(char *val, int i, int c)
|
|
{
|
|
int num_screens;
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
if (i > 0 && i <= num_screens) {
|
|
screens[i - 1].c[c].pixel = name_to_pixel(val);
|
|
free(screens[i - 1].c[c].name);
|
|
if ((screens[i - 1].c[c].name = strdup(val)) == NULL)
|
|
err(1, "strdup");
|
|
} else if (i == -1) {
|
|
for (i = 0; i < num_screens; i++) {
|
|
screens[i].c[c].pixel = name_to_pixel(val);
|
|
free(screens[i].c[c].name);
|
|
if ((screens[i].c[c].name = strdup(val)) == NULL)
|
|
err(1, "strdup");
|
|
}
|
|
} else
|
|
errx(1, "invalid screen index: %d out of bounds (maximum %d)",
|
|
i, num_screens);
|
|
}
|
|
|
|
void
|
|
fancy_stacker(struct workspace *ws)
|
|
{
|
|
strlcpy(ws->stacker, "[ ]", sizeof ws->stacker);
|
|
if (ws->cur_layout->l_stack == vertical_stack)
|
|
snprintf(ws->stacker, sizeof ws->stacker,
|
|
ws->l_state.vertical_flip ? "[%d>%d]" : "[%d|%d]",
|
|
ws->l_state.vertical_mwin, ws->l_state.vertical_stacks);
|
|
if (ws->cur_layout->l_stack == horizontal_stack)
|
|
snprintf(ws->stacker, sizeof ws->stacker,
|
|
ws->l_state.horizontal_flip ? "[%dv%d]" : "[%d-%d]",
|
|
ws->l_state.horizontal_mwin, ws->l_state.horizontal_stacks);
|
|
}
|
|
|
|
void
|
|
plain_stacker(struct workspace *ws)
|
|
{
|
|
strlcpy(ws->stacker, "[ ]", sizeof ws->stacker);
|
|
if (ws->cur_layout->l_stack == vertical_stack)
|
|
strlcpy(ws->stacker, ws->l_state.vertical_flip ? "[>]" : "[|]",
|
|
sizeof ws->stacker);
|
|
if (ws->cur_layout->l_stack == horizontal_stack)
|
|
strlcpy(ws->stacker, ws->l_state.horizontal_flip ? "[v]" : "[-]",
|
|
sizeof ws->stacker);
|
|
}
|
|
|
|
void
|
|
custom_region(char *val)
|
|
{
|
|
unsigned int x, y, w, h;
|
|
int sidx, num_screens;
|
|
xcb_screen_t *screen;
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
if (sscanf(val, "screen[%d]:%ux%u+%u+%u", &sidx, &w, &h, &x, &y) != 5)
|
|
errx(1, "invalid custom region, "
|
|
"should be 'screen[<n>]:<n>x<n>+<n>+<n>");
|
|
if (sidx < 1 || sidx > num_screens)
|
|
errx(1, "invalid screen index: %d out of bounds (maximum %d)",
|
|
sidx, num_screens);
|
|
sidx--;
|
|
|
|
if ((screen = get_screen(sidx)) == NULL)
|
|
errx(1, "ERROR: can't get screen %d.", sidx);
|
|
|
|
if (w < 1 || h < 1)
|
|
errx(1, "region %ux%u+%u+%u too small", w, h, x, y);
|
|
|
|
if (x > screen->width_in_pixels ||
|
|
y > screen->height_in_pixels ||
|
|
w + x > screen->width_in_pixels ||
|
|
h + y > screen->height_in_pixels) {
|
|
warnx("ignoring region %ux%u+%u+%u - not within screen "
|
|
"boundaries (%ux%u)", w, h, x, y,
|
|
screen->width_in_pixels, screen->height_in_pixels);
|
|
return;
|
|
}
|
|
|
|
new_region(&screens[sidx], x, y, w, h);
|
|
}
|
|
|
|
void
|
|
socket_setnonblock(int fd)
|
|
{
|
|
int flags;
|
|
|
|
if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
|
|
err(1, "fcntl F_GETFL");
|
|
flags |= O_NONBLOCK;
|
|
if ((flags = fcntl(fd, F_SETFL, flags)) == -1)
|
|
err(1, "fcntl F_SETFL");
|
|
}
|
|
|
|
void
|
|
bar_print_legacy(struct swm_region *r, const char *s)
|
|
{
|
|
xcb_rectangle_t rect;
|
|
uint32_t gcv[1];
|
|
XGCValues gcvd;
|
|
int x = 0;
|
|
size_t len;
|
|
XRectangle ibox, lbox;
|
|
GC draw;
|
|
|
|
len = strlen(s);
|
|
XmbTextExtents(bar_fs, s, len, &ibox, &lbox);
|
|
|
|
switch (bar_justify) {
|
|
case SWM_BAR_JUSTIFY_LEFT:
|
|
x = SWM_BAR_OFFSET;
|
|
break;
|
|
case SWM_BAR_JUSTIFY_CENTER:
|
|
x = (WIDTH(r) - lbox.width) / 2;
|
|
break;
|
|
case SWM_BAR_JUSTIFY_RIGHT:
|
|
x = WIDTH(r) - lbox.width - SWM_BAR_OFFSET;
|
|
break;
|
|
}
|
|
|
|
if (x < SWM_BAR_OFFSET)
|
|
x = SWM_BAR_OFFSET;
|
|
|
|
rect.x = 0;
|
|
rect.y = 0;
|
|
rect.width = WIDTH(r->bar);
|
|
rect.height = HEIGHT(r->bar);
|
|
|
|
/* clear back buffer */
|
|
gcv[0] = r->s->c[SWM_S_COLOR_BAR].pixel;
|
|
xcb_change_gc(conn, r->s->bar_gc, XCB_GC_FOREGROUND, gcv);
|
|
xcb_poly_fill_rectangle(conn, r->bar->buffer, r->s->bar_gc, 1, &rect);
|
|
|
|
/* draw back buffer */
|
|
gcvd.graphics_exposures = 0;
|
|
draw = XCreateGC(display, r->bar->buffer, GCGraphicsExposures, &gcvd);
|
|
XSetForeground(display, draw, r->s->c[SWM_S_COLOR_BAR_FONT].pixel);
|
|
DRAWSTRING(display, r->bar->buffer, bar_fs, draw,
|
|
x, (bar_fs_extents->max_logical_extent.height - lbox.height) / 2 -
|
|
lbox.y, s, len);
|
|
XFreeGC(display, draw);
|
|
|
|
/* blt */
|
|
xcb_copy_area(conn, r->bar->buffer, r->bar->id, r->s->bar_gc, 0, 0,
|
|
0, 0, WIDTH(r->bar), HEIGHT(r->bar));
|
|
}
|
|
|
|
void
|
|
bar_print(struct swm_region *r, const char *s)
|
|
{
|
|
size_t len;
|
|
xcb_rectangle_t rect;
|
|
uint32_t gcv[1];
|
|
int32_t x = 0;
|
|
XGlyphInfo info;
|
|
XftDraw *draw;
|
|
|
|
len = strlen(s);
|
|
|
|
XftTextExtentsUtf8(display, bar_font, (FcChar8 *)s, len, &info);
|
|
|
|
switch (bar_justify) {
|
|
case SWM_BAR_JUSTIFY_LEFT:
|
|
x = SWM_BAR_OFFSET;
|
|
break;
|
|
case SWM_BAR_JUSTIFY_CENTER:
|
|
x = (WIDTH(r) - info.width) / 2;
|
|
break;
|
|
case SWM_BAR_JUSTIFY_RIGHT:
|
|
x = WIDTH(r) - info.width - SWM_BAR_OFFSET;
|
|
break;
|
|
}
|
|
|
|
if (x < SWM_BAR_OFFSET)
|
|
x = SWM_BAR_OFFSET;
|
|
|
|
rect.x = 0;
|
|
rect.y = 0;
|
|
rect.width = WIDTH(r->bar);
|
|
rect.height = HEIGHT(r->bar);
|
|
|
|
/* clear back buffer */
|
|
gcv[0] = r->s->c[SWM_S_COLOR_BAR].pixel;
|
|
xcb_change_gc(conn, r->s->bar_gc, XCB_GC_FOREGROUND, gcv);
|
|
xcb_poly_fill_rectangle(conn, r->bar->buffer, r->s->bar_gc, 1, &rect);
|
|
|
|
/* draw back buffer */
|
|
draw = XftDrawCreate(display, r->bar->buffer,
|
|
DefaultVisual(display, r->s->idx),
|
|
DefaultColormap(display, r->s->idx));
|
|
|
|
XftDrawStringUtf8(draw, &bar_font_color, bar_font, x,
|
|
(HEIGHT(r->bar) + bar_font->height) / 2 - bar_font->descent,
|
|
(FcChar8 *)s, len);
|
|
|
|
XftDrawDestroy(draw);
|
|
|
|
/* blt */
|
|
xcb_copy_area(conn, r->bar->buffer, r->bar->id, r->s->bar_gc, 0, 0,
|
|
0, 0, WIDTH(r->bar), HEIGHT(r->bar));
|
|
}
|
|
|
|
void
|
|
bar_extra_stop(void)
|
|
{
|
|
if (bar_pipe[0]) {
|
|
close(bar_pipe[0]);
|
|
bzero(bar_pipe, sizeof bar_pipe);
|
|
}
|
|
if (bar_pid) {
|
|
kill(bar_pid, SIGTERM);
|
|
bar_pid = 0;
|
|
}
|
|
strlcpy(bar_ext, "", sizeof bar_ext);
|
|
bar_extra = 0;
|
|
}
|
|
|
|
void
|
|
bar_class_name(char *s, size_t sz, struct swm_region *r)
|
|
{
|
|
if (r == NULL || r->ws == NULL || r->ws->focus == NULL)
|
|
return;
|
|
if (r->ws->focus->ch.class_name != NULL)
|
|
strlcat(s, r->ws->focus->ch.class_name, sz);
|
|
}
|
|
|
|
void
|
|
bar_title_name(char *s, size_t sz, struct swm_region *r)
|
|
{
|
|
if (r == NULL || r->ws == NULL || r->ws->focus == NULL)
|
|
return;
|
|
if (r->ws->focus->ch.instance_name != NULL)
|
|
strlcat(s, r->ws->focus->ch.instance_name, sz);
|
|
}
|
|
|
|
void
|
|
bar_class_title_name(char *s, size_t sz, struct swm_region *r)
|
|
{
|
|
if (r == NULL || r->ws == NULL || r->ws->focus == NULL)
|
|
return;
|
|
|
|
bar_class_name(s, sz, r);
|
|
strlcat(s, ":", sz);
|
|
bar_title_name(s, sz, r);
|
|
}
|
|
|
|
void
|
|
bar_window_float(char *s, size_t sz, struct swm_region *r)
|
|
{
|
|
if (r == NULL || r ->ws == NULL || r->ws->focus == NULL)
|
|
return;
|
|
if (r->ws->focus->floating)
|
|
strlcat(s, "(f)", sz);
|
|
}
|
|
|
|
void
|
|
bar_window_name(char *s, size_t sz, struct swm_region *r)
|
|
{
|
|
char *title;
|
|
|
|
if (r == NULL || r->ws == NULL || r->ws->focus == NULL)
|
|
return;
|
|
if ((title = get_win_name(r->ws->focus->id)) == NULL)
|
|
return;
|
|
|
|
strlcat(s, title, sz);
|
|
free(title);
|
|
}
|
|
|
|
int urgent[SWM_WS_MAX];
|
|
void
|
|
bar_urgent(char *s, size_t sz)
|
|
{
|
|
struct ws_win *win;
|
|
int i, j, num_screens;
|
|
char b[8];
|
|
xcb_get_property_cookie_t c;
|
|
xcb_icccm_wm_hints_t hints;
|
|
|
|
for (i = 0; i < workspace_limit; i++)
|
|
urgent[i] = 0;
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++)
|
|
for (j = 0; j < workspace_limit; j++)
|
|
TAILQ_FOREACH(win, &screens[i].ws[j].winlist, entry) {
|
|
c = xcb_icccm_get_wm_hints(conn, win->id);
|
|
if (xcb_icccm_get_wm_hints_reply(conn, c,
|
|
&hints, NULL) == 0)
|
|
continue;
|
|
if (hints.flags & XCB_ICCCM_WM_HINT_X_URGENCY)
|
|
urgent[j] = 1;
|
|
}
|
|
|
|
for (i = 0; i < workspace_limit; i++) {
|
|
if (urgent[i])
|
|
snprintf(b, sizeof b, "%d ", i + 1);
|
|
else
|
|
snprintf(b, sizeof b, "- ");
|
|
strlcat(s, b, sz);
|
|
}
|
|
}
|
|
|
|
void
|
|
bar_workspace_name(char *s, size_t sz, struct swm_region *r)
|
|
{
|
|
if (r == NULL || r->ws == NULL)
|
|
return;
|
|
if (r->ws->name != NULL)
|
|
strlcat(s, r->ws->name, sz);
|
|
}
|
|
|
|
/* build the default bar format according to the defined enabled options */
|
|
void
|
|
bar_fmt(const char *fmtexp, char *fmtnew, struct swm_region *r, size_t sz)
|
|
{
|
|
/* if format provided, just copy the buffers */
|
|
if (bar_format != NULL) {
|
|
strlcpy(fmtnew, fmtexp, sz);
|
|
return;
|
|
}
|
|
|
|
/* reset the output buffer */
|
|
*fmtnew = '\0';
|
|
|
|
strlcat(fmtnew, "+N:+I ", sz);
|
|
if (stack_enabled)
|
|
strlcat(fmtnew, "+S", sz);
|
|
strlcat(fmtnew, " ", sz);
|
|
|
|
/* only show the workspace name if there's actually one */
|
|
if (r != NULL && r->ws != NULL && r->ws->name != NULL)
|
|
strlcat(fmtnew, "<+D>", sz);
|
|
strlcat(fmtnew, "+3<", sz);
|
|
|
|
if (clock_enabled) {
|
|
strlcat(fmtnew, fmtexp, sz);
|
|
strlcat(fmtnew, "+4<", sz);
|
|
}
|
|
|
|
/* bar_urgent already adds the space before the last asterisk */
|
|
if (urgent_enabled)
|
|
strlcat(fmtnew, "* +U*+4<", sz);
|
|
|
|
if (title_class_enabled) {
|
|
strlcat(fmtnew, "+C", sz);
|
|
if (!title_name_enabled)
|
|
strlcat(fmtnew, "+4<", sz);
|
|
}
|
|
|
|
/* checks needed by the colon and floating strlcat(3) calls below */
|
|
if (r != NULL && r->ws != NULL && r->ws->focus != NULL) {
|
|
if (title_name_enabled) {
|
|
if (title_class_enabled)
|
|
strlcat(fmtnew, ":", sz);
|
|
strlcat(fmtnew, "+T+4<", sz);
|
|
}
|
|
if (window_name_enabled) {
|
|
if (r->ws->focus->floating)
|
|
strlcat(fmtnew, "+F ", sz);
|
|
strlcat(fmtnew, "+64W ", sz);
|
|
}
|
|
}
|
|
|
|
/* finally add the action script output and the version */
|
|
strlcat(fmtnew, "+4<+A+4<+V", sz);
|
|
}
|
|
|
|
void
|
|
bar_replace_pad(char *tmp, int *limit, size_t sz)
|
|
{
|
|
/* special case; no limit given, pad one space, instead */
|
|
if (*limit == (int)sz - 1)
|
|
*limit = 1;
|
|
snprintf(tmp, sz, "%*s", *limit, " ");
|
|
}
|
|
|
|
/* replaces the bar format character sequences (like in tmux(1)) */
|
|
char *
|
|
bar_replace_seq(char *fmt, char *fmtrep, struct swm_region *r, size_t *offrep,
|
|
size_t sz)
|
|
{
|
|
char *ptr;
|
|
char tmp[SWM_BAR_MAX];
|
|
int limit, size;
|
|
size_t len;
|
|
|
|
/* reset strlcat(3) buffer */
|
|
*tmp = '\0';
|
|
|
|
/* get number, if any */
|
|
fmt++;
|
|
size = 0;
|
|
if (sscanf(fmt, "%d%n", &limit, &size) != 1)
|
|
limit = sizeof tmp - 1;
|
|
if (limit <= 0 || limit >= (int)sizeof tmp)
|
|
limit = sizeof tmp - 1;
|
|
|
|
/* there is nothing to replace (ie EOL) */
|
|
fmt += size;
|
|
if (*fmt == '\0')
|
|
return (fmt);
|
|
|
|
switch (*fmt) {
|
|
case '<':
|
|
bar_replace_pad(tmp, &limit, sizeof tmp);
|
|
break;
|
|
case 'A':
|
|
snprintf(tmp, sizeof tmp, "%s", bar_ext);
|
|
break;
|
|
case 'C':
|
|
bar_class_name(tmp, sizeof tmp, r);
|
|
break;
|
|
case 'D':
|
|
bar_workspace_name(tmp, sizeof tmp, r);
|
|
break;
|
|
case 'F':
|
|
bar_window_float(tmp, sizeof tmp, r);
|
|
break;
|
|
case 'I':
|
|
snprintf(tmp, sizeof tmp, "%d", r->ws->idx + 1);
|
|
break;
|
|
case 'N':
|
|
snprintf(tmp, sizeof tmp, "%d", r->s->idx + 1);
|
|
break;
|
|
case 'P':
|
|
bar_class_title_name(tmp, sizeof tmp, r);
|
|
break;
|
|
case 'S':
|
|
snprintf(tmp, sizeof tmp, "%s", r->ws->stacker);
|
|
break;
|
|
case 'T':
|
|
bar_title_name(tmp, sizeof tmp, r);
|
|
break;
|
|
case 'U':
|
|
bar_urgent(tmp, sizeof tmp);
|
|
break;
|
|
case 'V':
|
|
snprintf(tmp, sizeof tmp, "%s", bar_vertext);
|
|
break;
|
|
case 'W':
|
|
bar_window_name(tmp, sizeof tmp, r);
|
|
break;
|
|
default:
|
|
/* unknown character sequence; copy as-is */
|
|
snprintf(tmp, sizeof tmp, "+%c", *fmt);
|
|
break;
|
|
}
|
|
|
|
len = strlen(tmp);
|
|
ptr = tmp;
|
|
if ((int)len < limit)
|
|
limit = len;
|
|
while (limit-- > 0) {
|
|
if (*offrep >= sz - 1)
|
|
break;
|
|
fmtrep[(*offrep)++] = *ptr++;
|
|
}
|
|
|
|
fmt++;
|
|
return (fmt);
|
|
}
|
|
|
|
void
|
|
bar_replace(char *fmt, char *fmtrep, struct swm_region *r, size_t sz)
|
|
{
|
|
size_t off;
|
|
|
|
off = 0;
|
|
while (*fmt != '\0') {
|
|
if (*fmt != '+') {
|
|
/* skip ordinary characters */
|
|
if (off >= sz - 1)
|
|
break;
|
|
fmtrep[off++] = *fmt++;
|
|
continue;
|
|
}
|
|
|
|
/* character sequence found; replace it */
|
|
fmt = bar_replace_seq(fmt, fmtrep, r, &off, sz);
|
|
if (off >= sz - 1)
|
|
break;
|
|
}
|
|
|
|
fmtrep[off] = '\0';
|
|
}
|
|
|
|
void
|
|
bar_fmt_expand(char *fmtexp, size_t sz)
|
|
{
|
|
char *fmt = NULL;
|
|
size_t len;
|
|
struct tm tm;
|
|
time_t tmt;
|
|
|
|
/* start by grabbing the current time and date */
|
|
time(&tmt);
|
|
localtime_r(&tmt, &tm);
|
|
|
|
/* figure out what to expand */
|
|
if (bar_format != NULL)
|
|
fmt = bar_format;
|
|
else if (bar_format == NULL && clock_enabled)
|
|
fmt = clock_format;
|
|
/* if nothing to expand bail out */
|
|
if (fmt == NULL) {
|
|
*fmtexp = '\0';
|
|
return;
|
|
}
|
|
|
|
/* copy as-is, just in case the format shouldn't be expanded below */
|
|
strlcpy(fmtexp, fmt, sz);
|
|
/* finally pass the string through strftime(3) */
|
|
#ifndef SWM_DENY_CLOCK_FORMAT
|
|
if ((len = strftime(fmtexp, sz, fmt, &tm)) == 0)
|
|
warnx("format too long");
|
|
fmtexp[len] = '\0';
|
|
#endif
|
|
}
|
|
|
|
/* Redraws the bar; need to follow with xcb_flush() or focus_flush(). */
|
|
void
|
|
bar_draw(void)
|
|
{
|
|
char fmtexp[SWM_BAR_MAX], fmtnew[SWM_BAR_MAX];
|
|
char fmtrep[SWM_BAR_MAX];
|
|
int i, num_screens;
|
|
struct swm_region *r;
|
|
|
|
/* expand the format by first passing it through strftime(3) */
|
|
bar_fmt_expand(fmtexp, sizeof fmtexp);
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++) {
|
|
TAILQ_FOREACH(r, &screens[i].rl, entry) {
|
|
if (r->bar == NULL)
|
|
continue;
|
|
|
|
if (bar_enabled && r->ws->bar_enabled)
|
|
xcb_map_window(conn, r->bar->id);
|
|
else {
|
|
xcb_unmap_window(conn, r->bar->id);
|
|
continue;
|
|
}
|
|
|
|
if (startup_exception)
|
|
snprintf(fmtrep, sizeof fmtrep, "total "
|
|
"exceptions: %d, first exception: %s",
|
|
nr_exceptions,
|
|
startup_exception);
|
|
else {
|
|
bar_fmt(fmtexp, fmtnew, r, sizeof fmtnew);
|
|
bar_replace(fmtnew, fmtrep, r, sizeof fmtrep);
|
|
}
|
|
if (bar_font_legacy)
|
|
bar_print_legacy(r, fmtrep);
|
|
else
|
|
bar_print(r, fmtrep);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Reads external script output; call when stdin is readable.
|
|
* Returns 1 if bar_ext was updated; otherwise 0.
|
|
*/
|
|
int
|
|
bar_extra_update(void)
|
|
{
|
|
size_t len;
|
|
char b[SWM_BAR_MAX];
|
|
int changed = 0;
|
|
|
|
if (!bar_extra)
|
|
return changed;
|
|
|
|
while (fgets(b, sizeof(b), stdin) != NULL) {
|
|
if (bar_enabled) {
|
|
len = strlen(b);
|
|
if (b[len - 1] == '\n') {
|
|
/* Remove newline. */
|
|
b[--len] = '\0';
|
|
|
|
/* "Clear" bar_ext. */
|
|
bar_ext[0] = '\0';
|
|
|
|
/* Flush buffered output. */
|
|
strlcpy(bar_ext, bar_ext_buf, sizeof(bar_ext));
|
|
bar_ext_buf[0] = '\0';
|
|
|
|
/* Append new output to bar. */
|
|
strlcat(bar_ext, b, sizeof(bar_ext));
|
|
|
|
changed = 1;
|
|
} else {
|
|
/* Buffer output. */
|
|
strlcat(bar_ext_buf, b, sizeof(bar_ext_buf));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (errno != EAGAIN) {
|
|
warn("bar_action failed");
|
|
bar_extra_stop();
|
|
changed = 1;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
void
|
|
bar_toggle(struct swm_region *r, union arg *args)
|
|
{
|
|
struct swm_region *tmpr;
|
|
int i, num_screens;
|
|
|
|
/* suppress unused warnings since vars are needed */
|
|
(void)r;
|
|
(void)args;
|
|
|
|
DNPRINTF(SWM_D_BAR, "bar_toggle\n");
|
|
|
|
switch (args->id) {
|
|
case SWM_ARG_ID_BAR_TOGGLE_WS:
|
|
/* Only change if master switch is enabled. */
|
|
if (bar_enabled)
|
|
r->ws->bar_enabled = !r->ws->bar_enabled;
|
|
else
|
|
bar_enabled = r->ws->bar_enabled = 1;
|
|
break;
|
|
case SWM_ARG_ID_BAR_TOGGLE:
|
|
bar_enabled = !bar_enabled;
|
|
break;
|
|
}
|
|
|
|
/* update bars as necessary */
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++)
|
|
TAILQ_FOREACH(tmpr, &screens[i].rl, entry)
|
|
if (tmpr->bar) {
|
|
if (bar_enabled && tmpr->ws->bar_enabled)
|
|
xcb_map_window(conn, tmpr->bar->id);
|
|
else
|
|
xcb_unmap_window(conn, tmpr->bar->id);
|
|
}
|
|
|
|
stack();
|
|
|
|
/* must be after stack */
|
|
bar_draw();
|
|
|
|
focus_flush();
|
|
}
|
|
|
|
void
|
|
bar_extra_setup(void)
|
|
{
|
|
/* do this here because the conf file is in memory */
|
|
if (!bar_extra && bar_argv[0]) {
|
|
/* launch external status app */
|
|
bar_extra = 1;
|
|
if (pipe(bar_pipe) == -1)
|
|
err(1, "pipe error");
|
|
socket_setnonblock(bar_pipe[0]);
|
|
socket_setnonblock(bar_pipe[1]); /* XXX hmmm, really? */
|
|
|
|
/* Set stdin to read from the pipe. */
|
|
if (dup2(bar_pipe[0], STDIN_FILENO) == -1)
|
|
err(1, "dup2");
|
|
|
|
/* Set stdout to write to the pipe. */
|
|
if (dup2(bar_pipe[1], STDOUT_FILENO) == -1)
|
|
err(1, "dup2");
|
|
|
|
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
|
|
err(1, "could not disable SIGPIPE");
|
|
switch (bar_pid = fork()) {
|
|
case -1:
|
|
err(1, "cannot fork");
|
|
break;
|
|
case 0: /* child */
|
|
close(bar_pipe[0]);
|
|
execvp(bar_argv[0], bar_argv);
|
|
err(1, "%s external app failed", bar_argv[0]);
|
|
break;
|
|
default: /* parent */
|
|
close(bar_pipe[1]);
|
|
break;
|
|
}
|
|
|
|
atexit(kill_bar_extra_atexit);
|
|
}
|
|
}
|
|
|
|
void
|
|
kill_bar_extra_atexit(void)
|
|
{
|
|
if (bar_pid)
|
|
kill(bar_pid, SIGTERM);
|
|
}
|
|
|
|
int
|
|
isxlfd(char *s)
|
|
{
|
|
int count = 0;
|
|
|
|
while ((s = index(s, '-'))) {
|
|
++count;
|
|
++s;
|
|
}
|
|
|
|
return (count == 14);
|
|
}
|
|
|
|
void
|
|
fontset_init(void)
|
|
{
|
|
char *default_string;
|
|
char **missing_charsets;
|
|
int num_missing_charsets = 0;
|
|
int i;
|
|
|
|
if (bar_fs) {
|
|
XFreeFontSet(display, bar_fs);
|
|
bar_fs = NULL;
|
|
}
|
|
|
|
DNPRINTF(SWM_D_INIT, "fontset_init: loading bar_fonts: %s\n", bar_fonts);
|
|
|
|
bar_fs = XCreateFontSet(display, bar_fonts, &missing_charsets,
|
|
&num_missing_charsets, &default_string);
|
|
|
|
if (num_missing_charsets > 0) {
|
|
warnx("Unable to load charset(s):");
|
|
|
|
for (i = 0; i < num_missing_charsets; ++i)
|
|
warnx("%s", missing_charsets[i]);
|
|
|
|
XFreeStringList(missing_charsets);
|
|
|
|
if (strcmp(default_string, ""))
|
|
warnx("Glyphs from those sets will be replaced "
|
|
"by '%s'.", default_string);
|
|
else
|
|
warnx("Glyphs from those sets won't be drawn.");
|
|
}
|
|
|
|
if (bar_fs == NULL)
|
|
errx(1, "Error creating font set structure.");
|
|
|
|
bar_fs_extents = XExtentsOfFontSet(bar_fs);
|
|
|
|
bar_height = bar_fs_extents->max_logical_extent.height +
|
|
2 * bar_border_width;
|
|
|
|
if (bar_height < 1)
|
|
bar_height = 1;
|
|
}
|
|
|
|
void
|
|
xft_init(struct swm_region *r)
|
|
{
|
|
char *font, *d, *search;
|
|
XRenderColor color;
|
|
|
|
if (bar_font == NULL) {
|
|
if ((d = strdup(bar_fonts)) == NULL)
|
|
errx(1, "insufficient memory.");
|
|
search = d;
|
|
while ((font = strsep(&search, ",")) != NULL) {
|
|
if (*font == '\0')
|
|
continue;
|
|
|
|
DNPRINTF(SWM_D_INIT, "xft_init: try font %s\n", font);
|
|
|
|
if (isxlfd(font)) {
|
|
bar_font = XftFontOpenXlfd(display, r->s->idx,
|
|
font);
|
|
} else {
|
|
bar_font = XftFontOpenName(display, r->s->idx,
|
|
font);
|
|
}
|
|
|
|
if (!bar_font) {
|
|
warnx("unable to load font %s", font);
|
|
continue;
|
|
} else {
|
|
DNPRINTF(SWM_D_INIT, "successfully opened "
|
|
"font %s\n", font);
|
|
break;
|
|
}
|
|
}
|
|
free(d);
|
|
}
|
|
|
|
if (bar_font == NULL)
|
|
errx(1, "unable to open a font");
|
|
|
|
PIXEL_TO_XRENDERCOLOR(r->s->c[SWM_S_COLOR_BAR_FONT].pixel, color);
|
|
|
|
if (!XftColorAllocValue(display, DefaultVisual(display, r->s->idx),
|
|
DefaultColormap(display, r->s->idx), &color, &bar_font_color))
|
|
warn("Xft error: unable to allocate color.");
|
|
|
|
bar_height = bar_font->height + 2 * bar_border_width;
|
|
|
|
if (bar_height < 1)
|
|
bar_height = 1;
|
|
}
|
|
|
|
void
|
|
bar_setup(struct swm_region *r)
|
|
{
|
|
xcb_screen_t *screen;
|
|
uint32_t wa[3];
|
|
|
|
DNPRINTF(SWM_D_BAR, "bar_setup: screen %d.\n",
|
|
r->s->idx);
|
|
|
|
if ((screen = get_screen(r->s->idx)) == NULL)
|
|
errx(1, "ERROR: can't get screen %d.", r->s->idx);
|
|
|
|
if (r->bar != NULL)
|
|
return;
|
|
|
|
if ((r->bar = calloc(1, sizeof(struct swm_bar))) == NULL)
|
|
err(1, "bar_setup: calloc: failed to allocate memory.");
|
|
|
|
if (bar_font_legacy)
|
|
fontset_init();
|
|
else
|
|
xft_init(r);
|
|
|
|
X(r->bar) = X(r);
|
|
Y(r->bar) = bar_at_bottom ? (Y(r) + HEIGHT(r) - bar_height) : Y(r);
|
|
WIDTH(r->bar) = WIDTH(r) - 2 * bar_border_width;
|
|
HEIGHT(r->bar) = bar_height - 2 * bar_border_width;
|
|
|
|
/* Assume region is unfocused when we create the bar. */
|
|
r->bar->id = xcb_generate_id(conn);
|
|
wa[0] = r->s->c[SWM_S_COLOR_BAR].pixel;
|
|
wa[1] = r->s->c[SWM_S_COLOR_BAR_BORDER_UNFOCUS].pixel;
|
|
wa[2] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_POINTER_MOTION |
|
|
XCB_EVENT_MASK_POINTER_MOTION_HINT;
|
|
|
|
xcb_create_window(conn, XCB_COPY_FROM_PARENT, r->bar->id, r->s->root,
|
|
X(r->bar), Y(r->bar), WIDTH(r->bar), HEIGHT(r->bar),
|
|
bar_border_width, XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
|
XCB_COPY_FROM_PARENT, XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL
|
|
| XCB_CW_EVENT_MASK, wa);
|
|
|
|
r->bar->buffer = xcb_generate_id(conn);
|
|
xcb_create_pixmap(conn, screen->root_depth, r->bar->buffer, r->bar->id,
|
|
WIDTH(r->bar), HEIGHT(r->bar));
|
|
|
|
if (xrandr_support)
|
|
xcb_randr_select_input(conn, r->bar->id,
|
|
XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE);
|
|
|
|
if (bar_enabled)
|
|
xcb_map_window(conn, r->bar->id);
|
|
|
|
DNPRINTF(SWM_D_BAR, "bar_setup: window: 0x%x, (x,y) w x h: (%d,%d) "
|
|
"%d x %d\n", WINID(r->bar), X(r->bar), Y(r->bar), WIDTH(r->bar),
|
|
HEIGHT(r->bar));
|
|
|
|
bar_extra_setup();
|
|
}
|
|
|
|
void
|
|
bar_cleanup(struct swm_region *r)
|
|
{
|
|
if (r->bar == NULL)
|
|
return;
|
|
xcb_destroy_window(conn, r->bar->id);
|
|
xcb_free_pixmap(conn, r->bar->buffer);
|
|
free(r->bar);
|
|
r->bar = NULL;
|
|
}
|
|
|
|
void
|
|
set_win_state(struct ws_win *win, uint16_t state)
|
|
{
|
|
uint16_t data[2] = { state, XCB_ATOM_NONE };
|
|
|
|
DNPRINTF(SWM_D_EVENT, "set_win_state: window: 0x%x, state: %u\n",
|
|
win->id, state);
|
|
|
|
if (win == NULL)
|
|
return;
|
|
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->id, a_state,
|
|
a_state, 32, 2, data);
|
|
}
|
|
|
|
uint32_t
|
|
getstate(xcb_window_t w)
|
|
{
|
|
uint32_t result = 0;
|
|
xcb_get_property_cookie_t c;
|
|
xcb_get_property_reply_t *r;
|
|
|
|
c = xcb_get_property(conn, 0, w, a_state, a_state, 0L, 2L);
|
|
r = xcb_get_property_reply(conn, c, NULL);
|
|
|
|
if (r) {
|
|
if (r->type == a_state && r->format == 32 && r->length == 2)
|
|
result = *((uint32_t *)xcb_get_property_value(r));
|
|
free(r);
|
|
}
|
|
|
|
DNPRINTF(SWM_D_MISC, "getstate property: win 0x%x state %u\n", w,
|
|
result);
|
|
return (result);
|
|
}
|
|
|
|
void
|
|
version(struct swm_region *r, union arg *args)
|
|
{
|
|
/* suppress unused warnings since vars are needed */
|
|
(void)r;
|
|
(void)args;
|
|
|
|
bar_version = !bar_version;
|
|
if (bar_version)
|
|
snprintf(bar_vertext, sizeof bar_vertext,
|
|
"Version: %s Build: %s", SPECTRWM_VERSION, buildstr);
|
|
else
|
|
strlcpy(bar_vertext, "", sizeof bar_vertext);
|
|
|
|
bar_draw();
|
|
xcb_flush(conn);
|
|
}
|
|
|
|
void
|
|
client_msg(struct ws_win *win, xcb_atom_t a, xcb_timestamp_t t)
|
|
{
|
|
xcb_client_message_event_t ev;
|
|
#ifdef SWM_DEBUG
|
|
char *name;
|
|
#endif
|
|
|
|
if (win == NULL)
|
|
return;
|
|
#ifdef SWM_DEBUG
|
|
name = get_atom_name(a);
|
|
DNPRINTF(SWM_D_EVENT, "client_msg: window: 0x%x, atom: %s(%u), "
|
|
"time: %#x\n",
|
|
win->id, name, a, t);
|
|
free(name);
|
|
#endif
|
|
|
|
bzero(&ev, sizeof ev);
|
|
ev.response_type = XCB_CLIENT_MESSAGE;
|
|
ev.window = win->id;
|
|
ev.type = a_prot;
|
|
ev.format = 32;
|
|
ev.data.data32[0] = a;
|
|
ev.data.data32[1] = t;
|
|
|
|
xcb_send_event(conn, 0, win->id,
|
|
XCB_EVENT_MASK_NO_EVENT, (const char *)&ev);
|
|
}
|
|
|
|
/* synthetic response to a ConfigureRequest when not making a change */
|
|
void
|
|
config_win(struct ws_win *win, xcb_configure_request_event_t *ev)
|
|
{
|
|
xcb_configure_notify_event_t ce;
|
|
|
|
if (win == NULL)
|
|
return;
|
|
|
|
/* send notification of unchanged state. */
|
|
bzero(&ce, sizeof(ce));
|
|
ce.response_type = XCB_CONFIGURE_NOTIFY;
|
|
ce.x = X(win);
|
|
ce.y = Y(win);
|
|
ce.width = WIDTH(win);
|
|
ce.height = HEIGHT(win);
|
|
ce.override_redirect = 0;
|
|
|
|
if (ev == NULL) {
|
|
/* EWMH */
|
|
ce.event = win->id;
|
|
ce.window = win->id;
|
|
ce.border_width = BORDER(win);
|
|
ce.above_sibling = XCB_WINDOW_NONE;
|
|
} else {
|
|
/* normal */
|
|
ce.event = ev->window;
|
|
ce.window = ev->window;
|
|
|
|
/* make response appear more WM_SIZE_HINTS-compliant */
|
|
if (win->sh.flags) {
|
|
DNPRINTF(SWM_D_MISC, "config_win: hints: window: 0x%x,"
|
|
" sh.flags: %u, min: %d x %d, max: %d x %d, inc: "
|
|
"%d x %d\n", win->id, win->sh.flags, SH_MIN_W(win),
|
|
SH_MIN_H(win), SH_MAX_W(win), SH_MAX_H(win),
|
|
SH_INC_W(win), SH_INC_H(win));
|
|
}
|
|
|
|
/* min size */
|
|
if (SH_MIN(win)) {
|
|
/* the hint may be set... to 0! */
|
|
if (SH_MIN_W(win) > 0 && ce.width < SH_MIN_W(win))
|
|
ce.width = SH_MIN_W(win);
|
|
if (SH_MIN_H(win) > 0 && ce.height < SH_MIN_H(win))
|
|
ce.height = SH_MIN_H(win);
|
|
}
|
|
|
|
/* max size */
|
|
if (SH_MAX(win)) {
|
|
/* may also be advertized as 0 */
|
|
if (SH_MAX_W(win) > 0 && ce.width > SH_MAX_W(win))
|
|
ce.width = SH_MAX_W(win);
|
|
if (SH_MAX_H(win) > 0 && ce.height > SH_MAX_H(win))
|
|
ce.height = SH_MAX_H(win);
|
|
}
|
|
|
|
/* resize increment. */
|
|
if (SH_INC(win)) {
|
|
if (SH_INC_W(win) > 1 && ce.width > SH_INC_W(win))
|
|
ce.width -= (ce.width - SH_MIN_W(win)) %
|
|
SH_INC_W(win);
|
|
if (SH_INC_H(win) > 1 && ce.height > SH_INC_H(win))
|
|
ce.height -= (ce.height - SH_MIN_H(win)) %
|
|
SH_INC_H(win);
|
|
}
|
|
|
|
/* adjust x and y for requested border_width. */
|
|
ce.x += BORDER(win) - ev->border_width;
|
|
ce.y += BORDER(win) - ev->border_width;
|
|
ce.border_width = ev->border_width;
|
|
ce.above_sibling = ev->sibling;
|
|
}
|
|
|
|
DNPRINTF(SWM_D_MISC, "config_win: ewmh: %s, window: 0x%x, (x,y) w x h: "
|
|
"(%d,%d) %d x %d, border: %d\n", YESNO(ev == NULL), win->id, ce.x,
|
|
ce.y, ce.width, ce.height, ce.border_width);
|
|
|
|
xcb_send_event(conn, 0, win->id, XCB_EVENT_MASK_STRUCTURE_NOTIFY,
|
|
(char *)&ce);
|
|
}
|
|
|
|
int
|
|
count_win(struct workspace *ws, int count_transient)
|
|
{
|
|
struct ws_win *win;
|
|
int count = 0;
|
|
|
|
TAILQ_FOREACH(win, &ws->winlist, entry) {
|
|
if (!count_transient && win->floating)
|
|
continue;
|
|
if (!count_transient && win->transient)
|
|
continue;
|
|
if (win->iconic)
|
|
continue;
|
|
count++;
|
|
}
|
|
DNPRINTF(SWM_D_MISC, "count_win: %d\n", count);
|
|
|
|
return (count);
|
|
}
|
|
|
|
void
|
|
quit(struct swm_region *r, union arg *args)
|
|
{
|
|
/* suppress unused warnings since vars are needed */
|
|
(void)r;
|
|
(void)args;
|
|
|
|
DNPRINTF(SWM_D_MISC, "quit\n");
|
|
running = 0;
|
|
}
|
|
|
|
void
|
|
map_window(struct ws_win *win)
|
|
{
|
|
uint32_t val = XCB_STACK_MODE_ABOVE;
|
|
|
|
if (win == NULL)
|
|
return;
|
|
|
|
DNPRINTF(SWM_D_EVENT, "map_window: win 0x%x, mapped: %s\n", win->id,
|
|
YESNO(win->mapped));
|
|
|
|
xcb_configure_window(conn, win->id,
|
|
XCB_CONFIG_WINDOW_STACK_MODE, &val);
|
|
|
|
if (win->mapped)
|
|
return;
|
|
|
|
xcb_map_window(conn, win->id);
|
|
set_win_state(win, XCB_ICCCM_WM_STATE_NORMAL);
|
|
win->mapped = 1;
|
|
}
|
|
|
|
void
|
|
unmap_window(struct ws_win *win)
|
|
{
|
|
if (win == NULL)
|
|
return;
|
|
|
|
DNPRINTF(SWM_D_EVENT, "unmap_window: win 0x%x, mapped: %s\n", win->id,
|
|
YESNO(win->mapped));
|
|
|
|
if (!win->mapped)
|
|
return;
|
|
|
|
xcb_unmap_window(conn, win->id);
|
|
set_win_state(win, XCB_ICCCM_WM_STATE_ICONIC);
|
|
win->mapped = 0;
|
|
}
|
|
|
|
void
|
|
unmap_all(void)
|
|
{
|
|
struct ws_win *win;
|
|
int i, j, num_screens;
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++)
|
|
for (j = 0; j < workspace_limit; j++)
|
|
TAILQ_FOREACH(win, &screens[i].ws[j].winlist, entry)
|
|
unmap_window(win);
|
|
}
|
|
|
|
void
|
|
fake_keypress(struct ws_win *win, xcb_keysym_t keysym, uint16_t modifiers)
|
|
{
|
|
xcb_key_press_event_t event;
|
|
xcb_keycode_t *keycode;
|
|
|
|
if (win == NULL)
|
|
return;
|
|
|
|
keycode = xcb_key_symbols_get_keycode(syms, keysym);
|
|
|
|
DNPRINTF(SWM_D_MISC, "fake_keypress: win 0x%x keycode %u\n",
|
|
win->id, *keycode);
|
|
|
|
bzero(&event, sizeof(event));
|
|
event.event = win->id;
|
|
event.root = win->s->root;
|
|
event.child = XCB_WINDOW_NONE;
|
|
event.time = XCB_CURRENT_TIME;
|
|
event.event_x = X(win);
|
|
event.event_y = Y(win);
|
|
event.root_x = 1;
|
|
event.root_y = 1;
|
|
event.same_screen = 1;
|
|
event.detail = *keycode;
|
|
event.state = modifiers;
|
|
|
|
event.response_type = XCB_KEY_PRESS;
|
|
xcb_send_event(conn, 1, win->id,
|
|
XCB_EVENT_MASK_KEY_PRESS, (const char *)&event);
|
|
|
|
event.response_type = XCB_KEY_RELEASE;
|
|
xcb_send_event(conn, 1, win->id,
|
|
XCB_EVENT_MASK_KEY_RELEASE, (const char *)&event);
|
|
|
|
free(keycode);
|
|
}
|
|
|
|
void
|
|
restart(struct swm_region *r, union arg *args)
|
|
{
|
|
/* suppress unused warning since var is needed */
|
|
(void)r;
|
|
(void)args;
|
|
|
|
DNPRINTF(SWM_D_MISC, "restart: %s\n", start_argv[0]);
|
|
|
|
shutdown_cleanup();
|
|
|
|
execvp(start_argv[0], start_argv);
|
|
warn("execvp failed");
|
|
quit(NULL, NULL);
|
|
}
|
|
|
|
struct ws_win *
|
|
get_pointer_win(xcb_window_t root)
|
|
{
|
|
struct ws_win *win = NULL;
|
|
xcb_query_pointer_reply_t *r;
|
|
|
|
DNPRINTF(SWM_D_EVENT, "get_pointer_win: root: 0x%x.\n", root);
|
|
|
|
r = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL);
|
|
if (r) {
|
|
win = find_window(r->child);
|
|
if (win) {
|
|
DNPRINTF(SWM_D_EVENT, "get_pointer_win: 0x%x.\n",
|
|
win->id);
|
|
} else {
|
|
DNPRINTF(SWM_D_EVENT, "get_pointer_win: none.\n");
|
|
}
|
|
free(r);
|
|
}
|
|
|
|
return win;
|
|
}
|
|
|
|
struct swm_region *
|
|
root_to_region(xcb_window_t root, int check)
|
|
{
|
|
struct ws_win *cfw;
|
|
struct swm_region *r = NULL;
|
|
int i, num_screens;
|
|
xcb_query_pointer_reply_t *qpr;
|
|
xcb_get_input_focus_reply_t *gifr;
|
|
|
|
DNPRINTF(SWM_D_MISC, "root_to_region: window: 0x%x\n", root);
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++)
|
|
if (screens[i].root == root)
|
|
break;
|
|
|
|
if (check & SWM_CK_REGION)
|
|
r = screens[i].r_focus;
|
|
|
|
if (r == NULL && check & SWM_CK_FOCUS) {
|
|
/* Try to find an actively focused window */
|
|
gifr = xcb_get_input_focus_reply(conn,
|
|
xcb_get_input_focus(conn), NULL);
|
|
if (gifr) {
|
|
cfw = find_window(gifr->focus);
|
|
if (cfw && cfw->ws->r)
|
|
r = cfw->ws->r;
|
|
|
|
free(gifr);
|
|
}
|
|
}
|
|
|
|
if (r == NULL && check & SWM_CK_POINTER) {
|
|
/* No region with an active focus; try to use pointer. */
|
|
qpr = xcb_query_pointer_reply(conn, xcb_query_pointer(conn,
|
|
screens[i].root), NULL);
|
|
|
|
if (qpr) {
|
|
DNPRINTF(SWM_D_MISC, "root_to_region: pointer: "
|
|
"(%d,%d)\n", qpr->root_x, qpr->root_y);
|
|
TAILQ_FOREACH(r, &screens[i].rl, entry)
|
|
if (X(r) <= qpr->root_x &&
|
|
qpr->root_x < MAX_X(r) &&
|
|
Y(r) <= qpr->root_y &&
|
|
qpr->root_y < MAX_Y(r))
|
|
break;
|
|
free(qpr);
|
|
}
|
|
}
|
|
|
|
/* Last resort. */
|
|
if (r == NULL && check & SWM_CK_FALLBACK)
|
|
r = TAILQ_FIRST(&screens[i].rl);
|
|
|
|
return (r);
|
|
}
|
|
|
|
struct ws_win *
|
|
find_unmanaged_window(xcb_window_t id)
|
|
{
|
|
struct ws_win *win;
|
|
int i, j, num_screens;
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++)
|
|
for (j = 0; j < workspace_limit; j++)
|
|
TAILQ_FOREACH(win, &screens[i].ws[j].unmanagedlist,
|
|
entry)
|
|
if (id == win->id)
|
|
return (win);
|
|
return (NULL);
|
|
}
|
|
|
|
struct ws_win *
|
|
find_window(xcb_window_t id)
|
|
{
|
|
struct ws_win *win;
|
|
int i, j, num_screens;
|
|
xcb_query_tree_reply_t *r;
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++)
|
|
for (j = 0; j < workspace_limit; j++)
|
|
TAILQ_FOREACH(win, &screens[i].ws[j].winlist, entry)
|
|
if (id == win->id)
|
|
return (win);
|
|
|
|
r = xcb_query_tree_reply(conn, xcb_query_tree(conn, id), NULL);
|
|
if (!r)
|
|
return (NULL);
|
|
|
|
/* if we were looking for the parent return that window instead */
|
|
if (r->parent == 0 || r->root == r->parent) {
|
|
free(r);
|
|
return (NULL);
|
|
}
|
|
|
|
/* look for parent */
|
|
for (i = 0; i < num_screens; i++)
|
|
for (j = 0; j < workspace_limit; j++)
|
|
TAILQ_FOREACH(win, &screens[i].ws[j].winlist, entry)
|
|
if (r->parent == win->id) {
|
|
free(r);
|
|
return (win);
|
|
}
|
|
|
|
free(r);
|
|
return (NULL);
|
|
}
|
|
|
|
void
|
|
spawn(int ws_idx, union arg *args, int close_fd)
|
|
{
|
|
int fd;
|
|
char *ret = NULL;
|
|
|
|
DNPRINTF(SWM_D_MISC, "spawn: %s\n", args->argv[0]);
|
|
|
|
close(xcb_get_file_descriptor(conn));
|
|
|
|
setenv("LD_PRELOAD", SWM_LIB, 1);
|
|
|
|
if (asprintf(&ret, "%d", ws_idx) == -1) {
|
|
warn("spawn: asprintf SWM_WS");
|
|
_exit(1);
|
|
}
|
|
setenv("_SWM_WS", ret, 1);
|
|
free(ret);
|
|
ret = NULL;
|
|
|
|
if (asprintf(&ret, "%d", getpid()) == -1) {
|
|
warn("spawn: asprintf _SWM_PID");
|
|
_exit(1);
|
|
}
|
|
setenv("_SWM_PID", ret, 1);
|
|
free(ret);
|
|
ret = NULL;
|
|
|
|
if (setsid() == -1) {
|
|
warn("spawn: setsid");
|
|
_exit(1);
|
|
}
|
|
|
|
if (close_fd) {
|
|
/*
|
|
* close stdin and stdout to prevent interaction between apps
|
|
* and the baraction script
|
|
* leave stderr open to record errors
|
|
*/
|
|
if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) == -1) {
|
|
warn("spawn: open");
|
|
_exit(1);
|
|
}
|
|
dup2(fd, STDIN_FILENO);
|
|
dup2(fd, STDOUT_FILENO);
|
|
if (fd > 2)
|
|
close(fd);
|
|
}
|
|
|
|
execvp(args->argv[0], args->argv);
|
|
|
|
warn("spawn: execvp");
|
|
_exit(1);
|
|
}
|
|
|
|
void
|
|
kill_refs(struct ws_win *win)
|
|
{
|
|
int i, x, num_screens;
|
|
struct swm_region *r;
|
|
struct workspace *ws;
|
|
|
|
if (win == NULL)
|
|
return;
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++)
|
|
TAILQ_FOREACH(r, &screens[i].rl, entry)
|
|
for (x = 0; x < workspace_limit; x++) {
|
|
ws = &r->s->ws[x];
|
|
if (win == ws->focus)
|
|
ws->focus = NULL;
|
|
if (win == ws->focus_prev)
|
|
ws->focus_prev = NULL;
|
|
}
|
|
}
|
|
|
|
int
|
|
validate_win(struct ws_win *testwin)
|
|
{
|
|
struct ws_win *win;
|
|
struct workspace *ws;
|
|
struct swm_region *r;
|
|
int i, x, num_screens;
|
|
|
|
if (testwin == NULL)
|
|
return (0);
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++)
|
|
TAILQ_FOREACH(r, &screens[i].rl, entry)
|
|
for (x = 0; x < workspace_limit; x++) {
|
|
ws = &r->s->ws[x];
|
|
TAILQ_FOREACH(win, &ws->winlist, entry)
|
|
if (win == testwin)
|
|
return (0);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
int
|
|
validate_ws(struct workspace *testws)
|
|
{
|
|
struct swm_region *r;
|
|
struct workspace *ws;
|
|
int i, x, num_screens;
|
|
|
|
/* validate all ws */
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++)
|
|
TAILQ_FOREACH(r, &screens[i].rl, entry)
|
|
for (x = 0; x < workspace_limit; x++) {
|
|
ws = &r->s->ws[x];
|
|
if (ws == testws)
|
|
return (0);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
void
|
|
unfocus_win(struct ws_win *win)
|
|
{
|
|
xcb_window_t none = XCB_WINDOW_NONE;
|
|
|
|
DNPRINTF(SWM_D_FOCUS, "unfocus_win: window: 0x%x\n", WINID(win));
|
|
|
|
if (win == NULL)
|
|
return;
|
|
if (win->ws == NULL) {
|
|
DNPRINTF(SWM_D_FOCUS, "unfocus_win: NULL ws.\n");
|
|
return;
|
|
}
|
|
|
|
if (validate_ws(win->ws)) {
|
|
DNPRINTF(SWM_D_FOCUS, "unfocus_win: invalid ws.\n");
|
|
return;
|
|
}
|
|
|
|
if (win->ws->r == NULL) {
|
|
DNPRINTF(SWM_D_FOCUS, "unfocus_win: NULL region.\n");
|
|
return;
|
|
}
|
|
|
|
if (validate_win(win)) {
|
|
DNPRINTF(SWM_D_FOCUS, "unfocus_win: invalid win.\n");
|
|
kill_refs(win);
|
|
return;
|
|
}
|
|
|
|
if (win->ws->focus == win) {
|
|
win->ws->focus = NULL;
|
|
win->ws->focus_prev = win;
|
|
}
|
|
|
|
if (validate_win(win->ws->focus)) {
|
|
kill_refs(win->ws->focus);
|
|
win->ws->focus = NULL;
|
|
}
|
|
|
|
if (validate_win(win->ws->focus_prev)) {
|
|
kill_refs(win->ws->focus_prev);
|
|
win->ws->focus_prev = NULL;
|
|
}
|
|
|
|
xcb_change_window_attributes(conn, win->id, XCB_CW_BORDER_PIXEL,
|
|
&win->ws->r->s->c[SWM_S_COLOR_UNFOCUS].pixel);
|
|
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->s->root,
|
|
ewmh[_NET_ACTIVE_WINDOW].atom, XCB_ATOM_WINDOW, 32, 1, &none);
|
|
|
|
DNPRINTF(SWM_D_FOCUS, "unfocus_win: done.\n");
|
|
}
|
|
|
|
void
|
|
focus_win(struct ws_win *win)
|
|
{
|
|
struct ws_win *cfw = NULL, *parent = NULL, *w;
|
|
struct workspace *ws;
|
|
xcb_get_input_focus_reply_t *r;
|
|
|
|
DNPRINTF(SWM_D_FOCUS, "focus_win: window: 0x%x\n", WINID(win));
|
|
|
|
if (win == NULL)
|
|
goto out;
|
|
|
|
if (win->ws == NULL)
|
|
goto out;
|
|
|
|
if (!win->mapped)
|
|
goto out;
|
|
|
|
ws = win->ws;
|
|
|
|
if (validate_ws(ws))
|
|
goto out;
|
|
|
|
if (validate_win(win)) {
|
|
kill_refs(win);
|
|
goto out;
|
|
}
|
|
|
|
r = xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL);
|
|
if (r) {
|
|
cfw = find_window(r->focus);
|
|
if (cfw != win)
|
|
unfocus_win(cfw);
|
|
free(r);
|
|
}
|
|
|
|
if (ws->focus != win) {
|
|
if (ws->focus && ws->focus != cfw)
|
|
unfocus_win(ws->focus);
|
|
ws->focus = win;
|
|
}
|
|
|
|
/* If this window directs focus to a child window, then clear. */
|
|
if (win->focus_child)
|
|
win->focus_child = NULL;
|
|
|
|
/* If transient, adjust parent's focus child for focus_magic. */
|
|
if (win->transient) {
|
|
parent = find_window(win->transient);
|
|
if (parent && parent->focus_child != win)
|
|
parent->focus_child = win;
|
|
}
|
|
|
|
if (cfw != win && ws->r != NULL) {
|
|
/* Set input focus if no input hint, or indicated by hint. */
|
|
if (!(win->hints.flags & XCB_ICCCM_WM_HINT_INPUT) ||
|
|
(win->hints.flags & XCB_ICCCM_WM_HINT_INPUT &&
|
|
win->hints.input))
|
|
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT,
|
|
win->id, last_event_time);
|
|
|
|
/* Tell app it can adjust focus to a specific window. */
|
|
if (win->take_focus) {
|
|
/* java is special; always tell parent */
|
|
if (win->transient && win->java)
|
|
client_msg(parent, a_takefocus,
|
|
last_event_time);
|
|
else
|
|
client_msg(win, a_takefocus, last_event_time);
|
|
}
|
|
|
|
xcb_change_window_attributes(conn, win->id, XCB_CW_BORDER_PIXEL,
|
|
&ws->r->s->c[SWM_S_COLOR_FOCUS].pixel);
|
|
|
|
if (ws->cur_layout->flags & SWM_L_MAPONFOCUS ||
|
|
ws->always_raise) {
|
|
/* If a parent exists, map it first. */
|
|
if (parent) {
|
|
map_window(parent);
|
|
|
|
/* Map siblings next. */
|
|
TAILQ_FOREACH(w, &ws->winlist, entry)
|
|
if (w != win && !w->iconic &&
|
|
w->transient == parent->id)
|
|
map_window(w);
|
|
}
|
|
|
|
/* Map focused window. */
|
|
map_window(win);
|
|
|
|
/* Finally, map children of focus window. */
|
|
TAILQ_FOREACH(w, &ws->winlist, entry)
|
|
if (w->transient == win->id && !w->iconic)
|
|
map_window(w);
|
|
}
|
|
|
|
set_region(ws->r);
|
|
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->s->root,
|
|
ewmh[_NET_ACTIVE_WINDOW].atom, XCB_ATOM_WINDOW, 32, 1,
|
|
&win->id);
|
|
}
|
|
|
|
out:
|
|
bar_draw();
|
|
|
|
DNPRINTF(SWM_D_FOCUS, "focus_win: done.\n");
|
|
}
|
|
|
|
/* If a child window should have focus instead, return it. */
|
|
struct ws_win *
|
|
get_focus_magic(struct ws_win *win)
|
|
{
|
|
struct ws_win *parent = NULL;
|
|
struct ws_win *child = NULL;
|
|
|
|
DNPRINTF(SWM_D_FOCUS, "get_focus_magic: window: 0x%x\n", WINID(win));
|
|
if (win == NULL)
|
|
return win;
|
|
|
|
if (win->transient) {
|
|
parent = find_window(win->transient);
|
|
|
|
/* If parent prefers focus elsewhere, then try to do so. */
|
|
if (parent && (child = parent->focus_child)) {
|
|
if (validate_win(child) == 0 && child->mapped)
|
|
win = child;
|
|
else
|
|
parent->focus_child = NULL;
|
|
}
|
|
}
|
|
|
|
/* If this window prefers focus elsewhere, then try to do so. */
|
|
if ((child = win->focus_child)) {
|
|
if (validate_win(child) == 0 && child->mapped)
|
|
win = child;
|
|
else
|
|
win->focus_child = NULL;
|
|
}
|
|
|
|
return win;
|
|
}
|
|
|
|
void
|
|
event_drain(uint8_t rt)
|
|
{
|
|
xcb_generic_event_t *evt;
|
|
|
|
/* ensure all pending requests have been processed before filtering. */
|
|
xcb_aux_sync(conn);
|
|
while ((evt = xcb_poll_for_event(conn))) {
|
|
if (XCB_EVENT_RESPONSE_TYPE(evt) != rt)
|
|
event_handle(evt);
|
|
|
|
free(evt);
|
|
}
|
|
}
|
|
|
|
void
|
|
set_region(struct swm_region *r)
|
|
{
|
|
struct swm_region *rf;
|
|
|
|
if (r == NULL)
|
|
return;
|
|
|
|
rf = r->s->r_focus;
|
|
/* Unfocus old region bar. */
|
|
if (rf) {
|
|
if (rf == r)
|
|
return;
|
|
|
|
xcb_change_window_attributes(conn, rf->bar->id,
|
|
XCB_CW_BORDER_PIXEL,
|
|
&r->s->c[SWM_S_COLOR_BAR_BORDER_UNFOCUS].pixel);
|
|
}
|
|
|
|
/* Set region bar border to focus_color. */
|
|
xcb_change_window_attributes(conn, r->bar->id,
|
|
XCB_CW_BORDER_PIXEL, &r->s->c[SWM_S_COLOR_BAR_BORDER].pixel);
|
|
|
|
r->s->r_focus = r;
|
|
}
|
|
|
|
void
|
|
focus_region(struct swm_region *r)
|
|
{
|
|
struct ws_win *nfw;
|
|
struct swm_region *old_r;
|
|
|
|
if (r == NULL)
|
|
return;
|
|
|
|
old_r = r->s->r_focus;
|
|
set_region(r);
|
|
|
|
nfw = get_region_focus(r);
|
|
if (nfw) {
|
|
focus_win(nfw);
|
|
} else {
|
|
/* New region is empty; need to manually unfocus win. */
|
|
if (old_r)
|
|
unfocus_win(old_r->ws->focus);
|
|
|
|
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT, r->s->root,
|
|
XCB_CURRENT_TIME);
|
|
|
|
/* Clear bar since empty. */
|
|
bar_draw();
|
|
}
|
|
}
|
|
|
|
void
|
|
switchws(struct swm_region *r, union arg *args)
|
|
{
|
|
int wsid = args->id, unmap_old = 0;
|
|
struct swm_region *this_r, *other_r;
|
|
struct ws_win *win;
|
|
struct workspace *new_ws, *old_ws;
|
|
|
|
if (!(r && r->s))
|
|
return;
|
|
|
|
if (wsid >= workspace_limit)
|
|
return;
|
|
|
|
this_r = r;
|
|
old_ws = this_r->ws;
|
|
new_ws = &this_r->s->ws[wsid];
|
|
|
|
DNPRINTF(SWM_D_WS, "switchws: screen[%d]:%dx%d+%d+%d: %d -> %d\n",
|
|
r->s->idx, WIDTH(r), HEIGHT(r), X(r), Y(r), old_ws->idx, wsid);
|
|
|
|
if (new_ws == NULL || old_ws == NULL)
|
|
return;
|
|
if (new_ws == old_ws)
|
|
return;
|
|
|
|
unfocus_win(old_ws->focus);
|
|
|
|
other_r = new_ws->r;
|
|
if (other_r == NULL) {
|
|
/* the other workspace is hidden, hide this one */
|
|
old_ws->r = NULL;
|
|
unmap_old = 1;
|
|
} else {
|
|
/* the other ws is visible in another region, exchange them */
|
|
other_r->ws_prior = new_ws;
|
|
other_r->ws = old_ws;
|
|
old_ws->r = other_r;
|
|
}
|
|
this_r->ws_prior = old_ws;
|
|
this_r->ws = new_ws;
|
|
new_ws->r = this_r;
|
|
|
|
/* Set focus_pending before stacking. */
|
|
if (focus_mode != SWM_FOCUS_FOLLOW)
|
|
new_ws->focus_pending = get_region_focus(new_ws->r);
|
|
|
|
stack();
|
|
|
|
/* unmap old windows */
|
|
if (unmap_old)
|
|
TAILQ_FOREACH(win, &old_ws->winlist, entry)
|
|
unmap_window(win);
|
|
|
|
/* if workspaces were swapped, then don't wait to set focus */
|
|
if (old_ws->r && focus_mode != SWM_FOCUS_FOLLOW) {
|
|
if (new_ws->focus_pending) {
|
|
focus_win(new_ws->focus_pending);
|
|
} else {
|
|
/* Empty region, focus on root. */
|
|
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT,
|
|
new_ws->r->s[new_ws->r->s->idx].root,
|
|
XCB_CURRENT_TIME);
|
|
}
|
|
}
|
|
|
|
/* Clear bar if new ws is empty. */
|
|
if (new_ws->focus_pending == NULL)
|
|
bar_draw();
|
|
|
|
focus_flush();
|
|
|
|
DNPRINTF(SWM_D_WS, "switchws: done.\n");
|
|
}
|
|
|
|
void
|
|
cyclews(struct swm_region *r, union arg *args)
|
|
{
|
|
union arg a;
|
|
struct swm_screen *s = r->s;
|
|
int cycle_all = 0;
|
|
|
|
DNPRINTF(SWM_D_WS, "cyclews: id: %d, screen[%d]:%dx%d+%d+%d, ws: %d\n",
|
|
args->id, r->s->idx, WIDTH(r), HEIGHT(r), X(r), Y(r), r->ws->idx);
|
|
|
|
a.id = r->ws->idx;
|
|
do {
|
|
switch (args->id) {
|
|
case SWM_ARG_ID_CYCLEWS_UP_ALL:
|
|
cycle_all = 1;
|
|
/* FALLTHROUGH */
|
|
case SWM_ARG_ID_CYCLEWS_UP:
|
|
if (a.id < workspace_limit - 1)
|
|
a.id++;
|
|
else
|
|
a.id = 0;
|
|
break;
|
|
case SWM_ARG_ID_CYCLEWS_DOWN_ALL:
|
|
cycle_all = 1;
|
|
/* FALLTHROUGH */
|
|
case SWM_ARG_ID_CYCLEWS_DOWN:
|
|
if (a.id > 0)
|
|
a.id--;
|
|
else
|
|
a.id = workspace_limit - 1;
|
|
break;
|
|
default:
|
|
return;
|
|
};
|
|
|
|
if (!cycle_all &&
|
|
(!cycle_empty && TAILQ_EMPTY(&s->ws[a.id].winlist)))
|
|
continue;
|
|
if (!cycle_visible && s->ws[a.id].r != NULL)
|
|
continue;
|
|
|
|
switchws(r, &a);
|
|
} while (a.id != r->ws->idx);
|
|
}
|
|
|
|
void
|
|
priorws(struct swm_region *r, union arg *args)
|
|
{
|
|
union arg a;
|
|
|
|
(void)args;
|
|
|
|
DNPRINTF(SWM_D_WS, "priorws: id: %d, screen[%d]:%dx%d+%d+%d, ws: %d\n",
|
|
args->id, r->s->idx, WIDTH(r), HEIGHT(r), X(r), Y(r), r->ws->idx);
|
|
|
|
if (r->ws_prior == NULL)
|
|
return;
|
|
|
|
a.id = r->ws_prior->idx;
|
|
switchws(r, &a);
|
|
}
|
|
|
|
void
|
|
focusrg(struct swm_region *r, union arg *args)
|
|
{
|
|
int ridx = args->id, i, num_screens;
|
|
struct swm_region *rr = NULL;
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
/* do nothing if we don't have more than one screen */
|
|
if (!(num_screens > 1 || outputs > 1))
|
|
return;
|
|
|
|
DNPRINTF(SWM_D_FOCUS, "focusrg: id: %d\n", ridx);
|
|
|
|
rr = TAILQ_FIRST(&r->s->rl);
|
|
for (i = 0; i < ridx; ++i)
|
|
rr = TAILQ_NEXT(rr, entry);
|
|
|
|
if (rr == NULL)
|
|
return;
|
|
|
|
focus_region(rr);
|
|
focus_flush();
|
|
}
|
|
|
|
void
|
|
cyclerg(struct swm_region *r, union arg *args)
|
|
{
|
|
struct swm_region *rr = NULL;
|
|
int i, num_screens;
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
/* do nothing if we don't have more than one screen */
|
|
if (!(num_screens > 1 || outputs > 1))
|
|
return;
|
|
|
|
i = r->s->idx;
|
|
switch (args->id) {
|
|
case SWM_ARG_ID_CYCLERG_UP:
|
|
rr = TAILQ_NEXT(r, entry);
|
|
if (rr == NULL)
|
|
rr = TAILQ_FIRST(&screens[i].rl);
|
|
break;
|
|
case SWM_ARG_ID_CYCLERG_DOWN:
|
|
rr = TAILQ_PREV(r, swm_region_list, entry);
|
|
if (rr == NULL)
|
|
rr = TAILQ_LAST(&screens[i].rl, swm_region_list);
|
|
break;
|
|
default:
|
|
return;
|
|
};
|
|
if (rr == NULL)
|
|
return;
|
|
|
|
focus_region(rr);
|
|
focus_flush();
|
|
}
|
|
|
|
void
|
|
sort_windows(struct ws_win_list *wl)
|
|
{
|
|
struct ws_win *win, *parent, *nxt;
|
|
|
|
if (wl == NULL)
|
|
return;
|
|
|
|
for (win = TAILQ_FIRST(wl); win != TAILQ_END(wl); win = nxt) {
|
|
nxt = TAILQ_NEXT(win, entry);
|
|
if (win->transient) {
|
|
parent = find_window(win->transient);
|
|
if (parent == NULL) {
|
|
warnx("not possible bug");
|
|
continue;
|
|
}
|
|
TAILQ_REMOVE(wl, win, entry);
|
|
TAILQ_INSERT_AFTER(wl, parent, win, entry);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
swapwin(struct swm_region *r, union arg *args)
|
|
{
|
|
struct ws_win *target, *source;
|
|
struct ws_win *cur_focus;
|
|
struct ws_win_list *wl;
|
|
|
|
DNPRINTF(SWM_D_WS, "swapwin: id: %d, screen[%d]:%dx%d+%d+%d, ws: %d\n",
|
|
args->id, r->s->idx, WIDTH(r), HEIGHT(r), X(r), Y(r), r->ws->idx);
|
|
|
|
cur_focus = r->ws->focus;
|
|
if (cur_focus == NULL)
|
|
return;
|
|
|
|
source = cur_focus;
|
|
wl = &source->ws->winlist;
|
|
|
|
switch (args->id) {
|
|
case SWM_ARG_ID_SWAPPREV:
|
|
if (source->transient)
|
|
source = find_window(source->transient);
|
|
target = TAILQ_PREV(source, ws_win_list, entry);
|
|
if (target && target->transient)
|
|
target = find_window(target->transient);
|
|
TAILQ_REMOVE(wl, source, entry);
|
|
if (target == NULL)
|
|
TAILQ_INSERT_TAIL(wl, source, entry);
|
|
else
|
|
TAILQ_INSERT_BEFORE(target, source, entry);
|
|
break;
|
|
case SWM_ARG_ID_SWAPNEXT:
|
|
target = TAILQ_NEXT(source, entry);
|
|
/* move the parent and let the sort handle the move */
|
|
if (source->transient)
|
|
source = find_window(source->transient);
|
|
TAILQ_REMOVE(wl, source, entry);
|
|
if (target == NULL)
|
|
TAILQ_INSERT_HEAD(wl, source, entry);
|
|
else
|
|
TAILQ_INSERT_AFTER(wl, target, source, entry);
|
|
break;
|
|
case SWM_ARG_ID_SWAPMAIN:
|
|
target = TAILQ_FIRST(wl);
|
|
if (target == source) {
|
|
if (source->ws->focus_prev != NULL &&
|
|
source->ws->focus_prev != target)
|
|
source = source->ws->focus_prev;
|
|
else
|
|
return;
|
|
}
|
|
if (target == NULL || source == NULL)
|
|
return;
|
|
source->ws->focus_prev = target;
|
|
TAILQ_REMOVE(wl, target, entry);
|
|
TAILQ_INSERT_BEFORE(source, target, entry);
|
|
TAILQ_REMOVE(wl, source, entry);
|
|
TAILQ_INSERT_HEAD(wl, source, entry);
|
|
break;
|
|
case SWM_ARG_ID_MOVELAST:
|
|
TAILQ_REMOVE(wl, source, entry);
|
|
TAILQ_INSERT_TAIL(wl, source, entry);
|
|
break;
|
|
default:
|
|
DNPRINTF(SWM_D_MOVE, "swapwin: invalid id: %d\n", args->id);
|
|
return;
|
|
}
|
|
|
|
sort_windows(wl);
|
|
|
|
stack();
|
|
|
|
focus_flush();
|
|
}
|
|
|
|
struct ws_win *
|
|
get_focus_prev(struct ws_win *win)
|
|
{
|
|
struct ws_win *winfocus = NULL;
|
|
struct ws_win *cur_focus = NULL;
|
|
struct ws_win_list *wl = NULL;
|
|
struct workspace *ws = NULL;
|
|
|
|
if (!(win && win->ws))
|
|
return NULL;
|
|
|
|
ws = win->ws;
|
|
wl = &ws->winlist;
|
|
cur_focus = ws->focus;
|
|
|
|
DNPRINTF(SWM_D_FOCUS, "get_focus_prev: window: 0x%x, cur_focus: 0x%x\n",
|
|
WINID(win), WINID(cur_focus));
|
|
|
|
/* pickle, just focus on whatever */
|
|
if (cur_focus == NULL) {
|
|
/* use prev_focus if valid */
|
|
if (ws->focus_prev && ws->focus_prev != cur_focus &&
|
|
find_window(WINID(ws->focus_prev)))
|
|
winfocus = ws->focus_prev;
|
|
goto done;
|
|
}
|
|
|
|
/* if transient focus on parent */
|
|
if (cur_focus->transient) {
|
|
winfocus = find_window(cur_focus->transient);
|
|
goto done;
|
|
}
|
|
|
|
/* if in max_stack try harder */
|
|
if ((win->quirks & SWM_Q_FOCUSPREV) ||
|
|
(ws->cur_layout->flags & SWM_L_FOCUSPREV)) {
|
|
if (cur_focus != ws->focus_prev)
|
|
winfocus = ws->focus_prev;
|
|
else
|
|
winfocus = TAILQ_PREV(win, ws_win_list, entry);
|
|
if (winfocus)
|
|
goto done;
|
|
}
|
|
|
|
DNPRINTF(SWM_D_FOCUS, "get_focus_prev: focus_close: %d\n", focus_close);
|
|
|
|
if (winfocus == NULL || winfocus == win) {
|
|
switch (focus_close) {
|
|
case SWM_STACK_BOTTOM:
|
|
TAILQ_FOREACH(winfocus, wl, entry)
|
|
if (!winfocus->iconic && winfocus != cur_focus)
|
|
break;
|
|
break;
|
|
case SWM_STACK_TOP:
|
|
TAILQ_FOREACH_REVERSE(winfocus, wl, ws_win_list, entry)
|
|
if (!winfocus->iconic && winfocus != cur_focus)
|
|
break;
|
|
break;
|
|
case SWM_STACK_ABOVE:
|
|
winfocus = TAILQ_NEXT(cur_focus, entry);
|
|
while (winfocus && winfocus->iconic)
|
|
winfocus = TAILQ_NEXT(winfocus, entry);
|
|
|
|
if (winfocus == NULL) {
|
|
if (focus_close_wrap) {
|
|
TAILQ_FOREACH(winfocus, wl, entry)
|
|
if (!winfocus->iconic &&
|
|
winfocus != cur_focus)
|
|
break;
|
|
} else {
|
|
TAILQ_FOREACH_REVERSE(winfocus, wl,
|
|
ws_win_list, entry)
|
|
if (!winfocus->iconic &&
|
|
winfocus != cur_focus)
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case SWM_STACK_BELOW:
|
|
winfocus = TAILQ_PREV(cur_focus, ws_win_list, entry);
|
|
while (winfocus && winfocus->iconic)
|
|
winfocus = TAILQ_PREV(winfocus, ws_win_list,
|
|
entry);
|
|
|
|
if (winfocus == NULL) {
|
|
if (focus_close_wrap) {
|
|
TAILQ_FOREACH_REVERSE(winfocus, wl,
|
|
ws_win_list, entry)
|
|
if (!winfocus->iconic &&
|
|
winfocus != cur_focus)
|
|
break;
|
|
} else {
|
|
TAILQ_FOREACH(winfocus, wl, entry)
|
|
if (!winfocus->iconic &&
|
|
winfocus != cur_focus)
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
done:
|
|
if (winfocus == NULL ||
|
|
(winfocus && (winfocus->iconic || winfocus == cur_focus))) {
|
|
if (focus_default == SWM_STACK_TOP) {
|
|
TAILQ_FOREACH_REVERSE(winfocus, wl, ws_win_list, entry)
|
|
if (!winfocus->iconic && winfocus != cur_focus)
|
|
break;
|
|
} else {
|
|
TAILQ_FOREACH(winfocus, wl, entry)
|
|
if (!winfocus->iconic && winfocus != cur_focus)
|
|
break;
|
|
}
|
|
}
|
|
|
|
kill_refs(win);
|
|
|
|
return get_focus_magic(winfocus);
|
|
}
|
|
|
|
struct ws_win *
|
|
get_region_focus(struct swm_region *r)
|
|
{
|
|
struct ws_win *winfocus = NULL;
|
|
|
|
if (!(r && r->ws))
|
|
return NULL;
|
|
|
|
if (r->ws->focus && !r->ws->focus->iconic)
|
|
winfocus = r->ws->focus;
|
|
else if (r->ws->focus_prev && !r->ws->focus_prev->iconic)
|
|
winfocus = r->ws->focus_prev;
|
|
else
|
|
TAILQ_FOREACH(winfocus, &r->ws->winlist, entry)
|
|
if (!winfocus->iconic)
|
|
break;
|
|
|
|
return get_focus_magic(winfocus);
|
|
}
|
|
|
|
void
|
|
focus(struct swm_region *r, union arg *args)
|
|
{
|
|
struct ws_win *head, *cur_focus = NULL, *winfocus = NULL;
|
|
struct ws_win_list *wl = NULL;
|
|
struct workspace *ws = NULL;
|
|
int all_iconics;
|
|
|
|
if (!(r && r->ws))
|
|
return;
|
|
|
|
DNPRINTF(SWM_D_FOCUS, "focus: id: %d\n", args->id);
|
|
|
|
if ((cur_focus = r->ws->focus) == NULL)
|
|
return;
|
|
ws = r->ws;
|
|
wl = &ws->winlist;
|
|
if (TAILQ_EMPTY(wl))
|
|
return;
|
|
/* make sure there is at least one uniconified window */
|
|
all_iconics = 1;
|
|
TAILQ_FOREACH(winfocus, wl, entry)
|
|
if (!winfocus->iconic) {
|
|
all_iconics = 0;
|
|
break;
|
|
}
|
|
if (all_iconics)
|
|
return;
|
|
|
|
switch (args->id) {
|
|
case SWM_ARG_ID_FOCUSPREV:
|
|
head = TAILQ_PREV(cur_focus, ws_win_list, entry);
|
|
if (head == NULL)
|
|
head = TAILQ_LAST(wl, ws_win_list);
|
|
winfocus = head;
|
|
if (WINID(winfocus) == cur_focus->transient) {
|
|
head = TAILQ_PREV(winfocus, ws_win_list, entry);
|
|
if (head == NULL)
|
|
head = TAILQ_LAST(wl, ws_win_list);
|
|
winfocus = head;
|
|
}
|
|
|
|
/* skip iconics */
|
|
if (winfocus && winfocus->iconic) {
|
|
while (winfocus != cur_focus) {
|
|
if (winfocus == NULL)
|
|
winfocus = TAILQ_LAST(wl, ws_win_list);
|
|
if (!winfocus->iconic)
|
|
break;
|
|
winfocus = TAILQ_PREV(winfocus, ws_win_list,
|
|
entry);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SWM_ARG_ID_FOCUSNEXT:
|
|
head = TAILQ_NEXT(cur_focus, entry);
|
|
if (head == NULL)
|
|
head = TAILQ_FIRST(wl);
|
|
winfocus = head;
|
|
|
|
/* skip iconics */
|
|
if (winfocus && winfocus->iconic) {
|
|
while (winfocus != cur_focus) {
|
|
if (winfocus == NULL)
|
|
winfocus = TAILQ_FIRST(wl);
|
|
if (!winfocus->iconic)
|
|
break;
|
|
winfocus = TAILQ_NEXT(winfocus, entry);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SWM_ARG_ID_FOCUSMAIN:
|
|
winfocus = TAILQ_FIRST(wl);
|
|
if (winfocus == cur_focus)
|
|
winfocus = cur_focus->ws->focus_prev;
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
focus_win(get_focus_magic(winfocus));
|
|
|
|
focus_flush();
|
|
}
|
|
|
|
void
|
|
cycle_layout(struct swm_region *r, union arg *args)
|
|
{
|
|
struct workspace *ws = r->ws;
|
|
|
|
/* suppress unused warning since var is needed */
|
|
(void)args;
|
|
|
|
DNPRINTF(SWM_D_EVENT, "cycle_layout: workspace: %d\n", ws->idx);
|
|
|
|
ws->cur_layout++;
|
|
if (ws->cur_layout->l_stack == NULL)
|
|
ws->cur_layout = &layouts[0];
|
|
|
|
stack();
|
|
bar_draw();
|
|
|
|
focus_win(get_region_focus(r));
|
|
|
|
focus_flush();
|
|
}
|
|
|
|
void
|
|
stack_config(struct swm_region *r, union arg *args)
|
|
{
|
|
struct workspace *ws = r->ws;
|
|
|
|
DNPRINTF(SWM_D_STACK, "stack_config: id: %d workspace: %d\n",
|
|
args->id, ws->idx);
|
|
|
|
if (ws->cur_layout->l_config != NULL)
|
|
ws->cur_layout->l_config(ws, args->id);
|
|
|
|
if (args->id != SWM_ARG_ID_STACKINIT)
|
|
stack();
|
|
bar_draw();
|
|
|
|
focus_flush();
|
|
}
|
|
|
|
void
|
|
stack(void) {
|
|
struct swm_geometry g;
|
|
struct swm_region *r;
|
|
int i, num_screens;
|
|
#ifdef SWM_DEBUG
|
|
int j;
|
|
#endif
|
|
|
|
DNPRINTF(SWM_D_STACK, "stack: begin\n");
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++) {
|
|
#ifdef SWM_DEBUG
|
|
j = 0;
|
|
#endif
|
|
TAILQ_FOREACH(r, &screens[i].rl, entry) {
|
|
DNPRINTF(SWM_D_STACK, "stack: workspace: %d "
|
|
"(screen: %d, region: %d)\n", r->ws->idx, i, j++);
|
|
|
|
/* Adjust stack area for region bar and padding. */
|
|
g = r->g;
|
|
g.x += region_padding;
|
|
g.y += region_padding;
|
|
g.w -= 2 * border_width + 2 * region_padding;
|
|
g.h -= 2 * border_width + 2 * region_padding;
|
|
if (bar_enabled && r->ws->bar_enabled) {
|
|
if (!bar_at_bottom)
|
|
g.y += bar_height;
|
|
g.h -= bar_height;
|
|
}
|
|
r->ws->cur_layout->l_stack(r->ws, &g);
|
|
r->ws->cur_layout->l_string(r->ws);
|
|
/* save r so we can track region changes */
|
|
r->ws->old_r = r;
|
|
}
|
|
}
|
|
if (font_adjusted)
|
|
font_adjusted--;
|
|
|
|
DNPRINTF(SWM_D_STACK, "stack: end\n");
|
|
}
|
|
|
|
void
|
|
store_float_geom(struct ws_win *win, struct swm_region *r)
|
|
{
|
|
if (win == NULL || r == NULL)
|
|
return;
|
|
|
|
/* retain window geom and region geom */
|
|
win->g_float = win->g;
|
|
win->g_float.x -= X(r);
|
|
win->g_float.y -= Y(r);
|
|
win->g_floatvalid = 1;
|
|
DNPRINTF(SWM_D_MISC, "store_float_geom: window: 0x%x, g: (%d,%d)"
|
|
" %d x %d, g_float: (%d,%d) %d x %d\n", win->id, X(win), Y(win),
|
|
WIDTH(win), HEIGHT(win), win->g_float.x, win->g_float.y,
|
|
win->g_float.w, win->g_float.h);
|
|
}
|
|
|
|
void
|
|
load_float_geom(struct ws_win *win, struct swm_region *r)
|
|
{
|
|
if (win == NULL || r == NULL)
|
|
return;
|
|
|
|
if (win->g_floatvalid) {
|
|
win->g = win->g_float;
|
|
X(win) += X(r);
|
|
Y(win) += Y(r);
|
|
DNPRINTF(SWM_D_MISC, "load_float_geom: window: 0x%x, g: (%d,%d)"
|
|
"%d x %d\n", win->id, X(win), Y(win), WIDTH(win),
|
|
HEIGHT(win));
|
|
} else {
|
|
DNPRINTF(SWM_D_MISC, "load_float_geom: window: 0x%x, g_float "
|
|
"is not set.\n", win->id);
|
|
}
|
|
}
|
|
|
|
void
|
|
stack_floater(struct ws_win *win, struct swm_region *r)
|
|
{
|
|
if (win == NULL)
|
|
return;
|
|
|
|
DNPRINTF(SWM_D_MISC, "stack_floater: window: 0x%x\n", win->id);
|
|
|
|
/*
|
|
* to allow windows to change their size (e.g. mplayer fs) only retrieve
|
|
* geom on ws switches or return from max mode
|
|
*/
|
|
if (win->floatmaxed || (r != r->ws->old_r &&
|
|
!(win->ewmh_flags & EWMH_F_FULLSCREEN))) {
|
|
/* update geometry for the new region */
|
|
load_float_geom(win, r);
|
|
}
|
|
|
|
win->floatmaxed = 0;
|
|
|
|
/*
|
|
* if set to fullscreen mode, configure window to maximum size.
|
|
*/
|
|
if (win->ewmh_flags & EWMH_F_FULLSCREEN) {
|
|
if (!win->g_floatvalid)
|
|
store_float_geom(win, win->ws->r);
|
|
|
|
win->g = r->g;
|
|
}
|
|
|
|
/*
|
|
* remove border on fullscreen floater when in fullscreen mode or when
|
|
* the quirk is present.
|
|
*/
|
|
if ((win->ewmh_flags & EWMH_F_FULLSCREEN) ||
|
|
((win->quirks & SWM_Q_FULLSCREEN) &&
|
|
(WIDTH(win) >= WIDTH(r)) && (HEIGHT(win) >= HEIGHT(r)))) {
|
|
if (win->bordered) {
|
|
win->bordered = 0;
|
|
X(win) += border_width;
|
|
Y(win) += border_width;
|
|
}
|
|
} else if (!win->bordered) {
|
|
win->bordered = 1;
|
|
X(win) -= border_width;
|
|
Y(win) -= border_width;
|
|
}
|
|
|
|
if (win->transient && (win->quirks & SWM_Q_TRANSSZ)) {
|
|
WIDTH(win) = (double)WIDTH(r) * dialog_ratio;
|
|
HEIGHT(win) = (double)HEIGHT(r) * dialog_ratio;
|
|
}
|
|
|
|
if (!win->manual && !(win->ewmh_flags & EWMH_F_FULLSCREEN) &&
|
|
!(win->quirks & SWM_Q_ANYWHERE)) {
|
|
/*
|
|
* floaters and transients are auto-centred unless moved,
|
|
* resized or ANYWHERE quirk is set.
|
|
*/
|
|
X(win) = X(r) + (WIDTH(r) - WIDTH(win)) / 2 - BORDER(win);
|
|
Y(win) = Y(r) + (HEIGHT(r) - HEIGHT(win)) / 2 - BORDER(win);
|
|
|
|
store_float_geom(win, r);
|
|
}
|
|
|
|
/* keep window within region bounds */
|
|
constrain_window(win, r, 0);
|
|
|
|
update_window(win);
|
|
}
|
|
|
|
/*
|
|
* Send keystrokes to terminal to decrease/increase the font size as the
|
|
* window size changes.
|
|
*/
|
|
void
|
|
adjust_font(struct ws_win *win)
|
|
{
|
|
if (!(win->quirks & SWM_Q_XTERM_FONTADJ) ||
|
|
win->floating || win->transient)
|
|
return;
|
|
|
|
if (win->sh.width_inc && win->last_inc != win->sh.width_inc &&
|
|
WIDTH(win) / win->sh.width_inc < term_width &&
|
|
win->font_steps < SWM_MAX_FONT_STEPS) {
|
|
win->font_size_boundary[win->font_steps] =
|
|
(win->sh.width_inc * term_width) + win->sh.base_width;
|
|
win->font_steps++;
|
|
font_adjusted++;
|
|
win->last_inc = win->sh.width_inc;
|
|
fake_keypress(win, XK_KP_Subtract, XCB_MOD_MASK_SHIFT);
|
|
} else if (win->font_steps && win->last_inc != win->sh.width_inc &&
|
|
WIDTH(win) > win->font_size_boundary[win->font_steps - 1]) {
|
|
win->font_steps--;
|
|
font_adjusted++;
|
|
win->last_inc = win->sh.width_inc;
|
|
fake_keypress(win, XK_KP_Add, XCB_MOD_MASK_SHIFT);
|
|
}
|
|
}
|
|
|
|
#define SWAPXY(g) do { \
|
|
int tmp; \
|
|
tmp = (g)->y; (g)->y = (g)->x; (g)->x = tmp; \
|
|
tmp = (g)->h; (g)->h = (g)->w; (g)->w = tmp; \
|
|
} while (0)
|
|
void
|
|
stack_master(struct workspace *ws, struct swm_geometry *g, int rot, int flip)
|
|
{
|
|
struct swm_geometry win_g, r_g = *g;
|
|
struct ws_win *win, *fs_win = NULL;
|
|
int i, j, s, stacks;
|
|
int w_inc = 1, h_inc, w_base = 1, h_base;
|
|
int hrh, extra = 0, h_slice, last_h = 0;
|
|
int split, colno, winno, mwin, msize, mscale;
|
|
int remain, missing, v_slice, reconfigure = 0;
|
|
int bordered = 1;
|
|
|
|
DNPRINTF(SWM_D_STACK, "stack_master: workspace: %d, rot: %s, "
|
|
"flip: %s\n", ws->idx, YESNO(rot), YESNO(flip));
|
|
|
|
winno = count_win(ws, 0);
|
|
if (winno == 0 && count_win(ws, 1) == 0)
|
|
return;
|
|
|
|
TAILQ_FOREACH(win, &ws->winlist, entry)
|
|
if (!win->transient && !win->floating && !win->iconic)
|
|
break;
|
|
|
|
if (win == NULL)
|
|
goto notiles;
|
|
|
|
if (rot) {
|
|
w_inc = win->sh.width_inc;
|
|
w_base = win->sh.base_width;
|
|
mwin = ws->l_state.horizontal_mwin;
|
|
mscale = ws->l_state.horizontal_msize;
|
|
stacks = ws->l_state.horizontal_stacks;
|
|
SWAPXY(&r_g);
|
|
} else {
|
|
w_inc = win->sh.height_inc;
|
|
w_base = win->sh.base_height;
|
|
mwin = ws->l_state.vertical_mwin;
|
|
mscale = ws->l_state.vertical_msize;
|
|
stacks = ws->l_state.vertical_stacks;
|
|
}
|
|
win_g = r_g;
|
|
|
|
if (stacks > winno - mwin)
|
|
stacks = winno - mwin;
|
|
if (stacks < 1)
|
|
stacks = 1;
|
|
|
|
h_slice = r_g.h / SWM_H_SLICE;
|
|
if (mwin && winno > mwin) {
|
|
v_slice = r_g.w / SWM_V_SLICE;
|
|
|
|
split = mwin;
|
|
colno = split;
|
|
win_g.w = v_slice * mscale;
|
|
|
|
if (w_inc > 1 && w_inc < v_slice) {
|
|
/* adjust for window's requested size increment */
|
|
remain = (win_g.w - w_base) % w_inc;
|
|
win_g.w -= remain;
|
|
}
|
|
|
|
msize = win_g.w;
|
|
if (flip)
|
|
win_g.x += r_g.w - msize;
|
|
} else {
|
|
msize = -2;
|
|
colno = split = winno / stacks;
|
|
win_g.w = ((r_g.w - (stacks * 2 * border_width) +
|
|
2 * border_width) / stacks);
|
|
}
|
|
hrh = r_g.h / colno;
|
|
extra = r_g.h - (colno * hrh);
|
|
win_g.h = hrh - 2 * border_width;
|
|
|
|
/* stack all the tiled windows */
|
|
i = j = 0, s = stacks;
|
|
TAILQ_FOREACH(win, &ws->winlist, entry) {
|
|
if (win->transient || win->floating || win->iconic)
|
|
continue;
|
|
|
|
if (win->ewmh_flags & EWMH_F_FULLSCREEN) {
|
|
fs_win = win;
|
|
continue;
|
|
}
|
|
|
|
if (split && i == split) {
|
|
colno = (winno - mwin) / stacks;
|
|
if (s <= (winno - mwin) % stacks)
|
|
colno++;
|
|
split = split + colno;
|
|
hrh = (r_g.h / colno);
|
|
extra = r_g.h - (colno * hrh);
|
|
if (flip)
|
|
win_g.x = r_g.x;
|
|
else
|
|
win_g.x += win_g.w + 2 * border_width +
|
|
tile_gap;
|
|
win_g.w = (r_g.w - msize -
|
|
(stacks * (2 * border_width + tile_gap))) / stacks;
|
|
if (s == 1)
|
|
win_g.w += (r_g.w - msize -
|
|
(stacks * (2 * border_width + tile_gap))) %
|
|
stacks;
|
|
s--;
|
|
j = 0;
|
|
}
|
|
|
|
win_g.h = hrh - 2 * border_width - tile_gap;
|
|
|
|
if (rot) {
|
|
h_inc = win->sh.width_inc;
|
|
h_base = win->sh.base_width;
|
|
} else {
|
|
h_inc = win->sh.height_inc;
|
|
h_base = win->sh.base_height;
|
|
}
|
|
|
|
if (j == colno - 1) {
|
|
win_g.h = hrh + extra;
|
|
} else if (h_inc > 1 && h_inc < h_slice) {
|
|
/* adjust for window's requested size increment */
|
|
remain = (win_g.h - h_base) % h_inc;
|
|
missing = h_inc - remain;
|
|
|
|
if (missing <= extra || j == 0) {
|
|
extra -= missing;
|
|
win_g.h += missing;
|
|
} else {
|
|
win_g.h -= remain;
|
|
extra += remain;
|
|
}
|
|
}
|
|
|
|
if (j == 0)
|
|
win_g.y = r_g.y;
|
|
else
|
|
win_g.y += last_h + 2 * border_width + tile_gap;
|
|
|
|
if (disable_border && !(bar_enabled && ws->bar_enabled) &&
|
|
winno == 1){
|
|
bordered = 0;
|
|
win_g.w += 2 * border_width;
|
|
win_g.h += 2 * border_width;
|
|
} else {
|
|
bordered = 1;
|
|
}
|
|
|
|
if (rot) {
|
|
if (X(win) != win_g.y || Y(win) != win_g.x ||
|
|
WIDTH(win) != win_g.h || HEIGHT(win) != win_g.w) {
|
|
reconfigure = 1;
|
|
X(win) = win_g.y;
|
|
Y(win) = win_g.x;
|
|
WIDTH(win) = win_g.h;
|
|
HEIGHT(win) = win_g.w;
|
|
}
|
|
} else {
|
|
if (X(win) != win_g.x || Y(win) != win_g.y ||
|
|
WIDTH(win) != win_g.w || HEIGHT(win) != win_g.h) {
|
|
reconfigure = 1;
|
|
X(win) = win_g.x;
|
|
Y(win) = win_g.y;
|
|
WIDTH(win) = win_g.w;
|
|
HEIGHT(win) = win_g.h;
|
|
}
|
|
}
|
|
|
|
if (bordered != win->bordered) {
|
|
reconfigure = 1;
|
|
win->bordered = bordered;
|
|
}
|
|
|
|
if (reconfigure) {
|
|
adjust_font(win);
|
|
update_window(win);
|
|
}
|
|
|
|
map_window(win);
|
|
|
|
last_h = win_g.h;
|
|
i++;
|
|
j++;
|
|
}
|
|
|
|
notiles:
|
|
/* now, stack all the floaters and transients */
|
|
TAILQ_FOREACH(win, &ws->winlist, entry) {
|
|
if (!win->transient && !win->floating)
|
|
continue;
|
|
if (win->iconic)
|
|
continue;
|
|
if (win->ewmh_flags & EWMH_F_FULLSCREEN) {
|
|
fs_win = win;
|
|
continue;
|
|
}
|
|
|
|
stack_floater(win, ws->r);
|
|
map_window(win);
|
|
}
|
|
|
|
/* Make sure fs_win is stacked last so it's on top. */
|
|
if (fs_win) {
|
|
stack_floater(fs_win, ws->r);
|
|
map_window(fs_win);
|
|
}
|
|
}
|
|
|
|
void
|
|
vertical_config(struct workspace *ws, int id)
|
|
{
|
|
DNPRINTF(SWM_D_STACK, "vertical_config: id: %d, workspace: %d\n",
|
|
id, ws->idx);
|
|
|
|
switch (id) {
|
|
case SWM_ARG_ID_STACKRESET:
|
|
case SWM_ARG_ID_STACKINIT:
|
|
ws->l_state.vertical_msize = SWM_V_SLICE / 2;
|
|
ws->l_state.vertical_mwin = 1;
|
|
ws->l_state.vertical_stacks = 1;
|
|
break;
|
|
case SWM_ARG_ID_MASTERSHRINK:
|
|
if (ws->l_state.vertical_msize > 1)
|
|
ws->l_state.vertical_msize--;
|
|
break;
|
|
case SWM_ARG_ID_MASTERGROW:
|
|
if (ws->l_state.vertical_msize < SWM_V_SLICE - 1)
|
|
ws->l_state.vertical_msize++;
|
|
break;
|
|
case SWM_ARG_ID_MASTERADD:
|
|
ws->l_state.vertical_mwin++;
|
|
break;
|
|
case SWM_ARG_ID_MASTERDEL:
|
|
if (ws->l_state.vertical_mwin > 0)
|
|
ws->l_state.vertical_mwin--;
|
|
break;
|
|
case SWM_ARG_ID_STACKINC:
|
|
ws->l_state.vertical_stacks++;
|
|
break;
|
|
case SWM_ARG_ID_STACKDEC:
|
|
if (ws->l_state.vertical_stacks > 1)
|
|
ws->l_state.vertical_stacks--;
|
|
break;
|
|
case SWM_ARG_ID_FLIPLAYOUT:
|
|
ws->l_state.vertical_flip = !ws->l_state.vertical_flip;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
vertical_stack(struct workspace *ws, struct swm_geometry *g)
|
|
{
|
|
DNPRINTF(SWM_D_STACK, "vertical_stack: workspace: %d\n", ws->idx);
|
|
|
|
stack_master(ws, g, 0, ws->l_state.vertical_flip);
|
|
}
|
|
|
|
void
|
|
horizontal_config(struct workspace *ws, int id)
|
|
{
|
|
DNPRINTF(SWM_D_STACK, "horizontal_config: workspace: %d\n", ws->idx);
|
|
|
|
switch (id) {
|
|
case SWM_ARG_ID_STACKRESET:
|
|
case SWM_ARG_ID_STACKINIT:
|
|
ws->l_state.horizontal_mwin = 1;
|
|
ws->l_state.horizontal_msize = SWM_H_SLICE / 2;
|
|
ws->l_state.horizontal_stacks = 1;
|
|
break;
|
|
case SWM_ARG_ID_MASTERSHRINK:
|
|
if (ws->l_state.horizontal_msize > 1)
|
|
ws->l_state.horizontal_msize--;
|
|
break;
|
|
case SWM_ARG_ID_MASTERGROW:
|
|
if (ws->l_state.horizontal_msize < SWM_H_SLICE - 1)
|
|
ws->l_state.horizontal_msize++;
|
|
break;
|
|
case SWM_ARG_ID_MASTERADD:
|
|
ws->l_state.horizontal_mwin++;
|
|
break;
|
|
case SWM_ARG_ID_MASTERDEL:
|
|
if (ws->l_state.horizontal_mwin > 0)
|
|
ws->l_state.horizontal_mwin--;
|
|
break;
|
|
case SWM_ARG_ID_STACKINC:
|
|
ws->l_state.horizontal_stacks++;
|
|
break;
|
|
case SWM_ARG_ID_STACKDEC:
|
|
if (ws->l_state.horizontal_stacks > 1)
|
|
ws->l_state.horizontal_stacks--;
|
|
break;
|
|
case SWM_ARG_ID_FLIPLAYOUT:
|
|
ws->l_state.horizontal_flip = !ws->l_state.horizontal_flip;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
horizontal_stack(struct workspace *ws, struct swm_geometry *g)
|
|
{
|
|
DNPRINTF(SWM_D_STACK, "horizontal_stack: workspace: %d\n", ws->idx);
|
|
|
|
stack_master(ws, g, 1, ws->l_state.horizontal_flip);
|
|
}
|
|
|
|
/* fullscreen view */
|
|
void
|
|
max_stack(struct workspace *ws, struct swm_geometry *g)
|
|
{
|
|
struct swm_geometry gg = *g;
|
|
struct ws_win *w, *win = NULL, *parent = NULL;
|
|
int winno;
|
|
|
|
DNPRINTF(SWM_D_STACK, "max_stack: workspace: %d\n", ws->idx);
|
|
|
|
if (ws == NULL)
|
|
return;
|
|
|
|
winno = count_win(ws, 0);
|
|
if (winno == 0 && count_win(ws, 1) == 0)
|
|
return;
|
|
|
|
/* Figure out which top level window should be visible. */
|
|
if (ws->focus_pending)
|
|
win = ws->focus_pending;
|
|
else if (ws->focus)
|
|
win = ws->focus;
|
|
else
|
|
win = TAILQ_FIRST(&ws->winlist);
|
|
|
|
if (win->transient)
|
|
parent = find_window(win->transient);
|
|
|
|
DNPRINTF(SWM_D_STACK, "max_stack: win: 0x%x\n", win->id);
|
|
|
|
/* maximize all top level windows */
|
|
TAILQ_FOREACH(w, &ws->winlist, entry) {
|
|
if (w->transient || w->iconic)
|
|
continue;
|
|
|
|
if (!w->mapped && w != win)
|
|
map_window(w);
|
|
|
|
if (w->floating && !w->floatmaxed) {
|
|
/*
|
|
* retain geometry for retrieval on exit from
|
|
* max_stack mode
|
|
*/
|
|
store_float_geom(w, ws->r);
|
|
w->floatmaxed = 1;
|
|
}
|
|
|
|
/* only reconfigure if necessary */
|
|
if (X(w) != gg.x || Y(w) != gg.y || WIDTH(w) != gg.w ||
|
|
HEIGHT(w) != gg.h) {
|
|
w->g = gg;
|
|
if (bar_enabled && ws->bar_enabled){
|
|
w->bordered = 1;
|
|
} else {
|
|
w->bordered = 0;
|
|
WIDTH(w) += 2 * border_width;
|
|
HEIGHT(w) += 2 * border_width;
|
|
}
|
|
|
|
update_window(w);
|
|
}
|
|
}
|
|
|
|
/* If a parent exists, map/raise it first. */
|
|
if (parent) {
|
|
map_window(parent);
|
|
|
|
/* Map siblings next. */
|
|
TAILQ_FOREACH(w, &ws->winlist, entry)
|
|
if (w != win && !w->iconic &&
|
|
w->transient == parent->id) {
|
|
stack_floater(w, ws->r);
|
|
map_window(w);
|
|
}
|
|
}
|
|
|
|
/* Map/raise focused window. */
|
|
map_window(win);
|
|
|
|
/* Finally, map/raise children of focus window. */
|
|
TAILQ_FOREACH(w, &ws->winlist, entry)
|
|
if (w->transient == win->id && !w->iconic) {
|
|
stack_floater(w, ws->r);
|
|
map_window(w);
|
|
}
|
|
}
|
|
|
|
void
|
|
send_to_rg(struct swm_region *r, union arg *args)
|
|
{
|
|
int ridx = args->id, i, num_screens;
|
|
struct swm_region *rr = NULL;
|
|
union arg a;
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
/* do nothing if we don't have more than one screen */
|
|
if (!(num_screens > 1 || outputs > 1))
|
|
return;
|
|
|
|
DNPRINTF(SWM_D_FOCUS, "send_to_rg: id: %d\n", ridx);
|
|
|
|
rr = TAILQ_FIRST(&r->s->rl);
|
|
for (i = 0; i < ridx; ++i)
|
|
rr = TAILQ_NEXT(rr, entry);
|
|
|
|
if (rr == NULL)
|
|
return;
|
|
|
|
a.id = rr->ws->idx;
|
|
|
|
send_to_ws(r, &a);
|
|
}
|
|
|
|
void
|
|
send_to_ws(struct swm_region *r, union arg *args)
|
|
{
|
|
int wsid = args->id;
|
|
struct ws_win *win = NULL, *parent;
|
|
struct workspace *ws, *nws, *pws;
|
|
char ws_idx_str[SWM_PROPLEN];
|
|
|
|
if (wsid >= workspace_limit)
|
|
return;
|
|
|
|
if (r && r->ws && r->ws->focus)
|
|
win = r->ws->focus;
|
|
else
|
|
return;
|
|
|
|
if (win->ws->idx == wsid)
|
|
return;
|
|
|
|
DNPRINTF(SWM_D_MOVE, "send_to_ws: win 0x%x, ws %d -> %d\n", win->id,
|
|
win->ws->idx, wsid);
|
|
|
|
ws = win->ws;
|
|
nws = &win->s->ws[wsid];
|
|
|
|
/* Update the window's workspace property: _SWM_WS */
|
|
if (snprintf(ws_idx_str, SWM_PROPLEN, "%d", nws->idx) < SWM_PROPLEN) {
|
|
if (focus_mode != SWM_FOCUS_FOLLOW)
|
|
ws->focus_pending = get_focus_prev(win);
|
|
|
|
/* Move the parent if this is a transient window. */
|
|
if (win->transient) {
|
|
parent = find_window(win->transient);
|
|
if (parent) {
|
|
pws = parent->ws;
|
|
/* Set new focus in parent's ws if needed. */
|
|
if (pws->focus == parent) {
|
|
if (focus_mode != SWM_FOCUS_FOLLOW)
|
|
pws->focus_pending =
|
|
get_focus_prev(parent);
|
|
|
|
unfocus_win(parent);
|
|
|
|
if (focus_mode != SWM_FOCUS_FOLLOW)
|
|
pws->focus = pws->focus_pending;
|
|
|
|
if (focus_mode != SWM_FOCUS_FOLLOW)
|
|
pws->focus_pending = NULL;
|
|
}
|
|
|
|
/* Don't unmap parent if new ws is visible */
|
|
if (nws->r == NULL)
|
|
unmap_window(parent);
|
|
|
|
/* Transfer */
|
|
TAILQ_REMOVE(&ws->winlist, parent, entry);
|
|
TAILQ_INSERT_TAIL(&nws->winlist, parent, entry);
|
|
parent->ws = nws;
|
|
|
|
DNPRINTF(SWM_D_PROP, "send_to_ws: set "
|
|
"property: _SWM_WS: %s\n", ws_idx_str);
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE,
|
|
parent->id, a_swm_ws, XCB_ATOM_STRING, 8,
|
|
strlen(ws_idx_str), ws_idx_str);
|
|
}
|
|
}
|
|
|
|
unfocus_win(win);
|
|
|
|
/* Don't unmap if new ws is visible */
|
|
if (nws->r == NULL)
|
|
unmap_window(win);
|
|
|
|
/* Transfer */
|
|
TAILQ_REMOVE(&ws->winlist, win, entry);
|
|
TAILQ_INSERT_TAIL(&nws->winlist, win, entry);
|
|
win->ws = nws;
|
|
|
|
/* Set focus on new ws. */
|
|
unfocus_win(nws->focus);
|
|
nws->focus = win;
|
|
|
|
DNPRINTF(SWM_D_PROP, "send_to_ws: set property: _SWM_WS: %s\n",
|
|
ws_idx_str);
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->id,
|
|
a_swm_ws, XCB_ATOM_STRING, 8, strlen(ws_idx_str),
|
|
ws_idx_str);
|
|
|
|
/* Restack and set new focus. */
|
|
stack();
|
|
|
|
if (focus_mode != SWM_FOCUS_FOLLOW) {
|
|
focus_win(ws->focus_pending);
|
|
ws->focus_pending = NULL;
|
|
}
|
|
|
|
focus_flush();
|
|
}
|
|
|
|
DNPRINTF(SWM_D_MOVE, "send_to_ws: done.\n");
|
|
}
|
|
|
|
void
|
|
pressbutton(struct swm_region *r, union arg *args)
|
|
{
|
|
/* suppress unused warning since var is needed */
|
|
(void)r;
|
|
|
|
xcb_test_fake_input(conn, XCB_BUTTON_PRESS, args->id,
|
|
XCB_CURRENT_TIME, XCB_WINDOW_NONE, 0, 0, 0);
|
|
xcb_test_fake_input(conn, XCB_BUTTON_RELEASE, args->id,
|
|
XCB_CURRENT_TIME, XCB_WINDOW_NONE, 0, 0, 0);
|
|
}
|
|
|
|
void
|
|
raise_toggle(struct swm_region *r, union arg *args)
|
|
{
|
|
/* suppress unused warning since var is needed */
|
|
(void)args;
|
|
|
|
if (r == NULL || r->ws == NULL)
|
|
return;
|
|
|
|
r->ws->always_raise = !r->ws->always_raise;
|
|
|
|
/* bring floaters back to top */
|
|
if (!r->ws->always_raise)
|
|
stack();
|
|
|
|
focus_flush();
|
|
}
|
|
|
|
void
|
|
iconify(struct swm_region *r, union arg *args)
|
|
{
|
|
/* suppress unused warning since var is needed */
|
|
(void)args;
|
|
|
|
if (r->ws->focus == NULL)
|
|
return;
|
|
|
|
set_swm_iconic(r->ws->focus, 1);
|
|
|
|
xcb_flush(conn);
|
|
}
|
|
|
|
char *
|
|
get_win_name(xcb_window_t win)
|
|
{
|
|
char *name = NULL;
|
|
xcb_get_property_cookie_t c;
|
|
xcb_get_property_reply_t *r;
|
|
|
|
/* First try _NET_WM_NAME for UTF-8. */
|
|
c = xcb_get_property(conn, 0, win, a_netwmname,
|
|
XCB_GET_PROPERTY_TYPE_ANY, 0, UINT_MAX);
|
|
r = xcb_get_property_reply(conn, c, NULL);
|
|
|
|
if (r) {
|
|
if (r->type == XCB_NONE) {
|
|
free(r);
|
|
/* Use WM_NAME instead; no UTF-8. */
|
|
c = xcb_get_property(conn, 0, win, XCB_ATOM_WM_NAME,
|
|
XCB_GET_PROPERTY_TYPE_ANY, 0, UINT_MAX);
|
|
r = xcb_get_property_reply(conn, c, NULL);
|
|
|
|
if (!r)
|
|
return (NULL);
|
|
if (r->type == XCB_NONE) {
|
|
free(r);
|
|
return (NULL);
|
|
}
|
|
}
|
|
if (r->length > 0)
|
|
name = strndup(xcb_get_property_value(r),
|
|
xcb_get_property_value_length(r));
|
|
|
|
free(r);
|
|
}
|
|
|
|
return (name);
|
|
}
|
|
|
|
void
|
|
uniconify(struct swm_region *r, union arg *args)
|
|
{
|
|
struct ws_win *win;
|
|
FILE *lfile;
|
|
char *name;
|
|
int count = 0;
|
|
|
|
DNPRINTF(SWM_D_MISC, "uniconify\n");
|
|
|
|
if (r == NULL || r->ws == NULL)
|
|
return;
|
|
|
|
/* make sure we have anything to uniconify */
|
|
TAILQ_FOREACH(win, &r->ws->winlist, entry) {
|
|
if (win->ws == NULL)
|
|
continue; /* should never happen */
|
|
if (!win->iconic)
|
|
continue;
|
|
count++;
|
|
}
|
|
if (count == 0)
|
|
return;
|
|
|
|
search_r = r;
|
|
search_resp_action = SWM_SEARCH_UNICONIFY;
|
|
|
|
spawn_select(r, args, "search", &searchpid);
|
|
|
|
if ((lfile = fdopen(select_list_pipe[1], "w")) == NULL)
|
|
return;
|
|
|
|
TAILQ_FOREACH(win, &r->ws->winlist, entry) {
|
|
if (win->ws == NULL)
|
|
continue; /* should never happen */
|
|
if (!win->iconic)
|
|
continue;
|
|
|
|
name = get_win_name(win->id);
|
|
if (name == NULL)
|
|
continue;
|
|
fprintf(lfile, "%s.%u\n", name, win->id);
|
|
free(name);
|
|
}
|
|
|
|
fclose(lfile);
|
|
}
|
|
|
|
void
|
|
name_workspace(struct swm_region *r, union arg *args)
|
|
{
|
|
FILE *lfile;
|
|
|
|
DNPRINTF(SWM_D_MISC, "name_workspace\n");
|
|
|
|
if (r == NULL)
|
|
return;
|
|
|
|
search_r = r;
|
|
search_resp_action = SWM_SEARCH_NAME_WORKSPACE;
|
|
|
|
spawn_select(r, args, "name_workspace", &searchpid);
|
|
|
|
if ((lfile = fdopen(select_list_pipe[1], "w")) == NULL)
|
|
return;
|
|
|
|
fprintf(lfile, "%s", "");
|
|
fclose(lfile);
|
|
}
|
|
|
|
void
|
|
search_workspace(struct swm_region *r, union arg *args)
|
|
{
|
|
int i;
|
|
struct workspace *ws;
|
|
FILE *lfile;
|
|
|
|
DNPRINTF(SWM_D_MISC, "search_workspace\n");
|
|
|
|
if (r == NULL)
|
|
return;
|
|
|
|
search_r = r;
|
|
search_resp_action = SWM_SEARCH_SEARCH_WORKSPACE;
|
|
|
|
spawn_select(r, args, "search", &searchpid);
|
|
|
|
if ((lfile = fdopen(select_list_pipe[1], "w")) == NULL)
|
|
return;
|
|
|
|
for (i = 0; i < workspace_limit; i++) {
|
|
ws = &r->s->ws[i];
|
|
if (ws == NULL)
|
|
continue;
|
|
fprintf(lfile, "%d%s%s\n", ws->idx + 1,
|
|
(ws->name ? ":" : ""), (ws->name ? ws->name : ""));
|
|
}
|
|
|
|
fclose(lfile);
|
|
}
|
|
|
|
void
|
|
search_win_cleanup(void)
|
|
{
|
|
struct search_window *sw = NULL;
|
|
|
|
while ((sw = TAILQ_FIRST(&search_wl)) != NULL) {
|
|
xcb_destroy_window(conn, sw->indicator);
|
|
TAILQ_REMOVE(&search_wl, sw, entry);
|
|
free(sw);
|
|
}
|
|
}
|
|
|
|
void
|
|
search_win(struct swm_region *r, union arg *args)
|
|
{
|
|
struct ws_win *win = NULL;
|
|
struct search_window *sw = NULL;
|
|
xcb_window_t w;
|
|
uint32_t wa[2];
|
|
int i, width, height;
|
|
char s[8];
|
|
FILE *lfile;
|
|
size_t len;
|
|
XftDraw *draw;
|
|
XGlyphInfo info;
|
|
GC l_draw;
|
|
XGCValues l_gcv;
|
|
XRectangle l_ibox, l_lbox;
|
|
|
|
DNPRINTF(SWM_D_MISC, "search_win\n");
|
|
|
|
search_r = r;
|
|
search_resp_action = SWM_SEARCH_SEARCH_WINDOW;
|
|
|
|
spawn_select(r, args, "search", &searchpid);
|
|
|
|
if ((lfile = fdopen(select_list_pipe[1], "w")) == NULL)
|
|
return;
|
|
|
|
TAILQ_INIT(&search_wl);
|
|
|
|
i = 1;
|
|
TAILQ_FOREACH(win, &r->ws->winlist, entry) {
|
|
if (win->iconic)
|
|
continue;
|
|
|
|
sw = calloc(1, sizeof(struct search_window));
|
|
if (sw == NULL) {
|
|
warn("search_win: calloc");
|
|
fclose(lfile);
|
|
search_win_cleanup();
|
|
return;
|
|
}
|
|
sw->idx = i;
|
|
sw->win = win;
|
|
|
|
snprintf(s, sizeof s, "%d", i);
|
|
len = strlen(s);
|
|
|
|
w = xcb_generate_id(conn);
|
|
wa[0] = r->s->c[SWM_S_COLOR_FOCUS].pixel;
|
|
wa[1] = r->s->c[SWM_S_COLOR_UNFOCUS].pixel;
|
|
|
|
if (bar_font_legacy) {
|
|
XmbTextExtents(bar_fs, s, len, &l_ibox, &l_lbox);
|
|
width = l_lbox.width + 4;
|
|
height = bar_fs_extents->max_logical_extent.height + 4;
|
|
} else {
|
|
XftTextExtentsUtf8(display, bar_font, (FcChar8 *)s, len,
|
|
&info);
|
|
width = info.width + 4;
|
|
height = bar_font->height + 4;
|
|
}
|
|
|
|
xcb_create_window(conn, XCB_COPY_FROM_PARENT, w, win->id, 0, 0,
|
|
width, height, 1, XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
|
XCB_COPY_FROM_PARENT, XCB_CW_BACK_PIXEL |
|
|
XCB_CW_BORDER_PIXEL, wa);
|
|
|
|
xcb_map_window(conn, w);
|
|
|
|
sw->indicator = w;
|
|
TAILQ_INSERT_TAIL(&search_wl, sw, entry);
|
|
|
|
if (bar_font_legacy) {
|
|
l_gcv.graphics_exposures = 0;
|
|
l_draw = XCreateGC(display, w, 0, &l_gcv);
|
|
|
|
XSetForeground(display, l_draw,
|
|
r->s->c[SWM_S_COLOR_BAR].pixel);
|
|
|
|
DRAWSTRING(display, w, bar_fs, l_draw, 2,
|
|
(bar_fs_extents->max_logical_extent.height -
|
|
l_lbox.height) / 2 - l_lbox.y, s, len);
|
|
|
|
XFreeGC(display, l_draw);
|
|
} else {
|
|
|
|
draw = XftDrawCreate(display, w,
|
|
DefaultVisual(display, r->s->idx),
|
|
DefaultColormap(display, r->s->idx));
|
|
|
|
XftDrawStringUtf8(draw, &bar_font_color, bar_font, 2,
|
|
(HEIGHT(r->bar) + bar_font->height) / 2 -
|
|
bar_font->descent, (FcChar8 *)s, len);
|
|
|
|
XftDrawDestroy(draw);
|
|
}
|
|
|
|
DNPRINTF(SWM_D_MISC, "search_win: mapped window: 0x%x\n", w);
|
|
|
|
fprintf(lfile, "%d\n", i);
|
|
i++;
|
|
}
|
|
|
|
fclose(lfile);
|
|
|
|
xcb_flush(conn);
|
|
}
|
|
|
|
void
|
|
search_resp_uniconify(const char *resp, unsigned long len)
|
|
{
|
|
char *name;
|
|
struct ws_win *win;
|
|
char *s;
|
|
|
|
DNPRINTF(SWM_D_MISC, "search_resp_uniconify: resp: %s\n", resp);
|
|
|
|
TAILQ_FOREACH(win, &search_r->ws->winlist, entry) {
|
|
if (!win->iconic)
|
|
continue;
|
|
name = get_win_name(win->id);
|
|
if (name == NULL)
|
|
continue;
|
|
if (asprintf(&s, "%s.%u", name, win->id) == -1) {
|
|
free(name);
|
|
continue;
|
|
}
|
|
free(name);
|
|
if (strncmp(s, resp, len) == 0) {
|
|
/* XXX this should be a callback to generalize */
|
|
set_swm_iconic(win, 0);
|
|
free(s);
|
|
break;
|
|
}
|
|
free(s);
|
|
}
|
|
}
|
|
|
|
void
|
|
search_resp_name_workspace(const char *resp, unsigned long len)
|
|
{
|
|
struct workspace *ws;
|
|
|
|
DNPRINTF(SWM_D_MISC, "search_resp_name_workspace: resp: %s\n", resp);
|
|
|
|
if (search_r->ws == NULL)
|
|
return;
|
|
ws = search_r->ws;
|
|
|
|
if (ws->name) {
|
|
free(search_r->ws->name);
|
|
search_r->ws->name = NULL;
|
|
}
|
|
|
|
if (len > 1) {
|
|
ws->name = strdup(resp);
|
|
if (ws->name == NULL) {
|
|
DNPRINTF(SWM_D_MISC, "search_resp_name_workspace: "
|
|
"strdup: %s", strerror(errno));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
search_resp_search_workspace(const char *resp)
|
|
{
|
|
char *p, *q;
|
|
int ws_idx;
|
|
const char *errstr;
|
|
union arg a;
|
|
|
|
DNPRINTF(SWM_D_MISC, "search_resp_search_workspace: resp: %s\n", resp);
|
|
|
|
q = strdup(resp);
|
|
if (!q) {
|
|
DNPRINTF(SWM_D_MISC, "search_resp_search_workspace: strdup: %s",
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
p = strchr(q, ':');
|
|
if (p != NULL)
|
|
*p = '\0';
|
|
ws_idx = (int)strtonum(q, 1, workspace_limit, &errstr);
|
|
if (errstr) {
|
|
DNPRINTF(SWM_D_MISC, "workspace idx is %s: %s",
|
|
errstr, q);
|
|
free(q);
|
|
return;
|
|
}
|
|
free(q);
|
|
a.id = ws_idx - 1;
|
|
switchws(search_r, &a);
|
|
}
|
|
|
|
void
|
|
search_resp_search_window(const char *resp)
|
|
{
|
|
char *s;
|
|
int idx;
|
|
const char *errstr;
|
|
struct search_window *sw;
|
|
|
|
DNPRINTF(SWM_D_MISC, "search_resp_search_window: resp: %s\n", resp);
|
|
|
|
s = strdup(resp);
|
|
if (!s) {
|
|
DNPRINTF(SWM_D_MISC, "search_resp_search_window: strdup: %s",
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
|
|
idx = (int)strtonum(s, 1, INT_MAX, &errstr);
|
|
if (errstr) {
|
|
DNPRINTF(SWM_D_MISC, "window idx is %s: %s",
|
|
errstr, s);
|
|
free(s);
|
|
return;
|
|
}
|
|
free(s);
|
|
|
|
TAILQ_FOREACH(sw, &search_wl, entry)
|
|
if (idx == sw->idx) {
|
|
focus_win(sw->win);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define MAX_RESP_LEN 1024
|
|
|
|
void
|
|
search_do_resp(void)
|
|
{
|
|
ssize_t rbytes;
|
|
char *resp;
|
|
unsigned long len;
|
|
|
|
DNPRINTF(SWM_D_MISC, "search_do_resp:\n");
|
|
|
|
search_resp = 0;
|
|
searchpid = 0;
|
|
|
|
if ((resp = calloc(1, MAX_RESP_LEN + 1)) == NULL) {
|
|
warn("search: calloc");
|
|
goto done;
|
|
}
|
|
|
|
rbytes = read(select_resp_pipe[0], resp, MAX_RESP_LEN);
|
|
if (rbytes <= 0) {
|
|
warn("search: read error");
|
|
goto done;
|
|
}
|
|
resp[rbytes] = '\0';
|
|
|
|
/* XXX:
|
|
* Older versions of dmenu (Atleast pre 4.4.1) do not send a
|
|
* newline, so work around that by sanitizing the resp now.
|
|
*/
|
|
resp[strcspn(resp, "\n")] = '\0';
|
|
len = strlen(resp);
|
|
|
|
switch (search_resp_action) {
|
|
case SWM_SEARCH_UNICONIFY:
|
|
search_resp_uniconify(resp, len);
|
|
break;
|
|
case SWM_SEARCH_NAME_WORKSPACE:
|
|
search_resp_name_workspace(resp, len);
|
|
break;
|
|
case SWM_SEARCH_SEARCH_WORKSPACE:
|
|
search_resp_search_workspace(resp);
|
|
break;
|
|
case SWM_SEARCH_SEARCH_WINDOW:
|
|
search_resp_search_window(resp);
|
|
break;
|
|
}
|
|
|
|
done:
|
|
if (search_resp_action == SWM_SEARCH_SEARCH_WINDOW)
|
|
search_win_cleanup();
|
|
|
|
search_resp_action = SWM_SEARCH_NONE;
|
|
close(select_resp_pipe[0]);
|
|
free(resp);
|
|
|
|
xcb_flush(conn);
|
|
}
|
|
|
|
void
|
|
wkill(struct swm_region *r, union arg *args)
|
|
{
|
|
DNPRINTF(SWM_D_MISC, "wkill: id: %d\n", args->id);
|
|
|
|
if (r->ws->focus == NULL)
|
|
return;
|
|
|
|
if (args->id == SWM_ARG_ID_KILLWINDOW)
|
|
xcb_kill_client(conn, r->ws->focus->id);
|
|
else
|
|
if (r->ws->focus->can_delete)
|
|
client_msg(r->ws->focus, a_delete, 0);
|
|
|
|
focus_flush();
|
|
}
|
|
|
|
int
|
|
floating_toggle_win(struct ws_win *win)
|
|
{
|
|
struct swm_region *r;
|
|
|
|
if (win == NULL)
|
|
return (0);
|
|
|
|
if (!win->ws->r)
|
|
return (0);
|
|
|
|
r = win->ws->r;
|
|
|
|
/* reject floating toggles in max stack mode */
|
|
if (win->ws->cur_layout == &layouts[SWM_MAX_STACK])
|
|
return (0);
|
|
|
|
if (win->floating) {
|
|
if (!win->floatmaxed) {
|
|
/* retain position for refloat */
|
|
store_float_geom(win, r);
|
|
}
|
|
win->floating = 0;
|
|
} else {
|
|
load_float_geom(win, r);
|
|
win->floating = 1;
|
|
}
|
|
|
|
ewmh_update_actions(win);
|
|
|
|
return (1);
|
|
}
|
|
|
|
void
|
|
floating_toggle(struct swm_region *r, union arg *args)
|
|
{
|
|
struct ws_win *win = r->ws->focus;
|
|
|
|
/* suppress unused warning since var is needed */
|
|
(void)args;
|
|
|
|
if (win == NULL)
|
|
return;
|
|
|
|
if (win->ewmh_flags & EWMH_F_FULLSCREEN)
|
|
return;
|
|
|
|
ewmh_update_win_state(win, ewmh[_NET_WM_STATE_ABOVE].atom,
|
|
_NET_WM_STATE_TOGGLE);
|
|
|
|
stack();
|
|
|
|
if (win == win->ws->focus)
|
|
focus_win(win);
|
|
|
|
focus_flush();
|
|
}
|
|
|
|
void
|
|
constrain_window(struct ws_win *win, struct swm_region *r, int resizable)
|
|
{
|
|
if (MAX_X(win) + BORDER(win) > MAX_X(r)) {
|
|
if (resizable)
|
|
WIDTH(win) = MAX_X(r) - X(win) - BORDER(win);
|
|
else
|
|
X(win) = MAX_X(r)- WIDTH(win) - BORDER(win);
|
|
}
|
|
|
|
if (X(win) + BORDER(win) < X(r)) {
|
|
if (resizable)
|
|
WIDTH(win) -= X(r) - X(win) - BORDER(win);
|
|
|
|
X(win) = X(r) - BORDER(win);
|
|
}
|
|
|
|
if (MAX_Y(win) + BORDER(win) > MAX_Y(r)) {
|
|
if (resizable)
|
|
HEIGHT(win) = MAX_Y(r) - Y(win) - BORDER(win);
|
|
else
|
|
Y(win) = MAX_Y(r) - HEIGHT(win) - BORDER(win);
|
|
}
|
|
|
|
if (Y(win) + BORDER(win) < Y(r)) {
|
|
if (resizable)
|
|
HEIGHT(win) -= Y(r) - Y(win) - BORDER(win);
|
|
|
|
Y(win) = Y(r) - BORDER(win);
|
|
}
|
|
|
|
if (resizable) {
|
|
if (WIDTH(win) < 1)
|
|
WIDTH(win) = 1;
|
|
if (HEIGHT(win) < 1)
|
|
HEIGHT(win) = 1;
|
|
}
|
|
}
|
|
|
|
void
|
|
update_window(struct ws_win *win)
|
|
{
|
|
uint16_t mask;
|
|
uint32_t wc[5];
|
|
|
|
mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
|
|
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
|
|
XCB_CONFIG_WINDOW_BORDER_WIDTH;
|
|
wc[0] = X(win);
|
|
wc[1] = Y(win);
|
|
wc[2] = WIDTH(win);
|
|
wc[3] = HEIGHT(win);
|
|
wc[4] = BORDER(win);
|
|
|
|
DNPRINTF(SWM_D_EVENT, "update_window: window: 0x%x, (x,y) w x h: "
|
|
"(%d,%d) %d x %d, bordered: %s\n", win->id, wc[0], wc[1], wc[2],
|
|
wc[3], YESNO(win->bordered));
|
|
|
|
xcb_configure_window(conn, win->id, mask, wc);
|
|
}
|
|
|
|
#define SWM_RESIZE_STEPS (50)
|
|
|
|
void
|
|
resize(struct ws_win *win, union arg *args)
|
|
{
|
|
xcb_timestamp_t timestamp = 0;
|
|
struct swm_region *r = NULL;
|
|
int resize_stp = 0;
|
|
struct swm_geometry g;
|
|
int top = 0, left = 0, resizing;
|
|
int dx, dy;
|
|
xcb_cursor_t cursor;
|
|
xcb_query_pointer_reply_t *xpr;
|
|
xcb_generic_event_t *evt;
|
|
xcb_motion_notify_event_t *mne;
|
|
|
|
if (win == NULL)
|
|
return;
|
|
r = win->ws->r;
|
|
|
|
if (win->ewmh_flags & EWMH_F_FULLSCREEN)
|
|
return;
|
|
|
|
DNPRINTF(SWM_D_EVENT, "resize: window: 0x%x, floating: %s, "
|
|
"transient: 0x%x\n", win->id, YESNO(win->floating),
|
|
win->transient);
|
|
|
|
if (!win->transient && !win->floating)
|
|
return;
|
|
|
|
/* reject resizes in max mode for floaters (transient ok) */
|
|
if (win->floatmaxed)
|
|
return;
|
|
|
|
win->manual = 1;
|
|
ewmh_update_win_state(win, ewmh[_SWM_WM_STATE_MANUAL].atom,
|
|
_NET_WM_STATE_ADD);
|
|
|
|
stack();
|
|
|
|
focus_flush();
|
|
|
|
/* It's possible for win to have been freed during focus_flush(). */
|
|
if (validate_win(win)) {
|
|
DNPRINTF(SWM_D_EVENT, "move: invalid win.\n");
|
|
goto out;
|
|
}
|
|
|
|
switch (args->id) {
|
|
case SWM_ARG_ID_WIDTHSHRINK:
|
|
WIDTH(win) -= SWM_RESIZE_STEPS;
|
|
resize_stp = 1;
|
|
break;
|
|
case SWM_ARG_ID_WIDTHGROW:
|
|
WIDTH(win) += SWM_RESIZE_STEPS;
|
|
resize_stp = 1;
|
|
break;
|
|
case SWM_ARG_ID_HEIGHTSHRINK:
|
|
HEIGHT(win) -= SWM_RESIZE_STEPS;
|
|
resize_stp = 1;
|
|
break;
|
|
case SWM_ARG_ID_HEIGHTGROW:
|
|
HEIGHT(win) += SWM_RESIZE_STEPS;
|
|
resize_stp = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (resize_stp) {
|
|
constrain_window(win, r, 1);
|
|
update_window(win);
|
|
store_float_geom(win,r);
|
|
return;
|
|
}
|
|
|
|
/* get cursor offset from window root */
|
|
xpr = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, win->id),
|
|
NULL);
|
|
if (!xpr)
|
|
return;
|
|
|
|
g = win->g;
|
|
|
|
if (xpr->win_x < WIDTH(win) / 2)
|
|
left = 1;
|
|
|
|
if (xpr->win_y < HEIGHT(win) / 2)
|
|
top = 1;
|
|
|
|
if (args->id == SWM_ARG_ID_CENTER)
|
|
cursor = cursors[XC_SIZING].cid;
|
|
else if (top)
|
|
cursor = cursors[left ? XC_TOP_LEFT_CORNER :
|
|
XC_TOP_RIGHT_CORNER].cid;
|
|
else
|
|
cursor = cursors[left ? XC_BOTTOM_LEFT_CORNER :
|
|
XC_BOTTOM_RIGHT_CORNER].cid;
|
|
|
|
xcb_grab_pointer(conn, 0, win->id, MOUSEMASK,
|
|
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_WINDOW_NONE, cursor,
|
|
XCB_CURRENT_TIME),
|
|
|
|
xcb_flush(conn);
|
|
resizing = 1;
|
|
while (resizing && (evt = xcb_wait_for_event(conn))) {
|
|
switch (XCB_EVENT_RESPONSE_TYPE(evt)) {
|
|
case XCB_BUTTON_RELEASE:
|
|
DNPRINTF(SWM_D_EVENT, "resize: BUTTON_RELEASE\n");
|
|
resizing = 0;
|
|
break;
|
|
case XCB_MOTION_NOTIFY:
|
|
mne = (xcb_motion_notify_event_t *)evt;
|
|
/* cursor offset/delta from start of the operation */
|
|
dx = mne->root_x - xpr->root_x;
|
|
dy = mne->root_y - xpr->root_y;
|
|
|
|
/* vertical */
|
|
if (top)
|
|
dy = -dy;
|
|
|
|
if (args->id == SWM_ARG_ID_CENTER) {
|
|
if (g.h / 2 + dy < 1)
|
|
dy = 1 - g.h / 2;
|
|
|
|
Y(win) = g.y - dy;
|
|
HEIGHT(win) = g.h + 2 * dy;
|
|
} else {
|
|
if (g.h + dy < 1)
|
|
dy = 1 - g.h;
|
|
|
|
if (top)
|
|
Y(win) = g.y - dy;
|
|
|
|
HEIGHT(win) = g.h + dy;
|
|
}
|
|
|
|
/* horizontal */
|
|
if (left)
|
|
dx = -dx;
|
|
|
|
if (args->id == SWM_ARG_ID_CENTER) {
|
|
if (g.w / 2 + dx < 1)
|
|
dx = 1 - g.w / 2;
|
|
|
|
X(win) = g.x - dx;
|
|
WIDTH(win) = g.w + 2 * dx;
|
|
} else {
|
|
if (g.w + dx < 1)
|
|
dx = 1 - g.w;
|
|
|
|
if (left)
|
|
X(win) = g.x - dx;
|
|
|
|
WIDTH(win) = g.w + dx;
|
|
}
|
|
|
|
constrain_window(win, r, 1);
|
|
|
|
/* not free, don't sync more than 120 times / second */
|
|
if ((mne->time - timestamp) > (1000 / 120) ) {
|
|
timestamp = mne->time;
|
|
update_window(win);
|
|
xcb_flush(conn);
|
|
}
|
|
break;
|
|
default:
|
|
event_handle(evt);
|
|
|
|
/* It's possible for win to have been freed above. */
|
|
if (validate_win(win)) {
|
|
DNPRINTF(SWM_D_EVENT, "move: invalid win.\n");
|
|
goto out;
|
|
}
|
|
break;
|
|
}
|
|
free(evt);
|
|
}
|
|
if (timestamp) {
|
|
update_window(win);
|
|
xcb_flush(conn);
|
|
}
|
|
store_float_geom(win,r);
|
|
out:
|
|
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
|
|
free(xpr);
|
|
DNPRINTF(SWM_D_EVENT, "resize: done.\n");
|
|
}
|
|
|
|
void
|
|
resize_step(struct swm_region *r, union arg *args)
|
|
{
|
|
struct ws_win *win = NULL;
|
|
|
|
if (r && r->ws && r->ws->focus)
|
|
win = r->ws->focus;
|
|
else
|
|
return;
|
|
|
|
resize(win, args);
|
|
focus_flush();
|
|
}
|
|
|
|
#define SWM_MOVE_STEPS (50)
|
|
|
|
void
|
|
move(struct ws_win *win, union arg *args)
|
|
{
|
|
xcb_timestamp_t timestamp = 0;
|
|
int move_stp = 0, moving;
|
|
struct swm_region *r = NULL;
|
|
xcb_query_pointer_reply_t *qpr;
|
|
xcb_generic_event_t *evt;
|
|
xcb_motion_notify_event_t *mne;
|
|
|
|
if (win == NULL)
|
|
return;
|
|
r = win->ws->r;
|
|
|
|
if (win->ewmh_flags & EWMH_F_FULLSCREEN)
|
|
return;
|
|
|
|
DNPRINTF(SWM_D_EVENT, "move: window: 0x%x, floating: %s, transient: "
|
|
"0x%x\n", win->id, YESNO(win->floating), win->transient);
|
|
|
|
/* in max_stack mode should only move transients */
|
|
if (win->ws->cur_layout == &layouts[SWM_MAX_STACK] && !win->transient)
|
|
return;
|
|
|
|
win->manual = 1;
|
|
if (!win->floating && !win->transient) {
|
|
store_float_geom(win, r);
|
|
ewmh_update_win_state(win, ewmh[_NET_WM_STATE_ABOVE].atom,
|
|
_NET_WM_STATE_ADD);
|
|
}
|
|
ewmh_update_win_state(win, ewmh[_SWM_WM_STATE_MANUAL].atom,
|
|
_NET_WM_STATE_ADD);
|
|
|
|
stack();
|
|
|
|
focus_flush();
|
|
|
|
/* It's possible for win to have been freed during focus_flush(). */
|
|
if (validate_win(win)) {
|
|
DNPRINTF(SWM_D_EVENT, "move: invalid win.\n");
|
|
goto out;
|
|
}
|
|
|
|
move_stp = 0;
|
|
switch (args->id) {
|
|
case SWM_ARG_ID_MOVELEFT:
|
|
X(win) -= (SWM_MOVE_STEPS - border_width);
|
|
move_stp = 1;
|
|
break;
|
|
case SWM_ARG_ID_MOVERIGHT:
|
|
X(win) += (SWM_MOVE_STEPS - border_width);
|
|
move_stp = 1;
|
|
break;
|
|
case SWM_ARG_ID_MOVEUP:
|
|
Y(win) -= (SWM_MOVE_STEPS - border_width);
|
|
move_stp = 1;
|
|
break;
|
|
case SWM_ARG_ID_MOVEDOWN:
|
|
Y(win) += (SWM_MOVE_STEPS - border_width);
|
|
move_stp = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (move_stp) {
|
|
constrain_window(win, r, 0);
|
|
update_window(win);
|
|
store_float_geom(win, r);
|
|
return;
|
|
}
|
|
|
|
xcb_grab_pointer(conn, 0, win->id, MOUSEMASK,
|
|
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
|
|
XCB_WINDOW_NONE, cursors[XC_FLEUR].cid, XCB_CURRENT_TIME);
|
|
|
|
/* get cursor offset from window root */
|
|
qpr = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, win->id),
|
|
NULL);
|
|
if (!qpr) {
|
|
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
|
|
return;
|
|
}
|
|
|
|
xcb_flush(conn);
|
|
moving = 1;
|
|
while (moving && (evt = xcb_wait_for_event(conn))) {
|
|
switch (XCB_EVENT_RESPONSE_TYPE(evt)) {
|
|
case XCB_BUTTON_RELEASE:
|
|
DNPRINTF(SWM_D_EVENT, "move: BUTTON_RELEASE\n");
|
|
moving = 0;
|
|
break;
|
|
case XCB_MOTION_NOTIFY:
|
|
mne = (xcb_motion_notify_event_t *)evt;
|
|
X(win) = mne->root_x - qpr->win_x - border_width;
|
|
Y(win) = mne->root_y - qpr->win_y - border_width;
|
|
|
|
constrain_window(win, r, 0);
|
|
|
|
/* not free, don't sync more than 120 times / second */
|
|
if ((mne->time - timestamp) > (1000 / 120) ) {
|
|
timestamp = mne->time;
|
|
update_window(win);
|
|
xcb_flush(conn);
|
|
}
|
|
break;
|
|
default:
|
|
event_handle(evt);
|
|
|
|
/* It's possible for win to have been freed above. */
|
|
if (validate_win(win)) {
|
|
DNPRINTF(SWM_D_EVENT, "move: invalid win.\n");
|
|
goto out;
|
|
}
|
|
break;
|
|
}
|
|
free(evt);
|
|
}
|
|
if (timestamp) {
|
|
update_window(win);
|
|
xcb_flush(conn);
|
|
}
|
|
store_float_geom(win, r);
|
|
out:
|
|
free(qpr);
|
|
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
|
|
DNPRINTF(SWM_D_EVENT, "move: done.\n");
|
|
}
|
|
|
|
void
|
|
move_step(struct swm_region *r, union arg *args)
|
|
{
|
|
struct ws_win *win = NULL;
|
|
|
|
if (r && r->ws && r->ws->focus)
|
|
win = r->ws->focus;
|
|
else
|
|
return;
|
|
|
|
if (!win->transient && !win->floating)
|
|
return;
|
|
|
|
move(win, args);
|
|
focus_flush();
|
|
}
|
|
|
|
/* key definitions */
|
|
struct keyfunc {
|
|
char name[SWM_FUNCNAME_LEN];
|
|
void (*func)(struct swm_region *r, union arg *);
|
|
union arg args;
|
|
} keyfuncs[KF_INVALID + 1] = {
|
|
/* name function argument */
|
|
{ "bar_toggle", bar_toggle, {.id = SWM_ARG_ID_BAR_TOGGLE} },
|
|
{ "bar_toggle_ws", bar_toggle, {.id = SWM_ARG_ID_BAR_TOGGLE_WS} },
|
|
{ "button2", pressbutton, {2} },
|
|
{ "cycle_layout", cycle_layout, {0} },
|
|
{ "flip_layout", stack_config, {.id = SWM_ARG_ID_FLIPLAYOUT} },
|
|
{ "float_toggle", floating_toggle,{0} },
|
|
{ "focus_main", focus, {.id = SWM_ARG_ID_FOCUSMAIN} },
|
|
{ "focus_next", focus, {.id = SWM_ARG_ID_FOCUSNEXT} },
|
|
{ "focus_prev", focus, {.id = SWM_ARG_ID_FOCUSPREV} },
|
|
{ "height_grow", resize_step, {.id = SWM_ARG_ID_HEIGHTGROW} },
|
|
{ "height_shrink", resize_step, {.id = SWM_ARG_ID_HEIGHTSHRINK} },
|
|
{ "iconify", iconify, {0} },
|
|
{ "master_shrink", stack_config, {.id = SWM_ARG_ID_MASTERSHRINK} },
|
|
{ "master_grow", stack_config, {.id = SWM_ARG_ID_MASTERGROW} },
|
|
{ "master_add", stack_config, {.id = SWM_ARG_ID_MASTERADD} },
|
|
{ "master_del", stack_config, {.id = SWM_ARG_ID_MASTERDEL} },
|
|
{ "move_down", move_step, {.id = SWM_ARG_ID_MOVEDOWN} },
|
|
{ "move_left", move_step, {.id = SWM_ARG_ID_MOVELEFT} },
|
|
{ "move_right", move_step, {.id = SWM_ARG_ID_MOVERIGHT} },
|
|
{ "move_up", move_step, {.id = SWM_ARG_ID_MOVEUP} },
|
|
{ "mvrg_1", send_to_rg, {.id = 0} },
|
|
{ "mvrg_2", send_to_rg, {.id = 1} },
|
|
{ "mvrg_3", send_to_rg, {.id = 2} },
|
|
{ "mvrg_4", send_to_rg, {.id = 3} },
|
|
{ "mvrg_5", send_to_rg, {.id = 4} },
|
|
{ "mvrg_6", send_to_rg, {.id = 5} },
|
|
{ "mvrg_7", send_to_rg, {.id = 6} },
|
|
{ "mvrg_8", send_to_rg, {.id = 7} },
|
|
{ "mvrg_9", send_to_rg, {.id = 8} },
|
|
{ "mvws_1", send_to_ws, {.id = 0} },
|
|
{ "mvws_2", send_to_ws, {.id = 1} },
|
|
{ "mvws_3", send_to_ws, {.id = 2} },
|
|
{ "mvws_4", send_to_ws, {.id = 3} },
|
|
{ "mvws_5", send_to_ws, {.id = 4} },
|
|
{ "mvws_6", send_to_ws, {.id = 5} },
|
|
{ "mvws_7", send_to_ws, {.id = 6} },
|
|
{ "mvws_8", send_to_ws, {.id = 7} },
|
|
{ "mvws_9", send_to_ws, {.id = 8} },
|
|
{ "mvws_10", send_to_ws, {.id = 9} },
|
|
{ "mvws_11", send_to_ws, {.id = 10} },
|
|
{ "mvws_12", send_to_ws, {.id = 11} },
|
|
{ "mvws_13", send_to_ws, {.id = 12} },
|
|
{ "mvws_14", send_to_ws, {.id = 13} },
|
|
{ "mvws_15", send_to_ws, {.id = 14} },
|
|
{ "mvws_16", send_to_ws, {.id = 15} },
|
|
{ "mvws_17", send_to_ws, {.id = 16} },
|
|
{ "mvws_18", send_to_ws, {.id = 17} },
|
|
{ "mvws_19", send_to_ws, {.id = 18} },
|
|
{ "mvws_20", send_to_ws, {.id = 19} },
|
|
{ "mvws_21", send_to_ws, {.id = 20} },
|
|
{ "mvws_22", send_to_ws, {.id = 21} },
|
|
{ "name_workspace", name_workspace, {0} },
|
|
{ "quit", quit, {0} },
|
|
{ "raise_toggle", raise_toggle, {0} },
|
|
{ "restart", restart, {0} },
|
|
{ "rg_1", focusrg, {.id = 0} },
|
|
{ "rg_2", focusrg, {.id = 1} },
|
|
{ "rg_3", focusrg, {.id = 2} },
|
|
{ "rg_4", focusrg, {.id = 3} },
|
|
{ "rg_5", focusrg, {.id = 4} },
|
|
{ "rg_6", focusrg, {.id = 5} },
|
|
{ "rg_7", focusrg, {.id = 6} },
|
|
{ "rg_8", focusrg, {.id = 7} },
|
|
{ "rg_9", focusrg, {.id = 8} },
|
|
{ "rg_next", cyclerg, {.id = SWM_ARG_ID_CYCLERG_UP} },
|
|
{ "rg_prev", cyclerg, {.id = SWM_ARG_ID_CYCLERG_DOWN} },
|
|
{ "screen_next", cyclerg, {.id = SWM_ARG_ID_CYCLERG_UP} },
|
|
{ "screen_prev", cyclerg, {.id = SWM_ARG_ID_CYCLERG_DOWN} },
|
|
{ "search_win", search_win, {0} },
|
|
{ "search_workspace", search_workspace, {0} },
|
|
{ "spawn_custom", NULL, {0} },
|
|
{ "stack_inc", stack_config, {.id = SWM_ARG_ID_STACKINC} },
|
|
{ "stack_dec", stack_config, {.id = SWM_ARG_ID_STACKDEC} },
|
|
{ "stack_reset", stack_config, {.id = SWM_ARG_ID_STACKRESET} },
|
|
{ "swap_main", swapwin, {.id = SWM_ARG_ID_SWAPMAIN} },
|
|
{ "swap_next", swapwin, {.id = SWM_ARG_ID_SWAPNEXT} },
|
|
{ "swap_prev", swapwin, {.id = SWM_ARG_ID_SWAPPREV} },
|
|
{ "uniconify", uniconify, {0} },
|
|
{ "version", version, {0} },
|
|
{ "width_grow", resize_step, {.id = SWM_ARG_ID_WIDTHGROW} },
|
|
{ "width_shrink", resize_step, {.id = SWM_ARG_ID_WIDTHSHRINK} },
|
|
{ "wind_del", wkill, {.id = SWM_ARG_ID_DELETEWINDOW} },
|
|
{ "wind_kill", wkill, {.id = SWM_ARG_ID_KILLWINDOW} },
|
|
{ "ws_1", switchws, {.id = 0} },
|
|
{ "ws_2", switchws, {.id = 1} },
|
|
{ "ws_3", switchws, {.id = 2} },
|
|
{ "ws_4", switchws, {.id = 3} },
|
|
{ "ws_5", switchws, {.id = 4} },
|
|
{ "ws_6", switchws, {.id = 5} },
|
|
{ "ws_7", switchws, {.id = 6} },
|
|
{ "ws_8", switchws, {.id = 7} },
|
|
{ "ws_9", switchws, {.id = 8} },
|
|
{ "ws_10", switchws, {.id = 9} },
|
|
{ "ws_11", switchws, {.id = 10} },
|
|
{ "ws_12", switchws, {.id = 11} },
|
|
{ "ws_13", switchws, {.id = 12} },
|
|
{ "ws_14", switchws, {.id = 13} },
|
|
{ "ws_15", switchws, {.id = 14} },
|
|
{ "ws_16", switchws, {.id = 15} },
|
|
{ "ws_17", switchws, {.id = 16} },
|
|
{ "ws_18", switchws, {.id = 17} },
|
|
{ "ws_19", switchws, {.id = 18} },
|
|
{ "ws_20", switchws, {.id = 19} },
|
|
{ "ws_21", switchws, {.id = 20} },
|
|
{ "ws_22", switchws, {.id = 21} },
|
|
{ "ws_next", cyclews, {.id = SWM_ARG_ID_CYCLEWS_UP} },
|
|
{ "ws_next_all", cyclews, {.id = SWM_ARG_ID_CYCLEWS_UP_ALL} },
|
|
{ "ws_prev", cyclews, {.id = SWM_ARG_ID_CYCLEWS_DOWN} },
|
|
{ "ws_prev_all", cyclews, {.id = SWM_ARG_ID_CYCLEWS_DOWN_ALL} },
|
|
{ "ws_prior", priorws, {0} },
|
|
{ "dumpwins", dumpwins, {0} }, /* MUST BE LAST */
|
|
{ "invalid key func", NULL, {0} },
|
|
};
|
|
|
|
int
|
|
key_cmp(struct key *kp1, struct key *kp2)
|
|
{
|
|
if (kp1->keysym < kp2->keysym)
|
|
return (-1);
|
|
if (kp1->keysym > kp2->keysym)
|
|
return (1);
|
|
|
|
if (kp1->mod < kp2->mod)
|
|
return (-1);
|
|
if (kp1->mod > kp2->mod)
|
|
return (1);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* mouse */
|
|
enum { client_click, root_click };
|
|
struct button {
|
|
unsigned int action;
|
|
unsigned int mask;
|
|
unsigned int button;
|
|
void (*func)(struct ws_win *, union arg *);
|
|
union arg args;
|
|
} buttons[] = {
|
|
#define MODKEY_SHIFT MODKEY | XCB_MOD_MASK_SHIFT
|
|
/* action key mouse button func args */
|
|
{ client_click, MODKEY, XCB_BUTTON_INDEX_3, resize, {.id = SWM_ARG_ID_DONTCENTER} },
|
|
{ client_click, MODKEY_SHIFT, XCB_BUTTON_INDEX_3, resize, {.id = SWM_ARG_ID_CENTER} },
|
|
{ client_click, MODKEY, XCB_BUTTON_INDEX_1, move, {0} },
|
|
#undef MODKEY_SHIFT
|
|
};
|
|
|
|
void
|
|
update_modkey(unsigned int mod)
|
|
{
|
|
int i;
|
|
struct key *kp;
|
|
|
|
mod_key = mod;
|
|
RB_FOREACH(kp, key_tree, &keys)
|
|
if (kp->mod & XCB_MOD_MASK_SHIFT)
|
|
kp->mod = mod | XCB_MOD_MASK_SHIFT;
|
|
else
|
|
kp->mod = mod;
|
|
|
|
for (i = 0; i < LENGTH(buttons); i++)
|
|
if (buttons[i].mask & XCB_MOD_MASK_SHIFT)
|
|
buttons[i].mask = mod | XCB_MOD_MASK_SHIFT;
|
|
else
|
|
buttons[i].mask = mod;
|
|
}
|
|
|
|
int
|
|
spawn_expand(struct swm_region *r, union arg *args, const char *spawn_name,
|
|
char ***ret_args)
|
|
{
|
|
struct spawn_prog *prog = NULL;
|
|
int i;
|
|
char *ap, **real_args;
|
|
|
|
/* suppress unused warning since var is needed */
|
|
(void)args;
|
|
|
|
DNPRINTF(SWM_D_SPAWN, "spawn_expand: %s\n", spawn_name);
|
|
|
|
/* find program */
|
|
TAILQ_FOREACH(prog, &spawns, entry) {
|
|
if (!strcasecmp(spawn_name, prog->name))
|
|
break;
|
|
}
|
|
if (prog == NULL) {
|
|
warnx("spawn_custom: program %s not found", spawn_name);
|
|
return (-1);
|
|
}
|
|
|
|
/* make room for expanded args */
|
|
if ((real_args = calloc(prog->argc + 1, sizeof(char *))) == NULL)
|
|
err(1, "spawn_custom: calloc real_args");
|
|
|
|
/* expand spawn_args into real_args */
|
|
for (i = 0; i < prog->argc; i++) {
|
|
ap = prog->argv[i];
|
|
DNPRINTF(SWM_D_SPAWN, "spawn_custom: raw arg: %s\n", ap);
|
|
if (!strcasecmp(ap, "$bar_border")) {
|
|
if ((real_args[i] =
|
|
strdup(r->s->c[SWM_S_COLOR_BAR_BORDER].name))
|
|
== NULL)
|
|
err(1, "spawn_custom border color");
|
|
} else if (!strcasecmp(ap, "$bar_color")) {
|
|
if ((real_args[i] =
|
|
strdup(r->s->c[SWM_S_COLOR_BAR].name))
|
|
== NULL)
|
|
err(1, "spawn_custom bar color");
|
|
} else if (!strcasecmp(ap, "$bar_font")) {
|
|
if ((real_args[i] = strdup(bar_fonts))
|
|
== NULL)
|
|
err(1, "spawn_custom bar fonts");
|
|
} else if (!strcasecmp(ap, "$bar_font_color")) {
|
|
if ((real_args[i] =
|
|
strdup(r->s->c[SWM_S_COLOR_BAR_FONT].name))
|
|
== NULL)
|
|
err(1, "spawn_custom color font");
|
|
} else if (!strcasecmp(ap, "$color_focus")) {
|
|
if ((real_args[i] =
|
|
strdup(r->s->c[SWM_S_COLOR_FOCUS].name))
|
|
== NULL)
|
|
err(1, "spawn_custom color focus");
|
|
} else if (!strcasecmp(ap, "$color_unfocus")) {
|
|
if ((real_args[i] =
|
|
strdup(r->s->c[SWM_S_COLOR_UNFOCUS].name))
|
|
== NULL)
|
|
err(1, "spawn_custom color unfocus");
|
|
} else if (!strcasecmp(ap, "$region_index")) {
|
|
if (asprintf(&real_args[i], "%d",
|
|
get_region_index(r) + 1) < 1)
|
|
err(1, "spawn_custom region index");
|
|
} else if (!strcasecmp(ap, "$workspace_index")) {
|
|
if (asprintf(&real_args[i], "%d", r->ws->idx + 1) < 1)
|
|
err(1, "spawn_custom workspace index");
|
|
} else {
|
|
/* no match --> copy as is */
|
|
if ((real_args[i] = strdup(ap)) == NULL)
|
|
err(1, "spawn_custom strdup(ap)");
|
|
}
|
|
DNPRINTF(SWM_D_SPAWN, "spawn_custom: cooked arg: %s\n",
|
|
real_args[i]);
|
|
}
|
|
|
|
#ifdef SWM_DEBUG
|
|
DNPRINTF(SWM_D_SPAWN, "spawn_custom: result: ");
|
|
for (i = 0; i < prog->argc; i++)
|
|
DNPRINTF(SWM_D_SPAWN, "\"%s\" ", real_args[i]);
|
|
DNPRINTF(SWM_D_SPAWN, "\n");
|
|
#endif
|
|
*ret_args = real_args;
|
|
return (prog->argc);
|
|
}
|
|
|
|
void
|
|
spawn_custom(struct swm_region *r, union arg *args, const char *spawn_name)
|
|
{
|
|
union arg a;
|
|
char **real_args;
|
|
int spawn_argc, i;
|
|
|
|
if ((spawn_argc = spawn_expand(r, args, spawn_name, &real_args)) < 0)
|
|
return;
|
|
a.argv = real_args;
|
|
if (fork() == 0)
|
|
spawn(r->ws->idx, &a, 1);
|
|
|
|
for (i = 0; i < spawn_argc; i++)
|
|
free(real_args[i]);
|
|
free(real_args);
|
|
}
|
|
|
|
void
|
|
spawn_select(struct swm_region *r, union arg *args, const char *spawn_name,
|
|
int *pid)
|
|
{
|
|
union arg a;
|
|
char **real_args;
|
|
int i, spawn_argc;
|
|
|
|
if ((spawn_argc = spawn_expand(r, args, spawn_name, &real_args)) < 0)
|
|
return;
|
|
a.argv = real_args;
|
|
|
|
if (pipe(select_list_pipe) == -1)
|
|
err(1, "pipe error");
|
|
if (pipe(select_resp_pipe) == -1)
|
|
err(1, "pipe error");
|
|
|
|
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
|
|
err(1, "could not disable SIGPIPE");
|
|
switch (*pid = fork()) {
|
|
case -1:
|
|
err(1, "cannot fork");
|
|
break;
|
|
case 0: /* child */
|
|
if (dup2(select_list_pipe[0], STDIN_FILENO) == -1)
|
|
err(1, "dup2");
|
|
if (dup2(select_resp_pipe[1], STDOUT_FILENO) == -1)
|
|
err(1, "dup2");
|
|
close(select_list_pipe[1]);
|
|
close(select_resp_pipe[0]);
|
|
spawn(r->ws->idx, &a, 0);
|
|
break;
|
|
default: /* parent */
|
|
close(select_list_pipe[0]);
|
|
close(select_resp_pipe[1]);
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < spawn_argc; i++)
|
|
free(real_args[i]);
|
|
free(real_args);
|
|
}
|
|
|
|
void
|
|
spawn_insert(const char *name, const char *args, int flags)
|
|
{
|
|
char *arg, *cp, *ptr;
|
|
struct spawn_prog *sp;
|
|
|
|
DNPRINTF(SWM_D_SPAWN, "spawn_insert: %s\n", name);
|
|
|
|
if ((sp = calloc(1, sizeof *sp)) == NULL)
|
|
err(1, "spawn_insert: calloc");
|
|
if ((sp->name = strdup(name)) == NULL)
|
|
err(1, "spawn_insert: strdup");
|
|
|
|
/* convert the arguments to an argument list */
|
|
if ((ptr = cp = strdup(args)) == NULL)
|
|
err(1, "spawn_insert: strdup");
|
|
while ((arg = strsep(&ptr, " \t")) != NULL) {
|
|
/* empty field; skip it */
|
|
if (*arg == '\0')
|
|
continue;
|
|
|
|
sp->argc++;
|
|
if ((sp->argv = realloc(sp->argv, sp->argc *
|
|
sizeof *sp->argv)) == NULL)
|
|
err(1, "spawn_insert: realloc");
|
|
if ((sp->argv[sp->argc - 1] = strdup(arg)) == NULL)
|
|
err(1, "spawn_insert: strdup");
|
|
}
|
|
free(cp);
|
|
|
|
sp->flags = flags;
|
|
|
|
TAILQ_INSERT_TAIL(&spawns, sp, entry);
|
|
DNPRINTF(SWM_D_SPAWN, "spawn_insert: leave\n");
|
|
}
|
|
|
|
void
|
|
spawn_remove(struct spawn_prog *sp)
|
|
{
|
|
int i;
|
|
|
|
DNPRINTF(SWM_D_SPAWN, "spawn_remove: %s\n", sp->name);
|
|
|
|
TAILQ_REMOVE(&spawns, sp, entry);
|
|
for (i = 0; i < sp->argc; i++)
|
|
free(sp->argv[i]);
|
|
free(sp->argv);
|
|
free(sp->name);
|
|
free(sp);
|
|
|
|
DNPRINTF(SWM_D_SPAWN, "spawn_remove: leave\n");
|
|
}
|
|
|
|
void
|
|
setspawn(const char *name, const char *args, int flags)
|
|
{
|
|
struct spawn_prog *sp;
|
|
|
|
DNPRINTF(SWM_D_SPAWN, "setspawn: %s\n", name);
|
|
|
|
if (name == NULL)
|
|
return;
|
|
|
|
/* Remove any old spawn under the same name. */
|
|
TAILQ_FOREACH(sp, &spawns, entry)
|
|
if (!strcmp(sp->name, name)) {
|
|
spawn_remove(sp);
|
|
break;
|
|
}
|
|
|
|
if (*args != '\0')
|
|
spawn_insert(name, args, flags);
|
|
else
|
|
warnx("error: setspawn: cannot find program: %s", name);
|
|
|
|
DNPRINTF(SWM_D_SPAWN, "setspawn: leave\n");
|
|
}
|
|
|
|
int
|
|
setconfspawn(char *selector, char *value, int flags)
|
|
{
|
|
char *args;
|
|
|
|
args = expand_tilde(value);
|
|
|
|
DNPRINTF(SWM_D_SPAWN, "setconfspawn: [%s] [%s]\n", selector, args);
|
|
|
|
setspawn(selector, args, flags);
|
|
free(args);
|
|
|
|
DNPRINTF(SWM_D_SPAWN, "setconfspawn: done.\n");
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
validate_spawns(void)
|
|
{
|
|
struct spawn_prog *sp;
|
|
char which[PATH_MAX];
|
|
size_t i;
|
|
|
|
struct key *kp;
|
|
|
|
RB_FOREACH(kp, key_tree, &keys) {
|
|
if (kp->funcid != KF_SPAWN_CUSTOM)
|
|
continue;
|
|
|
|
/* find program */
|
|
TAILQ_FOREACH(sp, &spawns, entry) {
|
|
if (!strcasecmp(kp->spawn_name, sp->name))
|
|
break;
|
|
}
|
|
|
|
if (sp == NULL || sp->flags & SWM_SPAWN_OPTIONAL)
|
|
continue;
|
|
|
|
/* verify we have the goods */
|
|
snprintf(which, sizeof which, "which %s", sp->argv[0]);
|
|
DNPRINTF(SWM_D_CONF, "validate_spawns: which %s\n",
|
|
sp->argv[0]);
|
|
for (i = strlen("which "); i < strlen(which); i++)
|
|
if (which[i] == ' ') {
|
|
which[i] = '\0';
|
|
break;
|
|
}
|
|
if (system(which) != 0)
|
|
add_startup_exception("could not find %s",
|
|
&which[strlen("which ")]);
|
|
}
|
|
}
|
|
|
|
void
|
|
setup_spawn(void)
|
|
{
|
|
setconfspawn("lock", "xlock", 0);
|
|
|
|
setconfspawn("term", "xterm", 0);
|
|
setconfspawn("spawn_term", "xterm", 0);
|
|
|
|
setconfspawn("menu", "dmenu_run"
|
|
" -fn $bar_font"
|
|
" -nb $bar_color"
|
|
" -nf $bar_font_color"
|
|
" -sb $bar_border"
|
|
" -sf $bar_color", 0);
|
|
|
|
setconfspawn("search", "dmenu"
|
|
" -i"
|
|
" -fn $bar_font"
|
|
" -nb $bar_color"
|
|
" -nf $bar_font_color"
|
|
" -sb $bar_border"
|
|
" -sf $bar_color", 0);
|
|
|
|
setconfspawn("name_workspace", "dmenu"
|
|
" -p Workspace"
|
|
" -fn $bar_font"
|
|
" -nb $bar_color"
|
|
" -nf $bar_font_color"
|
|
" -sb $bar_border"
|
|
" -sf $bar_color", 0);
|
|
|
|
/* These are not verified for existence, even with a binding set. */
|
|
setconfspawn("screenshot_all", "screenshot.sh full", SWM_SPAWN_OPTIONAL);
|
|
setconfspawn("screenshot_wind", "screenshot.sh window", SWM_SPAWN_OPTIONAL);
|
|
setconfspawn("initscr", "initscreen.sh", SWM_SPAWN_OPTIONAL);
|
|
}
|
|
|
|
/* key bindings */
|
|
#define SWM_MODNAME_SIZE 32
|
|
#define SWM_KEY_WS "\n+ \t"
|
|
int
|
|
parsekeys(char *keystr, unsigned int currmod, unsigned int *mod, KeySym *ks)
|
|
{
|
|
char *cp, *name;
|
|
KeySym uks;
|
|
DNPRINTF(SWM_D_KEY, "parsekeys: enter [%s]\n", keystr);
|
|
if (mod == NULL || ks == NULL) {
|
|
DNPRINTF(SWM_D_KEY, "parsekeys: no mod or key vars\n");
|
|
return (1);
|
|
}
|
|
if (keystr == NULL || strlen(keystr) == 0) {
|
|
DNPRINTF(SWM_D_KEY, "parsekeys: no keystr\n");
|
|
return (1);
|
|
}
|
|
cp = keystr;
|
|
*ks = XCB_NO_SYMBOL;
|
|
*mod = 0;
|
|
while ((name = strsep(&cp, SWM_KEY_WS)) != NULL) {
|
|
DNPRINTF(SWM_D_KEY, "parsekeys: key [%s]\n", name);
|
|
if (cp)
|
|
cp += (long)strspn(cp, SWM_KEY_WS);
|
|
if (strncasecmp(name, "MOD", SWM_MODNAME_SIZE) == 0)
|
|
*mod |= currmod;
|
|
else if (!strncasecmp(name, "Mod1", SWM_MODNAME_SIZE))
|
|
*mod |= XCB_MOD_MASK_1;
|
|
else if (!strncasecmp(name, "Mod2", SWM_MODNAME_SIZE))
|
|
*mod += XCB_MOD_MASK_2;
|
|
else if (!strncmp(name, "Mod3", SWM_MODNAME_SIZE))
|
|
*mod |= XCB_MOD_MASK_3;
|
|
else if (!strncmp(name, "Mod4", SWM_MODNAME_SIZE))
|
|
*mod |= XCB_MOD_MASK_4;
|
|
else if (strncasecmp(name, "SHIFT", SWM_MODNAME_SIZE) == 0)
|
|
*mod |= XCB_MOD_MASK_SHIFT;
|
|
else if (strncasecmp(name, "CONTROL", SWM_MODNAME_SIZE) == 0)
|
|
*mod |= XCB_MOD_MASK_CONTROL;
|
|
else {
|
|
*ks = XStringToKeysym(name);
|
|
XConvertCase(*ks, ks, &uks);
|
|
if (ks == XCB_NO_SYMBOL) {
|
|
DNPRINTF(SWM_D_KEY,
|
|
"parsekeys: invalid key %s\n",
|
|
name);
|
|
return (1);
|
|
}
|
|
}
|
|
}
|
|
DNPRINTF(SWM_D_KEY, "parsekeys: leave ok\n");
|
|
return (0);
|
|
}
|
|
|
|
char *
|
|
strdupsafe(const char *str)
|
|
{
|
|
if (str == NULL)
|
|
return (NULL);
|
|
else
|
|
return (strdup(str));
|
|
}
|
|
|
|
void
|
|
key_insert(unsigned int mod, KeySym ks, enum keyfuncid kfid,
|
|
const char *spawn_name)
|
|
{
|
|
struct key *kp;
|
|
|
|
DNPRINTF(SWM_D_KEY, "key_insert: enter %s [%s]\n",
|
|
keyfuncs[kfid].name, spawn_name);
|
|
|
|
if ((kp = malloc(sizeof *kp)) == NULL)
|
|
err(1, "key_insert: malloc");
|
|
|
|
kp->mod = mod;
|
|
kp->keysym = ks;
|
|
kp->funcid = kfid;
|
|
kp->spawn_name = strdupsafe(spawn_name);
|
|
RB_INSERT(key_tree, &keys, kp);
|
|
|
|
DNPRINTF(SWM_D_KEY, "key_insert: leave\n");
|
|
}
|
|
|
|
struct key *
|
|
key_lookup(unsigned int mod, KeySym ks)
|
|
{
|
|
struct key kp;
|
|
|
|
kp.keysym = ks;
|
|
kp.mod = mod;
|
|
|
|
return (RB_FIND(key_tree, &keys, &kp));
|
|
}
|
|
|
|
void
|
|
key_remove(struct key *kp)
|
|
{
|
|
DNPRINTF(SWM_D_KEY, "key_remove: %s\n", keyfuncs[kp->funcid].name);
|
|
|
|
RB_REMOVE(key_tree, &keys, kp);
|
|
free(kp->spawn_name);
|
|
free(kp);
|
|
|
|
DNPRINTF(SWM_D_KEY, "key_remove: leave\n");
|
|
}
|
|
|
|
void
|
|
key_replace(struct key *kp, unsigned int mod, KeySym ks, enum keyfuncid kfid,
|
|
const char *spawn_name)
|
|
{
|
|
DNPRINTF(SWM_D_KEY, "key_replace: %s [%s]\n", keyfuncs[kp->funcid].name,
|
|
spawn_name);
|
|
|
|
key_remove(kp);
|
|
key_insert(mod, ks, kfid, spawn_name);
|
|
|
|
DNPRINTF(SWM_D_KEY, "key_replace: leave\n");
|
|
}
|
|
|
|
void
|
|
setkeybinding(unsigned int mod, KeySym ks, enum keyfuncid kfid,
|
|
const char *spawn_name)
|
|
{
|
|
struct key *kp;
|
|
|
|
DNPRINTF(SWM_D_KEY, "setkeybinding: enter %s [%s]\n",
|
|
keyfuncs[kfid].name, spawn_name);
|
|
|
|
if ((kp = key_lookup(mod, ks)) != NULL) {
|
|
if (kfid == KF_INVALID)
|
|
key_remove(kp);
|
|
else
|
|
key_replace(kp, mod, ks, kfid, spawn_name);
|
|
DNPRINTF(SWM_D_KEY, "setkeybinding: leave\n");
|
|
return;
|
|
}
|
|
if (kfid == KF_INVALID) {
|
|
warnx("bind: Key combination already unbound.");
|
|
DNPRINTF(SWM_D_KEY, "setkeybinding: leave\n");
|
|
return;
|
|
}
|
|
|
|
key_insert(mod, ks, kfid, spawn_name);
|
|
DNPRINTF(SWM_D_KEY, "setkeybinding: leave\n");
|
|
}
|
|
|
|
int
|
|
setconfbinding(char *selector, char *value, int flags)
|
|
{
|
|
enum keyfuncid kfid;
|
|
unsigned int mod;
|
|
KeySym ks;
|
|
struct spawn_prog *sp;
|
|
|
|
/* suppress unused warning since var is needed */
|
|
(void)flags;
|
|
|
|
DNPRINTF(SWM_D_KEY, "setconfbinding: enter\n");
|
|
if (selector == NULL) {
|
|
DNPRINTF(SWM_D_KEY, "setconfbinding: unbind %s\n", value);
|
|
if (parsekeys(value, mod_key, &mod, &ks) == 0) {
|
|
kfid = KF_INVALID;
|
|
setkeybinding(mod, ks, kfid, NULL);
|
|
return (0);
|
|
} else
|
|
return (1);
|
|
}
|
|
/* search by key function name */
|
|
for (kfid = 0; kfid < KF_INVALID; (kfid)++) {
|
|
if (strncasecmp(selector, keyfuncs[kfid].name,
|
|
SWM_FUNCNAME_LEN) == 0) {
|
|
DNPRINTF(SWM_D_KEY, "setconfbinding: %s: match "
|
|
"keyfunc\n", selector);
|
|
if (parsekeys(value, mod_key, &mod, &ks) == 0) {
|
|
setkeybinding(mod, ks, kfid, NULL);
|
|
return (0);
|
|
} else
|
|
return (1);
|
|
}
|
|
}
|
|
/* search by custom spawn name */
|
|
TAILQ_FOREACH(sp, &spawns, entry) {
|
|
if (strcasecmp(selector, sp->name) == 0) {
|
|
DNPRINTF(SWM_D_KEY, "setconfbinding: %s: match "
|
|
"spawn\n", selector);
|
|
if (parsekeys(value, mod_key, &mod, &ks) == 0) {
|
|
setkeybinding(mod, ks, KF_SPAWN_CUSTOM,
|
|
sp->name);
|
|
return (0);
|
|
} else
|
|
return (1);
|
|
}
|
|
}
|
|
DNPRINTF(SWM_D_KEY, "setconfbinding: no match\n");
|
|
return (1);
|
|
}
|
|
|
|
void
|
|
setup_keys(void)
|
|
{
|
|
#define MODKEY_SHIFT MODKEY | XCB_MOD_MASK_SHIFT
|
|
setkeybinding(MODKEY, XK_b, KF_BAR_TOGGLE, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_b, KF_BAR_TOGGLE_WS,NULL);
|
|
setkeybinding(MODKEY, XK_v, KF_BUTTON2, NULL);
|
|
setkeybinding(MODKEY, XK_space, KF_CYCLE_LAYOUT,NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_backslash, KF_FLIP_LAYOUT, NULL);
|
|
setkeybinding(MODKEY, XK_t, KF_FLOAT_TOGGLE,NULL);
|
|
setkeybinding(MODKEY, XK_m, KF_FOCUS_MAIN, NULL);
|
|
setkeybinding(MODKEY, XK_j, KF_FOCUS_NEXT, NULL);
|
|
setkeybinding(MODKEY, XK_Tab, KF_FOCUS_NEXT, NULL);
|
|
setkeybinding(MODKEY, XK_k, KF_FOCUS_PREV, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_Tab, KF_FOCUS_PREV, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_equal, KF_HEIGHT_GROW,NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_minus, KF_HEIGHT_SHRINK,NULL);
|
|
setkeybinding(MODKEY, XK_w, KF_ICONIFY, NULL);
|
|
setkeybinding(MODKEY, XK_h, KF_MASTER_SHRINK, NULL);
|
|
setkeybinding(MODKEY, XK_l, KF_MASTER_GROW, NULL);
|
|
setkeybinding(MODKEY, XK_comma, KF_MASTER_ADD, NULL);
|
|
setkeybinding(MODKEY, XK_period, KF_MASTER_DEL, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_bracketright,KF_MOVE_DOWN,NULL);
|
|
setkeybinding(MODKEY, XK_bracketleft, KF_MOVE_LEFT,NULL);
|
|
setkeybinding(MODKEY, XK_bracketright,KF_MOVE_RIGHT,NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_bracketleft, KF_MOVE_UP, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_KP_End, KF_MVRG_1, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_KP_Down, KF_MVRG_2, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_KP_Next, KF_MVRG_3, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_KP_Left, KF_MVRG_4, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_KP_Begin, KF_MVRG_5, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_KP_Right, KF_MVRG_6, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_KP_Home, KF_MVRG_7, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_KP_Up, KF_MVRG_8, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_KP_Prior, KF_MVRG_9, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_1, KF_MVWS_1, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_2, KF_MVWS_2, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_3, KF_MVWS_3, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_4, KF_MVWS_4, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_5, KF_MVWS_5, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_6, KF_MVWS_6, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_7, KF_MVWS_7, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_8, KF_MVWS_8, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_9, KF_MVWS_9, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_0, KF_MVWS_10, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_F1, KF_MVWS_11, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_F2, KF_MVWS_12, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_F3, KF_MVWS_13, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_F4, KF_MVWS_14, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_F5, KF_MVWS_15, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_F6, KF_MVWS_16, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_F7, KF_MVWS_17, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_F8, KF_MVWS_18, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_F9, KF_MVWS_19, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_F10, KF_MVWS_20, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_F11, KF_MVWS_21, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_F12, KF_MVWS_22, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_slash, KF_NAME_WORKSPACE,NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_q, KF_QUIT, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_r, KF_RAISE_TOGGLE,NULL);
|
|
setkeybinding(MODKEY, XK_q, KF_RESTART, NULL);
|
|
setkeybinding(MODKEY, XK_KP_End, KF_RG_1, NULL);
|
|
setkeybinding(MODKEY, XK_KP_Down, KF_RG_2, NULL);
|
|
setkeybinding(MODKEY, XK_KP_Next, KF_RG_3, NULL);
|
|
setkeybinding(MODKEY, XK_KP_Left, KF_RG_4, NULL);
|
|
setkeybinding(MODKEY, XK_KP_Begin, KF_RG_5, NULL);
|
|
setkeybinding(MODKEY, XK_KP_Right, KF_RG_6, NULL);
|
|
setkeybinding(MODKEY, XK_KP_Home, KF_RG_7, NULL);
|
|
setkeybinding(MODKEY, XK_KP_Up, KF_RG_8, NULL);
|
|
setkeybinding(MODKEY, XK_KP_Prior, KF_RG_9, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_Right, KF_RG_NEXT, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_Left, KF_RG_PREV, NULL);
|
|
setkeybinding(MODKEY, XK_f, KF_SEARCH_WIN, NULL);
|
|
setkeybinding(MODKEY, XK_slash, KF_SEARCH_WORKSPACE,NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_i, KF_SPAWN_CUSTOM,"initscr");
|
|
setkeybinding(MODKEY_SHIFT, XK_Delete, KF_SPAWN_CUSTOM,"lock");
|
|
setkeybinding(MODKEY, XK_p, KF_SPAWN_CUSTOM,"menu");
|
|
setkeybinding(MODKEY, XK_s, KF_SPAWN_CUSTOM,"screenshot_all");
|
|
setkeybinding(MODKEY_SHIFT, XK_s, KF_SPAWN_CUSTOM,"screenshot_wind");
|
|
setkeybinding(MODKEY_SHIFT, XK_Return, KF_SPAWN_CUSTOM,"term");
|
|
setkeybinding(MODKEY_SHIFT, XK_comma, KF_STACK_INC, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_period, KF_STACK_DEC, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_space, KF_STACK_RESET, NULL);
|
|
setkeybinding(MODKEY, XK_Return, KF_SWAP_MAIN, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_j, KF_SWAP_NEXT, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_k, KF_SWAP_PREV, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_w, KF_UNICONIFY, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_v, KF_VERSION, NULL);
|
|
setkeybinding(MODKEY, XK_equal, KF_WIDTH_GROW, NULL);
|
|
setkeybinding(MODKEY, XK_minus, KF_WIDTH_SHRINK,NULL);
|
|
setkeybinding(MODKEY, XK_x, KF_WIND_DEL, NULL);
|
|
setkeybinding(MODKEY_SHIFT, XK_x, KF_WIND_KILL, NULL);
|
|
setkeybinding(MODKEY, XK_1, KF_WS_1, NULL);
|
|
setkeybinding(MODKEY, XK_2, KF_WS_2, NULL);
|
|
setkeybinding(MODKEY, XK_3, KF_WS_3, NULL);
|
|
setkeybinding(MODKEY, XK_4, KF_WS_4, NULL);
|
|
setkeybinding(MODKEY, XK_5, KF_WS_5, NULL);
|
|
setkeybinding(MODKEY, XK_6, KF_WS_6, NULL);
|
|
setkeybinding(MODKEY, XK_7, KF_WS_7, NULL);
|
|
setkeybinding(MODKEY, XK_8, KF_WS_8, NULL);
|
|
setkeybinding(MODKEY, XK_9, KF_WS_9, NULL);
|
|
setkeybinding(MODKEY, XK_0, KF_WS_10, NULL);
|
|
setkeybinding(MODKEY, XK_F1, KF_WS_11, NULL);
|
|
setkeybinding(MODKEY, XK_F2, KF_WS_12, NULL);
|
|
setkeybinding(MODKEY, XK_F3, KF_WS_13, NULL);
|
|
setkeybinding(MODKEY, XK_F4, KF_WS_14, NULL);
|
|
setkeybinding(MODKEY, XK_F5, KF_WS_15, NULL);
|
|
setkeybinding(MODKEY, XK_F6, KF_WS_16, NULL);
|
|
setkeybinding(MODKEY, XK_F7, KF_WS_17, NULL);
|
|
setkeybinding(MODKEY, XK_F8, KF_WS_18, NULL);
|
|
setkeybinding(MODKEY, XK_F9, KF_WS_19, NULL);
|
|
setkeybinding(MODKEY, XK_F10, KF_WS_20, NULL);
|
|
setkeybinding(MODKEY, XK_F11, KF_WS_21, NULL);
|
|
setkeybinding(MODKEY, XK_F12, KF_WS_22, NULL);
|
|
setkeybinding(MODKEY, XK_Right, KF_WS_NEXT, NULL);
|
|
setkeybinding(MODKEY, XK_Left, KF_WS_PREV, NULL);
|
|
setkeybinding(MODKEY, XK_Up, KF_WS_NEXT_ALL, NULL);
|
|
setkeybinding(MODKEY, XK_Down, KF_WS_PREV_ALL, NULL);
|
|
setkeybinding(MODKEY, XK_a, KF_WS_PRIOR, NULL);
|
|
#ifdef SWM_DEBUG
|
|
setkeybinding(MODKEY_SHIFT, XK_d, KF_DUMPWINS, NULL);
|
|
#endif
|
|
#undef MODKEY_SHIFT
|
|
}
|
|
|
|
void
|
|
clear_keys(void)
|
|
{
|
|
struct key *kp;
|
|
|
|
while (RB_EMPTY(&keys) == 0) {
|
|
kp = RB_ROOT(&keys);
|
|
key_remove(kp);
|
|
}
|
|
}
|
|
|
|
int
|
|
setkeymapping(char *selector, char *value, int flags)
|
|
{
|
|
char *keymapping_file;
|
|
|
|
/* suppress unused warnings since vars are needed */
|
|
(void)selector;
|
|
(void)flags;
|
|
|
|
DNPRINTF(SWM_D_KEY, "setkeymapping: enter\n");
|
|
|
|
keymapping_file = expand_tilde(value);
|
|
|
|
clear_keys();
|
|
/* load new key bindings; if it fails, revert to default bindings */
|
|
if (conf_load(keymapping_file, SWM_CONF_KEYMAPPING)) {
|
|
clear_keys();
|
|
setup_keys();
|
|
}
|
|
|
|
free(keymapping_file);
|
|
|
|
DNPRINTF(SWM_D_KEY, "setkeymapping: leave\n");
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
updatenumlockmask(void)
|
|
{
|
|
unsigned int i, j;
|
|
xcb_get_modifier_mapping_reply_t *modmap_r;
|
|
xcb_keycode_t *modmap, kc, *keycode;
|
|
|
|
numlockmask = 0;
|
|
|
|
modmap_r = xcb_get_modifier_mapping_reply(conn,
|
|
xcb_get_modifier_mapping(conn),
|
|
NULL);
|
|
if (modmap_r) {
|
|
modmap = xcb_get_modifier_mapping_keycodes(modmap_r);
|
|
for (i = 0; i < 8; i++) {
|
|
for (j = 0; j < modmap_r->keycodes_per_modifier; j++) {
|
|
kc = modmap[i * modmap_r->keycodes_per_modifier
|
|
+ j];
|
|
keycode = xcb_key_symbols_get_keycode(syms,
|
|
XK_Num_Lock);
|
|
if (kc == *keycode)
|
|
numlockmask = (1 << i);
|
|
free(keycode);
|
|
}
|
|
}
|
|
free(modmap_r);
|
|
}
|
|
DNPRINTF(SWM_D_MISC, "updatenumlockmask: %d\n", numlockmask);
|
|
}
|
|
|
|
void
|
|
grabkeys(void)
|
|
{
|
|
struct key *kp;
|
|
int num_screens, k, j;
|
|
unsigned int modifiers[4];
|
|
xcb_keycode_t *code;
|
|
|
|
DNPRINTF(SWM_D_MISC, "grabkeys\n");
|
|
updatenumlockmask();
|
|
|
|
modifiers[0] = 0;
|
|
modifiers[1] = numlockmask;
|
|
modifiers[2] = XCB_MOD_MASK_LOCK;
|
|
modifiers[3] = numlockmask | XCB_MOD_MASK_LOCK;
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (k = 0; k < num_screens; k++) {
|
|
if (TAILQ_EMPTY(&screens[k].rl))
|
|
continue;
|
|
xcb_ungrab_key(conn, XCB_GRAB_ANY, screens[k].root,
|
|
XCB_MOD_MASK_ANY);
|
|
RB_FOREACH(kp, key_tree, &keys) {
|
|
if ((code = xcb_key_symbols_get_keycode(syms,
|
|
kp->keysym))) {
|
|
for (j = 0; j < LENGTH(modifiers); j++)
|
|
xcb_grab_key(conn, 1,
|
|
screens[k].root,
|
|
kp->mod | modifiers[j],
|
|
*code, XCB_GRAB_MODE_SYNC,
|
|
XCB_GRAB_MODE_SYNC);
|
|
free(code);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
grabbuttons(struct ws_win *win)
|
|
{
|
|
unsigned int modifiers[4];
|
|
int i, j;
|
|
|
|
DNPRINTF(SWM_D_MOUSE, "grabbuttons: win 0x%x\n", win->id);
|
|
updatenumlockmask();
|
|
|
|
modifiers[0] = 0;
|
|
modifiers[1] = numlockmask;
|
|
modifiers[2] = XCB_MOD_MASK_LOCK;
|
|
modifiers[3] = numlockmask | XCB_MOD_MASK_LOCK;
|
|
|
|
for (i = 0; i < LENGTH(buttons); i++)
|
|
if (buttons[i].action == client_click)
|
|
for (j = 0; j < LENGTH(modifiers); ++j)
|
|
xcb_grab_button(conn, 0, win->id, BUTTONMASK,
|
|
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC,
|
|
XCB_WINDOW_NONE, XCB_CURSOR_NONE,
|
|
buttons[i].button, buttons[i].mask |
|
|
modifiers[j]);
|
|
}
|
|
|
|
const char *quirkname[] = {
|
|
"NONE", /* config string for "no value" */
|
|
"FLOAT",
|
|
"TRANSSZ",
|
|
"ANYWHERE",
|
|
"XTERM_FONTADJ",
|
|
"FULLSCREEN",
|
|
"FOCUSPREV",
|
|
};
|
|
|
|
/* SWM_Q_WS: retain '|' for back compat for now (2009-08-11) */
|
|
#define SWM_Q_WS "\n|+ \t"
|
|
int
|
|
parsequirks(char *qstr, unsigned long *quirk)
|
|
{
|
|
char *cp, *name;
|
|
int i;
|
|
|
|
if (quirk == NULL)
|
|
return (1);
|
|
|
|
cp = qstr;
|
|
*quirk = 0;
|
|
while ((name = strsep(&cp, SWM_Q_WS)) != NULL) {
|
|
if (cp)
|
|
cp += (long)strspn(cp, SWM_Q_WS);
|
|
for (i = 0; i < LENGTH(quirkname); i++) {
|
|
if (!strncasecmp(name, quirkname[i], SWM_QUIRK_LEN)) {
|
|
DNPRINTF(SWM_D_QUIRK,
|
|
"parsequirks: %s\n", name);
|
|
if (i == 0) {
|
|
*quirk = 0;
|
|
return (0);
|
|
}
|
|
*quirk |= 1 << (i-1);
|
|
break;
|
|
}
|
|
}
|
|
if (i >= LENGTH(quirkname)) {
|
|
DNPRINTF(SWM_D_QUIRK,
|
|
"parsequirks: invalid quirk [%s]\n", name);
|
|
return (1);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
quirk_insert(const char *class, const char *name, unsigned long quirk)
|
|
{
|
|
struct quirk *qp;
|
|
|
|
DNPRINTF(SWM_D_QUIRK, "quirk_insert: %s:%s [%lu]\n", class, name,
|
|
quirk);
|
|
|
|
if ((qp = malloc(sizeof *qp)) == NULL)
|
|
err(1, "quirk_insert: malloc");
|
|
if ((qp->class = strdup(class)) == NULL)
|
|
err(1, "quirk_insert: strdup");
|
|
if ((qp->name = strdup(name)) == NULL)
|
|
err(1, "quirk_insert: strdup");
|
|
|
|
qp->quirk = quirk;
|
|
TAILQ_INSERT_TAIL(&quirks, qp, entry);
|
|
|
|
DNPRINTF(SWM_D_QUIRK, "quirk_insert: leave\n");
|
|
}
|
|
|
|
void
|
|
quirk_remove(struct quirk *qp)
|
|
{
|
|
DNPRINTF(SWM_D_QUIRK, "quirk_remove: %s:%s [%lu]\n", qp->class,
|
|
qp->name, qp->quirk);
|
|
|
|
TAILQ_REMOVE(&quirks, qp, entry);
|
|
free(qp->class);
|
|
free(qp->name);
|
|
free(qp);
|
|
|
|
DNPRINTF(SWM_D_QUIRK, "quirk_remove: leave\n");
|
|
}
|
|
|
|
void
|
|
quirk_replace(struct quirk *qp, const char *class, const char *name,
|
|
unsigned long quirk)
|
|
{
|
|
DNPRINTF(SWM_D_QUIRK, "quirk_replace: %s:%s [%lu]\n", qp->class,
|
|
qp->name, qp->quirk);
|
|
|
|
quirk_remove(qp);
|
|
quirk_insert(class, name, quirk);
|
|
|
|
DNPRINTF(SWM_D_QUIRK, "quirk_replace: leave\n");
|
|
}
|
|
|
|
void
|
|
setquirk(const char *class, const char *name, unsigned long quirk)
|
|
{
|
|
struct quirk *qp;
|
|
|
|
DNPRINTF(SWM_D_QUIRK, "setquirk: enter %s:%s [%lu]\n", class, name,
|
|
quirk);
|
|
|
|
/* Remove/replace existing quirk. */
|
|
TAILQ_FOREACH(qp, &quirks, entry) {
|
|
if (!strcmp(qp->class, class) && !strcmp(qp->name, name)) {
|
|
if (!quirk)
|
|
quirk_remove(qp);
|
|
else
|
|
quirk_replace(qp, class, name, quirk);
|
|
DNPRINTF(SWM_D_QUIRK, "setquirk: leave\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Only insert if quirk is not NONE. */
|
|
if (quirk)
|
|
quirk_insert(class, name, quirk);
|
|
|
|
DNPRINTF(SWM_D_QUIRK, "setquirk: leave\n");
|
|
}
|
|
|
|
int
|
|
setconfquirk(char *selector, char *value, int flags)
|
|
{
|
|
char *cp, *class, *name;
|
|
int retval;
|
|
unsigned long qrks;
|
|
|
|
/* suppress unused warning since var is needed */
|
|
(void)flags;
|
|
|
|
if (selector == NULL)
|
|
return (0);
|
|
if ((cp = strchr(selector, ':')) == NULL)
|
|
return (0);
|
|
*cp = '\0';
|
|
class = selector;
|
|
name = cp + 1;
|
|
if ((retval = parsequirks(value, &qrks)) == 0)
|
|
setquirk(class, name, qrks);
|
|
return (retval);
|
|
}
|
|
|
|
void
|
|
setup_quirks(void)
|
|
{
|
|
setquirk("MPlayer", "xv", SWM_Q_FLOAT | SWM_Q_FULLSCREEN | SWM_Q_FOCUSPREV);
|
|
setquirk("OpenOffice.org 3.2", "VCLSalFrame", SWM_Q_FLOAT);
|
|
setquirk("Firefox-bin", "firefox-bin", SWM_Q_TRANSSZ);
|
|
setquirk("Firefox", "Dialog", SWM_Q_FLOAT);
|
|
setquirk("Gimp", "gimp", SWM_Q_FLOAT | SWM_Q_ANYWHERE);
|
|
setquirk("XTerm", "xterm", SWM_Q_XTERM_FONTADJ);
|
|
setquirk("xine", "Xine Window", SWM_Q_FLOAT | SWM_Q_ANYWHERE);
|
|
setquirk("Xitk", "Xitk Combo", SWM_Q_FLOAT | SWM_Q_ANYWHERE);
|
|
setquirk("xine", "xine Panel", SWM_Q_FLOAT | SWM_Q_ANYWHERE);
|
|
setquirk("Xitk", "Xine Window", SWM_Q_FLOAT | SWM_Q_ANYWHERE);
|
|
setquirk("xine", "xine Video Fullscreen Window", SWM_Q_FULLSCREEN | SWM_Q_FLOAT);
|
|
setquirk("pcb", "pcb", SWM_Q_FLOAT);
|
|
setquirk("SDL_App", "SDL_App", SWM_Q_FLOAT | SWM_Q_FULLSCREEN);
|
|
}
|
|
|
|
/* conf file stuff */
|
|
#define SWM_CONF_FILE "spectrwm.conf"
|
|
#define SWM_CONF_FILE_OLD "scrotwm.conf"
|
|
|
|
enum {
|
|
SWM_S_BAR_ACTION,
|
|
SWM_S_BAR_AT_BOTTOM,
|
|
SWM_S_BAR_BORDER_WIDTH,
|
|
SWM_S_BAR_DELAY,
|
|
SWM_S_BAR_ENABLED,
|
|
SWM_S_BAR_ENABLED_WS,
|
|
SWM_S_BAR_FONT,
|
|
SWM_S_BAR_FORMAT,
|
|
SWM_S_BAR_JUSTIFY,
|
|
SWM_S_BORDER_WIDTH,
|
|
SWM_S_CLOCK_ENABLED,
|
|
SWM_S_CLOCK_FORMAT,
|
|
SWM_S_CYCLE_EMPTY,
|
|
SWM_S_CYCLE_VISIBLE,
|
|
SWM_S_DIALOG_RATIO,
|
|
SWM_S_DISABLE_BORDER,
|
|
SWM_S_FOCUS_CLOSE,
|
|
SWM_S_FOCUS_CLOSE_WRAP,
|
|
SWM_S_FOCUS_DEFAULT,
|
|
SWM_S_FOCUS_MODE,
|
|
SWM_S_REGION_PADDING,
|
|
SWM_S_SPAWN_ORDER,
|
|
SWM_S_SPAWN_TERM,
|
|
SWM_S_SS_APP,
|
|
SWM_S_SS_ENABLED,
|
|
SWM_S_STACK_ENABLED,
|
|
SWM_S_TERM_WIDTH,
|
|
SWM_S_TILE_GAP,
|
|
SWM_S_TITLE_CLASS_ENABLED,
|
|
SWM_S_TITLE_NAME_ENABLED,
|
|
SWM_S_URGENT_ENABLED,
|
|
SWM_S_VERBOSE_LAYOUT,
|
|
SWM_S_WINDOW_NAME_ENABLED,
|
|
SWM_S_WORKSPACE_LIMIT
|
|
};
|
|
|
|
int
|
|
setconfvalue(char *selector, char *value, int flags)
|
|
{
|
|
struct workspace *ws;
|
|
int i, ws_id, num_screens;
|
|
char *b;
|
|
|
|
/* suppress unused warning since var is needed */
|
|
(void)selector;
|
|
|
|
switch (flags) {
|
|
case SWM_S_BAR_ACTION:
|
|
free(bar_argv[0]);
|
|
if ((bar_argv[0] = expand_tilde(value)) == NULL)
|
|
err(1, "setconfvalue: bar_action");
|
|
break;
|
|
case SWM_S_BAR_AT_BOTTOM:
|
|
bar_at_bottom = atoi(value);
|
|
break;
|
|
case SWM_S_BAR_BORDER_WIDTH:
|
|
bar_border_width = atoi(value);
|
|
if (bar_border_width < 0)
|
|
bar_border_width = 0;
|
|
break;
|
|
case SWM_S_BAR_DELAY:
|
|
/* No longer needed; leave to not break old conf files. */
|
|
break;
|
|
case SWM_S_BAR_ENABLED:
|
|
bar_enabled = atoi(value);
|
|
break;
|
|
case SWM_S_BAR_ENABLED_WS:
|
|
ws_id = atoi(selector) - 1;
|
|
if (ws_id < 0 || ws_id >= workspace_limit)
|
|
errx(1, "setconfvalue: bar_enabled_ws: invalid "
|
|
"workspace %d.", ws_id + 1);
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++) {
|
|
ws = (struct workspace *)&screens[i].ws;
|
|
ws[ws_id].bar_enabled = atoi(value);
|
|
}
|
|
break;
|
|
case SWM_S_BAR_FONT:
|
|
b = bar_fonts;
|
|
if (asprintf(&bar_fonts, "%s,%s", value, bar_fonts) == -1)
|
|
err(1, "setconfvalue: asprintf: failed to allocate "
|
|
"memory for bar_fonts.");
|
|
free(b);
|
|
|
|
/* If already in xft mode, then we are done. */
|
|
if (!bar_font_legacy)
|
|
break;
|
|
|
|
/* If there are any non-XLFD entries, switch to Xft mode. */
|
|
while ((b = strsep(&value, ",")) != NULL) {
|
|
if (*b == '\0')
|
|
continue;
|
|
if (!isxlfd(b)) {
|
|
bar_font_legacy = 0;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case SWM_S_BAR_FORMAT:
|
|
free(bar_format);
|
|
if ((bar_format = strdup(value)) == NULL)
|
|
err(1, "setconfvalue: bar_format");
|
|
break;
|
|
case SWM_S_BAR_JUSTIFY:
|
|
if (!strcmp(value, "left"))
|
|
bar_justify = SWM_BAR_JUSTIFY_LEFT;
|
|
else if (!strcmp(value, "center"))
|
|
bar_justify = SWM_BAR_JUSTIFY_CENTER;
|
|
else if (!strcmp(value, "right"))
|
|
bar_justify = SWM_BAR_JUSTIFY_RIGHT;
|
|
else
|
|
errx(1, "invalid bar_justify");
|
|
break;
|
|
case SWM_S_BORDER_WIDTH:
|
|
border_width = atoi(value);
|
|
if (border_width < 0)
|
|
border_width = 0;
|
|
break;
|
|
case SWM_S_CLOCK_ENABLED:
|
|
clock_enabled = atoi(value);
|
|
break;
|
|
case SWM_S_CLOCK_FORMAT:
|
|
#ifndef SWM_DENY_CLOCK_FORMAT
|
|
free(clock_format);
|
|
if ((clock_format = strdup(value)) == NULL)
|
|
err(1, "setconfvalue: clock_format");
|
|
#endif
|
|
break;
|
|
case SWM_S_CYCLE_EMPTY:
|
|
cycle_empty = atoi(value);
|
|
break;
|
|
case SWM_S_CYCLE_VISIBLE:
|
|
cycle_visible = atoi(value);
|
|
break;
|
|
case SWM_S_DIALOG_RATIO:
|
|
dialog_ratio = atof(value);
|
|
if (dialog_ratio > 1.0 || dialog_ratio <= .3)
|
|
dialog_ratio = .6;
|
|
break;
|
|
case SWM_S_DISABLE_BORDER:
|
|
disable_border = atoi(value);
|
|
break;
|
|
case SWM_S_FOCUS_CLOSE:
|
|
if (!strcmp(value, "first"))
|
|
focus_close = SWM_STACK_BOTTOM;
|
|
else if (!strcmp(value, "last"))
|
|
focus_close = SWM_STACK_TOP;
|
|
else if (!strcmp(value, "next"))
|
|
focus_close = SWM_STACK_ABOVE;
|
|
else if (!strcmp(value, "previous"))
|
|
focus_close = SWM_STACK_BELOW;
|
|
else
|
|
errx(1, "focus_close");
|
|
break;
|
|
case SWM_S_FOCUS_CLOSE_WRAP:
|
|
focus_close_wrap = atoi(value);
|
|
break;
|
|
case SWM_S_FOCUS_DEFAULT:
|
|
if (!strcmp(value, "last"))
|
|
focus_default = SWM_STACK_TOP;
|
|
else if (!strcmp(value, "first"))
|
|
focus_default = SWM_STACK_BOTTOM;
|
|
else
|
|
errx(1, "focus_default");
|
|
break;
|
|
case SWM_S_FOCUS_MODE:
|
|
if (!strcmp(value, "default"))
|
|
focus_mode = SWM_FOCUS_DEFAULT;
|
|
else if (!strcmp(value, "follow") ||
|
|
!strcmp(value, "follow_cursor"))
|
|
focus_mode = SWM_FOCUS_FOLLOW;
|
|
else if (!strcmp(value, "manual"))
|
|
focus_mode = SWM_FOCUS_MANUAL;
|
|
else
|
|
errx(1, "focus_mode");
|
|
break;
|
|
case SWM_S_REGION_PADDING:
|
|
region_padding = atoi(value);
|
|
if (region_padding < 0)
|
|
region_padding = 0;
|
|
break;
|
|
case SWM_S_SPAWN_ORDER:
|
|
if (!strcmp(value, "first"))
|
|
spawn_position = SWM_STACK_BOTTOM;
|
|
else if (!strcmp(value, "last"))
|
|
spawn_position = SWM_STACK_TOP;
|
|
else if (!strcmp(value, "next"))
|
|
spawn_position = SWM_STACK_ABOVE;
|
|
else if (!strcmp(value, "previous"))
|
|
spawn_position = SWM_STACK_BELOW;
|
|
else
|
|
errx(1, "spawn_position");
|
|
break;
|
|
case SWM_S_SPAWN_TERM:
|
|
setconfspawn("term", value, 0);
|
|
setconfspawn("spawn_term", value, 0);
|
|
break;
|
|
case SWM_S_SS_APP:
|
|
/* No longer needed; leave to not break old conf files. */
|
|
break;
|
|
case SWM_S_SS_ENABLED:
|
|
/* No longer needed; leave to not break old conf files. */
|
|
break;
|
|
case SWM_S_STACK_ENABLED:
|
|
stack_enabled = atoi(value);
|
|
break;
|
|
case SWM_S_TERM_WIDTH:
|
|
term_width = atoi(value);
|
|
if (term_width < 0)
|
|
term_width = 0;
|
|
break;
|
|
case SWM_S_TILE_GAP:
|
|
tile_gap = atoi(value);
|
|
if (tile_gap < 0)
|
|
tile_gap = 0;
|
|
break;
|
|
case SWM_S_TITLE_CLASS_ENABLED:
|
|
title_class_enabled = atoi(value);
|
|
break;
|
|
case SWM_S_TITLE_NAME_ENABLED:
|
|
title_name_enabled = atoi(value);
|
|
break;
|
|
case SWM_S_URGENT_ENABLED:
|
|
urgent_enabled = atoi(value);
|
|
break;
|
|
case SWM_S_VERBOSE_LAYOUT:
|
|
verbose_layout = atoi(value);
|
|
for (i = 0; layouts[i].l_stack != NULL; i++) {
|
|
if (verbose_layout)
|
|
layouts[i].l_string = fancy_stacker;
|
|
else
|
|
layouts[i].l_string = plain_stacker;
|
|
}
|
|
break;
|
|
case SWM_S_WINDOW_NAME_ENABLED:
|
|
window_name_enabled = atoi(value);
|
|
break;
|
|
case SWM_S_WORKSPACE_LIMIT:
|
|
workspace_limit = atoi(value);
|
|
if (workspace_limit > SWM_WS_MAX)
|
|
workspace_limit = SWM_WS_MAX;
|
|
else if (workspace_limit < 1)
|
|
workspace_limit = 1;
|
|
break;
|
|
default:
|
|
return (1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
setconfmodkey(char *selector, char *value, int flags)
|
|
{
|
|
/* suppress unused warnings since vars are needed */
|
|
(void)selector;
|
|
(void)flags;
|
|
|
|
if (!strncasecmp(value, "Mod1", strlen("Mod1")))
|
|
update_modkey(XCB_MOD_MASK_1);
|
|
else if (!strncasecmp(value, "Mod2", strlen("Mod2")))
|
|
update_modkey(XCB_MOD_MASK_2);
|
|
else if (!strncasecmp(value, "Mod3", strlen("Mod3")))
|
|
update_modkey(XCB_MOD_MASK_3);
|
|
else if (!strncasecmp(value, "Mod4", strlen("Mod4")))
|
|
update_modkey(XCB_MOD_MASK_4);
|
|
else
|
|
return (1);
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
setconfcolor(char *selector, char *value, int flags)
|
|
{
|
|
setscreencolor(value, ((selector == NULL)?-1:atoi(selector)), flags);
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
setconfregion(char *selector, char *value, int flags)
|
|
{
|
|
/* suppress unused warnings since vars are needed */
|
|
(void)selector;
|
|
(void)flags;
|
|
|
|
custom_region(value);
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
setautorun(char *selector, char *value, int flags)
|
|
{
|
|
int ws_id;
|
|
char s[1024];
|
|
char *ap, *sp;
|
|
union arg a;
|
|
int argc = 0;
|
|
pid_t pid;
|
|
struct pid_e *p;
|
|
|
|
/* suppress unused warnings since vars are needed */
|
|
(void)selector;
|
|
(void)flags;
|
|
|
|
if (getenv("SWM_STARTED"))
|
|
return (0);
|
|
|
|
bzero(s, sizeof s);
|
|
if (sscanf(value, "ws[%d]:%1023c", &ws_id, s) != 2)
|
|
errx(1, "invalid autorun entry, should be 'ws[<idx>]:command'");
|
|
ws_id--;
|
|
if (ws_id < 0 || ws_id >= workspace_limit)
|
|
errx(1, "autorun: invalid workspace %d", ws_id + 1);
|
|
|
|
sp = expand_tilde((char *)&s);
|
|
|
|
/*
|
|
* This is a little intricate
|
|
*
|
|
* If the pid already exists we simply reuse it because it means it was
|
|
* used before AND not claimed by manage_window. We get away with
|
|
* altering it in the parent after INSERT because this can not be a race
|
|
*/
|
|
a.argv = NULL;
|
|
while ((ap = strsep(&sp, " \t")) != NULL) {
|
|
if (*ap == '\0')
|
|
continue;
|
|
DNPRINTF(SWM_D_SPAWN, "setautorun: arg [%s]\n", ap);
|
|
argc++;
|
|
if ((a.argv = realloc(a.argv, argc * sizeof(char *))) == NULL)
|
|
err(1, "setautorun: realloc");
|
|
a.argv[argc - 1] = ap;
|
|
}
|
|
free(sp);
|
|
|
|
if ((a.argv = realloc(a.argv, (argc + 1) * sizeof(char *))) == NULL)
|
|
err(1, "setautorun: realloc");
|
|
a.argv[argc] = NULL;
|
|
|
|
if ((pid = fork()) == 0) {
|
|
spawn(ws_id, &a, 1);
|
|
/* NOTREACHED */
|
|
_exit(1);
|
|
}
|
|
free(a.argv);
|
|
|
|
/* parent */
|
|
p = find_pid(pid);
|
|
if (p == NULL) {
|
|
p = calloc(1, sizeof *p);
|
|
if (p == NULL)
|
|
return (1);
|
|
TAILQ_INSERT_TAIL(&pidlist, p, entry);
|
|
}
|
|
|
|
p->pid = pid;
|
|
p->ws = ws_id;
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
setlayout(char *selector, char *value, int flags)
|
|
{
|
|
int ws_id, i, x, mg, ma, si, ar, f = 0;
|
|
int st = SWM_V_STACK, num_screens;
|
|
char s[1024];
|
|
struct workspace *ws;
|
|
|
|
/* suppress unused warnings since vars are needed */
|
|
(void)selector;
|
|
(void)flags;
|
|
|
|
if (getenv("SWM_STARTED"))
|
|
return (0);
|
|
|
|
bzero(s, sizeof s);
|
|
if (sscanf(value, "ws[%d]:%d:%d:%d:%d:%1023c",
|
|
&ws_id, &mg, &ma, &si, &ar, s) != 6)
|
|
errx(1, "invalid layout entry, should be 'ws[<idx>]:"
|
|
"<master_grow>:<master_add>:<stack_inc>:<always_raise>:"
|
|
"<type>'");
|
|
ws_id--;
|
|
if (ws_id < 0 || ws_id >= workspace_limit)
|
|
errx(1, "layout: invalid workspace %d", ws_id + 1);
|
|
|
|
if (!strcasecmp(s, "vertical"))
|
|
st = SWM_V_STACK;
|
|
else if (!strcasecmp(s, "vertical_flip")) {
|
|
st = SWM_V_STACK;
|
|
f = 1;
|
|
} else if (!strcasecmp(s, "horizontal"))
|
|
st = SWM_H_STACK;
|
|
else if (!strcasecmp(s, "horizontal_flip")) {
|
|
st = SWM_H_STACK;
|
|
f = 1;
|
|
} else if (!strcasecmp(s, "fullscreen"))
|
|
st = SWM_MAX_STACK;
|
|
else
|
|
errx(1, "invalid layout entry, should be 'ws[<idx>]:"
|
|
"<master_grow>:<master_add>:<stack_inc>:<always_raise>:"
|
|
"<type>'");
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++) {
|
|
ws = (struct workspace *)&screens[i].ws;
|
|
ws[ws_id].cur_layout = &layouts[st];
|
|
|
|
ws[ws_id].always_raise = ar;
|
|
if (st == SWM_MAX_STACK)
|
|
continue;
|
|
|
|
/* master grow */
|
|
for (x = 0; x < abs(mg); x++) {
|
|
ws[ws_id].cur_layout->l_config(&ws[ws_id],
|
|
mg >= 0 ? SWM_ARG_ID_MASTERGROW :
|
|
SWM_ARG_ID_MASTERSHRINK);
|
|
stack();
|
|
}
|
|
/* master add */
|
|
for (x = 0; x < abs(ma); x++) {
|
|
ws[ws_id].cur_layout->l_config(&ws[ws_id],
|
|
ma >= 0 ? SWM_ARG_ID_MASTERADD :
|
|
SWM_ARG_ID_MASTERDEL);
|
|
stack();
|
|
}
|
|
/* stack inc */
|
|
for (x = 0; x < abs(si); x++) {
|
|
ws[ws_id].cur_layout->l_config(&ws[ws_id],
|
|
si >= 0 ? SWM_ARG_ID_STACKINC :
|
|
SWM_ARG_ID_STACKDEC);
|
|
stack();
|
|
}
|
|
/* Apply flip */
|
|
if (f) {
|
|
ws[ws_id].cur_layout->l_config(&ws[ws_id],
|
|
SWM_ARG_ID_FLIPLAYOUT);
|
|
stack();
|
|
}
|
|
}
|
|
|
|
focus_flush();
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* config options */
|
|
struct config_option {
|
|
char *optname;
|
|
int (*func)(char*, char*, int);
|
|
int funcflags;
|
|
};
|
|
struct config_option configopt[] = {
|
|
{ "autorun", setautorun, 0 },
|
|
{ "bar_action", setconfvalue, SWM_S_BAR_ACTION },
|
|
{ "bar_at_bottom", setconfvalue, SWM_S_BAR_AT_BOTTOM },
|
|
{ "bar_border", setconfcolor, SWM_S_COLOR_BAR_BORDER },
|
|
{ "bar_border_unfocus", setconfcolor, SWM_S_COLOR_BAR_BORDER_UNFOCUS },
|
|
{ "bar_border_width", setconfvalue, SWM_S_BAR_BORDER_WIDTH },
|
|
{ "bar_color", setconfcolor, SWM_S_COLOR_BAR },
|
|
{ "bar_delay", setconfvalue, SWM_S_BAR_DELAY },
|
|
{ "bar_enabled", setconfvalue, SWM_S_BAR_ENABLED },
|
|
{ "bar_enabled_ws", setconfvalue, SWM_S_BAR_ENABLED_WS },
|
|
{ "bar_font", setconfvalue, SWM_S_BAR_FONT },
|
|
{ "bar_font_color", setconfcolor, SWM_S_COLOR_BAR_FONT },
|
|
{ "bar_format", setconfvalue, SWM_S_BAR_FORMAT },
|
|
{ "bar_justify", setconfvalue, SWM_S_BAR_JUSTIFY },
|
|
{ "bind", setconfbinding, 0 },
|
|
{ "border_width", setconfvalue, SWM_S_BORDER_WIDTH },
|
|
{ "clock_enabled", setconfvalue, SWM_S_CLOCK_ENABLED },
|
|
{ "clock_format", setconfvalue, SWM_S_CLOCK_FORMAT },
|
|
{ "color_focus", setconfcolor, SWM_S_COLOR_FOCUS },
|
|
{ "color_unfocus", setconfcolor, SWM_S_COLOR_UNFOCUS },
|
|
{ "cycle_empty", setconfvalue, SWM_S_CYCLE_EMPTY },
|
|
{ "cycle_visible", setconfvalue, SWM_S_CYCLE_VISIBLE },
|
|
{ "dialog_ratio", setconfvalue, SWM_S_DIALOG_RATIO },
|
|
{ "disable_border", setconfvalue, SWM_S_DISABLE_BORDER },
|
|
{ "focus_close", setconfvalue, SWM_S_FOCUS_CLOSE },
|
|
{ "focus_close_wrap", setconfvalue, SWM_S_FOCUS_CLOSE_WRAP },
|
|
{ "focus_default", setconfvalue, SWM_S_FOCUS_DEFAULT },
|
|
{ "focus_mode", setconfvalue, SWM_S_FOCUS_MODE },
|
|
{ "keyboard_mapping", setkeymapping, 0 },
|
|
{ "layout", setlayout, 0 },
|
|
{ "modkey", setconfmodkey, 0 },
|
|
{ "program", setconfspawn, 0 },
|
|
{ "quirk", setconfquirk, 0 },
|
|
{ "region", setconfregion, 0 },
|
|
{ "region_padding", setconfvalue, SWM_S_REGION_PADDING },
|
|
{ "screenshot_app", setconfvalue, SWM_S_SS_APP },
|
|
{ "screenshot_enabled", setconfvalue, SWM_S_SS_ENABLED },
|
|
{ "spawn_position", setconfvalue, SWM_S_SPAWN_ORDER },
|
|
{ "spawn_term", setconfvalue, SWM_S_SPAWN_TERM },
|
|
{ "stack_enabled", setconfvalue, SWM_S_STACK_ENABLED },
|
|
{ "term_width", setconfvalue, SWM_S_TERM_WIDTH },
|
|
{ "tile_gap", setconfvalue, SWM_S_TILE_GAP },
|
|
{ "title_class_enabled", setconfvalue, SWM_S_TITLE_CLASS_ENABLED },
|
|
{ "title_name_enabled", setconfvalue, SWM_S_TITLE_NAME_ENABLED },
|
|
{ "urgent_enabled", setconfvalue, SWM_S_URGENT_ENABLED },
|
|
{ "verbose_layout", setconfvalue, SWM_S_VERBOSE_LAYOUT },
|
|
{ "window_name_enabled", setconfvalue, SWM_S_WINDOW_NAME_ENABLED },
|
|
{ "workspace_limit", setconfvalue, SWM_S_WORKSPACE_LIMIT },
|
|
};
|
|
|
|
void
|
|
_add_startup_exception(const char *fmt, va_list ap)
|
|
{
|
|
if (vasprintf(&startup_exception, fmt, ap) == -1)
|
|
warn("%s: asprintf", __func__);
|
|
}
|
|
|
|
void
|
|
add_startup_exception(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
nr_exceptions++;
|
|
|
|
if (startup_exception)
|
|
return;
|
|
|
|
/* force bar to be enabled due to exception */
|
|
bar_enabled = 1;
|
|
|
|
va_start(ap, fmt);
|
|
_add_startup_exception(fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
int
|
|
conf_load(const char *filename, int keymapping)
|
|
{
|
|
FILE *config;
|
|
char *line = NULL, *cp, *optsub, *optval = NULL;
|
|
size_t linelen, lineno = 0;
|
|
int wordlen, i, optidx;
|
|
struct config_option *opt = NULL;
|
|
|
|
DNPRINTF(SWM_D_CONF, "conf_load: begin\n");
|
|
|
|
if (filename == NULL) {
|
|
warnx("conf_load: no filename");
|
|
return (1);
|
|
}
|
|
|
|
DNPRINTF(SWM_D_CONF, "conf_load: open %s\n", filename);
|
|
|
|
if ((config = fopen(filename, "r")) == NULL) {
|
|
warn("conf_load: fopen: %s", filename);
|
|
return (1);
|
|
}
|
|
|
|
while (!feof(config)) {
|
|
if (line)
|
|
free(line);
|
|
|
|
if ((line = fparseln(config, &linelen, &lineno, NULL,
|
|
FPARSELN_UNESCCOMM | FPARSELN_UNESCCONT)) == NULL) {
|
|
if (ferror(config))
|
|
err(1, "%s", filename);
|
|
else
|
|
continue;
|
|
}
|
|
cp = line;
|
|
cp += strspn(cp, " \t\n"); /* eat whitespace */
|
|
if (cp[0] == '\0') {
|
|
/* empty line */
|
|
continue;
|
|
}
|
|
/* get config option */
|
|
wordlen = strcspn(cp, "=[ \t\n");
|
|
if (wordlen == 0) {
|
|
add_startup_exception("%s: line %zd: no option found",
|
|
filename, lineno);
|
|
continue;
|
|
}
|
|
optidx = -1;
|
|
for (i = 0; i < LENGTH(configopt); i++) {
|
|
opt = &configopt[i];
|
|
if (!strncasecmp(cp, opt->optname, wordlen) &&
|
|
(int)strlen(opt->optname) == wordlen) {
|
|
optidx = i;
|
|
break;
|
|
}
|
|
}
|
|
if (optidx == -1) {
|
|
add_startup_exception("%s: line %zd: unknown option "
|
|
"%.*s", filename, lineno, wordlen, cp);
|
|
continue;
|
|
}
|
|
if (keymapping && opt && strcmp(opt->optname, "bind")) {
|
|
add_startup_exception("%s: line %zd: invalid option "
|
|
"%.*s", filename, lineno, wordlen, cp);
|
|
continue;
|
|
}
|
|
cp += wordlen;
|
|
cp += strspn(cp, " \t\n"); /* eat whitespace */
|
|
|
|
/* from here on out we call goto invalid to continue */
|
|
|
|
/* get [selector] if any */
|
|
optsub = NULL;
|
|
if (*cp == '[') {
|
|
cp++;
|
|
wordlen = strcspn(cp, "]");
|
|
if (*cp != ']') {
|
|
if (wordlen == 0) {
|
|
add_startup_exception("%s: line %zd: "
|
|
"syntax error", filename, lineno);
|
|
goto invalid;
|
|
}
|
|
|
|
if (asprintf(&optsub, "%.*s", wordlen, cp) ==
|
|
-1) {
|
|
add_startup_exception("%s: line %zd: "
|
|
"unable to allocatememory for "
|
|
"selector", filename, lineno);
|
|
goto invalid;
|
|
}
|
|
}
|
|
cp += wordlen;
|
|
cp += strspn(cp, "] \t\n"); /* eat trailing */
|
|
}
|
|
cp += strspn(cp, "= \t\n"); /* eat trailing */
|
|
/* get RHS value */
|
|
optval = strdup(cp);
|
|
if (strlen(optval) == 0) {
|
|
add_startup_exception("%s: line %zd: must supply value "
|
|
"to %s", filename, lineno,
|
|
configopt[optidx].optname);
|
|
goto invalid;
|
|
}
|
|
/* call function to deal with it all */
|
|
if (configopt[optidx].func(optsub, optval,
|
|
configopt[optidx].funcflags) != 0) {
|
|
add_startup_exception("%s: line %zd: invalid data for "
|
|
"%s", filename, lineno, configopt[optidx].optname);
|
|
goto invalid;
|
|
}
|
|
invalid:
|
|
if (optval) {
|
|
free(optval);
|
|
optval = NULL;
|
|
}
|
|
if (optsub) {
|
|
free(optsub);
|
|
optsub = NULL;
|
|
}
|
|
}
|
|
|
|
if (line)
|
|
free(line);
|
|
fclose(config);
|
|
DNPRINTF(SWM_D_CONF, "conf_load: end\n");
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
set_child_transient(struct ws_win *win, xcb_window_t *trans)
|
|
{
|
|
struct ws_win *parent, *w;
|
|
struct swm_region *r;
|
|
struct workspace *ws;
|
|
xcb_icccm_wm_hints_t wmh;
|
|
|
|
parent = find_window(win->transient);
|
|
if (parent)
|
|
parent->focus_child = win;
|
|
else {
|
|
DNPRINTF(SWM_D_MISC, "set_child_transient: parent doesn't exist"
|
|
" for 0x%x trans 0x%x\n", win->id, win->transient);
|
|
|
|
r = root_to_region(win->wa->root, SWM_CK_ALL);
|
|
ws = r->ws;
|
|
/* parent doen't exist in our window list */
|
|
TAILQ_FOREACH(w, &ws->winlist, entry) {
|
|
if (xcb_icccm_get_wm_hints_reply(conn,
|
|
xcb_icccm_get_wm_hints(conn, w->id),
|
|
&wmh, NULL) != 1) {
|
|
warnx("can't get hints for 0x%x", w->id);
|
|
continue;
|
|
}
|
|
|
|
if (win->hints.window_group != wmh.window_group)
|
|
continue;
|
|
|
|
w->focus_child = win;
|
|
win->transient = w->id;
|
|
*trans = w->id;
|
|
DNPRINTF(SWM_D_MISC, "set_child_transient: adjusting "
|
|
"transient to 0x%x\n", win->transient);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
pid_t
|
|
window_get_pid(xcb_window_t win)
|
|
{
|
|
pid_t ret = 0;
|
|
const char *errstr;
|
|
xcb_atom_t apid;
|
|
xcb_get_property_cookie_t pc;
|
|
xcb_get_property_reply_t *pr;
|
|
|
|
apid = get_atom_from_string("_NET_WM_PID");
|
|
if (apid == XCB_ATOM_NONE)
|
|
goto tryharder;
|
|
|
|
pc = xcb_get_property(conn, 0, win, apid, XCB_ATOM_CARDINAL, 0, 1);
|
|
pr = xcb_get_property_reply(conn, pc, NULL);
|
|
if (!pr)
|
|
goto tryharder;
|
|
if (pr->type != XCB_ATOM_CARDINAL) {
|
|
free(pr);
|
|
goto tryharder;
|
|
}
|
|
|
|
if (pr->type == apid && pr->format == 32)
|
|
ret = *((pid_t *)xcb_get_property_value(pr));
|
|
free(pr);
|
|
|
|
return (ret);
|
|
|
|
tryharder:
|
|
apid = get_atom_from_string("_SWM_PID");
|
|
pc = xcb_get_property(conn, 0, win, apid, XCB_ATOM_STRING,
|
|
0, SWM_PROPLEN);
|
|
pr = xcb_get_property_reply(conn, pc, NULL);
|
|
if (!pr)
|
|
return (0);
|
|
if (pr->type != apid) {
|
|
free(pr);
|
|
return (0);
|
|
}
|
|
|
|
ret = (pid_t)strtonum(xcb_get_property_value(pr), 0, INT_MAX, &errstr);
|
|
free(pr);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
get_ws_idx(xcb_window_t id)
|
|
{
|
|
int ws_idx = -1;
|
|
char *prop = NULL;
|
|
size_t proplen;
|
|
const char *errstr;
|
|
xcb_get_property_reply_t *gpr;
|
|
|
|
gpr = xcb_get_property_reply(conn,
|
|
xcb_get_property(conn, 0, id, a_swm_ws,
|
|
XCB_ATOM_STRING, 0, SWM_PROPLEN),
|
|
NULL);
|
|
if (!gpr)
|
|
return (-1);
|
|
if (gpr->type) {
|
|
proplen = xcb_get_property_value_length(gpr);
|
|
if (proplen > 0) {
|
|
prop = malloc(proplen + 1);
|
|
if (prop) {
|
|
memcpy(prop,
|
|
xcb_get_property_value(gpr),
|
|
proplen);
|
|
prop[proplen] = '\0';
|
|
}
|
|
}
|
|
}
|
|
free(gpr);
|
|
|
|
if (prop) {
|
|
DNPRINTF(SWM_D_PROP, "get_ws_idx: _SWM_WS: %s\n", prop);
|
|
ws_idx = (int)strtonum(prop, 0, workspace_limit - 1, &errstr);
|
|
if (errstr) {
|
|
DNPRINTF(SWM_D_PROP, "get_ws_idx: window: #%s: %s",
|
|
errstr, prop);
|
|
}
|
|
free(prop);
|
|
}
|
|
|
|
return ws_idx;
|
|
}
|
|
|
|
struct ws_win *
|
|
manage_window(xcb_window_t id, uint16_t mapped)
|
|
{
|
|
xcb_window_t trans = XCB_WINDOW_NONE;
|
|
struct ws_win *win, *ww;
|
|
int ws_idx;
|
|
char ws_idx_str[SWM_PROPLEN];
|
|
struct swm_region *r;
|
|
struct pid_e *p;
|
|
struct quirk *qp;
|
|
uint32_t i, wa[2];
|
|
xcb_icccm_get_wm_protocols_reply_t wpr;
|
|
|
|
if ((win = find_window(id)) != NULL) {
|
|
DNPRINTF(SWM_D_MISC, "manage_window: win 0x%x already "
|
|
"managed; skipping.)\n", id);
|
|
return (win); /* Already managed. */
|
|
}
|
|
|
|
/* See if window is on the unmanaged list. */
|
|
if ((win = find_unmanaged_window(id)) != NULL) {
|
|
DNPRINTF(SWM_D_MISC, "manage_window: win 0x%x found on "
|
|
"unmanaged list.\n", id);
|
|
TAILQ_REMOVE(&win->ws->unmanagedlist, win, entry);
|
|
|
|
if (win->transient)
|
|
set_child_transient(win, &trans);
|
|
|
|
goto out;
|
|
} else {
|
|
DNPRINTF(SWM_D_MISC, "manage_window: win 0x%x is new.\n", id);
|
|
}
|
|
|
|
/* Create and initialize ws_win object. */
|
|
if ((win = calloc(1, sizeof(struct ws_win))) == NULL)
|
|
err(1, "manage_window: calloc: failed to allocate memory for "
|
|
"new window");
|
|
|
|
win->id = id;
|
|
|
|
/* Get window geometry. */
|
|
win->wa = xcb_get_geometry_reply(conn,
|
|
xcb_get_geometry(conn, win->id),
|
|
NULL);
|
|
|
|
/* Figure out which region the window belongs to. */
|
|
r = root_to_region(win->wa->root, SWM_CK_ALL);
|
|
|
|
/* Ignore window border if there is one. */
|
|
WIDTH(win) = win->wa->width;
|
|
HEIGHT(win) = win->wa->height;
|
|
X(win) = win->wa->x + win->wa->border_width - border_width;
|
|
Y(win) = win->wa->y + win->wa->border_width - border_width;
|
|
win->bordered = 1;
|
|
win->mapped = mapped;
|
|
win->floatmaxed = 0;
|
|
win->ewmh_flags = 0;
|
|
win->s = r->s; /* this never changes */
|
|
|
|
store_float_geom(win, r);
|
|
|
|
/* Get WM_SIZE_HINTS. */
|
|
xcb_icccm_get_wm_normal_hints_reply(conn,
|
|
xcb_icccm_get_wm_normal_hints(conn, win->id),
|
|
&win->sh, NULL);
|
|
|
|
/* Get WM_HINTS. */
|
|
xcb_icccm_get_wm_hints_reply(conn,
|
|
xcb_icccm_get_wm_hints(conn, win->id),
|
|
&win->hints, NULL);
|
|
|
|
/* Get WM_TRANSIENT_FOR; see if window is a transient. */
|
|
xcb_icccm_get_wm_transient_for_reply(conn,
|
|
xcb_icccm_get_wm_transient_for(conn, win->id),
|
|
&trans, NULL);
|
|
if (trans) {
|
|
win->transient = trans;
|
|
set_child_transient(win, &win->transient);
|
|
}
|
|
|
|
/* Get supported protocols. */
|
|
if (xcb_icccm_get_wm_protocols_reply(conn,
|
|
xcb_icccm_get_wm_protocols(conn, win->id, a_prot),
|
|
&wpr, NULL)) {
|
|
for (i = 0; i < wpr.atoms_len; i++) {
|
|
if (wpr.atoms[i] == a_takefocus)
|
|
win->take_focus = 1;
|
|
if (wpr.atoms[i] == a_delete)
|
|
win->can_delete = 1;
|
|
}
|
|
xcb_icccm_get_wm_protocols_reply_wipe(&wpr);
|
|
}
|
|
|
|
win->iconic = get_swm_iconic(win);
|
|
|
|
/* Figure out which workspace the window belongs to. */
|
|
if ((p = find_pid(window_get_pid(win->id))) != NULL) {
|
|
win->ws = &r->s->ws[p->ws];
|
|
TAILQ_REMOVE(&pidlist, p, entry);
|
|
free(p);
|
|
p = NULL;
|
|
} else if ((ws_idx = get_ws_idx(win->id)) != -1 &&
|
|
!win->transient) {
|
|
/* _SWM_WS is set; use that. */
|
|
win->ws = &r->s->ws[ws_idx];
|
|
} else if (trans && (ww = find_window(trans)) != NULL) {
|
|
/* Launch transients in the same ws as parent. */
|
|
win->ws = ww->ws;
|
|
} else {
|
|
win->ws = r->ws;
|
|
}
|
|
|
|
/* Set the _SWM_WS atom so we can remember this after reincarnation. */
|
|
if (snprintf(ws_idx_str, SWM_PROPLEN, "%d", win->ws->idx) <
|
|
SWM_PROPLEN) {
|
|
DNPRINTF(SWM_D_PROP, "manage_window: set _SWM_WS: %s\n",
|
|
ws_idx_str);
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->id,
|
|
a_swm_ws, XCB_ATOM_STRING, 8, strlen(ws_idx_str),
|
|
ws_idx_str);
|
|
}
|
|
|
|
/* Handle EWMH */
|
|
ewmh_autoquirk(win);
|
|
|
|
/* Determine initial quirks. */
|
|
if (xcb_icccm_get_wm_class_reply(conn,
|
|
xcb_icccm_get_wm_class(conn, win->id),
|
|
&win->ch, NULL)) {
|
|
DNPRINTF(SWM_D_CLASS, "manage_window: class: %s, name: %s\n",
|
|
win->ch.class_name, win->ch.instance_name);
|
|
|
|
/* java is retarded so treat it special */
|
|
if (strstr(win->ch.instance_name, "sun-awt")) {
|
|
DNPRINTF(SWM_D_CLASS, "manage_window: java window "
|
|
"detected.\n");
|
|
win->java = 1;
|
|
}
|
|
|
|
TAILQ_FOREACH(qp, &quirks, entry) {
|
|
if (!strcmp(win->ch.class_name, qp->class) &&
|
|
!strcmp(win->ch.instance_name, qp->name)) {
|
|
DNPRINTF(SWM_D_CLASS, "manage_window: on quirks"
|
|
"list; mask: 0x%lx\n", qp->quirk);
|
|
if (qp->quirk & SWM_Q_FLOAT)
|
|
win->floating = 1;
|
|
win->quirks = qp->quirk;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Alter window position if quirky */
|
|
if (win->quirks & SWM_Q_ANYWHERE)
|
|
win->manual = 1;
|
|
|
|
/* Reset font sizes (the bruteforce way; no default keybinding). */
|
|
if (win->quirks & SWM_Q_XTERM_FONTADJ) {
|
|
for (i = 0; i < SWM_MAX_FONT_STEPS; i++)
|
|
fake_keypress(win, XK_KP_Subtract, XCB_MOD_MASK_SHIFT);
|
|
for (i = 0; i < SWM_MAX_FONT_STEPS; i++)
|
|
fake_keypress(win, XK_KP_Add, XCB_MOD_MASK_SHIFT);
|
|
}
|
|
|
|
/* Make sure window is positioned inside its region, if its active. */
|
|
if (win->ws->r) {
|
|
constrain_window(win, win->ws->r, 0);
|
|
update_window(win);
|
|
}
|
|
|
|
/* Select which X events to monitor and set border pixel color. */
|
|
wa[0] = win->s->c[SWM_S_COLOR_UNFOCUS].pixel;
|
|
wa[1] = XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_PROPERTY_CHANGE |
|
|
XCB_EVENT_MASK_STRUCTURE_NOTIFY;
|
|
#ifdef SWM_DEBUG
|
|
wa[1] |= XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE;
|
|
#endif
|
|
|
|
xcb_change_window_attributes(conn, win->id, XCB_CW_BORDER_PIXEL |
|
|
XCB_CW_EVENT_MASK, wa);
|
|
|
|
out:
|
|
/* Figure out where to stack the window in the workspace. */
|
|
if (trans && (ww = find_window(trans)))
|
|
TAILQ_INSERT_AFTER(&win->ws->winlist, ww, win, entry);
|
|
else if (win->ws->focus && spawn_position == SWM_STACK_ABOVE)
|
|
TAILQ_INSERT_AFTER(&win->ws->winlist, win->ws->focus, win,
|
|
entry);
|
|
else if (win->ws->focus && spawn_position == SWM_STACK_BELOW)
|
|
TAILQ_INSERT_BEFORE(win->ws->focus, win, entry);
|
|
else switch (spawn_position) {
|
|
default:
|
|
case SWM_STACK_TOP:
|
|
case SWM_STACK_ABOVE:
|
|
TAILQ_INSERT_TAIL(&win->ws->winlist, win, entry);
|
|
break;
|
|
case SWM_STACK_BOTTOM:
|
|
case SWM_STACK_BELOW:
|
|
TAILQ_INSERT_HEAD(&win->ws->winlist, win, entry);
|
|
}
|
|
|
|
/* Get initial _NET_WM_STATE */
|
|
ewmh_get_win_state(win);
|
|
/* Set initial _NET_WM_ALLOWED_ACTIONS */
|
|
ewmh_update_actions(win);
|
|
|
|
grabbuttons(win);
|
|
|
|
DNPRINTF(SWM_D_MISC, "manage_window: done. window: 0x%x, (x,y) w x h: "
|
|
"(%d,%d) %d x %d, ws: %d, iconic: %s, transient: 0x%x\n", win->id,
|
|
X(win), Y(win), WIDTH(win), HEIGHT(win), win->ws->idx,
|
|
YESNO(win->iconic), win->transient);
|
|
|
|
return (win);
|
|
}
|
|
|
|
void
|
|
free_window(struct ws_win *win)
|
|
{
|
|
DNPRINTF(SWM_D_MISC, "free_window: window: 0x%x\n", win->id);
|
|
|
|
if (win == NULL)
|
|
return;
|
|
|
|
TAILQ_REMOVE(&win->ws->unmanagedlist, win, entry);
|
|
|
|
if (win->wa)
|
|
free(win->wa);
|
|
|
|
xcb_icccm_get_wm_class_reply_wipe(&win->ch);
|
|
|
|
kill_refs(win);
|
|
|
|
/* paint memory */
|
|
memset(win, 0xff, sizeof *win); /* XXX kill later */
|
|
|
|
free(win);
|
|
DNPRINTF(SWM_D_MISC, "free_window: done.\n");
|
|
}
|
|
|
|
void
|
|
unmanage_window(struct ws_win *win)
|
|
{
|
|
struct ws_win *parent;
|
|
|
|
if (win == NULL)
|
|
return;
|
|
|
|
DNPRINTF(SWM_D_MISC, "unmanage_window: window: 0x%x\n", win->id);
|
|
|
|
if (win->transient) {
|
|
parent = find_window(win->transient);
|
|
if (parent)
|
|
parent->focus_child = NULL;
|
|
}
|
|
|
|
TAILQ_REMOVE(&win->ws->winlist, win, entry);
|
|
TAILQ_INSERT_TAIL(&win->ws->unmanagedlist, win, entry);
|
|
}
|
|
|
|
void
|
|
expose(xcb_expose_event_t *e)
|
|
{
|
|
int i, num_screens;
|
|
struct swm_region *r;
|
|
|
|
DNPRINTF(SWM_D_EVENT, "expose: window: 0x%x\n", e->window);
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++)
|
|
TAILQ_FOREACH(r, &screens[i].rl, entry)
|
|
if (e->window == WINID(r->bar))
|
|
bar_draw();
|
|
|
|
xcb_flush(conn);
|
|
}
|
|
|
|
#ifdef SWM_DEBUG
|
|
void
|
|
focusin(xcb_focus_in_event_t *e)
|
|
{
|
|
DNPRINTF(SWM_D_EVENT, "focusin: window: 0x%x, mode: %s(%u), "
|
|
"detail: %s(%u)\n", e->event, get_notify_mode_label(e->mode),
|
|
e->mode, get_notify_detail_label(e->detail), e->detail);
|
|
}
|
|
|
|
void
|
|
focusout(xcb_focus_out_event_t *e)
|
|
{
|
|
DNPRINTF(SWM_D_EVENT, "focusout: window: 0x%x, mode: %s(%u), "
|
|
"detail: %s(%u)\n", e->event, get_notify_mode_label(e->mode),
|
|
e->mode, get_notify_detail_label(e->detail), e->detail);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
keypress(xcb_key_press_event_t *e)
|
|
{
|
|
xcb_keysym_t keysym;
|
|
struct key *kp;
|
|
|
|
keysym = xcb_key_press_lookup_keysym(syms, e, 0);
|
|
|
|
DNPRINTF(SWM_D_EVENT, "keypress: keysym: %u, win (x,y): 0x%x (%d,%d), "
|
|
"detail: %u, time: %u, root (x,y): 0x%x (%d,%d), child: 0x%x, "
|
|
"state: %u, same_screen: %s\n", keysym, e->event, e->event_x,
|
|
e->event_y, e->detail, e->time, e->root, e->root_x, e->root_y,
|
|
e->child, e->state, YESNO(e->same_screen));
|
|
|
|
if ((kp = key_lookup(CLEANMASK(e->state), keysym)) == NULL)
|
|
goto out;
|
|
|
|
last_event_time = e->time;
|
|
|
|
if (kp->funcid == KF_SPAWN_CUSTOM)
|
|
spawn_custom(root_to_region(e->root, SWM_CK_ALL),
|
|
&(keyfuncs[kp->funcid].args), kp->spawn_name);
|
|
else if (keyfuncs[kp->funcid].func)
|
|
keyfuncs[kp->funcid].func(root_to_region(e->root, SWM_CK_ALL),
|
|
&(keyfuncs[kp->funcid].args));
|
|
|
|
out:
|
|
/* Unfreeze grab events. */
|
|
xcb_allow_events(conn, XCB_ALLOW_ASYNC_KEYBOARD, e->time);
|
|
xcb_flush(conn);
|
|
|
|
DNPRINTF(SWM_D_EVENT, "keypress: done.\n");
|
|
}
|
|
|
|
void
|
|
buttonpress(xcb_button_press_event_t *e)
|
|
{
|
|
struct ws_win *win = NULL;
|
|
struct swm_region *r, *old_r;
|
|
int i;
|
|
int handled = 0;
|
|
|
|
DNPRINTF(SWM_D_EVENT, "buttonpress: win (x,y): 0x%x (%d,%d), "
|
|
"detail: %u, time: %u, root (x,y): 0x%x (%d,%d), child: 0x%x, "
|
|
"state: %u, same_screen: %s\n", e->event, e->event_x, e->event_y,
|
|
e->detail, e->time, e->root, e->root_x, e->root_y, e->child,
|
|
e->state, YESNO(e->same_screen));
|
|
|
|
if (e->event == e->root) {
|
|
if (e->child != 0) {
|
|
win = find_window(e->child);
|
|
/* Pass ButtonPress to window if it isn't managed. */
|
|
if (win == NULL)
|
|
goto out;
|
|
} else {
|
|
/* Focus on empty region */
|
|
/* If no windows on region if its empty. */
|
|
r = root_to_region(e->root, SWM_CK_POINTER);
|
|
if (r == NULL) {
|
|
DNPRINTF(SWM_D_EVENT, "buttonpress: "
|
|
"NULL region; ignoring.\n");
|
|
goto out;
|
|
}
|
|
|
|
if (TAILQ_EMPTY(&r->ws->winlist)) {
|
|
old_r = root_to_region(e->root, SWM_CK_FOCUS);
|
|
if (old_r && old_r != r)
|
|
unfocus_win(old_r->ws->focus);
|
|
|
|
xcb_set_input_focus(conn,
|
|
XCB_INPUT_FOCUS_PARENT, e->root, e->time);
|
|
|
|
/* Clear bar since empty. */
|
|
bar_draw();
|
|
|
|
handled = 1;
|
|
goto out;
|
|
}
|
|
}
|
|
} else {
|
|
win = find_window(e->event);
|
|
}
|
|
|
|
if (win == NULL)
|
|
goto out;
|
|
|
|
last_event_time = e->time;
|
|
|
|
focus_win(get_focus_magic(win));
|
|
|
|
for (i = 0; i < LENGTH(buttons); i++)
|
|
if (client_click == buttons[i].action && buttons[i].func &&
|
|
buttons[i].button == e->detail &&
|
|
CLEANMASK(buttons[i].mask) == CLEANMASK(e->state)) {
|
|
buttons[i].func(win, &buttons[i].args);
|
|
handled = 1;
|
|
}
|
|
|
|
out:
|
|
if (!handled) {
|
|
DNPRINTF(SWM_D_EVENT, "buttonpress: passing to window.\n");
|
|
/* Replay event to event window */
|
|
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, e->time);
|
|
} else {
|
|
DNPRINTF(SWM_D_EVENT, "buttonpress: handled.\n");
|
|
/* Unfreeze grab events. */
|
|
xcb_allow_events(conn, XCB_ALLOW_SYNC_POINTER, e->time);
|
|
}
|
|
|
|
xcb_flush(conn);
|
|
}
|
|
|
|
#ifdef SWM_DEBUG
|
|
void
|
|
print_win_geom(xcb_window_t w)
|
|
{
|
|
xcb_get_geometry_reply_t *wa;
|
|
|
|
wa = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, w), NULL);
|
|
|
|
if (!wa) {
|
|
DNPRINTF(SWM_D_MISC, "print_win_geom: window not found: 0x%x\n",
|
|
w);
|
|
return;
|
|
}
|
|
|
|
DNPRINTF(SWM_D_MISC, "print_win_geom: window: 0x%x, root: 0x%x, "
|
|
"depth: %u, (x,y) w x h: (%d,%d) %d x %d, border: %d\n",
|
|
w, wa->root, wa->depth, wa->x, wa->y, wa->width, wa->height,
|
|
wa->border_width);
|
|
|
|
free(wa);
|
|
}
|
|
#endif
|
|
|
|
#ifdef SWM_DEBUG
|
|
char *
|
|
get_stack_mode_name(uint8_t mode)
|
|
{
|
|
char *name;
|
|
|
|
switch(mode) {
|
|
case XCB_STACK_MODE_ABOVE:
|
|
name = "Above";
|
|
break;
|
|
case XCB_STACK_MODE_BELOW:
|
|
name = "Below";
|
|
break;
|
|
case XCB_STACK_MODE_TOP_IF:
|
|
name = "TopIf";
|
|
break;
|
|
case XCB_STACK_MODE_BOTTOM_IF:
|
|
name = "BottomIf";
|
|
break;
|
|
case XCB_STACK_MODE_OPPOSITE:
|
|
name = "Opposite";
|
|
break;
|
|
default:
|
|
name = "Unknown";
|
|
}
|
|
|
|
return name;
|
|
}
|
|
#endif
|
|
|
|
void
|
|
configurerequest(xcb_configure_request_event_t *e)
|
|
{
|
|
struct ws_win *win;
|
|
struct swm_region *r = NULL;
|
|
int new = 0, i = 0;
|
|
uint16_t mask = 0;
|
|
uint32_t wc[7] = {0};
|
|
|
|
if ((win = find_window(e->window)) == NULL)
|
|
if ((win = find_unmanaged_window(e->window)) == NULL)
|
|
new = 1;
|
|
|
|
#ifdef SWM_DEBUG
|
|
if (swm_debug & SWM_D_EVENT) {
|
|
print_win_geom(e->window);
|
|
|
|
DNPRINTF(SWM_D_EVENT, "configurerequest: window: 0x%x, "
|
|
"parent: 0x%x, new: %s, value_mask: %u { ", e->window,
|
|
e->parent, YESNO(new), e->value_mask);
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_X)
|
|
DPRINTF("X: %d ", e->x);
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_Y)
|
|
DPRINTF("Y: %d ", e->y);
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH)
|
|
DPRINTF("W: %u ", e->width);
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT)
|
|
DPRINTF("H: %u ", e->height);
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH)
|
|
DPRINTF("Border: %u ", e->border_width);
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_SIBLING)
|
|
DPRINTF("Sibling: 0x%x ", e->sibling);
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE)
|
|
DPRINTF("StackMode: %s(%u) ",
|
|
get_stack_mode_name(e->stack_mode), e->stack_mode);
|
|
DPRINTF("}\n");
|
|
}
|
|
#endif
|
|
|
|
if (new) {
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_X) {
|
|
mask |= XCB_CONFIG_WINDOW_X;
|
|
wc[i++] = e->x;
|
|
}
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_Y) {
|
|
mask |= XCB_CONFIG_WINDOW_Y;
|
|
wc[i++] = e->y;
|
|
}
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH) {
|
|
mask |= XCB_CONFIG_WINDOW_WIDTH;
|
|
wc[i++] = e->width;
|
|
}
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
|
|
mask |= XCB_CONFIG_WINDOW_HEIGHT;
|
|
wc[i++] = e->height;
|
|
}
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) {
|
|
mask |= XCB_CONFIG_WINDOW_BORDER_WIDTH;
|
|
wc[i++] = e->border_width;
|
|
}
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_SIBLING) {
|
|
mask |= XCB_CONFIG_WINDOW_SIBLING;
|
|
wc[i++] = e->sibling;
|
|
}
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) {
|
|
mask |= XCB_CONFIG_WINDOW_STACK_MODE;
|
|
wc[i++] = e->stack_mode;
|
|
}
|
|
|
|
if (mask != 0) {
|
|
xcb_configure_window(conn, e->window, mask, wc);
|
|
xcb_flush(conn);
|
|
}
|
|
} else if ((!win->manual || win->quirks & SWM_Q_ANYWHERE) &&
|
|
!(win->ewmh_flags & EWMH_F_FULLSCREEN)) {
|
|
if (win->ws->r)
|
|
r = win->ws->r;
|
|
else if (win->ws->old_r)
|
|
r = win->ws->old_r;
|
|
|
|
/* windows are centered unless ANYWHERE quirk is set. */
|
|
if (win->quirks & SWM_Q_ANYWHERE) {
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_X) {
|
|
win->g_float.x = e->x;
|
|
if (r)
|
|
win->g_float.x -= X(r);
|
|
}
|
|
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_Y) {
|
|
win->g_float.y = e->y;
|
|
if (r)
|
|
win->g_float.y -= Y(r);
|
|
}
|
|
}
|
|
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH)
|
|
win->g_float.w = e->width;
|
|
|
|
if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT)
|
|
win->g_float.h = e->height;
|
|
|
|
win->g_floatvalid = 1;
|
|
|
|
if (win->floating && r && (win->transient ||
|
|
win->ws->cur_layout != &layouts[SWM_MAX_STACK])) {
|
|
WIDTH(win) = win->g_float.w;
|
|
HEIGHT(win) = win->g_float.h;
|
|
|
|
if (r) {
|
|
stack_floater(win, r);
|
|
focus_flush();
|
|
}
|
|
} else {
|
|
config_win(win, e);
|
|
xcb_flush(conn);
|
|
}
|
|
} else {
|
|
config_win(win, e);
|
|
xcb_flush(conn);
|
|
}
|
|
|
|
DNPRINTF(SWM_D_EVENT, "configurerequest: done.\n");
|
|
}
|
|
|
|
void
|
|
configurenotify(xcb_configure_notify_event_t *e)
|
|
{
|
|
struct ws_win *win;
|
|
|
|
DNPRINTF(SWM_D_EVENT, "configurenotify: win 0x%x, event win: 0x%x, "
|
|
"(x,y) WxH: (%d,%d) %ux%u, border: %u, above_sibling: 0x%x, "
|
|
"override_redirect: %s\n", e->window, e->event, e->x, e->y,
|
|
e->width, e->height, e->border_width, e->above_sibling,
|
|
YESNO(e->override_redirect));
|
|
|
|
win = find_window(e->window);
|
|
if (win) {
|
|
xcb_icccm_get_wm_normal_hints_reply(conn,
|
|
xcb_icccm_get_wm_normal_hints(conn, win->id),
|
|
&win->sh, NULL);
|
|
adjust_font(win);
|
|
if (font_adjusted) {
|
|
stack();
|
|
xcb_flush(conn);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
destroynotify(xcb_destroy_notify_event_t *e)
|
|
{
|
|
struct ws_win *win;
|
|
|
|
DNPRINTF(SWM_D_EVENT, "destroynotify: window: 0x%x\n", e->window);
|
|
|
|
if ((win = find_window(e->window)) == NULL) {
|
|
if ((win = find_unmanaged_window(e->window)) == NULL)
|
|
return;
|
|
free_window(win);
|
|
return;
|
|
}
|
|
|
|
if (focus_mode != SWM_FOCUS_FOLLOW) {
|
|
/* If we were focused, make sure we focus on something else. */
|
|
if (win == win->ws->focus)
|
|
win->ws->focus_pending = get_focus_prev(win);
|
|
}
|
|
|
|
unmanage_window(win);
|
|
stack();
|
|
|
|
if (focus_mode != SWM_FOCUS_FOLLOW) {
|
|
if (win->ws->focus_pending) {
|
|
focus_win(win->ws->focus_pending);
|
|
win->ws->focus_pending = NULL;
|
|
}
|
|
}
|
|
|
|
free_window(win);
|
|
|
|
focus_flush();
|
|
}
|
|
|
|
#ifdef SWM_DEBUG
|
|
char *
|
|
get_notify_detail_label(uint8_t detail)
|
|
{
|
|
char *label;
|
|
|
|
switch (detail) {
|
|
case XCB_NOTIFY_DETAIL_ANCESTOR:
|
|
label = "Ancestor";
|
|
break;
|
|
case XCB_NOTIFY_DETAIL_VIRTUAL:
|
|
label = "Virtual";
|
|
break;
|
|
case XCB_NOTIFY_DETAIL_INFERIOR:
|
|
label = "Inferior";
|
|
break;
|
|
case XCB_NOTIFY_DETAIL_NONLINEAR:
|
|
label = "Nonlinear";
|
|
break;
|
|
case XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL:
|
|
label = "NonlinearVirtual";
|
|
break;
|
|
case XCB_NOTIFY_DETAIL_POINTER:
|
|
label = "Pointer";
|
|
break;
|
|
case XCB_NOTIFY_DETAIL_POINTER_ROOT:
|
|
label = "PointerRoot";
|
|
break;
|
|
case XCB_NOTIFY_DETAIL_NONE:
|
|
label = "None";
|
|
break;
|
|
default:
|
|
label = "Unknown";
|
|
}
|
|
|
|
return label;
|
|
}
|
|
|
|
char *
|
|
get_notify_mode_label(uint8_t mode)
|
|
{
|
|
char *label;
|
|
|
|
switch (mode) {
|
|
case XCB_NOTIFY_MODE_NORMAL:
|
|
label = "Normal";
|
|
break;
|
|
case XCB_NOTIFY_MODE_GRAB:
|
|
label = "Grab";
|
|
break;
|
|
case XCB_NOTIFY_MODE_UNGRAB:
|
|
label = "Ungrab";
|
|
break;
|
|
case XCB_NOTIFY_MODE_WHILE_GRABBED:
|
|
label = "WhileGrabbed";
|
|
break;
|
|
default:
|
|
label = "Unknown";
|
|
}
|
|
|
|
return label;
|
|
}
|
|
#endif
|
|
|
|
void
|
|
enternotify(xcb_enter_notify_event_t *e)
|
|
{
|
|
struct ws_win *win;
|
|
struct swm_region *r;
|
|
|
|
DNPRINTF(SWM_D_FOCUS, "enternotify: time: %u, win (x,y): 0x%x "
|
|
"(%d,%d), mode: %s(%d), detail: %s(%d), root (x,y): 0x%x (%d,%d), "
|
|
"child: 0x%x, same_screen_focus: %s, state: %d\n",
|
|
e->time, e->event, e->event_x, e->event_y,
|
|
get_notify_mode_label(e->mode), e->mode,
|
|
get_notify_detail_label(e->detail), e->detail,
|
|
e->root, e->root_x, e->root_y, e->child,
|
|
YESNO(e->same_screen_focus), e->state);
|
|
|
|
if (focus_mode == SWM_FOCUS_MANUAL &&
|
|
e->mode == XCB_NOTIFY_MODE_NORMAL) {
|
|
DNPRINTF(SWM_D_EVENT, "enternotify: manual focus; ignoring.\n");
|
|
return;
|
|
}
|
|
|
|
last_event_time = e->time;
|
|
|
|
if ((win = find_window(e->event)) == NULL) {
|
|
if (e->event == e->root) {
|
|
/* If no windows on pointer region, then focus root. */
|
|
r = root_to_region(e->root, SWM_CK_POINTER);
|
|
if (r == NULL) {
|
|
DNPRINTF(SWM_D_EVENT, "enternotify: "
|
|
"NULL region; ignoring.\n");
|
|
return;
|
|
}
|
|
|
|
focus_region(r);
|
|
} else {
|
|
DNPRINTF(SWM_D_EVENT, "enternotify: window is NULL; "
|
|
"ignoring\n");
|
|
return;
|
|
}
|
|
} else {
|
|
focus_win(get_focus_magic(win));
|
|
}
|
|
|
|
xcb_flush(conn);
|
|
}
|
|
|
|
#ifdef SWM_DEBUG
|
|
void
|
|
leavenotify(xcb_leave_notify_event_t *e)
|
|
{
|
|
DNPRINTF(SWM_D_FOCUS, "leavenotify: time: %u, win (x,y): 0x%x "
|
|
"(%d,%d), mode: %s(%d), detail: %s(%d), root (x,y): 0x%x (%d,%d), "
|
|
"child: 0x%x, same_screen_focus: %s, state: %d\n",
|
|
e->time, e->event, e->event_x, e->event_y,
|
|
get_notify_mode_label(e->mode), e->mode,
|
|
get_notify_detail_label(e->detail), e->detail,
|
|
e->root, e->root_x, e->root_y, e->child,
|
|
YESNO(e->same_screen_focus), e->state);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
mapnotify(xcb_map_notify_event_t *e)
|
|
{
|
|
struct ws_win *win;
|
|
|
|
DNPRINTF(SWM_D_EVENT, "mapnotify: window: 0x%x\n", e->window);
|
|
|
|
if ((win = find_window(e->window)) == NULL)
|
|
win = manage_window(e->window, 1);
|
|
|
|
win->mapped = 1;
|
|
set_win_state(win, XCB_ICCCM_WM_STATE_NORMAL);
|
|
|
|
if (focus_mode != SWM_FOCUS_FOLLOW) {
|
|
if (win->ws->focus_pending == win) {
|
|
focus_win(win);
|
|
win->ws->focus_pending = NULL;
|
|
focus_flush();
|
|
}
|
|
}
|
|
|
|
xcb_flush(conn);
|
|
}
|
|
|
|
void
|
|
mappingnotify(xcb_mapping_notify_event_t *e)
|
|
{
|
|
xcb_refresh_keyboard_mapping(syms, e);
|
|
|
|
if (e->request == XCB_MAPPING_KEYBOARD)
|
|
grabkeys();
|
|
}
|
|
|
|
void
|
|
maprequest(xcb_map_request_event_t *e)
|
|
{
|
|
struct ws_win *win;
|
|
xcb_get_window_attributes_reply_t *war;
|
|
|
|
DNPRINTF(SWM_D_EVENT, "maprequest: win 0x%x\n",
|
|
e->window);
|
|
|
|
war = xcb_get_window_attributes_reply(conn,
|
|
xcb_get_window_attributes(conn, e->window),
|
|
NULL);
|
|
if (war == NULL) {
|
|
DNPRINTF(SWM_D_EVENT, "maprequest: window lost.\n");
|
|
goto out;
|
|
}
|
|
|
|
if (war->override_redirect) {
|
|
DNPRINTF(SWM_D_EVENT, "maprequest: override_redirect; "
|
|
"skipping.\n");
|
|
goto out;
|
|
}
|
|
|
|
win = manage_window(e->window,
|
|
(war->map_state == XCB_MAP_STATE_VIEWABLE));
|
|
|
|
/* The new window should get focus; prepare. */
|
|
if (focus_mode != SWM_FOCUS_FOLLOW)
|
|
win->ws->focus_pending = get_focus_magic(win);
|
|
|
|
/* All windows need to be mapped if they are in the current workspace.*/
|
|
if (win->ws->r)
|
|
stack();
|
|
|
|
/* Ignore EnterNotify to handle the mapnotify without interference. */
|
|
if (focus_mode == SWM_FOCUS_DEFAULT)
|
|
event_drain(XCB_ENTER_NOTIFY);
|
|
out:
|
|
free(war);
|
|
DNPRINTF(SWM_D_EVENT, "maprequest: done.\n");
|
|
}
|
|
|
|
void
|
|
motionnotify(xcb_motion_notify_event_t *e)
|
|
{
|
|
struct swm_region *r;
|
|
int i, num_screens;
|
|
|
|
DNPRINTF(SWM_D_FOCUS, "motionnotify: time: %u, win (x,y): 0x%x "
|
|
"(%d,%d), detail: %s(%d), root (x,y): 0x%x (%d,%d), "
|
|
"child: 0x%x, same_screen_focus: %s, state: %d\n",
|
|
e->time, e->event, e->event_x, e->event_y,
|
|
get_notify_detail_label(e->detail), e->detail,
|
|
e->root, e->root_x, e->root_y, e->child,
|
|
YESNO(e->same_screen), e->state);
|
|
|
|
last_event_time = e->time;
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++)
|
|
if (screens[i].root == e->root)
|
|
break;
|
|
|
|
TAILQ_FOREACH(r, &screens[i].rl, entry)
|
|
if (X(r) <= e->root_x && e->root_x < MAX_X(r) &&
|
|
Y(r) <= e->root_y && e->root_y < MAX_Y(r))
|
|
break;
|
|
|
|
focus_region(r);
|
|
}
|
|
|
|
#ifdef SWM_DEBUG
|
|
char *
|
|
get_atom_name(xcb_atom_t atom)
|
|
{
|
|
char *name = NULL;
|
|
#if 0
|
|
/*
|
|
* This should be disabled during most debugging since
|
|
* xcb_get_* causes an xcb_flush.
|
|
*/
|
|
size_t len;
|
|
xcb_get_atom_name_reply_t *r;
|
|
|
|
r = xcb_get_atom_name_reply(conn,
|
|
xcb_get_atom_name(conn, atom),
|
|
NULL);
|
|
if (r) {
|
|
len = xcb_get_atom_name_name_length(r);
|
|
if (len > 0) {
|
|
name = malloc(len + 1);
|
|
if (name) {
|
|
memcpy(name, xcb_get_atom_name_name(r), len);
|
|
name[len] = '\0';
|
|
}
|
|
}
|
|
free(r);
|
|
}
|
|
#else
|
|
(void)atom;
|
|
#endif
|
|
return (name);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
propertynotify(xcb_property_notify_event_t *e)
|
|
{
|
|
struct ws_win *win;
|
|
#ifdef SWM_DEBUG
|
|
char *name;
|
|
|
|
name = get_atom_name(e->atom);
|
|
DNPRINTF(SWM_D_EVENT, "propertynotify: window: 0x%x, atom: %s(%u), "
|
|
"time: %#x, state: %u\n", e->window, name, e->atom, e->time,
|
|
e->state);
|
|
free(name);
|
|
#endif
|
|
win = find_window(e->window);
|
|
if (win == NULL)
|
|
return;
|
|
|
|
last_event_time = e->time;
|
|
|
|
if (e->atom == a_swm_iconic) {
|
|
if (e->state == XCB_PROPERTY_NEW_VALUE) {
|
|
if (focus_mode != SWM_FOCUS_FOLLOW)
|
|
win->ws->focus_pending = get_focus_prev(win);
|
|
|
|
unfocus_win(win);
|
|
unmap_window(win);
|
|
|
|
if (win->ws->r) {
|
|
stack();
|
|
if (focus_mode != SWM_FOCUS_FOLLOW) {
|
|
focus_win(win->ws->focus_pending);
|
|
win->ws->focus_pending = NULL;
|
|
}
|
|
focus_flush();
|
|
}
|
|
} else if (e->state == XCB_PROPERTY_DELETE) {
|
|
/* The window is no longer iconic, restack ws. */
|
|
if (focus_mode != SWM_FOCUS_FOLLOW)
|
|
win->ws->focus_pending = get_focus_magic(win);
|
|
|
|
stack();
|
|
|
|
/* Flush EnterNotify for mapnotify, if needed. */
|
|
focus_flush();
|
|
}
|
|
} else if (e->atom == a_state) {
|
|
/* State just changed, make sure it gets focused if mapped. */
|
|
if (e->state == XCB_PROPERTY_NEW_VALUE) {
|
|
if (focus_mode != SWM_FOCUS_FOLLOW) {
|
|
if (win->mapped &&
|
|
win->ws->focus_pending == win) {
|
|
focus_win(win->ws->focus_pending);
|
|
win->ws->focus_pending = NULL;
|
|
}
|
|
}
|
|
}
|
|
} else if (e->atom == XCB_ATOM_WM_CLASS ||
|
|
e->atom == XCB_ATOM_WM_NAME) {
|
|
bar_draw();
|
|
}
|
|
|
|
xcb_flush(conn);
|
|
}
|
|
|
|
void
|
|
unmapnotify(xcb_unmap_notify_event_t *e)
|
|
{
|
|
struct ws_win *win;
|
|
struct workspace *ws;
|
|
|
|
DNPRINTF(SWM_D_EVENT, "unmapnotify: window: 0x%x\n", e->window);
|
|
|
|
/* If we aren't managing the window, then ignore. */
|
|
win = find_window(e->window);
|
|
if (win == NULL || win->id != e->window)
|
|
return;
|
|
|
|
ws = win->ws;
|
|
|
|
if (getstate(e->window) != XCB_ICCCM_WM_STATE_ICONIC)
|
|
set_win_state(win, XCB_ICCCM_WM_STATE_ICONIC);
|
|
|
|
if (win->mapped) {
|
|
/* window unmapped itself */
|
|
/* do unmap/unfocus/restack and unmanage */
|
|
win->mapped = 0;
|
|
|
|
/* If win was focused, make sure to focus on something else. */
|
|
if (win == ws->focus) {
|
|
if (focus_mode != SWM_FOCUS_FOLLOW) {
|
|
ws->focus_pending = get_focus_prev(win);
|
|
DNPRINTF(SWM_D_EVENT, "unmapnotify: "
|
|
"focus_pending: 0x%x\n",
|
|
WINID(ws->focus_pending));
|
|
}
|
|
|
|
unfocus_win(win);
|
|
}
|
|
|
|
unmanage_window(win);
|
|
|
|
if (ws->r)
|
|
stack();
|
|
|
|
if (focus_mode == SWM_FOCUS_FOLLOW) {
|
|
if (ws->r)
|
|
focus_win(get_pointer_win(ws->r->s->root));
|
|
} else {
|
|
if (ws->focus_pending) {
|
|
focus_win(ws->focus_pending);
|
|
ws->focus_pending = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (getstate(e->window) == XCB_ICCCM_WM_STATE_NORMAL)
|
|
set_win_state(win, XCB_ICCCM_WM_STATE_ICONIC);
|
|
|
|
focus_flush();
|
|
}
|
|
|
|
#if 0
|
|
void
|
|
visibilitynotify(xcb_visibility_notify_event_t *e)
|
|
{
|
|
DNPRINTF(SWM_D_EVENT, "visibilitynotify: window: 0x%x\n",
|
|
e->window);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
clientmessage(xcb_client_message_event_t *e)
|
|
{
|
|
struct ws_win *win;
|
|
xcb_map_request_event_t mre;
|
|
#ifdef SWM_DEBUG
|
|
char *name;
|
|
|
|
name = get_atom_name(e->type);
|
|
DNPRINTF(SWM_D_EVENT, "clientmessage: window: 0x%x, atom: %s(%u)\n",
|
|
e->window, name, e->type);
|
|
free(name);
|
|
#endif
|
|
win = find_window(e->window);
|
|
|
|
if (win == NULL) {
|
|
if (e->type == ewmh[_NET_ACTIVE_WINDOW].atom) {
|
|
/* Manage the window with maprequest. */
|
|
DNPRINTF(SWM_D_EVENT, "clientmessage: request focus on "
|
|
"unmanaged window.\n");
|
|
mre.window = e->window;
|
|
maprequest(&mre);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (e->type == ewmh[_NET_ACTIVE_WINDOW].atom) {
|
|
DNPRINTF(SWM_D_EVENT, "clientmessage: _NET_ACTIVE_WINDOW\n");
|
|
focus_win(win);
|
|
}
|
|
if (e->type == ewmh[_NET_CLOSE_WINDOW].atom) {
|
|
DNPRINTF(SWM_D_EVENT, "clientmessage: _NET_CLOSE_WINDOW\n");
|
|
if (win->can_delete)
|
|
client_msg(win, a_delete, 0);
|
|
else
|
|
xcb_kill_client(conn, win->id);
|
|
}
|
|
if (e->type == ewmh[_NET_MOVERESIZE_WINDOW].atom) {
|
|
DNPRINTF(SWM_D_EVENT,
|
|
"clientmessage: _NET_MOVERESIZE_WINDOW\n");
|
|
if (win->floating) {
|
|
if (e->data.data32[0] & (1<<8)) /* x */
|
|
X(win) = e->data.data32[1];
|
|
if (e->data.data32[0] & (1<<9)) /* y */
|
|
Y(win) = e->data.data32[2];
|
|
if (e->data.data32[0] & (1<<10)) /* width */
|
|
WIDTH(win) = e->data.data32[3];
|
|
if (e->data.data32[0] & (1<<11)) /* height */
|
|
HEIGHT(win) = e->data.data32[4];
|
|
|
|
update_window(win);
|
|
}
|
|
else {
|
|
/* TODO: Change stack sizes */
|
|
/* notify no change was made. */
|
|
config_win(win, NULL);
|
|
}
|
|
}
|
|
if (e->type == ewmh[_NET_WM_STATE].atom) {
|
|
DNPRINTF(SWM_D_EVENT, "clientmessage: _NET_WM_STATE\n");
|
|
ewmh_update_win_state(win, e->data.data32[1], e->data.data32[0]);
|
|
if (e->data.data32[2])
|
|
ewmh_update_win_state(win, e->data.data32[2],
|
|
e->data.data32[0]);
|
|
|
|
stack();
|
|
}
|
|
|
|
focus_flush();
|
|
}
|
|
|
|
void
|
|
check_conn(void)
|
|
{
|
|
int errcode = xcb_connection_has_error(conn);
|
|
#ifdef XCB_CONN_ERROR
|
|
char *s;
|
|
switch (errcode) {
|
|
case XCB_CONN_ERROR:
|
|
s = "Socket error, pipe error or other stream error.";
|
|
break;
|
|
case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:
|
|
s = "Extension not supported.";
|
|
break;
|
|
case XCB_CONN_CLOSED_MEM_INSUFFICIENT:
|
|
s = "Insufficient memory.";
|
|
break;
|
|
case XCB_CONN_CLOSED_REQ_LEN_EXCEED:
|
|
s = "Request length was exceeded.";
|
|
break;
|
|
case XCB_CONN_CLOSED_PARSE_ERR:
|
|
s = "Error parsing display string.";
|
|
break;
|
|
default:
|
|
s = "Unknown error.";
|
|
}
|
|
if (errcode)
|
|
errx(errcode, "X CONNECTION ERROR: %s", s);
|
|
#else
|
|
if (errcode)
|
|
errx(errcode, "X CONNECTION ERROR");
|
|
#endif
|
|
}
|
|
|
|
int
|
|
enable_wm(void)
|
|
{
|
|
int num_screens, i;
|
|
const uint32_t val = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
|
|
XCB_EVENT_MASK_ENTER_WINDOW;
|
|
xcb_screen_t *sc;
|
|
xcb_void_cookie_t wac;
|
|
xcb_generic_error_t *error;
|
|
|
|
/* this causes an error if some other window manager is running */
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++) {
|
|
if ((sc = get_screen(i)) == NULL)
|
|
errx(1, "ERROR: can't get screen %d.", i);
|
|
DNPRINTF(SWM_D_INIT, "enable_wm: screen %d, root: 0x%x\n",
|
|
i, sc->root);
|
|
wac = xcb_change_window_attributes_checked(conn, sc->root,
|
|
XCB_CW_EVENT_MASK, &val);
|
|
if ((error = xcb_request_check(conn, wac))) {
|
|
DNPRINTF(SWM_D_INIT, "enable_wm: error_code: %u\n",
|
|
error->error_code);
|
|
free(error);
|
|
return 1;
|
|
}
|
|
|
|
/* click to focus on empty region */
|
|
xcb_grab_button(conn, 1, sc->root, BUTTONMASK,
|
|
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, XCB_WINDOW_NONE,
|
|
XCB_CURSOR_NONE, XCB_BUTTON_INDEX_1, XCB_BUTTON_MASK_ANY);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
new_region(struct swm_screen *s, int x, int y, int w, int h)
|
|
{
|
|
struct swm_region *r, *n;
|
|
struct workspace *ws = NULL;
|
|
int i;
|
|
uint32_t wa[1];
|
|
|
|
DNPRINTF(SWM_D_MISC, "new region: screen[%d]:%dx%d+%d+%d\n",
|
|
s->idx, w, h, x, y);
|
|
|
|
/* remove any conflicting regions */
|
|
n = TAILQ_FIRST(&s->rl);
|
|
while (n) {
|
|
r = n;
|
|
n = TAILQ_NEXT(r, entry);
|
|
if (X(r) < (x + w) && (X(r) + WIDTH(r)) > x &&
|
|
Y(r) < (y + h) && (Y(r) + HEIGHT(r)) > y) {
|
|
if (r->ws->r != NULL)
|
|
r->ws->old_r = r->ws->r;
|
|
r->ws->r = NULL;
|
|
bar_cleanup(r);
|
|
xcb_destroy_window(conn, r->id);
|
|
TAILQ_REMOVE(&s->rl, r, entry);
|
|
TAILQ_INSERT_TAIL(&s->orl, r, entry);
|
|
}
|
|
}
|
|
|
|
/* search old regions for one to reuse */
|
|
|
|
/* size + location match */
|
|
TAILQ_FOREACH(r, &s->orl, entry)
|
|
if (X(r) == x && Y(r) == y &&
|
|
HEIGHT(r) == h && WIDTH(r) == w)
|
|
break;
|
|
|
|
/* size match */
|
|
TAILQ_FOREACH(r, &s->orl, entry)
|
|
if (HEIGHT(r) == h && WIDTH(r) == w)
|
|
break;
|
|
|
|
if (r != NULL) {
|
|
TAILQ_REMOVE(&s->orl, r, entry);
|
|
/* try to use old region's workspace */
|
|
if (r->ws->r == NULL)
|
|
ws = r->ws;
|
|
} else
|
|
if ((r = calloc(1, sizeof(struct swm_region))) == NULL)
|
|
err(1, "new_region: calloc: failed to allocate memory "
|
|
"for screen");
|
|
|
|
/* if we don't have a workspace already, find one */
|
|
if (ws == NULL) {
|
|
for (i = 0; i < workspace_limit; i++)
|
|
if (s->ws[i].r == NULL) {
|
|
ws = &s->ws[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ws == NULL)
|
|
errx(1, "new_region: no free workspaces");
|
|
|
|
X(r) = x;
|
|
Y(r) = y;
|
|
WIDTH(r) = w;
|
|
HEIGHT(r) = h;
|
|
r->s = s;
|
|
r->ws = ws;
|
|
r->ws_prior = NULL;
|
|
ws->r = r;
|
|
outputs++;
|
|
TAILQ_INSERT_TAIL(&s->rl, r, entry);
|
|
|
|
/* Invisible region window to detect pointer events on empty regions. */
|
|
r->id = xcb_generate_id(conn);
|
|
wa[0] = XCB_EVENT_MASK_POINTER_MOTION |
|
|
XCB_EVENT_MASK_POINTER_MOTION_HINT;
|
|
|
|
xcb_create_window(conn, XCB_COPY_FROM_PARENT, r->id, r->s->root,
|
|
X(r), Y(r), WIDTH(r), HEIGHT(r), 0, XCB_WINDOW_CLASS_INPUT_ONLY,
|
|
XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, wa);
|
|
|
|
xcb_map_window(conn, r->id);
|
|
}
|
|
|
|
void
|
|
scan_xrandr(int i)
|
|
{
|
|
#ifdef SWM_XRR_HAS_CRTC
|
|
int c;
|
|
int ncrtc = 0;
|
|
#endif /* SWM_XRR_HAS_CRTC */
|
|
struct swm_region *r;
|
|
struct ws_win *win;
|
|
int num_screens;
|
|
xcb_randr_get_screen_resources_current_cookie_t src;
|
|
xcb_randr_get_screen_resources_current_reply_t *srr;
|
|
xcb_randr_get_crtc_info_cookie_t cic;
|
|
xcb_randr_get_crtc_info_reply_t *cir = NULL;
|
|
xcb_randr_crtc_t *crtc;
|
|
xcb_screen_t *screen;
|
|
|
|
DNPRINTF(SWM_D_MISC, "scan_xrandr: screen: %d\n", i);
|
|
|
|
if ((screen = get_screen(i)) == NULL)
|
|
errx(1, "ERROR: can't get screen %d.", i);
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
if (i >= num_screens)
|
|
errx(1, "scan_xrandr: invalid screen");
|
|
|
|
/* remove any old regions */
|
|
while ((r = TAILQ_FIRST(&screens[i].rl)) != NULL) {
|
|
r->ws->old_r = r->ws->r = NULL;
|
|
bar_cleanup(r);
|
|
xcb_destroy_window(conn, r->id);
|
|
TAILQ_REMOVE(&screens[i].rl, r, entry);
|
|
TAILQ_INSERT_TAIL(&screens[i].orl, r, entry);
|
|
}
|
|
outputs = 0;
|
|
|
|
/* map virtual screens onto physical screens */
|
|
#ifdef SWM_XRR_HAS_CRTC
|
|
if (xrandr_support) {
|
|
src = xcb_randr_get_screen_resources_current(conn,
|
|
screens[i].root);
|
|
srr = xcb_randr_get_screen_resources_current_reply(conn, src,
|
|
NULL);
|
|
if (srr == NULL) {
|
|
new_region(&screens[i], 0, 0,
|
|
screen->width_in_pixels,
|
|
screen->height_in_pixels);
|
|
goto out;
|
|
} else
|
|
ncrtc = srr->num_crtcs;
|
|
|
|
crtc = xcb_randr_get_screen_resources_current_crtcs(srr);
|
|
for (c = 0; c < ncrtc; c++) {
|
|
cic = xcb_randr_get_crtc_info(conn, crtc[c],
|
|
XCB_CURRENT_TIME);
|
|
cir = xcb_randr_get_crtc_info_reply(conn, cic, NULL);
|
|
if (cir == NULL)
|
|
continue;
|
|
if (cir->num_outputs == 0) {
|
|
free(cir);
|
|
continue;
|
|
}
|
|
|
|
if (cir->mode == 0)
|
|
new_region(&screens[i], 0, 0,
|
|
screen->width_in_pixels,
|
|
screen->height_in_pixels);
|
|
else
|
|
new_region(&screens[i],
|
|
cir->x, cir->y, cir->width, cir->height);
|
|
free(cir);
|
|
}
|
|
free(srr);
|
|
}
|
|
#endif /* SWM_XRR_HAS_CRTC */
|
|
|
|
/* If detection failed, create a single region that spans the screen. */
|
|
if (TAILQ_EMPTY(&screens[i].rl))
|
|
new_region(&screens[i], 0, 0, screen->width_in_pixels,
|
|
screen->height_in_pixels);
|
|
|
|
out:
|
|
/* Cleanup unused previously visible workspaces. */
|
|
TAILQ_FOREACH(r, &screens[i].orl, entry) {
|
|
TAILQ_FOREACH(win, &r->ws->winlist, entry)
|
|
unmap_window(win);
|
|
|
|
/* The screen shouldn't focus on an unused region. */
|
|
if (screens[i].r_focus == r)
|
|
screens[i].r_focus = NULL;
|
|
}
|
|
|
|
DNPRINTF(SWM_D_MISC, "scan_xrandr: done.\n");
|
|
}
|
|
|
|
void
|
|
screenchange(xcb_randr_screen_change_notify_event_t *e)
|
|
{
|
|
struct swm_region *r;
|
|
int i, num_screens;
|
|
|
|
DNPRINTF(SWM_D_EVENT, "screenchange: root: 0x%x\n", e->root);
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
/* silly event doesn't include the screen index */
|
|
for (i = 0; i < num_screens; i++)
|
|
if (screens[i].root == e->root)
|
|
break;
|
|
if (i >= num_screens)
|
|
errx(1, "screenchange: screen not found");
|
|
|
|
/* brute force for now, just re-enumerate the regions */
|
|
scan_xrandr(i);
|
|
|
|
#ifdef SWM_DEBUG
|
|
print_win_geom(e->root);
|
|
#endif
|
|
/* add bars to all regions */
|
|
for (i = 0; i < num_screens; i++) {
|
|
TAILQ_FOREACH(r, &screens[i].rl, entry)
|
|
bar_setup(r);
|
|
}
|
|
|
|
stack();
|
|
|
|
/* Make sure a region has focus on each screen. */
|
|
for (i = 0; i < num_screens; i++) {
|
|
if (screens[i].r_focus == NULL) {
|
|
r = TAILQ_FIRST(&screens[i].rl);
|
|
if (r != NULL)
|
|
focus_region(r);
|
|
}
|
|
}
|
|
|
|
bar_draw();
|
|
focus_flush();
|
|
}
|
|
|
|
void
|
|
grab_windows(void)
|
|
{
|
|
struct swm_region *r = NULL;
|
|
xcb_window_t *wins = NULL, trans;
|
|
int no;
|
|
int i, j, num_screens;
|
|
uint16_t state, manage, mapped;
|
|
|
|
xcb_query_tree_cookie_t qtc;
|
|
xcb_query_tree_reply_t *qtr;
|
|
xcb_get_window_attributes_cookie_t gac;
|
|
xcb_get_window_attributes_reply_t *gar;
|
|
xcb_get_property_cookie_t pc;
|
|
|
|
DNPRINTF(SWM_D_INIT, "grab_windows: begin\n");
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++) {
|
|
qtc = xcb_query_tree(conn, screens[i].root);
|
|
qtr = xcb_query_tree_reply(conn, qtc, NULL);
|
|
if (!qtr)
|
|
continue;
|
|
wins = xcb_query_tree_children(qtr);
|
|
no = xcb_query_tree_children_length(qtr);
|
|
/* attach windows to a region */
|
|
/* normal windows */
|
|
DNPRINTF(SWM_D_INIT, "grab_windows: grab top level windows.\n");
|
|
for (j = 0; j < no; j++) {
|
|
TAILQ_FOREACH(r, &screens[i].rl, entry) {
|
|
if (r->id == wins[j]) {
|
|
DNPRINTF(SWM_D_INIT, "grab_windows: "
|
|
"skip %#x; region input window.\n",
|
|
wins[j]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (r)
|
|
continue;
|
|
|
|
gac = xcb_get_window_attributes(conn, wins[j]);
|
|
gar = xcb_get_window_attributes_reply(conn, gac, NULL);
|
|
if (!gar) {
|
|
DNPRINTF(SWM_D_INIT, "grab_windows: skip %#x; "
|
|
"doesn't exist.\n", wins[j]);
|
|
continue;
|
|
}
|
|
|
|
if (gar->override_redirect) {
|
|
DNPRINTF(SWM_D_INIT, "grab_windows: skip %#x; "
|
|
"override_redirect set.\n", wins[j]);
|
|
free(gar);
|
|
continue;
|
|
}
|
|
|
|
pc = xcb_icccm_get_wm_transient_for(conn, wins[j]);
|
|
if (xcb_icccm_get_wm_transient_for_reply(conn, pc,
|
|
&trans, NULL)) {
|
|
DNPRINTF(SWM_D_INIT, "grab_windows: skip %#x; "
|
|
"is transient for %#x.\n", wins[j], trans);
|
|
free(gar);
|
|
continue;
|
|
}
|
|
|
|
state = getstate(wins[j]);
|
|
manage = state != XCB_ICCCM_WM_STATE_WITHDRAWN;
|
|
mapped = gar->map_state != XCB_MAP_STATE_UNMAPPED;
|
|
if (mapped || manage)
|
|
manage_window(wins[j], mapped);
|
|
free(gar);
|
|
}
|
|
/* transient windows */
|
|
DNPRINTF(SWM_D_INIT, "grab_windows: grab transient windows.\n");
|
|
for (j = 0; j < no; j++) {
|
|
gac = xcb_get_window_attributes(conn, wins[j]);
|
|
gar = xcb_get_window_attributes_reply(conn, gac, NULL);
|
|
if (!gar) {
|
|
DNPRINTF(SWM_D_INIT, "grab_windows: skip %#x; "
|
|
"doesn't exist.\n", wins[j]);
|
|
continue;
|
|
}
|
|
|
|
if (gar->override_redirect) {
|
|
DNPRINTF(SWM_D_INIT, "grab_windows: skip %#x; "
|
|
"override_redirect set.\n", wins[j]);
|
|
free(gar);
|
|
continue;
|
|
}
|
|
|
|
state = getstate(wins[j]);
|
|
manage = state != XCB_ICCCM_WM_STATE_WITHDRAWN;
|
|
mapped = gar->map_state != XCB_MAP_STATE_UNMAPPED;
|
|
pc = xcb_icccm_get_wm_transient_for(conn, wins[j]);
|
|
if (xcb_icccm_get_wm_transient_for_reply(conn, pc,
|
|
&trans, NULL) && manage)
|
|
manage_window(wins[j], mapped);
|
|
free(gar);
|
|
}
|
|
free(qtr);
|
|
}
|
|
DNPRINTF(SWM_D_INIT, "grab_windows: done.\n");
|
|
}
|
|
|
|
void
|
|
setup_screens(void)
|
|
{
|
|
int i, j, k, num_screens;
|
|
struct workspace *ws;
|
|
uint32_t gcv[1], wa[1];
|
|
const xcb_query_extension_reply_t *qep;
|
|
xcb_screen_t *screen;
|
|
xcb_randr_query_version_cookie_t c;
|
|
xcb_randr_query_version_reply_t *r;
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
if ((screens = calloc(num_screens,
|
|
sizeof(struct swm_screen))) == NULL)
|
|
err(1, "setup_screens: calloc: failed to allocate memory for "
|
|
"screens");
|
|
|
|
/* initial Xrandr setup */
|
|
xrandr_support = 0;
|
|
qep = xcb_get_extension_data(conn, &xcb_randr_id);
|
|
if (qep->present) {
|
|
c = xcb_randr_query_version(conn, 1, 1);
|
|
r = xcb_randr_query_version_reply(conn, c, NULL);
|
|
if (r) {
|
|
if (r->major_version >= 1) {
|
|
xrandr_support = 1;
|
|
xrandr_eventbase = qep->first_event;
|
|
}
|
|
free(r);
|
|
}
|
|
}
|
|
|
|
wa[0] = cursors[XC_LEFT_PTR].cid;
|
|
|
|
/* map physical screens */
|
|
for (i = 0; i < num_screens; i++) {
|
|
DNPRINTF(SWM_D_WS, "setup_screens: init screen: %d\n", i);
|
|
screens[i].idx = i;
|
|
screens[i].r_focus = NULL;
|
|
|
|
TAILQ_INIT(&screens[i].rl);
|
|
TAILQ_INIT(&screens[i].orl);
|
|
if ((screen = get_screen(i)) == NULL)
|
|
errx(1, "ERROR: can't get screen %d.", i);
|
|
screens[i].root = screen->root;
|
|
|
|
/* set default colors */
|
|
setscreencolor("red", i + 1, SWM_S_COLOR_FOCUS);
|
|
setscreencolor("rgb:88/88/88", i + 1, SWM_S_COLOR_UNFOCUS);
|
|
setscreencolor("rgb:00/80/80", i + 1, SWM_S_COLOR_BAR_BORDER);
|
|
setscreencolor("rgb:00/40/40", i + 1,
|
|
SWM_S_COLOR_BAR_BORDER_UNFOCUS);
|
|
setscreencolor("black", i + 1, SWM_S_COLOR_BAR);
|
|
setscreencolor("rgb:a0/a0/a0", i + 1, SWM_S_COLOR_BAR_FONT);
|
|
|
|
/* create graphics context on screen */
|
|
screens[i].bar_gc = xcb_generate_id(conn);
|
|
gcv[0] = 0;
|
|
xcb_create_gc(conn, screens[i].bar_gc, screens[i].root,
|
|
XCB_GC_GRAPHICS_EXPOSURES, gcv);
|
|
|
|
/* set default cursor */
|
|
xcb_change_window_attributes(conn, screens[i].root,
|
|
XCB_CW_CURSOR, wa);
|
|
|
|
/* init all workspaces */
|
|
/* XXX these should be dynamically allocated too */
|
|
for (j = 0; j < SWM_WS_MAX; j++) {
|
|
ws = &screens[i].ws[j];
|
|
ws->idx = j;
|
|
ws->name = NULL;
|
|
ws->bar_enabled = 1;
|
|
ws->focus = NULL;
|
|
ws->focus_prev = NULL;
|
|
ws->focus_pending = NULL;
|
|
ws->r = NULL;
|
|
ws->old_r = NULL;
|
|
TAILQ_INIT(&ws->winlist);
|
|
TAILQ_INIT(&ws->unmanagedlist);
|
|
|
|
for (k = 0; layouts[k].l_stack != NULL; k++)
|
|
if (layouts[k].l_config != NULL)
|
|
layouts[k].l_config(ws,
|
|
SWM_ARG_ID_STACKINIT);
|
|
ws->cur_layout = &layouts[0];
|
|
ws->cur_layout->l_string(ws);
|
|
}
|
|
|
|
scan_xrandr(i);
|
|
|
|
if (xrandr_support)
|
|
xcb_randr_select_input(conn, screens[i].root,
|
|
XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE);
|
|
}
|
|
}
|
|
|
|
void
|
|
setup_globals(void)
|
|
{
|
|
if ((bar_fonts = strdup(SWM_BAR_FONTS)) == NULL)
|
|
err(1, "setup_globals: strdup: failed to allocate memory.");
|
|
|
|
if ((clock_format = strdup("%a %b %d %R %Z %Y")) == NULL)
|
|
err(1, "setup_globals: strdup: failed to allocate memory.");
|
|
|
|
if ((syms = xcb_key_symbols_alloc(conn)) == NULL)
|
|
errx(1, "unable to allocate key symbols");
|
|
|
|
a_state = get_atom_from_string("WM_STATE");
|
|
a_prot = get_atom_from_string("WM_PROTOCOLS");
|
|
a_delete = get_atom_from_string("WM_DELETE_WINDOW");
|
|
a_takefocus = get_atom_from_string("WM_TAKE_FOCUS");
|
|
a_wmname = get_atom_from_string("WM_NAME");
|
|
a_netwmname = get_atom_from_string("_NET_WM_NAME");
|
|
a_utf8_string = get_atom_from_string("UTF8_STRING");
|
|
a_string = get_atom_from_string("STRING");
|
|
a_swm_iconic = get_atom_from_string("_SWM_ICONIC");
|
|
a_swm_ws = get_atom_from_string("_SWM_WS");
|
|
}
|
|
|
|
void
|
|
workaround(void)
|
|
{
|
|
int i, num_screens;
|
|
xcb_atom_t netwmcheck;
|
|
xcb_window_t root, win;
|
|
|
|
/* work around sun jdk bugs, code from wmname */
|
|
netwmcheck = get_atom_from_string("_NET_SUPPORTING_WM_CHECK");
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++) {
|
|
root = screens[i].root;
|
|
|
|
win = xcb_generate_id(conn);
|
|
xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, root,
|
|
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
|
XCB_COPY_FROM_PARENT, 0, NULL);
|
|
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
|
|
netwmcheck, XCB_ATOM_WINDOW, 32, 1, &win);
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win,
|
|
netwmcheck, XCB_ATOM_WINDOW, 32, 1, &win);
|
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win,
|
|
a_netwmname, a_utf8_string, 8, strlen("LG3D"), "LG3D");
|
|
}
|
|
}
|
|
|
|
void
|
|
shutdown_cleanup(void)
|
|
{
|
|
int i, num_screens;
|
|
|
|
/* disable alarm because the following code may not be interrupted */
|
|
alarm(0);
|
|
if (signal(SIGALRM, SIG_IGN) == SIG_ERR)
|
|
err(1, "can't disable alarm");
|
|
|
|
bar_extra_stop();
|
|
unmap_all();
|
|
|
|
cursors_cleanup();
|
|
|
|
teardown_ewmh();
|
|
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; ++i) {
|
|
if (screens[i].bar_gc != 0)
|
|
xcb_free_gc(conn, screens[i].bar_gc);
|
|
if (!bar_font_legacy)
|
|
XftColorFree(display, DefaultVisual(display, i),
|
|
DefaultColormap(display, i), &bar_font_color);
|
|
}
|
|
|
|
if (bar_font_legacy)
|
|
XFreeFontSet(display, bar_fs);
|
|
else {
|
|
XftFontClose(display, bar_font);
|
|
}
|
|
|
|
xcb_key_symbols_free(syms);
|
|
xcb_flush(conn);
|
|
xcb_disconnect(conn);
|
|
}
|
|
|
|
void
|
|
event_error(xcb_generic_error_t *e)
|
|
{
|
|
(void)e;
|
|
|
|
DNPRINTF(SWM_D_EVENT, "event_error: %s(%u) from %s(%u), sequence: %u, "
|
|
"resource_id: %u, minor_code: %u\n",
|
|
xcb_event_get_error_label(e->error_code), e->error_code,
|
|
xcb_event_get_request_label(e->major_code), e->major_code,
|
|
e->sequence, e->resource_id, e->minor_code);
|
|
}
|
|
|
|
void
|
|
event_handle(xcb_generic_event_t *evt)
|
|
{
|
|
uint8_t type = XCB_EVENT_RESPONSE_TYPE(evt);
|
|
|
|
DNPRINTF(SWM_D_EVENT, "XCB Event: %s(%d), seq %u\n",
|
|
xcb_event_get_label(XCB_EVENT_RESPONSE_TYPE(evt)),
|
|
XCB_EVENT_RESPONSE_TYPE(evt), evt->sequence);
|
|
|
|
switch (type) {
|
|
#define EVENT(type, callback) case type: callback((void *)evt); return
|
|
EVENT(0, event_error);
|
|
EVENT(XCB_BUTTON_PRESS, buttonpress);
|
|
/*EVENT(XCB_BUTTON_RELEASE, buttonpress);*/
|
|
/*EVENT(XCB_CIRCULATE_NOTIFY, );*/
|
|
/*EVENT(XCB_CIRCULATE_REQUEST, );*/
|
|
EVENT(XCB_CLIENT_MESSAGE, clientmessage);
|
|
/*EVENT(XCB_COLORMAP_NOTIFY, );*/
|
|
EVENT(XCB_CONFIGURE_NOTIFY, configurenotify);
|
|
EVENT(XCB_CONFIGURE_REQUEST, configurerequest);
|
|
/*EVENT(XCB_CREATE_NOTIFY, );*/
|
|
EVENT(XCB_DESTROY_NOTIFY, destroynotify);
|
|
EVENT(XCB_ENTER_NOTIFY, enternotify);
|
|
EVENT(XCB_EXPOSE, expose);
|
|
#ifdef SWM_DEBUG
|
|
EVENT(XCB_FOCUS_IN, focusin);
|
|
EVENT(XCB_FOCUS_OUT, focusout);
|
|
#endif
|
|
/*EVENT(XCB_GRAPHICS_EXPOSURE, );*/
|
|
/*EVENT(XCB_GRAVITY_NOTIFY, );*/
|
|
EVENT(XCB_KEY_PRESS, keypress);
|
|
/*EVENT(XCB_KEY_RELEASE, keypress);*/
|
|
/*EVENT(XCB_KEYMAP_NOTIFY, );*/
|
|
#ifdef SWM_DEBUG
|
|
EVENT(XCB_LEAVE_NOTIFY, leavenotify);
|
|
#endif
|
|
EVENT(XCB_MAP_NOTIFY, mapnotify);
|
|
EVENT(XCB_MAP_REQUEST, maprequest);
|
|
EVENT(XCB_MAPPING_NOTIFY, mappingnotify);
|
|
EVENT(XCB_MOTION_NOTIFY, motionnotify);
|
|
/*EVENT(XCB_NO_EXPOSURE, );*/
|
|
EVENT(XCB_PROPERTY_NOTIFY, propertynotify);
|
|
/*EVENT(XCB_REPARENT_NOTIFY, );*/
|
|
/*EVENT(XCB_RESIZE_REQUEST, );*/
|
|
/*EVENT(XCB_SELECTION_CLEAR, );*/
|
|
/*EVENT(XCB_SELECTION_NOTIFY, );*/
|
|
/*EVENT(XCB_SELECTION_REQUEST, );*/
|
|
EVENT(XCB_UNMAP_NOTIFY, unmapnotify);
|
|
/*EVENT(XCB_VISIBILITY_NOTIFY, visibilitynotify);*/
|
|
#undef EVENT
|
|
}
|
|
if (type - xrandr_eventbase == XCB_RANDR_SCREEN_CHANGE_NOTIFY)
|
|
screenchange((void *)evt);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
struct swm_region *r;
|
|
char conf[PATH_MAX], *cfile = NULL;
|
|
struct stat sb;
|
|
int xfd, i, num_screens, startup = 1;
|
|
struct sigaction sact;
|
|
xcb_generic_event_t *evt;
|
|
struct timeval tv;
|
|
fd_set rd;
|
|
int rd_max;
|
|
int stdin_ready = 0;
|
|
int num_readable;
|
|
|
|
/* suppress unused warning since var is needed */
|
|
(void)argc;
|
|
|
|
time_started = time(NULL);
|
|
|
|
start_argv = argv;
|
|
warnx("Welcome to spectrwm V%s Build: %s", SPECTRWM_VERSION, buildstr);
|
|
if (!setlocale(LC_CTYPE, "") || !setlocale(LC_TIME, ""))
|
|
warnx("no locale support");
|
|
|
|
/* handle some signals */
|
|
bzero(&sact, sizeof(sact));
|
|
sigemptyset(&sact.sa_mask);
|
|
sact.sa_flags = 0;
|
|
sact.sa_handler = sighdlr;
|
|
sigaction(SIGINT, &sact, NULL);
|
|
sigaction(SIGQUIT, &sact, NULL);
|
|
sigaction(SIGTERM, &sact, NULL);
|
|
sigaction(SIGHUP, &sact, NULL);
|
|
|
|
sact.sa_handler = sighdlr;
|
|
sact.sa_flags = SA_NOCLDSTOP;
|
|
sigaction(SIGCHLD, &sact, NULL);
|
|
|
|
if (!(display = XOpenDisplay(0)))
|
|
errx(1, "can not open display");
|
|
|
|
conn = XGetXCBConnection(display);
|
|
if (xcb_connection_has_error(conn))
|
|
errx(1, "can not get XCB connection");
|
|
|
|
XSetEventQueueOwner(display, XCBOwnsEventQueue);
|
|
|
|
xcb_prefetch_extension_data(conn, &xcb_randr_id);
|
|
xfd = xcb_get_file_descriptor(conn);
|
|
|
|
/* look for local and global conf file */
|
|
pwd = getpwuid(getuid());
|
|
if (pwd == NULL)
|
|
errx(1, "invalid user: %d", getuid());
|
|
|
|
xcb_grab_server(conn);
|
|
xcb_aux_sync(conn);
|
|
|
|
/* flush all events */
|
|
while ((evt = xcb_poll_for_event(conn))) {
|
|
if (XCB_EVENT_RESPONSE_TYPE(evt) == 0)
|
|
event_handle(evt);
|
|
free(evt);
|
|
}
|
|
|
|
if (enable_wm() != 0)
|
|
errx(1, "another window manager is currently running");
|
|
|
|
/* Load Xcursors and/or cursorfont glyph cursors. */
|
|
cursors_load();
|
|
|
|
xcb_aux_sync(conn);
|
|
|
|
setup_globals();
|
|
setup_screens();
|
|
setup_keys();
|
|
setup_quirks();
|
|
setup_spawn();
|
|
|
|
/* load config */
|
|
for (i = 0; ; i++) {
|
|
conf[0] = '\0';
|
|
switch (i) {
|
|
case 0:
|
|
/* ~ */
|
|
snprintf(conf, sizeof conf, "%s/.%s",
|
|
pwd->pw_dir, SWM_CONF_FILE);
|
|
break;
|
|
case 1:
|
|
/* global */
|
|
snprintf(conf, sizeof conf, "/etc/%s",
|
|
SWM_CONF_FILE);
|
|
break;
|
|
case 2:
|
|
/* ~ compat */
|
|
snprintf(conf, sizeof conf, "%s/.%s",
|
|
pwd->pw_dir, SWM_CONF_FILE_OLD);
|
|
break;
|
|
case 3:
|
|
/* global compat */
|
|
snprintf(conf, sizeof conf, "/etc/%s",
|
|
SWM_CONF_FILE_OLD);
|
|
break;
|
|
default:
|
|
goto noconfig;
|
|
}
|
|
|
|
if (strlen(conf) && stat(conf, &sb) != -1)
|
|
if (S_ISREG(sb.st_mode)) {
|
|
cfile = conf;
|
|
break;
|
|
}
|
|
}
|
|
noconfig:
|
|
|
|
/* load conf (if any) */
|
|
if (cfile)
|
|
conf_load(cfile, SWM_CONF_DEFAULT);
|
|
|
|
validate_spawns();
|
|
|
|
setup_ewmh();
|
|
/* set some values to work around bad programs */
|
|
workaround();
|
|
/* grab existing windows (before we build the bars) */
|
|
grab_windows();
|
|
|
|
if (getenv("SWM_STARTED") == NULL)
|
|
setenv("SWM_STARTED", "YES", 1);
|
|
|
|
/* setup all bars */
|
|
num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
|
|
for (i = 0; i < num_screens; i++)
|
|
TAILQ_FOREACH(r, &screens[i].rl, entry)
|
|
bar_setup(r);
|
|
|
|
grabkeys();
|
|
stack();
|
|
bar_draw();
|
|
|
|
xcb_ungrab_server(conn);
|
|
xcb_flush(conn);
|
|
|
|
rd_max = xfd > STDIN_FILENO ? xfd : STDIN_FILENO;
|
|
|
|
while (running) {
|
|
while ((evt = xcb_poll_for_event(conn))) {
|
|
if (!running)
|
|
goto done;
|
|
event_handle(evt);
|
|
free(evt);
|
|
}
|
|
|
|
/* If just (re)started, set default focus if needed. */
|
|
if (startup) {
|
|
startup = 0;
|
|
|
|
if (focus_mode != SWM_FOCUS_FOLLOW) {
|
|
r = TAILQ_FIRST(&screens[0].rl);
|
|
if (r) {
|
|
focus_region(r);
|
|
focus_flush();
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
FD_ZERO(&rd);
|
|
|
|
if (bar_extra)
|
|
FD_SET(STDIN_FILENO, &rd);
|
|
|
|
FD_SET(xfd, &rd);
|
|
tv.tv_sec = 1;
|
|
tv.tv_usec = 0;
|
|
num_readable = select(rd_max + 1, &rd, NULL, NULL, &tv);
|
|
if (num_readable == -1 && errno != EINTR) {
|
|
DNPRINTF(SWM_D_MISC, "select failed");
|
|
} else if (num_readable > 0 && FD_ISSET(STDIN_FILENO, &rd)) {
|
|
stdin_ready = 1;
|
|
}
|
|
|
|
if (restart_wm)
|
|
restart(NULL, NULL);
|
|
|
|
if (search_resp)
|
|
search_do_resp();
|
|
|
|
if (!running)
|
|
goto done;
|
|
|
|
if (stdin_ready) {
|
|
stdin_ready = 0;
|
|
bar_extra_update();
|
|
}
|
|
|
|
bar_draw();
|
|
xcb_flush(conn);
|
|
}
|
|
done:
|
|
shutdown_cleanup();
|
|
|
|
return (0);
|
|
}
|