diff options
-rw-r--r-- | README | 560 | ||||
-rw-r--r-- | src/colors.def | 3 | ||||
-rw-r--r-- | src/context.c | 222 | ||||
-rw-r--r-- | src/context.h | 23 | ||||
-rw-r--r-- | src/desktop.c | 137 | ||||
-rw-r--r-- | src/desktop.h | 4 | ||||
-rw-r--r-- | src/key.c | 235 | ||||
-rw-r--r-- | src/screen.c | 11 | ||||
-rw-r--r-- | src/vwm.c | 12 | ||||
-rw-r--r-- | src/vwm.h | 19 | ||||
-rw-r--r-- | src/window.c | 185 | ||||
-rw-r--r-- | src/window.h | 2 | ||||
-rw-r--r-- | src/xwindow.c | 20 |
13 files changed, 902 insertions, 531 deletions
@@ -1,72 +1,164 @@ \/\/\ -Built-ins: +Modifiers: - Mod1-RClick Focus the clicked window, but suppress raising + Mod1-r- Modifies some desktop/context/window switching + operations to be reversed. By itself is effectively + a noop. + + Unlike the other modifiers, I haven't bothered + explicitly documenting -r's applicability below. + It's basically: Tab, Space, and `/Grave. + + Mod1-Shift- Modifies many operations into "Migrates". A migrate + is a focused desktop/context changing operation which + brings the currently focused window along. By itself + is effectively a noop. + + Mod1-s- Modifies window "Migrate" operations into "Send", by + itself is effectively a noop. (Was "shelve" window in + previous versions) + + Mod1-Shift-s- Modifies some operations into a "Migrate"-like + "Send". Where a plain "Send" tends to create a new + empty desktop for receiving the sent window, when + combined with Shift, an existing focused desktop at + the destination will be used to receive the sent + window, which is how migrates work, but unlike + migrate, no actual switching occurs. + + These also haven't been explicitly documented below, + currently it's basically: `/Grave, and 0-9. + + +Built-in operations: + + 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 + the window from its nearest corner until drag + completes. - Mod1-LClick Focus and raise the clicked window + Mod1-LClick Focus and raise the clicked window. Mod1-LClick-drag Focus and raise the clicked window, moving the window - until the drag completes + until the drag completes. -* Mod1-l Switch to virtual desktop to the right (if exists) +* Mod1-l Switch to virtual desktop to the right (if exists). -* Mod1-h Switch to virtual desktop to the left (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 + "allscreen" mode it will simply be fullscreened first + without lowering. Mod1-k [-k [-k] Raise focused window [a second k raises and [-k] [-k]] fullscreens the window [a third k raises and - "allscreens" without a visible border [a fourth k - raises and fullscreens across all heads [a fifth k - raises and "allscreens" across all heads]]] + "allscreens" without a visible border [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-k Migrate the focused window to the next/upper context + (if exists), keeping the window focused. - 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-j Migrate the focused window to the previous/lower + context (if exists), keeping the window focused. * Mod1-Shift-l Migrate the focused window to the virtual desktop - to the right (if exists), keeping the window focused + 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 + to the left (if exists), keeping the window focused. + +* Mod1-s-k Send the focused window to the next/upper context's + focused desktop (if exists), keeping the window + focused in the destination, but without switching to + the destination. + +* Mod1-s-j Send the focused window to the previous/lower + context's focused desktop (if exists), keeping the + window focused in the destination, but without + switching to the destination. + +* Mod1-s-l Send the focused window to the virtual desktop + to the right within the same context (if exists), + keeping the window focused in the destination, but + without switching to the destination. + +* Mod1-s-h Migrate the focused window to the virtual desktop + to the left within the same context (if exists), + keeping the window focused in the destination, but + without switching to the destination. - Mod1-v Create a new virtual desktop and switch to it + Mod1-v Create a new empty virtual desktop within the current + context and switch to it. - Mod1-Shift-v Create a new virtual desktop, move the focused window - to it, and switch to it + Mod1-Shift-v Create a new virtual desktop within the current + context and switch to it, bringing the currently + focused window along (if present). -* Mod1-Space Switch to the most recently used virtual desktop - (like Mod1-Tab but for virtual desktops) + Mod1-s-v Create a new virtual desktop within the current + context, send the focused window to it, but don't + switch to the new virtual desktop. -* Mod1-Shift-Space Switch to the most recently used virtual desktop, - bringing the focused window with + Mod1-c Create a new empty virtual desktop within the next + unused context, implicitly creating a new context, + and switch to it. - Mod1-` Alternate between shelf (if populated) and virtual - desktop contexts + Mod1-Shift-c Create a new empty virtual desktop within the next + unused context, implicitly creating a new context, + and switch to it, bringing the currently focused + window along (if present). - Mod1-s Shelve the focused window without switching to the - shelf, adopting the newly shelved window as the - focused window within the shelf + Mod1-s-c Create a new empty virtual desktop within the next + unused context, implicitly creating a new context, + send the focused window to it, but don't switch to + the new virtual desktop/context. + + Mod1-0...9 Switch to the numbered context's focused desktop, + implicitly creating it if currently unused. + + Mod1-Shift-0..9 Switch to the numbered context's focused desktop, + implicitly creating it if currently unused, bringing + the focused window along (if present). + + Mod1-s-0..9 Send the focused window (if present) to a newly + created desktop within the numbered context. + +* Mod1-Space Switch to the next most recently used virtual desktop + within the current context (like Mod1-Tab but for + virtual desktops). + +* Mod1-Shift-Space Switch to the next most recently used virtual desktop + within the current context, bringing the focused + window along. + + Mod1-s-Space Send the focused window to the next most recently + used desktop within the current context, keeping it + focused there, but without actually switching + desktops. + + Mod1-` Switch to the next most recently used context's + focused virtual desktop. + + Mod1-Shift-` Switch to the most recently used context's focused + virtual desktop, bringing the focused window along. + + Mod1-s-` Send the currently focused window to a newly created + desktop within the next most recently used context, + without actually switching to it. * 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. + 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 MRU order until releasing Mod1. In + multihead configurations the next window selection is + further confined to within the current screen+desktop. Mod1-Shift-Tab Identical to Mod1-Tab except switches to the MRU - window on another screen in a multihead configuration + 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 @@ -122,8 +214,8 @@ Built-ins: If a simultaneous second Mod1 is pressed at any point during a *'d command, the window (and its desktop) focused when the *'d command began will immediately be refocused - but not raised. This is - intentional to simplify the arranging of obscured focused windows. If - you find yourself restored to a desktop full of windows where your + intentional to simplify the arranging of obscured focused windows. + If you find yourself restored to a desktop full of windows where your focused window is totally obscured/invisible, simply press Mod1-k to raise it if desired. @@ -142,85 +234,127 @@ Default launchers (configure by editing launchers.def and rebuild): 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. + 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. - 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. + When new windows appear on a non-empty 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. Mod1-r-Tab, using r as a modifier, may + be used to reverse the switching direction, handy for undoing an + accidental overshoot. + + Like windows, virtual desktops are also kept on an MRU-ordered list. + These are cycled through via Mod1-Space, created with Mod1-v, and + destroyed with Mod1-d when empty. As with windows, Mod1-r-Space may be + used to reverse the switching direction. + + Virtual desktops are grouped by contexts. Contexts are also kept on + MRU-ordered lists, which are cycled through via Mod1-`, created with + Mod1-c, and switched to by number with Mod1-0 through 9, which implicitly + creates the switched-to context if needed. + + Prior versions of vwm included a "shelf" feature, this has been removed + in favor of the more generalized contexts. In the past Mod1-s would + "shelve" a window, and Mod1-` would switch between the shelf and focused + virtual desktop. Now Mod1-s is a modifier for sending windows elsewhere, + with one of the destinations being other contexts. + + The shelf was used as a sort of junk drawer for things like xterms + running background processes without losing easy access to their + interactivity/output, while not polluting the active virtual desktops. + + When vwm starts, it creates two contexts, numbers 0 and 1. 1 is what's + focused on startup, with 0 intended to serve as the shelf equivalent. + Now users may send windows to the shelf/junk drawer equivalent by + pressing Mod1-s-0, or Mod1-Shift-s-0, the former creating a new virtual + desktop for the sent window in context 0, the latter targeting the + existing focused desktop in context 0. Omitting the -s- from the former + switches to the focused desktop in contetxt 0, from the latter migrates + the focused window to the focused desktop in context 0. 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. + 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. + Things like Mod1-[, Mod1-], and mod1-k-k respect screen boundaries of the + window's majority containing screen, and mod1-k-k-k mod1-k-k-k-k can be + used to violate those boundaries for creating fullscreen/allscreen + windows spanning multiple displays. + + Multihead support is currently very limited. There's currently no + builtin for things like migrating windows to different screens, which + would be useful, especially for the mod1-[, mod1-], mod1-k-k style + autoconfigured windows, since they could automatically reconfigure + themselves migrating to different screen dimensions. The best way to + move windows to different screens is to Mod1-LClick-drag until the window + is at least mostly within the destination screen. At that point all the + autoconfigure window builtins utilize the most-overlapped screen as the + container. + Composite/Monitoring: - One reason vwm was created was to provide a simplified platform for the - research and development of a window manager with integrated omnipresent - first-class local process monitoring. Early attempts were made to modify an - existing window manager (WindowMaker) which produced unsatisfactory though - inspiring results. The window managers vwm[12] were created shortly after to - flesh out the interaction model and solidify an perfectly usable and easily - modified window manager foundation, while libvmon was created in parallel to - facilitate the efficient process monitoring required for such a task. - - After a number of iterations it was found that the Composite extension (along - with the accompanying Damage and Render extensions) would give the best - results on a modern Xorg linux system. Compositing hurts the rendering - performance of X applications significantly however, so a hybrid model has - been employed. - - Monitoring overlays visibility is toggled using Mod1-Semicolon, the sample - rate is increased using Mod1-Right, and decreased using Mod1-Left. - - When the monitoring is not visible, vwm3 continues to leave X in immediate - rendering mode with no additional overhead in the rendering pipeline, just - like vwm2. The only additional overhead is the cost of regularly sampling - process statistics and maintaining the state of window overlays (which does - involve some X rendering of graphs and text, but does not introduce overhead - to the drawing of client windows). + One reason vwm was created was to provide a simplified platform for + research and development of a window manager having integrated local + process CPU utilization monitoring. Early attempts were made to modify + an existing window manager (WindowMaker) which produced unsatisfactory + though inspiring results. The window managers vwm[12] were created + shortly after to flesh out the interaction model and solidify a tolerably + usable and easily modified window manager foundation, while libvmon was + created in parallel to facilitate the lightweight, high-frequency process + monitoring required for such a task. + + After a number of iterations it was found that the Composite extension + (along with the accompanying Damage and Render extensions) would give the + best results on a modern Xorg linux system. Compositing hurts the + rendering performance of X applications significantly however, so a + hybrid model has been employed. + + Monitoring overlays visibility is toggled using Mod1-Semicolon, the + sample rate is increased using Mod1-Right, and decreased using Mod1-Left. + + When the monitoring is not visible, vwm3 continues to leave X in + immediate rendering mode with no additional overhead in the rendering + pipeline, just like vwm2. The only additional overhead is the cost of + regularly sampling process statistics and maintaining the state of window + overlays (which does involve some X rendering of graphs and text, but + does not introduce overhead to the drawing of client windows). When monitoring overlays are made visible vwm3 temporarily becomes a - compositing manager, redirecting the rendering of all windows to offscreen - memory and assuming the responsibility of drawing all damaged contents to the - root window on their behalf. This is what gives vwm3 the opportunity to draw - alpha-blended contextual monitoring data over all of the windows, but it does - come with a cost. - - Most modern GNU/Linux desktop environments are full-time composited, meaning - all X clients are redirected at all times. This makes their draws more - costly and latent due to all the additional copies being performed. - Depending on how things have been implemented, in the interests of supporting - things like transparent windows it also generally results in overlapping - window regions being drawn repeatedly for every overlapping window from the - bottom-up rather than just the top one. - - In vwm3 transparent windows are not supported, and shape windows (xeyes) are - made rectangular in composited mode. This is so overlapping regions are only - drawn once for the top windows having damage per burst of screen updates. - - Immediate rendering mode is restored upon disabling the monitoring overlays, - restoring the drawing performance to vwm[12] levels where vwm3 is completely - out of the drawing loop. + compositing manager, redirecting the rendering of all windows to + offscreen memory and assuming the responsibility of drawing all damaged + contents to the root window on their behalf. This is what gives vwm3 the + opportunity to draw alpha-blended contextual monitoring data over all of + the windows, but it does come with a cost. + + Most modern GNU/Linux desktop environments are full-time composited, + meaning all X clients are redirected at all times. This makes their + draws more costly and latent due to all the additional copies being + performed. Depending on how things have been implemented, in the + interests of supporting things like transparent windows it also generally + results in overlapping window regions being drawn repeatedly for every + overlapping window from the bottom-up rather than just the top one. + + In vwm3 transparent windows are not supported, and shape windows (xeyes) + are made rectangular in composited mode. This is so overlapping regions + are only drawn once for the top windows having damage per burst of screen + updates. + + Immediate rendering mode is restored upon disabling the monitoring + overlays, restoring the drawing performance to vwm[12] levels where vwm3 + is completely out of the drawing loop. Here are some relevant things worth noting: @@ -231,157 +365,167 @@ Composite/Monitoring: the explicitly monitored client precesses. - tmux orphans its backend process immediately at startup, discarding its - parent->child relationship, so you don't get any monitoring of the commands - running in your local tmux session. Use GNU screen instead. + parent->child relationship, so you don't get any monitoring of the + commands running in your local tmux session. Use GNU screen instead. - - GNU screen orphans its backend on detach, so on reattach you've lost the - parent->child relationship and find yourself in the same situation tmux - puts you in immediately. I've developed an adopt() system call patch for - the linux kernel to enable adopting orphans in this situation, but it - hasn't been accepted. With this patch and a one line change to GNU screen - the parent->child relationship is restored on reattach. + - GNU screen orphans its backend on detach, so on reattach you've lost + the parent->child relationship and find yourself in the same situation + tmux puts you in immediately. I've developed an adopt() system call + patch for the linux kernel to enable adopting orphans in this + situation, but it hasn't been accepted. With this patch and a one line + change to GNU screen the parent->child relationship is restored on + reattach. - You may find patches for adding the adopt() system call to Linux and its - integration into GNU screen in the patches/ subdirectory. + You may find patches for adding the adopt() system call to Linux and + its integration into GNU screen in the patches/ subdirectory. - The top row of the overlays shows: Total CPU Idle % (cyan): - The height of every cyan vertical line reflects the %age of ticks since - the previous sample which were spent in the idle task. + The height of every cyan vertical line reflects the %age of ticks + since the previous sample which were spent in the idle task. Total CPU IOWait % (red): - The height of every red vertical line reflects the %age of ticks since - the previous sample which were lost to IO wait. Many people don't - understand this correctly. This reflects opportunities to execute - something other than the idle task which were lost because _all_ - runnable tasks at the time were blocked in IO. + The height of every red vertical line reflects the %age of ticks + since the previous sample which were lost to IO wait. Many people + don't understand this correctly. This reflects opportunities to + execute something other than the idle task which were lost because + _all_ runnable tasks at the time were blocked in IO. An absence of IOWait does not mean nothing is blocked on IO. It just - means there weren't opportunities to execute something which were lost due - to waiting on IO. + means there weren't opportunities to execute something which were + lost due to waiting on IO. - For example, lets say you have a dual core machine, and you launch two - "yes > /dev/null &" commands. These two yes commands are essentially busy - loops writing "yes" to /dev/null, they will always be runnable, and you - will see a top row in the overlay devoid of any cyan _or_red_ while they - execute. + For example, lets say you have a dual core machine, and you launch + two "yes > /dev/null &" commands. These two yes commands are + essentially busy loops writing "yes" to /dev/null, they will always + be runnable, and you will see a top row in the overlay devoid of any + cyan _or_red_ while they execute. While they execute run something like: "sudo echo 2 > /proc/sys/vm/drop_caches && du /" - Still no IOWait in the top row. We know that du is blocking on IO, the - caches are empty, but because there is always something runnable on the - two cores thanks to the two yes commands, we'll never see IOWait. The - other runnable processes mask the IOWait from our visibility. + Still no IOWait in the top row. We know that du is blocking on IO, + the caches are empty, but because there is always something runnable + on the two cores thanks to the two yes commands, we'll never see + IOWait. The other runnable processes mask the IOWait from our + visibility. Now kill the two yes commands and rerun the du command, watch the top - row. Some red should appear, the red indicates that there was CPU time - available for running something, and the _only_ things available for that - time was blocked in IO. Had there something else runnable, we wouldn't - see the time lost to IOWait. + row. Some red should appear, the red indicates that there was CPU + time available for running something, and the _only_ things available + for that time was blocked in IO. Had there something else runnable, + we wouldn't see the time lost to IOWait. When you see IOWait time, it's just saying nothing executed for that - time, not for lack of any runnable tasks, just that all runnable tasks - were blocked. It's still of value, but easily obscured on a system with - any cpu-bound tasks constantly running. + time, not for lack of any runnable tasks, just that all runnable + tasks were blocked. It's still of value, but easily obscured on a + system with any cpu-bound tasks constantly running. - The per-task (processes or threads) rows of the overlays show: User CPU % (cyan): - The height of every cyan vertical line reflects the %age of ticks since - the previous sample which were spent executing this task in the user - context. + The height of every cyan vertical line reflects the %age of ticks + since the previous sample which were spent executing this task in the + user context. System CPU % (red): - The height of every red vertical line reflects the %age of ticks since - the previous sample which were spent executing this task in kernel/system - context. - - Task monitoring beginning and ending is indicated with solid and checkered - vertical bars, respectively. These generally coincide with the task clone - and exit, but not always, especially the cloning. - - - You can pause sampling by lowering its rate (Mod1-Left) to 0Hz. Just be - aware that this also pauses the updating of the overlay contents, so window - resizes won't be adapted to in the overlay until increasing the sample rate - (Mod1-Right). Pausing is useful if you've seen something interesting and - would like to screenshot, study, or discuss. BTW, to take a screenshot - when composited you have to capture the root window. If you capture the - client window, you won't get the overlays, you'll just get the redirected - window contents. Compositing mode composites everything into the root - window, when you're interacting with the composited vwm3, you're looking at - the root window the entire time. - - - The sample rate will automatically be lowered by vwm3 when it detects that - it's having trouble maintaining the current one. If you have many windows - or have a slow or heavily loaded processor/GPU they can become bottlenecks, - especially at higher sample rates. Rather than continuing to bog down your - X server (Xorg is not good at fairly scheduling clients under GPU - contention), vwm3 will simply back off the sample rate as if you had hit - Mod1-Left, to try ameliorate rather than exacerbate the situation. + The height of every red vertical line reflects the %age of ticks + since the previous sample which were spent executing this task in + kernel/system context. + + Task monitoring beginning and ending is indicated with solid and + checkered vertical bars, respectively. These generally coincide with + the task clone and exit, but not always, especially the cloning. + + - You can pause sampling by lowering its rate (Mod1-Left) to 0Hz. Just + be aware that this also pauses the updating of the overlay contents, so + window resizes won't be adapted to in the overlay until increasing the + sample rate (Mod1-Right). Pausing is useful if you've seen something + interesting and would like to screenshot, study, or discuss. BTW, to + take a screenshot when composited you have to capture the root window. + If you capture the client window, you won't get the overlays, you'll + just get the redirected window contents. Compositing mode composites + everything into the root window, when you're interacting with the + composited vwm3, you're looking at the root window the entire time. + + - The sample rate will automatically be lowered by vwm3 when it detects + that it's having trouble maintaining the current one. If you have many + windows or have a slow or heavily loaded processor/GPU they can become + bottlenecks, especially at higher sample rates. Rather than continuing + to bog down your X server (Xorg is not good at fairly scheduling + clients under GPU contention), vwm3 will simply back off the sample + rate as if you had hit Mod1-Left, to try ameliorate rather than + exacerbate the situation. - The monitoring is implemented using sampling, not tracing. Below the current process heirarchy for every window there is an exited tasks - snowflakes section filling the remaining space. Do not mistake this for - something lossless like bash history or strace output, it's lossy since - it's produced from sampled data. In part to try avoid interpretation of - these as a reliable process history I refer to them as snowflakes in the - code, since they fall downwards and sideways. + snowflakes section filling the remaining space. Do not mistake this + for something lossless like bash history or strace output, it's lossy + since it's produced from sampled data. In part to try avoid + interpretation of these as a reliable process history I refer to them + as snowflakes in the code, since they fall downwards and sideways. With sufficiently high sample rates the output starts to take on the - appearance of tracing, and while it may happen to capture every process in - slower executions, most automata will execute entire commands in the time - between samples. So try keep this in mind before thinking something's - broken because you don't see something you expected in the snowflakes. + appearance of tracing, and while it may happen to capture every process + in slower executions, most automata will execute entire commands in the + time between samples. So try keep this in mind before thinking + something's broken because you don't see something you expected in the + snowflakes. Some artifacts you might notice due to this which are not bugs are: - "#missed it!" being shown as the command name, this happens when - libvmon caught the process but the process exited before libvmon caught - a sample of the name. + libvmon caught the process but the process exited before libvmon + caught a sample of the name. - A parent's command name in the child when a different command was executed. In UNIX systems processes fork before execing the new command, in that window of time between the fork and exec, the child - process is a clone of the parent, command and argv included. Sometimes - the sample catches at just the right moment to see this in action. + process is a clone of the parent, command and argv included. + Sometimes the sample catches at just the right moment to see this in + action. - Varying outputs in seeming identical actions. Things like simply - launching xterm may produce no snowflakes at all in the new xterm, or a - few like "bash" "dircolors -b" and "utempter add :0", especially if you - have the sample rate turned up and cause some load on the system to slow - the xterm and interactive bash startup scripts down. + launching xterm may produce no snowflakes at all in the new xterm, + or a few like "bash" "dircolors -b" and "utempter add :0", + especially if you have the sample rate turned up and cause some load + on the system to slow the xterm and interactive bash startup scripts + down. - - In the interests of being efficient, nothing is being logged historically. - The snowflakes area is all you get, which is limited to the free pixel - space below the instantaneous process heirarchy within the window. + - In the interests of being efficient, nothing is being logged + historically. The snowflakes area is all you get, which is limited to + the free pixel space below the instantaneous process heirarchy within + the window. - Everything which falls off the edges of the screen is lost forever, with - the exception of windows which have been made smaller than they were. + Everything which falls off the edges of the screen is lost forever, + with the exception of windows which have been made smaller than they + were. - You cannot scroll down or to the right to see older snowflakes or graphs. + You cannot scroll down or to the right to see older snowflakes or + graphs. You cannot search the snowflakes. - The native text and numeric representations of the sampled data is not kept - any longer than the current sample, just long enough to update the + The native text and numeric representations of the sampled data is not + kept any longer than the current sample, just long enough to update the overlays. From that point on the information exists only as visualized - pixels in the overlay layers with no additional indexing or relationships - being maintained with the currently monitored state. + pixels in the overlay layers with no additional indexing or + relationships being maintained with the currently monitored state. - You can wipe the snowflakes of the focused window with Mod1-Apostrophe - - The client PID is found via the _NET_WM_PID X property. This must be set - by the client, and not all clients cooperate (xpdf is one I've noticed). + - The client PID is found via the _NET_WM_PID X property. This must be + set by the client, and not all clients cooperate (xpdf is one I've + noticed). - This is annoying especially considering the vast majority of X clients run - on modern systems are local clients connected via UNIX domain sockets. - These sockets support ancillary messages including SCM_CREDENTIALS, which - contains the pid of the connected process. Some investigation into the - Xorg sources found it already queries this information and has it on hand, - but doesn't bother setting the _NET_WM_PID property even though it's well- - positioned to do so. + This is annoying especially considering the vast majority of X clients + run on modern systems are local clients connected via UNIX domain + sockets. These sockets support ancillary messages including + SCM_CREDENTIALS, which contains the pid of the connected process. Some + investigation into the Xorg sources found it already queries this + information and has it on hand, but doesn't bother setting the + _NET_WM_PID property even though it's well- positioned to do so. I've developed and submitted upstream a patch for Xorg which sets _NET_WM_PID on local connections, it complements vwm3 nicely. @@ -389,11 +533,11 @@ Composite/Monitoring: You can find the patch in the patches directory. - There are around 5 files kept open in /proc for every task monitored by - vwm. This applies to children processes and threads alike, so on a busy - system it's not unrealistic to exceed 1024, a common default open files - ulimit for GNU/Linux distributions. You can generally change this limit - for your user via configuration in /etc/security/limits.conf or - /etc/security/limits.d/. + vwm. This applies to children processes and threads alike, so on a + busy system it's not unrealistic to exceed 1024, a common default open + files ulimit for GNU/Linux distributions. You can generally change + this limit for your user via configuration in /etc/security/limits.conf + or /etc/security/limits.d/. TODO finish and polish this readme... diff --git a/src/colors.def b/src/colors.def index 97c0dc9..7c71da0 100644 --- a/src/colors.def +++ b/src/colors.def @@ -1,6 +1,3 @@ -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/src/context.c b/src/context.c index 32d7b3f..255480c 100644 --- a/src/context.c +++ b/src/context.c @@ -15,89 +15,163 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - /* desktop/shelf context handling */ + /* contexts (this is derived from desktops) */ +#include <assert.h> #include <X11/Xlib.h> +#include <stdlib.h> +#include <string.h> +#include "list.h" #include "context.h" #include "desktop.h" #include "vwm.h" #include "xwindow.h" -#include "window.h" -/* switch to the desired context if it isn't already the focused one, inform caller if anything happened */ -int vwm_context_focus(vwm_t *vwm, vwm_context_t desired_context) +/* make the specified context the most recently used one */ +vwm_context_t * vwm_context_mru(vwm_t *vwm, vwm_context_t *context) { - vwm_context_t entry_context = vwm->focused_context; - - switch (vwm->focused_context) { - vwm_xwindow_t *xwin; - vwm_window_t *vwin; - - case VWM_CONTEXT_SHELF: - if (desired_context == VWM_CONTEXT_SHELF) - break; - - /* desired == DESKTOP && focused == SHELF */ - - VWM_TRACE("unmapping shelf window \"%s\"", vwm->focused_shelf->xwindow->name); - vwm_win_unmap(vwm, vwm->focused_shelf); - XFlush(VWM_XDISPLAY(vwm)); /* for a more responsive feel */ - - /* map the focused desktop, from the top of the stack down */ - list_for_each_entry_prev(xwin, &vwm->xwindows, xwindows) { - if (!(vwin = xwin->managed)) - continue; - - if (vwin->desktop == vwm->focused_desktop && !vwin->shelved) { - VWM_TRACE("Mapping desktop window \"%s\"", xwin->name); - vwm_win_map(vwm, vwin); - } - } - - if (vwm->focused_desktop->focused_window) { - VWM_TRACE("Focusing \"%s\"", vwm->focused_desktop->focused_window->xwindow->name); - XSetInputFocus(VWM_XDISPLAY(vwm), vwm->focused_desktop->focused_window->xwindow->id, RevertToPointerRoot, CurrentTime); - } - - vwm->focused_context = VWM_CONTEXT_DESKTOP; - break; - - case VWM_CONTEXT_DESKTOP: - /* unmap everything, map the shelf */ - if (desired_context == VWM_CONTEXT_DESKTOP) - break; - - /* desired == SHELF && focused == DESKTOP */ - - /* there should be a focused shelf if the shelf contains any windows, we NOOP the switch if the shelf is empty. */ - if (vwm->focused_shelf) { - /* unmap everything on the current desktop */ - list_for_each_entry(xwin, &vwm->xwindows, xwindows) { - if (!(vwin = xwin->managed)) - continue; - - if (vwin->desktop == vwm->focused_desktop) { - VWM_TRACE("Unmapping desktop window \"%s\"", xwin->name); - vwm_win_unmap(vwm, vwin); - } - } - - XFlush(VWM_XDISPLAY(vwm)); /* for a more responsive feel */ - - VWM_TRACE("Mapping shelf window \"%s\"", vwm->focused_shelf->xwindow->name); - vwm_win_map(vwm, vwm->focused_shelf); - vwm_win_focus(vwm, vwm->focused_shelf); - - vwm->focused_context = VWM_CONTEXT_SHELF; - } - break; - - default: - VWM_BUG("unexpected focused context %x", vwm->focused_context); - break; + VWM_TRACE("MRU context: %p", context); + list_move(&context->contexts_mru, &vwm->contexts_mru); + + return context; +} + + +/* return next MRU context relative to the supplied context */ +vwm_context_t * vwm_context_next_mru(vwm_t *vwm, vwm_context_t *context, vwm_direction_t direction) +{ + list_head_t *next; + + switch (direction) { + case VWM_DIRECTION_FORWARD: + if (context->contexts_mru.next == &vwm->contexts_mru) { + next = context->contexts_mru.next->next; + } else { + next = context->contexts_mru.next; + } + break; + + case VWM_DIRECTION_REVERSE: + if (context->contexts_mru.prev == &vwm->contexts_mru) { + next = context->contexts_mru.prev->prev; + } else { + next = context->contexts_mru.prev; + } + break; + + default: + assert(0); + } + + return list_entry(next, vwm_context_t, contexts_mru); +} + + +/* return next context spatially relative to the supplied context, no wrap-around */ +vwm_context_t * vwm_context_next(vwm_t *vwm, vwm_context_t *context, vwm_direction_t direction) +{ + switch (direction) { + case VWM_DIRECTION_FORWARD: + if (context->contexts.next != &vwm->contexts) + context = list_entry(context->contexts.next, vwm_context_t, contexts); + break; + + case VWM_DIRECTION_REVERSE: + if (context->contexts.prev != &vwm->contexts) + context = list_entry(context->contexts.prev, vwm_context_t, contexts); + break; + } + + return context; +} + + +/* helper for automatically assigning context colors */ +static int next_context_color_idx(vwm_t *vwm) +{ + int counts[VWM_CONTEXT_COLOR_MAX] = {}; + vwm_context_t *context; + int color = 0; + + /* TODO: contexts should probably keep a window count, + * so this could skip empty contexts, then those + * would be automatically recycled. + */ + list_for_each_entry(context, &vwm->contexts, contexts) + counts[context->color]++; + + for (int i = 0; i < NELEMS(counts); i++) { + if (counts[i] < counts[color]) + color = i; + } + + return color; +} + + +/* create a context */ +/* if color = -1 one is automatically assigned, + * otherwise the supplied color is used. + */ +vwm_context_t * vwm_context_create(vwm_t *vwm, int color, vwm_desktop_t *desktop) +{ + vwm_context_t *context; + + context = calloc(1, sizeof(vwm_context_t)); + if (context == NULL) { + VWM_PERROR("Failed to allocate context"); + goto _fail; + } + + if (color < 0) + color = next_context_color_idx(vwm); + + assert(color < NELEMS(vwm->context_colors)); + + context->color = color; + + list_add_tail(&context->contexts, &vwm->contexts); + list_add_tail(&context->contexts_mru, &vwm->contexts_mru); + + if (!desktop) + desktop = vwm_desktop_create(vwm, context); + + context->focused_desktop = desktop; + + return context; + +_fail: + return NULL; +} + + +/* destroy a context */ +void vwm_context_destroy(vwm_t *vwm, vwm_context_t *context) +{ + /* silently refuse to destroy a context having windows (for now) */ + /* there's _always_ a focused window on a context having mapped windows */ + if (context->focused_desktop && context->focused_desktop->focused_window) + return; + + /* also silently refuse to destroy the last context (for now) */ + if (context->contexts.next == context->contexts.prev) + return; + + list_del(&context->contexts); + list_del(&context->contexts_mru); +} + + +/* find a context by color, creating one if needed */ +vwm_context_t * vwm_context_by_color(vwm_t *vwm, unsigned color) +{ + vwm_context_t *context; + + list_for_each_entry(context, &vwm->contexts, contexts) { + if (context->color == color) + return context; } - /* return if the context has been changed, the caller may need to branch differently if nothing happened */ - return (vwm->focused_context != entry_context); + return vwm_context_create(vwm, color, NULL); } diff --git a/src/context.h b/src/context.h index 1604bd3..5eebc43 100644 --- a/src/context.h +++ b/src/context.h @@ -1,14 +1,27 @@ #ifndef _CONTEXT_H #define _CONTEXT_H +#include "direction.h" +#include "list.h" + typedef struct _vwm_t vwm_t; +typedef struct _vwm_desktop_t vwm_desktop_t; -typedef enum _vwm_context_t { - VWM_CONTEXT_DESKTOP = 0, /* focus the desktop context */ - VWM_CONTEXT_SHELF, /* focus the shelf context */ - VWM_CONTEXT_OTHER /* focus the other context relative to the current one */ +/* contexts and desktops are *very* similar, they should likely share code, + * simply duplicating for now. + */ +typedef struct _vwm_context_t { + list_head_t contexts; /* global list of contexts in spatial created-in order */ + list_head_t contexts_mru; /* global list of contexts in MRU order */ + vwm_desktop_t *focused_desktop; /* the focused desktop on this context */ + unsigned color; /* color used for focused border on this context */ } vwm_context_t; -int vwm_context_focus(vwm_t *vwm, vwm_context_t desired_context); +vwm_context_t * vwm_context_mru(vwm_t *vwm, vwm_context_t *context); +vwm_context_t * vwm_context_create(vwm_t *vwm, int color, vwm_desktop_t *desktop); +void vwm_context_destroy(vwm_t *vwm, vwm_context_t *context); +vwm_context_t * vwm_context_next_mru(vwm_t *vwm, vwm_context_t *context, vwm_direction_t direction); +vwm_context_t * vwm_context_next(vwm_t *vwm, vwm_context_t *context, vwm_direction_t direction); +vwm_context_t * vwm_context_by_color(vwm_t *vwm, unsigned color); #endif diff --git a/src/desktop.c b/src/desktop.c index 8abc4d6..d88fa93 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -33,26 +33,27 @@ vwm_desktop_t * vwm_desktop_mru(vwm_t *vwm, vwm_desktop_t *desktop) { VWM_TRACE("MRU desktop: %p", desktop); list_move(&desktop->desktops_mru, &vwm->desktops_mru); + vwm_context_mru(vwm, desktop->context); return desktop; } /* focus a virtual desktop */ -/* this switches to the desktop context if necessary, maps and unmaps windows accordingly if necessary */ +/* this updates the focused context if necessary, maps and unmaps windows accordingly if necessary */ int vwm_desktop_focus(vwm_t *vwm, vwm_desktop_t *desktop) { XGrabServer(VWM_XDISPLAY(vwm)); XSync(VWM_XDISPLAY(vwm), False); - /* if the context switched and the focused desktop is the desired desktop there's nothing else to do */ - if ((vwm_context_focus(vwm, VWM_CONTEXT_DESKTOP) && vwm->focused_desktop != desktop) || vwm->focused_desktop != desktop) { + if (vwm->focused_desktop != desktop) { vwm_xwindow_t *xwin; vwm_window_t *vwin; /* unmap the windows on the currently focused desktop, map those on the newly focused one */ list_for_each_entry(xwin, &vwm->xwindows, xwindows) { - if (!(vwin = xwin->managed) || vwin->shelved) + vwin = xwin->managed; + if (!vwin) continue; if (vwin->desktop == vwm->focused_desktop) @@ -62,7 +63,8 @@ int vwm_desktop_focus(vwm_t *vwm, vwm_desktop_t *desktop) XFlush(VWM_XDISPLAY(vwm)); list_for_each_entry_prev(xwin, &vwm->xwindows, xwindows) { - if (!(vwin = xwin->managed) || vwin->shelved) + vwin = xwin->managed; + if (!vwin) continue; if (vwin->desktop == desktop) @@ -70,6 +72,7 @@ int vwm_desktop_focus(vwm_t *vwm, vwm_desktop_t *desktop) } vwm->focused_desktop = desktop; + desktop->context->focused_desktop = desktop; } /* directly focus the desktop's focused window if there is one, we don't use vwm_win_focus() intentionally XXX */ @@ -84,53 +87,67 @@ int vwm_desktop_focus(vwm_t *vwm, vwm_desktop_t *desktop) } -/* return next MRU desktop relative to the supplied desktop, wraps-around */ +/* return next MRU desktop within the same context relative to the supplied desktop, wraps-around */ vwm_desktop_t * vwm_desktop_next_mru(vwm_t *vwm, vwm_desktop_t *desktop, vwm_direction_t direction) { vwm_desktop_t *next = desktop; - /* this dance is necessary because the list head @ vwm->desktops_mru has no vwm_desktop_t container, - * and we're exploiting the circular nature of the doubly linked lists, so we need to take care to skip - * past the container-less head. - */ - switch (direction) { - case VWM_DIRECTION_FORWARD: - if (next->desktops_mru.next == &vwm->desktops_mru) { - next = list_entry(next->desktops_mru.next->next, vwm_desktop_t, desktops_mru); - } else { - next = list_entry(next->desktops_mru.next, vwm_desktop_t, desktops_mru); - } - break; + do { + /* this dance is necessary because the list head @ vwm->desktops_mru has no vwm_desktop_t container, + * and we're exploiting the circular nature of the doubly linked lists, so we need to take care to skip + * past the container-less head. + */ + switch (direction) { + case VWM_DIRECTION_FORWARD: + if (next->desktops_mru.next == &vwm->desktops_mru) { + next = list_entry(next->desktops_mru.next->next, vwm_desktop_t, desktops_mru); + } else { + next = list_entry(next->desktops_mru.next, vwm_desktop_t, desktops_mru); + } + break; - case VWM_DIRECTION_REVERSE: - if (next->desktops_mru.prev == &vwm->desktops_mru) { - next = list_entry(next->desktops_mru.prev->prev, vwm_desktop_t, desktops_mru); - } else { - next = list_entry(next->desktops_mru.prev, vwm_desktop_t, desktops_mru); - } - break; + case VWM_DIRECTION_REVERSE: + if (next->desktops_mru.prev == &vwm->desktops_mru) { + next = list_entry(next->desktops_mru.prev->prev, vwm_desktop_t, desktops_mru); + } else { + next = list_entry(next->desktops_mru.prev, vwm_desktop_t, desktops_mru); + } + break; - default: - assert(0); - } + default: + assert(0); + } + } while (next->context != desktop->context); return next; } -/* return next desktop spatially relative to the supplied desktop, no wrap-around */ +/* return next in-context desktop spatially relative to the supplied desktop, no wrap-around */ vwm_desktop_t * vwm_desktop_next(vwm_t *vwm, vwm_desktop_t *desktop, vwm_direction_t direction) { switch (direction) { - case VWM_DIRECTION_FORWARD: - if (desktop->desktops.next != &vwm->desktops) - return list_entry(desktop->desktops.next, vwm_desktop_t, desktops); + case VWM_DIRECTION_FORWARD: { + vwm_desktop_t *next = desktop; + + while (next->desktops.next != &vwm->desktops) { + next = list_entry(next->desktops.next, vwm_desktop_t, desktops); + if (next->context == desktop->context) + return next; + } break; + } - case VWM_DIRECTION_REVERSE: - if (desktop->desktops.prev != &vwm->desktops) - return list_entry(desktop->desktops.prev, vwm_desktop_t, desktops); + case VWM_DIRECTION_REVERSE: { + vwm_desktop_t *next = desktop; + + while (next->desktops.prev != &vwm->desktops) { + next = list_entry(next->desktops.prev, vwm_desktop_t, desktops); + if (next->context == desktop->context) + return next; + } break; + } default: assert(0); @@ -139,24 +156,39 @@ vwm_desktop_t * vwm_desktop_next(vwm_t *vwm, vwm_desktop_t *desktop, vwm_directi return desktop; } -/* create a virtual desktop */ -vwm_desktop_t * vwm_desktop_create(vwm_t *vwm) + +/* TODO: when "sending" windows to contexts, I currently always create a + * new desktop with this function. There should really be a "create if needed" + * variant which just returns an empty desktop in the target context, creating + * only if no empty one already exists, for "sending" purposes. The current + * approach tends to litter empty desktops unnecessarily. + */ +/* create a virtual desktop on the supplied context, + * if context is NULL a new one is created + * the desktop becomes the context's focused desktop if there isn't already one + */ +vwm_desktop_t * vwm_desktop_create(vwm_t *vwm, vwm_context_t *context) { vwm_desktop_t *desktop; desktop = calloc(1, sizeof(vwm_desktop_t)); if (desktop == NULL) { VWM_PERROR("Failed to allocate desktop"); - goto _fail; + return NULL; } + if (!context) + context = vwm_context_create(vwm, -1, desktop); + + if (!context->focused_desktop) + context->focused_desktop = desktop; + + desktop->context = context; + list_add_tail(&desktop->desktops, &vwm->desktops); list_add_tail(&desktop->desktops_mru, &vwm->desktops_mru); return desktop; - -_fail: - return NULL; } @@ -169,16 +201,31 @@ void vwm_desktop_destroy(vwm_t *vwm, vwm_desktop_t *desktop) if (desktop->focused_window || (desktop->desktops.next == desktop->desktops.prev)) return; - /* focus the MRU desktop that isn't this one if we're the focused desktop */ - if (desktop == vwm->focused_desktop) { + /* focus the desktop context's MRU desktop that isn't this one, + * if desktop is the context's focused desktop */ + if (desktop == desktop->context->focused_desktop) { vwm_desktop_t *next_desktop; list_for_each_entry(next_desktop, &vwm->desktops_mru, desktops_mru) { - if (next_desktop != desktop) { - vwm_desktop_focus(vwm, next_desktop); + if (next_desktop != desktop && next_desktop->context == desktop->context) { + desktop->context->focused_desktop = next_desktop; break; } } + + /* if *still* the context's focused desktop, the context is finished. + * find a desktop from the next MRU context to focus if this desktop + * was the vwm focused one before destroying the context + */ + if (desktop == desktop->context->focused_desktop) { + if (desktop == vwm->focused_desktop) + next_desktop = vwm_context_next_mru(vwm, desktop->context, VWM_DIRECTION_FORWARD)->focused_desktop; + + vwm_context_destroy(vwm, desktop->context); + } + + if (desktop == vwm->focused_desktop) + vwm_desktop_focus(vwm, next_desktop); } list_del(&desktop->desktops); diff --git a/src/desktop.h b/src/desktop.h index 1503407..597adb8 100644 --- a/src/desktop.h +++ b/src/desktop.h @@ -7,16 +7,18 @@ typedef struct _vwm_t vwm_t; typedef struct _vwm_window_t vwm_window_t; +typedef struct _vwm_context_t vwm_context_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 */ + vwm_context_t *context; /* context this desktop belongs to */ vwm_window_t *focused_window; /* the focused window on this virtual desktop */ } vwm_desktop_t; vwm_desktop_t * vwm_desktop_mru(vwm_t *vwm, vwm_desktop_t *desktop); int vwm_desktop_focus(vwm_t *vwm, vwm_desktop_t *desktop); -vwm_desktop_t * vwm_desktop_create(vwm_t *vwm); +vwm_desktop_t * vwm_desktop_create(vwm_t *vwm, vwm_context_t *context); void vwm_desktop_destroy(vwm_t *vwm, vwm_desktop_t *desktop); vwm_desktop_t * vwm_desktop_next_mru(vwm_t *vwm, vwm_desktop_t *desktop, vwm_direction_t direction); vwm_desktop_t * vwm_desktop_next(vwm_t *vwm, vwm_desktop_t *desktop, vwm_direction_t direction); @@ -31,6 +31,7 @@ static int key_is_grabbed; /* flag for tracking keyboard grab state */ static vwm_direction_t direction = VWM_DIRECTION_FORWARD; /* flag for reversing directional actions */ +static int send_it; /* flag for "sending" a migration operation without following it */ /* Poll the keyboard state to see if _any_ keys are pressed */ static int keys_pressed(vwm_t *vwm) @@ -75,8 +76,7 @@ void vwm_key_released(vwm_t *vwm, Window win, XKeyReleasedEvent *keyrelease) vwm_win_mru(vwm, vwin); /* make the focused desktop the most recently used */ - if (vwm->focused_context == VWM_CONTEXT_DESKTOP && vwm->focused_desktop) - vwm_desktop_mru(vwm, vwm->focused_desktop); + vwm_desktop_mru(vwm, vwm->focused_desktop); break; @@ -85,6 +85,11 @@ void vwm_key_released(vwm_t *vwm, Window win, XKeyReleasedEvent *keyrelease) direction = VWM_DIRECTION_FORWARD; break; + case XK_s: + VWM_TRACE("XK_s released with send_it=%i", send_it); + send_it = 0; + break; + default: VWM_TRACE("Unhandled keycode: %x", (unsigned int)sym); break; @@ -144,9 +149,32 @@ void vwm_key_pressed(vwm_t *vwm, Window win, XKeyPressedEvent *keypress) direction = VWM_DIRECTION_REVERSE; break; - case XK_grave: /* toggle shelf visibility */ - vwm_context_focus(vwm, VWM_CONTEXT_OTHER); + case XK_s: /* "send" migrational actions */ + VWM_TRACE("XK_s pressed with send_it=%i", send_it); + send_it = 1; + break; + + case XK_grave: { /* cycle focused desktop by context */ + vwm_context_t *next_context; + + do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ + next_context = vwm_context_next_mru(vwm, vwm->focused_desktop->context, direction); + + if (send_it && (keypress->state & ShiftMask)) { /* "send" the focused window to the MRU context's MRU desktop */ + if (vwin) + vwm_win_send(vwm, vwin, vwm_desktop_mru(vwm, next_context->focused_desktop)); + } else if (send_it) { /* "send" the focused window to a new desktop created on the MRU context */ + if (vwin) + vwm_win_send(vwm, vwin, vwm_desktop_mru(vwm, vwm_desktop_create(vwm, next_context))); + } else if (keypress->state & ShiftMask) { + /* migrate the focused window with the desktop focus to the MRU context's MRU desktop */ + if (vwin) + vwm_win_migrate(vwm, vwin, next_context->focused_desktop); + } else { + vwm_desktop_focus(vwm, next_context->focused_desktop); + } break; + } case XK_Tab: /* cycle focused window */ do_grab = 1; /* update MRU window on commit (Mod1 release) */ @@ -154,22 +182,53 @@ void vwm_key_pressed(vwm_t *vwm, Window win, XKeyPressedEvent *keypress) /* focus the next window, note this doesn't affect MRU yet, that happens on Mod1 release */ if (vwin) { if (keypress->state & ShiftMask) { - vwm_win_focus_next(vwm, vwin, direction, VWM_FENCE_MASKED_VIOLATE); + /* TODO: in keeping with the Shift==migrate behavior, perhaps + * for Tab it should really do a in-desktop migration of sorts + * where the focused window swaps places with the next window? + */ + VWM_TRACE("in-desktop migrate not implemented yet"); } else { vwm_win_focus_next(vwm, vwin, direction, VWM_FENCE_RESPECT); } } break; + case XK_backslash: + do_grab = 1; + + if (vwin) { + if (keypress->state & ShiftMask) { + /* TODO: migrate window to another screen within this desktop, + * like VWM_FENCE_MASKED_VIOLATE would focus the next window on + * the next screen, but instead of focusing the next window on + * the next display, move the focused one to that next desktop. + * + * since screens are handled within vwm_win_focus_next() via + * the fence abstraction, but fences aren't exposed outside of + * their, it's non-trivial to implement here. I may want to + * break that out into a more public interface to make things + * more composable at the screen level. + */ + VWM_TRACE("migrate window to screen not implemented yet"); + } else { + vwm_win_focus_next(vwm, vwin, direction, VWM_FENCE_MASKED_VIOLATE); + } + } + break; + case XK_space: { /* cycle focused desktop utilizing MRU */ vwm_desktop_t *next_desktop; - next_desktop = vwm_desktop_next_mru(vwm, vwm->focused_desktop, direction); - do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ + next_desktop = vwm_desktop_next_mru(vwm, vwm->focused_desktop, direction); - if (keypress->state & ShiftMask) { - /* migrate the focused window with the desktop focus to the most recently used desktop */ + if (send_it && (keypress->state & ShiftMask)) { /* "send" the focused window to the MRU desktop */ + if (vwin) + vwm_win_send(vwm, vwin, vwm_desktop_mru(vwm, next_desktop)); + } else if (send_it) { /* "send" the focused window to a new desktop in the current context, kind of an alias of send_it+XK_v */ + if (vwin) + vwm_win_send(vwm, vwin, vwm_desktop_mru(vwm, vwm_desktop_create(vwm, vwin->desktop->context))); + } else if (keypress->state & ShiftMask) { /* migrate the focused window with the desktop focus to the most recently used desktop */ if (vwin) vwm_win_migrate(vwm, vwin, next_desktop); } else { @@ -185,7 +244,7 @@ void vwm_key_pressed(vwm_t *vwm, Window win, XKeyPressedEvent *keypress) } else { /* kindly destroy the focused window */ vwm_xwin_message(vwm, vwin->xwindow, vwm->wm_protocols_atom, vwm->wm_delete_atom); } - } else if (vwm->focused_context == VWM_CONTEXT_DESKTOP) { + } else { /* destroy the focused desktop when destroy occurs without any windows */ vwm_desktop_destroy(vwm, vwm->focused_desktop); } @@ -201,80 +260,111 @@ void vwm_key_pressed(vwm_t *vwm, Window win, XKeyPressedEvent *keypress) 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->state & ShiftMask) { - if (vwin) { - /* migrate the focused window to a newly created virtual desktop, focusing the new desktop simultaneously */ - vwm_win_migrate(vwm, vwin, vwm_desktop_create(vwm)); - } + if (send_it) { /* "send" the focused window to a newly created virtual desktop, */ + if (vwin) + vwm_win_send(vwm, vwin, vwm_desktop_mru(vwm, vwm_desktop_create(vwm, vwin->desktop->context))); + } else if (keypress->state & ShiftMask) { /* migrate the focused window to a newly created virtual desktop, focusing the new desktop simultaneously */ + if (vwin) + vwm_win_migrate(vwm, vwin, vwm_desktop_create(vwm, vwin->desktop->context)); } else { - vwm_desktop_focus(vwm, vwm_desktop_create(vwm)); - vwm_desktop_mru(vwm, vwm->focused_desktop); + vwm_desktop_focus(vwm, vwm_desktop_create(vwm, vwm->focused_desktop->context)); } break; - case XK_h: /* previous virtual desktop, if we're in the shelf context this will simply switch to desktop context */ + case XK_c: /* instantiate (and focus) a new (potentialy empty, unless migrating) virtual desktop in a new context */ do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ - if (keypress->state & ShiftMask) { - if (vwin) { - /* migrate the focused window with the desktop focus to the previous desktop */ - vwm_win_migrate(vwm, vwin, vwm_desktop_next(vwm, vwin->desktop, VWM_DIRECTION_REVERSE)); - } + if (send_it) { /* "send" the focused window to a newly created virtual desktop in a new context */ + if (vwin) + vwm_win_send(vwm, vwin, vwm_desktop_mru(vwm, vwm_desktop_create(vwm, NULL))); + } else if (keypress->state & ShiftMask) { /* migrate the focused window to a newly created virtual desktop in a new context, focusing the new desktop simultaneously */ + + if (vwin) + vwm_win_migrate(vwm, vwin, vwm_desktop_create(vwm, NULL)); } else { - if (vwm->focused_context == VWM_CONTEXT_SHELF) { - /* focus the focused desktop instead of the shelf */ - vwm_context_focus(vwm, VWM_CONTEXT_DESKTOP); - } else { - /* focus the previous desktop */ - vwm_desktop_focus(vwm, vwm_desktop_next(vwm, vwm->focused_desktop, VWM_DIRECTION_REVERSE)); - } + vwm_desktop_focus(vwm, vwm_desktop_create(vwm, NULL)); } break; - case XK_l: /* next virtual desktop, if we're in the shelf context this will simply switch to desktop context */ + case XK_0: + case XK_1: + case XK_2: + case XK_3: + case XK_4: + case XK_5: + case XK_6: + case XK_7: + case XK_8: + case XK_9: do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ - if (keypress->state & ShiftMask) { - if (vwin) { - /* migrate the focused window with the desktop focus to the next desktop */ - vwm_win_migrate(vwm, vwin, vwm_desktop_next(vwm, vwin->desktop, VWM_DIRECTION_FORWARD)); - } + if (send_it && (keypress->state & ShiftMask)) { /* "send" the focused window to the specified context */ + if (vwin) + vwm_win_send(vwm, vwin, vwm_desktop_mru(vwm, vwm_context_by_color(vwm, sym - XK_0)->focused_desktop)); + } else if (send_it) { /* "send" the focused window to a new desktop created on the specified context */ + if (vwin) + vwm_win_send(vwm, vwin, vwm_desktop_mru(vwm, vwm_desktop_create(vwm, vwm_context_by_color(vwm, sym - XK_0)))); + } else if (keypress->state & ShiftMask) { /* migrate the focused window to the specified context */ + if (vwin) + vwm_win_migrate(vwm, vwin, vwm_context_by_color(vwm, sym - XK_0)->focused_desktop); } else { - if (vwm->focused_context == VWM_CONTEXT_SHELF) { - /* focus the focused desktop instead of the shelf */ - vwm_context_focus(vwm, VWM_CONTEXT_DESKTOP); - } else { - /* focus the next desktop */ - vwm_desktop_focus(vwm, vwm_desktop_next(vwm, vwm->focused_desktop, VWM_DIRECTION_FORWARD)); - } + vwm_desktop_focus(vwm, vwm_context_by_color(vwm, sym - XK_0)->focused_desktop); + } + break; + + case XK_h: /* previous virtual desktop spatially */ + do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ + + if (send_it) { /* "send" the focused window to the previous desktop */ + if (vwin) + vwm_win_send(vwm, vwin, vwm_desktop_mru(vwm, vwm_desktop_next(vwm, vwin->desktop, VWM_DIRECTION_REVERSE))); + } else if (keypress->state & ShiftMask) { /* migrate the focused window with the desktop focus to the previous desktop */ + if (vwin) + vwm_win_migrate(vwm, vwin, vwm_desktop_next(vwm, vwin->desktop, VWM_DIRECTION_REVERSE)); + } else { /* focus the previous desktop */ + vwm_desktop_focus(vwm, vwm_desktop_next(vwm, vwm->focused_desktop, VWM_DIRECTION_REVERSE)); + } + break; + + case XK_l: /* next virtual desktop spatially */ + do_grab = 1; /* update MRU desktop on commit (Mod1 release) */ + + if (send_it) { /* "send" the focused window to the next desktop */ + if (vwin) + vwm_win_send(vwm, vwin, vwm_desktop_mru(vwm, vwm_desktop_next(vwm, vwin->desktop, VWM_DIRECTION_FORWARD))); + } else if (keypress->state & ShiftMask) { /* migrate the focused window with the desktop focus to the next desktop */ + if (vwin) + vwm_win_migrate(vwm, vwin, vwm_desktop_next(vwm, vwin->desktop, VWM_DIRECTION_FORWARD)); + } else { /* focus the next desktop */ + vwm_desktop_focus(vwm, vwm_desktop_next(vwm, vwm->focused_desktop, VWM_DIRECTION_FORWARD)); } break; - case XK_k: /* raise or shelve the focused window */ + case XK_k: /* raise or context-migrate the focused window up */ if (vwin) { - if (keypress->state & ShiftMask) { /* shelf the window and focus the shelf */ - if (vwm->focused_context != VWM_CONTEXT_SHELF) { - /* shelve the focused window while focusing the shelf */ - vwm_win_shelve(vwm, vwin); - vwm_context_focus(vwm, VWM_CONTEXT_SHELF); - } - } else { - do_grab = 1; + do_grab = 1; + + /* TODO: maybe bare send_it should create a new desktop in the next context, + * with Shift+send_it being the migrate-like send */ + if (send_it) { /* "send" the focused window to the next context */ + vwm_context_t *next_context = vwm_context_next(vwm, vwin->desktop->context, VWM_DIRECTION_FORWARD); + + vwm_win_send(vwm, vwin, vwm_desktop_mru(vwm, next_context->focused_desktop)); + } else if (keypress->state & ShiftMask) { /* migrate the window and focus the new context */ + vwm_context_t *next_context = vwm_context_next(vwm, vwin->desktop->context, VWM_DIRECTION_FORWARD); + vwm_win_migrate(vwm, vwin, next_context->focused_desktop); + } else { XRaiseWindow(VWM_XDISPLAY(vwm), vwin->xwindow->id); - if (repeat_cnt == 1) { - /* double: reraise & fullscreen */ + if (repeat_cnt == 1) { /* double: reraise & fullscreen */ vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); - } else if (repeat_cnt == 2) { - /* triple: reraise & fullscreen w/borders obscured by screen perimiter */ + } else if (repeat_cnt == 2) { /* triple: reraise & fullscreen w/borders obscured by screen perimiter */ vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_ALL); } else if (vwm->xinerama_screens_cnt > 1) { - if (repeat_cnt == 3) { - /* triple: reraise & fullscreen across all screens */ + if (repeat_cnt == 3) { /* triple: reraise & fullscreen across all screens */ vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_TOTAL, VWM_WIN_AUTOCONF_FULL); - } else if (repeat_cnt == 4) { - /* quadruple: reraise & fullscreen w/borders obscured by screen perimiter */ + } else if (repeat_cnt == 4) { /* quadruple: reraise & fullscreen w/borders obscured by screen perimiter */ vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_TOTAL, VWM_WIN_AUTOCONF_ALL); } } @@ -283,13 +373,20 @@ void vwm_key_pressed(vwm_t *vwm, Window win, XKeyPressedEvent *keypress) } break; - case XK_j: /* lower or unshelve the focused window */ + case XK_j: /* lower or context-migrate the focused window down */ if (vwin) { - if (keypress->state & ShiftMask) { /* unshelf the window to the focused desktop, and focus the desktop */ - if (vwm->focused_context == VWM_CONTEXT_SHELF) { - /* unshelve the focused window, focus the desktop it went to */ - vwm_win_migrate(vwm, vwin, vwm->focused_desktop); - } + do_grab = 1; + + /* TODO: maybe bare send_it should create a new desktop in the previous context, + * with Shift+send_it being the migrate-like send */ + if (send_it) { /* "send" the focused window to the previous context */ + vwm_context_t *prev_context = vwm_context_next(vwm, vwin->desktop->context, VWM_DIRECTION_REVERSE); + + vwm_win_send(vwm, vwin, vwm_desktop_mru(vwm, prev_context->focused_desktop)); + } else if (keypress->state & ShiftMask) { /* migrate the window and focus the new context */ + vwm_context_t *prev_context = vwm_context_next(vwm, vwin->desktop->context, VWM_DIRECTION_REVERSE); + + vwm_win_migrate(vwm, vwin, prev_context->focused_desktop); } else { if (vwin->autoconfigured == VWM_WIN_AUTOCONF_ALL) { vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_XWIN, VWM_WIN_AUTOCONF_FULL); @@ -311,12 +408,6 @@ void vwm_key_pressed(vwm_t *vwm, Window win, XKeyPressedEvent *keypress) } break; - case XK_s: /* shelve focused window */ - if (vwin && !vwin->shelved) - vwm_win_shelve(vwm, vwin); - - break; - case XK_bracketleft: /* reconfigure the focused window to occupy the left or top half of the screen or left quarters on repeat */ if (vwin) { do_grab = 1; diff --git a/src/screen.c b/src/screen.c index 32a0fc7..4449fb5 100644 --- a/src/screen.c +++ b/src/screen.c @@ -169,23 +169,20 @@ _out: int vwm_screen_is_empty(vwm_t *vwm, const vwm_screen_t *scr, vwm_xwindow_t *ignore_xwin) { vwm_xwindow_t *xwin; - int is_empty = 1; list_for_each_entry(xwin, &vwm->xwindows, xwindows) { if (xwin == ignore_xwin || !xwin->client_mapped) continue; - if (!xwin->managed || (xwin->managed->desktop == vwm->focused_desktop && !xwin->managed->shelved)) { + if (!xwin->managed || xwin->managed->desktop == vwm->focused_desktop) { /* XXX: it may make more sense to see what %age of the screen is overlapped by windows, and consider it empty if < some % */ /* This is just seeing if any window is predominantly within the specified screen, the rationale being if you had a focusable * window on the screen you would have used the keyboard to make windows go there; this function is only used in determining * wether a new window should go where the pointer is or not. */ - if (vwm_screen_overlaps_xwin(vwm, scr, xwin) >= 0.05) { - is_empty = 0; - break; - } + if (vwm_screen_overlaps_xwin(vwm, scr, xwin) >= 0.05) + return 0; } } - return is_empty; + return 1; } @@ -70,6 +70,8 @@ static vwm_t * vwm_startup(void) goto _err; } + INIT_LIST_HEAD(&vwm->contexts); + INIT_LIST_HEAD(&vwm->contexts_mru); INIT_LIST_HEAD(&vwm->desktops); INIT_LIST_HEAD(&vwm->desktops_mru); INIT_LIST_HEAD(&vwm->windows_mru); @@ -125,6 +127,11 @@ static vwm_t * vwm_startup(void) #include "colors.def" #undef color +#define color(_num, _str) \ + XAllocNamedColor(VWM_XDISPLAY(vwm), VWM_XCMAP(vwm), _str, &vwm->context_colors[_num], &vwm->context_colors[_num]); +#include "context_colors.def" +#undef color + XSelectInput(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), FocusChangeMask | PropertyChangeMask | SubstructureNotifyMask | SubstructureRedirectMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask); XGrabKey(VWM_XDISPLAY(vwm), AnyKey, WM_GRAB_MODIFIER, VWM_XROOT(vwm), False, GrabModeAsync, GrabModeAsync); @@ -133,8 +140,9 @@ static vwm_t * vwm_startup(void) XSetInputFocus(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), RevertToPointerRoot, CurrentTime); - /* create initial virtual desktop */ - vwm_desktop_focus(vwm, vwm_desktop_create(vwm)); + /* create initial contexts and desktop */ + vwm_desktop_create(vwm, NULL); /* shelf */ + vwm_desktop_focus(vwm, vwm_desktop_create(vwm, NULL)); /* general */ vwm_desktop_mru(vwm, vwm->focused_desktop); /* manage all preexisting windows */ @@ -32,6 +32,15 @@ typedef struct _vwm_window_t vwm_window_t; typedef struct _vwm_desktop_t vwm_desktop_t; +/* this contortion is currently just to get VWM_CONTEXT_COLOR_MAX defined */ +typedef enum _vwm_context_color_t { +#define color(_num, _str) \ + VWM_CONTEXT_COLOR_ ## _num, +#include "context_colors.def" +#undef color + VWM_CONTEXT_COLOR_MAX +} vwm_context_color_t; + typedef struct _vwm_t { vwm_xserver_t *xserver; /* global xserver instance */ vwm_charts_t *charts; /* golbal charts instance */ @@ -45,24 +54,26 @@ typedef struct _vwm_t { int xinerama_screens_cnt; int done; /* global flag to cause vwm to quit */ + list_head_t contexts; /* global list of all contexts in spatial created-in order */ + list_head_t contexts_mru; /* global list of all contexts kept in MRU order */ list_head_t desktops; /* global list of all (virtual) desktops in spatial created-in order */ list_head_t desktops_mru; /* global list of all (virtual) desktops in MRU order */ list_head_t windows_mru; /* global list of all managed windows kept in MRU order */ list_head_t xwindows; /* global list of all xwindows kept in the X server stacking order */ + vwm_window_t *console; /* the console window */ vwm_window_t *focused_origin; /* the originating window in a grabbed operation/transaction */ - vwm_desktop_t *focused_desktop; /* currently focused (virtual) desktop */ - vwm_window_t *focused_shelf; /* currently focused shelved window */ - vwm_context_t focused_context; /* currently focused context */ + vwm_desktop_t *focused_desktop; /* currently focused desktop */ int priority; /* scheduling priority of the vwm process, launcher nices relative to this */ unsigned long fence_mask; /* 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. */ + * if you use vwm on enough screens to overflow this, pics or it didn't happen. */ struct colors { #define color(_sym, _str) \ XColor _sym ## _color; #include "colors.def" #undef color } colors; + XColor context_colors[VWM_CONTEXT_COLOR_MAX]; } vwm_t; #endif diff --git a/src/window.c b/src/window.c index d1441f3..5410ba6 100644 --- a/src/window.c +++ b/src/window.c @@ -80,20 +80,10 @@ vwm_window_t * vwm_win_lookup(vwm_t *vwm, Window win) } -/* return the currently focused window (considers current context...), may return NULL */ +/* return the currently focused window, may return NULL */ vwm_window_t * vwm_win_get_focused(vwm_t *vwm) { - switch (vwm->focused_context) { - case VWM_CONTEXT_SHELF: - return vwm->focused_shelf; - - case VWM_CONTEXT_DESKTOP: - return vwm->focused_desktop->focused_window; - - default: - VWM_BUG("Unsupported context"); - assert(0); - } + return vwm->focused_desktop->focused_window; } @@ -103,22 +93,15 @@ vwm_window_t * vwm_win_get_focused(vwm_t *vwm) void vwm_win_set_focused(vwm_t *vwm, 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(VWM_XDISPLAY(vwm), vwin->xwindow->id, vwin == vwm->console ? vwm->colors.shelved_console_border_color.pixel : vwm->colors.shelved_window_border_color.pixel); - /* fullscreen windows in the shelf when focused, since we don't intend to overlap there */ - vwm_win_autoconf(vwm, vwin, VWM_SCREEN_REL_POINTER, VWM_WIN_AUTOCONF_FULL); /* XXX TODO: for now the shelf follows the pointer, it's simple. */ - } else { - if (vwin->desktop->focused_window) - /* set the border of the previously focused window on the same desktop to the unfocused color */ - XSetWindowBorder(VWM_XDISPLAY(vwm), vwin->desktop->focused_window->xwindow->id, vwm->colors.unfocused_window_border_color.pixel); + if (vwin->desktop->focused_window) + /* set the border of the previously focused window on the same desktop to the unfocused color */ + XSetWindowBorder(VWM_XDISPLAY(vwm), vwin->desktop->focused_window->xwindow->id, vwm->colors.unfocused_window_border_color.pixel); - /* set the border of the newly focused window to the focused color */ - XSetWindowBorder(VWM_XDISPLAY(vwm), vwin->xwindow->id, vwm->colors.focused_window_border_color.pixel); + /* set the border of the newly focused window to the focused color */ + XSetWindowBorder(VWM_XDISPLAY(vwm), vwin->xwindow->id, vwm->context_colors[vwin->desktop->context->color].pixel); - /* persist this on a per-desktop basis so it can be restored on desktop switches */ - vwin->desktop->focused_window = vwin; - } + /* persist this on a per-desktop basis so it can be restored on desktop switches */ + vwin->desktop->focused_window = vwin; } @@ -131,7 +114,7 @@ void vwm_win_autoconf_magic(vwm_t *vwm, vwm_window_t *vwin, const vwm_screen_t * if (!scr) scr = vwm_screen_find(vwm, VWM_SCREEN_REL_RECT, x, y, width, height); - if (!vwin->shelved && scr && + if (scr && width == scr->width && height == scr->height) { VWM_TRACE_WIN(vwin->xwindow->id, "auto-allscreened window"); @@ -329,21 +312,9 @@ _retry: VWM_TRACE("VWM_FENCE_MASKED_VIOLATE fence_mask now: 0x%lx\n", vwm->fence_mask); } - if (vwin->shelved) { - if (next != vwm->focused_shelf) { - if (vwm->focused_context == VWM_CONTEXT_SHELF) { - vwm_win_unmap(vwm, vwm->focused_shelf); - XFlush(VWM_XDISPLAY(vwm)); - vwm_win_map(vwm, next); - } - vwm->focused_shelf = next; - vwm_win_focus(vwm, next); - } - } else { - if (next != next->desktop->focused_window) { - vwm_win_focus(vwm, next); - XRaiseWindow(VWM_XDISPLAY(vwm), next->xwindow->id); - } + if (next != next->desktop->focused_window) { + vwm_win_focus(vwm, next); + XRaiseWindow(VWM_XDISPLAY(vwm), next->xwindow->id); } VWM_TRACE("vwin=%p xwin=%p name=\"%s\"", next, next->xwindow, next->xwindow->name); @@ -352,48 +323,62 @@ _retry: } -/* shelves a window, if the window is focused we focus the next one (if possible) */ +/* "shelves" a window, if the window is focused we focus the next one (if exists) */ +/* originally there was a special shelf context having different semantics of a fullscreen + * window at a time, this evolved into generic contexts containing virtual + * desktops, and now shelving is just the process of sending a window to the bottom/first + * context into a newly created desktop there, in an unattended fashion (like an unattended migrate, + * to an assumed bottom destination context created for this purpose at startup, and into its own + * desktop there). + */ void vwm_win_shelve(vwm_t *vwm, vwm_window_t *vwin) { - /* already shelved, NOOP */ - if (vwin->shelved) + vwm_context_t *shelf = list_entry(vwm->contexts.next, vwm_context_t, contexts); + vwm_desktop_t *desktop; + + /* already in the first, AKA "shelf" context, NOOP */ + if (&vwin->desktop->context->contexts == vwm->contexts.next) return; /* shelving focused window, focus the next window */ if (vwin == vwin->desktop->focused_window) vwm_win_mru(vwm, vwm_win_focus_next(vwm, vwin, VWM_DIRECTION_FORWARD, VWM_FENCE_RESPECT)); + /* vwin appears to be alone */ if (vwin == vwin->desktop->focused_window) - /* TODO: we can probably put this into vwm_win_focus_next() and have it always handled there... */ vwin->desktop->focused_window = NULL; - vwin->shelved = 1; - vwm_win_mru(vwm, vwin); + /* TODO: ^^^ there should probably be a helper for withdrawing a window + * from a desktop which handles the above focus next -> lone window + * nonsense, and hands back an orphan window to do whatever with. + */ + + /* shelved windows always get an empty desktop in the shelf context, + * look for an empty one and only create a new one if there is none + * to use. + */ + vwin->desktop = NULL; + list_for_each_entry(desktop, &vwm->desktops_mru, desktops_mru) { + if (desktop->context == shelf && !desktop->focused_window) { + vwin->desktop = desktop; + break; + } + } - /* newly shelved windows always become the focused shelf */ - vwm->focused_shelf = vwin; + if (!vwin->desktop) + vwin->desktop = vwm_desktop_create(vwm, shelf); + /* always leave the newly shelved window's desktop focused */ + vwin->desktop->context->focused_desktop = vwin->desktop; + vwm_win_set_focused(vwm, vwin); + vwm_win_mru(vwm, vwin); vwm_win_unmap(vwm, vwin); } -/* helper for (idempotently) unfocusing a window, deals with context switching etc... */ +/* helper for (idempotently) unfocusing a window */ void vwm_win_unfocus(vwm_t *vwm, vwm_window_t *vwin) { - /* if we're the focused shelved window, cycle the focus to the next shelved window if possible, if there's no more shelf, switch to the desktop */ - /* TODO: there's probably some icky behaviors for focused windows unmapping/destroying in unfocused contexts, we probably jump contexts suddenly. */ - if (vwin == vwm->focused_shelf) { - VWM_TRACE("unfocusing focused shelf"); - vwm_win_focus_next(vwm, vwin, VWM_DIRECTION_FORWARD, VWM_FENCE_IGNORE); - - if (vwin == vwm->focused_shelf) { - VWM_TRACE("shelf empty, leaving"); - /* no other shelved windows, exit the shelf context */ - vwm_context_focus(vwm, VWM_CONTEXT_DESKTOP); - vwm->focused_shelf = NULL; - } - } - /* if we're the focused window cycle the focus to the next window on the desktop if possible */ if (vwin->desktop->focused_window == vwin) { VWM_TRACE("unfocusing focused window"); @@ -401,7 +386,7 @@ void vwm_win_unfocus(vwm_t *vwm, vwm_window_t *vwin) } if (vwin->desktop->focused_window == vwin) { - VWM_TRACE("desktop empty"); + VWM_TRACE("unfocused last window on desktop"); vwin->desktop->focused_window = NULL; } } @@ -468,26 +453,23 @@ static void vwm_win_assimilate(vwm_t *vwm, vwm_window_t *vwin) /* TODO: this is a good place to hook in a window placement algo */ /* on client-requested mapping we place the window */ - if (!vwin->shelved) { - /* we place the window on the screen containing the the pointer only if that screen is empty, - * otherwise we place windows on the screen containing the currently focused window */ - scr = vwm_screen_find(vwm, VWM_SCREEN_REL_POINTER); - if (vwm_screen_is_empty(vwm, scr, vwin->xwindow)) { - /* focus the new window if it isn't already focused when it's going to an empty screen */ - VWM_TRACE("window \"%s\" is alone on screen \"%i\", focusing", vwin->xwindow->name, scr->screen_number); - vwm_win_focus(vwm, vwin); - } else { - scr = vwm_screen_find(vwm, VWM_SCREEN_REL_XWIN, vwm->focused_desktop->focused_window->xwindow); - } - - changes.x = scr->x_org; - changes.y = scr->y_org; - } else if (vwm->focused_context == VWM_CONTEXT_SHELF) { - scr = vwm_screen_find(vwm, VWM_SCREEN_REL_XWIN, vwm->focused_shelf->xwindow); - changes.x = scr->x_org; - changes.y = scr->y_org; + /* 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 */ + scr = vwm_screen_find(vwm, VWM_SCREEN_REL_POINTER); + if (vwm_screen_is_empty(vwm, scr, vwin->xwindow)) { + /* focus the new window if it isn't already focused when it's going to an empty screen */ + VWM_TRACE("window \"%s\" is alone on screen \"%i\", focusing", vwin->xwindow->name, scr->screen_number); + vwm_win_focus(vwm, vwin); + } else { + /* FIXME TODO: there's some situation where we get here but focused_desktop->focused_window == NULL, + * which shouldn't be possible; for there to be a non-empty screen, the focused_desktop must have a focused_window. + */ + scr = vwm_screen_find(vwm, VWM_SCREEN_REL_XWIN, vwm->focused_desktop->focused_window->xwindow); } + changes.x = scr->x_org; + changes.y = scr->y_org; + /* XXX TODO: does this belong here? */ XGetWMNormalHints(VWM_XDISPLAY(vwm), xwin->id, vwin->hints, &vwin->hints_supplied); XGetWindowAttributes(VWM_XDISPLAY(vwm), xwin->id, &attrs); @@ -541,7 +523,6 @@ vwm_window_t * vwm_win_manage_xwin(vwm_t *vwm, vwm_xwindow_t *xwin) vwin->desktop = vwm->focused_desktop; vwin->autoconfigured = VWM_WIN_AUTOCONF_NONE; - vwin->shelved = (vwm->focused_context == VWM_CONTEXT_SHELF); /* if we're in the shelf when the window is created, the window is shelved */ vwin->client = xwin->attrs; /* remember whatever the current attributes are */ VWM_TRACE("hints: flags=%lx x=%i y=%i w=%i h=%i minw=%i minh=%i maxw=%i maxh=%i winc=%i hinc=%i basew=%i baseh=%i grav=%x", @@ -572,9 +553,9 @@ vwm_window_t * vwm_win_manage_xwin(vwm_t *vwm, vwm_xwindow_t *xwin) /* always raise newly managed windows so we know about them. */ XRaiseWindow(VWM_XDISPLAY(vwm), xwin->id); - /* if the desktop has no focused window yet, automatically focus the newly managed one, provided we're on the desktop context */ - if (!vwm->focused_desktop->focused_window && vwm->focused_context == VWM_CONTEXT_DESKTOP) { - VWM_TRACE("Mapped new window \"%s\" is alone on desktop, focusing", xwin->name); + /* if the desktop has no focused window yet, automatically focus the newly managed one */ + if (!vwm->focused_desktop->focused_window) { + VWM_TRACE("Mapped new window \"%s\" is alone on desktop \"%s\", focusing", xwin->name, vwm->focused_desktop->name); vwm_win_focus(vwm, vwin); } @@ -595,12 +576,34 @@ _fail: void vwm_win_migrate(vwm_t *vwm, vwm_window_t *vwin, vwm_desktop_t *desktop) { vwm_win_unfocus(vwm, vwin); /* go through the motions of unfocusing the window if it is focused */ - vwin->shelved = 0; /* ensure not shelved */ - vwin->desktop = desktop; /* assign the new desktop */ + vwin->desktop = desktop; /* assign the new desktop */ vwm_desktop_focus(vwm, desktop); /* currently we always focus the new desktop in a migrate */ vwm_win_focus(vwm, vwin); /* focus the window so borders get updated */ vwm_win_mru(vwm, vwin); /* TODO: is this right? shouldn't the Mod1 release be what's responsible for this? I migrate so infrequently it probably doesn't matter */ - XRaiseWindow(VWM_XDISPLAY(vwm), vwin->xwindow->id); /* ensure the window is raised */ + XRaiseWindow(VWM_XDISPLAY(vwm), vwin->xwindow->id); /* ensure the window is @ top of stack */ +} + + +/* "send" a window to another desktop, no desktop/context switching occurs. */ +void vwm_win_send(vwm_t *vwm, vwm_window_t *vwin, vwm_desktop_t *desktop) +{ + if (desktop == vwin->desktop) + return; + + vwm_win_unfocus(vwm, vwin); + vwm_win_unmap(vwm, vwin); + vwin->desktop = desktop; + + /* XXX: only focus the destination desktop when not the focused context, as + * it creates an awkward disconnect for the focused context's focused desktop + * to become updated to something else while looking at it without actually + * realizing that focus change like a migrate does. + */ + if (vwm->focused_desktop->context != desktop->context) + desktop->context->focused_desktop = desktop; + + vwm_win_set_focused(vwm, vwin); + XRaiseWindow(VWM_XDISPLAY(vwm), vwin->xwindow->id); /* ensure the window is @ top of stack */ } diff --git a/src/window.h b/src/window.h index eef639e..daddf14 100644 --- a/src/window.h +++ b/src/window.h @@ -35,7 +35,6 @@ typedef struct _vwm_window_t { unsigned int autoconfigured:3; /* autoconfigured window states (none/quarter/half/full/all) */ unsigned int mapping:1; /* is the window being mapped? (by vwm) */ unsigned int unmapping:1; /* is the window being unmapped? (by vwm) */ - unsigned int shelved:1; /* is the window shelved? */ } vwm_window_t; @@ -78,6 +77,7 @@ void vwm_win_unfocus(vwm_t *vwm, vwm_window_t *vwin); vwm_xwindow_t * vwm_win_unmanage(vwm_t *vwm, vwm_window_t *vwin); vwm_window_t * vwm_win_manage_xwin(vwm_t *vwm, vwm_xwindow_t *xwin); void vwm_win_migrate(vwm_t *vwm, vwm_window_t *vwin, vwm_desktop_t *desktop); +void vwm_win_send(vwm_t *vwm, vwm_window_t *vwin, vwm_desktop_t *desktop); #endif diff --git a/src/xwindow.c b/src/xwindow.c index 86e8e9d..1deaf33 100644 --- a/src/xwindow.c +++ b/src/xwindow.c @@ -62,31 +62,15 @@ vwm_xwindow_t * vwm_xwin_lookup(vwm_t *vwm, Window win) } -/* determine if a window is mapped (vwm-mapped) according to the current context */ +/* determine if a window is mapped (vwm-mapped) according to the focused context */ int vwm_xwin_is_mapped(vwm_t *vwm, vwm_xwindow_t *xwin) { vwm_window_t *vwin = xwin->managed; - int ret = 0; if (!xwin->client_mapped || !vwin) return xwin->client_mapped; - switch (vwm->focused_context) { - case VWM_CONTEXT_SHELF: - if (vwm->focused_shelf == vwin) - ret = 1; - break; - - case VWM_CONTEXT_DESKTOP: - if (vwm->focused_desktop == vwin->desktop && !vwin->shelved) - ret = 1; - break; - - default: - VWM_BUG("Unsupported context"); - } - - return ret; + return (vwm->focused_desktop == vwin->desktop); } |