diff options
Diffstat (limited to 'vwm.c')
-rw-r--r-- | vwm.c | 3285 |
1 files changed, 0 insertions, 3285 deletions
@@ -1,3285 +0,0 @@ -/* - * \/\/\ - * - * Copyright (C) 2012-2015 Vito Caputo - <vcaputo@gnugeneration.com> - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3 as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/* The compositing code is heavily influenced by Keith Packard's xcompmgr. - */ - -#include <X11/Xlib.h> -#include <X11/keysym.h> -#include <X11/cursorfont.h> -#include <X11/Xatom.h> -#include <X11/extensions/sync.h> /* SYNC extension, enables us to give vwm the highest X client priority, helps keep vwm responsive at all times */ -#include <X11/extensions/Xinerama.h> /* XINERAMA extension, facilitates easy multihead awareness */ -#include <X11/extensions/Xrandr.h> /* RANDR extension, facilitates display configuration change awareness */ -#include <X11/extensions/Xdamage.h> /* Damage extension, enables receipt of damage events, reports visible regions needing updating (compositing) */ -#include <X11/extensions/Xrender.h> /* Render extension, enables use of alpha channels and accelerated rendering of surfaces having alpha (compositing) */ -#include <X11/extensions/Xcomposite.h> /* Composite extension, enables off-screen redirection of window rendering (compositing) */ -#include <X11/extensions/Xfixes.h> /* XFixes extension exposes things like regions (compositing) */ -#include <unistd.h> -#include <fcntl.h> -#include <stdio.h> -#include <stdlib.h> -#include <stdint.h> -#include <stdarg.h> -#include <string.h> -#include <inttypes.h> -#include <values.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <sys/time.h> -#include <sys/resource.h> -#include <poll.h> -#include "libvmon/vmon.h" -#include "vwm.h" - -#define WINDOW_BORDER_WIDTH 1 -#define CONSOLE_WM_CLASS "VWMConsoleXTerm" /* the class we specify to the "console" xterm */ -#define CONSOLE_SESSION_STRING "_vwm_console.$DISPLAY" /* the unique console screen session identifier */ - -#define WM_GRAB_MODIFIER Mod1Mask /* the modifier for invoking vwm's controls */ - /* Mod4Mask would be the windows key instead of Alt, but there's an assumption - * in the code that grabs are being activated by Alt which complicates changing it, - * search for XGetModifierMapping to see where, feel free to fix it. Or you can - * just hack the code to expect the appropriate key instead of Alt, I didn't see the - * value of making it modifier mapping aware if it's always Alt for me. */ -#define LAUNCHED_RELATIVE_PRIORITY 10 /* the wm priority plus this is used as the priority of launched processes */ -#define HONOR_OVERRIDE_REDIRECT /* search for HONOR_OVERRIDE_REDIRECT for understanding */ - - -#define OVERLAY_MASK_DEPTH 8 /* XXX: 1 would save memory, but Xorg isn't good at it */ -#define OVERLAY_MASK_FORMAT PictStandardA8 -#define OVERLAY_FIXED_FONT "-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso10646-1" -#define OVERLAY_ROW_HEIGHT 15 /* this should always be larger than the font height */ -#define OVERLAY_GRAPH_MIN_WIDTH 200 /* always create graphs at least this large */ -#define OVERLAY_GRAPH_MIN_HEIGHT (4 * OVERLAY_ROW_HEIGHT) -#define OVERLAY_ISTHREAD_ARGV "~" /* use this string to mark threads in the argv field */ -#define OVERLAY_NOCOMM_ARGV "#missed it!" /* use this string to substitute the command when missing in argv field */ - - -typedef enum _vwm_context_focus_t { - VWM_CONTEXT_FOCUS_OTHER = 0, /* focus the other context relative to the current one */ - VWM_CONTEXT_FOCUS_DESKTOP, /* focus the desktop context */ - VWM_CONTEXT_FOCUS_SHELF /* focus the shelf context */ -} vwm_context_focus_t; - -typedef enum _vwm_compositing_mode_t { - VWM_COMPOSITING_OFF = 0, /* non-composited, no redirected windows, most efficient */ - VWM_COMPOSITING_MONITORS = 1 /* composited process monitoring overlays, slower but really useful. */ -} vwm_compositing_mode_t; - -typedef XineramaScreenInfo vwm_screen_t; /* conveniently reuse the xinerama type for describing screens */ - -static LIST_HEAD(desktops); /* global list of all (virtual) desktops in spatial created-in order */ -static LIST_HEAD(desktops_mru); /* global list of all (virtual) desktops in MRU order */ -static LIST_HEAD(windows_mru); /* global list of all managed windows kept in MRU order */ -static LIST_HEAD(xwindows); /* global list of all xwindows kept in the X server stacking order */ -static vwm_window_t *console = NULL; /* the console window */ -static vwm_window_t *focused_origin = NULL; /* the originating window in a grabbed operation/transaction */ -static vwm_desktop_t *focused_desktop = NULL; /* currently focused (virtual) desktop */ -static vwm_window_t *focused_shelf = NULL; /* currently focused shelved window */ -static vwm_context_focus_t focused_context = VWM_CONTEXT_FOCUS_DESKTOP; /* currently focused context */ -static vwm_compositing_mode_t compositing_mode = VWM_COMPOSITING_OFF; /* current compositing mode */ -static int key_is_grabbed = 0; /* flag for tracking keyboard grab state */ -static int priority; /* scheduling priority of the vwm process, launcher nices relative to this */ -static unsigned long fence_mask = 0; /* global mask state for vwm_win_focus_next(... VWM_FENCE_MASKED_VIOLATE), - * if you use vwm on enough screens to overflow this, pics or it didn't happen. */ - - /* Uninteresting stuff */ -static Display *display; -static Colormap cmap; - -#define color(_sym, _str) \ -static XColor _sym ## _color; -#include "colors.def" -#undef color - -static int screen_num; -static GC gc; -static Atom wm_delete_atom; -static Atom wm_protocols_atom; -static Atom wm_pid_atom; -static int sync_event, sync_error; - - /* Xinerama */ -static int xinerama_event, xinerama_error; -static XineramaScreenInfo *xinerama_screens = NULL; -static int xinerama_screens_cnt; -static int randr_event, randr_error; - - /* Compositing */ -static int composite_event, composite_error, composite_opcode; -static int damage_event, damage_error; -static XserverRegion combined_damage = None; -static Picture root_picture = None, root_buffer = None; /* compositing gets double buffered */ -static XWindowAttributes root_attrs; -static XRenderPictureAttributes pa_inferiors = { .subwindow_mode = IncludeInferiors }; - - /* Compositing / Overlays */ -static XFontStruct *overlay_font; -static GC text_gc; -static XRenderPictureAttributes pa_repeat = { .repeat = 1 }; -static XRenderPictureAttributes pa_no_repeat = { .repeat = 0 }; -static Picture overlay_shadow_fill, /* TODO: the repetition here smells like an XMacro waiting to happen */ - overlay_text_fill, - overlay_bg_fill, - overlay_snowflakes_text_fill, - overlay_grapha_fill, - overlay_graphb_fill, - overlay_finish_fill; -static XRenderColor overlay_visible_color = { 0xffff, 0xffff, 0xffff, 0xffff }, - overlay_shadow_color = { 0x0000, 0x0000, 0x0000, 0x8800}, - overlay_bg_color = { 0x0, 0x1000, 0x0, 0x9000}, - overlay_div_color = { 0x2000, 0x3000, 0x2000, 0x9000}, - overlay_snowflakes_visible_color = { 0xd000, 0xd000, 0xd000, 0x8000 }, - overlay_trans_color = {0x00, 0x00, 0x00, 0x00}, - overlay_grapha_color = { 0xff00, 0x0000, 0x0000, 0x3000 }, /* ~red */ - overlay_graphb_color = { 0x0000, 0xffff, 0xffff, 0x3000 }; /* ~cyan */ - - /* libvmon */ -static struct timeval maybe_sample, last_sample, this_sample = {0,0}; -static typeof(((vmon_sys_stat_t *)0)->user) last_user_cpu; -static typeof(((vmon_sys_stat_t *)0)->system) last_system_cpu; -static unsigned long long last_total, this_total, total_delta; -static unsigned long long last_idle, last_iowait, idle_delta, iowait_delta; -static vmon_t vmon; - -static float sampling_intervals[] = { - 1, /* ~1Hz */ - .1, /* ~10Hz */ - .05, /* ~20Hz */ - .025, /* ~40Hz */ - .01666}; /* ~60Hz */ -static int prev_sampling_interval = 1, sampling_interval = 1; - - /* some needed prototypes */ -static vwm_xwindow_t * vwm_xwin_lookup(Window win); -static inline int vwm_xwin_is_mapped(vwm_xwindow_t *xwin); -static void vwm_comp_damage_add(XserverRegion damage); -static vwm_xwindow_t * vwm_win_unmanage(vwm_window_t *vwin); -static vwm_window_t * vwm_win_manage_xwin(vwm_xwindow_t *xwin); -static void vwm_win_unmap(vwm_window_t *vwin); -static void vwm_win_map(vwm_window_t *vwin); -static void vwm_win_focus(vwm_window_t *vwin); -static void vwm_keypressed(Window win, XEvent *keypress); - -#define MIN(_a, _b) ((_a) < (_b) ? (_a) : (_b)) -#define MAX(_a, _b) ((_a) > (_b) ? (_a) : (_b)) - - - /* libvmon integration, warning: this gets a little crazy especially in the rendering. */ - -/* space we need for every process being monitored */ -typedef struct _vwm_perproc_ctxt_t { - typeof(vmon.generation) generation; - typeof(((vmon_proc_stat_t *)0)->utime) last_utime; - typeof(((vmon_proc_stat_t *)0)->stime) last_stime; - typeof(((vmon_proc_stat_t *)0)->utime) utime_delta; - typeof(((vmon_proc_stat_t *)0)->stime) stime_delta; -} vwm_perproc_ctxt_t; - - -/* moves what's below a given row up above it if specified, the row becoming discarded */ -static void snowflake_row(vwm_xwindow_t *xwin, Picture pic, int copy, int row) -{ - VWM_TRACE("pid=%i xwin=%p row=%i copy=%i heirarhcy_end=%i", xwin->monitor->pid, xwin, row, copy, xwin->overlay.heirarchy_end); - - if (copy) { - /* copy row to tmp */ - XRenderComposite(display, PictOpSrc, pic, None, xwin->overlay.tmp_picture, - 0, row * OVERLAY_ROW_HEIGHT, /* src */ - 0, 0, /* mask */ - 0, 0, /* dest */ - xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ - } - - /* shift up */ - XRenderChangePicture(display, pic, CPRepeat, &pa_no_repeat); - XRenderComposite(display, PictOpSrc, pic, None, pic, - 0, (1 + row) * OVERLAY_ROW_HEIGHT, /* src */ - 0, 0, /* mask */ - 0, row * OVERLAY_ROW_HEIGHT, /* dest */ - xwin->overlay.width, (1 + xwin->overlay.heirarchy_end) * OVERLAY_ROW_HEIGHT - (1 + row) * OVERLAY_ROW_HEIGHT); /* dimensions */ - XRenderChangePicture(display, pic, CPRepeat, &pa_repeat); - - if (copy) { - /* copy tmp to top of snowflakes */ - XRenderComposite(display, PictOpSrc, xwin->overlay.tmp_picture, None, pic, - 0, 0, /* src */ - 0, 0, /* mask */ - 0, (xwin->overlay.heirarchy_end) * OVERLAY_ROW_HEIGHT, /* dest */ - xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ - } else { - /* clear the snowflake row */ - XRenderFillRectangle(display, PictOpSrc, pic, &overlay_trans_color, - 0, (xwin->overlay.heirarchy_end) * OVERLAY_ROW_HEIGHT, /* dest */ - xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ - } -} - -/* XXX TODO libvmon automagic children following races with explicit X client pid monitoring with different outcomes, it should be irrelevant which wins, - * currently the only visible difference is the snowflakes gap (heirarchy_end) varies, which is why I haven't bothered to fix it, I barely even notice. - */ - -/* shifts what's below a given row down a row, and clears the row, preparing it for populating */ -static void allocate_row(vwm_xwindow_t *xwin, Picture pic, int row) -{ - VWM_TRACE("pid=%i xwin=%p row=%i", xwin->monitor->pid, xwin, row); - - /* shift everything below the row down */ - XRenderComposite(display, PictOpSrc, pic, None, pic, - 0, row * OVERLAY_ROW_HEIGHT, /* src */ - 0, 0, /* mask */ - 0, (1 + row) * OVERLAY_ROW_HEIGHT, /* dest */ - xwin->overlay.width, xwin->overlay.height - (1 + row) * OVERLAY_ROW_HEIGHT); /* dimensions */ - /* fill the space created with transparent pixels */ - XRenderFillRectangle(display, PictOpSrc, pic, &overlay_trans_color, - 0, row * OVERLAY_ROW_HEIGHT, /* dest */ - xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ -} - - -/* shadow a row from the text layer in the shadow layer */ -static void shadow_row(vwm_xwindow_t *xwin, int row) -{ - /* the current technique for creating the shadow is to simply render the text at +1/-1 pixel offsets on both axis in translucent black */ - XRenderComposite(display, PictOpSrc, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, - 0, 0, - -1, row * OVERLAY_ROW_HEIGHT, - 0, row * OVERLAY_ROW_HEIGHT, - xwin->attrs.width, OVERLAY_ROW_HEIGHT); - - XRenderComposite(display, PictOpOver, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, - 0, 0, - 0, -1 + row * OVERLAY_ROW_HEIGHT, - 0, row * OVERLAY_ROW_HEIGHT, - xwin->attrs.width, OVERLAY_ROW_HEIGHT); - - XRenderComposite(display, PictOpOver, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, - 0, 0, - 1, row * OVERLAY_ROW_HEIGHT, - 0, row * OVERLAY_ROW_HEIGHT, - xwin->attrs.width, OVERLAY_ROW_HEIGHT); - - XRenderComposite(display, PictOpOver, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, - 0, 0, - 0, 1 + row * OVERLAY_ROW_HEIGHT, - 0, row * OVERLAY_ROW_HEIGHT, - xwin->attrs.width, OVERLAY_ROW_HEIGHT); -} - - -/* simple helper to map the vmon per-proc argv array into an XTextItem array, deals with threads vs. processes and the possibility of the comm field not getting read in before the process exited... */ -static void argv2xtext(vmon_proc_t *proc, XTextItem *items, int *nr_items) /* XXX TODO: reallocate items when too small... */ -{ - int i; - int nr = 0; - - if (proc->is_thread) { /* stick the thread marker at the start of threads */ - items[0].nchars = sizeof(OVERLAY_ISTHREAD_ARGV) - 1; - items[0].chars = OVERLAY_ISTHREAD_ARGV; - items[0].delta = 4; - items[0].font = None; - nr++; - } - - if (((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len) { - items[nr].nchars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len - 1; - items[nr].chars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.array; - } else { - /* sometimes a process is so ephemeral we don't manage to sample its comm, XXX TODO: we always have a pid, stringify it? */ - items[nr].nchars = sizeof(OVERLAY_NOCOMM_ARGV) - 1; - items[nr].chars = OVERLAY_NOCOMM_ARGV; - } - items[nr].delta = 4; - items[nr].font = None; - nr++; - - for (i = 1; i < ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argc; nr++, i++) { - items[nr].chars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argv[i]; - items[nr].nchars = strlen(((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argv[i]); /* TODO: libvmon should inform us of the length */ - items[nr].delta = 4; - items[nr].font = None; - } - - (*nr_items) = nr; -} - - -/* helper for counting number of existing descendants subtrees */ -static int count_rows(vmon_proc_t *proc) { - int count = 1; /* XXX maybe suppress proc->is_new? */ - vmon_proc_t *child; - - if (!proc->is_thread) { - list_for_each_entry(child, &proc->threads, threads) { - count += count_rows(child); - } - } - - list_for_each_entry(child, &proc->children, siblings) { - count += count_rows(child); - } - - return count; -} - - -/* recursive draw function for the consolidated version of the overlay rendering which also implements snowflakes */ -static void draw_overlay(vwm_xwindow_t *xwin, vmon_proc_t *proc, int *depth, int *row) -{ - vmon_proc_t *child; - vwm_perproc_ctxt_t *proc_ctxt = proc->foo; - vmon_proc_stat_t *proc_stat = proc->stores[VMON_STORE_PROC_STAT]; - - /* graph variables */ - int a_height, b_height; - double utime_delta, stime_delta; - - /* text variables */ - char str[256]; - int str_len; - XTextItem items[1024]; /* XXX TODO: dynamically allocate this and just keep it at the high water mark.. create a struct to encapsulate this, nr_items, and alloc_items... */ - int nr_items; - int direction, ascent, descent; - XCharStruct charstruct; - - if ((*row)) { /* except row 0 (Idle/IOWait graph), handle any stale and new processes/threads */ - if (proc->is_stale) { - /* what to do when a process (subtree) has gone away */ - static int in_stale = 0; - int in_stale_entrypoint = 0; - - /* I snowflake the stale processes from the leaves up for a more intuitive snowflake order... - * (I expect the command at the root of the subtree to appear at the top of the snowflakes...) */ - /* This does require that I do a separate forward recursion to determine the number of rows - * so I can correctly snowflake in reverse */ - if (!in_stale) { - VWM_TRACE("entered stale at xwin=%p depth=%i row=%i", xwin, *depth, *row); - in_stale_entrypoint = in_stale = 1; - (*row) += count_rows(proc) - 1; - } - - (*depth)++; - list_for_each_entry_prev(child, &proc->children, siblings) { - draw_overlay(xwin, child, depth, row); - (*row)--; - } - - if (!proc->is_thread) { - list_for_each_entry_prev(child, &proc->threads, threads) { - draw_overlay(xwin, child, depth, row); - (*row)--; - } - } - (*depth)--; - - VWM_TRACE("%i (%.*s) is stale @ depth %i row %i is_thread=%i", proc->pid, - ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len - 1, - ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.array, - (*depth), (*row), proc->is_thread); - - /* stamp the graphs with the finish line */ - XRenderComposite(display, PictOpSrc, overlay_finish_fill, None, xwin->overlay.grapha_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ - 1, OVERLAY_ROW_HEIGHT - 1); - XRenderComposite(display, PictOpSrc, overlay_finish_fill, None, xwin->overlay.graphb_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ - 1, OVERLAY_ROW_HEIGHT - 1); - - /* extract the row from the various layers */ - snowflake_row(xwin, xwin->overlay.grapha_picture, 1, (*row)); - snowflake_row(xwin, xwin->overlay.graphb_picture, 1, (*row)); - snowflake_row(xwin, xwin->overlay.text_picture, 0, (*row)); - snowflake_row(xwin, xwin->overlay.shadow_picture, 0, (*row)); - xwin->overlay.snowflakes_cnt++; - - /* stamp the name (and whatever else we include) into overlay.text_picture */ - argv2xtext(proc, items, &nr_items); - XDrawText(display, xwin->overlay.text_pixmap, text_gc, - 5, (xwin->overlay.heirarchy_end + 1) * OVERLAY_ROW_HEIGHT - 3,/* dst x, y */ - items, nr_items); - shadow_row(xwin, xwin->overlay.heirarchy_end); - - xwin->overlay.heirarchy_end--; - - if (in_stale_entrypoint) { - VWM_TRACE("exited stale at xwin=%p depth=%i row=%i", xwin, *depth, *row); - in_stale = 0; - } - - return; - } else if (proc->is_new) { - /* what to do when a process has been introduced */ - VWM_TRACE("%i is new", proc->pid); - - allocate_row(xwin, xwin->overlay.grapha_picture, (*row)); - allocate_row(xwin, xwin->overlay.graphb_picture, (*row)); - allocate_row(xwin, xwin->overlay.text_picture, (*row)); - allocate_row(xwin, xwin->overlay.shadow_picture, (*row)); - - xwin->overlay.heirarchy_end++; - } - } - -/* CPU utilization graphs */ - if (!(*row)) { - /* XXX: sortof kludged in IOWait and Idle % @ row 0 */ - stime_delta = iowait_delta; - utime_delta = idle_delta; - } else { - /* use the generation number to avoid recomputing this stuff for callbacks recurring on the same process in the same sample */ - if (proc_ctxt->generation != vmon.generation) { - proc_ctxt->stime_delta = proc_stat->stime - proc_ctxt->last_stime; - proc_ctxt->utime_delta = proc_stat->utime - proc_ctxt->last_utime; - proc_ctxt->last_utime = proc_stat->utime; - proc_ctxt->last_stime = proc_stat->stime; - - proc_ctxt->generation = vmon.generation; - } - - if (proc->is_new) { - /* we need a minimum of two samples before we can compute a delta to plot, - * so we suppress that and instead mark the start of monitoring with an impossible 100% of both graph contexts, a starting line. */ - stime_delta = utime_delta = total_delta; - } else { - stime_delta = proc_ctxt->stime_delta; - utime_delta = proc_ctxt->utime_delta; - } - } - - /* compute the bar heights for this sample */ - a_height = (stime_delta / total_delta * (double)(OVERLAY_ROW_HEIGHT - 1)); /* give up 1 pixel for the div */ - b_height = (utime_delta / total_delta * (double)(OVERLAY_ROW_HEIGHT - 1)); - - /* round up to 1 pixel when the scaled result is a fraction less than 1, - * I want to at least see 1 pixel blips for the slightest cpu utilization */ - if (stime_delta && !a_height) a_height = 1; - if (utime_delta && !b_height) b_height = 1; - - /* draw the two bars for this sample at the current phase in the graphs, note the first is ceiling-based, second floor-based */ - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.grapha_picture, &overlay_visible_color, - xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ - 1, a_height); /* dst w, h */ - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.graphb_picture, &overlay_visible_color, - xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT + (OVERLAY_ROW_HEIGHT - b_height) - 1, /* dst x, y */ - 1, b_height); /* dst w, h */ - - if (!(*row)) { - /* here's where the Idle/IOWait row drawing concludes */ - if (compositing_mode) { - snprintf(str, sizeof(str), "\\/\\/\\ %2iHz %n", (int)(sampling_interval < 0 ? 0 : 1 / sampling_intervals[sampling_interval]), &str_len); - /* TODO: I clear and redraw this row every time, which is unnecessary, small optimization would be to only do so when: - * - overlay resized, and then constrain the clear to the affected width - * - Hz changed - */ - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, - 0, 0, /* dst x, y */ - xwin->attrs.width, OVERLAY_ROW_HEIGHT); /* dst w, h */ - XTextExtents(overlay_font, str, str_len, &direction, &ascent, &descent, &charstruct); - XDrawString(display, xwin->overlay.text_pixmap, text_gc, - xwin->attrs.width - charstruct.width, OVERLAY_ROW_HEIGHT - 3, /* dst x, y */ - str, str_len); - shadow_row(xwin, 0); - } - (*row)++; - draw_overlay(xwin, proc, depth, row); - return; - } - -/* process heirarchy text and accompanying per-process details like wchan/pid/state... */ - if (compositing_mode) { /* this stuff can be skipped when monitors aren't visible */ - /* TODO: make the columns interactively configurable @ runtime */ - if (!proc->is_new) { - /* XXX for now always clear the row, this should be capable of being optimized in the future (if the datums driving the text haven't changed...) */ - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, - 0, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ - xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dst w, h */ - } - - /* put the process' wchan, state, and PID columns @ the far right */ - if (proc->is_thread || list_empty(&proc->threads)) { /* only threads or non-threaded processes include the wchan and state */ - snprintf(str, sizeof(str), " %.*s %5i %c %n", - proc_stat->wchan.len, - proc_stat->wchan.len == 1 && proc_stat->wchan.array[0] == '0' ? "-" : proc_stat->wchan.array, - proc->pid, - proc_stat->state, - &str_len); - } else { /* we're a process having threads, suppress the wchan and state, as they will be displayed for the thread of same pid */ - snprintf(str, sizeof(str), " %5i %n", proc->pid, &str_len); - } - - XTextExtents(overlay_font, str, str_len, &direction, &ascent, &descent, &charstruct); - - /* the process' comm label indented according to depth, followed with their respective argv's */ - argv2xtext(proc, items, &nr_items); - XDrawText(display, xwin->overlay.text_pixmap, text_gc, - (*depth) * (OVERLAY_ROW_HEIGHT / 2), ((*row) + 1) * OVERLAY_ROW_HEIGHT - 3, /* dst x, y */ - items, nr_items); - - /* ensure the area for the rest of the stuff is cleared, we don't put much text into thread rows so skip it for those. */ - if (!proc->is_thread) { - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, - xwin->attrs.width - charstruct.width, (*row) * OVERLAY_ROW_HEIGHT, /* dst x,y */ - xwin->overlay.width - (xwin->attrs.width - charstruct.width), OVERLAY_ROW_HEIGHT); /* dst w,h */ - } - - XDrawString(display, xwin->overlay.text_pixmap, text_gc, - xwin->attrs.width - charstruct.width, ((*row) + 1) * OVERLAY_ROW_HEIGHT - 3, /* dst x, y */ - str, str_len); - - /* only if this process isn't the root process @ the window shall we consider all relational drawing conditions */ - if (proc != xwin->monitor) { - vmon_proc_t *ancestor, *sibling, *last_sibling = NULL; - struct list_head *rem; - int needs_tee = 0; - int bar_x = 0, bar_y = 0; - int sub; - - /* XXX: everything done in this code block only dirties _this_ process' row in the rendered overlay output */ - - /* walk up the ancestors until reaching xwin->monitor, any ancestors we encounter which have more siblings we draw a vertical bar for */ - /* this draws the |'s in something like: | | | | comm */ - for (sub = 1, ancestor = proc->parent; ancestor && ancestor != xwin->monitor; ancestor = ancestor->parent) { - sub++; - bar_x = ((*depth) - sub) * (OVERLAY_ROW_HEIGHT / 2) + 4; - bar_y = ((*row) + 1) * OVERLAY_ROW_HEIGHT; - - /* determine if the ancestor has remaining siblings which are not stale, if so, draw a connecting bar at its depth */ - for (rem = ancestor->siblings.next; rem != &ancestor->parent->children; rem = rem->next) { - if (!(list_entry(rem, vmon_proc_t, siblings)->is_stale)) { - XDrawLine(display, xwin->overlay.text_pixmap, text_gc, - bar_x, bar_y - OVERLAY_ROW_HEIGHT, /* dst x1, y1 */ - bar_x, bar_y); /* dst x2, y2 (vertical line) */ - break; /* stop looking for more siblings at this ancestor when we find one that isn't stale */ - } - } - } - - /* determine if _any_ of our siblings have children requiring us to draw a tee immediately before our comm string. - * The only sibling which doesn't cause this to happen is the last one in the children list, if it has children it has no impact on its remaining - * siblings, as there are none. - * - * This draws the + in something like: | | | | +comm - */ - - /* find the last sibling (this has to be done due to the potential for stale siblings at the tail, and we'd rather not repeatedly check for it) */ - list_for_each_entry(sibling, &proc->parent->children, siblings) { - if (!sibling->is_stale) last_sibling = sibling; - } - - /* now look for siblings with non-stale children to determine if a tee is needed, ignoring the last sibling */ - list_for_each_entry(sibling, &proc->parent->children, siblings) { - /* skip stale siblings, they aren't interesting as they're invisible, and the last sibling has no bearing on wether we tee or not. */ - if (sibling->is_stale || sibling == last_sibling) continue; - - /* if any of the other siblings have children which are not stale, put a tee in front of our name, but ignore stale children */ - list_for_each_entry(child, &sibling->children, siblings) { - if (!child->is_stale) { - needs_tee = 1; - break; - } - } - - /* if we still don't think we need a tee, check if there are threads */ - if (!needs_tee) { - list_for_each_entry(child, &sibling->threads, threads) { - if (!child->is_stale) { - needs_tee = 1; - break; - } - } - } - - /* found a tee is necessary, all that's left is to determine if the tee is a corner and draw it accordingly, stopping the search. */ - if (needs_tee) { - bar_x = ((*depth) - 1) * (OVERLAY_ROW_HEIGHT / 2) + 4; - - /* if we're the last sibling, corner the tee by shortening the vbar */ - if (proc == last_sibling) { - XDrawLine(display, xwin->overlay.text_pixmap, text_gc, - bar_x, bar_y - OVERLAY_ROW_HEIGHT, /* dst x1, y1 */ - bar_x, bar_y - 4); /* dst x2, y2 (vertical bar) */ - } else { - XDrawLine(display, xwin->overlay.text_pixmap, text_gc, - bar_x, bar_y - OVERLAY_ROW_HEIGHT, /* dst x1, y1 */ - bar_x, bar_y); /* dst x2, y2 (vertical bar) */ - } - - XDrawLine(display, xwin->overlay.text_pixmap, text_gc, - bar_x, bar_y - 4, /* dst x1, y1 */ - bar_x + 2, bar_y - 4); /* dst x2, y2 (horizontal bar) */ - - /* terminate the outer sibling loop upon drawing the tee... */ - break; - } - } - } - shadow_row(xwin, (*row)); - } - - (*row)++; - - /* recur any threads first, then any children processes */ - (*depth)++; - if (!proc->is_thread) { /* XXX: the threads member serves as the list head only when not a thread */ - list_for_each_entry(child, &proc->threads, threads) { - draw_overlay(xwin, child, depth, row); - } - } - - list_for_each_entry(child, &proc->children, siblings) { - draw_overlay(xwin, child, depth, row); - } - (*depth)--; -} - - -/* consolidated version of overlay text and graph rendering, makes snowflakes integration cleaner, this always gets called regadless of the overlays mode */ -static void maintain_overlay(vwm_xwindow_t *xwin) -{ - int row = 0, depth = 0; - - if (!xwin->monitor || !xwin->monitor->stores[VMON_STORE_PROC_STAT]) return; - - /* TODO: - * I side effect of responding to window resizes in this function is there's a latency proportional to the current sample_interval. - * Something to fix is to resize the overlays when the window resizes. - * However, simply resizing the overlays is insufficient. Their contents need to be redrawn in the new dimensions, this is where it - * gets annoying. The current maintain/draw_overlay makes assumptions about being run from the periodic vmon per-process callback. - * There needs to be a redraw mode added where draw_overlay is just reconstructing the current state, which requires that we suppress - * the phase advance and in maintain_overlay() and just enter draw_overlay() to redraw everything for the same generation. - * So this probably requires some tweaking of draw_overlay() as well as maintain_overlay(). I want to be able tocall mainta_overlays() - * from anywhere, and have it detect if it's being called on the same generation or if the generation has advanced. - * For now, the monitors will just be a little latent in window resizes which is pretty harmless artifact. - */ - - /* if the window is larger than the overlays currently are, enlarge them */ - if (xwin->attrs.width > xwin->overlay.width || xwin->attrs.height > xwin->overlay.height) { - vwm_overlay_t existing; - Pixmap pixmap; - - existing = xwin->overlay; - - xwin->overlay.width = MAX(xwin->overlay.width, MAX(xwin->attrs.width, OVERLAY_GRAPH_MIN_WIDTH)); - xwin->overlay.height = MAX(xwin->overlay.height, MAX(xwin->attrs.height, OVERLAY_GRAPH_MIN_HEIGHT)); - - pixmap = XCreatePixmap(display, RootWindow(display, screen_num), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); - xwin->overlay.grapha_picture = XRenderCreatePicture(display, pixmap, XRenderFindStandardFormat(display, OVERLAY_MASK_FORMAT), CPRepeat, &pa_repeat); - XFreePixmap(display, pixmap); - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.grapha_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); - - pixmap = XCreatePixmap(display, RootWindow(display, screen_num), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); - xwin->overlay.graphb_picture = XRenderCreatePicture(display, pixmap, XRenderFindStandardFormat(display, OVERLAY_MASK_FORMAT), CPRepeat, &pa_repeat); - XFreePixmap(display, pixmap); - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.graphb_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); - - pixmap = XCreatePixmap(display, RootWindow(display, screen_num), xwin->overlay.width, OVERLAY_ROW_HEIGHT, OVERLAY_MASK_DEPTH); - xwin->overlay.tmp_picture = XRenderCreatePicture(display, pixmap, XRenderFindStandardFormat(display, OVERLAY_MASK_FORMAT), 0, NULL); - XFreePixmap(display, pixmap); - - /* keep the text_pixmap reference around for XDrawText usage */ - xwin->overlay.text_pixmap = XCreatePixmap(display, RootWindow(display, screen_num), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); - xwin->overlay.text_picture = XRenderCreatePicture(display, xwin->overlay.text_pixmap, XRenderFindStandardFormat(display, OVERLAY_MASK_FORMAT), 0, NULL); - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); - - pixmap = XCreatePixmap(display, RootWindow(display, screen_num), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); - xwin->overlay.shadow_picture = XRenderCreatePicture(display, pixmap, XRenderFindStandardFormat(display, OVERLAY_MASK_FORMAT), 0, NULL); - XFreePixmap(display, pixmap); - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.shadow_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); - - pixmap = XCreatePixmap(display, RootWindow(display, screen_num), xwin->overlay.width, xwin->overlay.height, 32); - xwin->overlay.picture = XRenderCreatePicture(display, pixmap, XRenderFindStandardFormat(display, PictStandardARGB32), 0, NULL); - XFreePixmap(display, pixmap); - - if (existing.width) { - /* XXX: note the graph pictures are copied from their current phase in the x dimension */ - XRenderComposite(display, PictOpSrc, existing.grapha_picture, None, xwin->overlay.grapha_picture, - existing.phase, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - existing.width, existing.height); - XRenderComposite(display, PictOpSrc, existing.graphb_picture, None, xwin->overlay.graphb_picture, - existing.phase, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - existing.width, existing.height); - XRenderComposite(display, PictOpSrc, existing.text_picture, None, xwin->overlay.text_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - existing.width, existing.height); - XRenderComposite(display, PictOpSrc, existing.shadow_picture, None, xwin->overlay.shadow_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - existing.width, existing.height); - XRenderComposite(display, PictOpSrc, existing.picture, None, xwin->overlay.picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - existing.width, existing.height); - xwin->overlay.phase = 0; /* having unrolled the existing graph[ab] pictures into the larger ones, phase is reset to 0 */ - XRenderFreePicture(display, existing.grapha_picture); - XRenderFreePicture(display, existing.graphb_picture); - XRenderFreePicture(display, existing.tmp_picture); - XRenderFreePicture(display, existing.text_picture); - XFreePixmap(display, existing.text_pixmap); - XRenderFreePicture(display, existing.shadow_picture); - XRenderFreePicture(display, existing.picture); - } - } - - xwin->overlay.phase += (xwin->overlay.width - 1); /* simply change this to .phase++ to scroll the other direction */ - xwin->overlay.phase %= xwin->overlay.width; - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.grapha_picture, &overlay_trans_color, xwin->overlay.phase, 0, 1, xwin->overlay.height); - XRenderFillRectangle(display, PictOpSrc, xwin->overlay.graphb_picture, &overlay_trans_color, xwin->overlay.phase, 0, 1, xwin->overlay.height); - - /* recursively draw the monitored processes to the overlay */ - draw_overlay(xwin, xwin->monitor, &depth, &row); -} - - -/* return the composed height of the overlay */ -static int overlay_composed_height(vwm_xwindow_t *xwin) -{ - int snowflakes = xwin->overlay.snowflakes_cnt ? 1 + xwin->overlay.snowflakes_cnt : 0; /* don't include the separator row if there are no snowflakes */ - return MIN((xwin->overlay.heirarchy_end + snowflakes) * OVERLAY_ROW_HEIGHT, xwin->attrs.height); -} - - -/* this composes the maintained overlay into the window's overlay picture, this gets called from paint_all() on every repaint of xwin */ -/* we noop the call if the gen_last_composed and monitor->proc.generation numbers match, indicating there's nothing new to compose. */ -static void compose_overlay(vwm_xwindow_t *xwin) -{ - XserverRegion region; - XRectangle damage; - int height; - - if (!xwin->overlay.width) return; /* prevent winning race with maintain_overlay() and using an unready overlay... */ - - if (xwin->overlay.gen_last_composed == xwin->monitor->generation) return; /* noop if no sampling occurred since last compose */ - xwin->overlay.gen_last_composed = xwin->monitor->generation; /* remember this generation */ - - //VWM_TRACE("composing %p", xwin); - - height = overlay_composed_height(xwin); - - /* fill the overlay picture with the background */ - XRenderComposite(display, PictOpSrc, overlay_bg_fill, None, xwin->overlay.picture, - 0, 0, - 0, 0, - 0, 0, - xwin->attrs.width, height); - - /* draw the graphs into the overlay through the stencils being maintained by the sample callbacks */ - XRenderComposite(display, PictOpOver, overlay_grapha_fill, xwin->overlay.grapha_picture, xwin->overlay.picture, - 0, 0, - xwin->overlay.phase, 0, - 0, 0, - xwin->attrs.width, height); - XRenderComposite(display, PictOpOver, overlay_graphb_fill, xwin->overlay.graphb_picture, xwin->overlay.picture, - 0, 0, - xwin->overlay.phase, 0, - 0, 0, - xwin->attrs.width, height); - - /* draw the shadow into the overlay picture using a translucent black source drawn through the shadow mask */ - XRenderComposite(display, PictOpOver, overlay_shadow_fill, xwin->overlay.shadow_picture, xwin->overlay.picture, - 0, 0, - 0, 0, - 0, 0, - xwin->attrs.width, height); - - /* render overlay text into the overlay picture using a white source drawn through the overlay text as a mask, on top of everything */ - XRenderComposite(display, PictOpOver, overlay_text_fill, xwin->overlay.text_picture, xwin->overlay.picture, - 0, 0, - 0, 0, - 0, 0, - xwin->attrs.width, (xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT)); - - XRenderComposite(display, PictOpOver, overlay_snowflakes_text_fill, xwin->overlay.text_picture, xwin->overlay.picture, - 0, 0, - 0, xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT, - 0, xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT, - xwin->attrs.width, height - (xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT)); - - /* damage the window to ensure the updated overlay is drawn (TODO: this can be done more selectively/efficiently) */ - damage.x = xwin->attrs.x + xwin->attrs.border_width; - damage.y = xwin->attrs.y + xwin->attrs.border_width; - damage.width = xwin->attrs.width; - damage.height = height; - region = XFixesCreateRegion(display, &damage, 1); - vwm_comp_damage_add(region); -} - - -/* this callback gets invoked at sample time for every process we've explicitly monitored (not autofollowed children/threads) - * It's where we update the cumulative data for all windows, including the graph masks, regardless of their visibility - * It's also where we compose the graphs and text for visible windows into a picture ready for compositing with the window contents */ -static void proc_sample_callback(vmon_t *vmon, vmon_proc_t *proc, vwm_xwindow_t *xwin) -{ - //VWM_TRACE("proc=%p xwin=%p", proc, xwin); - /* render the various always-updated overlays, this is the component we do regardless of the overlays mode and window visibility, - * essentially the incrementally rendered/historic components */ - maintain_overlay(xwin); - - /* render other non-historic things and compose the various layers into an updated overlay */ - /* this leaves everything ready to be composed with the window contents in paint_all() */ - /* paint_all() also enters compose_overlay() to update the overlays on windows which become mapped (desktop switches) */ - if (compositing_mode && vwm_xwin_is_mapped(xwin)) compose_overlay(xwin); -} - - -/* this callback gets invoked at sample time once "per sys" */ -static void sample_callback(vmon_t *_vmon) -{ - vmon_sys_stat_t *sys_stat = vmon.stores[VMON_STORE_SYS_STAT]; - this_total = sys_stat->user + sys_stat->nice + sys_stat->system + - sys_stat->idle + sys_stat->iowait + sys_stat->irq + - sys_stat->softirq + sys_stat->steal + sys_stat->guest; - - total_delta = this_total - last_total; - idle_delta = sys_stat->idle - last_idle; - iowait_delta = sys_stat->iowait - last_iowait; -} - - -/* these callbacks are invoked by the vmon library when process instances become monitored/unmonitored */ -static void vmon_ctor_cb(vmon_t *vmon, vmon_proc_t *proc) -{ - VWM_TRACE("proc->pid=%i", proc->pid); - proc->foo = calloc(1, sizeof(vwm_perproc_ctxt_t)); -} - - -static void vmon_dtor_cb(vmon_t *vmon, vmon_proc_t *proc) -{ - VWM_TRACE("proc->pid=%i", proc->pid); - if (proc->foo) { - free(proc->foo); - proc->foo = NULL; - } -} - - - /* Xinerama/multihead screen functions */ - -/* return what fraction (0.0-1.0) of vwin overlaps with scr */ -static float vwm_screen_overlaps_xwin(const vwm_screen_t *scr, vwm_xwindow_t *xwin) -{ - float pct = 0, xover = 0, yover = 0; - - if (scr->x_org + scr->width < xwin->attrs.x || scr->x_org > xwin->attrs.x + xwin->attrs.width || - scr->y_org + scr->height < xwin->attrs.y || scr->y_org > xwin->attrs.y + xwin->attrs.height) - goto _out; - - /* they overlap, by how much? */ - xover = MIN(scr->x_org + scr->width, xwin->attrs.x + xwin->attrs.width) - MAX(scr->x_org, xwin->attrs.x); - yover = MIN(scr->y_org + scr->height, xwin->attrs.y + xwin->attrs.height) - MAX(scr->y_org, xwin->attrs.y); - - pct = (xover * yover) / (xwin->attrs.width * xwin->attrs.height); -_out: - VWM_TRACE("xover=%f yover=%f width=%i height=%i pct=%.4f", xover, yover, xwin->attrs.width, xwin->attrs.height, pct); - return pct; -} - - -/* return the appropriate screen, don't use the return value across event loops because randr events reallocate the array. */ -typedef enum _vwm_screen_rel_t { - VWM_SCREEN_REL_XWIN, /* return the screen the supplied window most resides in */ - VWM_SCREEN_REL_POINTER, /* return the screen the pointer resides in */ - VWM_SCREEN_REL_TOTAL, /* return the bounding rectangle of all screens as one */ -} vwm_screen_rel_t; - -static const vwm_screen_t * vwm_screen_find(vwm_screen_rel_t rel, ...) -{ - static vwm_screen_t faux; - vwm_screen_t *scr, *best = &faux; /* default to faux as best */ - int i; - - faux.screen_number = 0; - faux.x_org = 0; - faux.y_org = 0; - faux.width = WidthOfScreen(DefaultScreenOfDisplay(display)); - faux.height = HeightOfScreen(DefaultScreenOfDisplay(display)); - - if (!xinerama_screens) goto _out; - -#define for_each_screen(_tmp) \ - for (i = 0, _tmp = xinerama_screens; i < xinerama_screens_cnt; _tmp = &xinerama_screens[++i]) - - switch (rel) { - case VWM_SCREEN_REL_XWIN: { - va_list ap; - vwm_xwindow_t *xwin; - float best_pct = 0, this_pct; - - va_start(ap, rel); - xwin = va_arg(ap, vwm_xwindow_t *); - va_end(ap); - - for_each_screen(scr) { - this_pct = vwm_screen_overlaps_xwin(scr, xwin); - if (this_pct > best_pct) { - best = scr; - best_pct = this_pct; - } - } - break; - } - - case VWM_SCREEN_REL_POINTER: { - int root_x, root_y, win_x, win_y; - unsigned int mask; - Window root, child; - - /* get the pointer coordinates and find which screen it's in */ - XQueryPointer(display, RootWindow(display, screen_num), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask); - - for_each_screen(scr) { - if (root_x >= scr->x_org && root_x < scr->x_org + scr->width && - root_y >= scr->y_org && root_y < scr->y_org + scr->height) { - best = scr; - break; - } - } - break; - } - - case VWM_SCREEN_REL_TOTAL: { - short x1 = MAXSHORT, y1 = MAXSHORT, x2 = MINSHORT, y2 = MINSHORT; - /* find the smallest x_org and y_org, the highest x_org + width and y_org + height, those are the two corners of the total rect */ - for_each_screen(scr) { - if (scr->x_org < x1) x1 = scr->x_org; - if (scr->y_org < y1) y1 = scr->y_org; - if (scr->x_org + scr->width > x2) x2 = scr->x_org + scr->width; - if (scr->y_org + scr->height > y2) y2 = scr->y_org + scr->height; - } - faux.x_org = x1; - faux.y_org = y1; - faux.width = x2 - x1; - faux.height = y2 - y1; - best = &faux; - break; - } - } -_out: - VWM_TRACE("Found Screen #%i: %hix%hi @ %hi,%hi", best->screen_number, best->width, best->height, best->x_org, best->y_org); - - return best; -} - - -/* check if a screen contains any windows (assuming the current desktop) */ -static int vwm_screen_is_empty(const vwm_screen_t *scr) -{ - vwm_xwindow_t *xwin; - int is_empty = 1; - - list_for_each_entry(xwin, &xwindows, xwindows) { - if (!xwin->mapped) continue; - if (!xwin->managed || (xwin->managed->desktop == focused_desktop && !xwin->managed->shelved && !xwin->managed->configuring)) { - /* XXX: it may make more sense to see what %age of the screen is overlapped by windows, and consider it empty if < some % */ - /* This is just seeing if any window is predominantly within the specified screen, the rationale being if you had a focusable - * window on the screen you would have used the keyboard to make windows go there; this function is only used in determining - * wether a new window should go where the pointer is or not. */ - if (vwm_screen_overlaps_xwin(scr, xwin) >= 0.05) { - is_empty = 0; - break; - } - } - } - - return is_empty; -} - - - /* startup logo */ - -/* animated \/\/\ logo done with simple XOR'd lines, a display of the WM being started and ready */ -#define VWM_LOGO_POINTS 6 -static void vwm_draw_logo(void) -{ - int i; - unsigned int width, height, yoff, xoff; - XPoint points[VWM_LOGO_POINTS]; - const vwm_screen_t *scr = vwm_screen_find(VWM_SCREEN_REL_POINTER); - - XGrabServer(display); - - /* use the dimensions of the pointer-containing screen */ - width = scr->width; - height = scr->height; - xoff = scr->x_org; - yoff = scr->y_org + ((float)height * .333); - height /= 3; - - /* the logo gets shrunken vertically until it's essentially a flat line */ - while (height -= 2) { - /* scale and center the points to the screen size */ - for (i = 0; i < VWM_LOGO_POINTS; i++) { - points[i].x = xoff + (i * .2 * (float)width); - points[i].y = (i % 2 * (float)height) + yoff; - } - - XDrawLines(display, RootWindow(display, screen_num), gc, points, sizeof(points) / sizeof(XPoint), CoordModeOrigin); - XFlush(display); - usleep(3333); - XDrawLines(display, RootWindow(display, screen_num), gc, points, sizeof(points) / sizeof(XPoint), CoordModeOrigin); - XFlush(display); - - /* the width is shrunken as well, but only by as much as it is tall */ - yoff++; - width -= 4; - xoff += 2; - } - - XUngrabServer(display); -} - - - /* launching of external processes / X clients */ - -/* launch a child command specified in argv, mode decides if we wait for the child to exit before returning. */ -typedef enum _vwm_launch_mode_t { - VWM_LAUNCH_MODE_FG, - VWM_LAUNCH_MODE_BG, -} vwm_launch_mode_t; - -static void vwm_launch(char **argv, vwm_launch_mode_t mode) -{ - /* XXX: in BG mode I double fork and let init inherit the orphan so I don't have to collect the return status */ - if (mode == VWM_LAUNCH_MODE_FG || !fork()) { - if (!fork()) { - /* child */ - setpriority(PRIO_PROCESS, getpid(), priority + LAUNCHED_RELATIVE_PRIORITY); - execvp(argv[0], argv); - } - if (mode == VWM_LAUNCH_MODE_BG) exit(0); - } - wait(NULL); /* TODO: could wait for the specific pid, particularly in FG mode ... */ -} - - - /* desktop/shelf context handling */ - -/* switch to the desired context if it isn't already the focused one, inform caller if anything happened */ -static int vwm_context_focus(vwm_context_focus_t desired_context) -{ - vwm_context_focus_t entry_context = focused_context; - - switch (focused_context) { - vwm_xwindow_t *xwin; - vwm_window_t *vwin; - - case VWM_CONTEXT_FOCUS_SHELF: - if (desired_context == VWM_CONTEXT_FOCUS_SHELF) break; - - /* desired == DESKTOP && focused == SHELF */ - - VWM_TRACE("unmapping shelf window \"%s\"", focused_shelf->xwindow->name); - vwm_win_unmap(focused_shelf); - XFlush(display); /* for a more responsive feel */ - - /* map the focused desktop, from the top of the stack down */ - list_for_each_entry_prev(xwin, &xwindows, xwindows) { - if (!(vwin = xwin->managed)) continue; - if (vwin->desktop == focused_desktop && !vwin->shelved) { - VWM_TRACE("Mapping desktop window \"%s\"", xwin->name); - vwm_win_map(vwin); - } - } - - if (focused_desktop->focused_window) { - VWM_TRACE("Focusing \"%s\"", focused_desktop->focused_window->xwindow->name); - XSetInputFocus(display, focused_desktop->focused_window->xwindow->id, RevertToPointerRoot, CurrentTime); - } - - focused_context = VWM_CONTEXT_FOCUS_DESKTOP; - break; - - case VWM_CONTEXT_FOCUS_DESKTOP: - /* unmap everything, map the shelf */ - if (desired_context == VWM_CONTEXT_FOCUS_DESKTOP) break; - - /* desired == SHELF && focused == DESKTOP */ - - /* there should be a focused shelf if the shelf contains any windows, we NOOP the switch if the shelf is empty. */ - if (focused_shelf) { - /* unmap everything on the current desktop */ - list_for_each_entry(xwin, &xwindows, xwindows) { - if (!(vwin = xwin->managed)) continue; - if (vwin->desktop == focused_desktop) { - VWM_TRACE("Unmapping desktop window \"%s\"", xwin->name); - vwm_win_unmap(vwin); - } - } - - XFlush(display); /* for a more responsive feel */ - - VWM_TRACE("Mapping shelf window \"%s\"", focused_shelf->xwindow->name); - vwm_win_map(focused_shelf); - vwm_win_focus(focused_shelf); - - focused_context = VWM_CONTEXT_FOCUS_SHELF; - } - break; - - default: - VWM_BUG("unexpected focused context %x", focused_context); - break; - } - - /* return if the context has been changed, the caller may need to branch differently if nothing happened */ - return (focused_context != entry_context); -} - - - /* virtual desktops */ - -/* make the specified desktop the most recently used one */ -static void vwm_desktop_mru(vwm_desktop_t *desktop) -{ - VWM_TRACE("MRU desktop: %p", desktop); - list_move(&desktop->desktops_mru, &desktops_mru); -} - - -/* focus a virtual desktop */ -/* this switches to the desktop context if necessary, maps and unmaps windows accordingly if necessary */ -static int vwm_desktop_focus(vwm_desktop_t *desktop) -{ - XGrabServer(display); - XSync(display, False); - - /* if the context switched and the focused desktop is the desired desktop there's nothing else to do */ - if ((vwm_context_focus(VWM_CONTEXT_FOCUS_DESKTOP) && focused_desktop != desktop) || focused_desktop != desktop) { - vwm_xwindow_t *xwin; - vwm_window_t *vwin; - - /* unmap the windows on the currently focused desktop, map those on the newly focused one */ - list_for_each_entry(xwin, &xwindows, xwindows) { - if (!(vwin = xwin->managed) || vwin->shelved) continue; - if (vwin->desktop == focused_desktop) vwm_win_unmap(vwin); - } - - XFlush(display); - - list_for_each_entry_prev(xwin, &xwindows, xwindows) { - if (!(vwin = xwin->managed) || vwin->shelved) continue; - if (vwin->desktop == desktop) vwm_win_map(vwin); - } - - focused_desktop = desktop; - } - - /* directly focus the desktop's focused window if there is one, we don't use vwm_win_focus() intentionally XXX */ - if (focused_desktop->focused_window) { - VWM_TRACE("Focusing \"%s\"", focused_desktop->focused_window->xwindow->name); - XSetInputFocus(display, focused_desktop->focused_window->xwindow->id, RevertToPointerRoot, CurrentTime); - } - - XUngrabServer(display); - - return 1; -} - - -/* create a virtual desktop */ -static vwm_desktop_t * vwm_desktop_create(char *name) -{ - vwm_desktop_t *desktop; - - desktop = malloc(sizeof(vwm_desktop_t)); - if (desktop == NULL) { - VWM_PERROR("Failed to allocate desktop"); - goto _fail; - } - - desktop->name = name == NULL ? name : strdup(name); - desktop->focused_window = NULL; - - list_add_tail(&desktop->desktops, &desktops); - list_add_tail(&desktop->desktops_mru, &desktops_mru); - - return desktop; - -_fail: - return NULL; -} - - -/* destroy a virtual desktop */ -static void vwm_desktop_destroy(vwm_desktop_t *desktop) -{ - /* silently refuse to destroy a desktop having windows (for now) */ - /* there's _always_ a focused window on a desktop having mapped windows */ - /* also silently refuse to destroy the last desktop (for now) */ - if (desktop->focused_window || (desktop->desktops.next == desktop->desktops.prev)) return; - - /* focus the MRU desktop that isn't this one if we're the focused desktop */ - if (desktop == focused_desktop) { - vwm_desktop_t *next_desktop; - - list_for_each_entry(next_desktop, &desktops_mru, desktops_mru) { - if (next_desktop != desktop) { - vwm_desktop_focus(next_desktop); - break; - } - } - } - - list_del(&desktop->desktops); - list_del(&desktop->desktops_mru); -} - - - /* bare X windows stuff, there's a distinction between bare xwindows and the vwm managed windows */ - -/* send a client message to a window (currently used for WM_DELETE) */ -static void vwm_xwin_message(vwm_xwindow_t *xwin, Atom type, long foo) -{ - XEvent event; - - memset(&event, 0, sizeof(event)); - event.xclient.type = ClientMessage; - event.xclient.window = xwin->id; - event.xclient.message_type = type; - event.xclient.format = 32; - event.xclient.data.l[0] = foo; - event.xclient.data.l[1] = CurrentTime; /* XXX TODO: is CurrentTime actually correct to use for this purpose? */ - - XSendEvent(display, xwin->id, False, 0, &event); -} - - -/* look up the X window in the global xwindows list (includes unmanaged windows like override_redirect/popup menus) */ -static vwm_xwindow_t * vwm_xwin_lookup(Window win) -{ - vwm_xwindow_t *tmp, *xwin = NULL; - - list_for_each_entry(tmp, &xwindows, xwindows) { - if (tmp->id == win) { - xwin = tmp; - break; - } - } - - return xwin; -} - - -/* determine if a window is mapped (vwm-mapped) according to the current context */ -static inline int vwm_xwin_is_mapped(vwm_xwindow_t *xwin) -{ - int ret = 0; - - if (!xwin->mapped) return 0; - - if (xwin->managed) { - switch (focused_context) { - case VWM_CONTEXT_FOCUS_SHELF: - if (focused_shelf == xwin->managed) ret = xwin->mapped; - break; - - case VWM_CONTEXT_FOCUS_DESKTOP: - if (focused_desktop == xwin->managed->desktop && !xwin->managed->shelved) ret = xwin->mapped; - break; - - default: - VWM_BUG("Unsupported context"); - break; - } - } else { /* unmanaged xwins like popup dialogs when mapped are always visible */ - ret = 1; - } - - /* annoyingly, Xorg stops delivering VisibilityNotify events for redirected windows, so we don't conveniently know if a window is obscured or not :( */ - /* I could maintain my own data structure for answering this question, but that's pretty annoying when Xorg already has that knowledge. */ - - return ret; -} - - -/* bind the window to a "namewindowpixmap" and create a picture from it (compositing) */ -void vwm_xwin_bind_namewindow(vwm_xwindow_t *xwin) -{ - xwin->pixmap = XCompositeNameWindowPixmap(display, xwin->id); - xwin->picture = XRenderCreatePicture(display, xwin->pixmap, - XRenderFindVisualFormat(display, xwin->attrs.visual), - CPSubwindowMode, &pa_inferiors); - XFreePixmap(display, xwin->pixmap); -} - - -/* free the window's picture for accessing its redirected contents (compositing) */ -void vwm_xwin_unbind_namewindow(vwm_xwindow_t *xwin) -{ - XRenderFreePicture(display, xwin->picture); -} - - -/* install a monitor on the window if it doesn't already have one and has _NET_WM_PID set */ -static void vwm_xwin_monitor(vwm_xwindow_t *xwin) -{ - Atom type; - int fmt; - unsigned long nitems; - unsigned long nbytes; - long *foo = NULL; - int pid = -1; - - if (xwin->monitor) return; - - if (XGetWindowProperty(display, xwin->id, wm_pid_atom, 0, 1, False, XA_CARDINAL, - &type, &fmt, &nitems, &nbytes, (unsigned char **)&foo) != Success || !foo) return; - - pid = *foo; - XFree(foo); - - /* add the client process to the monitoring heirarchy */ - /* XXX note libvmon here maintains a unique callback for each unique callback+xwin pair, so multi-window processes work */ - xwin->monitor = vmon_proc_monitor(&vmon, NULL, pid, VMON_WANT_PROC_INHERIT, (void (*)(vmon_t *, vmon_proc_t *, void *))proc_sample_callback, xwin); - /* FIXME: count_rows() isn't returning the right count sometimes (off by ~1), it seems to be related to racing with the automatic child monitoring */ - /* the result is an extra row sometimes appearing below the process heirarchy */ - xwin->overlay.heirarchy_end = 1 + count_rows(xwin->monitor); - xwin->overlay.snowflakes_cnt = 0; -} - - -/* creates and potentially manages a new window (called in response to CreateNotify events, and during startup for all existing windows) */ -/* if the window is already mapped and not an override_redirect window, it becomes managed here. */ -typedef enum _vwm_grab_mode_t { - VWM_NOT_GRABBED = 0, - VWM_GRABBED -} vwm_grab_mode_t; - -static vwm_xwindow_t * vwm_xwin_create(Window win, vwm_grab_mode_t grabbed) -{ - XWindowAttributes attrs; - vwm_xwindow_t *xwin = NULL; - - VWM_TRACE("creating %#x", (unsigned int)win); - - /* prevent races */ - if (!grabbed) { - XGrabServer(display); - XSync(display, False); - } - - /* verify the window still exists */ - if (!XGetWindowAttributes(display, win, &attrs)) goto _out_grabbed; - - /* don't create InputOnly windows */ - if (attrs.class == InputOnly) goto _out_grabbed; - - if (!(xwin = (vwm_xwindow_t *)malloc(sizeof(vwm_xwindow_t)))) { - VWM_PERROR("Failed to allocate xwin"); - goto _out_grabbed; - } - - xwin->id = win; - xwin->attrs = attrs; - xwin->managed = NULL; - xwin->name = NULL; - XFetchName(display, win, &xwin->name); - - xwin->monitor = NULL; - xwin->overlay.width = xwin->overlay.height = xwin->overlay.phase = 0; - xwin->overlay.gen_last_composed = -1; - - /* This is so we get the PropertyNotify event and can get the pid when it's set post-create, - * with my _NET_WM_PID patch the property is immediately available */ - XSelectInput(display, win, PropertyChangeMask); - - vwm_xwin_monitor(xwin); - - /* we must track the mapped-by-client state of the window independent of managed vs. unmanaged because - * in the case of override_redirect windows they may be unmapped (invisible) or mapped (visible) like menus without being managed. - * otherwise we could just use !xwin.managed to indicate unmapped, which is more vwm2-like, but insufficient when compositing. */ - xwin->mapped = (attrs.map_state != IsUnmapped); - - if (compositing_mode) { - vwm_xwin_bind_namewindow(xwin); - xwin->damage = XDamageCreate(display, xwin->id, XDamageReportNonEmpty); - } - - list_add_tail(&xwin->xwindows, &xwindows); /* created windows are always placed on the top of the stacking order */ - -#ifdef HONOR_OVERRIDE_REDIRECT - if (!attrs.override_redirect && xwin->mapped) vwm_win_manage_xwin(xwin); -#else - if (xwin->mapped) vwm_win_manage_xwin(xwin); -#endif -_out_grabbed: - if (!grabbed) XUngrabServer(display); - - return xwin; -} - - -/* destroy a window, called in response to DestroyNotify events */ -/* if the window is also managed it will be unmanaged first */ -static void vwm_xwin_destroy(vwm_xwindow_t *xwin) -{ - XGrabServer(display); - XSync(display, False); - - if (xwin->managed) vwm_win_unmanage(xwin->managed); - - list_del(&xwin->xwindows); - - if (xwin->monitor) vmon_proc_unmonitor(&vmon, xwin->monitor, (void (*)(vmon_t *, vmon_proc_t *, void *))proc_sample_callback, xwin); - if (xwin->name) XFree(xwin->name); - - if (compositing_mode) { - vwm_xwin_unbind_namewindow(xwin); - XDamageDestroy(display, xwin->damage); - } - free(xwin); - - XUngrabServer(display); -} - - -/* maintain the stack-ordered xwindows list, when new_above is != None xwin is to be placed above new_above, when == None xwin goes to the bottom. */ -void vwm_xwin_restack(vwm_xwindow_t *xwin, Window new_above) -{ - Window old_above; -#ifdef TRACE - vwm_xwindow_t *tmp; - fprintf(stderr, "restack of %#x new_above=%#x\n", (unsigned int)xwin->id, (unsigned int)new_above); - fprintf(stderr, "restack pre:"); - list_for_each_entry(tmp, &xwindows, xwindows) { - fprintf(stderr, " %#x", (unsigned int)tmp->id); - } - fprintf(stderr, "\n"); -#endif - if (xwin->xwindows.prev != &xwindows) { - old_above = list_entry(xwin->xwindows.prev, vwm_xwindow_t, xwindows)->id; - } else { - old_above = None; - } - - if (old_above != new_above) { - vwm_xwindow_t *new; - - if (new_above == None) { /* to the bottom of the stack, so just above the &xwindows head */ - list_move(&xwin->xwindows, &xwindows); - } else if ((new = vwm_xwin_lookup(new_above))) { /* to just above new_above */ - list_move(&xwin->xwindows, &new->xwindows); - } - } -#ifdef TRACE - fprintf(stderr, "restack post:"); - list_for_each_entry(tmp, &xwindows, xwindows) { - fprintf(stderr, " %#x", (unsigned int)tmp->id); - } - fprintf(stderr, "\n\n"); -#endif -} - - - /* vwm "managed" windows (vwm_window_t) (which are built upon the "core" X windows (vwm_xwindow_t)) */ - -/* unmap the specified window and set the unmapping-in-progress flag so we can discard vwm-generated UnmapNotify events */ -static void vwm_win_unmap(vwm_window_t *vwin) -{ - if (!vwin->xwindow->mapped) { - VWM_TRACE("inhibited unmap of \"%s\", not mapped by client", vwin->xwindow->name); - return; - } - VWM_TRACE("Unmapping \"%s\"", vwin->xwindow->name); - vwin->unmapping = 1; - XUnmapWindow(display, vwin->xwindow->id); -} - - -/* map the specified window and set the mapping-in-progress flag so we can discard vwm-generated MapNotify events */ -static void vwm_win_map(vwm_window_t *vwin) -{ - if (!vwin->xwindow->mapped) { - VWM_TRACE("inhibited map of \"%s\", not mapped by client", vwin->xwindow->name); - return; - } - VWM_TRACE("Mapping \"%s\"", vwin->xwindow->name); - vwin->mapping = 1; - XMapWindow(display, vwin->xwindow->id); -} - - -/* make the specified window the most recently used one */ -static void vwm_win_mru(vwm_window_t *vwin) -{ - list_move(&vwin->windows_mru, &windows_mru); -} - - -/* look up the X window in the global managed windows list */ -static vwm_window_t * vwm_win_lookup(Window win) -{ - vwm_window_t *tmp, *vwin = NULL; - - list_for_each_entry(tmp, &windows_mru, windows_mru) { - if (tmp->xwindow->id == win) { - vwin = tmp; - break; - } - } - - return vwin; -} - - -/* return the currently focused window (considers current context...), may return NULL */ -static vwm_window_t * vwm_win_focused(void) -{ - vwm_window_t *vwin = NULL; - - switch (focused_context) { - case VWM_CONTEXT_FOCUS_SHELF: - vwin = focused_shelf; - break; - - case VWM_CONTEXT_FOCUS_DESKTOP: - if (focused_desktop) vwin = focused_desktop->focused_window; - break; - - default: - VWM_BUG("Unsupported context"); - break; - } - - return vwin; -} - - -/* "autoconfigure" windows (configuration shortcuts like fullscreen/halfscreen/quarterscreen) and restoring the window */ -typedef enum _vwm_win_autoconf_t { - VWM_WIN_AUTOCONF_NONE, /* un-autoconfigured window (used to restore the configuration) */ - VWM_WIN_AUTOCONF_QUARTER, /* quarter-screened */ - VWM_WIN_AUTOCONF_HALF, /* half-screened */ - VWM_WIN_AUTOCONF_FULL, /* full-screened */ - VWM_WIN_AUTOCONF_ALL /* all-screened (borderless) */ -} vwm_win_autoconf_t; - -typedef enum _vwm_side_t { - VWM_SIDE_TOP, - VWM_SIDE_BOTTOM, - VWM_SIDE_LEFT, - VWM_SIDE_RIGHT -} vwm_side_t; - -typedef enum _vwm_corner_t { - VWM_CORNER_TOP_LEFT, - VWM_CORNER_TOP_RIGHT, - VWM_CORNER_BOTTOM_RIGHT, - VWM_CORNER_BOTTOM_LEFT -} vwm_corner_t; - -static void vwm_win_autoconf(vwm_window_t *vwin, vwm_screen_rel_t rel, vwm_win_autoconf_t conf, ...) -{ - const vwm_screen_t *scr; - va_list ap; - XWindowChanges changes = { .border_width = WINDOW_BORDER_WIDTH }; - - /* remember the current configuration as the "client" configuration if it's not an autoconfigured one. */ - if (vwin->autoconfigured == VWM_WIN_AUTOCONF_NONE) vwin->client = vwin->xwindow->attrs; - - scr = vwm_screen_find(rel, vwin->xwindow); /* XXX FIXME: this becomes a bug when vwm_screen_find() uses non-xwin va_args */ - va_start(ap, conf); - switch (conf) { - case VWM_WIN_AUTOCONF_QUARTER: { - vwm_corner_t corner = va_arg(ap, vwm_corner_t); - changes.width = scr->width / 2 - (WINDOW_BORDER_WIDTH * 2); - changes.height = scr->height / 2 - (WINDOW_BORDER_WIDTH * 2); - switch (corner) { - case VWM_CORNER_TOP_LEFT: - changes.x = scr->x_org; - changes.y = scr->y_org; - break; - - case VWM_CORNER_TOP_RIGHT: - changes.x = scr->x_org + scr->width / 2; - changes.y = scr->y_org; - break; - - case VWM_CORNER_BOTTOM_RIGHT: - changes.x = scr->x_org + scr->width / 2; - changes.y = scr->y_org + scr->height / 2; - break; - - case VWM_CORNER_BOTTOM_LEFT: - changes.x = scr->x_org; - changes.y = scr->y_org + scr->height / 2; - break; - } - break; - } - - case VWM_WIN_AUTOCONF_HALF: { - vwm_side_t side = va_arg(ap, vwm_side_t); - switch (side) { - case VWM_SIDE_TOP: - changes.width = scr->width - (WINDOW_BORDER_WIDTH * 2); - changes.height = scr->height / 2 - (WINDOW_BORDER_WIDTH * 2); - changes.x = scr->x_org; - changes.y = scr->y_org; - break; - - case VWM_SIDE_BOTTOM: - changes.width = scr->width - (WINDOW_BORDER_WIDTH * 2); - changes.height = scr->height / 2 - (WINDOW_BORDER_WIDTH * 2); - changes.x = scr->x_org; - changes.y = scr->y_org + scr->height / 2; - break; - - case VWM_SIDE_LEFT: - changes.width = scr->width / 2 - (WINDOW_BORDER_WIDTH * 2); - changes.height = scr->height - (WINDOW_BORDER_WIDTH * 2); - changes.x = scr->x_org; - changes.y = scr->y_org; - break; - - case VWM_SIDE_RIGHT: - changes.width = scr->width / 2 - (WINDOW_BORDER_WIDTH * 2); - changes.height = scr->height - (WINDOW_BORDER_WIDTH * 2); - changes.x = scr->x_org + scr->width / 2; - changes.y = scr->y_org; - break; - } - break; - } - - case VWM_WIN_AUTOCONF_FULL: - changes.width = scr->width - WINDOW_BORDER_WIDTH * 2; - changes.height = scr->height - WINDOW_BORDER_WIDTH * 2; - changes.x = scr->x_org; - changes.y = scr->y_org; - break; - - case VWM_WIN_AUTOCONF_ALL: - changes.width = scr->width; - changes.height = scr->height; - changes.x = scr->x_org; - changes.y = scr->y_org; - changes.border_width = 0; - break; - - case VWM_WIN_AUTOCONF_NONE: /* restore window if autoconfigured */ - changes.width = vwin->client.width; - changes.height = vwin->client.height; - changes.x = vwin->client.x; - changes.y = vwin->client.y; - break; - } - va_end(ap); - - XConfigureWindow(display, vwin->xwindow->id, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &changes); - vwin->autoconfigured = conf; -} - - -/* focus a window */ -/* this updates window border color as needed and the X input focus if mapped */ -static void vwm_win_focus(vwm_window_t *vwin) -{ - VWM_TRACE("focusing: %#x", (unsigned int)vwin->xwindow->id); - - if (vwm_xwin_is_mapped(vwin->xwindow)) { - /* if vwin is mapped give it the input focus */ - XSetInputFocus(display, vwin->xwindow->id, RevertToPointerRoot, CurrentTime); - } - - /* update the border color accordingly */ - if (vwin->shelved) { - /* set the border of the newly focused window to the shelved color */ - XSetWindowBorder(display, vwin->xwindow->id, vwin == console ? shelved_console_border_color.pixel : shelved_window_border_color.pixel); - /* fullscreen windows in the shelf when focused, since we don't intend to overlap there */ - vwm_win_autoconf(vwin, VWM_SCREEN_REL_POINTER, VWM_WIN_AUTOCONF_FULL); /* XXX TODO: for now the shelf follows the pointer, it's simple. */ - } else { - if (vwin->desktop->focused_window) { - /* set the border of the previously focused window on the same desktop to the unfocused color */ - XSetWindowBorder(display, vwin->desktop->focused_window->xwindow->id, unfocused_window_border_color.pixel); - } - - /* set the border of the newly focused window to the focused color */ - XSetWindowBorder(display, vwin->xwindow->id, focused_window_border_color.pixel); - - /* persist this on a per-desktop basis so it can be restored on desktop switches */ - vwin->desktop->focused_window = vwin; - } -} - - -/* focus the next window on a virtual desktop relative to the supplied window, in the specified context, respecting screen boundaries according to fence. */ -typedef enum _vwm_fence_t { - VWM_FENCE_IGNORE = 0, /* behave as if screen boundaries don't exist (like the pre-Xinerama code) */ - VWM_FENCE_RESPECT, /* confine operation to within the screen */ - VWM_FENCE_TRY_RESPECT, /* confine operation to within the screen, unless no options exist. */ - VWM_FENCE_VIOLATE, /* leave the screen for any other */ - VWM_FENCE_MASKED_VIOLATE /* leave the screen for any other not masked */ -} vwm_fence_t; - -static vwm_window_t * vwm_win_focus_next(vwm_window_t *vwin, vwm_fence_t fence) -{ - const vwm_screen_t *scr = vwm_screen_find(VWM_SCREEN_REL_XWIN, vwin->xwindow), *next_scr = NULL; - vwm_window_t *next; - unsigned long visited_mask; - -_retry: - visited_mask = 0; - list_for_each_entry(next, &vwin->windows_mru, windows_mru) { - /* searching for the next mapped window in this context, using vwin->windows as the head */ - if (&next->windows_mru == &windows_mru) continue; /* XXX: skip the containerless head, we're leveraging the circular list implementation */ - - if ((vwin->shelved && next->shelved) || - ((!vwin->shelved && !next->shelved && next->desktop == vwin->desktop) && - (fence == VWM_FENCE_IGNORE || - ((fence == VWM_FENCE_RESPECT || fence == VWM_FENCE_TRY_RESPECT) && vwm_screen_find(VWM_SCREEN_REL_XWIN, next->xwindow) == scr) || - (fence == VWM_FENCE_VIOLATE && vwm_screen_find(VWM_SCREEN_REL_XWIN, next->xwindow) != scr) || - (fence == VWM_FENCE_MASKED_VIOLATE && (next_scr = vwm_screen_find(VWM_SCREEN_REL_XWIN, next->xwindow)) != scr && - !((1UL << next_scr->screen_number) & fence_mask)) - ))) break; - - if (fence == VWM_FENCE_MASKED_VIOLATE && next_scr && next_scr != scr) visited_mask |= (1UL << next_scr->screen_number); - } - - if (fence == VWM_FENCE_TRY_RESPECT && next == vwin) { - /* if we tried to respect the fence and failed to find a next, fallback to ignoring the fence and try again */ - fence = VWM_FENCE_IGNORE; - goto _retry; - } else if (fence == VWM_FENCE_MASKED_VIOLATE && next_scr) { - /* if we did a masked violate update the mask with the potentially new screen number */ - if (next == vwin && visited_mask) { - /* if we failed to find a next window on a different screen but had candidates we've exhausted screens and need to reset the mask then retry */ - VWM_TRACE("all candidate screens masked @ 0x%lx, resetting mask", fence_mask); - fence_mask = 0; - goto _retry; - } - fence_mask |= (1UL << next_scr->screen_number); - VWM_TRACE("VWM_FENCE_MASKED_VIOLATE fence_mask now: 0x%lx\n", fence_mask); - } - - if (vwin->shelved) { - if (next != focused_shelf) { - /* shelf switch, unmap the focused shelf and take it over */ - /* TODO FIXME: this makes assumptions about the shelf being focused calling unmap/map directly.. */ - vwm_win_unmap(focused_shelf); - - XFlush(display); - - vwm_win_map(next); - focused_shelf = next; - vwm_win_focus(next); - } - } else { - if (next != next->desktop->focused_window) { - /* focus the changed window */ - vwm_win_focus(next); - XRaiseWindow(display, next->xwindow->id); - } - } - - VWM_TRACE("vwin=%p xwin=%p name=\"%s\"", next, next->xwindow, next->xwindow->name); - - return next; -} - - -/* shelves a window, if the window is focused we focus the next one (if possible) */ -static void vwm_win_shelve(vwm_window_t *vwin) -{ - /* already shelved, NOOP */ - if (vwin->shelved) return; - - /* shelving focused window, focus the next window */ - if (vwin == vwin->desktop->focused_window) { - vwm_win_mru(vwm_win_focus_next(vwin, VWM_FENCE_RESPECT)); - } - - if (vwin == vwin->desktop->focused_window) { - /* TODO: we can probably put this into vwm_win_focus_next() and have it always handled there... */ - vwin->desktop->focused_window = NULL; - } - - vwin->shelved = 1; - vwm_win_mru(vwin); - - /* newly shelved windows always become the focused shelf */ - focused_shelf = vwin; - - vwm_win_unmap(vwin); -} - - -/* helper for (idempotently) unfocusing a window, deals with context switching etc... */ -static void vwm_win_unfocus(vwm_window_t *vwin) -{ - /* if we're the focused shelved window, cycle the focus to the next shelved window if possible, if there's no more shelf, switch to the desktop */ - /* TODO: there's probably some icky behaviors for focused windows unmapping/destroying in unfocused contexts, we probably jump contexts suddenly. */ - if (vwin == focused_shelf) { - VWM_TRACE("unfocusing focused shelf"); - vwm_win_focus_next(vwin, VWM_FENCE_IGNORE); - - if (vwin == focused_shelf) { - VWM_TRACE("shelf empty, leaving"); - /* no other shelved windows, exit the shelf context */ - vwm_context_focus(VWM_CONTEXT_FOCUS_DESKTOP); - focused_shelf = NULL; - } - } - - /* if we're the focused window cycle the focus to the next window on the desktop if possible */ - if (vwin->desktop->focused_window == vwin) { - VWM_TRACE("unfocusing focused window"); - vwm_win_focus_next(vwin, VWM_FENCE_TRY_RESPECT); - } - - if (vwin->desktop->focused_window == vwin) { - VWM_TRACE("desktop empty"); - vwin->desktop->focused_window = NULL; - } -} - - -/* demote an managed window to an unmanaged one */ -static vwm_xwindow_t * vwm_win_unmanage(vwm_window_t *vwin) -{ - vwm_win_mru(vwin); /* bump vwin to the mru head before unfocusing so we always move focus to the current head on unmanage of the focused window */ - vwm_win_unfocus(vwin); - list_del(&vwin->windows_mru); - - if (vwin == console) console = NULL; - if (vwin == focused_origin) focused_origin = NULL; - - vwin->xwindow->managed = NULL; - - return vwin->xwindow; -} - - -/* promote an unmanaged window to a managed one */ -static vwm_window_t * vwm_win_manage_xwin(vwm_xwindow_t *xwin) -{ - vwm_window_t *focused, *vwin = NULL; - - if (xwin->managed) { - VWM_TRACE("suppressed re-management of xwin=%p", xwin); - goto _fail; - } - - if (!(vwin = (vwm_window_t *)malloc(sizeof(vwm_window_t)))) { - VWM_PERROR("Failed to allocate vwin"); - goto _fail; - } - - XUngrabButton(display, AnyButton, AnyModifier, xwin->id); - XGrabButton(display, AnyButton, WM_GRAB_MODIFIER, xwin->id, False, (PointerMotionMask | ButtonPressMask | ButtonReleaseMask), GrabModeAsync, GrabModeAsync, None, None); - XGrabKey(display, AnyKey, WM_GRAB_MODIFIER, xwin->id, False, GrabModeAsync, GrabModeAsync); - XSetWindowBorder(display, xwin->id, unfocused_window_border_color.pixel); - - vwin->hints = XAllocSizeHints(); - if (!vwin->hints) { - VWM_PERROR("Failed to allocate WM hints"); - goto _fail; - } - XGetWMNormalHints(display, xwin->id, vwin->hints, &vwin->hints_supplied); - - xwin->managed = vwin; - vwin->xwindow = xwin; - - vwin->desktop = focused_desktop; - vwin->autoconfigured = VWM_WIN_AUTOCONF_NONE; - vwin->mapping = vwin->unmapping = vwin->configuring = 0; - vwin->shelved = (focused_context == VWM_CONTEXT_FOCUS_SHELF); /* if we're in the shelf when the window is created, the window is shelved */ - vwin->client = xwin->attrs; /* remember whatever the current attributes are */ - - VWM_TRACE("hints: flags=%lx x=%i y=%i w=%i h=%i minw=%i minh=%i maxw=%i maxh=%i winc=%i hinc=%i basew=%i baseh=%i grav=%x", - vwin->hints->flags, - vwin->hints->x, vwin->hints->y, - vwin->hints->width, vwin->hints->height, - vwin->hints->min_width, vwin->hints->min_height, - vwin->hints->max_width, vwin->hints->max_height, - vwin->hints->width_inc, vwin->hints->height_inc, - vwin->hints->base_width, vwin->hints->base_height, - vwin->hints->win_gravity); - - if ((vwin->hints_supplied & (USSize | PSize))) { - vwin->client.width = vwin->hints->base_width; - vwin->client.height = vwin->hints->base_height; - } - - /* put it on the global windows_mru list, if there's a focused window insert the new one after it */ - if (!list_empty(&windows_mru) && (focused = vwm_win_focused())) { - /* insert the vwin immediately after the focused window, so Mod1+Tab goes to the new window */ - list_add(&vwin->windows_mru, &focused->windows_mru); - } else { - list_add(&vwin->windows_mru, &windows_mru); - } - - /* always raise newly managed windows so we know about them. */ - XRaiseWindow(display, xwin->id); - - /* if the desktop has no focused window yet, automatically focus the newly managed one, provided we're on the desktop context */ - if (!focused_desktop->focused_window && focused_context == VWM_CONTEXT_FOCUS_DESKTOP) { - VWM_TRACE("Mapped new window \"%s\" is alone on desktop \"%s\", focusing", xwin->name, focused_desktop->name); - vwm_win_focus(vwin); - } - - return vwin; - -_fail: - if (vwin) { - if (vwin->hints) XFree(vwin->hints); - free(vwin); - } - return NULL; -} - - -/* migrate a window to a new desktop, focuses the destination desktop as well */ -static void vwm_win_migrate(vwm_window_t *vwin, vwm_desktop_t *desktop) -{ - vwm_win_unfocus(vwin); /* go through the motions of unfocusing the window if it is focused */ - vwin->shelved = 0; /* ensure not shelved */ - vwin->desktop = desktop; /* assign the new desktop */ - vwm_desktop_focus(desktop); /* currently we always focus the new desktop in a migrate */ - - vwm_win_focus(vwin); /* focus the window so borders get updated */ - vwm_win_mru(vwin); /* TODO: is this right? shouldn't the Mod1 release be what's responsible for this? I migrate so infrequently it probably doesn't matter */ - - XRaiseWindow(display, vwin->xwindow->id); /* ensure the window is raised */ -} - - - /* compositing manager stuff */ - -/* add damage to the global combined damage queue where we accumulate damage between calls to paint_all() */ -static void vwm_comp_damage_add(XserverRegion damage) -{ - if (combined_damage != None) { - XFixesUnionRegion(display, combined_damage, combined_damage, damage); - XFixesDestroyRegion(display, damage); - } else { - combined_damage = damage; - } -} - - -/* take what regions of the damaged window have been damaged, subtract them from the per-window damage object, and add them to the combined damage */ -static void vwm_comp_damage_event(XDamageNotifyEvent *ev) -{ - XserverRegion region; - vwm_xwindow_t *xwin; - - xwin = vwm_xwin_lookup(ev->drawable); - if (!xwin) { - VWM_ERROR("damaged unknown drawable %x", (unsigned int)ev->drawable); - return; - } - - region = XFixesCreateRegion(display, NULL, 0); - XDamageSubtract(display, xwin->damage, None, region); - XFixesTranslateRegion(display, region, xwin->attrs.x + xwin->attrs.border_width, xwin->attrs.y + xwin->attrs.border_width); - vwm_comp_damage_add(region); -} - - -/* helper to damage an entire window including the border */ -static void vwm_comp_damage_win(vwm_xwindow_t *xwin) -{ - XserverRegion region; - XRectangle rect = { xwin->attrs.x, - xwin->attrs.y, - xwin->attrs.width + xwin->attrs.border_width * 2, - xwin->attrs.height + xwin->attrs.border_width * 2 }; - region = XFixesCreateRegion(display, &rect, 1); - vwm_comp_damage_add(region); -} - - -/* throw away our double buffered root pictures so they get recreated on the next paint_all() */ -/* used in response to screen configuration changes... */ -static void vwm_comp_invalidate_root() -{ - if (root_picture) XRenderFreePicture(display, root_picture); - root_picture = None; - if (root_buffer) XRenderFreePicture(display, root_picture); - root_buffer = None; -} - - -/* consume combined_damage by iterating the xwindows list from the top down, drawing visible windows as encountered, subtracting their area from combined_damage */ -/* when compositing is active this is the sole function responsible for making things show up on the screen */ -static void vwm_comp_paint_all() -{ - vwm_xwindow_t *xwin; - XRenderColor bgcolor = {0x0000, 0x00, 0x00, 0xffff}; - Region occluded = XCreateRegion(); - static XserverRegion undamage_region = None; - - if (!undamage_region) undamage_region = XFixesCreateRegion(display, NULL, 0); - - /* (re)create the root picture from the root window and allocate a double buffer for the off-screen composition of the damaged contents */ - if (root_picture == None) { - Pixmap root_pixmap; - - XGetWindowAttributes(display, RootWindow(display, screen_num), &root_attrs); - root_picture = XRenderCreatePicture(display, RootWindow(display, screen_num), - XRenderFindVisualFormat(display, DefaultVisual(display, screen_num)), - CPSubwindowMode, &pa_inferiors); - root_pixmap = XCreatePixmap (display, RootWindow(display, screen_num), root_attrs.width, root_attrs.height, DefaultDepth (display, screen_num)); - root_buffer = XRenderCreatePicture (display, root_pixmap, XRenderFindVisualFormat (display, DefaultVisual (display, screen_num)), 0, 0); - XFreePixmap(display, root_pixmap); - } - - /* compose overlays for all visible windows up front in a separate pass (kind of lame, but it's simpler since compose_overlay() adds to combined_damage) */ - list_for_each_entry_prev(xwin, &xwindows, xwindows) { - XRectangle r; - - if (!vwm_xwin_is_mapped(xwin)) continue; /* if !mapped skip */ - - /* Everything mapped next goes through an occlusion check. - * Since the composite extension stops delivery of VisibilityNotify events for redirected windows, - * (it assumes redirected windows should be treated as part of a potentially transparent composite, and provides no api to alter this assumption) - * we can't simply select the VisibilityNotify events on all windows and cache their visibility state in vwm_xwindow_t then skip - * xwin->state==VisibilityFullyObscured windows here to avoid the cost of pointlessly composing overlays and rendering fully obscured windows :(. - * - * Instead we accumulate an occluded region (starting empty) of painted windows from the top-down on every paint_all(). - * Before we compose_overlay() a window, we check if the window's rectangle fits entirely within the occluded region. - * If it does, no compose_overlay() is performed. - * If it doesn't, compose_overlay() is called, and the window's rect is added to the occluded region. - * The occluded knowledge is also cached for the XRenderComposite() loop immediately following, where we skip the rendering of - * occluded windows as well. - * This does technically break SHAPE windows (xeyes, xmms), but only when monitoring is enabled which covers them with rectangular overlays anyways. - */ - r.x = xwin->attrs.x; - r.y = xwin->attrs.y; - r.width = xwin->attrs.width + xwin->attrs.border_width * 2; - r.height = xwin->attrs.height + xwin->attrs.border_width * 2; - if (XRectInRegion(occluded, r.x, r.y, r.width, r.height) != RectangleIn) { - /* the window isn't fully occluded, compose it and add it to occluded */ - if (xwin->monitor && !xwin->attrs.override_redirect) compose_overlay(xwin); - XUnionRectWithRegion(&r, occluded, occluded); - xwin->occluded = 0; - } else { - xwin->occluded = 1; - VWM_TRACE("window %#x occluded, skipping compose_overlay()", (int)xwin->id); - } - } - XDestroyRegion(occluded); - - /* start with the clip regions set to the damaged area accumulated since the previous paint_all() */ - XFixesSetPictureClipRegion(display, root_buffer, 0, 0, combined_damage); /* this is the double buffer where the in-flight screen contents are staged */ - XFixesSetPictureClipRegion(display, root_picture, 0, 0, combined_damage); /* this is the visible root window */ - - /* since translucent windows aren't supported in vwm, I can do this more efficiently */ - list_for_each_entry_prev(xwin, &xwindows, xwindows) { - XRectangle r; - - if (!vwm_xwin_is_mapped(xwin) || xwin->occluded) continue; /* if !mapped or occluded skip */ - - /* these coordinates + dimensions incorporate the border (since XCompositeNameWindowPixmap is being used) */ - r.x = xwin->attrs.x; - r.y = xwin->attrs.y; - r.width = xwin->attrs.width + xwin->attrs.border_width * 2; - r.height = xwin->attrs.height + xwin->attrs.border_width * 2; - - /* render the redirected window contents into root_buffer, note root_buffer has the remaining combined_damage set as the clip region */ - XRenderComposite(display, PictOpSrc, xwin->picture, None, root_buffer, - 0, 0, 0, 0, /* src x, y, mask x, y */ - r.x, r.y, /* dst x, y */ - r.width, r.height); - - if (xwin->monitor && !xwin->attrs.override_redirect && xwin->overlay.width) { - /* draw the monitoring overlay atop the window, note we stay within the window borders here. */ - XRenderComposite(display, PictOpOver, xwin->overlay.picture, None, root_buffer, - 0, 0, 0, 0, /* src x,y, maxk x, y */ - xwin->attrs.x + xwin->attrs.border_width, /* dst x */ - xwin->attrs.y + xwin->attrs.border_width, /* dst y */ - xwin->attrs.width, overlay_composed_height(xwin)); /* w, h */ - } - - /* subtract the region of the window from the combined damage and update the root_buffer clip region to reflect the remaining damage */ - XFixesSetRegion(display, undamage_region, &r, 1); - XFixesSubtractRegion(display, combined_damage, combined_damage, undamage_region); - XFixesSetPictureClipRegion(display, root_buffer, 0, 0, combined_damage); - } - - /* at this point all of the visible windows have been subtracted from the clip region, so paint any root window damage (draw background) */ - XRenderFillRectangle(display, PictOpSrc, root_buffer, &bgcolor, 0, 0, root_attrs.width, root_attrs.height); - - /* discard the root_buffer clip region and copy root_buffer to root_picture, root_picture still has the combined damage as its clip region */ - XFixesSetPictureClipRegion(display, root_buffer, 0, 0, None); - XRenderComposite(display, PictOpSrc, root_buffer, None, root_picture, 0, 0, 0, 0, 0, 0, root_attrs.width, root_attrs.height); - - /* fin */ - XFixesDestroyRegion(display, combined_damage); - combined_damage = None; -} - - -/* toggle compositing/monitoring overlays on/off */ -static void vwm_comp_toggle(void) -{ - vwm_xwindow_t *xwin; - - XGrabServer(display); - XSync(display, False); - - switch (compositing_mode) { - case VWM_COMPOSITING_OFF: - VWM_TRACE("enabling compositing"); - compositing_mode = VWM_COMPOSITING_MONITORS; - XCompositeRedirectSubwindows(display, RootWindow(display, screen_num), CompositeRedirectManual); - list_for_each_entry_prev(xwin, &xwindows, xwindows) { - vwm_xwin_bind_namewindow(xwin); - xwin->damage = XDamageCreate(display, xwin->id, XDamageReportNonEmpty); - } - /* damage everything */ - /* TODO: keep a list of rects reflecting all the current screens and create a region from that... */ - vwm_comp_damage_add(XFixesCreateRegionFromWindow(display, RootWindow(display, screen_num), WindowRegionBounding)); - break; - - case VWM_COMPOSITING_MONITORS: { - XEvent ev; - - VWM_TRACE("disabling compositing"); - compositing_mode = VWM_COMPOSITING_OFF; - list_for_each_entry_prev(xwin, &xwindows, xwindows) { - vwm_xwin_unbind_namewindow(xwin); - XDamageDestroy(display, xwin->damage); - } - XCompositeUnredirectSubwindows(display, RootWindow(display, screen_num), CompositeRedirectManual); - vwm_comp_invalidate_root(); - - /* if there's any damage queued up discard it so we don't enter paint_all() until compositing is reenabled again. */ - if (combined_damage) { - XFixesDestroyRegion(display, combined_damage); - combined_damage = None; - } - while (XCheckTypedEvent(display, damage_event + XDamageNotify, &ev) != False); - break; - } - } - - XUngrabServer(display); -} - - - /* input event handling stuff */ - -/* simple little state machine for managing mouse click/drag/release sequences so we don't need another event loop */ -typedef enum _vwm_adjust_mode_t { - VWM_ADJUST_RESIZE, - VWM_ADJUST_MOVE -} vwm_adjust_mode_t; - -typedef struct _vwm_clickety_t { - vwm_window_t *vwin; - vwm_adjust_mode_t mode; - XWindowAttributes orig, lastrect; - int impetus_x, impetus_y, impetus_x_root, impetus_y_root; -} vwm_clickety_t; - -/* helper function for resizing the window, how the motion is applied depends on where in the window the impetus event occurred */ -static void compute_resize(vwm_clickety_t *clickety, XEvent *terminus, XWindowAttributes *new) -{ - vwm_window_t *vwin; - int dw = (clickety->orig.width / 2); - int dh = (clickety->orig.height / 2); - int xdelta = (terminus->xbutton.x_root - clickety->impetus_x_root); - int ydelta = (terminus->xbutton.y_root - clickety->impetus_y_root); - int min_width = 0, min_height = 0; - int width_inc = 1, height_inc = 1; - - /* TODO: there's a problem here WRT border width, I should be considering it, I just haven't bothered to fix it since it doesn't seem to matter */ - if ((vwin = clickety->vwin) && vwin->hints) { - if ((vwin->hints_supplied & PMinSize)) { - min_width = vwin->hints->min_width; - min_height = vwin->hints->min_height; - VWM_TRACE("window size hints exist and minimum sizes are w=%i h=%i", min_width, min_height); - } - - if ((vwin->hints_supplied & PResizeInc)) { - width_inc = vwin->hints->width_inc; - height_inc = vwin->hints->height_inc; - VWM_TRACE("window size hints exist and resize increments are w=%i h=%i", width_inc, height_inc); - if (!width_inc) width_inc = 1; - if (!height_inc) height_inc = 1; - } - } - - xdelta = xdelta / width_inc * width_inc; - ydelta = ydelta / height_inc * height_inc; - - if (clickety->impetus_x < dw && clickety->impetus_y < dh) { - /* grabbed top left */ - new->x = clickety->orig.x + xdelta; - new->y = clickety->orig.y + ydelta; - new->width = clickety->orig.width - xdelta; - new->height = clickety->orig.height - ydelta; - } else if (clickety->impetus_x > dw && clickety->impetus_y < dh) { - /* grabbed top right */ - new->x = clickety->orig.x; - new->y = clickety->orig.y + ydelta; - new->width = clickety->orig.width + xdelta; - new->height = clickety->orig.height - ydelta; - } else if (clickety->impetus_x < dw && clickety->impetus_y > dh) { - /* grabbed bottom left */ - new->x = clickety->orig.x + xdelta; - new->y = clickety->orig.y; - new->width = clickety->orig.width - xdelta; - new->height = clickety->orig.height + ydelta; - } else if (clickety->impetus_x > dw && clickety->impetus_y > dh) { - /* grabbed bottom right */ - new->x = clickety->orig.x; - new->y = clickety->orig.y; - new->width = clickety->orig.width + xdelta; - new->height = clickety->orig.height + ydelta; - } - - /* constrain the width and height of the window according to the minimums */ - if (new->width < min_width) { - if (clickety->orig.x != new->x) new->x -= (min_width - new->width); - new->width = min_width; - } - - if (new->height < min_height) { - if (clickety->orig.y != new->y) new->y -= (min_height - new->height); - new->height = min_height; - } -} - - -static void vwm_clickety_motion(vwm_clickety_t *clickety, Window win, XMotionEvent *motion) -{ - XWindowChanges changes = { .border_width = WINDOW_BORDER_WIDTH }; - - if (!clickety->vwin) return; - - /* TODO: verify win matches clickety->vwin? */ - switch (clickety->mode) { - case VWM_ADJUST_MOVE: - changes.x = clickety->orig.x + (motion->x_root - clickety->impetus_x_root); - changes.y = clickety->orig.y + (motion->y_root - clickety->impetus_y_root); - XConfigureWindow(display, win, CWX | CWY | CWBorderWidth, &changes); - break; - - case VWM_ADJUST_RESIZE: { - XWindowAttributes resized; - - /* XXX: it just so happens the XMotionEvent structure is identical to XButtonEvent in the fields - * needed by compute_resize... */ - compute_resize(clickety, (XEvent *)motion, &resized); - /* TODO: this is probably broken with compositing active, but it doesn't seem to be too messed up in practice */ - /* erase the last rectangle */ - XDrawRectangle(display, RootWindow(display, screen_num), gc, clickety->lastrect.x, clickety->lastrect.y, clickety->lastrect.width, clickety->lastrect.height); - /* draw a frame @ resized coordinates */ - XDrawRectangle(display, RootWindow(display, screen_num), gc, resized.x, resized.y, resized.width, resized.height); - /* remember the last rectangle */ - clickety->lastrect = resized; - break; - } - - default: - break; - } -} - - -static void vwm_clickety_released(vwm_clickety_t *clickety, Window win, XButtonPressedEvent *terminus) -{ - XWindowChanges changes = { .border_width = WINDOW_BORDER_WIDTH }; - - if (!clickety->vwin) return; - - switch (clickety->mode) { - case VWM_ADJUST_MOVE: - changes.x = clickety->orig.x + (terminus->x_root - clickety->impetus_x_root); - changes.y = clickety->orig.y + (terminus->y_root - clickety->impetus_y_root); - XConfigureWindow(display, win, CWX | CWY | CWBorderWidth, &changes); - break; - - case VWM_ADJUST_RESIZE: { - XWindowAttributes resized; - compute_resize(clickety, (XEvent *)terminus, &resized); - /* move and resize the window @ resized */ - XDrawRectangle(display, RootWindow(display, screen_num), gc, clickety->lastrect.x, clickety->lastrect.y, clickety->lastrect.width, clickety->lastrect.height); - changes.x = resized.x; - changes.y = resized.y; - changes.width = resized.width; - changes.height = resized.height; - XConfigureWindow(display, win, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &changes); - XUngrabServer(display); - break; - } - - default: - break; - } - /* once you manipulate the window it's no longer fullscreened, simply hitting Mod1+Return once will restore fullscreened mode */ - clickety->vwin->autoconfigured = VWM_WIN_AUTOCONF_NONE; - - clickety->vwin = NULL; /* reset clickety */ - - XFlush(display); - XUngrabPointer(display, CurrentTime); -} - - -/* on pointer buttonpress we initiate a clickety sequence; setup clickety with the window and impetus coordinates.. */ -static int vwm_clickety_pressed(vwm_clickety_t *clickety, Window win, XButtonPressedEvent *impetus) -{ - vwm_window_t *vwin; - - /* verify the window still exists */ - if (!XGetWindowAttributes(display, win, &clickety->orig)) goto _fail; - - if (!(vwin = vwm_win_lookup(win))) goto _fail; - - if (impetus->state & WM_GRAB_MODIFIER) { - - /* always set the input focus to the clicked window, note if we allow this to happen on the root window, it enters sloppy focus mode - * until a non-root window is clicked, which is an interesting hybrid but not how I prefer it. */ - if (vwin != focused_desktop->focused_window && vwin->xwindow->id != RootWindow(display, screen_num)) { - vwm_win_focus(vwin); - vwm_win_mru(vwin); - } - - switch (impetus->button) { - case Button1: - /* immediately raise the window if we're relocating, - * resizes are supported without raising (which also enables NULL resizes to focus without raising) */ - clickety->mode = VWM_ADJUST_MOVE; - XRaiseWindow(display, win); - break; - - case Button3: - /* grab the server on resize for the xor rubber-banding's sake */ - XGrabServer(display); - XSync(display, False); - - /* FIXME: none of the resize DrawRectangle() calls consider the window border. */ - XDrawRectangle(display, RootWindow(display, screen_num), gc, clickety->orig.x, clickety->orig.y, clickety->orig.width, clickety->orig.height); - clickety->lastrect = clickety->orig; - - clickety->mode = VWM_ADJUST_RESIZE; - break; - - default: - goto _fail; - } - clickety->vwin = vwin; - clickety->impetus_x_root = impetus->x_root; - clickety->impetus_y_root = impetus->y_root; - clickety->impetus_x = impetus->x; - clickety->impetus_y = impetus->y; - } - - return 1; - -_fail: - XUngrabPointer(display, CurrentTime); - - return 0; -} - -/* Poll the keyboard state to see if _any_ keys are pressed */ -static int vwm_keyspressed() { - int i; - char state[32]; - - XQueryKeymap(display, state); - - for (i = 0; i < sizeof(state); i++) { - if (state[i]) return 1; - } - - return 0; -} - -/* Called in response to KeyRelease events, for now only interesting for detecting when Mod1 is released termintaing - * window cycling for application of MRU on the focused window */ -static void vwm_keyreleased(Window win, XEvent *keyrelease) -{ - vwm_window_t *vwin; - KeySym sym; - - switch ((sym = XLookupKeysym(&keyrelease->xkey, 0))) { - case XK_Alt_R: - case XK_Alt_L: /* TODO: actually use the modifier mapping, for me XK_Alt_[LR] is Mod1. XGetModifierMapping()... */ - VWM_TRACE("XK_Alt_[LR] released"); - - /* aborted? try restore focused_origin */ - if (key_is_grabbed > 1 && focused_origin) { - VWM_TRACE("restoring %p on %p", focused_origin, focused_origin->desktop); - vwm_desktop_focus(focused_origin->desktop); - vwm_win_focus(focused_origin); - } - - /* make the focused window the most recently used */ - if ((vwin = vwm_win_focused())) vwm_win_mru(vwin); - - /* make the focused desktop the most recently used */ - if (focused_context == VWM_CONTEXT_FOCUS_DESKTOP && focused_desktop) vwm_desktop_mru(focused_desktop); - - break; - - default: - VWM_TRACE("Unhandled keycode: %x", (unsigned int)sym); - break; - } - - if (key_is_grabbed && !vwm_keyspressed()) { - XUngrabKeyboard(display, CurrentTime); - XFlush(display); - key_is_grabbed = 0; - fence_mask = 0; /* reset the fence mask on release for VWM_FENCE_MASKED_VIOLATE */ - } -} - - -/* Called in response to KeyPress events, I currenly only grab Mod1 keypress events */ -static void vwm_keypressed(Window win, XEvent *keypress) -{ - vwm_window_t *vwin; - KeySym sym; - static KeySym last_sym; - static typeof(keypress->xkey.state) last_state; - static int repeat_cnt = 0; - int do_grab = 0; - char *quit_console_args[] = {"/bin/sh", "-c", "screen -dr " CONSOLE_SESSION_STRING " -X quit", NULL}; - - sym = XLookupKeysym(&keypress->xkey, 0); - - /* detect repeaters, note repeaters do not span interrupted Mod1 sequences! */ - if (key_is_grabbed && sym == last_sym && keypress->xkey.state == last_state) { - repeat_cnt++; - } else { - repeat_cnt = 0; - } - - vwin = vwm_win_focused(); - - switch (sym) { - -#define launcher(_sym, _label, _argv)\ - case _sym: \ - { \ - char *args[] = {"/bin/sh", "-c", "screen -dr " CONSOLE_SESSION_STRING " -X screen /bin/sh -i -x -c \"" _argv " || sleep 86400\"", NULL};\ - vwm_launch(args, VWM_LAUNCH_MODE_BG);\ - break; \ - } -#include "launchers.def" -#undef launcher - case XK_Alt_L: /* transaction abort */ - case XK_Alt_R: - if (key_is_grabbed) key_is_grabbed++; - VWM_TRACE("aborting with origin %p", focused_origin); - break; - - case XK_grave: /* toggle shelf visibility */ - vwm_context_focus(VWM_CONTEXT_FOCUS_OTHER); - break; - - case XK_Tab: /* cycle focused window */ - do_grab = 1; /* update MRU window on commit (Mod1 release) */ - - /* focus the next window, note this doesn't affect MRU yet, that happens on Mod1 release */ - if (vwin) { - if (keypress->xkey.state & ShiftMask) { - vwm_win_focus_next(vwin, VWM_FENCE_MASKED_VIOLATE); - } else { - vwm_win_focus_next(vwin, VWM_FENCE_RESPECT); - } - } - break; - - case XK_space: { /* cycle focused desktop utilizing MRU */ - vwm_desktop_t *next_desktop = list_entry(focused_desktop->desktops_mru.next == &desktops_mru ? focused_desktop->desktops_mru.next->next : focused_desktop->desktops_mru.next, vwm_desktop_t, desktops_mru); /* XXX: note the sensitivity to the desktops_mru head here, we want to look past it. */ - - do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ - - if (keypress->xkey.state & ShiftMask) { - /* migrate the focused window with the desktop focus to the most recently used desktop */ - if (vwin) vwm_win_migrate(vwin, next_desktop); - } else { - vwm_desktop_focus(next_desktop); - } - break; - } - - case XK_d: /* destroy focused */ - if (vwin) { - if (keypress->xkey.state & ShiftMask) { /* brutally destroy the focused window */ - XKillClient(display, vwin->xwindow->id); - } else { /* kindly destroy the focused window */ - vwm_xwin_message(vwin->xwindow, wm_protocols_atom, wm_delete_atom); - } - } else if (focused_context == VWM_CONTEXT_FOCUS_DESKTOP) { - /* destroy the focused desktop when destroy occurs without any windows */ - vwm_desktop_destroy(focused_desktop); - } - break; - - case XK_Escape: /* leave VWM rudely, after triple press */ - do_grab = 1; - - if (repeat_cnt == 2) { - vwm_launch(quit_console_args, VWM_LAUNCH_MODE_FG); - exit(42); - } - break; - - case XK_v: /* instantiate (and focus) a new (potentially empty, unless migrating) virtual desktop */ - do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ - - if (keypress->xkey.state & ShiftMask) { - if (vwin) { - /* migrate the focused window to a newly created virtual desktop, focusing the new desktop simultaneously */ - vwm_win_migrate(vwin, vwm_desktop_create(NULL)); - } - } else { - vwm_desktop_focus(vwm_desktop_create(NULL)); - vwm_desktop_mru(focused_desktop); - } - break; - - case XK_h: /* previous virtual desktop, if we're in the shelf context this will simply switch to desktop context */ - do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ - - if (keypress->xkey.state & ShiftMask) { - if (vwin && vwin->desktop->desktops.prev != &desktops) { - /* migrate the focused window with the desktop focus to the previous desktop */ - vwm_win_migrate(vwin, list_entry(vwin->desktop->desktops.prev, vwm_desktop_t, desktops)); - } - } else { - if (focused_context == VWM_CONTEXT_FOCUS_SHELF) { - /* focus the focused desktop instead of the shelf */ - vwm_context_focus(VWM_CONTEXT_FOCUS_DESKTOP); - } else if (focused_desktop->desktops.prev != &desktops) { - /* focus the previous desktop */ - vwm_desktop_focus(list_entry(focused_desktop->desktops.prev, vwm_desktop_t, desktops)); - } - } - break; - - case XK_l: /* next virtual desktop, if we're in the shelf context this will simply switch to desktop context */ - do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ - - if (keypress->xkey.state & ShiftMask) { - if (vwin && vwin->desktop->desktops.next != &desktops) { - /* migrate the focused window with the desktop focus to the next desktop */ - vwm_win_migrate(vwin, list_entry(vwin->desktop->desktops.next, vwm_desktop_t, desktops)); - } - } else { - if (focused_context == VWM_CONTEXT_FOCUS_SHELF) { - /* focus the focused desktop instead of the shelf */ - vwm_context_focus(VWM_CONTEXT_FOCUS_DESKTOP); - } else if (focused_desktop->desktops.next != &desktops) { - /* focus the next desktop */ - vwm_desktop_focus(list_entry(focused_desktop->desktops.next, vwm_desktop_t, desktops)); - } - } - break; - - case XK_k: /* raise or shelve the focused window */ - if (vwin) { - if (keypress->xkey.state & ShiftMask) { /* shelf the window and focus the shelf */ - if (focused_context != VWM_CONTEXT_FOCUS_SHELF) { - /* shelve the focused window while focusing the shelf */ - vwm_win_shelve(vwin); - vwm_context_focus(VWM_CONTEXT_FOCUS_SHELF); - } - } else { - do_grab = 1; - - XRaiseWindow(display, vwin->xwindow->id); - - if (repeat_cnt == 1) { - /* double: reraise & fullscreen */ - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); - } else if (repeat_cnt == 2) { - /* triple: reraise & fullscreen w/borders obscured by screen perimiter */ - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_ALL); - } else if (xinerama_screens_cnt > 1) { - if (repeat_cnt == 3) { - /* triple: reraise & fullscreen across all screens */ - vwm_win_autoconf(vwin, VWM_SCREEN_REL_TOTAL, VWM_WIN_AUTOCONF_FULL); - } else if (repeat_cnt == 4) { - /* quadruple: reraise & fullscreen w/borders obscured by screen perimiter */ - vwm_win_autoconf(vwin, VWM_SCREEN_REL_TOTAL, VWM_WIN_AUTOCONF_ALL); - } - } - XFlush(display); - } - } - break; - - case XK_j: /* lower or unshelve the focused window */ - if (vwin) { - if (keypress->xkey.state & ShiftMask) { /* unshelf the window to the focused desktop, and focus the desktop */ - if (focused_context == VWM_CONTEXT_FOCUS_SHELF) { - /* unshelve the focused window, focus the desktop it went to */ - vwm_win_migrate(vwin, focused_desktop); - } - } else { - if (vwin->autoconfigured == VWM_WIN_AUTOCONF_ALL) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); - } else { - XLowerWindow(display, vwin->xwindow->id); - } - XFlush(display); - } - } - break; - - case XK_Return: /* (full-screen / restore) focused window */ - if (vwin) { - if (vwin->autoconfigured) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_NONE); - } else { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); - } - } - break; - - case XK_s: /* shelve focused window */ - if (vwin && !vwin->shelved) vwm_win_shelve(vwin); - break; - - case XK_bracketleft: /* reconfigure the focused window to occupy the left or top half of the screen or left quarters on repeat */ - if (vwin) { - do_grab = 1; - - if (keypress->xkey.state & ShiftMask) { - if (!repeat_cnt) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_TOP); - } else { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_TOP_LEFT); - } - } else { - if (!repeat_cnt) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_LEFT); - } else { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_BOTTOM_LEFT); - } - } - } - break; - - case XK_bracketright: /* reconfigure the focused window to occupy the right or bottom half of the screen or right quarters on repeat */ - if (vwin) { - do_grab = 1; - - if (keypress->xkey.state & ShiftMask) { - if (!repeat_cnt) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_BOTTOM); - } else { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_BOTTOM_RIGHT); - } - } else { - if (!repeat_cnt) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_RIGHT); - } else { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_TOP_RIGHT); - } - } - } - break; - - case XK_semicolon: /* toggle composited overlays */ - vwm_comp_toggle(); - break; - - case XK_apostrophe: /* reset snowflakes of the focused window, suppressed when not compositing */ - if (vwin && compositing_mode && vwin->xwindow->overlay.snowflakes_cnt) { - vwin->xwindow->overlay.snowflakes_cnt = 0; - vwm_comp_damage_win(vwin->xwindow); - } - break; - - case XK_Right: /* increase sampling frequency */ - if (sampling_interval + 1 < sizeof(sampling_intervals) / sizeof(sampling_intervals[0])) sampling_interval++; - break; - - case XK_Left: /* decrease sampling frequency, -1 pauses */ - if (sampling_interval >= 0) sampling_interval--; - break; - default: - VWM_TRACE("Unhandled keycode: %x", (unsigned int)sym); - break; - } - - /* if what we're doing requests a grab, if not already grabbed, grab keyboard */ - if (!key_is_grabbed && do_grab) { - VWM_TRACE("saving focused_origin of %p", vwin); - focused_origin = vwin; /* for returning to on abort */ - XGrabKeyboard(display, RootWindow(display, screen_num), False, GrabModeAsync, GrabModeAsync, CurrentTime); - key_is_grabbed = 1; - } - - /* remember the symbol for repeater detection */ - last_sym = sym; - last_state = keypress->xkey.state; -} - - - /* some randomish things called from main() */ - -/* manage all existing windows (for startup) */ -static int vwm_manage_existing(void) -{ - Window root, parent; - Window *children = NULL; - unsigned int n_children, i; - - XGrabServer(display); - XSync(display, False); - XQueryTree(display, RootWindow(display, screen_num), &root, &parent, &children, &n_children); - - for (i = 0; i < n_children; i++) { - if (children[i] == None) continue; - - if ((vwm_xwin_create(children[i], VWM_GRABBED) == NULL)) goto _fail_grabbed; - } - - XUngrabServer(display); - - if (children) XFree(children); - - return 1; - -_fail_grabbed: - XUngrabServer(display); - - if (children) XFree(children); - - return 0; -} - - -/* comvenience function for returning the time delta as a seconds.fraction float */ -static float delta(struct timeval *cur, struct timeval *prev) -{ - struct timeval res; - float delta; - - /* determine the # of whole.fractional seconds between prev and cur */ - timersub(cur, prev, &res); - - delta = res.tv_sec; - delta += (float)((float)res.tv_usec) / 1000000.0; - - return delta; -} - -static int errhandler(Display *display, XErrorEvent *err) -{ - /* TODO */ - return 1; -} - -int main(int argc, char *argv[]) -{ - int err = 0; - int done = 0; - XEvent event; - Cursor pointer; - struct pollfd pfd; - char *console_args[] = {"xterm", "-class", CONSOLE_WM_CLASS, "-e", "/bin/sh", "-c", "screen -D -RR " CONSOLE_SESSION_STRING, NULL}; - Window bitmask; - vwm_clickety_t clickety = { .vwin = NULL }; - -#define reterr_if(_cond, _fmt, _args...) \ - err++;\ - if (_cond) {\ - VWM_ERROR(_fmt, ##_args);\ - return err;\ - } - - /* open connection with the server */ - reterr_if((display = XOpenDisplay(NULL)) == NULL, "Cannot open display"); - - /* prevent children from inheriting the X connection */ - reterr_if(fcntl(ConnectionNumber(display), F_SETFD, FD_CLOEXEC) < 0, "Cannot set FD_CLOEXEC on X connection"); - - /* get our scheduling priority, clients are launched with a priority LAUNCHED_RELATIVE_PRIORITY nicer than this */ - reterr_if((priority = getpriority(PRIO_PROCESS, getpid())) == -1, "Cannot get scheduling priority"); - - XSetErrorHandler(errhandler); - - screen_num = DefaultScreen(display); - - reterr_if(!XQueryExtension (display, COMPOSITE_NAME, &composite_opcode, &composite_event, &composite_error), "No composite extension available"); - reterr_if(!XDamageQueryExtension(display, &damage_event, &damage_error), "No damage extension available"); - if (XSyncQueryExtension(display, &sync_event, &sync_error)) { - /* set the window manager to the maximum X client priority */ - XSyncSetPriority(display, RootWindow(display, screen_num), 0x7fffffff); - } - - if (XineramaQueryExtension(display, &xinerama_event, &xinerama_error)) { - xinerama_screens = XineramaQueryScreens(display, &xinerama_screens_cnt); - } - - if (XRRQueryExtension(display, &randr_event, &randr_error)) { - XRRSelectInput(display, RootWindow(display, screen_num), RRScreenChangeNotifyMask); - } - - /* allocate colors, I make assumptions about the X server's color capabilities since I'll only use this on modern-ish computers... */ - cmap = DefaultColormap(display, screen_num); - -#define color(_sym, _str) \ - XAllocNamedColor(display, cmap, _str, &_sym ## _color, &_sym ## _color); -#include "colors.def" -#undef color - - wm_delete_atom = XInternAtom(display, "WM_DELETE_WINDOW", False); - wm_protocols_atom = XInternAtom(display, "WM_PROTOCOLS", False); - wm_pid_atom = XInternAtom(display, "_NET_WM_PID", False); - - XSelectInput(display, RootWindow(display, screen_num), - PropertyChangeMask | SubstructureNotifyMask | SubstructureRedirectMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask); - XGrabKey(display, AnyKey, WM_GRAB_MODIFIER, RootWindow(display, screen_num), False, GrabModeAsync, GrabModeAsync); - - XFlush(display); - - XSetInputFocus(display, RootWindow(display, screen_num), RevertToPointerRoot, CurrentTime); - - /* initialize libvmon */ - vmon_init(&vmon, VMON_FLAG_2PASS, VMON_WANT_SYS_STAT, (VMON_WANT_PROC_STAT | VMON_WANT_PROC_FOLLOW_CHILDREN | VMON_WANT_PROC_FOLLOW_THREADS)); - vmon.proc_ctor_cb = vmon_ctor_cb; - vmon.proc_dtor_cb = vmon_dtor_cb; - vmon.sample_cb = sample_callback; - - /* get all the text and graphics stuff setup for overlays */ - reterr_if(!(overlay_font = XLoadQueryFont(display, OVERLAY_FIXED_FONT)), "failed to open font: " OVERLAY_FIXED_FONT); - - /* create a GC for rendering the text using Xlib into the text overlay stencils */ - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, 1, OVERLAY_MASK_DEPTH); - text_gc = XCreateGC(display, bitmask, 0, NULL); - XSetForeground(display, text_gc, WhitePixel(display, screen_num)); - XFreePixmap(display, bitmask); - - /* create some repeating source fill pictures for drawing through the text and graph stencils */ - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, 1, 32); - overlay_text_fill = XRenderCreatePicture(display, bitmask, XRenderFindStandardFormat(display, PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(display, PictOpSrc, overlay_text_fill, &overlay_visible_color, 0, 0, 1, 1); - - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, 1, 32); - overlay_shadow_fill = XRenderCreatePicture(display, bitmask, XRenderFindStandardFormat(display, PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(display, PictOpSrc, overlay_shadow_fill, &overlay_shadow_color, 0, 0, 1, 1); - - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, OVERLAY_ROW_HEIGHT, 32); - overlay_bg_fill = XRenderCreatePicture(display, bitmask, XRenderFindStandardFormat(display, PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(display, PictOpSrc, overlay_bg_fill, &overlay_bg_color, 0, 0, 1, OVERLAY_ROW_HEIGHT); - XRenderFillRectangle(display, PictOpSrc, overlay_bg_fill, &overlay_div_color, 0, OVERLAY_ROW_HEIGHT - 1, 1, 1); - - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, 1, 32); - overlay_snowflakes_text_fill = XRenderCreatePicture(display, bitmask, XRenderFindStandardFormat(display, PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(display, PictOpSrc, overlay_snowflakes_text_fill, &overlay_snowflakes_visible_color, 0, 0, 1, 1); - - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, 1, 32); - overlay_grapha_fill = XRenderCreatePicture(display, bitmask, XRenderFindStandardFormat(display, PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(display, PictOpSrc, overlay_grapha_fill, &overlay_grapha_color, 0, 0, 1, 1); - - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, 1, 32); - overlay_graphb_fill = XRenderCreatePicture(display, bitmask, XRenderFindStandardFormat(display, PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(display, PictOpSrc, overlay_graphb_fill, &overlay_graphb_color, 0, 0, 1, 1); - - bitmask = XCreatePixmap(display, RootWindow(display, screen_num), 1, 2, 32); - overlay_finish_fill = XRenderCreatePicture(display, bitmask, XRenderFindStandardFormat(display, PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(display, PictOpSrc, overlay_finish_fill, &overlay_visible_color, 0, 0, 1, 1); - XRenderFillRectangle(display, PictOpSrc, overlay_finish_fill, &overlay_trans_color, 0, 1, 1, 1); - - /* create initial virtual desktop */ - vwm_desktop_focus(vwm_desktop_create(NULL)); - vwm_desktop_mru(focused_desktop); - - /* manage all preexisting windows */ - vwm_manage_existing(); - - /* create GC for logo drawing and window rubber-banding */ - gc = XCreateGC(display, RootWindow(display, screen_num), 0, NULL); - XSetSubwindowMode(display, gc, IncludeInferiors); - XSetFunction(display, gc, GXxor); - - /* launch the console here so it's likely ready by the time the logo animation finishes (there's no need to synchronize with it currently) */ - vwm_launch(console_args, VWM_LAUNCH_MODE_BG); - - /* first the logo color is the foreground */ - XSetForeground(display, gc, logo_color.pixel); - vwm_draw_logo(); - - /* change to the rubber-banding foreground color */ - XSetForeground(display, gc, rubberband_color.pixel); - - XClearWindow(display, RootWindow(display, screen_num)); - - /* set the pointer */ - pointer = XCreateFontCursor(display, XC_X_cursor); - XDefineCursor(display, RootWindow(display, screen_num), pointer); - - pfd.events = POLLIN; - pfd.revents = 0; - pfd.fd = ConnectionNumber(display); - - gettimeofday(&this_sample, NULL); - while (!done) { - do { - static int sampling_paused = 0; - static int contiguous_drops = 0; - float this_delta; - - gettimeofday(&maybe_sample, NULL); - if ((sampling_interval == -1 && !sampling_paused) || /* XXX this is kind of a kludge to get the 0 Hz indicator drawn before pausing */ - (sampling_interval != -1 && ((this_delta = delta(&maybe_sample, &this_sample)) >= sampling_intervals[sampling_interval]))) { - vmon_sys_stat_t *sys_stat; - - /* automatically lower the sample rate if we can't keep up with the current sample rate */ - if (sampling_interval != -1 && sampling_interval <= prev_sampling_interval && - this_delta >= (sampling_intervals[sampling_interval] * 1.5)) { - contiguous_drops++; - /* require > 1 contiguous drops before lowering the rate, tolerates spurious one-off stalls */ - if (contiguous_drops > 2) sampling_interval--; - } else contiguous_drops = 0; - - /* age the sys-wide sample data into "last" variables, before the new sample overwrites them. */ - last_sample = this_sample; - this_sample = maybe_sample; - if ((sys_stat = vmon.stores[VMON_STORE_SYS_STAT])) { - last_user_cpu = sys_stat->user; - last_system_cpu = sys_stat->system; - last_total = sys_stat->user + - sys_stat->nice + - sys_stat->system + - sys_stat->idle + - sys_stat->iowait + - sys_stat->irq + - sys_stat->softirq + - sys_stat->steal + - sys_stat->guest; - - last_idle = sys_stat->idle; - last_iowait = sys_stat->iowait; - } - - vmon_sample(&vmon); /* XXX: calls proc_sample_callback() for explicitly monitored processes after sampling their descendants */ - /* XXX: also calls sample_callback() per invocation after sampling the sys wants */ - sampling_paused = (sampling_interval == -1); - prev_sampling_interval = sampling_interval; - } - - XFlush(display); - - if (!XPending(display)) { - /* TODO: make some effort to compute how long to sleep, but this is perfectly fine for now. */ - if (poll(&pfd, 1, sampling_interval != -1 ? sampling_intervals[sampling_interval] * 300.0 : -1) == 0) break; - } - - XNextEvent(display, &event); - switch (event.type) { - case KeyPress: - VWM_TRACE("keypress"); - vwm_keypressed(event.xkey.window, &event); - break; - - case KeyRelease: - VWM_TRACE("keyrelease"); - vwm_keyreleased(event.xkey.window, &event); - break; - - case ButtonPress: - VWM_TRACE("buttonpresss"); - vwm_clickety_pressed(&clickety, event.xbutton.window, &event.xbutton); - break; - - case MotionNotify: - //VWM_TRACE("motionnotify"); - vwm_clickety_motion(&clickety, event.xmotion.window, &event.xmotion); - break; - - case ButtonRelease: - VWM_TRACE("buttonrelease"); - vwm_clickety_released(&clickety, event.xbutton.window, &event.xbutton); - break; - - case CreateNotify: - VWM_TRACE("createnotify"); - vwm_xwin_create(event.xcreatewindow.window, VWM_NOT_GRABBED); - break; - - case DestroyNotify: { - vwm_xwindow_t *xwin; - VWM_TRACE("destroynotify"); - if ((xwin = vwm_xwin_lookup(event.xdestroywindow.window))) { - vwm_xwin_destroy(xwin); - } - break; - } - - case ConfigureRequest: { - XWindowChanges changes = { - .x = event.xconfigurerequest.x, /* TODO: for now I don't manipulate anything */ - .y = event.xconfigurerequest.y, - .width = event.xconfigurerequest.width, - .height = event.xconfigurerequest.height, - .border_width = WINDOW_BORDER_WIDTH /* except I do override whatever the border width may be */ - }; - unsigned long change_mask = (event.xconfigurerequest.value_mask & (CWX | CWY | CWWidth | CWHeight)) | CWBorderWidth; - vwm_xwindow_t *xwin; - - /* XXX: windows raising themselves is annoying, so discard CWSibling and CWStackMode. */ - VWM_TRACE("configurerequest x=%i y=%i w=%i h=%i", changes.x, changes.y, changes.width, changes.height); - - if ((xwin = vwm_xwin_lookup(event.xconfigure.window)) && - xwin->managed && - xwin->managed->autoconfigured == VWM_WIN_AUTOCONF_ALL) { - /* this is to allow auto-allscreen to succeed in getting a borderless window configured */ - change_mask &= ~CWBorderWidth; - } - - XConfigureWindow(display, event.xconfigurerequest.window, change_mask, &changes); - break; - } - - case ConfigureNotify: { - vwm_xwindow_t *xwin; - VWM_TRACE("configurenotify"); - if ((xwin = vwm_xwin_lookup(event.xconfigure.window))) { - XWindowAttributes attrs; - vwm_xwin_restack(xwin, event.xconfigure.above); - XGetWindowAttributes(display, event.xconfigure.window, &attrs); - if (compositing_mode) { - /* damage the old and new window areas */ - XserverRegion region; - XRectangle rects[2] = { { xwin->attrs.x, - xwin->attrs.y, - xwin->attrs.width + xwin->attrs.border_width * 2, - xwin->attrs.height + xwin->attrs.border_width * 2 }, - { attrs.x, - attrs.y, - attrs.width + attrs.border_width * 2, - attrs.height + attrs.border_width * 2 } }; - - region = XFixesCreateRegion(display, rects, 2); - vwm_comp_damage_add(region); - vwm_xwin_unbind_namewindow(xwin); - vwm_xwin_bind_namewindow(xwin); - } - VWM_TRACE("pre x=%i y=%i w=%i h=%i\n", xwin->attrs.x, xwin->attrs.y, xwin->attrs.width, xwin->attrs.height); - xwin->attrs = attrs; - VWM_TRACE("post x=%i y=%i w=%i h=%i\n", xwin->attrs.x, xwin->attrs.y, xwin->attrs.width, xwin->attrs.height); - } - break; - } - - case UnmapNotify: { - vwm_xwindow_t *xwin; - VWM_TRACE("unmapnotify"); - /* unlike MapRequest, we simply are notified when a window is unmapped. */ - if ((xwin = vwm_xwin_lookup(event.xunmap.window))) { - if (xwin->managed) { - if (xwin->managed->unmapping) { - VWM_TRACE("swallowed vwm-induced UnmapNotify"); - xwin->managed->unmapping = 0; - } else { - /* client requested unmap, demote the window and note the unmapped state */ - vwm_win_unmanage(xwin->managed); - xwin->mapped = 0; - } - } else { - /* if it's not managed, we can't have caused the map */ - xwin->mapped = 0; - } - - if (compositing_mode) vwm_comp_damage_win(xwin); - } - break; - } - - case MapNotify: { - vwm_xwindow_t *xwin; - VWM_TRACE("mapnotify"); - if ((xwin = vwm_xwin_lookup(event.xmap.window))) { - if (xwin->managed && xwin->managed->mapping) { - VWM_TRACE("swallowed vwm-induced MapNotify"); - xwin->managed->mapping = 0; - } else { - /* some windows like popup dialog boxes bypass MapRequest */ - xwin->mapped = 1; - } - - if (compositing_mode) { - vwm_comp_damage_win(xwin); - vwm_xwin_unbind_namewindow(xwin); - vwm_xwin_bind_namewindow(xwin); - } - } - break; - } - - case MapRequest: { - vwm_xwindow_t *xwin; - vwm_window_t *vwin = NULL; - int domap = 1; - VWM_TRACE("maprequest"); - if ((xwin = vwm_xwin_lookup(event.xmap.window)) && - ((vwin = xwin->managed) || (vwin = vwm_win_manage_xwin(xwin)))) { - XWindowAttributes attrs; - XWindowChanges changes = {.x = 0, .y = 0}; - unsigned changes_mask = (CWX | CWY); - XClassHint *classhint; - const vwm_screen_t *scr = NULL; - - xwin->mapped = 1; /* note that the client mapped the window */ - - /* figure out if the window is the console */ - if ((classhint = XAllocClassHint())) { - if (XGetClassHint(display, event.xmap.window, classhint) && !strcmp(classhint->res_class, CONSOLE_WM_CLASS)) { - console = vwin; - vwm_win_shelve(vwin); - vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); - domap = 0; - } - - if (classhint->res_class) XFree(classhint->res_class); - if (classhint->res_name) XFree(classhint->res_name); - XFree(classhint); - } - - /* TODO: this is a good place to hook in a window placement algo */ - - /* on client-requested mapping we place the window */ - if (!vwin->shelved) { - /* we place the window on the screen containing the the pointer only if that screen is empty, - * otherwise we place windows on the screen containing the currently focused window */ - /* since we query the geometry of windows in determining where to place them, a configuring - * flag is used to exclude the window being configured from those queries */ - scr = vwm_screen_find(VWM_SCREEN_REL_POINTER); - vwin->configuring = 1; - if (vwm_screen_is_empty(scr)) { - /* focus the new window if it isn't already focused when it's going to an empty screen */ - VWM_TRACE("window \"%s\" is alone on screen \"%i\", focusing", vwin->xwindow->name, scr->screen_number); - vwm_win_focus(vwin); - } else { - scr = vwm_screen_find(VWM_SCREEN_REL_XWIN, focused_desktop->focused_window->xwindow); - } - vwin->configuring = 0; - - changes.x = scr->x_org; - changes.y = scr->y_org; - } else if (focused_context == VWM_CONTEXT_FOCUS_SHELF) { - scr = vwm_screen_find(VWM_SCREEN_REL_XWIN, focused_shelf->xwindow); - changes.x = scr->x_org; - changes.y = scr->y_org; - } - - /* XXX TODO: does this belong here? */ - XGetWMNormalHints(display, event.xmap.window, vwin->hints, &vwin->hints_supplied); - XGetWindowAttributes(display, event.xmap.window, &attrs); - - - /* if the window size is precisely the screen size then directly "allscreen" the window right here */ - if (!vwin->shelved && scr && - attrs.width == scr->width && - attrs.height == scr->height) { - VWM_TRACE("auto-allscreened window \"%s\"", vwin->xwindow->name); - changes.border_width = 0; - changes_mask |= CWBorderWidth; - vwin->autoconfigured = VWM_WIN_AUTOCONF_ALL; - } - - vwin->client.x = changes.x; - vwin->client.y = changes.y; - vwin->client.height = attrs.height; - vwin->client.width = attrs.width; - - XConfigureWindow(display, event.xmap.window, changes_mask, &changes); - } - - if (domap) { - XMapWindow(display, event.xmap.window); - if (vwin && vwin->desktop->focused_window == vwin) { - XSync(display, False); - XSetInputFocus(display, vwin->xwindow->id, RevertToPointerRoot, CurrentTime); - } - } - break; - } - - case PropertyNotify: { - vwm_xwindow_t *xwin; - VWM_TRACE("property notify"); - if ((xwin = vwm_xwin_lookup(event.xproperty.window)) && - event.xproperty.atom == wm_pid_atom && - event.xproperty.state == PropertyNewValue) vwm_xwin_monitor(xwin); - break; - } - - case MappingNotify: - VWM_TRACE("mapping notify"); - XRefreshKeyboardMapping(&event.xmapping); - break; - - case Expose: - VWM_TRACE("expose"); - break; - case GravityNotify: - VWM_TRACE("gravitynotify"); - break; - case ReparentNotify: - VWM_TRACE("reparentnotify"); - break; - default: - if (event.type == randr_event + RRScreenChangeNotify) { - VWM_TRACE("rrscreenchangenotify"); - if (xinerama_screens) XFree(xinerama_screens); - xinerama_screens = XineramaQueryScreens(display, &xinerama_screens_cnt); - - if (compositing_mode) vwm_comp_invalidate_root(); - } else if (event.type == damage_event + XDamageNotify) { - //VWM_TRACE("damagenotify"); - vwm_comp_damage_event((XDamageNotifyEvent *)&event); - } else { - VWM_ERROR("Unhandled X op %i", event.type); - } - break; - } - } while (QLength(display)); - - if (combined_damage != None) { /* if there's damage to repaint, do it, this only happens when compositing for overlays is enabled */ - vwm_comp_paint_all(); - XSync(display, False); - } - } - - /* close connection to server */ - XFlush(display); - XCloseDisplay(display); - - return 0; -} |