diff options
author | Vito Caputo <vcaputo@gnugeneration.com> | 2014-09-14 07:16:02 -0700 |
---|---|---|
committer | Vito Caputo <vcaputo@gnugeneration.com> | 2014-09-14 09:38:35 -0700 |
commit | c9554e3a6a27802aea1206ec492727d616f23a0a (patch) | |
tree | 359abfd4b25a9d3f76453520af2eed57a1d92a2b | |
parent | a4430b79845c0c5417b8725a4272f91e04d42349 (diff) |
Import of vwm2 changes from published source tgz
Major changes from vwm1:
- GNU screen-based "console" integration for monitored launching of X
clients via screen remote commands, replacing the simple double fork
approach used in vwm1. Clients exiting with non-zero status retain
their screen window in the console for 86400 seconds, facilitating
easier debugging and troubleshooting. The console xterm is accessed in
the shelf and has a red border by default.
- Xinerama/multihead support backported from vwm3, including the "screen
fencing" implementation facilitating screen-oriented window focus
cycling.
Shifting the Mod1-Tab window cycling focuses the next most recently
used window on another display. Unshifted stays confined to the
current display.
- SYNC extension integration for prioritizing the WM over other X clients
- setpriority() integration for "nicing" X client processes relative to
the WM process
- "autoconf" windows, horizontal/vertical halfscreen windows,
quarterscreen windows in addition to the full/all screen functions.
Mod1-[ and Mod1-] resize the focused window vertically with left and
right justification to half the screen in width. Shifting these does
the same thing just horizontally. Repeating the operation with a
second ] or [ press quarters the window in the respective screen
corner, extending upon the repeater pattern established in vwm1 for
full/allscreen windows with Mod1-k[k[k]]
- Exit now requires 3 consecutive strikes of Mod1-Esc
- Introduction of a README file
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | README | 94 | ||||
-rw-r--r-- | TODO | 295 | ||||
-rw-r--r-- | colors.def | 1 | ||||
-rw-r--r-- | launchers.def | 12 | ||||
-rw-r--r-- | vwm.c | 1051 | ||||
-rw-r--r-- | vwm.h | 28 |
7 files changed, 1151 insertions, 332 deletions
@@ -1,5 +1,5 @@ vwm: vwm.c vwm.h list.h colors.def launchers.def Makefile - $(CC) -Wall -o vwm vwm.c -lX11 #-g -DTRACE + $(CC) -Wall -o vwm vwm.c -lX11 -lXext -lXinerama -lXrandr #-g -DTRACE clean: rm -f vwm @@ -0,0 +1,94 @@ + \/\/\ + +Built-ins: + + Mod1-RClick Focus the clicked window, but suppress raising + Mod1-RClick-drag Focus the clicked window, suppress raising, resizing the window from its nearest corner until drag completes + Mod1-LClick Focus and raise the clicked window + Mod1-LClick-drag Focus and raise the clicked window, moving the window until the drag completes +* Mod1-l Switch to virtual desktop to the right (if exists) +* Mod1-h Switch to virtual desktop to the left (if exists) + Mod1-j Lower focused window, if the focused window is in "allscreen" mode it will simply be fullscreened first without lowering + Mod1-k [-k [-k] Raise focused window [a second k raises and fullscreens the window [a third k raises and "allscreens" without a visible border + [-k] [-k]] [a fourth k raises and fullscreens across all heads [a fifth k raises and "allscreens" across all heads]]] + Mod1-Shift-k Shelve focused window and switch to the shelf context + Mod1-Shift-j Migrate the focused window from the shelf to the last focused virtual desktop, switch to that virtual desktop, and focus the migrated window +* Mod1-Shift-l Migrate the focused window to the virtual desktop to the right (if exists), keeping the window focused +* Mod1-Shift-h Migrate the focused window to the virtual desktop to the left (if exists), keeping the window focused + Mod1-v Create a new virtual desktop and switch to it + Mod1-Shift-v Create a new virtual desktop, move the focused window to it, and switch to it +* Mod1-Space Switch to the most recently used virtual desktop (like Mod1-Tab but for virtual desktops) +* Mod1-Shift-Space Switch to the most recently used virtual desktop, bringing the focused window with + Mod1-` Alternate between shelf (if populated) and virtual desktop contexts + Mod1-s Shelve the focused window without switching to the shelf, adopting the newly shelved window as the focused window within the shelf +* Mod1-Tab Focus and raise the next window in the current context (shelf or virtual desktop), the focused window is not 'committed' as the MRU window + until Mod1 is released, so you may peruse intermediate windows without affecting the order until releasing Mod1. In multihead configurations + the next window selection is confined to the current screen. + Mod1-Shift-Tab Identical to Mod1-Tab except switches to the MRU window on another screen in a multihead configuration + Mod1-d Request the client destroy the focused window, or destroy the current virtual desktop when no windows exist on it + Mod1-Shift-d XKillClient the focused window (useful for misbehaving clients) + Mod1-Enter Alternate between full-screen and windowed dimensions for the focused window + Mod1-[ [-[] Reconfigure the focused window to fill the left half of the screen [ left bottom quarter of screen ] + Mod1-] [-]] Reconfigure the focused window to fill the right half of the screen [ right top quarter of screen ] + Mod1-Shift-[ [-[] Reconfigure the focused window to fill the top half of the screen [ top left quarter of screen ] + Mod1-Shift-] [-]] Reconfigure the focused window to fill the bottom half of the screen [ bottom right quarter of screen ] + Mod1-Esc-Esc-Esc Exit vwm (if vwm is the child of your X server, X exits too, so your X clients all lose their connection and die. + However, your clients are running under screen, and if your X server didn't exit, they won't lose their X connection.) + + *'s above indicate commands which initiate an MRU-update to be committed on the next Mod1 release. One may traverse windows and desktops without affecting + their MRU order by returning to the original initiating window and/or desktop before releasing Mod1. This permits one to do quite a lot of things + under a single, long-duration Mod1 press only committing to a potentially different focused window/desktop at the very end. + Think of the Mod1 release as a transaction commit when coupled with the *'d commands. + +Default launchers (configure by editing launchers.def and rebuild): + + Mod1-x xterm + Mod1-b iceweasel + Mod1-g gimp + Mod1-. xlock + Mod1-- xset -dpms s off + Mod1-= xset +dpms s on + + +General: + + Newly created windows are raised but not focused unless they are the first + window on an otherwise empty virtual desktop, then they are focused as well. + When new windows appear on a populated virtual desktop, they are inserted + immediately after the currently focused window in the windows list, so a + Mod1-Tab will immediately focus new windows. Windows are kept in a MRU (Most + Recently Used) order, keeping it efficient to alternate between an evolving + set of active windows. + + The shelf is a sort of omnipresent and limited virtual desktop always + available via Mod1-`, it only shows a single window at a time, Mod1-Tab + cycles through the shelved windows. I use it as a place to stow xterms + running backround processes I would like to retain the ability to observe the + output of or interact with occasionally. Programs like transmission-gtk, + cmus, wpa_supplicant under xterm, sometimes even iceweasel sessions find + themselves in my shelf on a regular basis. + + +Multihead/Xinerama: + + In multihead configurations new windows are placed on the screen containing + the pointer if that screen is empty. Should the pointer be on a non-empty + screen, then new windows are placed on the screen containing the currently + focused window. + + New windows will automatically be focused if the screen they were placed on + is empty, even if their virtual desktop is not, which is a divergence from + the single-headed behavior where only lone windows on virtual desktops are + automatically focused. + + + + + + + +TODO ... + + + + @@ -1,23 +1,101 @@ +XINERAMA: +- XRandR events removing screens containing windows turns them into + pseudo-phantom windows. You can reclaim them by Mod1-Tabbing to them (which + you can infer by not seeing a green square anywhere visible) and using a + window autoconfiguration hotkey like Mod1-enter or Mod1-]. + *** May require Mod1-Shift-Tabbing now actually. + + vwm should detect when screens disappear containing such windows and move + them to another screen, even if it's just the 0,0 coordinate of a present + screen. + +- Add a modifier for window configuration hotkeys to become pointer-relative, + so one can move a window to a different screen while resizing it by simply + having the pointer in the desired screen and hitting the appropriately + modified resize command. + +- There's no direct keyboard method for migrating a window to another screen, + much like one can migrate windows from one virtual desktop to another by + simply holding shift while switching, it should have something similar for + moving a window to the next screen within the same virtual desktop. + This could also be autoconfig-aware, so if you migrated half-screened + window to another screen, the autoconfigure could recur adapting to the + new screen's potentially different dimensions... + + This isn't difficult to implement, just need to come up with a good key for + it. Mod1-Shift-Tab is already taken for switching focus across screens. + Maybe it makes more sense to assign a new key to switching focus across + screens rather than modifying the Tab, then shifting that key will turn it + into a screen migration. + +- If shelf semantics were murky before they're muddy with Xinerama. + *** a particularly annoying issue is shelved windows don't get put in the + same screen, I'm leaning towards the screen vwm started with the pointer + in (the one the logo gets drawn on) gets assigned as the shelf screen + too. + *** Instead it would probably be more convenient to have the shelf always + appear on the currently focused screen at the time of being toggled. + This just requires shelf focusing of windows to always configure them + on the focused screen (fullscreening already happens at tht time so + it should be trivial to move them to the current screen as well) + *** in the mean time, the shelf follows the screen containing the pointer. + *** maybe Mod1-Shift-Tab in the shelf context should move the shelf to + another screen... + + vwm is supposed to do what is expected at all times, when it doesn't it's a + bug (which are numerous). It's kept simple to promote success in achieving + this goal, and even so this has proven quite challenging. + + Xinerama introduces many new interactions which will require time and thought + to make behave as expected. I don't know what other WM's are like in this + regard, but find multihead vwm pretty annoying in its current state. + *** The usability of multihead vwm has improved substantially with the + introduction of screen fencing (for Mod1-Tab) and the explicit violation + of those fences (Mod1-Shift-Tab). + + BUGS: +- The halfscreen/quarterscreen shortcuts are treated as user-configured windows + clobbering the cached original client configuration, I don't think this is + the right thing to do. I'm leaning towards all the shortcutted + configurations being treated as various forms of fullscreened state which + don't get remembered in window reconfiguration, keeping the other + configuration available for window restore. But maybe there's a need for + more flexibility. Below I talk about keeping a list of window configurations + per window in MRU order then have a way to cycle through them... perhaps it + makes more sense to just have a last-configuration cache in the window + instance, as well as a cache of the most recent non-shortcutted (either + original client configuration or one that was manually sized) available with + a modifier. Like Mod1-Enter flips between the two most recent + configurations, and adding Shift always brings you back to the original size? + *** for now Mod1-Enter fullscreens a non-autoconfigured window, or restores + an autoconfigured window to the latest non-autoconfigured dimensions. + All the half/quarter/full/all screen modes for windows are uniformly + considered autoconfigured states now. The functions have been + consolidated... -- Audit the code for memory leaks, I don't clean up things diligently on - destroy of things, especially surrounding X resources I presume. +- I suspect there are some situations where the MRU desktop is updated but + shouldn't be, but I haven't made a significant effort to specifically isolate + and characterize them, just something I feel like I've groaned about + occasionally when something unexpected occurred requiring a Mod1-Space storm. -- There seems to be a problem with automatically focusing the remaining window - when the focused window is unmanaged. Seems like I can simply repro. this by - starting iceweasel in an empty desktop. It seems to be due to the 10x10 plugin - container window, why are we tripped up by this little window? Why is this - window managed but not focusable? fucking X + * MRU-based window migration (Mod1-Shift-Space) updates MRU without a Mod1 + release, that's a bug, no commit has been done. -- Shelving a window doesn't focus it in the shelf context (is this limited to - only migration shelving?) +- Seems like there's a bug when exiting a shelved transmission without + waiting for it to fully exit before returning to a virtual desktop. + Upon switching to the virtual desktop the other shelved window had + originated from it is shown erroenously with a focused-shelved window border + with the other windows of that desktop. + +- Audit the code for memory leaks, I don't clean up things diligently on + destroy of things, especially surrounding X resources I presume. - A floating window in the shelf, even when rendered as focused, doesn't behave as it's focused.... this is really annoying. I really need to formalize the multi-window (new window) behavior within the shelf, it's totally undefined currently, I just avoid launching things in the shelf and don't run apps which create windows from within it. - ... Window creation while in the shelf is still a bit odd. The shelf isn't @@ -34,24 +112,123 @@ BUGS: multi-window state within the shelf is a transient one which ceases to occur the moment the shelf context is left or a subsequent Alt+Tab further cycles the focused shelf, unmapping the currently mapped windows. + ... + + Shelf is like the junk drawer, I don't think it matters, as long as I can + throw windows I want out of my main workflow there and eventually find them + should I need to it's working well enough. It's not really where any time + gets spent other than queueing music in cmus or checking on gtk-transmission + downloads. + + It might be interesting to have a concept of a persistent shelf actually, + I tend to _always_ launch alsamixer and cmus in the shelf. I'd appreciate + that happening automatically on startup. Humm... then it becomes appealing + to assign specific identifiers to their respective shelf windows rather + than having to skip through the shelved windows searching, slippery slope. + + Fuck it, let's consider this; Mod1_` continues to toggle the shelf, then + Mod1_1-0 focus the 1-10th persistent shelved windows? They all exist in the + shelf as before, in MRU order, cyclable via Mod1_Tab, but Mod1_1-0 are + shortcuts to the first 10 shelved windows in shelved order? Or is it reserved + for specifically persistent shelved windows which have been specified in the + code somewhere, like a launchers.def-like list (shelves.def?)? + + It's actually somewhat complicated to launch an X client into a specific + context. I abuse resource classes for identifying the xterm-based console + window and placing it in the shelf with the red border. If I wanted to have + automatically launched clients @ startup getting placed in the shelf, it's + not necessarily safe to assume every client can specify a class for vwm to + recognize like xterm -class, not to mention that's a resource class, it has + existing semantics and I've just overloaded it for the console. The clients + may not want to have their resource class changed arbitrarily... there needs + to be a better way. -- Mod1+d destruction of an empty virtual desktop should probably require double - press of the 'd', and perhaps a visual indicator of emminent destruction after - the initial 'd'. + As an interim solution, what I could do is generalize the launching and + shelving of commands (like the console) according to their class. I + currently only want to put things under xterms into the shelf at startup + anyways, so the xterm -class hack fulfills my immediate needs, and I already + basically do it for the console so this would just be more of the same with a + generalized implementation. Things like -class VWMMixerXTerm and -class + VWMCmusXTerm, define them in a shelves.def where their commands and keysym + ids are assigned as well, throw the console in there too removing its + specialized shit from vwm.c then generate the startup, identification, and + shelving code for all those listed in the file. + + Worth it? All to save having to launch two xterms and running alsamixer and + cmus every time I boot my computer more or less? Unsure. It might be nicer + to also have them directly locatable via Mod1-1 and Mod1-2, perhaps + suppressing the MRU update on direct addressing to not pollute the MRU of the + ephemeral shelves. + + (this bug entry has become a feature monologue) + +- Virtual desktop window unmap/map sequences aren't Z-ordered, this causes some + visual artifacts when switching desktops depending on how things happen to + be ordered. Fixing this requires maintaining a windows list in the same + stacking order as the X server, worth fixing, and will be needed anyways when + I integrate the composited window child monitoring code. There are features + below which also would benefit from the ability to preserve window stacking. FEATURES: -- It would be useful if fullscreened windows obscured their border when they are - not obscured, as-is I lose a pixel on each edge in fullscreen xterms just so I - can see if the window is focused or not via the border color. This is only - appreciated when focus is potentially ambiguous. Come up with a good approach - which allows obstruction of the border while still showing the border in - potentially ambiguous situations. +- Introduce a run command key? Include a modifier for focusing the console + upon execution? So you can directly launch (without creating an xterm) + command lines? + + Initially I thought it might be a good idea to make vwm interactively take + the input for the command in a custom widget of sorts. + + But after sitting on this for awhile and giving it more thought, I don't + think that makes any sense. It's very rare when I know specifically what I + want to run with precisely the right arguments from the correct directory + without some filesystem navigation, reverse history search, and perhaps an + --help invocation or two. + + For instance, these days I'm often getting on various wireless networks and I + invoke wpa supplicant directly using a locale-named configuration file as + root largely via an reverse history search of the locale's name. + + What I do today is I create an xterm for wpa_supplicant and shelve it, which + wastes a page in the shelf list on an xterm I'm really unlikely to need to + visit. The reason I'd like the run command is for launching things like + wpa_supplicant without dedicating an xterm to it. It would also be nice to + run some of these things under screen so they don't necessarily have to exit + should I exit vwm/X. Losing wireless connectivity because I quit vwm is + annoying sometimes, like if I'm doing alot of restarting vwm experimenting or + something. + + Maybe what makes the most sense is for a run command to simply focus the + console in the shelf while sending the console screen session the remote + command for creating a window. + + Or maybe there should be a separate console session for executing run + commands like wpa_supplicant/cmus/alsamixer? Just because it would be + cumbersome to navigate a polluted screen session full of all the X clients in + addition to the interactive launches, when you're really only going to be + interested in the interactive launches. In essence, this is a vwm binding + for ^a-c in a dedicated per-X-display screen session (would it be useful to + make it a global per-user screen session? could multiple vwm instances share + a launcher screen? DISPLAY= would be correct for just one of them, but that + has no effect on non-X processes. Interesting to consider, but is there any + practical reason to have multiple vwm's running for a user? Unless you're + hacking and experimenting with a new feature long enough to want to try live + in it for a few hours without leaving your current production arrangement? + + Unsure. + +- Multihead-awareness? I don't really have a setup to mess with this on right + now, which also removes the pressure from me to actually make it + well-integrated. However, I've used xrandr --same-as configurations for + watching movies on a projector, and provided I set the external display as + the "primary" (xrandr terminology) then things like allscreen use the + appropriate geometry for the external display. Upon stopping the external + usage I need to disable the output or set the internal one as primary etc to + restore internal display correctness for geometry-dependent functionality. - Mod1+enter currently toggles between the currently user-specified (starts out at client-configured) dimensions, and full-screen. I think it would be useful - to support 3 states, the clients originally configured dimensions, the + to support 3 states, the client's originally configured dimensions, the user-specified dimensions (if they have been varied from the client-configured dimensions) and full-screen. This needs more thought, because it might not be very intuitive to sometimes have 3 persisted window states vs. 2, depending @@ -67,16 +244,82 @@ FEATURES: should the resize last long enough to become realized rather than suppressed and treated as only a focus without raise event. +- Mod1+tab currently changes the window stacking order by leaving intermediate + windows raised, even though they were simply visited. This is common in WM's, + but what I really want is for the window focused at the time of Mod1 release to + be the only window remaining raised. The intermediates should only be temporarily + raised when they were visited but restored to their previous position in the stack + at the subsequent tab press. + - Window cycling should probably be reversible (shift+Mod1+Tab?) + *** strangely lacking this hasn't really annoyed me, MRU keeps things + mostly where you need them. - Window placement is static, at least cascade, preferably intelligent placement resorting to cascade when the desktop is filled. I actually like how wmaker 'auto mode' tries to fill open spaces with window placement then falls back to cascade once no open spaces exist to accomodate the created window's preferred size. + *** funny how simply throwing all new windows in the corner is pretty damn + acceptable, at least it's deterministic, moving it is easy enough. + +- An snap-to grid for window resize and movement may be useful, perhaps + activated by a modifier (shift?) pressed during the grab? I'm not sure how I + feel about making windows snap to arbitrary alignments at neighboring window + borders vs. simply adhering to some fixed absolute grid totally ignorant of + other window boundaries. + +- With the introduction of Mod1-[], Mod1-Shift-[] for halfscreens, and the + repeaters for quarter windows, most appreciable XGA window layouts are + quickly achieved with the keyboard alone. However, on higher resolution + screens, quarter windows can still be too large with high probability. I'm + thinking it might be better to adapt the current technique to a more + generalized quadtree-inspired navigation. By quadtree I mean incremental + recursive bisection of a window along choosable axis in simultaneously chosen + directions. So Mod1-[ would be left subhalf bisecting the X axis, Mod1-] + right subhalf bisecting the X axis. Shifted Mod1-[ would be top subhalf + bisecting the Y axis, and shifted Mod1-] the bottom subhalf bisecting the Y + axis. The subhalves are always confined to the window's current space, the + recursive bisecting continuing until the Mod1 is released. If Mod1 is + released, then the bisection resumed, the process will restart from the + screen half initially chosen. It might also be interesting to support a + means of going "up" the tree rather than down, to enlarge the window on a + chosen axis and direction, additionally it may be useful to move the window + laterally in its current dimensions along quadtree-aligned boundaries. + Or maybe it's simply good enough to restart from a toplevel half when you + wish to effectively move up or laterally in the virtual quadtree. - Virtual desktop genesis needs to query the user for the name, and create a full-screen xterm running screen -S $desktopname for the root (or something) + (perhaps something like specifying special per-virtual-desktop classes to + the xterm creation can be used to establish desktop affinity and apply special + rules to these per-desktop screen-under-xterm sessions) + + The per-desktop fullscreen xterm can be automatically made borderless when + it's shown alone, and bordered when overlapping. A key combination can be + defined to (un)map all windows on a desktop except the "root" fullscreen + desktop window. This may be achievable entirely via resource classes (xterm + -class) so vwm can identify these special per-desktop xterms. + + * I think it might make more sense to just add a concept of virtual desktop + "leaders", and have the default be a fullscreen xterm running screen, but + also allow it to be anything defined in launchers.def (added labels to + those anticipating a possible best-match search to be performed @ + genesis). + + Then vwm can have a key for "show leader" which hides everything on the focused + desktop except the leader window. A key can also be added to assign leadership + to any window, but the common pattern would be to @ desktop genesis time establish + the leader when it's created, and that will usually be a fullscreened xterm running + screen with a named session you name interactively immediately after Mod1-v. + Of course there would also need to be the opposite of "show leader" which would + probably just be the same key pressed again like a toggle, to show the inferiors. + I think there's an important subtle detail in the leader/inferiors visibility + toggling to make it particularly useful, and that's one of focus. Show leader + should focus the leader, but show inferiors should probably restore focus to the + inferior if one was focused at the time show leader was performed. Maintenance + of the window stacking order would be helpful too in this case, which is something + I don't maintain at all right now. - Implement screen lock (right now I use xlock, I want something integrated which does the following: @@ -88,3 +331,15 @@ FEATURES: * if possible, disable ctrl-alt-backspace, allowing me to run Xorg with this enabled without making my desktop totally insecure. When unlocked I like this ability, but when locked it can't be available for obvious reasons. + + *** I think the best way to achieve this is to actually extend the Xorg server to + support the window manager sending two new requests: + 1. "close all backdoors" + 2. "restore all backdoors" + Then the window manager can send request #1 prior to running any lock displays, + and #2 upon exiting a lock display. + + *** Queried Keith Packard (fd.o) on this topic, he thinks it makes sense and + might be appropriate to add to xinput or xkbd extensions rather than + creating a + new one. @@ -1,5 +1,6 @@ color(focused_window_border, "Green") color(shelved_window_border, "purple") color(unfocused_window_border, "DarkGray") +color(shelved_console_border, "red") color(rubberband, "Orange") color(logo, "LimeGreen") diff --git a/launchers.def b/launchers.def index 428e5f9..2c63f96 100644 --- a/launchers.def +++ b/launchers.def @@ -1,6 +1,8 @@ /* it's up to you to ensure you don't conflict with the global VWM keys */ -/* keysym, argv list */ -launcher(XK_x, "/usr/bin/xterm") -launcher(XK_b, "/usr/bin/iceweasel", "-ProfileManager", "-no-remote") -launcher(XK_g, "/usr/bin/gimp") -launcher(XK_period, "/usr/local/bin/xlock") +/* keysym, label, cmd */ +launcher( XK_x, "xterm", "/usr/bin/xterm") +launcher( XK_b, "iceweasel", "/usr/bin/iceweasel -ProfileManager -no-remote") +launcher( XK_g, "gimp", "/usr/bin/gimp") +launcher( XK_period, "lock", "/usr/bin/xlock") +launcher( XK_minus, "dpms off", "/usr/bin/xset -dpms s off") +launcher( XK_equal, "dpms on", "/usr/bin/xset +dpms s on") @@ -1,12 +1,11 @@ /* - * VWM - Vito's Window Manager, a minimal, non-reparenting X11 window manager + * vwm - Vito's Window Manager, a minimal, non-reparenting X11 window manager * - * Copyright (C) 2012 Vito Caputo - <vcaputo@gnugeneration.com> + * Copyright (C) 2012-2014 Vito Caputo - <vcaputo@gnugeneration.com> * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -21,41 +20,80 @@ * clearly I decided to just use Xlib instead at some point, and here we are. */ +/* 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> #include <X11/Xatom.h> +#include <X11/extensions/sync.h> /* SYNC extension, enables us to give vwm the highest X client priority, helps keep vwm responsive at all times */ +#include <X11/extensions/Xinerama.h> /* XINERAMA extension, facilitates easy multihead awareness */ +#include <X11/extensions/Xrandr.h> /* RANDR extension, facilitates display configuration change awareness */ #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> +#include <stdarg.h> #include <string.h> #include <inttypes.h> +#include <values.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> +#include <sys/time.h> +#include <sys/resource.h> #include "vwm.h" #define WINDOW_BORDER_WIDTH 1 -#define WINDOW_CASCADE_DELTA 100 +#define CONSOLE_WM_CLASS "VWMConsoleXTerm" /* the class we specify to the "console" xterm */ +#define CONSOLE_SESSION_STRING "_vwm_console.$DISPLAY" /* the unique console screen session identifier */ + +#define WM_GRAB_MODIFIER Mod1Mask /* the modifier for invoking vwm's controls */ + /* Mod4Mask would be the windows key instead of Alt, but there's an assumption + * in the code that grabs are being activated by Alt which complicates changing it, + * search for XGetModifierMapping to see where, feel free to fix it. Or you can + * just hack the code to expect the appropriate key instead of Alt, I didn't see the + * value of making it modifier mapping aware if it's always Alt for me. */ +#define LAUNCHED_RELATIVE_PRIORITY 10 /* the wm priority plus this is used as the priority of launched processes */ +#define HONOR_OVERRIDE_REDIRECT /* search for HONOR_OVERRIDE_REDIRECT for understanding */ +#define QUIT_CONSOLE_ON_EXIT /* instruct the console screen session to quit @ exit */ typedef enum _vwm_context_focus_t { - VWM_CONTEXT_FOCUS_OTHER = 0, - VWM_CONTEXT_FOCUS_DESKTOP, - VWM_CONTEXT_FOCUS_SHELF + VWM_CONTEXT_FOCUS_OTHER = 0, /* focus the other context relative to the current one */ + VWM_CONTEXT_FOCUS_DESKTOP, /* focus the desktop context */ + VWM_CONTEXT_FOCUS_SHELF /* focus the shelf context */ } vwm_context_focus_t; +typedef 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 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 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. */ static Display *display; static Colormap cmap; @@ -69,54 +107,181 @@ static int screen_num; static GC gc; static Atom wm_delete_atom; static Atom wm_protocols_atom; - +static int sync_event, sync_error; +static int xinerama_event, xinerama_error; +static int randr_event, randr_error; +static XineramaScreenInfo *xinerama_screens = NULL; +static int xinerama_screens_cnt; + +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); -/* animated vwm logo done with simple XOR'd lines, a display of the WM being started and ready */ -typedef struct _vwm_point_t { - float x, y; -} vwm_point_t; - -vwm_point_t vwm_logo[] = { {0.0, 0.0}, - {0.170731, 1.0}, - {0.341463, 0.0}, - {0.463414, 1.0}, - {0.536585, 0.6}, - {0.609756, 1.0}, - {0.731707, 0.0}, - {0.804878, 0.4}, - {0.878048, 0.0}, - {1.0, 1.0} }; +#define MIN(_a, _b) ((_a) < (_b) ? (_a) : (_b)) +#define MAX(_a, _b) ((_a) > (_b) ? (_a) : (_b)) + + +/* 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) +{ + 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) + 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); + + pct = (xover * yover) / (vwin->config.width * vwin->config.height); +_out: + VWM_TRACE("xover=%f yover=%f width=%i height=%i pct=%.4f", xover, yover, vwin->config.width, vwin->config.height, pct); + return pct; +} + + +/* helper for returning the correct screen, don't use the return value across event loops. */ +typedef enum _vwm_screen_rel_t { + VWM_SCREEN_REL_WINDOW, /* return the screen the supplied window most resides in */ + VWM_SCREEN_REL_POINTER, /* return the screen the pointer resides in */ + VWM_SCREEN_REL_TOTAL, /* return the bounding rectangle of all screens as one */ +} vwm_screen_rel_t; + +static const vwm_screen_t * vwm_screen_find(vwm_screen_rel_t rel, ...) +{ + static vwm_screen_t faux; + vwm_screen_t *scr, *best = &faux; /* default to faux as best */ + int i; + + faux.screen_number = 0; + faux.x_org = 0; + faux.y_org = 0; + faux.width = WidthOfScreen(DefaultScreenOfDisplay(display)); + faux.height = HeightOfScreen(DefaultScreenOfDisplay(display)); + + if(!xinerama_screens) goto _out; + +#define for_each_screen(_tmp) \ + for(i = 0, _tmp = xinerama_screens; i < xinerama_screens_cnt; _tmp = &xinerama_screens[++i]) + + switch(rel) { + case VWM_SCREEN_REL_WINDOW: { + va_list ap; + vwm_window_t *vwin; + float best_pct = 0, this_pct; + + va_start(ap, rel); + vwin = va_arg(ap, vwm_window_t *); + va_end(ap); + + for_each_screen(scr) { + this_pct = vwm_screen_overlaps_win(scr, vwin); + if(this_pct > best_pct) { + best = scr; + best_pct = this_pct; + } + } + break; + } + + case VWM_SCREEN_REL_POINTER: { + int root_x, root_y, win_x, win_y; + unsigned int mask; + Window root, child; + + /* get the pointer coordinates and find which screen it's in */ + XQueryPointer(display, RootWindow(display, screen_num), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask); + + for_each_screen(scr) { + if(root_x >= scr->x_org && root_x < scr->x_org + scr->width && + root_y >= scr->y_org && root_y < scr->y_org + scr->height) { + best = scr; + break; + } + } + break; + } + + case VWM_SCREEN_REL_TOTAL: { + short x1 = MAXSHORT, y1 = MAXSHORT, x2 = MINSHORT, y2 = MINSHORT; + /* find the smallest x_org and y_org, the highest x_org + width and y_org + height, those are the two corners of the total rect */ + for_each_screen(scr) { + if(scr->x_org < x1) x1 = scr->x_org; + if(scr->y_org < y1) y1 = scr->y_org; + if(scr->x_org + scr->width > x2) x2 = scr->x_org + scr->width; + if(scr->y_org + scr->height > y2) y2 = scr->y_org + scr->height; + } + faux.x_org = x1; + faux.y_org = y1; + faux.width = x2 - x1; + faux.height = y2 - y1; + best = &faux; + break; + } + } +_out: + VWM_TRACE("Found Screen #%i: %hix%hi @ %hi,%hi", best->screen_number, best->width, best->height, best->x_org, best->y_org); + return best; +} + + +/* helper for determining 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; + int is_empty = 1; + + list_for_each_entry(vwin, &windows, windows) { + if(vwin->desktop == focused_desktop && !vwin->shelved && !vwin->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) { + is_empty = 0; + break; + } + } + } + + return is_empty; +} + + +/* 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) { - Window root; - int x, y, i; - unsigned int width, height, border_width, depth, yoff, xoff; - XPoint points[sizeof(vwm_logo) / sizeof(vwm_point_t)]; + int i; + unsigned int width, height, yoff, xoff; + XPoint points[VWM_LOGO_POINTS]; + const vwm_screen_t *scr = vwm_screen_find(VWM_SCREEN_REL_POINTER); XGrabServer(display); - /* learn the dimensions of the root window */ - XGetGeometry(display, RootWindow(display, screen_num), &root, &x, &y, &width, &height, &border_width, &depth); - - yoff = ((float)height * .375); - xoff = 0; - height /= 4; + /* use the dimensions of the pointer-containing screen */ + width = scr->width; + height = scr->height; + xoff = scr->x_org; + yoff = scr->y_org + ((float)height * .333); + height /= 3; /* the logo gets shrunken vertically until it's essentially a flat line */ - while(height--) { + while(height -= 2) { /* scale and center the points to the screen size */ - for(i = 0; i < sizeof(points) / sizeof(XPoint); i++) { - points[i].x = xoff + (vwm_logo[i].x * (float)width); - points[i].y = (vwm_logo[i].y * (float)height) + yoff; + for(i = 0; i < VWM_LOGO_POINTS; i++) { + points[i].x = xoff + (i * .2 * (float)width); + points[i].y = (i % 2 * (float)height) + yoff; } XDrawLines(display, RootWindow(display, screen_num), gc, points, sizeof(points) / sizeof(XPoint), CoordModeOrigin); XFlush(display); usleep(3333); XDrawLines(display, RootWindow(display, screen_num), gc, points, sizeof(points) / sizeof(XPoint), CoordModeOrigin); + XFlush(display); /* the width is shrunken as well, but only by as much as it is tall */ yoff++; @@ -129,16 +294,22 @@ static void vwm_draw_logo(void) /* XXX: for now just double forks to avoid having to collect return statuses of the long-running process */ -static void vwm_launch(char **argv) +typedef enum _vwm_launch_mode_t { + VWM_LAUNCH_MODE_FG, + VWM_LAUNCH_MODE_BG, +} vwm_launch_mode_t; + +static void vwm_launch(char **argv, vwm_launch_mode_t mode) { - if(!fork()) { + if(mode == VWM_LAUNCH_MODE_FG || !fork()) { if(!fork()) { /* child */ + setpriority(PRIO_PROCESS, getpid(), priority + LAUNCHED_RELATIVE_PRIORITY); execvp(argv[0], argv); } - exit(0); + if(mode == VWM_LAUNCH_MODE_BG) exit(0); } - wait(NULL); + wait(NULL); /* TODO: could wait for the specific pid, particularly in FG mode ... */ } @@ -156,14 +327,14 @@ static int vwm_context_focus(vwm_context_focus_t desired_context) /* desired == DESKTOP && focused == SHELF */ VWM_TRACE("unmapping shelf window \"%s\"", focused_shelf->name); - XUnmapWindow(display, focused_shelf->window); + vwm_win_unmap(focused_shelf); XFlush(display); /* for a more responsive feel */ /* map the focused desktop */ list_for_each_entry(vwin, &windows, windows) { if(vwin->desktop == focused_desktop && !vwin->shelved) { VWM_TRACE("Mapping desktop window \"%s\"", vwin->name); - XMapWindow(display, vwin->window); + vwm_win_map(vwin); } } @@ -187,14 +358,14 @@ static int vwm_context_focus(vwm_context_focus_t desired_context) list_for_each_entry(vwin, &windows, windows) { if(vwin->desktop == focused_desktop) { VWM_TRACE("Unmapping desktop window \"%s\"", vwin->name); - XUnmapWindow(display, vwin->window); + vwm_win_unmap(vwin); } } XFlush(display); /* for a more responsive feel */ VWM_TRACE("Mapping shelf window \"%s\"", focused_shelf->name); - XMapWindow(display, focused_shelf->window); + vwm_win_map(focused_shelf); vwm_win_focus(focused_shelf); focused_context = VWM_CONTEXT_FOCUS_SHELF; @@ -214,6 +385,7 @@ static int vwm_context_focus(vwm_context_focus_t desired_context) /* make the specified desktop the most recently used one */ static void vwm_desktop_mru(vwm_desktop_t *d) { + VWM_TRACE("MRU desktop: %p", d); list_move(&d->desktops_mru, &desktops_mru); } @@ -226,28 +398,20 @@ static int vwm_desktop_focus(vwm_desktop_t *d) 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) { + if((vwm_context_focus(VWM_CONTEXT_FOCUS_DESKTOP) && focused_desktop != d) || focused_desktop != d) { vwm_window_t *w; /* 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_TRACE("Unmapping \"%s\"", w->name); - XUnmapWindow(display, w->window); - } + if(w->desktop == focused_desktop) vwm_win_unmap(w); } XFlush(display); list_for_each_entry(w, &windows, windows) { if(w->shelved) continue; - - if(w->desktop == d) { - VWM_TRACE("Mapping \"%s\"", w->name); - XMapWindow(display, w->window); - } + if(w->desktop == d) vwm_win_map(w); } focused_desktop = d; @@ -297,13 +461,15 @@ static void vwm_desktop_destroy(vwm_desktop_t *d) /* also silently refuse to destroy the last desktop (for now) */ if(d->focused_window || (d->desktops.next == d->desktops.prev)) return; - /* focus another desktop if we're the focused desktop */ + /* focus the MRU desktop that isn't this one if we're the focused desktop */ if(d == focused_desktop) { - /* we just look for one that is different from ours, relative to ours, looking at the next one then the previous */ - if(d->desktops.next != &desktops) { - vwm_desktop_focus(list_entry(d->desktops.next, vwm_desktop_t, desktops)); - } else if(d->desktops.prev != &desktops) { - vwm_desktop_focus(list_entry(d->desktops.prev, vwm_desktop_t, desktops)); + vwm_desktop_t *next_desktop; + + list_for_each_entry(next_desktop, &desktops_mru, desktops_mru) { + if(next_desktop != d) { + vwm_desktop_focus(next_desktop); + break; + } } } @@ -312,6 +478,23 @@ static void vwm_desktop_destroy(vwm_desktop_t *d) } +/* 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) +{ + VWM_TRACE("Unmapping \"%s\"", vwin->name); + vwin->unmapping = 1; + XUnmapWindow(display, vwin->window); +} + + +/* map the specified window */ +static void vwm_win_map(vwm_window_t *vwin) +{ + VWM_TRACE("Mapping \"%s\"", vwin->name); + XMapWindow(display, vwin->window); +} + + /* make the specified window the most recently used one */ static void vwm_win_mru(vwm_window_t *vwin) { @@ -374,50 +557,135 @@ static vwm_window_t * vwm_win_focused(void) return vwin; } -/* fullscreen a window with borders obscured "allscreen" */ -static void vwm_win_allscreen(vwm_window_t *vwin) + +/* "autoconfigure" windows (configuration shortcuts like fullscreen/halfscreen/quarterscreen) and restoring the window */ +typedef enum _vwm_win_autoconf_t { + VWM_WIN_AUTOCONF_NONE, /* un-autoconfigured window (used to restore the configuration) */ + VWM_WIN_AUTOCONF_QUARTER, /* quarter-screened */ + VWM_WIN_AUTOCONF_HALF, /* half-screened */ + VWM_WIN_AUTOCONF_FULL, /* full-screened */ + VWM_WIN_AUTOCONF_ALL /* all-screened (borderless) */ +} vwm_win_autoconf_t; + +typedef enum _vwm_side_t { + VWM_SIDE_TOP, + VWM_SIDE_BOTTOM, + VWM_SIDE_LEFT, + VWM_SIDE_RIGHT +} vwm_side_t; + +typedef enum _vwm_corner_t { + VWM_CORNER_TOP_LEFT, + VWM_CORNER_TOP_RIGHT, + VWM_CORNER_BOTTOM_RIGHT, + VWM_CORNER_BOTTOM_LEFT +} vwm_corner_t; + +static void vwm_win_autoconf(vwm_window_t *vwin, vwm_screen_rel_t rel, vwm_win_autoconf_t conf, ...) { - Window root; - int x, y; - unsigned int width, height, border_width, depth; + const vwm_screen_t *scr; + va_list ap; + XWindowChanges changes = { .border_width = WINDOW_BORDER_WIDTH }; + + /* remember the current configuration as the "client" configuration if it's not an autoconfigured one. */ + if(vwin->autoconfigured == VWM_WIN_AUTOCONF_NONE) vwin->client = vwin->config; + + scr = vwm_screen_find(rel, vwin); + va_start(ap, conf); + switch(conf) { + case VWM_WIN_AUTOCONF_QUARTER: { + vwm_corner_t corner = va_arg(ap, vwm_corner_t); + changes.width = scr->width / 2 - (WINDOW_BORDER_WIDTH * 2); + changes.height = scr->height / 2 - (WINDOW_BORDER_WIDTH * 2); + switch(corner) { + case VWM_CORNER_TOP_LEFT: + changes.x = scr->x_org; + changes.y = scr->y_org; + break; - if(vwin->fullscreened == 2) return; + case VWM_CORNER_TOP_RIGHT: + changes.x = scr->x_org + scr->width / 2; + changes.y = scr->y_org; + break; - XGetGeometry(display, RootWindow(display, screen_num), &root, &x, &y, &width, &height, &border_width, &depth); - XGetGeometry(display, vwin->window, &root, &vwin->client.x, &vwin->client.y, &vwin->client.width, &vwin->client.height, &border_width, &depth); - XMoveResizeWindow(display, vwin->window, -border_width, -border_width, width, height); - vwin->fullscreened = 2; -} + case VWM_CORNER_BOTTOM_RIGHT: + changes.x = scr->x_org + scr->width / 2; + changes.y = scr->y_org + scr->height / 2; + break; -/* fullscreen a window */ -static void vwm_win_fullscreen(vwm_window_t *vwin) -{ - Window root; - int x, y; - unsigned int width, height, border_width, depth; + case VWM_CORNER_BOTTOM_LEFT: + changes.x = scr->x_org; + changes.y = scr->y_org + scr->height / 2; + break; + } + break; + } - if(vwin->fullscreened == 1) return; + case VWM_WIN_AUTOCONF_HALF: { + vwm_side_t side = va_arg(ap, vwm_side_t); + switch(side) { + case VWM_SIDE_TOP: + changes.width = scr->width - (WINDOW_BORDER_WIDTH * 2); + changes.height = scr->height / 2 - (WINDOW_BORDER_WIDTH * 2); + changes.x = scr->x_org; + changes.y = scr->y_org; + break; - XGetGeometry(display, RootWindow(display, screen_num), &root, &x, &y, &width, &height, &border_width, &depth); - /* TODO: do not remember the current geometry if we're going from allscreen to fullscreen! */ - XGetGeometry(display, vwin->window, &root, &vwin->client.x, &vwin->client.y, &vwin->client.width, &vwin->client.height, &border_width, &depth); - XMoveResizeWindow(display, vwin->window, 0, 0, width - (border_width * 2), height - (border_width * 2)); - vwin->fullscreened = 1; -} + case VWM_SIDE_BOTTOM: + changes.width = scr->width - (WINDOW_BORDER_WIDTH * 2); + changes.height = scr->height / 2 - (WINDOW_BORDER_WIDTH * 2); + changes.x = scr->x_org; + changes.y = scr->y_org + scr->height / 2; + break; + case VWM_SIDE_LEFT: + changes.width = scr->width / 2 - (WINDOW_BORDER_WIDTH * 2); + changes.height = scr->height - (WINDOW_BORDER_WIDTH * 2); + changes.x = scr->x_org; + changes.y = scr->y_org; + break; -/* restore a window to its non-fullscreened size and coordinates */ -static void vwm_win_restore(vwm_window_t *vwin) -{ - if(!vwin->fullscreened) return; + case VWM_SIDE_RIGHT: + changes.width = scr->width / 2 - (WINDOW_BORDER_WIDTH * 2); + changes.height = scr->height - (WINDOW_BORDER_WIDTH * 2); + changes.x = scr->x_org + scr->width / 2; + changes.y = scr->y_org; + break; + } + break; + } - XMoveResizeWindow(display, vwin->window, vwin->client.x, vwin->client.y, vwin->client.width, vwin->client.height); - vwin->fullscreened = 0; + case VWM_WIN_AUTOCONF_FULL: + changes.width = scr->width - WINDOW_BORDER_WIDTH * 2; + changes.height = scr->height - WINDOW_BORDER_WIDTH * 2; + changes.x = scr->x_org; + changes.y = scr->y_org; + break; + + case VWM_WIN_AUTOCONF_ALL: + changes.width = scr->width; + changes.height = scr->height; + changes.x = scr->x_org; + changes.y = scr->y_org; + changes.border_width = 0; + break; + + case VWM_WIN_AUTOCONF_NONE: /* restore window if autoconfigured */ + changes.width = vwin->client.width; + changes.height = vwin->client.height; + changes.x = vwin->client.x; + changes.y = vwin->client.y; + break; + } + va_end(ap); + + XConfigureWindow(display, vwin->window, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &changes); + vwin->autoconfigured = conf; } /* focus a window */ -/* this updates window borders as needed and the X input focus */ +/* 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); @@ -428,7 +696,9 @@ static void vwm_win_focus(vwm_window_t *vwin) /* update the border color accordingly */ if(vwin->shelved) { /* set the border of the newly focused window to the shelved color */ - XSetWindowBorder(display, vwin->window, shelved_window_border_color.pixel); + XSetWindowBorder(display, vwin->window, 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 @@ -445,16 +715,53 @@ static void vwm_win_focus(vwm_window_t *vwin) } -/* focus the next window on a virtual desktop relative to the supplied window */ -static vwm_window_t * vwm_win_focus_next(vwm_window_t *vwin, vwm_context_focus_t context) +/* focus the next window on a virtual desktop relative to the supplied window, in the specified context, respecting screen boundaries according to fence. */ +typedef enum _vwm_fence_t { + VWM_FENCE_IGNORE = 0, /* behave as if screen boundaries don't exist (like the pre-Xinerama code) */ + VWM_FENCE_RESPECT, /* confine operation to within the screen */ + VWM_FENCE_TRY_RESPECT, /* confine operation to within the screen, unless no options exist. */ + VWM_FENCE_VIOLATE, /* leave the screen for any other*/ + VWM_FENCE_MASKED_VIOLATE /* leave the screen for any other not masked */ +} vwm_fence_t; + +static vwm_window_t * vwm_win_focus_next(vwm_window_t *vwin, vwm_context_focus_t context, vwm_fence_t fence) { - vwm_window_t *next; + const vwm_screen_t *scr = vwm_screen_find(VWM_SCREEN_REL_WINDOW, vwin), *next_scr = NULL; + vwm_window_t *next; + unsigned long visited_mask; +_retry: + visited_mask = 0; list_for_each_entry(next, &vwin->windows, windows) { /* 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->mapped && ((context == VWM_CONTEXT_FOCUS_SHELF && next->shelved) || (context == VWM_CONTEXT_FOCUS_DESKTOP && !next->shelved && next->desktop == focused_desktop))) break; + 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 && + !((1UL << next_scr->screen_number) & fence_mask)) + ))) break; + + if(fence == VWM_FENCE_MASKED_VIOLATE && next_scr && next_scr != scr) visited_mask |= (1UL << next_scr->screen_number); + } + + if(fence == VWM_FENCE_TRY_RESPECT && next == vwin) { + /* if we tried to respect the fence and failed to find a next, fallback to ignoring the fence and try again */ + fence = VWM_FENCE_IGNORE; + goto _retry; + } else if(fence == VWM_FENCE_MASKED_VIOLATE && next_scr) { + /* if we did a masked violate update the mask with the potentially new screen number */ + if(next == vwin && visited_mask) { + /* if we failed to find a next window on a different screen but had candidates we've exhausted screens and need to reset the mask then retry */ + VWM_TRACE("all candidate screens masked @ 0x%lx, resetting mask", fence_mask); + fence_mask = 0; + goto _retry; + } + fence_mask |= (1UL << next_scr->screen_number); + VWM_TRACE("VWM_FENCE_MASKED_VIOLATE fence_mask now: 0x%lx\n", fence_mask); } switch(context) { @@ -469,13 +776,11 @@ static vwm_window_t * vwm_win_focus_next(vwm_window_t *vwin, vwm_context_focus_t case VWM_CONTEXT_FOCUS_SHELF: if(next != focused_shelf) { /* shelf switch, unmap the focused shelf and take it over */ - VWM_TRACE("Unmapping window \"%s\"", focused_shelf->name); - XUnmapWindow(display, focused_shelf->window); + vwm_win_unmap(focused_shelf); XFlush(display); - VWM_TRACE("Mapping window \"%s\"", next->name); - XMapWindow(display, next->window); + vwm_win_map(next); focused_shelf = next; vwm_win_focus(next); } @@ -500,7 +805,7 @@ static void vwm_win_shelve(vwm_window_t *vwin) /* shelving focused window, focus the next window */ if(vwin == vwin->desktop->focused_window) { - vwm_win_mru(vwm_win_focus_next(vwin, VWM_CONTEXT_FOCUS_DESKTOP)); + vwm_win_mru(vwm_win_focus_next(vwin, VWM_CONTEXT_FOCUS_DESKTOP, VWM_FENCE_RESPECT)); } if(vwin == vwin->desktop->focused_window) { @@ -514,8 +819,7 @@ static void vwm_win_shelve(vwm_window_t *vwin) /* newly shelved windows always become the focused shelf */ focused_shelf = vwin; - VWM_TRACE("Unmapping \"%s\"", vwin->name); - XUnmapWindow(display, vwin->window); + vwm_win_unmap(vwin); } @@ -524,7 +828,7 @@ typedef enum _vwm_grab_mode_t { VWM_GRABBED } vwm_grab_mode_t; -/* manages a new window (called in response to CreateNotify events, and during startup to manage existing windows) */ +/* 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) { XWindowAttributes attrs; @@ -540,17 +844,14 @@ static vwm_window_t * vwm_win_manage(Window win, vwm_grab_mode_t grabbed) if(!XGetWindowAttributes(display, win, &attrs)) goto _fail_grabbed; /* 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 VWM_TRACE("Managing %#x", (unsigned int)win); - /* if no desktops exist, implicitly create one */ - if(!focused_desktop) { - /* TODO: interactively name desktops */ - vwm_desktop_focus(vwm_desktop_create(NULL)); - vwm_desktop_mru(focused_desktop); - } - /* 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) { @@ -559,8 +860,8 @@ static vwm_window_t * vwm_win_manage(Window win, vwm_grab_mode_t grabbed) } XUngrabButton(display, AnyButton, AnyModifier, win); - XGrabButton(display, AnyButton, Mod1Mask, win, False, (PointerMotionMask | ButtonPressMask | ButtonReleaseMask), GrabModeAsync, GrabModeAsync, None, None); - XGrabKey(display, AnyKey, Mod1Mask, win, False, GrabModeAsync, GrabModeAsync); + 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); vwin->name = NULL; @@ -575,15 +876,17 @@ static vwm_window_t * vwm_win_manage(Window win, vwm_grab_mode_t grabbed) vwin->desktop = focused_desktop; vwin->window = win; - vwin->fullscreened = 0; + vwin->autoconfigured = VWM_WIN_AUTOCONF_NONE; + vwin->unmapping = 0; + vwin->configuring = 0; vwin->shelved = (focused_context == VWM_CONTEXT_FOCUS_SHELF); /* if we're in the shelf when the window is created, the window is shelved */ - vwin->mapped = (attrs.map_state != IsUnmapped); /* 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; 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, @@ -608,10 +911,12 @@ static vwm_window_t * vwm_win_manage(Window win, vwm_grab_mode_t grabbed) list_add(&vwin->windows, &windows); } - /* if the desktop has no focused window yet, automatically focus the newly managed one */ - /* TODO: how does this interact with the shelf? */ - if(!focused_desktop->focused_window) { - VWM_TRACE("Mapped window \"%s\" is alone on desktop \"%s\", focusing", vwin->name, focused_desktop->name); + /* always raise newly managed windows so we know about them. */ + XRaiseWindow(display, vwin->window); + + /* 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_win_focus(vwin); } @@ -630,10 +935,10 @@ _fail_grabbed: static void vwm_win_unfocus(vwm_window_t *vwin) { /* 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: what if it's not mapped but goes away??? we don't want to try take the InputFocus it when not mapped... XXX TODO */ + /* 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_win_focus_next(vwin, VWM_CONTEXT_FOCUS_SHELF, VWM_FENCE_IGNORE); if(vwin == focused_shelf) { VWM_TRACE("shelf empty, leaving"); @@ -646,7 +951,7 @@ static void vwm_win_unfocus(vwm_window_t *vwin) /* 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_win_focus_next(vwin, VWM_CONTEXT_FOCUS_DESKTOP, VWM_FENCE_TRY_RESPECT); } if(vwin->desktop->focused_window == vwin) { @@ -656,7 +961,7 @@ static void vwm_win_unfocus(vwm_window_t *vwin) } -/* migrate a window to a new desktop, focuses the destionation desktop as well */ +/* 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) { /* go through the motions of unfocusing the window if it is focused */ @@ -673,7 +978,7 @@ static void vwm_win_migrate(vwm_window_t *vwin, vwm_desktop_t *desktop) /* focus the window so borders get updated */ vwm_win_focus(vwin); - vwm_win_mru(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 */ /* ensure the window is raised */ XRaiseWindow(display, vwin->window); @@ -681,35 +986,31 @@ static void vwm_win_migrate(vwm_window_t *vwin, vwm_desktop_t *desktop) /* unmanages a managed window (called in response to DestroyNotify) */ -static void vwm_win_unmanage(Window win) +static void vwm_win_unmanage(vwm_window_t *vwin) { - vwm_window_t *vwin; - - VWM_TRACE("unmanaging %x", (unsigned int)win); - - XGrabServer(display); - XSync(display, False); - - /* find the window */ - vwin = vwm_win_lookup(win); - - if(!vwin) { - VWM_TRACE("window %x not managed", (unsigned int)win); - goto _ungrab; - } - - /* unfocus the window */ +#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); - - /* remove the window from the global windows list */ list_del(&vwin->windows); - if(vwin->name) XFree(vwin->name); + if(vwin == console) console = NULL; + if(vwin->name) XFree(vwin->name); free(vwin); - -_ungrab: - XUngrabServer(display); } @@ -726,7 +1027,7 @@ static int vwm_manage_existing(void) XQueryTree(display, RootWindow(display, screen_num), &root, &parent, &children, &n_children); for(i = 0; i < n_children; i++) { - if (children[i] == None) continue; + if(children[i] == None) continue; if((vwin = vwm_win_manage(children[i], VWM_GRABBED)) == NULL) goto _fail_grabbed; } @@ -805,18 +1106,12 @@ static void compute_resize(vwm_window_t *vwin, XWindowAttributes *attrs, XEvent /* 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(attrs->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(attrs->y != new->y) new->y -= (min_height - new->height); new->height = min_height; } } @@ -831,6 +1126,7 @@ typedef enum _vwm_adjust_mode_t { static int vwm_clicked(Window win, XEvent *impetus) { XWindowAttributes orig, lastrect; + XWindowChanges changes = { .border_width = WINDOW_BORDER_WIDTH }; vwm_window_t *vwin; /* verify the window still exists */ @@ -838,10 +1134,17 @@ static int vwm_clicked(Window win, XEvent *impetus) if(!(vwin = vwm_win_lookup(win))) goto _fail; - if(impetus->xbutton.state & Mod1Mask) { + if(impetus->xbutton.state & WM_GRAB_MODIFIER) { int finished = 0; vwm_adjust_mode_t mode; + /* 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)) { + vwm_win_focus(vwin); + vwm_win_mru(vwin); + } + switch(impetus->xbutton.button) { case Button1: /* immediately raise the window if we're relocating, @@ -855,6 +1158,7 @@ static int vwm_clicked(Window win, XEvent *impetus) XGrabServer(display); 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; @@ -870,21 +1174,25 @@ static int vwm_clicked(Window win, XEvent *impetus) XEvent event; XWindowAttributes resized; - XNextEvent(display, &event); + XWindowEvent(display, win, ButtonReleaseMask | PointerMotionMask, &event); switch(event.type) { case ButtonRelease: switch(mode) { case VWM_ADJUST_MOVE: - XMoveWindow(display, win, - orig.x + (event.xbutton.x_root - impetus->xbutton.x_root), - orig.y + (event.xbutton.y_root - impetus->xbutton.y_root)); + 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); - XMoveResizeWindow(display, win, resized.x, resized.y, resized.width, resized.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; @@ -893,16 +1201,16 @@ static int vwm_clicked(Window win, XEvent *impetus) break; } /* once you manipulate the window it's no longer fullscreened, simply hitting Mod1+Return once will restore fullscreened mode */ - vwin->fullscreened = 0; + vwin->autoconfigured = VWM_WIN_AUTOCONF_NONE; finished = 1; break; case MotionNotify: switch(mode) { case VWM_ADJUST_MOVE: - XMoveWindow(display, win, - orig.x + (event.xmotion.x_root - impetus->xbutton.x_root), - orig.y + (event.xmotion.y_root - impetus->xbutton.y_root)); + 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: @@ -929,13 +1237,6 @@ static int vwm_clicked(Window win, XEvent *impetus) } } - /* 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)) { - vwm_win_focus(vwin); - vwm_win_mru(vwin); - } - XFlush(display); XUngrabPointer(display, CurrentTime); @@ -960,19 +1261,16 @@ static void vwm_keyreleased(Window win, XEvent *keyrelease) case XK_Alt_L: /* TODO: actually use the modifier mapping, for me XK_Alt_[LR] is Mod1. XGetModifierMapping()... */ VWM_TRACE("XK_Alt_[LR] released"); /* make the focused window the most recently used */ - if((vwin = vwm_win_focused())) { - vwm_win_mru(vwin); - } + if((vwin = vwm_win_focused())) vwm_win_mru(vwin); /* make the focused desktop the most recently used */ - if(focused_context == VWM_CONTEXT_FOCUS_DESKTOP && focused_desktop) { - vwm_desktop_mru(focused_desktop); - } + if(focused_context == VWM_CONTEXT_FOCUS_DESKTOP && focused_desktop) vwm_desktop_mru(focused_desktop); if(key_is_grabbed) { XUngrabKeyboard(display, CurrentTime); XFlush(display); key_is_grabbed = 0; + fence_mask = 0; /* reset the fence mask on release for VWM_FENCE_MASKED_VIOLATE */ } break; @@ -986,16 +1284,20 @@ static void vwm_keyreleased(Window win, XEvent *keyrelease) /* Called in response to KeyPress events, I currenly only grab Mod1 keypress events */ static void vwm_keypressed(Window win, XEvent *keypress) { - vwm_window_t *vwin; - KeySym sym; - static KeySym last_sym; - static int repeat_cnt = 0; - int do_grab = 0; + vwm_window_t *vwin; + KeySym sym; + static KeySym last_sym; + static typeof(keypress->xkey.state) last_state; + static int repeat_cnt = 0; + int do_grab = 0; +#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); /* detect repeaters, note repeaters do not span interrupted Mod1 sequences! */ - if(key_is_grabbed && sym == last_sym) { + if(key_is_grabbed && sym == last_sym && keypress->xkey.state == last_state) { repeat_cnt++; } else { repeat_cnt = 0; @@ -1003,11 +1305,11 @@ static void vwm_keypressed(Window win, XEvent *keypress) switch(sym) { -#define launcher(_sym, _argv...)\ +#define launcher(_sym, _label, _argv)\ case _sym: \ { \ - char *args[] = {_argv, NULL};\ - vwm_launch(args);\ + char *args[] = {"bash", "-c", "screen -dr " CONSOLE_SESSION_STRING " -X screen bash -i -x -c \"" _argv " || sleep 86400\"", NULL};\ + vwm_launch(args, VWM_LAUNCH_MODE_BG);\ break; \ } #include "launchers.def" @@ -1018,20 +1320,31 @@ static void vwm_keypressed(Window win, XEvent *keypress) break; case XK_Tab: /* cycle focused window */ - do_grab = 1; /* grab the keyboard so we can respond to the Mod1 release */ + do_grab = 1; /* update MRU window on commit (Mod1 release) */ - /* focus the next window, note this doesn't affect MRU, that happens on Mod1 release */ + /* focus the next window, note this doesn't affect MRU yet, that happens on Mod1 release */ if((vwin = vwm_win_focused())) { - vwm_win_focus_next(vwin, focused_context); + if(keypress->xkey.state & ShiftMask) { + vwm_win_focus_next(vwin, focused_context, VWM_FENCE_MASKED_VIOLATE); + } else { + vwm_win_focus_next(vwin, focused_context, VWM_FENCE_RESPECT); + } } break; - case XK_space: /* cycle focused desktop utilizing MRU */ - do_grab = 1; /* grab the keyboard so we can respond to the Mod1 release for MRU updating */ + case XK_space: { /* cycle focused desktop utilizing MRU */ + vwm_desktop_t *next_desktop = list_entry(focused_desktop->desktops_mru.next == &desktops_mru ? focused_desktop->desktops_mru.next->next : focused_desktop->desktops_mru.next, vwm_desktop_t, desktops_mru); /* XXX: note the sensitivity to the desktops_mru head here, we want to look past it. */ - /* XXX: note the sensitivity to the desktops_mru head here, we want to look past it. */ - vwm_desktop_focus(list_entry(focused_desktop->desktops_mru.next == &desktops_mru ? focused_desktop->desktops_mru.next->next : focused_desktop->desktops_mru.next, vwm_desktop_t, desktops_mru)); + do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ + + if(keypress->xkey.state & ShiftMask) { + /* migrate the focused window with the desktop focus to the most recently used desktop */ + if((vwin = vwm_win_focused())) vwm_win_migrate(vwin, next_desktop); + } else { + vwm_desktop_focus(next_desktop); + } break; + } case XK_d: /* destroy focused */ if((vwin = vwm_win_focused())) { @@ -1046,10 +1359,20 @@ static void vwm_keypressed(Window win, XEvent *keypress) } break; - case XK_Escape: /* leave VWM rudely */ - exit(42); + case XK_Escape: /* leave VWM rudely, after triple press */ + 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; case XK_v: /* instantiate (and focus) a new (potentially empty, unless migrating) virtual desktop */ + do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ + if(keypress->xkey.state & ShiftMask) { if((vwin = vwm_win_focused())) { /* migrate the focused window to a newly created virtual desktop, focusing the new desktop simultaneously */ @@ -1062,7 +1385,7 @@ static void vwm_keypressed(Window win, XEvent *keypress) break; case XK_h: /* previous virtual desktop, if we're in the shelf context this will simply switch to desktop context */ - do_grab = 1; /* grab the keyboard so we can respond to the Mod1 release for MRU updating */ + do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ if(keypress->xkey.state & ShiftMask) { if((vwin = vwm_win_focused()) && vwin->desktop->desktops.prev != &desktops) { @@ -1081,7 +1404,7 @@ static void vwm_keypressed(Window win, XEvent *keypress) break; case XK_l: /* next virtual desktop, if we're in the shelf context this will simply switch to desktop context */ - do_grab = 1; /* grab the keyboard so we can respond to the Mod1 release for MRU updating */ + do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ if(keypress->xkey.state & ShiftMask) { if((vwin = vwm_win_focused()) && vwin->desktop->desktops.next != &desktops) { @@ -1114,10 +1437,18 @@ static void vwm_keypressed(Window win, XEvent *keypress) if(repeat_cnt == 1) { /* double: reraise & fullscreen */ - vwm_win_fullscreen(vwin); + vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_FULL); } else if(repeat_cnt == 2) { /* triple: reraise & fullscreen w/borders obscured by screen perimiter */ - vwm_win_allscreen(vwin); + vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_ALL); + } else if(xinerama_screens_cnt > 1) { + if(repeat_cnt == 3) { + /* triple: reraise & fullscreen across all screens */ + vwm_win_autoconf(vwin, VWM_SCREEN_REL_TOTAL, VWM_WIN_AUTOCONF_FULL); + } else if(repeat_cnt == 4) { + /* quadruple: reraise & fullscreen w/borders obscured by screen perimiter */ + vwm_win_autoconf(vwin, VWM_SCREEN_REL_TOTAL, VWM_WIN_AUTOCONF_ALL); + } } XFlush(display); } @@ -1132,8 +1463,8 @@ static void vwm_keypressed(Window win, XEvent *keypress) vwm_win_migrate(vwin, focused_desktop); } } else { - if(vwin->fullscreened == 2) { - vwm_win_fullscreen(vwin); + if(vwin->autoconfigured == VWM_WIN_AUTOCONF_ALL) { + vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_FULL); } else { XLowerWindow(display, vwin->window); } @@ -1144,17 +1475,55 @@ static void vwm_keypressed(Window win, XEvent *keypress) case XK_Return: /* (full-screen / restore) focused window */ if((vwin = vwm_win_focused())) { - if(vwin->fullscreened) { - vwm_win_restore(vwin); + if(vwin->autoconfigured) { + vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_NONE); } else { - vwm_win_fullscreen(vwin); + vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_FULL); } } break; case XK_s: /* shelve focused window */ - if((vwin = vwm_win_focused()) && !vwin->shelved) { - vwm_win_shelve(vwin); + if((vwin = vwm_win_focused()) && !vwin->shelved) vwm_win_shelve(vwin); + break; + + case XK_bracketleft: /* reconfigure the focused window to occupy the left or top half of the screen or left quarters on repeat */ + if((vwin = vwm_win_focused())) { + do_grab = 1; + + if(keypress->xkey.state & ShiftMask) { + if(!repeat_cnt) { + vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_TOP); + } else { + vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, 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); + } else { + vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_BOTTOM_LEFT); + } + } + } + break; + + case XK_bracketright: /* reconfigure the focused window to occupy the right or bottom half of the screen or right quarters on repeat */ + if((vwin = vwm_win_focused())) { + do_grab = 1; + + if(keypress->xkey.state & ShiftMask) { + if(!repeat_cnt) { + vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_HALF, VWM_SIDE_BOTTOM); + } else { + vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, 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); + } else { + vwm_win_autoconf(vwin, VWM_SCREEN_REL_WINDOW, VWM_WIN_AUTOCONF_QUARTER, VWM_CORNER_TOP_RIGHT); + } + } } break; @@ -1163,7 +1532,7 @@ static void vwm_keypressed(Window win, XEvent *keypress) break; } - /* if what we're doing requests a grab if not already grabbed, grab keyboard */ + /* if what we're doing requests a grab, if not already grabbed, grab keyboard */ if(!key_is_grabbed && do_grab) { XGrabKeyboard(display, RootWindow(display, screen_num), False, GrabModeAsync, GrabModeAsync, CurrentTime); key_is_grabbed = 1; @@ -1171,6 +1540,7 @@ static void vwm_keypressed(Window win, XEvent *keypress) /* remember the symbol for repeater detection */ last_sym = sym; + last_state = keypress->xkey.state; } @@ -1179,29 +1549,48 @@ static int errhandler(Display *display, XErrorEvent *err) /* TODO */ return 1; } - + int main(int argc, char *argv[]) { - int done = 0; - XEvent event; - Cursor pointer; + 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}; + +#define reterr_if(_cond, _fmt, _args...) \ + err++;\ + if(_cond) {\ + VWM_ERROR(_fmt, ##_args);\ + return err;\ + } /* open connection with the server */ - if((display = XOpenDisplay(NULL)) == NULL) { - VWM_ERROR("Cannot open display"); - return 1; - } + reterr_if((display = XOpenDisplay(NULL)) == NULL, "Cannot open display"); /* prevent children from inheriting the X connection */ - if(fcntl(ConnectionNumber(display), F_SETFD, FD_CLOEXEC) < 0) { - VWM_ERROR("Cannot set FD_CLOEXEC on X connection"); - return 2; - } + reterr_if(fcntl(ConnectionNumber(display), F_SETFD, FD_CLOEXEC) < 0, "Cannot set FD_CLOEXEC on X connection"); + + /* get our scheduling priority, clients are launched with a priority LAUNCHED_RELATIVE_PRIORITY nicer than this */ + reterr_if((priority = getpriority(PRIO_PROCESS, getpid())) == -1, "Cannot get scheduling priority"); XSetErrorHandler(errhandler); screen_num = DefaultScreen(display); + if(XSyncQueryExtension(display, &sync_event, &sync_error)) { + /* set the window manager to the maximum X client priority */ + XSyncSetPriority(display, RootWindow(display, screen_num), 0x7fffffff); + } + + if(XineramaQueryExtension(display, &xinerama_event, &xinerama_error)) { + xinerama_screens = XineramaQueryScreens(display, &xinerama_screens_cnt); + } + + if(XRRQueryExtension(display, &randr_event, &randr_error)) { + XRRSelectInput(display, RootWindow(display, screen_num), RRScreenChangeNotifyMask); + } + /* allocate colors, I make assumptions about the X server's color capabilities since I'll only use this on modern-ish computers... */ cmap = DefaultColormap(display, screen_num); @@ -1213,13 +1602,18 @@ int main(int argc, char *argv[]) wm_delete_atom = XInternAtom(display, "WM_DELETE_WINDOW", False); wm_protocols_atom = XInternAtom(display, "WM_PROTOCOLS", False); - XSelectInput(display, RootWindow(display, screen_num), SubstructureNotifyMask | SubstructureRedirectMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask); - XGrabKey(display, AnyKey, Mod1Mask, RootWindow(display, screen_num), False, GrabModeAsync, GrabModeAsync); + XSelectInput(display, RootWindow(display, screen_num), + 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); + /* create initial virtual desktop */ + vwm_desktop_focus(vwm_desktop_create(NULL)); + vwm_desktop_mru(focused_desktop); + /* manage all preexisting windows */ vwm_manage_existing(); @@ -1228,6 +1622,9 @@ int main(int argc, char *argv[]) XSetSubwindowMode(display, gc, IncludeInferiors); XSetFunction(display, gc, GXxor); + /* launch the console here so it's likely ready by the time the logo animation finishes (there's no need to synchronize with it currently) */ + vwm_launch(console_args, VWM_LAUNCH_MODE_BG); + /* first the logo color is the foreground */ XSetForeground(display, gc, logo_color.pixel); vwm_draw_logo(); @@ -1242,13 +1639,9 @@ int main(int argc, char *argv[]) XDefineCursor(display, RootWindow(display, screen_num), pointer); /* event loop */ - while (!done) { + while(!done) { XNextEvent(display, &event); - switch (event.type) { - case Expose: - VWM_TRACE("expose"); - break; - + switch(event.type) { case KeyPress: VWM_TRACE("keypress"); vwm_keypressed(event.xkey.window, &event); @@ -1264,47 +1657,15 @@ int main(int argc, char *argv[]) vwm_clicked(event.xbutton.window, &event); break; - case ButtonRelease: - VWM_TRACE("buttonrelease"); - break; - case DestroyNotify: + case DestroyNotify: { + vwm_window_t *vwin; VWM_TRACE("destroynotify"); - vwm_win_unmanage(event.xdestroywindow.window); - break; - - case CirculateNotify: - VWM_TRACE("circulatenotify"); - break; - - case ConfigureNotify: - VWM_TRACE("configurenotify"); - break; - - case UnmapNotify: - VWM_TRACE("unmapnotify"); - break; - - case CreateNotify: - VWM_TRACE("createnotify"); - vwm_win_manage(event.xcreatewindow.window, VWM_NOT_GRABBED); - break; - - case GravityNotify: - VWM_TRACE("gravitynotify"); - break; - - case MapNotify: - VWM_TRACE("mapnotify"); - break; - - case ReparentNotify: - VWM_TRACE("reparentnotify"); - break; - - case MotionNotify: - VWM_TRACE("motionnotify"); + if((vwin = vwm_win_lookup(event.xdestroywindow.window))) { + vwm_win_unmanage(vwin); + } break; + } case ConfigureRequest: { XWindowChanges changes = { @@ -1314,45 +1675,122 @@ int main(int argc, char *argv[]) .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, (event.xconfigurerequest.value_mask | CWBorderWidth), &changes); + XConfigureWindow(display, event.xconfigurerequest.window, change_mask, &changes); break; } - case MapRequest: { + 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; + } - VWM_TRACE("maprequest"); + 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); + } + } + 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"); + } + break; + } - vwin = vwm_win_lookup(event.xmap.window); - if(vwin && !vwin->mapped) { - int x = 0, y = 0; + 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; + 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; + } + + if(classhint->res_class) XFree(classhint->res_class); + if(classhint->res_name) XFree(classhint->res_name); + XFree(classhint); + } + + /* TODO: this is a good place to hook in a window placement algo */ - /* on initial mapping of a window we discover the coordinates and dimensions of the window */ - changes.x = x; - changes.y = y; + /* 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); + } else { + scr = vwm_screen_find(VWM_SCREEN_REL_WINDOW, focused_desktop->focused_window); + } + 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; + } - vwin->mapped = 1; XGetWMNormalHints(display, event.xmap.window, vwin->hints, &vwin->hints_supplied); XGetWindowAttributes(display, event.xmap.window, &attrs); - /* TODO: this is a good place to hook in a window placement algo */ - vwin->client.x = x; - vwin->client.y = y; + 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); } - - XMapWindow(display, event.xmap.window); - if(vwin->desktop->focused_window == vwin) { - XSync(display, False); - XSetInputFocus(display, vwin->window, RevertToPointerRoot, CurrentTime); + + if(domap) { + XMapWindow(display, event.xmap.window); + if(vwin && vwin->desktop->focused_window == vwin) { + XSync(display, False); + XSetInputFocus(display, vwin->window, RevertToPointerRoot, CurrentTime); + } } break; } @@ -1362,8 +1800,35 @@ int main(int argc, char *argv[]) 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: - VWM_ERROR("Unhandled X op %i", event.type); + 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); + } break; } } @@ -8,14 +8,14 @@ #include "list.h" -#define VWM_ERROR(fmt, args...) fprintf(stderr, "%s:%i\t%s() "fmt"\n", __FILE__, __LINE__, __FUNCTION__, ##args) -#define VWM_PERROR(fmt, args...) fprintf(stderr, "%s:%i\t%s() "fmt"; %s\n", __FILE__, __LINE__, __FUNCTION__, ##args, strerror(errno)) -#define VWM_BUG(fmt, args...) fprintf(stderr, "BUG %s:%i\t%s() "fmt"; %s\n", __FILE__, __LINE__, __FUNCTION__, ##args, strerror(errno)) +#define VWM_ERROR(fmt, args...) fprintf(stderr, "%s:%i\t%s() "fmt"\n", __FILE__, __LINE__, __FUNCTION__, ##args) +#define VWM_PERROR(fmt, args...) fprintf(stderr, "%s:%i\t%s() "fmt"; %s\n", __FILE__, __LINE__, __FUNCTION__, ##args, strerror(errno)) +#define VWM_BUG(fmt, args...) fprintf(stderr, "BUG %s:%i\t%s() "fmt"; %s\n", __FILE__, __LINE__, __FUNCTION__, ##args, strerror(errno)) #ifdef TRACE -#define VWM_TRACE(fmt, args...) fprintf(stderr, "%s:%i\t%s() "fmt"\n", __FILE__, __LINE__, __FUNCTION__, ##args) +#define VWM_TRACE(fmt, args...) fprintf(stderr, "%s:%i\t%s() "fmt"\n", __FILE__, __LINE__, __FUNCTION__, ##args) #else -#define VWM_TRACE(fmt, args...) +#define VWM_TRACE(fmt, args...) do { } while(0) #endif typedef struct _vwm_box_t { @@ -24,26 +24,28 @@ typedef struct _vwm_box_t { } vwm_box_t; typedef struct _vwm_desktop_t { - list_head_t desktops; /* global list of (virtual) desktops */ - list_head_t desktops_mru; /* global list of (virtual) desktops in MRU order */ - char *name; /* name of the desktop (TODO) */ - struct _vwm_window_t *focused_window; /* the focused window on this virtual desktop */ + list_head_t desktops; /* global list of (virtual) desktops */ + list_head_t desktops_mru; /* global list of (virtual) desktops in MRU order */ + char *name; /* name of the desktop (TODO) */ + struct _vwm_window_t *focused_window; /* the focused window on this virtual desktop */ } vwm_desktop_t; typedef struct _vwm_window_t { list_head_t windows; /* global list of managed client windows */ - struct _vwm_desktop_t *desktop; /* desktop this window belongs to currently */ + vwm_desktop_t *desktop; /* desktop this window belongs to currently */ char *name; /* client name */ - vwm_box_t client; /* box of the configured client window relative to the root */ + vwm_box_t client; /* box of the client-configured window relative to the root */ + vwm_box_t config; /* box for the current window configuration maintained in handling of ConfigureNotify events */ Window window; /* the X window being managed by vwm */ XSizeHints *hints; /* hints the client supplied */ long hints_supplied; /* bitfield reflecting the hints the client supplied */ - unsigned int fullscreened:2; /* is the window fullscreened (1)? is the window "allscreened" with the border obscured (2)? */ - unsigned int mapped:1; /* has mapping been requested on the window? */ + unsigned int autoconfigured:3; /* autoconfigured window states (none/quarter/half/full/all) */ + unsigned int unmapping:1; /* is the window being unmapped? (by vwm) */ + unsigned int configuring:1; /* is the window being configured/placed? (by vwm) */ unsigned int shelved:1; /* is the window shelved? */ } vwm_window_t; |