summaryrefslogtreecommitdiff
path: root/vwm.c
diff options
context:
space:
mode:
Diffstat (limited to 'vwm.c')
-rw-r--r--vwm.c2549
1 files changed, 1972 insertions, 577 deletions
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 - <vcaputo@gnugeneration.com>
*
@@ -16,26 +16,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-/* 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 <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/cursorfont.h>
@@ -43,6 +26,10 @@
#include <X11/extensions/sync.h> /* SYNC extension, enables us to give vwm the highest X client priority, helps keep vwm responsive at all times */
#include <X11/extensions/Xinerama.h> /* XINERAMA extension, facilitates easy multihead awareness */
#include <X11/extensions/Xrandr.h> /* RANDR extension, facilitates display configuration change awareness */
+#include <X11/extensions/Xdamage.h> /* Damage extension, enables receipt of damage events, reports visible regions needing updating (compositing) */
+#include <X11/extensions/Xrender.h> /* Render extension, enables use of alpha channels and accelerated rendering of surfaces having alpha (compositing) */
+#include <X11/extensions/Xcomposite.h> /* Composite extension, enables off-screen redirection of window rendering (compositing) */
+#include <X11/extensions/Xfixes.h> /* XFixes extension exposes things like regions (compositing) */
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
@@ -58,7 +45,8 @@
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
-
+#include <poll.h>
+#include "libvmon/vmon.h"
#include "vwm.h"
#define WINDOW_BORDER_WIDTH 1
@@ -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);
-/* 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)
+ /* 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 */
+
+/* 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;
}
-/* looks up the window in the global window list */
+/* destroy a window, called in response to DestroyNotify events */
+/* if the window is also managed it will be unmanaged first */
+static void vwm_xwin_destroy(vwm_xwindow_t *xwin)
+{
+ XGrabServer(display);
+ XSync(display, False);
+
+ if(xwin->managed) vwm_win_unmanage(xwin->managed);
+
+ list_del(&xwin->xwindows);
+
+ if(xwin->monitor) vmon_proc_unmonitor(&vmon, xwin->monitor, (void (*)(vmon_t *, vmon_proc_t *, void *))proc_sample_callback, xwin);
+ if(xwin->name) XFree(xwin->name);
+
+ if(compositing_mode) {
+ vwm_xwin_unbind_namewindow(xwin);
+ XDamageDestroy(display, xwin->damage);
+ }
+ free(xwin);
+
+ XUngrabServer(display);
+}
+
+
+/* maintain the stack-ordered xwindows list, when new_above is != None xwin is to be placed above new_above, when == None xwin goes to the bottom. */
+void vwm_xwin_restack(vwm_xwindow_t *xwin, Window new_above)
+{
+ Window old_above;
+#ifdef TRACE
+ vwm_xwindow_t *tmp;
+ fprintf(stderr, "restack of %#x new_above=%#x\n", (unsigned int)xwin->id, (unsigned int)new_above);
+ fprintf(stderr, "restack pre:");
+ list_for_each_entry(tmp, &xwindows, xwindows) {
+ fprintf(stderr, " %#x", (unsigned int)tmp->id);
+ }
+ fprintf(stderr, "\n");
+#endif
+ if(xwin->xwindows.prev != &xwindows) {
+ old_above = list_entry(xwin->xwindows.prev, vwm_xwindow_t, xwindows)->id;
+ } else {
+ old_above = None;
+ }
+
+ if(old_above != new_above) {
+ vwm_xwindow_t *new;
+
+ if(new_above == None) { /* to the bottom of the stack, so just above the &xwindows head */
+ list_move(&xwin->xwindows, &xwindows);
+ } else if((new = vwm_xwin_lookup(new_above))) { /* to just above new_above */
+ list_move(&xwin->xwindows, &new->xwindows);
+ }
+ }
+#ifdef TRACE
+ fprintf(stderr, "restack post:");
+ list_for_each_entry(tmp, &xwindows, xwindows) {
+ fprintf(stderr, " %#x", (unsigned int)tmp->id);
+ }
+ fprintf(stderr, "\n\n");
+#endif
+}
+
+
+ /* vwm "managed" windows (vwm_window_t) (which are built upon the "core" X windows (vwm_xwindow_t)) */
+
+/* unmap the specified window and set the unmapping-in-progress flag so we can discard vwm-generated UnmapNotify events */
+static void vwm_win_unmap(vwm_window_t *vwin)
+{
+ if(!vwin->xwindow->mapped) {
+ VWM_TRACE("inhibited unmap of \"%s\", not mapped by client", vwin->xwindow->name);
+ return;
+ }
+ VWM_TRACE("Unmapping \"%s\"", vwin->xwindow->name);
+ vwin->unmapping = 1;
+ XUnmapWindow(display, vwin->xwindow->id);
+}
+
+
+/* map the specified window and set the mapping-in-progress flag so we can discard vwm-generated MapNotify events */
+static void vwm_win_map(vwm_window_t *vwin)
+{
+ if(!vwin->xwindow->mapped) {
+ VWM_TRACE("inhibited map of \"%s\", not mapped by client", vwin->xwindow->name);
+ return;
+ }
+ VWM_TRACE("Mapping \"%s\"", vwin->xwindow->name);
+ vwin->mapping = 1;
+ XMapWindow(display, vwin->xwindow->id);
+}
+
+
+/* make the specified window the most recently used one */
+static void vwm_win_mru(vwm_window_t *vwin)
+{
+ list_move(&vwin->windows_mru, &windows_mru);
+}
+
+
+/* look up the X window in the global managed windows list */
static vwm_window_t * vwm_win_lookup(Window win)
{
vwm_window_t *tmp, *vwin = NULL;
- list_for_each_entry(tmp, &windows, 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)
+/* 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()
{
-#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);
+ 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);
+ }
- if(vwin == console) console = NULL;
+ /* 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->name) XFree(vwin->name);
- free(vwin);
+ /* discard the root_buffer clip region and copy root_buffer to root_picture, root_picture still has the combined damage as its clip region */
+ XFixesSetPictureClipRegion(display, root_buffer, 0, 0, None);
+ XRenderComposite(display, PictOpSrc, root_buffer, None, root_picture, 0, 0, 0, 0, 0, 0, root_attrs.width, root_attrs.height);
+
+ /* fin */
+ XFixesDestroyRegion(display, combined_damage);
+ combined_damage = None;
}
-/* 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 };
+
+ 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;
+ }
-/* XXX: impetus is currently assumed to be an xbutton event */
-static int vwm_clicked(Window win, XEvent *impetus)
+ 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);
}
}
© All Rights Reserved