From 7bcde781975ddf4a5ce266a9214ba26f563ed063 Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Sun, 14 Sep 2014 10:30:13 -0700 Subject: Import of vwm3 changes from published source tgz Major changes from vwm2 include: - Introduction of integrated X client process and descendants monitoring in the form of per-window composited overlays. The rendering is done using the Composite, Damage, and Render extensions. When the monitors are visible, vwm3 is a compositing manager. When invisible, vwm3 continues to be an immediate-mode classic X11 minimalist window manager. Monitors are toggled via Mod1-;, Mod1-LeftArrow and Mod1-RightArrow may be used to decrease and increase the sampling frequency, respectively. Mod1-' clears the monitored tasks history for the focused window when monitoring is visible. This feature depends on the CONFIG_CHECKPOINT_RESTORE kernel configuration option for the discovery of descendant processes. Without this kernel option enabled, you'll only get a single process monitored per window; the X client process as indicated by the _NET_WM_PID atom of the window. A library called libvmon has been introduced for the abstraction of lightweight system and process statistics sampling. Since vwm2 received backported features unrelated to monitoring or compositing while vwm3 was developed, there isn't really much difference between the two outside the monitoring context. This isn't to say there isn't much activity in the code, the addition of compositing and monitoring requires a substantial amount of code relative to the scale of vwm[12]. --- vwm.c | 2551 ++++++++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 1973 insertions(+), 578 deletions(-) (limited to 'vwm.c') diff --git a/vwm.c b/vwm.c index 77836b1..0d18832 100644 --- a/vwm.c +++ b/vwm.c @@ -1,5 +1,5 @@ /* - * vwm - Vito's Window Manager, a minimal, non-reparenting X11 window manager + * \/\/\ * * Copyright (C) 2012-2014 Vito Caputo - * @@ -16,26 +16,9 @@ * along with this program. If not, see . */ -/* Work started 8/3/2012, in the form of research into creating WM's using xcb, - * clearly I decided to just use Xlib instead at some point, and here we are. +/* The compositing code is heavily influenced by Keith Packard's xcompmgr. */ -/* 4/25/2013 - Incorporated use of a screen session as a console/launcher. - * This introduces a shelved-at-startup window containing an xterm running - * a screen where all vwm-launched processes are executed with output - * captured. Most WM's I've used don't provide a way to access the - * stdout/stderr of WM-launched applications. - * - * vwm now depends on GNU screen as a result. - */ - -/* 6/09/2014 - After receiving a Xinerama-enabling patch from Philip Freeman, - * Xinerama support was added using his patch as a basis. There are still some - * rough edges though as I don't generally work at a multihead desk. - * See XINERAMA in the TODO file. - */ - - #include #include #include @@ -43,6 +26,10 @@ #include /* SYNC extension, enables us to give vwm the highest X client priority, helps keep vwm responsive at all times */ #include /* XINERAMA extension, facilitates easy multihead awareness */ #include /* RANDR extension, facilitates display configuration change awareness */ +#include /* Damage extension, enables receipt of damage events, reports visible regions needing updating (compositing) */ +#include /* Render extension, enables use of alpha channels and accelerated rendering of surfaces having alpha (compositing) */ +#include /* Composite extension, enables off-screen redirection of window rendering (compositing) */ +#include /* XFixes extension exposes things like regions (compositing) */ #include #include #include @@ -58,7 +45,8 @@ #include #include #include - +#include +#include "libvmon/vmon.h" #include "vwm.h" #define WINDOW_BORDER_WIDTH 1 @@ -73,7 +61,17 @@ * 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 QUIT_CONSOLE_ON_EXIT /* instruct the console screen session to quit @ exit */ + + +#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 */ @@ -81,20 +79,28 @@ typedef enum _vwm_context_focus_t { 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); /* global list of all managed windows kept 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_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; @@ -107,44 +113,800 @@ 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 int randr_event, randr_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_visible(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)) +#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 visible (desktop switches) */ + if(compositing_mode && vwm_xwin_is_visible(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 */ -/* helper for returning what fraction (0.0-1.0) of vwin overlaps with scr */ -static float vwm_screen_overlaps_win(const vwm_screen_t *scr, vwm_window_t *vwin) +/* 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 < vwin->config.x || scr->x_org > vwin->config.x + vwin->config.width || - scr->y_org + scr->height < vwin->config.y || scr->y_org > vwin->config.y + vwin->config.height) + 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, vwin->config.x + vwin->config.width) - MAX(scr->x_org, vwin->config.x); - yover = MIN(scr->y_org + scr->height, vwin->config.y + vwin->config.height) - MAX(scr->y_org, vwin->config.y); + 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) / (vwin->config.width * vwin->config.height); + pct = (xover * yover) / (xwin->attrs.width * xwin->attrs.height); _out: - VWM_TRACE("xover=%f yover=%f width=%i height=%i pct=%.4f", xover, yover, vwin->config.width, vwin->config.height, pct); + VWM_TRACE("xover=%f yover=%f width=%i height=%i pct=%.4f", xover, yover, xwin->attrs.width, xwin->attrs.height, pct); return pct; } -/* helper for returning the correct screen, don't use the return value across event loops. */ +/* 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_WINDOW, /* return the screen the supplied window most resides in */ + 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; @@ -167,17 +929,17 @@ static const vwm_screen_t * vwm_screen_find(vwm_screen_rel_t rel, ...) for(i = 0, _tmp = xinerama_screens; i < xinerama_screens_cnt; _tmp = &xinerama_screens[++i]) switch(rel) { - case VWM_SCREEN_REL_WINDOW: { + case VWM_SCREEN_REL_XWIN: { va_list ap; - vwm_window_t *vwin; + vwm_xwindow_t *xwin; float best_pct = 0, this_pct; va_start(ap, rel); - vwin = va_arg(ap, vwm_window_t *); + xwin = va_arg(ap, vwm_xwindow_t *); va_end(ap); for_each_screen(scr) { - this_pct = vwm_screen_overlaps_win(scr, vwin); + this_pct = vwm_screen_overlaps_xwin(scr, xwin); if(this_pct > best_pct) { best = scr; best_pct = this_pct; @@ -228,19 +990,20 @@ _out: } -/* helper for determining if a screen contains any windows (assuming the current desktop) */ +/* check if a screen contains any windows (assuming the current desktop) */ static int vwm_screen_is_empty(const vwm_screen_t *scr) { - vwm_window_t *vwin; + vwm_xwindow_t *xwin; int is_empty = 1; - list_for_each_entry(vwin, &windows, windows) { - if(vwin->desktop == focused_desktop && !vwin->shelved && !vwin->configuring) { + 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_win(scr, vwin) >= 0.05) { + if(vwm_screen_overlaps_xwin(scr, xwin) >= 0.05) { is_empty = 0; break; } @@ -251,6 +1014,8 @@ static int vwm_screen_is_empty(const vwm_screen_t *scr) } + /* 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) @@ -293,7 +1058,9 @@ static void vwm_draw_logo(void) } -/* XXX: for now just double forks to avoid having to collect return statuses of the long-running process */ + /* 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, @@ -301,6 +1068,7 @@ typedef enum _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 */ @@ -313,34 +1081,38 @@ static void vwm_launch(char **argv, vwm_launch_mode_t 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_window_t *vwin; + 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->name); + 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 */ - list_for_each_entry(vwin, &windows, windows) { + /* 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\"", vwin->name); + 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->name); - XSetInputFocus(display, focused_desktop->focused_window->window, RevertToPointerRoot, CurrentTime); + 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; @@ -355,16 +1127,17 @@ static int vwm_context_focus(vwm_context_focus_t desired_context) /* 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(vwin, &windows, windows) { + list_for_each_entry(xwin, &xwindows, xwindows) { + if(!(vwin = xwin->managed)) continue; if(vwin->desktop == focused_desktop) { - VWM_TRACE("Unmapping desktop window \"%s\"", vwin->name); + 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->name); + VWM_TRACE("Mapping shelf window \"%s\"", focused_shelf->xwindow->name); vwm_win_map(focused_shelf); vwm_win_focus(focused_shelf); @@ -382,45 +1155,48 @@ static int vwm_context_focus(vwm_context_focus_t desired_context) } + /* virtual desktops */ + /* make the specified desktop the most recently used one */ -static void vwm_desktop_mru(vwm_desktop_t *d) +static void vwm_desktop_mru(vwm_desktop_t *desktop) { - VWM_TRACE("MRU desktop: %p", d); - list_move(&d->desktops_mru, &desktops_mru); + 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 *d) +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 != d) || focused_desktop != d) { - vwm_window_t *w; + 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(w, &windows, windows) { - if(w->shelved) continue; - if(w->desktop == focused_desktop) vwm_win_unmap(w); + 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(w, &windows, windows) { - if(w->shelved) continue; - if(w->desktop == d) vwm_win_map(w); + 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 = d; + 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->name); - XSetInputFocus(display, focused_desktop->focused_window->window, RevertToPointerRoot, CurrentTime); + VWM_TRACE("Focusing \"%s\"", focused_desktop->focused_window->xwindow->name); + XSetInputFocus(display, focused_desktop->focused_window->xwindow->id, RevertToPointerRoot, CurrentTime); } XUngrabServer(display); @@ -432,21 +1208,21 @@ static int vwm_desktop_focus(vwm_desktop_t *d) /* create a virtual desktop */ static vwm_desktop_t * vwm_desktop_create(char *name) { - vwm_desktop_t *d; + vwm_desktop_t *desktop; - d = malloc(sizeof(vwm_desktop_t)); - if(d == NULL) { + desktop = malloc(sizeof(vwm_desktop_t)); + if(desktop == NULL) { VWM_PERROR("Failed to allocate desktop"); goto _fail; } - d->name = name == NULL ? name : strdup(name); - d->focused_window = NULL; + desktop->name = name == NULL ? name : strdup(name); + desktop->focused_window = NULL; - list_add_tail(&d->desktops, &desktops); - list_add_tail(&d->desktops_mru, &desktops_mru); + list_add_tail(&desktop->desktops, &desktops); + list_add_tail(&desktop->desktops_mru, &desktops_mru); - return d; + return desktop; _fail: return NULL; @@ -454,78 +1230,318 @@ _fail: /* destroy a virtual desktop */ -static void vwm_desktop_destroy(vwm_desktop_t *d) +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(d->focused_window || (d->desktops.next == d->desktops.prev)) return; + 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(d == focused_desktop) { + if(desktop == focused_desktop) { vwm_desktop_t *next_desktop; list_for_each_entry(next_desktop, &desktops_mru, desktops_mru) { - if(next_desktop != d) { + if(next_desktop != desktop) { vwm_desktop_focus(next_desktop); break; } } } - list_del(&d->desktops); - list_del(&d->desktops_mru); + list_del(&desktop->desktops); + list_del(&desktop->desktops_mru); } -/* 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) + /* 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) { - VWM_TRACE("Unmapping \"%s\"", vwin->name); - vwin->unmapping = 1; - XUnmapWindow(display, vwin->window); + 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); } -/* map the specified window */ -static void vwm_win_map(vwm_window_t *vwin) +/* 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_TRACE("Mapping \"%s\"", vwin->name); - XMapWindow(display, vwin->window); + vwm_xwindow_t *tmp, *xwin = NULL; + + list_for_each_entry(tmp, &xwindows, xwindows) { + if(tmp->id == win) { + xwin = tmp; + break; + } + } + + return xwin; } -/* make the specified window the most recently used one */ -static void vwm_win_mru(vwm_window_t *vwin) +/* determine if a window is visible (vwm-mapped) according to the current context */ +static inline int vwm_xwin_is_visible(vwm_xwindow_t *xwin) { - list_move(&vwin->windows, &windows); + 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; } -/* send a client message to a window (currently used for WM_DELETE) */ -static void vwm_win_message(vwm_window_t *vwin, Atom type, long foo) +/* bind the window to a "namewindowpixmap" and create a picture from it (compositing) */ +void vwm_xwin_bind_namewindow(vwm_xwindow_t *xwin) { - XEvent event; + xwin->pixmap = XCompositeNameWindowPixmap(display, xwin->id); + xwin->picture = XRenderCreatePicture(display, xwin->pixmap, + XRenderFindVisualFormat(display, xwin->attrs.visual), + CPSubwindowMode, &pa_inferiors); + XFreePixmap(display, xwin->pixmap); +} - memset(&event, 0, sizeof(event)); - event.xclient.type = ClientMessage; - event.xclient.window = vwin->window; - 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, vwin->window, False, 0, &event); +/* 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); } -/* looks up the window in the global window list */ +/* 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, windows) { - if(tmp->window == win) { + list_for_each_entry(tmp, &windows_mru, windows_mru) { + if(tmp->xwindow->id == win) { vwin = tmp; break; } @@ -535,7 +1551,7 @@ static vwm_window_t * vwm_win_lookup(Window win) } -/* helper for returning the currently focused window (considers current context...), may return NULL */ +/* return the currently focused window (considers current context...), may return NULL */ static vwm_window_t * vwm_win_focused(void) { vwm_window_t *vwin = NULL; @@ -588,9 +1604,9 @@ static void vwm_win_autoconf(vwm_window_t *vwin, vwm_screen_rel_t rel, vwm_win_a 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->config; + if(vwin->autoconfigured == VWM_WIN_AUTOCONF_NONE) vwin->client = vwin->xwindow->attrs; - scr = vwm_screen_find(rel, vwin); + scr = vwm_screen_find(rel, vwin->xwindow); va_start(ap, conf); switch(conf) { case VWM_WIN_AUTOCONF_QUARTER: { @@ -679,7 +1695,7 @@ static void vwm_win_autoconf(vwm_window_t *vwin, vwm_screen_rel_t rel, vwm_win_a } va_end(ap); - XConfigureWindow(display, vwin->window, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &changes); + XConfigureWindow(display, vwin->xwindow->id, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &changes); vwin->autoconfigured = conf; } @@ -688,26 +1704,26 @@ static void vwm_win_autoconf(vwm_window_t *vwin, vwm_screen_rel_t rel, vwm_win_a /* this updates window border color as needed and the X input focus */ static void vwm_win_focus(vwm_window_t *vwin) { - VWM_TRACE("focusing: %#x", (unsigned int)vwin->window); + VWM_TRACE("focusing: %#x", (unsigned int)vwin->xwindow->id); /* change the focus to the new window */ - XSetInputFocus(display, vwin->window, RevertToPointerRoot, CurrentTime); + 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->window, vwin == console ? shelved_console_border_color.pixel : shelved_window_border_color.pixel); + 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_desktop && focused_desktop->focused_window) { /* if we've changed focus within the same desktop, set the currently focused window border to the * unfocused color. Otherwise, we want to leave the focused color on the old window on the old desktop */ - XSetWindowBorder(display, focused_desktop->focused_window->window, unfocused_window_border_color.pixel); + XSetWindowBorder(display, focused_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->window, focused_window_border_color.pixel); + 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 */ focused_desktop->focused_window = vwin; @@ -720,28 +1736,28 @@ 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_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_context_focus_t context, vwm_fence_t fence) { - const vwm_screen_t *scr = vwm_screen_find(VWM_SCREEN_REL_WINDOW, vwin), *next_scr = NULL; + 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, windows) { + 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 == &windows) continue; /* XXX: skip the containerless head, we're leveraging the circular list implementation */ + if(&next->windows_mru == &windows_mru) continue; /* XXX: skip the containerless head, we're leveraging the circular list implementation */ if((context == VWM_CONTEXT_FOCUS_SHELF && next->shelved) || ((context == VWM_CONTEXT_FOCUS_DESKTOP && !next->shelved && next->desktop == focused_desktop) && (fence == VWM_FENCE_IGNORE || - ((fence == VWM_FENCE_RESPECT || fence == VWM_FENCE_TRY_RESPECT) && vwm_screen_find(VWM_SCREEN_REL_WINDOW, next) == scr) || - (fence == VWM_FENCE_VIOLATE && vwm_screen_find(VWM_SCREEN_REL_WINDOW, next) != scr) || - (fence == VWM_FENCE_MASKED_VIOLATE && (next_scr = vwm_screen_find(VWM_SCREEN_REL_WINDOW, next)) != scr && + ((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; @@ -769,7 +1785,7 @@ _retry: if(next != next->desktop->focused_window) { /* focus the changed window */ vwm_win_focus(next); - XRaiseWindow(display, next->window); + XRaiseWindow(display, next->xwindow->id); } break; @@ -791,7 +1807,7 @@ _retry: break; } - VWM_TRACE("\"%s\"", next->name); + VWM_TRACE("vwin=%p xwin=%p name=\"%s\"", next, next->xwindow, next->xwindow->name); return next; } @@ -823,70 +1839,86 @@ static void vwm_win_shelve(vwm_window_t *vwin) } -typedef enum _vwm_grab_mode_t { - VWM_NOT_GRABBED = 0, - VWM_GRABBED -} vwm_grab_mode_t; - -/* manages a mapped window (called in response to MapRequest events, and during startup to manage existing windows) */ -static vwm_window_t * vwm_win_manage(Window win, vwm_grab_mode_t grabbed) +/* helper for (idempotently) unfocusing a window, deals with context switching etc... */ +static void vwm_win_unfocus(vwm_window_t *vwin) { - XWindowAttributes attrs; - vwm_window_t *vwin, *focused; + /* if we're the 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_CONTEXT_FOCUS_SHELF, VWM_FENCE_IGNORE); - /* prevent races */ - if(!grabbed) { - XGrabServer(display); - XSync(display, False); + 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; + } } - /* verify the window still exists */ - if(!XGetWindowAttributes(display, win, &attrs)) goto _fail_grabbed; + /* 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_CONTEXT_FOCUS_DESKTOP, VWM_FENCE_TRY_RESPECT); + } - /* honor overrides, don't manage InputOnly windows */ -#ifdef HONOR_OVERRIDE_REDIRECT - if(attrs.override_redirect || attrs.class == InputOnly) goto _fail_grabbed; -#else - if(attrs.class == InputOnly) goto _fail_grabbed; -#endif + if(vwin->desktop->focused_window == vwin) { + VWM_TRACE("desktop empty"); + vwin->desktop->focused_window = NULL; + } +} - VWM_TRACE("Managing %#x", (unsigned int)win); - /* allocate and initialize our per-managed-window structure and get it on the global windows list */ - vwin = (vwm_window_t *)malloc(sizeof(vwm_window_t)); - if(vwin == NULL) { - VWM_PERROR("Failed to allocate vwin"); - goto _fail_grabbed; +/* 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; + + 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; } - XUngrabButton(display, AnyButton, AnyModifier, win); - XGrabButton(display, AnyButton, WM_GRAB_MODIFIER, win, False, (PointerMotionMask | ButtonPressMask | ButtonReleaseMask), GrabModeAsync, GrabModeAsync, None, None); - XGrabKey(display, AnyKey, WM_GRAB_MODIFIER, win, False, GrabModeAsync, GrabModeAsync); - XSetWindowBorder(display, win, unfocused_window_border_color.pixel); + if(!(vwin = (vwm_window_t *)malloc(sizeof(vwm_window_t)))) { + VWM_PERROR("Failed to allocate vwin"); + goto _fail; + } - vwin->name = NULL; - XFetchName(display, win, &vwin->name); + XUngrabButton(display, AnyButton, AnyModifier, xwin->id); + XGrabButton(display, AnyButton, Mod1Mask, xwin->id, False, (PointerMotionMask | ButtonPressMask | ButtonReleaseMask), GrabModeAsync, GrabModeAsync, None, None); + XGrabKey(display, AnyKey, Mod1Mask, 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_grabbed; + goto _fail; } - XGetWMNormalHints(display, win, vwin->hints, &vwin->hints_supplied); + XGetWMNormalHints(display, xwin->id, vwin->hints, &vwin->hints_supplied); + + xwin->managed = vwin; + vwin->xwindow = xwin; vwin->desktop = focused_desktop; - vwin->window = win; vwin->autoconfigured = VWM_WIN_AUTOCONF_NONE; - vwin->unmapping = 0; - vwin->configuring = 0; + 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 */ - - /* remember whatever the current attributes are */ - vwin->client.x = attrs.x; - vwin->client.y = attrs.y; - vwin->client.width = attrs.width; - vwin->client.height = attrs.height; - vwin->config = vwin->client; + 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, @@ -903,163 +1935,292 @@ static vwm_window_t * vwm_win_manage(Window win, vwm_grab_mode_t grabbed) vwin->client.height = vwin->hints->base_height; } - /* put it on the global windows list, if there's a focused window insert the new one after it */ - if(!list_empty(&windows) && (focused = vwm_win_focused())) { + /* 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, &focused->windows); + list_add(&vwin->windows_mru, &focused->windows_mru); } else { - list_add(&vwin->windows, &windows); + list_add(&vwin->windows_mru, &windows_mru); } /* always raise newly managed windows so we know about them. */ - XRaiseWindow(display, vwin->window); + 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", vwin->name, focused_desktop->name); + VWM_TRACE("Mapped new window \"%s\" is alone on desktop \"%s\", focusing", xwin->name, focused_desktop->name); vwm_win_focus(vwin); } - if(!grabbed) XUngrabServer(display); - return vwin; -_fail_grabbed: - if(!grabbed) XUngrabServer(display); - +_fail: + if(vwin) { + if(vwin->hints) XFree(vwin->hints); + free(vwin); + } return NULL; } -/* helper for (idempotently) unfocusing a window, deals with context switching etc... */ -static void vwm_win_unfocus(vwm_window_t *vwin) +/* 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) { - /* if we're the 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_CONTEXT_FOCUS_SHELF, VWM_FENCE_IGNORE); + 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 */ - 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; - } - } + 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 */ - /* 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_CONTEXT_FOCUS_DESKTOP, VWM_FENCE_TRY_RESPECT); - } + XRaiseWindow(display, vwin->xwindow->id); /* ensure the window is raised */ +} - if(vwin->desktop->focused_window == vwin) { - VWM_TRACE("desktop empty"); - vwin->desktop->focused_window = NULL; + + /* 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; } } -/* 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) +/* 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) { - /* go through the motions of unfocusing the window if it is focused */ - vwm_win_unfocus(vwin); + XserverRegion region; + vwm_xwindow_t *xwin; - /* ensure not shelved */ - vwin->shelved = 0; + xwin = vwm_xwin_lookup(ev->drawable); + if(!xwin) { + VWM_ERROR("damaged unknown drawable %x", (unsigned int)ev->drawable); + return; + } - /* assign the new desktop */ - vwin->desktop = desktop; + 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); +} - /* currently we always focus the new desktop in a migrate */ - vwm_desktop_focus(desktop); - /* focus the window so borders get updated */ - vwm_win_focus(vwin); - 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 */ +/* 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); +} + - /* ensure the window is raised */ - XRaiseWindow(display, vwin->window); +/* 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; } -/* unmanages a managed window (called in response to DestroyNotify) */ -static void vwm_win_unmanage(vwm_window_t *vwin) -{ -#if 0 -/* -I'm not sure how I feel about this yet, I like the flow of new windows queueing up as the next most recently used and on top of the stack, where I can -switch to the first one with Mod1-Tab, then destroy them all in sequence, never losing visibility of the next one. -When this is enabled, that doesn't happen, I see them all stacked on top, switch to the top one and destroy it, then my origin window gets raised and -focused as it's the truly most recently used window (the last one I committed on and was interacting with before giving attention to the new stack). -Without this change, it's a nice flow. -But without this change, odd edge cases emerge where a clearly not MRU window is raised after I destroy something, like the xterm running an mplayer command -when I switched from the movie window to a new floater xterm momentarily and destroyed it immediately without releasing Mod1. What happens is the floater xterm -gets unmanaged on destroy, and the movie window should get focused, but it doesn't, the window _after_ the movie window does, which is the xterm running mplayer, -because the unfocus() of the floater xterm looked at whatever was after it, since the new floater was inserted immediately after the movie window, it's between -the movie window and the xterm running mplayer, so the xterm running mplayer gets focused + raised. -Needs more thought. -*/ - 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 */ -#endif - vwm_win_unfocus(vwin); - list_del(&vwin->windows); +/* 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_visible(xwin)) continue; /* if invisible skip */ + + /* Everything "visible" 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 transparent, 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 the 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_visible(xwin) || xwin->occluded) continue; /* if invisible 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); - if(vwin == console) console = NULL; + /* 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); - if(vwin->name) XFree(vwin->name); - free(vwin); + /* fin */ + XFixesDestroyRegion(display, combined_damage); + combined_damage = None; } -/* manage all existing windows (for startup) */ -static int vwm_manage_existing(void) +/* toggle compositing/monitoring overlays on/off */ +static void vwm_comp_toggle(void) { - Window root, parent; - Window *children = NULL; - unsigned int n_children, i; - vwm_window_t *vwin; + vwm_xwindow_t *xwin; 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((vwin = vwm_win_manage(children[i], VWM_GRABBED)) == NULL) goto _fail_grabbed; - } + 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; - XUngrabServer(display); + case VWM_COMPOSITING_MONITORS: { + XEvent ev; - if(children) XFree(children); + 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(); - return 1; + /* 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; + } + } -_fail_grabbed: XUngrabServer(display); - - if(children) XFree(children); - - return 0; } -/* helper function for resizing a window, how the motion is applied depends on where in the window the impetus event occurred */ -static void compute_resize(vwm_window_t *vwin, XWindowAttributes *attrs, XEvent *impetus, XEvent *terminus, XWindowAttributes *new) -{ - int dw = (attrs->width / 2); - int dh = (attrs->height / 2); - int xdelta = (terminus->xbutton.x_root - impetus->xbutton.x_root); - int ydelta = (terminus->xbutton.y_root - impetus->xbutton.y_root); - int min_width = 0, min_height = 0; - int width_inc = 1, height_inc = 1; + /* 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; - /* TODO: there's probably a problem here WRT border width, I probably should be considering it. */ +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; - if(vwin && vwin->hints) { +/* 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; @@ -1078,78 +2239,145 @@ static void compute_resize(vwm_window_t *vwin, XWindowAttributes *attrs, XEvent xdelta = xdelta / width_inc * width_inc; ydelta = ydelta / height_inc * height_inc; - if(impetus->xbutton.x < dw && impetus->xbutton.y < dh) { + if(clickety->impetus_x < dw && clickety->impetus_y < dh) { /* grabbed top left */ - new->x = attrs->x + xdelta; - new->y = attrs->y + ydelta; - new->width = attrs->width - xdelta; - new->height = attrs->height - ydelta; - } else if(impetus->xbutton.x > dw && impetus->xbutton.y < dh) { + 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 = attrs->x; - new->y = attrs->y + ydelta; - new->width = attrs->width + xdelta; - new->height = attrs->height - ydelta; - } else if(impetus->xbutton.x < dw && impetus->xbutton.y > dh) { + 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 = attrs->x + xdelta; - new->y = attrs->y; - new->width = attrs->width - xdelta; - new->height = attrs->height + ydelta; - } else if(impetus->xbutton.x > dw && impetus->xbutton.y > dh) { + 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 = attrs->x; - new->y = attrs->y; - new->width = attrs->width + xdelta; - new->height = attrs->height + ydelta; + 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(attrs->x != new->x) new->x -= (min_width - new->width); + if(clickety->orig.x != new->x) new->x -= (min_width - new->width); new->width = min_width; } if(new->height < min_height) { - if(attrs->y != new->y) new->y -= (min_height - new->height); + if(clickety->orig.y != new->y) new->y -= (min_height - new->height); new->height = min_height; } } -typedef enum _vwm_adjust_mode_t { - VWM_ADJUST_RESIZE, - VWM_ADJUST_MOVE -} vwm_adjust_mode_t; +static void vwm_clickety_motion(vwm_clickety_t *clickety, Window win, XMotionEvent *motion) +{ + XWindowChanges changes = { .border_width = WINDOW_BORDER_WIDTH }; -/* XXX: impetus is currently assumed to be an xbutton event */ -static int vwm_clicked(Window win, XEvent *impetus) + 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) { - XWindowAttributes orig, lastrect; - XWindowChanges changes = { .border_width = WINDOW_BORDER_WIDTH }; - vwm_window_t *vwin; + 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, &orig)) goto _fail; + if(!XGetWindowAttributes(display, win, &clickety->orig)) goto _fail; if(!(vwin = vwm_win_lookup(win))) goto _fail; - if(impetus->xbutton.state & WM_GRAB_MODIFIER) { - int finished = 0; - vwm_adjust_mode_t mode; + 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->window != RootWindow(display, screen_num)) { + if(vwin != focused_desktop->focused_window && vwin->xwindow->id != RootWindow(display, screen_num)) { vwm_win_focus(vwin); vwm_win_mru(vwin); } - switch(impetus->xbutton.button) { + 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) */ - mode = VWM_ADJUST_MOVE; + clickety->mode = VWM_ADJUST_MOVE; XRaiseWindow(display, win); break; @@ -1159,87 +2387,22 @@ static int vwm_clicked(Window win, XEvent *impetus) XSync(display, False); /* FIXME: none of the resize DrawRectangle() calls consider the window border. */ - XDrawRectangle(display, RootWindow(display, screen_num), gc, orig.x, orig.y, orig.width, orig.height); - lastrect = orig; + XDrawRectangle(display, RootWindow(display, screen_num), gc, clickety->orig.x, clickety->orig.y, clickety->orig.width, clickety->orig.height); + clickety->lastrect = clickety->orig; - mode = VWM_ADJUST_RESIZE; + clickety->mode = VWM_ADJUST_RESIZE; break; default: goto _fail; } - - /* apply Motion events until the button is released, move vs. move+resize is performed depending on the supplied mode */ - while(!finished) { - XEvent event; - XWindowAttributes resized; - - XWindowEvent(display, win, ButtonReleaseMask | PointerMotionMask, &event); - switch(event.type) { - case ButtonRelease: - switch(mode) { - case VWM_ADJUST_MOVE: - changes.x = orig.x + (event.xmotion.x_root - impetus->xbutton.x_root); - changes.y = orig.y + (event.xmotion.y_root - impetus->xbutton.y_root); - XConfigureWindow(display, win, CWX | CWY | CWBorderWidth, &changes); - break; - - case VWM_ADJUST_RESIZE: - compute_resize(vwin, &orig, impetus, &event, &resized); - /* move and resize the window @ resized */ - XDrawRectangle(display, RootWindow(display, screen_num), gc, lastrect.x, lastrect.y, lastrect.width, 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 */ - vwin->autoconfigured = VWM_WIN_AUTOCONF_NONE; - finished = 1; - break; - - case MotionNotify: - switch(mode) { - case VWM_ADJUST_MOVE: - changes.x = orig.x + (event.xmotion.x_root - impetus->xbutton.x_root); - changes.y = orig.y + (event.xmotion.y_root - impetus->xbutton.y_root); - XConfigureWindow(display, win, CWX | CWY | CWBorderWidth, &changes); - break; - - case VWM_ADJUST_RESIZE: - /* XXX: it just so happens the XMotionEvent structure is identical to XButtonEvent in the fields - * needed by compute_resize... */ - compute_resize(vwin, &orig, impetus, &event, &resized); - /* erase the last rectangle */ - XDrawRectangle(display, RootWindow(display, screen_num), gc, lastrect.x, lastrect.y, lastrect.width, 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 */ - lastrect = resized; - break; - - default: - break; - } - break; - - default: - VWM_ERROR("unexpected event during window adjust %i", event.type); - break; - } - } + 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; } - XFlush(display); - XUngrabPointer(display, CurrentTime); - return 1; _fail: @@ -1290,9 +2453,7 @@ static void vwm_keypressed(Window win, XEvent *keypress) static typeof(keypress->xkey.state) last_state; static int repeat_cnt = 0; int do_grab = 0; -#ifdef QUIT_CONSOLE_ON_EXIT char *quit_console_args[] = {"bash", "-c", "screen -dr " CONSOLE_SESSION_STRING " -X quit", NULL}; -#endif sym = XLookupKeysym(&keypress->xkey, 0); @@ -1349,9 +2510,9 @@ static void vwm_keypressed(Window win, XEvent *keypress) case XK_d: /* destroy focused */ if((vwin = vwm_win_focused())) { if(keypress->xkey.state & ShiftMask) { /* brutally destroy the focused window */ - XKillClient(display, vwin->window); + XKillClient(display, vwin->xwindow->id); } else { /* kindly destroy the focused window */ - vwm_win_message(vwin, wm_protocols_atom, wm_delete_atom); + 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 */ @@ -1363,9 +2524,7 @@ static void vwm_keypressed(Window win, XEvent *keypress) do_grab = 1; if(repeat_cnt == 2) { -#ifdef QUIT_CONSOLE_ON_EXIT vwm_launch(quit_console_args, VWM_LAUNCH_MODE_FG); -#endif exit(42); } break; @@ -1433,14 +2592,14 @@ static void vwm_keypressed(Window win, XEvent *keypress) } else { do_grab = 1; - XRaiseWindow(display, vwin->window); + XRaiseWindow(display, vwin->xwindow->id); if(repeat_cnt == 1) { /* double: reraise & fullscreen */ - vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_FULL); + 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_WINDOW, VWM_WIN_AUTOCONF_ALL); + 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 */ @@ -1464,9 +2623,9 @@ static void vwm_keypressed(Window win, XEvent *keypress) } } else { if(vwin->autoconfigured == VWM_WIN_AUTOCONF_ALL) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_FULL); + vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); } else { - XLowerWindow(display, vwin->window); + XLowerWindow(display, vwin->xwindow->id); } XFlush(display); } @@ -1476,9 +2635,9 @@ static void vwm_keypressed(Window win, XEvent *keypress) case XK_Return: /* (full-screen / restore) focused window */ if((vwin = vwm_win_focused())) { if(vwin->autoconfigured) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_NONE); + vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_NONE); } else { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_FULL); + vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); } } break; @@ -1493,15 +2652,15 @@ static void vwm_keypressed(Window win, XEvent *keypress) if(keypress->xkey.state & ShiftMask) { if(!repeat_cnt) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_TOP); + vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_TOP); } else { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_TOP_LEFT); + 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_WINDOW, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_LEFT); + vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_LEFT); } else { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_BOTTOM_LEFT); + vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_BOTTOM_LEFT); } } } @@ -1513,20 +2672,38 @@ static void vwm_keypressed(Window win, XEvent *keypress) if(keypress->xkey.state & ShiftMask) { if(!repeat_cnt) { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_BOTTOM); + vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_BOTTOM); } else { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_BOTTOM_RIGHT); + 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_WINDOW, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_RIGHT); + vwm_win_autoconf(vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_RIGHT); } else { - vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_TOP_RIGHT); + 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 = vwm_win_focused()) && 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; @@ -1544,6 +2721,55 @@ static void vwm_keypressed(Window win, XEvent *keypress) } + /* 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 */ @@ -1552,11 +2778,14 @@ static int errhandler(Display *display, XErrorEvent *err) int main(int argc, char *argv[]) { - int err = 0; - int done = 0; - XEvent event; - Cursor pointer; - char *console_args[] = {"xterm", "-class", CONSOLE_WM_CLASS, "-e", "bash", "-c", "screen -D -RR " CONSOLE_SESSION_STRING, NULL}; + int err = 0; + int done = 0; + XEvent event; + Cursor pointer; + struct pollfd pfd; + char *console_args[] = {"xterm", "-class", CONSOLE_WM_CLASS, "-e", "bash", "-c", "screen -D -RR " CONSOLE_SESSION_STRING, NULL}; + Window bitmask; + vwm_clickety_t clickety = { .vwin = NULL }; #define reterr_if(_cond, _fmt, _args...) \ err++;\ @@ -1578,6 +2807,8 @@ int main(int argc, char *argv[]) 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); @@ -1601,15 +2832,62 @@ int main(int argc, char *argv[]) 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), - SubstructureNotifyMask | SubstructureRedirectMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask); + 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); @@ -1638,198 +2916,315 @@ int main(int argc, char *argv[]) pointer = XCreateFontCursor(display, XC_X_cursor); XDefineCursor(display, RootWindow(display, screen_num), pointer); - /* event loop */ - while(!done) { - XNextEvent(display, &event); - switch(event.type) { - case KeyPress: - VWM_TRACE("keypress"); - vwm_keypressed(event.xkey.window, &event); - break; + pfd.events = POLLIN; + pfd.revents = 0; + pfd.fd = ConnectionNumber(display); - case KeyRelease: - VWM_TRACE("keyrelease"); - vwm_keyreleased(event.xkey.window, &event); - break; + 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; + } - case ButtonPress: - VWM_TRACE("buttonpresss"); - vwm_clicked(event.xbutton.window, &event); - break; + 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); - case DestroyNotify: { - vwm_window_t *vwin; - VWM_TRACE("destroynotify"); - if((vwin = vwm_win_lookup(event.xdestroywindow.window))) { - vwm_win_unmanage(vwin); - } - break; + 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; } - 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; - /* 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); - XConfigureWindow(display, event.xconfigurerequest.window, change_mask, &changes); - break; - } + XNextEvent(display, &event); + switch(event.type) { + case KeyPress: + VWM_TRACE("keypress"); + vwm_keypressed(event.xkey.window, &event); + break; - case ConfigureNotify: { - vwm_window_t *vwin; - VWM_TRACE("configurenotify"); - if((vwin = vwm_win_lookup(event.xconfigure.window))) { - vwin->config.x = event.xconfigure.x; - vwin->config.y = event.xconfigure.y; - vwin->config.width = event.xconfigure.width; - vwin->config.height = event.xconfigure.height; - } - break; - } + case KeyRelease: + VWM_TRACE("keyrelease"); + vwm_keyreleased(event.xkey.window, &event); + break; - case UnmapNotify: { - vwm_window_t *vwin; - VWM_TRACE("unmapnotify"); - /* unlike MapRequest, we simply are notified when a window is unmapped. */ - if((vwin = vwm_win_lookup(event.xunmap.window))) { - if(vwin->unmapping) { - VWM_TRACE("swallowed vwm-induced UnmapNotify"); - vwin->unmapping = 0; - } else { - vwm_win_unmanage(vwin); + 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; } - break; - } - case MapNotify: { - vwm_window_t *vwin; - VWM_TRACE("mapnotify"); - if(!(vwin = vwm_win_lookup(event.xmap.window))) { - /* unmanaged windows becoming mapped arrive here, popups/menus and the like, if they - * don't want to be managed they'll set override_redirect, which will be ignored or honored depending on - * HONOR_OVERRIDE_REDIRECT, if we honor it this generally becomes a noop. */ - vwm_win_manage(event.xmap.window, VWM_NOT_GRABBED); - } else { - VWM_TRACE("swallowed vwm-induced MapNotify"); + 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; + /* 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); + 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; } - break; - } - case MapRequest: { - vwm_window_t *vwin; - int domap = 1; - VWM_TRACE("maprequest"); - if((vwin = vwm_win_lookup(event.xmap.window)) || (vwin = vwm_win_manage(event.xmap.window, VWM_NOT_GRABBED))) { - XWindowAttributes attrs; - XWindowChanges changes = {.x = 0, .y = 0}; - XClassHint *classhint; - const vwm_screen_t *scr; - - /* 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); - domap = 0; + 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(classhint->res_class) XFree(classhint->res_class); - if(classhint->res_name) XFree(classhint->res_name); - XFree(classhint); + if(compositing_mode) vwm_comp_damage_win(xwin); } + break; + } - /* 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->name, scr->screen_number); - vwm_win_focus(vwin); + 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"); } else { - scr = vwm_screen_find(VWM_SCREEN_REL_WINDOW, focused_desktop->focused_window); + /* 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); } - 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_WINDOW, focused_shelf); - changes.x = scr->x_org; - changes.y = scr->y_org; } + break; + } - XGetWMNormalHints(display, event.xmap.window, vwin->hints, &vwin->hints_supplied); - XGetWindowAttributes(display, event.xmap.window, &attrs); + 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}; + XClassHint *classhint; + const vwm_screen_t *scr; + + 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); + } - vwin->client.x = changes.x; - vwin->client.y = changes.y; + /* 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; + } - vwin->client.height = attrs.height; - vwin->client.width = attrs.width; + /* XXX TODO: does this belong here? */ + XGetWMNormalHints(display, event.xmap.window, vwin->hints, &vwin->hints_supplied); + XGetWindowAttributes(display, event.xmap.window, &attrs); - XConfigureWindow(display, event.xmap.window, (CWX | CWY), &changes); - } - - if(domap) { - XMapWindow(display, event.xmap.window); - if(vwin && vwin->desktop->focused_window == vwin) { - XSync(display, False); - XSetInputFocus(display, vwin->window, RevertToPointerRoot, CurrentTime); + 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, (CWX | CWY), &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; } - break; - } - - case MappingNotify: - VWM_TRACE("mapping notify"); - XRefreshKeyboardMapping(&event.xmapping); - break; - case ButtonRelease: - VWM_TRACE("buttonrelease"); - break; - case CirculateNotify: - VWM_TRACE("circulatenotify"); - break; - case CreateNotify: - VWM_TRACE("createnotify"); - break; - case Expose: - VWM_TRACE("expose"); - break; - case GravityNotify: - VWM_TRACE("gravitynotify"); - break; - case MotionNotify: - VWM_TRACE("motionnotify"); - 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); - } else { - VWM_ERROR("Unhandled X op %i", event.type); + 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; } - 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); } } -- cgit v1.2.3