├── .gitignore ├── LICENSE ├── Makefile.am ├── NEWS ├── README.md ├── TODO ├── WISHLIST ├── autogen.sh ├── config.h ├── configure.template ├── events.h ├── hidden.c ├── hidden.man ├── list.c ├── list.h ├── mcwm-screen-20110308.png ├── mcwm.c ├── mcwm.man └── scripts ├── 9icon ├── mcicon └── mcmenu /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | aclocal.m4 3 | autoconf.h 4 | autoconf.h.in 5 | autom4te.cache/ 6 | _build/ 7 | ChangeLog 8 | config.status 9 | configure 10 | configure.ac 11 | .deps/ 12 | hidden 13 | INSTALL 14 | _install/ 15 | *.log 16 | Makefile 17 | Makefile.in 18 | mcwm 19 | *.o 20 | scripts/compile 21 | scripts/depcomp 22 | scripts/install-sh 23 | scripts/missing 24 | stamp-h1 25 | .*.sw[op] 26 | _test/ 27 | *.tar.* 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010,2011,2012,2013,2014 Michael Cardell Widerkrantz 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 8 | WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 9 | WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 10 | AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 11 | DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 12 | PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 13 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 | PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | 2 | 3 | AM_CPPFLAGS = -include autoconf.h 4 | AM_CFLAGS = $(MCWM_CFLAGS) 5 | 6 | bin_PROGRAMS = mcwm hidden 7 | dist_pkglibexec_SCRIPTS = scripts/9icon scripts/mcicon scripts/mcmenu 8 | 9 | mcwm_SOURCES = mcwm.c events.h list.c list.h config.h 10 | mcwm_LDADD = $(MCWM_LIBS) 11 | 12 | hidden_SOURCES = hidden.c 13 | hidden_LDADD = $(MCWM_LIBS) 14 | 15 | dist_man1_MANS = mcwm.man hidden.man 16 | 17 | EXTRA_DIST = LICENSE TODO WISHLIST 18 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | -*- text -*- 2 | 3 | User visible changes 4 | 5 | 2013-08-19 6 | 7 | * New option: -s snap-margin. Optional snap to window and monitor 8 | borders. Thanks to Simone Vellei. 9 | 10 | 2013-02-08 11 | 12 | * New keys: MODKEY + c,v moves to previous or next workspace. Thanks 13 | to Per Cederqvist. 14 | 15 | * New key: Shift-TAB moves backwards in on screen window list. 16 | Thanks to Per Cederqvist. 17 | 18 | 2012-03-05 19 | 20 | * New key: MODKEY + i hides windows. 21 | 22 | * New program: hidden. Lists hidden windows. 23 | 24 | 2012-02-15 25 | 26 | * Added option -i to allow icons. 27 | 28 | * Added scripts: mcmenu (configurable start meny) and 9icon (to map 29 | iconified windows). 30 | 31 | 2012-02-07 32 | 33 | * Check for socket errors. Should prevent busy loops. 34 | 35 | 2011-11-22 36 | 37 | * Now optionally starts programs when MODKEY + mouse buttons are 38 | pressed on root window. See config.h for configuration and the 39 | manual page for an example. 40 | 41 | Also simplified starting programs. 42 | 43 | 2011-11-17 44 | 45 | * In a ConfigureRequest we no longer obey window movements not 46 | initiated by the user. 47 | 48 | * Fixed bug where client-initiated resize could resize outside of 49 | physical screen. 50 | 51 | * Fixed bug with topright command on other physical screens. 52 | 53 | * -b no longer boolean. Now takes parameter number of pixels. 54 | 55 | 2011-08-15 56 | 57 | * Fixed crash in one screen scenario. 58 | 59 | * Fixed non-starting bug when on an architecture with unsigned 60 | chars. 61 | 62 | 2011-08-12 63 | 64 | * It was still possible to map a window on coordinates outside 65 | physical screens. Now hopefully fixed, if not perfectly: If a 66 | window isn't bound to any physical screen it will be mapped on the 67 | first screen mcwm knows about. 68 | 69 | 2011-07-26 70 | 71 | * Tabbing warps to middle of window instead of corner. Makes it 72 | easier if you want to paste something with middle button without 73 | having to move the pointer. 74 | 75 | 2011-07-20 76 | 77 | * Speedup when using a trackball or high CPI mouse. Use only mouse 78 | pointer hints instead of actual mouse events and let mcwm ask for 79 | pointer position. 80 | 81 | Incidentally, this was originally added in June, 2010, but 82 | removed a month later after reading Gajewska and Manasse's "Why X 83 | Is Not Our Ideal Window System". Real experience shows that I was 84 | mistaken. 85 | 86 | 2011-06-20 87 | 88 | * Fixed windows were always inserted as the next window in window 89 | list after changing workspace. Now fixed. 90 | 91 | 2011-06-01 92 | 93 | * Beginning of RANDR support. mcwm now aware of the number of 94 | physical screens and their sizes. Move and resize is now limited 95 | by size of physical screen instead of virtual screen. 96 | 97 | * New keys: Mod4 + , moves focused window to previous screen and 98 | Mod4 + . moves it to the next screen. 99 | 100 | 2011-03-29 101 | 102 | * Panels and other windows with window manager override interfered 103 | with resize. mcwm got confused about the size of the window. Now 104 | fixed. 105 | 106 | 2011-03-28 107 | 108 | * Always raise window when fixing it on all workspaces. When moving 109 | windows between workspaces, this makes us sure we can find it on 110 | the workspace we're going to. Thanks to Simon Friis for noticing. 111 | 112 | 2011-03-26 113 | 114 | * You don't have to tell mcwm what keycode generates the MODKEY 115 | mask anymore (USERKEY_MOD in config.h is gone). mcwm now asks the 116 | X server what keys generate the mask and also supports several 117 | keys generating the mask. 118 | 119 | * Stops tabbing around not only if a key generating MODKEY mask is 120 | lifted but also if another command key is pressed. 121 | 122 | 2011-03-18 123 | 124 | * Now handles keyboard remapping. 125 | 126 | 2011-02-23 127 | 128 | * If you change focus window mcwm remembers where we last had 129 | focus. If you start using MODKEY+Tab to move around the window 130 | list the first Tab will always bring you to where we last had 131 | focus. 132 | 133 | 2011-02-19 134 | 135 | * Resets per window state of max or vertical max when physical 136 | screen size changes. 137 | 138 | * Doesn't reset x coordinate when unmaxing vertically. 139 | 140 | * Specifically grab the keycodes we use and keycodes + shift 141 | modifier. Now works OK with xbindkeys. 142 | 143 | 2010-11-18 144 | 145 | NOTA BENE! Modifier key changed! Now using Mod4 instead of Mod2 for 146 | key bindnings. I've been using my own key maps for so long that I 147 | forgot all about NumLock usually being Mod2 and users complained. 148 | I'm sorry. 149 | 150 | 2010-11-01 151 | 152 | * Fixed bug. We mangled a list when a window was destroyed on 153 | another workspace. This would lead to sticky windows that we 154 | didn't seem to know about. 155 | 156 | The same code also prepares for the possibility that a window 157 | might occupy several, but not all, workspaces at the same time. We 158 | still don't have any user interface to control this, however. 159 | 160 | 2010-09-08 161 | 162 | * New key: MODKEY + End closes window. Patch from Christian 163 | Neukirchen, who used MODKEY + ESC instead. 164 | 165 | 2010-08-23 166 | 167 | * Obey window coordinates when doing initial mapping if the client 168 | says the user specified them, for instance with a -geometry. 169 | 170 | 2010-08-10 171 | 172 | * Mod2-Tab no longer raises a window unless it's necessary for the 173 | window to be visible. 174 | 175 | * We no longer obey clients trying to map themselves on the current 176 | workspace when they belong to another workspace. Notably, this 177 | affects Emacs frames used with emacsclient if server-raise-frame 178 | is non-nil. The mouse pointer will still be warped, though, but at 179 | least we stop the window on the other workspace to suddenly move 180 | to the current workspace. 181 | 182 | 2010-07-28 183 | 184 | * Now works with unclutter. 185 | 186 | 2010-07-23 187 | 188 | * Better use of size increments. 189 | 190 | 2010-07-22 191 | 192 | * When resizing by keyboard we don't warp the pointer along with the 193 | window if the pointer was outside the window when we started 194 | resizing. 195 | 196 | 2010-07-21-2 197 | 198 | * When moving by keyboard, we don't warp the pointer along with the 199 | window if the pointer was outside the window to begin with. 200 | 201 | * Don't allow moving or resizing of fully maximized windows. 202 | 203 | 2010-07-21 204 | 205 | * Will react on signals directly instead of waiting for next X event. 206 | 207 | * Raises windows before moving to corner. 208 | 209 | 2010-07-20 210 | 211 | * Grabbed keypresses that mcwm doesn't know what to do with will be 212 | sent to the focused window. 213 | 214 | * More bug fixes. 215 | 216 | 2010-07-19-2 217 | 218 | * Many bug fixes. Including crashing bugs, triggered under Linux. 219 | 220 | 2010-07-18 221 | 222 | * Move and resize, both with keyboard and mouse, now keeps pointer 223 | in place within window if it's possible without losing focus. 224 | 225 | * Move window to screen corners with YUBN. 226 | 227 | 2010-07-10 228 | 229 | * Border colour of fixed windows can be set from command line with 230 | -x colour. 231 | 232 | * Dies gracefully and maps all windows on all workspaces. 233 | 234 | * Handle unmapped, but inactive, windows when changing workspaces. 235 | This means that xpdf's and gv's transient windows won't show up 236 | again when changing workspaces unless they're currently really 237 | active. 238 | 239 | 2010-07-07 240 | 241 | * Rearranges windows to be visible and fit on screen if a physical 242 | screen is removed or screen is tilted. 243 | 244 | * Maximize and vertical maximize toggles. 245 | 246 | * First virtual workspace, that is 1, is now numbered as 0 in the 247 | _NET_WM_DESKTOP as in the rest of the window managers following 248 | EWMH. 249 | 250 | 2010-06-30 251 | 252 | * Virtual workspaces available with Mod2-0,1,2... Fix on all 253 | workspaces with Mod2-f. 254 | 255 | 2010-06-29 256 | 257 | * Focus can change from keyboard with Mod2-Tab. 258 | 259 | 2010-06-24-3 260 | 261 | * -f and -u options for focused and unfocused colours. 262 | 263 | 2010-06-24 264 | 265 | * When starting, we don't care about windows with override redirect 266 | set. This means, for instance, that if a GTK application was 267 | running when we started, we won't draw any borders around its 268 | menus. 269 | 270 | * New option, -t . 271 | 272 | * Fixed bugs. 273 | 274 | 2010-06-22 275 | 276 | * Handles size changes of the root window. That is, if the user adds 277 | an additional screen or removes one when the X server supports 278 | RANDR. 279 | 280 | * MODKEY + R toggles raise/lower of window. 281 | 282 | * Maps new windows on pointer position and tries to fit on screen. 283 | 284 | * Resizes with size hints. This means, for instance, that most 285 | programs using fixed width fonts resizes by font width and height. 286 | No feedback yet, though. 287 | 288 | * Handles more requests, which means usable with more programs 289 | (xterm and xpdf, for instance, which hardly worked before). 290 | 291 | * Fixed bugs. 292 | 293 | 2010-06-19 294 | 295 | * Now a real window manager. Now controls mapping of windows. If we 296 | can't be a real window manager, this probably means there is 297 | already one running, so we fall back to co-running. 298 | 299 | Windows are mapped at the pointer position if we can control it. 300 | 301 | * A flag, -b, for not drawing any borders what so ever. Might be 302 | useful when co-running. 303 | 304 | * Now doesn't raise windows all the time while moving or resizing 305 | with the mouse. Just raises once. 306 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mcwm 2 | 3 | mcwm is minimalist, floating window manager written using XCB, the X 4 | protocol C binding. 5 | 6 | The only window decoration is a thin border. All functions can be done 7 | with the keyboard, but moving, resizing, raising and lowering can also 8 | be done with the mouse. 9 | 10 | If you don't like the default key bindings, border width, et cetera, 11 | look in the `config.h` file, change and recompile. 12 | 13 | See the manual page for how to use it. 14 | 15 | ## Compiling 16 | 17 | As distributed mcwm compiles fine under FreeBSD. If you want to 18 | compile mcwm under Debian-derived GNU/Linux systems you will need 19 | these packages and their dependencies: libxcb1-dev, libxcb-atom1-dev, 20 | libxcb-keysyms1-dev, libxcb-icccm1-dev, libxcb-randr0-dev. 21 | 22 | Other system are likely to have similar requirements. 23 | 24 | ## Screenshot 25 | 26 | ![](mcwm-screen-20110308.png) 27 | 28 | ## Children 29 | 30 | mcwm has been forked a few times. It was the beginning of the [2bwm 31 | window manager](https://github.com/venam/2bwm), for instance. 32 | 33 | ## More information & Contact 34 | 35 | Please see: 36 | 37 | https://hack.org/mc/projects/mcwm 38 | 39 | To contact me, write to: 40 | 41 | Michael Cardell Widerkrantz, mc at the domain hack.org. 42 | 43 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | -*- text -*- 2 | 3 | * IconicState 4 | 5 | Currently we set IconicState only on windows we hide and not when we 6 | unmap them to switch to another desktop. ICCCM and EWMH says we 7 | should set IconicState in both situations. 8 | 9 | We should also set _NET_WM_STATE_HIDDEN when hiding windows. 10 | 11 | hidden.c should list windows with _NET_WM_STATE_HIDDEN and, 12 | optionally, all with IconicState. 13 | 14 | * When unhiding a window we want it to re-appear in the same position. 15 | How? 16 | 17 | Setting XCB_ICCCM_SIZE_HINT_US_POSITION before hiding would probably 18 | work without adding more code, but does ICCCM allow that? 19 | 20 | PPosition? 21 | 22 | * Feature: Handle several screens (DISPLAY=0.x) in classical X. 23 | 24 | This means we will have several root windows. 25 | 26 | setup = xcb_get_setup(conn); 27 | screens = xcb_setup_roots_length(setup); 28 | 29 | returns the number of screens available. 30 | 31 | We can walk over them with xcb_setup_roots_iterator(setup). 32 | 33 | * Feature: Xinerama support. Needed when XRANDR above 1.1 not 34 | supported, for instance with Nvidia cards in Twinview configuration. 35 | 36 | * Bug: Ignore other modifiers, such as NumLock and CapsLock. 37 | 38 | We can use something like this to find the modifier mask for 39 | NumLock: 40 | 41 | xcb_keycode_t *num_lock; 42 | num_lock = xcb_key_symbols_get_keycode(symbols, XK_Num_Lock); 43 | 44 | and get an array of keycodes finished by XCB_NO_SYMBOL. 45 | 46 | then compare the keycodes in the array with the keycodes all the 47 | modifier masks give. See getmodkeys(). 48 | 49 | * Extended Window Manager Hints (EWMH) 50 | 51 | - Use the new xcb-ewmh for the EWMH hints. 52 | 53 | I suggest listing least these in _NET_SUPPORTED (* marks 54 | implemented): 55 | 56 | _NET_NUMBER_OF_DESKTOPS, _NET_WM_DESKTOP*, _NET_CURRENT_DESKTOP, 57 | _NET_WM_STATE, _NET_WM_STATE_STICKY, 58 | _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_HIDDEN, 59 | _NET_WM_STATE_FULLSCREEN, _NET_ACTIVE_WINDOW 60 | 61 | _NET_NUMBER_OF_DESKTOPS can be set to the constant WORKSPACES. 62 | 63 | _NET_CURRENT_DESKTOP is just curws. Set the hint when starting and 64 | when changing workspaces. 65 | 66 | We *may* want to support a message to the root window that *sets* 67 | _NET_CURRENT_DESKTOP and then switch to it. 68 | 69 | We might want to support _NET_WM_WINDOW_TYPE_DESKTOP as well. 70 | 71 | * Bug: We grab MODKEY all the time! We can grab it only when we start 72 | tabbing instead and release it when tabbing is complete. 73 | 74 | * Key to move windows to other workspaces, perhaps mod4+Shift + 0..9 75 | and mod4+shift + I/O. 76 | 77 | * Full-screen windows that are resized from client requests should be 78 | kept full-screen. 79 | 80 | * When moving windows between monitors, try to place the window on 81 | roughly the same place on a new monitor, if possible. 82 | 83 | * When a window tries to map itself outside the monitors always map it 84 | on the monitor closest to the coordinates it asked for instead of 85 | always mapping on the first monitor in the list. 86 | 87 | * Feature: We need to continue dragging for move and resize until 88 | *both* MODKEY and mouse button has been released. Will be much nicer 89 | with trackball. 90 | 91 | * Move to first window (or middle of monitor) on another monitor with 92 | MODKEY + . and , instead of moving windows to new monitor. Shifted 93 | these keys will move window to new monitor? 94 | 95 | * Handle new modes on physical outputs. What do we have to do? 96 | 97 | * A separate workspace list for every monitor. 98 | 99 | * Allow hexadecimal colour values on command line. 100 | 101 | * Grab specific keys 102 | 103 | Current code grabs modifier keys and any other keypress. We need to 104 | specifically grab the keycodes we need. xbindkeys and possibly other 105 | programs don't work. 106 | 107 | Partially done. Now grabs all our keys unshifted as well as shifted. 108 | 109 | * Feedback window 110 | 111 | We may need to tell the user the new geometry somehow. Is this what 112 | I want? 113 | 114 | The feedback window can also show what workspace we just changed to. 115 | 116 | Can we cooperate with a stand-alone program? Write our status to a 117 | root hint or named pipe than can be picked up by another program? 118 | 119 | * Virtual screens/workspaces 120 | 121 | Partially done. Still needed: 122 | 123 | - A window might be on one, *several* or all virtual screens. 124 | 125 | We already have a way of fixing a window on all screens 126 | (MODKEY-f), but we need a way of saying "stick on this workspace". 127 | Perhaps something like MODKEY-a , where is 1--9 for virtual 128 | screens. Better ways? Note that this seems to be mildly 129 | incompatible with the EWMH _NET_WM_DESKTOP hint we're currently 130 | using: We will only be able to save one of the desktops used. 131 | 132 | * Menu 133 | 134 | We might need a menu for hidden windows. Since I'm probably 135 | implementing menu windows anyway, perhaps I should add a menu with 136 | basic window functions, like 9wm and twm. This way, one might use 137 | the window manager without keyboard, if necessary. Not much work if 138 | I have to do the menu anyway... But also chords? 139 | 140 | On the other hand, see hidden.c and forthcoming external menu 141 | program. 142 | 143 | * Chords 144 | 145 | Maybe have mouse button chords to do move and resize? Configurable 146 | if on or off? 147 | 148 | Idea from Christian Neukirchen. 149 | 150 | * Gaps on borders for docks, status windows et cetera. 151 | 152 | Keep space reserved for Conky, dzen2 et cetera. 153 | 154 | Also respect EWMH hints _NET_WM_TYPE_DOCK and _NET_WM_TYPE_DESKTOP. 155 | 156 | * Flag to disable that mcwm won't allow windows to move off the 157 | screen? 158 | 159 | * Use xcb-event's event handlers? 160 | 161 | * Configurable keys. 162 | 163 | * Configuration file. 164 | 165 | * Handle Urgency hint 166 | 167 | Some windows might need attention and marks this with an urgency 168 | hint (for instance, urxvt can generate such a hint when receiving 169 | the BEL character). Do we want to handle it? How do we tell the 170 | user? Can this be done with some stand-alone program instead? 171 | 172 | * Code cleaning 173 | 174 | - Obvious cleanup: The event switch is way too big. 175 | 176 | - The states are known everywhere. A tight state machine would be 177 | nicer. 178 | 179 | - Dispatch table with function pointers for key bindings instead of 180 | keysym->enum->case? 181 | 182 | - Use bitfields instead of extra lists for workspaces? 183 | 184 | * Resize behaviour 185 | 186 | Better resize behaviour. We want not to accidentally change the 187 | vertical size when changing horizontal size and vice versa. 188 | -------------------------------------------------------------------------------- /WISHLIST: -------------------------------------------------------------------------------- 1 | -*- text -*- 2 | 3 | A wishlist for a window manager (v marks what mcwm fulfills 4 | completely): 5 | 6 | v Small. 7 | 8 | v Few external dependencies, both for running and building. 9 | 10 | v Fast. 11 | 12 | v Minimal decoration. 13 | 14 | v Sloppy focus. 15 | 16 | v Vertical and maximum maximizing. 17 | 18 | v Don't move windows off of physical screen. Configurable? 19 | 20 | v Window placement on virtual screens should be remembered even if wm 21 | killed and restarted. 22 | 23 | v Quickly move windows to corners. 24 | 25 | v Key bindings for all window functions. 26 | 27 | v Changing focus from keyboard which rememebers last focused window. 28 | Keep on the same physical screen. 29 | 30 | - Focus change from keyboard should restore stacking order after focus 31 | switch. 32 | 33 | - Configurable key bindings. 34 | 35 | v Know about physical screens dynamically (RandR). 36 | 37 | - Key binding to change focus to another physical screen. 38 | 39 | - Virtual screens. Separate sets for each physical screen. 40 | 41 | A window should be able to occupy one, several or all virtual 42 | screens. 43 | 44 | - Snap to border and screen edge, which favours the edge. 45 | 46 | v Mouse buttons on root window should start some configurable 47 | programs. 48 | 49 | - Feedback window for size when resizing. 50 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VERSION= 4 | 5 | if [ -z "$VERSION" ]; then 6 | VERSION='20130209-2' 7 | if [ -x "`which git 2>/dev/null`" -a -d .git ]; then 8 | VERSION=$(git describe --tags|sed 's,[-_],.,g;s,\.g.*$,,') 9 | ( 10 | echo -e "# created with git log --stat=76 | fmt -sct -w80\n" 11 | git log --stat=76 | fmt -sct -w80 12 | )>ChangeLog 13 | fi 14 | fi 15 | 16 | sed -r -e "s:[[]mcwm[]],[[][^]]*[]]:[mcwm],[$VERSION]: 17 | s:AC_REVISION\([[][^]]*[]]\):AC_REVISION([$VERSION]):" \ 18 | configure.template >configure.ac 19 | 20 | autoreconf -fiv 21 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* User configurable stuff. */ 2 | 3 | /* 4 | * Move this many pixels when moving or resizing with keyboard unless 5 | * the window has hints saying otherwise. 6 | */ 7 | #define MOVE_STEP 32 8 | 9 | /* 10 | * Use this modifier combined with other keys to control wm from 11 | * keyboard. Default is Mod4, which on my keyboard is the Alt key but 12 | * is usually the Windows key on more normal keyboard layouts. 13 | */ 14 | #define MODKEY XCB_MOD_MASK_4 15 | 16 | /* Extra modifier for resizing. Default is Shift. */ 17 | #define SHIFTMOD XCB_MOD_MASK_SHIFT 18 | 19 | /* 20 | * Modifier key to use with mouse buttons. Default Mod1, Meta on my 21 | * keyboard. 22 | */ 23 | #define MOUSEMODKEY XCB_MOD_MASK_1 24 | 25 | /* 26 | * Start this program when pressing MODKEY + USERKEY_TERMINAL. Needs 27 | * to be in $PATH. 28 | * 29 | * Change to "xterm" if you're feeling conservative. 30 | * 31 | * Can be set from command line with "-t program". 32 | */ 33 | #define TERMINAL "urxvt" 34 | 35 | /* 36 | * Do we allow windows to be iconified? Set to true if you want this 37 | * behaviour to be default. Can also be set by calling mcwm with -i. 38 | */ 39 | #define ALLOWICONS false 40 | 41 | /* 42 | * Start these programs when pressing MOUSEMODKEY and mouse buttons on 43 | * root window. 44 | */ 45 | #define MOUSE1 "" 46 | #define MOUSE2 "" 47 | #define MOUSE3 "mcmenu" 48 | 49 | /* 50 | * Default colour on border for focused windows. Can be set from 51 | * command line with "-f colour". 52 | */ 53 | #define FOCUSCOL "chocolate1" 54 | 55 | /* Ditto for unfocused. Use "-u colour". */ 56 | #define UNFOCUSCOL "grey40" 57 | 58 | /* Ditto for fixed windows. Use "-x colour". */ 59 | #define FIXEDCOL "grey90" 60 | 61 | /* Default width of border window, in pixels. Used unless -b width. */ 62 | #define BORDERWIDTH 1 63 | 64 | /* Default snap margin in pixels. Used unless -s width. */ 65 | #define SNAPMARGIN 0 66 | 67 | /* 68 | * Keysym codes for window operations. Look in X11/keysymdefs.h for 69 | * actual symbols. Use XK_VoidSymbol to disable a function. 70 | */ 71 | #define USERKEY_FIX XK_F 72 | #define USERKEY_MOVE_LEFT XK_H 73 | #define USERKEY_MOVE_DOWN XK_J 74 | #define USERKEY_MOVE_UP XK_K 75 | #define USERKEY_MOVE_RIGHT XK_L 76 | #define USERKEY_MAXVERT XK_M 77 | #define USERKEY_RAISE XK_R 78 | #define USERKEY_TERMINAL XK_Return 79 | #define USERKEY_MAX XK_X 80 | #define USERKEY_CHANGE XK_Tab 81 | #define USERKEY_BACKCHANGE XK_VoidSymbol 82 | #define USERKEY_WS1 XK_1 83 | #define USERKEY_WS2 XK_2 84 | #define USERKEY_WS3 XK_3 85 | #define USERKEY_WS4 XK_4 86 | #define USERKEY_WS5 XK_5 87 | #define USERKEY_WS6 XK_6 88 | #define USERKEY_WS7 XK_7 89 | #define USERKEY_WS8 XK_8 90 | #define USERKEY_WS9 XK_9 91 | #define USERKEY_WS10 XK_0 92 | #define USERKEY_PREVWS XK_C 93 | #define USERKEY_NEXTWS XK_V 94 | #define USERKEY_TOPLEFT XK_Y 95 | #define USERKEY_TOPRIGHT XK_U 96 | #define USERKEY_BOTLEFT XK_B 97 | #define USERKEY_BOTRIGHT XK_N 98 | #define USERKEY_DELETE XK_End 99 | #define USERKEY_PREVSCREEN XK_comma 100 | #define USERKEY_NEXTSCREEN XK_period 101 | #define USERKEY_ICONIFY XK_I 102 | -------------------------------------------------------------------------------- /configure.template: -------------------------------------------------------------------------------- 1 | # -*- Autoconf -*- 2 | # Process this file with autoconf to produce a configure script. 3 | 4 | AC_PREREQ([2.69]) 5 | AC_INIT([mcwm], [20130209-2], [http://github.com/bbidulock/mcwm/issues], [mcwm]) 6 | AC_CONFIG_SRCDIR([hidden.c]) 7 | AC_CONFIG_HEADERS([autoconf.h]) 8 | AC_CONFIG_AUX_DIR([scripts]) 9 | AC_USE_SYSTEM_EXTENSIONS 10 | 11 | # Initialize Automake 12 | AM_INIT_AUTOMAKE([foreign dist-bzip2 no-dist-gzip]) 13 | AM_MAINTAINER_MODE 14 | 15 | # Checks for programs. 16 | AC_PROG_CC 17 | AC_PROG_LN_S 18 | AC_PROG_MAKE_SET 19 | AC_PROG_MKDIR_P 20 | AC_PROG_INSTALL 21 | AC_ARG_VAR([LD], [Linker loader command]) 22 | 23 | PKG_PROG_PKG_CONFIG 24 | 25 | # Checks for libraries. 26 | # FIXME: Replace `main' with a function in `-lXau': 27 | #AC_CHECK_LIB([Xau], [main]) 28 | # FIXME: Replace `main' with a function in `-lXdmcp': 29 | #AC_CHECK_LIB([Xdmcp], [main]) 30 | # FIXME: Replace `main' with a function in `-lxcb': 31 | #AC_CHECK_LIB([xcb], [main]) 32 | 33 | # Checks for header files. 34 | AC_CHECK_HEADERS([stdint.h stdlib.h string.h unistd.h]) 35 | 36 | # Checks for typedefs, structures, and compiler characteristics. 37 | AC_CHECK_HEADER_STDBOOL 38 | AC_TYPE_INT16_T 39 | AC_TYPE_INT32_T 40 | AC_TYPE_PID_T 41 | AC_TYPE_UINT16_T 42 | AC_TYPE_UINT32_T 43 | AC_TYPE_UINT8_T 44 | 45 | # Checks for library functions. 46 | AC_FUNC_FORK 47 | AC_FUNC_MALLOC 48 | AC_CHECK_FUNCS([select]) 49 | 50 | AC_CONFIG_FILES([Makefile]) 51 | 52 | PKG_CHECK_MODULES([MCWM],[xcb-util xcb-keysyms xcb-randr xcb-icccm xcb-ewmh xcb xdmcp xau]) 53 | AC_OUTPUT 54 | 55 | # vim: set ft=config: 56 | -------------------------------------------------------------------------------- /events.h: -------------------------------------------------------------------------------- 1 | #define MAXEVENTS 34 2 | 3 | static char *evnames[] = { 4 | "", 5 | "", 6 | "KeyPress", 7 | "KeyRelease", 8 | "ButtonPress", 9 | "ButtonRelease", 10 | "MotionNotify", 11 | "EnterNotify", 12 | "LeaveNotify", 13 | "FocusIn", 14 | "FocusOut", 15 | "KeymapNotify", 16 | "Expose", 17 | "GraphicsExpose", 18 | "NoExpose", 19 | "VisibilityNotify", 20 | "CreateNotify", 21 | "DestroyNotify", 22 | "UnmapNotify", 23 | "MapNotify", 24 | "MapRequest", 25 | "ReparentNotify", 26 | "ConfigureNotify", 27 | "ConfigureRequest", 28 | "GravityNotify", 29 | "ResizeRequest", 30 | "CirculateNotify", 31 | "CirculateRequest", 32 | "PropertyNotify", 33 | "SelectionClear", 34 | "SelectionRequest", 35 | "SelectionNotify", 36 | "ColormapNotify", 37 | "ClientMessage", 38 | "MappingNotify" 39 | }; 40 | -------------------------------------------------------------------------------- /hidden.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hidden - A small program to listen all windows with WM_STATE set to 3 | * Iconic. 4 | * 5 | * Copyright (c) 2012 Michael Cardell Widerkrantz, mc at the domain 6 | * hack.org. 7 | * 8 | * Permission to use, copy, modify, and/or distribute this software 9 | * for any purpose with or without fee is hereby granted, provided 10 | * that the above copyright notice and this permission notice appear 11 | * in all copies. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 14 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 15 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 16 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 17 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 18 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 19 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 20 | */ 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | xcb_connection_t *conn; 30 | xcb_screen_t *screen; 31 | 32 | xcb_atom_t wm_state; 33 | xcb_atom_t wm_icon_name; 34 | 35 | bool printcommand = false; 36 | 37 | static uint32_t get_wm_state(xcb_drawable_t win); 38 | static int findhidden(void); 39 | static void init(void); 40 | static void cleanup(void); 41 | static xcb_atom_t getatom(char *atom_name); 42 | static void printhelp(void); 43 | 44 | uint32_t get_wm_state(xcb_drawable_t win) 45 | { 46 | xcb_get_property_reply_t *reply; 47 | xcb_get_property_cookie_t cookie; 48 | uint32_t *statep; 49 | uint32_t state = 0; 50 | 51 | cookie = xcb_get_property(conn, false, win, wm_state, wm_state, 0, 52 | sizeof (int32_t)); 53 | 54 | reply = xcb_get_property_reply(conn, cookie, NULL); 55 | if (NULL == reply) 56 | { 57 | fprintf(stderr, "mcwm: Couldn't get properties for win %d\n", win); 58 | return -1; 59 | } 60 | 61 | /* Length is 0 if we didn't find it. */ 62 | if (0 == xcb_get_property_value_length(reply)) 63 | { 64 | goto bad; 65 | } 66 | 67 | statep = xcb_get_property_value(reply); 68 | state = *statep; 69 | 70 | bad: 71 | free(reply); 72 | return state; 73 | } 74 | 75 | /* 76 | * List all hidden windows. 77 | * 78 | */ 79 | int findhidden(void) 80 | { 81 | xcb_query_tree_reply_t *reply; 82 | int i; 83 | int len; 84 | xcb_window_t *children; 85 | xcb_get_window_attributes_reply_t *attr; 86 | uint32_t state; 87 | xcb_get_property_cookie_t cookie; 88 | xcb_icccm_get_text_property_reply_t prop; 89 | xcb_generic_error_t *error; 90 | 91 | /* Get all children. */ 92 | reply = xcb_query_tree_reply(conn, 93 | xcb_query_tree(conn, screen->root), 0); 94 | if (NULL == reply) 95 | { 96 | return -1; 97 | } 98 | 99 | len = xcb_query_tree_children_length(reply); 100 | children = xcb_query_tree_children(reply); 101 | 102 | /* List all hidden windows on this root. */ 103 | for (i = 0; i < len; i ++) 104 | { 105 | attr = xcb_get_window_attributes_reply( 106 | conn, xcb_get_window_attributes(conn, children[i]), NULL); 107 | 108 | if (!attr) 109 | { 110 | fprintf(stderr, "Couldn't get attributes for window %d.", 111 | children[i]); 112 | continue; 113 | } 114 | 115 | /* 116 | * Don't bother windows in override redirect mode. 117 | * 118 | * This mode means they wouldn't have been reported to us 119 | * with a MapRequest if we had been running, so in the 120 | * normal case we wouldn't have seen them. 121 | */ 122 | if (!attr->override_redirect) 123 | { 124 | state = get_wm_state(children[i]); 125 | if (state == XCB_ICCCM_WM_STATE_ICONIC) 126 | { 127 | /* 128 | * Example names: 129 | * 130 | * _NET_WM_ICON_NAME(UTF8_STRING) = 0x75, 0x72, 0x78, 131 | * 0x76, 0x74 WM_ICON_NAME(STRING) = "urxvt" 132 | * _NET_WM_NAME(UTF8_STRING) = 0x75, 0x72, 0x78, 0x76, 133 | * 0x74 WM_NAME(STRING) = "urxvt" 134 | */ 135 | cookie = xcb_icccm_get_wm_icon_name(conn, children[i]); 136 | xcb_icccm_get_wm_icon_name_reply(conn, cookie, &prop, &error); 137 | 138 | prop.name[prop.name_len] = '\0'; 139 | if (printcommand) 140 | { 141 | /* FIXME: Need to escape : in prop.name. */ 142 | printf("'%s':'xdotool windowmap 0x%x windowraise 0x%x'\n", 143 | prop.name, children[i], children[i]); 144 | } 145 | else 146 | { 147 | puts(prop.name); 148 | } 149 | } 150 | } 151 | 152 | free(attr); 153 | } 154 | 155 | free(reply); 156 | 157 | return 0; 158 | } 159 | 160 | void init(void) 161 | { 162 | int scrno, i; 163 | xcb_screen_iterator_t iter; 164 | 165 | conn = xcb_connect(NULL, &scrno); 166 | if (!conn) 167 | { 168 | fprintf(stderr, "can't connect to an X server\n"); 169 | exit(1); 170 | } 171 | 172 | iter = xcb_setup_roots_iterator(xcb_get_setup(conn)); 173 | 174 | for (i = 0; i < scrno; ++i) 175 | { 176 | xcb_screen_next(&iter); 177 | } 178 | 179 | screen = iter.data; 180 | 181 | if (!screen) 182 | { 183 | fprintf(stderr, "can't get the current screen\n"); 184 | xcb_disconnect(conn); 185 | exit(1); 186 | } 187 | } 188 | 189 | void cleanup(void) 190 | { 191 | xcb_disconnect(conn); 192 | } 193 | 194 | /* 195 | * Get a defined atom from the X server. 196 | */ 197 | xcb_atom_t getatom(char *atom_name) 198 | { 199 | xcb_intern_atom_cookie_t atom_cookie; 200 | xcb_atom_t atom; 201 | xcb_intern_atom_reply_t *rep; 202 | 203 | atom_cookie = xcb_intern_atom(conn, 0, strlen(atom_name), atom_name); 204 | rep = xcb_intern_atom_reply(conn, atom_cookie, NULL); 205 | if (NULL != rep) 206 | { 207 | atom = rep->atom; 208 | free(rep); 209 | return atom; 210 | } 211 | 212 | /* 213 | * XXX Note that we return 0 as an atom if anything goes wrong. 214 | * Might become interesting. 215 | */ 216 | return 0; 217 | } 218 | 219 | void printhelp(void) 220 | { 221 | printf("hidden: Usage: hidden [-c]\n"); 222 | printf(" -c print 9menu/xdotool compatible output.\n"); 223 | } 224 | 225 | int main(int argc, char **argv) 226 | { 227 | int ch; /* Option character */ 228 | 229 | while (1) 230 | { 231 | ch = getopt(argc, argv, "c"); 232 | if (-1 == ch) 233 | { 234 | /* No more options, break out of while loop. */ 235 | break; 236 | } 237 | switch (ch) 238 | { 239 | case 'c': 240 | printcommand = true; 241 | break; 242 | 243 | default: 244 | printhelp(); 245 | exit(0); 246 | } /* switch ch */ 247 | } /* while 1 */ 248 | 249 | init(); 250 | wm_state = getatom("WM_STATE"); 251 | findhidden(); 252 | cleanup(); 253 | exit(0); 254 | } 255 | -------------------------------------------------------------------------------- /hidden.man: -------------------------------------------------------------------------------- 1 | .TH hidden 1 "Mar 09, 2012" "" "" 2 | .SH NAME 3 | hidden \- list iconified windows 4 | .SH SYNOPSIS 5 | .B hidden 6 | [ 7 | .B \-c 8 | ] 9 | 10 | .SH DESCRIPTION 11 | .B hidden\fP lists all windows on an X server with WM_STATE Iconic. 12 | .SH OPTIONS 13 | .PP 14 | \-c prints a command suitable to get the window back again. 15 | 16 | .SH ENVIRONMENT 17 | .B hidden\fP obeys the $DISPLAY variable. 18 | .SH AUTHOR 19 | Michael Cardell Widerkrantz . 20 | -------------------------------------------------------------------------------- /list.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "list.h" 4 | 5 | #ifdef DMALLOC 6 | #include "dmalloc.h" 7 | #endif 8 | 9 | #ifdef DEBUG 10 | #define PDEBUG(Args...) \ 11 | do { fprintf(stderr, "mcwm: "); fprintf(stderr, ##Args); } while(0) 12 | #define D(x) x 13 | #else 14 | #define PDEBUG(Args...) 15 | #define D(x) 16 | #endif 17 | 18 | /* 19 | * Move element in item to the head of list mainlist. 20 | */ 21 | void movetohead(struct item **mainlist, struct item *item) 22 | { 23 | if (NULL == item || NULL == mainlist || NULL == *mainlist) 24 | { 25 | return; 26 | } 27 | 28 | if (*mainlist == item) 29 | { 30 | /* item is NULL or we're already at head. Do nothing. */ 31 | return; 32 | } 33 | 34 | /* Braid together the list where we are now. */ 35 | if (NULL != item->prev) 36 | { 37 | item->prev->next = item->next; 38 | } 39 | 40 | if (NULL != item->next) 41 | { 42 | item->next->prev = item->prev; 43 | } 44 | 45 | /* Now we'at head, so no one before us. */ 46 | item->prev = NULL; 47 | 48 | /* Old head is our next. */ 49 | item->next = *mainlist; 50 | 51 | /* Old head needs to know about us. */ 52 | item->next->prev = item; 53 | 54 | /* Remember the new head. */ 55 | *mainlist = item; 56 | } 57 | 58 | /* 59 | * Create space for a new item and add it to the head of mainlist. 60 | * 61 | * Returns item or NULL if out of memory. 62 | */ 63 | struct item *additem(struct item **mainlist) 64 | { 65 | struct item *item; 66 | 67 | if (NULL == (item = (struct item *) malloc(sizeof (struct item)))) 68 | { 69 | return NULL; 70 | } 71 | 72 | if (NULL == *mainlist) 73 | { 74 | /* First in the list. */ 75 | 76 | item->prev = NULL; 77 | item->next = NULL; 78 | } 79 | else 80 | { 81 | /* Add to beginning of list. */ 82 | 83 | item->next = *mainlist; 84 | item->next->prev = item; 85 | item->prev = NULL; 86 | } 87 | 88 | *mainlist = item; 89 | 90 | return item; 91 | } 92 | 93 | void delitem(struct item **mainlist, struct item *item) 94 | { 95 | struct item *ml = *mainlist; 96 | 97 | if (NULL == mainlist || NULL == *mainlist || NULL == item) 98 | { 99 | return; 100 | } 101 | 102 | if (item == *mainlist) 103 | { 104 | /* First entry was removed. Remember the next one instead. */ 105 | *mainlist = ml->next; 106 | } 107 | else 108 | { 109 | item->prev->next = item->next; 110 | 111 | if (NULL != item->next) 112 | { 113 | /* This is not the last item in the list. */ 114 | item->next->prev = item->prev; 115 | } 116 | } 117 | 118 | free(item); 119 | } 120 | 121 | void freeitem(struct item **list, int *stored, 122 | struct item *item) 123 | { 124 | if (NULL == list || NULL == *list || NULL == item) 125 | { 126 | return; 127 | } 128 | 129 | if (NULL != item->data) 130 | { 131 | free(item->data); 132 | item->data = NULL; 133 | } 134 | 135 | delitem(list, item); 136 | 137 | if (NULL != stored) 138 | { 139 | (*stored) --; 140 | } 141 | } 142 | 143 | /* 144 | * Delete all elements in list and free memory resources. 145 | */ 146 | void delallitems(struct item **list, int *stored) 147 | { 148 | struct item *item; 149 | struct item *next; 150 | 151 | for (item = *list; item != NULL; item = next) 152 | { 153 | next = item->next; 154 | free(item->data); 155 | delitem(list, item); 156 | } 157 | 158 | if (NULL != stored) 159 | { 160 | (*stored) = 0; 161 | } 162 | } 163 | 164 | void listitems(struct item *mainlist) 165 | { 166 | struct item *item; 167 | int i; 168 | 169 | for (item = mainlist, i = 1; item != NULL; item = item->next, i ++) 170 | { 171 | printf("item #%d (stored at %p).\n", i, (void *)item); 172 | } 173 | } 174 | 175 | #if 0 176 | 177 | void listall(struct item *mainlist) 178 | { 179 | struct item *item; 180 | int i; 181 | 182 | printf("Listing all:\n"); 183 | 184 | for (item = mainlist, i = 1; item != NULL; item = item->next, i ++) 185 | { 186 | printf("%d at %p: %s.\n", i, (void *)item, (char *)item->data); 187 | printf(" prev: %p\n", item->prev); 188 | printf(" next: %p\n", item->next); 189 | } 190 | } 191 | 192 | int main(void) 193 | { 194 | struct item *mainlist = NULL; 195 | struct item *item1; 196 | struct item *item2; 197 | struct item *item3; 198 | struct item *item4; 199 | struct item *item; 200 | struct item *nextitem; 201 | int i; 202 | char *foo1 = "1"; 203 | char *foo2 = "2"; 204 | char *foo3 = "3"; 205 | char *foo4 = "4"; 206 | 207 | item1 = additem(&mainlist); 208 | if (NULL == item1) 209 | { 210 | printf("Couldn't allocate.\n"); 211 | exit(1); 212 | } 213 | item1->data = foo1; 214 | printf("Current elements:\n"); 215 | listall(mainlist); 216 | 217 | item2 = additem(&mainlist); 218 | if (NULL == item2) 219 | { 220 | printf("Couldn't allocate.\n"); 221 | exit(1); 222 | } 223 | item2->data = foo2; 224 | printf("Current elements:\n"); 225 | listall(mainlist); 226 | 227 | item3 = additem(&mainlist); 228 | if (NULL == item3) 229 | { 230 | printf("Couldn't allocate.\n"); 231 | exit(1); 232 | } 233 | item3->data = foo3; 234 | printf("Current elements:\n"); 235 | listall(mainlist); 236 | 237 | item4 = additem(&mainlist); 238 | if (NULL == item4) 239 | { 240 | printf("Couldn't allocate.\n"); 241 | exit(1); 242 | } 243 | item4->data = foo4; 244 | printf("Current elements:\n"); 245 | listall(mainlist); 246 | 247 | printf("----------------------------------------------------------------------\n"); 248 | 249 | printf("Moving item3 to be after item2\n"); 250 | movetonext(&mainlist, item2, item3); 251 | printf("Current elements:\n"); 252 | listall(mainlist); 253 | 254 | printf("----------------------------------------------------------------------\n"); 255 | 256 | printf("Moving head! item4 to be after item2\n"); 257 | movetonext(&mainlist, item2, item4); 258 | printf("Current elements:\n"); 259 | listall(mainlist); 260 | 261 | printf("----------------------------------------------------------------------\n"); 262 | 263 | printf("Moving tail! item1 to be after item2\n"); 264 | movetonext(&mainlist, item2, item1); 265 | printf("Current elements:\n"); 266 | listall(mainlist); 267 | 268 | printf("----------------------------------------------------------------------\n"); 269 | 270 | printf("Moving head to be after tail.\n"); 271 | movetonext(&mainlist, item3, item2); 272 | printf("Current elements:\n"); 273 | listall(mainlist); 274 | 275 | printf("Moving all the items after each other.\n"); 276 | /* item3 is tail. work backwards. */ 277 | for (item = mainlist, i = 1; 278 | item != NULL; 279 | item = item->next, i ++) 280 | { 281 | for (nextitem = item2; nextitem != NULL; nextitem = nextitem->prev) 282 | { 283 | movetonext(&mainlist, nextitem, item); 284 | printf("Current elements:\n"); 285 | listall(mainlist); 286 | } 287 | } 288 | 289 | printf("----------------------------------------------------------------------\n"); 290 | 291 | #if 0 292 | movetohead(&mainlist, item2); 293 | printf("Current elements:\n"); 294 | listall(mainlist); 295 | 296 | printf("----------------------------------------------------------------------\n"); 297 | #endif 298 | 299 | printf("Deleting item stored at %p\n", item3); 300 | delitem(&mainlist, item3); 301 | printf("Current elements:\n"); 302 | listall(mainlist); 303 | 304 | puts(""); 305 | 306 | delitem(&mainlist, item2); 307 | printf("Current elements:\n"); 308 | listall(mainlist); 309 | 310 | puts(""); 311 | 312 | delitem(&mainlist, item1); 313 | printf("Current elements:\n"); 314 | listall(mainlist); 315 | 316 | puts(""); 317 | 318 | printf("----------------------------------------------------------------------\n"); 319 | 320 | exit(0); 321 | } 322 | #endif 323 | -------------------------------------------------------------------------------- /list.h: -------------------------------------------------------------------------------- 1 | struct item 2 | { 3 | void *data; 4 | struct item *prev; 5 | struct item *next; 6 | }; 7 | 8 | /* 9 | * Move element in item to the head of list mainlist. 10 | */ 11 | void movetohead(struct item **mainlist, struct item *item); 12 | 13 | /* 14 | * Create space for a new item and add it to the head of mainlist. 15 | * 16 | * Returns item or NULL if out of memory. 17 | */ 18 | struct item *additem(struct item **mainlist); 19 | 20 | /* 21 | * Delete item from list mainlist. 22 | */ 23 | void delitem(struct item **mainlist, struct item *item); 24 | 25 | /* 26 | * Free any data in current item and then delete item. Optionally 27 | * update number of items in list if stored != NULL. 28 | */ 29 | void freeitem(struct item **list, int *stored, 30 | struct item *item); 31 | 32 | /* 33 | * Delete all items in list. Optionally update number of items in list 34 | * if stored != NULL. 35 | */ 36 | void delallitems(struct item **list, int *stored); 37 | 38 | /* 39 | * Print all items in mainlist on stdout. 40 | */ 41 | void listitems(struct item *mainlist); 42 | -------------------------------------------------------------------------------- /mcwm-screen-20110308.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbidulock/mcwm/cbbddc3a1aeb1d817980fe95d4afe665526b9a31/mcwm-screen-20110308.png -------------------------------------------------------------------------------- /mcwm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mcwm, a small window manager for the X Window System using the X 3 | * protocol C Binding libraries. 4 | * 5 | * For 'user' configurable stuff, see config.h. 6 | * 7 | * MC, mc at the domain hack.org 8 | * http://hack.org/mc/ 9 | * 10 | * Copyright (c) 2010, 2011, 2012, 2013, 2014 Michael Cardell 11 | * Widerkrantz, mc at the domain hack.org. 12 | * 13 | * Permission to use, copy, modify, and/or distribute this software for any 14 | * purpose with or without fee is hereby granted, provided that the above 15 | * copyright notice and this permission notice appear in all copies. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 18 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 19 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 20 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 21 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 22 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 23 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 24 | */ 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | #include 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | #include 47 | 48 | #include 49 | #include 50 | 51 | #ifdef DEBUG 52 | #include "events.h" 53 | #endif 54 | 55 | #include "list.h" 56 | 57 | /* Check here for user configurable parts: */ 58 | #include "config.h" 59 | 60 | #ifdef DMALLOC 61 | #include "dmalloc.h" 62 | #endif 63 | 64 | #ifdef DEBUG 65 | #define PDEBUG(Args...) \ 66 | do { fprintf(stderr, "mcwm: "); fprintf(stderr, ##Args); } while(0) 67 | #define D(x) x 68 | #else 69 | #define PDEBUG(Args...) 70 | #define D(x) 71 | #endif 72 | 73 | 74 | /* Internal Constants. */ 75 | 76 | /* We're currently moving a window with the mouse. */ 77 | #define MCWM_MOVE 2 78 | 79 | /* We're currently resizing a window with the mouse. */ 80 | #define MCWM_RESIZE 3 81 | 82 | /* 83 | * We're currently tabbing around the window list, looking for a new 84 | * window to focus on. 85 | */ 86 | #define MCWM_TABBING 4 87 | 88 | /* Number of workspaces. */ 89 | #define WORKSPACES 10 90 | 91 | /* Value in WM hint which means this window is fixed on all workspaces. */ 92 | #define NET_WM_FIXED 0xffffffff 93 | 94 | /* This means we didn't get any window hint at all. */ 95 | #define MCWM_NOWS 0xfffffffe 96 | 97 | 98 | /* Types. */ 99 | 100 | /* All our key shortcuts. */ 101 | typedef enum { 102 | KEY_F, 103 | KEY_H, 104 | KEY_J, 105 | KEY_K, 106 | KEY_L, 107 | KEY_M, 108 | KEY_R, 109 | KEY_RET, 110 | KEY_X, 111 | KEY_TAB, 112 | KEY_BACKTAB, 113 | KEY_1, 114 | KEY_2, 115 | KEY_3, 116 | KEY_4, 117 | KEY_5, 118 | KEY_6, 119 | KEY_7, 120 | KEY_8, 121 | KEY_9, 122 | KEY_0, 123 | KEY_Y, 124 | KEY_U, 125 | KEY_B, 126 | KEY_N, 127 | KEY_END, 128 | KEY_PREVSCR, 129 | KEY_NEXTSCR, 130 | KEY_ICONIFY, 131 | KEY_PREVWS, 132 | KEY_NEXTWS, 133 | KEY_MAX 134 | } key_enum_t; 135 | 136 | struct monitor 137 | { 138 | xcb_randr_output_t id; 139 | char *name; 140 | int16_t x; /* X and Y. */ 141 | int16_t y; 142 | uint16_t width; /* Width in pixels. */ 143 | uint16_t height; /* Height in pixels. */ 144 | struct item *item; /* Pointer to our place in output list. */ 145 | }; 146 | 147 | struct sizepos 148 | { 149 | int16_t x; 150 | int16_t y; 151 | uint16_t width; 152 | uint16_t height; 153 | }; 154 | 155 | /* Everything we know about a window. */ 156 | struct client 157 | { 158 | xcb_drawable_t id; /* ID of this window. */ 159 | bool usercoord; /* X,Y was set by -geom. */ 160 | int16_t x; /* X coordinate. */ 161 | int16_t y; /* Y coordinate. */ 162 | uint16_t width; /* Width in pixels. */ 163 | uint16_t height; /* Height in pixels. */ 164 | struct sizepos origsize; /* Original size if we're currently maxed. */ 165 | uint16_t min_width, min_height; /* Hints from application. */ 166 | uint16_t max_width, max_height; 167 | int32_t width_inc, height_inc; 168 | int32_t base_width, base_height; 169 | bool vertmaxed; /* Vertically maximized? */ 170 | bool maxed; /* Totally maximized? */ 171 | bool fixed; /* Visible on all workspaces? */ 172 | struct monitor *monitor; /* The physical output this window is on. */ 173 | struct item *winitem; /* Pointer to our place in global windows list. */ 174 | struct item *wsitem[WORKSPACES]; /* Pointer to our place in every 175 | * workspace window list. */ 176 | }; 177 | 178 | /* Window configuration data. */ 179 | struct winconf 180 | { 181 | int16_t x; 182 | int16_t y; 183 | uint16_t width; 184 | uint16_t height; 185 | uint8_t stackmode; 186 | xcb_window_t sibling; 187 | uint16_t borderwidth; 188 | }; 189 | 190 | 191 | /* Globals */ 192 | 193 | int sigcode; /* Signal code. Non-zero if we've been 194 | * interruped by a signal. */ 195 | xcb_connection_t *conn; /* Connection to X server. */ 196 | xcb_screen_t *screen; /* Our current screen. */ 197 | int randrbase; /* Beginning of RANDR extension events. */ 198 | uint32_t curws = 0; /* Current workspace. */ 199 | struct client *focuswin; /* Current focus window. */ 200 | struct client *lastfocuswin; /* Last focused window. NOTE! Only 201 | * used to communicate between 202 | * start and end of tabbing 203 | * mode. */ 204 | struct item *winlist = NULL; /* Global list of all client windows. */ 205 | struct item *monlist = NULL; /* List of all physical monitor outputs. */ 206 | int mode = 0; /* Internal mode, such as move or resize */ 207 | 208 | /* 209 | * Workspace list: Every workspace has a list of all visible 210 | * windows. 211 | */ 212 | struct item *wslist[WORKSPACES] = 213 | { 214 | NULL, 215 | NULL, 216 | NULL, 217 | NULL, 218 | NULL, 219 | NULL, 220 | NULL, 221 | NULL, 222 | NULL, 223 | NULL 224 | }; 225 | 226 | /* Shortcut key type and initializiation. */ 227 | struct keys 228 | { 229 | xcb_keysym_t keysym; 230 | xcb_keycode_t keycode; 231 | } keys[KEY_MAX] = 232 | { 233 | { USERKEY_FIX, 0 }, 234 | { USERKEY_MOVE_LEFT, 0 }, 235 | { USERKEY_MOVE_DOWN, 0 }, 236 | { USERKEY_MOVE_UP, 0 }, 237 | { USERKEY_MOVE_RIGHT, 0 }, 238 | { USERKEY_MAXVERT, 0 }, 239 | { USERKEY_RAISE, 0 }, 240 | { USERKEY_TERMINAL, 0 }, 241 | { USERKEY_MAX, 0 }, 242 | { USERKEY_CHANGE, 0 }, 243 | { USERKEY_BACKCHANGE, 0 }, 244 | { USERKEY_WS1, 0 }, 245 | { USERKEY_WS2, 0 }, 246 | { USERKEY_WS3, 0 }, 247 | { USERKEY_WS4, 0 }, 248 | { USERKEY_WS5, 0 }, 249 | { USERKEY_WS6, 0 }, 250 | { USERKEY_WS7, 0 }, 251 | { USERKEY_WS8, 0 }, 252 | { USERKEY_WS9, 0 }, 253 | { USERKEY_WS10, 0 }, 254 | { USERKEY_TOPLEFT, 0 }, 255 | { USERKEY_TOPRIGHT, 0 }, 256 | { USERKEY_BOTLEFT, 0 }, 257 | { USERKEY_BOTRIGHT, 0 }, 258 | { USERKEY_DELETE, 0 }, 259 | { USERKEY_PREVSCREEN, 0 }, 260 | { USERKEY_NEXTSCREEN, 0 }, 261 | { USERKEY_ICONIFY, 0 }, 262 | { USERKEY_PREVWS, 0 }, 263 | { USERKEY_NEXTWS, 0 }, 264 | }; 265 | 266 | /* All keycodes generating our MODKEY mask. */ 267 | struct modkeycodes 268 | { 269 | xcb_keycode_t *keycodes; 270 | unsigned len; 271 | } modkeys = 272 | { 273 | NULL, 274 | 0 275 | }; 276 | 277 | /* Global configuration. */ 278 | struct conf 279 | { 280 | int borderwidth; /* Do we draw borders? If so, how large? */ 281 | int snapmargin; /* Do we have snap margin? If so, how large? */ 282 | char *terminal; /* Path to terminal to start. */ 283 | uint32_t focuscol; /* Focused border colour. */ 284 | uint32_t unfocuscol; /* Unfocused border colour. */ 285 | uint32_t fixedcol; /* Fixed windows border colour. */ 286 | bool allowicons; /* Allow windows to be unmapped. */ 287 | } conf; 288 | 289 | xcb_atom_t atom_desktop; /* 290 | * EWMH _NET_WM_DESKTOP hint that says 291 | * what workspace a window should be 292 | * on. 293 | */ 294 | 295 | xcb_atom_t wm_delete_window; /* WM_DELETE_WINDOW event to close windows. */ 296 | xcb_atom_t wm_change_state; 297 | xcb_atom_t wm_state; 298 | xcb_atom_t wm_protocols; /* WM_PROTOCOLS. */ 299 | 300 | 301 | /* Functions declerations. */ 302 | 303 | static void finishtabbing(void); 304 | static struct modkeycodes getmodkeys(xcb_mod_mask_t modmask); 305 | static void cleanup(int code); 306 | static void arrangewindows(void); 307 | static void setwmdesktop(xcb_drawable_t win, uint32_t ws); 308 | static int32_t getwmdesktop(xcb_drawable_t win); 309 | static void addtoworkspace(struct client *client, uint32_t ws); 310 | static void delfromworkspace(struct client *client, uint32_t ws); 311 | static void changeworkspace(uint32_t ws); 312 | static void fixwindow(struct client *client, bool setcolour); 313 | static uint32_t getcolor(const char *colstr); 314 | static void forgetclient(struct client *client); 315 | static void forgetwin(xcb_window_t win); 316 | static void fitonscreen(struct client *client); 317 | static void newwin(xcb_window_t win); 318 | static struct client *setupwin(xcb_window_t win); 319 | static xcb_keycode_t keysymtokeycode(xcb_keysym_t keysym, 320 | xcb_key_symbols_t *keysyms); 321 | static int setupkeys(void); 322 | static int setupscreen(void); 323 | static int setuprandr(void); 324 | static void getrandr(void); 325 | static void getoutputs(xcb_randr_output_t *outputs, int len, 326 | xcb_timestamp_t timestamp); 327 | void arrbymon(struct monitor *monitor); 328 | static struct monitor *findmonitor(xcb_randr_output_t id); 329 | static struct monitor *findclones(xcb_randr_output_t id, int16_t x, int16_t y); 330 | static struct monitor *findmonbycoord(int16_t x, int16_t y); 331 | static void delmonitor(struct monitor *mon); 332 | static struct monitor *addmonitor(xcb_randr_output_t id, char *name, 333 | uint32_t x, uint32_t y, uint16_t width, 334 | uint16_t height); 335 | static void raisewindow(xcb_drawable_t win); 336 | static void raiseorlower(struct client *client); 337 | static void movelim(struct client *client); 338 | static void movewindow(xcb_drawable_t win, uint16_t x, uint16_t y); 339 | static struct client *findclient(xcb_drawable_t win); 340 | static void focusnext(bool reverse); 341 | static void setunfocus(xcb_drawable_t win); 342 | static void setfocus(struct client *client); 343 | static int start(char *program); 344 | static void resizelim(struct client *client); 345 | static void moveresize(xcb_drawable_t win, uint16_t x, uint16_t y, 346 | uint16_t width, uint16_t height); 347 | static void resize(xcb_drawable_t win, uint16_t width, uint16_t height); 348 | static void resizestep(struct client *client, char direction); 349 | static void snapwindow(struct client *client, int snap_mode); 350 | static void mousemove(struct client *client, int rel_x, int rel_y); 351 | static void mouseresize(struct client *client, int rel_x, int rel_y); 352 | static void movestep(struct client *client, char direction); 353 | static void setborders(struct client *client, int width); 354 | static void unmax(struct client *client); 355 | static void maximize(struct client *client); 356 | static void maxvert(struct client *client); 357 | static void hide(struct client *client); 358 | static bool getpointer(xcb_drawable_t win, int16_t *x, int16_t *y); 359 | static bool getgeom(xcb_drawable_t win, int16_t *x, int16_t *y, uint16_t *width, 360 | uint16_t *height); 361 | static void topleft(void); 362 | static void topright(void); 363 | static void botleft(void); 364 | static void botright(void); 365 | static void deletewin(void); 366 | static void prevscreen(void); 367 | static void nextscreen(void); 368 | static void handle_keypress(xcb_key_press_event_t *ev); 369 | static void configwin(xcb_window_t win, uint16_t mask, struct winconf wc); 370 | static void configurerequest(xcb_configure_request_event_t *e); 371 | static void events(void); 372 | static void printhelp(void); 373 | static void sigcatch(int sig); 374 | static xcb_atom_t getatom(char *atom_name); 375 | 376 | 377 | /* Function bodies. */ 378 | 379 | /* 380 | * MODKEY was released after tabbing around the 381 | * workspace window ring. This means this mode is 382 | * finished and we have found a new focus window. 383 | * 384 | * We need to move first the window we used to focus 385 | * on to the head of the window list and then move the 386 | * new focus to the head of the list as well. The list 387 | * should always start with the window we're focusing 388 | * on. 389 | */ 390 | void finishtabbing(void) 391 | { 392 | mode = 0; 393 | 394 | if (NULL != lastfocuswin) 395 | { 396 | movetohead(&wslist[curws], lastfocuswin->wsitem[curws]); 397 | lastfocuswin = NULL; 398 | } 399 | 400 | movetohead(&wslist[curws], focuswin->wsitem[curws]); 401 | } 402 | 403 | /* 404 | * Find out what keycode modmask is bound to. Returns a struct. If the 405 | * len in the struct is 0 something went wrong. 406 | */ 407 | struct modkeycodes getmodkeys(xcb_mod_mask_t modmask) 408 | { 409 | xcb_get_modifier_mapping_cookie_t cookie; 410 | xcb_get_modifier_mapping_reply_t *reply; 411 | xcb_keycode_t *modmap; 412 | struct modkeycodes keycodes = { 413 | NULL, 414 | 0 415 | }; 416 | int mask; 417 | unsigned i; 418 | const xcb_mod_mask_t masks[8] = { XCB_MOD_MASK_SHIFT, 419 | XCB_MOD_MASK_LOCK, 420 | XCB_MOD_MASK_CONTROL, 421 | XCB_MOD_MASK_1, 422 | XCB_MOD_MASK_2, 423 | XCB_MOD_MASK_3, 424 | XCB_MOD_MASK_4, 425 | XCB_MOD_MASK_5 }; 426 | 427 | cookie = xcb_get_modifier_mapping_unchecked(conn); 428 | 429 | if ((reply = xcb_get_modifier_mapping_reply(conn, cookie, NULL)) == NULL) 430 | { 431 | return keycodes; 432 | } 433 | 434 | if (NULL == (keycodes.keycodes = calloc(reply->keycodes_per_modifier, 435 | sizeof (xcb_keycode_t)))) 436 | { 437 | PDEBUG("Out of memory.\n"); 438 | return keycodes; 439 | } 440 | 441 | modmap = xcb_get_modifier_mapping_keycodes(reply); 442 | 443 | /* 444 | * The modmap now contains keycodes. 445 | * 446 | * The number of keycodes in the list is 8 * 447 | * keycodes_per_modifier. The keycodes are divided into eight 448 | * sets, with each set containing keycodes_per_modifier elements. 449 | * 450 | * Each set corresponds to a modifier in masks[] in the order 451 | * specified above. 452 | * 453 | * The keycodes_per_modifier value is chosen arbitrarily by the 454 | * server. Zeroes are used to fill in unused elements within each 455 | * set. 456 | */ 457 | for (mask = 0; mask < 8; mask ++) 458 | { 459 | if (masks[mask] == modmask) 460 | { 461 | for (i = 0; i < reply->keycodes_per_modifier; i ++) 462 | { 463 | if (0 != modmap[mask * reply->keycodes_per_modifier + i]) 464 | { 465 | keycodes.keycodes[i] 466 | = modmap[mask * reply->keycodes_per_modifier + i]; 467 | keycodes.len ++; 468 | } 469 | } 470 | 471 | PDEBUG("Got %d keycodes.\n", keycodes.len); 472 | } 473 | } 474 | 475 | free(reply); 476 | 477 | return keycodes; 478 | } 479 | 480 | /* 481 | * Set keyboard focus to follow mouse pointer. Then exit. 482 | * 483 | * We don't need to bother mapping all windows we know about. They 484 | * should all be in the X server's Save Set and should be mapped 485 | * automagically. 486 | */ 487 | void cleanup(int code) 488 | { 489 | xcb_set_input_focus(conn, XCB_NONE, 490 | XCB_INPUT_FOCUS_POINTER_ROOT, 491 | XCB_CURRENT_TIME); 492 | xcb_flush(conn); 493 | xcb_disconnect(conn); 494 | exit(code); 495 | } 496 | 497 | /* 498 | * Rearrange windows to fit new screen size. 499 | */ 500 | void arrangewindows(void) 501 | { 502 | struct item *item; 503 | struct client *client; 504 | 505 | /* 506 | * Go through all windows. If they don't fit on the new screen, 507 | * move them around and resize them as necessary. 508 | */ 509 | for (item = winlist; item != NULL; item = item->next) 510 | { 511 | client = item->data; 512 | fitonscreen(client); 513 | } 514 | } 515 | 516 | /* Set the EWMH hint that window win belongs on workspace ws. */ 517 | void setwmdesktop(xcb_drawable_t win, uint32_t ws) 518 | { 519 | PDEBUG("Changing _NET_WM_DESKTOP on window %d to %d\n", win, ws); 520 | 521 | xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win, 522 | atom_desktop, XCB_ATOM_CARDINAL, 32, 1, 523 | &ws); 524 | } 525 | 526 | /* 527 | * Get EWWM hint so we might know what workspace window win should be 528 | * visible on. 529 | * 530 | * Returns either workspace, NET_WM_FIXED if this window should be 531 | * visible on all workspaces or MCWM_NOWS if we didn't find any hints. 532 | */ 533 | int32_t getwmdesktop(xcb_drawable_t win) 534 | { 535 | xcb_get_property_reply_t *reply; 536 | xcb_get_property_cookie_t cookie; 537 | uint32_t *wsp; 538 | uint32_t ws; 539 | 540 | cookie = xcb_get_property(conn, false, win, atom_desktop, 541 | XCB_GET_PROPERTY_TYPE_ANY, 0, 542 | sizeof (int32_t)); 543 | 544 | reply = xcb_get_property_reply(conn, cookie, NULL); 545 | if (NULL == reply) 546 | { 547 | fprintf(stderr, "mcwm: Couldn't get properties for win %d\n", win); 548 | return MCWM_NOWS; 549 | } 550 | 551 | /* Length is 0 if we didn't find it. */ 552 | if (0 == xcb_get_property_value_length(reply)) 553 | { 554 | PDEBUG("_NET_WM_DESKTOP reply was 0 length.\n"); 555 | goto bad; 556 | } 557 | 558 | wsp = xcb_get_property_value(reply); 559 | 560 | ws = *wsp; 561 | 562 | PDEBUG("got _NET_WM_DESKTOP: %d stored at %p.\n", ws, (void *)wsp); 563 | 564 | free(reply); 565 | 566 | return ws; 567 | 568 | bad: 569 | free(reply); 570 | return MCWM_NOWS; 571 | } 572 | 573 | /* Add a window, specified by client, to workspace ws. */ 574 | void addtoworkspace(struct client *client, uint32_t ws) 575 | { 576 | struct item *item; 577 | 578 | item = additem(&wslist[ws]); 579 | if (NULL == item) 580 | { 581 | PDEBUG("addtoworkspace: Out of memory.\n"); 582 | return; 583 | } 584 | 585 | /* Remember our place in the workspace window list. */ 586 | client->wsitem[ws] = item; 587 | 588 | /* Remember the data. */ 589 | item->data = client; 590 | 591 | /* 592 | * Set window hint property so we can survive a crash. 593 | * 594 | * Fixed windows have their own special WM hint. We don't want to 595 | * mess with that. 596 | */ 597 | if (!client->fixed) 598 | { 599 | setwmdesktop(client->id, ws); 600 | } 601 | } 602 | 603 | /* Delete window client from workspace ws. */ 604 | void delfromworkspace(struct client *client, uint32_t ws) 605 | { 606 | delitem(&wslist[ws], client->wsitem[ws]); 607 | 608 | /* Reset our place in the workspace window list. */ 609 | client->wsitem[ws] = NULL; 610 | } 611 | 612 | /* Change current workspace to ws. */ 613 | void changeworkspace(uint32_t ws) 614 | { 615 | struct item *item; 616 | struct client *client; 617 | 618 | if (ws == curws) 619 | { 620 | PDEBUG("Changing to same workspace!\n"); 621 | return; 622 | } 623 | 624 | PDEBUG("Changing from workspace #%d to #%d\n", curws, ws); 625 | 626 | /* 627 | * We lose our focus if the window we focus isn't fixed. An 628 | * EnterNotify event will set focus later. 629 | */ 630 | if (NULL != focuswin && !focuswin->fixed) 631 | { 632 | setunfocus(focuswin->id); 633 | focuswin = NULL; 634 | } 635 | 636 | /* Go through list of current ws. Unmap everything that isn't fixed. */ 637 | for (item = wslist[curws]; item != NULL; item = item->next) 638 | { 639 | client = item->data; 640 | 641 | PDEBUG("changeworkspace. unmap phase. ws #%d, client-fixed: %d\n", 642 | curws, client->fixed); 643 | 644 | if (!client->fixed) 645 | { 646 | /* 647 | * This is an ordinary window. Just unmap it. Note that 648 | * this will generate an unnecessary UnmapNotify event 649 | * which we will try to handle later. 650 | */ 651 | xcb_unmap_window(conn, client->id); 652 | } 653 | } 654 | 655 | /* Go through list of new ws. Map everything that isn't fixed. */ 656 | for (item = wslist[ws]; item != NULL; item = item->next) 657 | { 658 | client = item->data; 659 | 660 | PDEBUG("changeworkspace. map phase. ws #%d, client-fixed: %d\n", 661 | ws, client->fixed); 662 | 663 | /* Fixed windows are already mapped. Map everything else. */ 664 | if (!client->fixed) 665 | { 666 | xcb_map_window(conn, client->id); 667 | } 668 | } 669 | 670 | xcb_flush(conn); 671 | 672 | curws = ws; 673 | } 674 | 675 | /* 676 | * Fix or unfix a window client from all workspaces. If setcolour is 677 | * set, also change back to ordinary focus colour when unfixing. 678 | */ 679 | void fixwindow(struct client *client, bool setcolour) 680 | { 681 | uint32_t values[1]; 682 | uint32_t ws; 683 | 684 | if (NULL == client) 685 | { 686 | return; 687 | } 688 | 689 | if (client->fixed) 690 | { 691 | client->fixed = false; 692 | setwmdesktop(client->id, curws); 693 | 694 | if (setcolour) 695 | { 696 | /* Set border color to ordinary focus colour. */ 697 | values[0] = conf.focuscol; 698 | xcb_change_window_attributes(conn, client->id, XCB_CW_BORDER_PIXEL, 699 | values); 700 | } 701 | 702 | /* Delete from all workspace lists except current. */ 703 | for (ws = 0; ws < WORKSPACES; ws ++) 704 | { 705 | if (ws != curws) 706 | { 707 | delfromworkspace(client, ws); 708 | } 709 | } 710 | } 711 | else 712 | { 713 | /* 714 | * First raise the window. If we're going to another desktop 715 | * we don't want this fixed window to be occluded behind 716 | * something else. 717 | */ 718 | raisewindow(client->id); 719 | 720 | client->fixed = true; 721 | setwmdesktop(client->id, NET_WM_FIXED); 722 | 723 | /* Add window to all workspace lists. */ 724 | for (ws = 0; ws < WORKSPACES; ws ++) 725 | { 726 | if (ws != curws) 727 | { 728 | addtoworkspace(client, ws); 729 | } 730 | } 731 | 732 | if (setcolour) 733 | { 734 | /* Set border color to fixed colour. */ 735 | values[0] = conf.fixedcol; 736 | xcb_change_window_attributes(conn, client->id, XCB_CW_BORDER_PIXEL, 737 | values); 738 | } 739 | } 740 | 741 | xcb_flush(conn); 742 | } 743 | 744 | /* 745 | * Get the pixel values of a named colour colstr. 746 | * 747 | * Returns pixel values. 748 | * */ 749 | uint32_t getcolor(const char *colstr) 750 | { 751 | xcb_alloc_named_color_reply_t *col_reply; 752 | xcb_colormap_t colormap; 753 | xcb_generic_error_t *error; 754 | xcb_alloc_named_color_cookie_t colcookie; 755 | 756 | colormap = screen->default_colormap; 757 | colcookie = xcb_alloc_named_color(conn, colormap, strlen(colstr), colstr); 758 | col_reply = xcb_alloc_named_color_reply(conn, colcookie, &error); 759 | if (NULL != error) 760 | { 761 | fprintf(stderr, "mcwm: Couldn't get pixel value for colour %s. " 762 | "Exiting.\n", colstr); 763 | 764 | xcb_disconnect(conn); 765 | exit(1); 766 | } 767 | 768 | return col_reply->pixel; 769 | } 770 | 771 | /* Forget everything about client client. */ 772 | void forgetclient(struct client *client) 773 | { 774 | uint32_t ws; 775 | 776 | if (NULL == client) 777 | { 778 | PDEBUG("forgetclient: client was NULL\n"); 779 | return; 780 | } 781 | 782 | /* 783 | * Delete this client from whatever workspace lists it belongs to. 784 | * Note that it's OK to be on several workspaces at once even if 785 | * you're not fixed. 786 | */ 787 | for (ws = 0; ws < WORKSPACES; ws ++) 788 | { 789 | if (NULL != client->wsitem[ws]) 790 | { 791 | delfromworkspace(client, ws); 792 | } 793 | } 794 | 795 | /* Remove from global window list. */ 796 | freeitem(&winlist, NULL, client->winitem); 797 | } 798 | 799 | /* Forget everything about a client with client->id win. */ 800 | void forgetwin(xcb_window_t win) 801 | { 802 | struct item *item; 803 | struct client *client; 804 | uint32_t ws; 805 | 806 | /* Find this window in the global window list. */ 807 | for (item = winlist; item != NULL; item = item->next) 808 | { 809 | client = item->data; 810 | 811 | /* 812 | * Forget about it completely and free allocated data. 813 | * 814 | * Note that it might already be freed by handling an 815 | * UnmapNotify, so it isn't necessarily an error if we don't 816 | * find it. 817 | */ 818 | PDEBUG("Win %d == client ID %d\n", win, client->id); 819 | if (win == client->id) 820 | { 821 | /* Found it. */ 822 | PDEBUG("Found it. Forgetting...\n"); 823 | 824 | /* 825 | * Delete window from whatever workspace lists it belonged 826 | * to. Note that it's OK to be on several workspaces at 827 | * once. 828 | */ 829 | for (ws = 0; ws < WORKSPACES; ws ++) 830 | { 831 | PDEBUG("Looking in ws #%d.\n", ws); 832 | if (NULL == client->wsitem[ws]) 833 | { 834 | PDEBUG(" but it wasn't there.\n"); 835 | } 836 | else 837 | { 838 | PDEBUG(" found it here. Deleting!\n"); 839 | delfromworkspace(client, ws); 840 | } 841 | } 842 | 843 | free(item->data); 844 | delitem(&winlist, item); 845 | 846 | return; 847 | } 848 | } 849 | } 850 | 851 | /* 852 | * Fit client on physical screen, moving and resizing as necessary. 853 | */ 854 | void fitonscreen(struct client *client) 855 | { 856 | int16_t mon_x; 857 | int16_t mon_y; 858 | uint16_t mon_width; 859 | uint16_t mon_height; 860 | bool willmove = false; 861 | bool willresize = false; 862 | 863 | client->vertmaxed = false; 864 | 865 | if (client->maxed) 866 | { 867 | client->maxed = false; 868 | setborders(client, conf.borderwidth); 869 | } 870 | 871 | if (NULL == client->monitor) 872 | { 873 | /* 874 | * This window isn't attached to any physical monitor. This 875 | * probably means there is no RANDR, so we use the root window 876 | * size. 877 | */ 878 | mon_x = 0; 879 | mon_y = 0; 880 | mon_width = screen->width_in_pixels; 881 | mon_height = screen->height_in_pixels; 882 | } 883 | else 884 | { 885 | mon_x = client->monitor->x; 886 | mon_y = client->monitor->y; 887 | mon_width = client->monitor->width; 888 | mon_height = client->monitor->height; 889 | } 890 | 891 | PDEBUG("Is window outside monitor?\n"); 892 | PDEBUG("x: %d between %d and %d?\n", client->x, mon_x, mon_x + mon_width); 893 | PDEBUG("y: %d between %d and %d?\n", client->y, mon_y, mon_y + mon_height); 894 | 895 | /* Is it outside the physical monitor? */ 896 | if (client->x > mon_x + mon_width) 897 | { 898 | client->x = mon_x + mon_width - client->width; 899 | willmove = true; 900 | } 901 | if (client->y > mon_y + mon_height) 902 | { 903 | client->y = mon_y + mon_height - client->height; 904 | willmove = true; 905 | } 906 | 907 | if (client->x < mon_x) 908 | { 909 | client->x = mon_x; 910 | willmove = true; 911 | } 912 | if (client->y < mon_y) 913 | { 914 | client->y = mon_y; 915 | willmove = true; 916 | } 917 | 918 | /* Is it smaller than it wants to be? */ 919 | if (0 != client->min_height && client->height < client->min_height) 920 | { 921 | client->height = client->min_height; 922 | willresize = true; 923 | } 924 | 925 | if (0 != client->min_width && client->width < client->min_width) 926 | { 927 | client->width = client->min_width; 928 | willresize = true; 929 | } 930 | 931 | /* 932 | * If the window is larger than our screen, just place it in the 933 | * corner and resize. 934 | */ 935 | if (client->width + conf.borderwidth * 2 > mon_width) 936 | { 937 | client->x = mon_x; 938 | client->width = mon_width - conf.borderwidth * 2;; 939 | willmove = true; 940 | willresize = true; 941 | } 942 | else if (client->x + client->width + conf.borderwidth * 2 943 | > mon_x + mon_width) 944 | { 945 | client->x = mon_x + mon_width - (client->width + conf.borderwidth * 2); 946 | willmove = true; 947 | } 948 | 949 | if (client->height + conf.borderwidth * 2 > mon_height) 950 | { 951 | client->y = mon_y; 952 | client->height = mon_height - conf.borderwidth * 2; 953 | willmove = true; 954 | willresize = true; 955 | } 956 | else if (client->y + client->height + conf.borderwidth * 2 957 | > mon_y + mon_height) 958 | { 959 | client->y = mon_y + mon_height - (client->height + conf.borderwidth 960 | * 2); 961 | willmove = true; 962 | } 963 | 964 | if (willmove) 965 | { 966 | PDEBUG("Moving to %d,%d.\n", client->x, client->y); 967 | movewindow(client->id, client->x, client->y); 968 | } 969 | 970 | if (willresize) 971 | { 972 | PDEBUG("Resizing to %d x %d.\n", client->width, client->height); 973 | resize(client->id, client->width, client->height); 974 | } 975 | } 976 | 977 | /* 978 | * Set position, geometry and attributes of a new window and show it 979 | * on the screen. 980 | */ 981 | void newwin(xcb_window_t win) 982 | { 983 | struct client *client; 984 | 985 | if (NULL != findclient(win)) 986 | { 987 | /* 988 | * We know this window from before. It's trying to map itself 989 | * on the current workspace, but since it's unmapped it 990 | * probably belongs on another workspace. We don't like that. 991 | * Silently ignore. 992 | */ 993 | return; 994 | } 995 | 996 | /* 997 | * Set up stuff, like borders, add the window to the client list, 998 | * et cetera. 999 | */ 1000 | client = setupwin(win); 1001 | if (NULL == client) 1002 | { 1003 | fprintf(stderr, "mcwm: Couldn't set up window. Out of memory.\n"); 1004 | return; 1005 | } 1006 | 1007 | /* Add this window to the current workspace. */ 1008 | addtoworkspace(client, curws); 1009 | 1010 | /* 1011 | * If the client doesn't say the user specified the coordinates 1012 | * for the window we map it where our pointer is instead. 1013 | */ 1014 | if (!client->usercoord) 1015 | { 1016 | int16_t pointx; 1017 | int16_t pointy; 1018 | PDEBUG("Coordinates not set by user. Using pointer: %d,%d.\n", 1019 | pointx, pointy); 1020 | 1021 | /* Get pointer position so we can move the window to the cursor. */ 1022 | if (!getpointer(screen->root, &pointx, &pointy)) 1023 | { 1024 | PDEBUG("Failed to get pointer coords!\n"); 1025 | pointx = 0; 1026 | pointy = 0; 1027 | } 1028 | 1029 | client->x = pointx; 1030 | client->y = pointy; 1031 | 1032 | movewindow(client->id, client->x, client->y); 1033 | } 1034 | else 1035 | { 1036 | PDEBUG("User set coordinates.\n"); 1037 | } 1038 | 1039 | /* Find the physical output this window will be on if RANDR is active. */ 1040 | if (-1 != randrbase) 1041 | { 1042 | client->monitor = findmonbycoord(client->x, client->y); 1043 | if (NULL == client->monitor) 1044 | { 1045 | /* 1046 | * Window coordinates are outside all physical monitors. 1047 | * Choose the first screen. 1048 | */ 1049 | if (NULL != monlist) 1050 | { 1051 | client->monitor = monlist->data; 1052 | } 1053 | } 1054 | } 1055 | 1056 | fitonscreen(client); 1057 | 1058 | /* Show window on screen. */ 1059 | xcb_map_window(conn, client->id); 1060 | 1061 | /* Declare window normal. */ 1062 | long data[] = { XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE }; 1063 | xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->id, 1064 | wm_state, wm_state, 32, 2, data); 1065 | 1066 | /* 1067 | * Move cursor into the middle of the window so we don't lose the 1068 | * pointer to another window. 1069 | */ 1070 | xcb_warp_pointer(conn, XCB_NONE, win, 0, 0, 0, 0, 1071 | client->width / 2, client->height / 2); 1072 | 1073 | xcb_flush(conn); 1074 | } 1075 | 1076 | /* Set border colour, width and event mask for window. */ 1077 | struct client *setupwin(xcb_window_t win) 1078 | { 1079 | uint32_t mask = 0; 1080 | uint32_t values[2]; 1081 | struct item *item; 1082 | struct client *client; 1083 | xcb_size_hints_t hints; 1084 | uint32_t ws; 1085 | 1086 | /* Set default border color. */ 1087 | values[0] = conf.unfocuscol; 1088 | xcb_change_window_attributes(conn, win, XCB_CW_BORDER_PIXEL, values); 1089 | 1090 | /* Subscribe to events we want to know about in this window. */ 1091 | mask = XCB_CW_EVENT_MASK; 1092 | values[0] = XCB_EVENT_MASK_ENTER_WINDOW; 1093 | xcb_change_window_attributes_checked(conn, win, mask, values); 1094 | 1095 | /* 1096 | * Add this window to the X Save Set, that is, the windows that 1097 | * will be automatically restored if we die. 1098 | */ 1099 | xcb_change_save_set(conn, XCB_SET_MODE_INSERT, win); 1100 | 1101 | xcb_flush(conn); 1102 | 1103 | /* Remember window and store a few things about it. */ 1104 | 1105 | item = additem(&winlist); 1106 | 1107 | if (NULL == item) 1108 | { 1109 | PDEBUG("newwin: Out of memory.\n"); 1110 | return NULL; 1111 | } 1112 | 1113 | client = malloc(sizeof (struct client)); 1114 | if (NULL == client) 1115 | { 1116 | PDEBUG("newwin: Out of memory.\n"); 1117 | return NULL; 1118 | } 1119 | 1120 | item->data = client; 1121 | 1122 | /* Initialize client. */ 1123 | client->id = win; 1124 | client->usercoord = false; 1125 | client->x = 0; 1126 | client->y = 0; 1127 | client->width = 0; 1128 | client->height = 0; 1129 | client->min_width = 0; 1130 | client->min_height = 0; 1131 | client->max_width = screen->width_in_pixels; 1132 | client->max_height = screen->height_in_pixels; 1133 | client->base_width = 0; 1134 | client->base_height = 0; 1135 | client->width_inc = 1; 1136 | client->height_inc = 1; 1137 | client->vertmaxed = false; 1138 | client->maxed = false; 1139 | client->fixed = false; 1140 | client->monitor = NULL; 1141 | 1142 | client->winitem = item; 1143 | 1144 | for (ws = 0; ws < WORKSPACES; ws ++) 1145 | { 1146 | client->wsitem[ws] = NULL; 1147 | } 1148 | 1149 | PDEBUG("Adding window %d\n", client->id); 1150 | 1151 | setborders(client, conf.borderwidth); 1152 | 1153 | /* Get window geometry. */ 1154 | if (!getgeom(client->id, &client->x, &client->y, &client->width, 1155 | &client->height)) 1156 | { 1157 | fprintf(stderr, "Couldn't get geometry in initial setup of window.\n"); 1158 | } 1159 | 1160 | /* 1161 | * Get the window's incremental size step, if any. 1162 | */ 1163 | if (!xcb_icccm_get_wm_normal_hints_reply( 1164 | conn, xcb_icccm_get_wm_normal_hints_unchecked( 1165 | conn, win), &hints, NULL)) 1166 | { 1167 | PDEBUG("Couldn't get size hints.\n"); 1168 | } 1169 | 1170 | /* 1171 | * The user specified the position coordinates. Remember that so 1172 | * we can use geometry later. 1173 | */ 1174 | if (hints.flags & XCB_ICCCM_SIZE_HINT_US_POSITION) 1175 | { 1176 | client->usercoord = true; 1177 | } 1178 | 1179 | if (hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) 1180 | { 1181 | client->min_width = hints.min_width; 1182 | client->min_height = hints.min_height; 1183 | } 1184 | 1185 | if (hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) 1186 | { 1187 | 1188 | client->max_width = hints.max_width; 1189 | client->max_height = hints.max_height; 1190 | } 1191 | 1192 | if (hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC) 1193 | { 1194 | client->width_inc = hints.width_inc; 1195 | client->height_inc = hints.height_inc; 1196 | 1197 | PDEBUG("widht_inc %d\nheight_inc %d\n", client->width_inc, 1198 | client->height_inc); 1199 | } 1200 | 1201 | if (hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) 1202 | { 1203 | client->base_width = hints.base_width; 1204 | client->base_height = hints.base_height; 1205 | } 1206 | 1207 | return client; 1208 | } 1209 | 1210 | /* 1211 | * Get a keycode from a keysym. 1212 | * 1213 | * Returns keycode value. 1214 | */ 1215 | xcb_keycode_t keysymtokeycode(xcb_keysym_t keysym, xcb_key_symbols_t *keysyms) 1216 | { 1217 | xcb_keycode_t *keyp; 1218 | xcb_keycode_t key; 1219 | 1220 | /* We only use the first keysymbol, even if there are more. */ 1221 | keyp = xcb_key_symbols_get_keycode(keysyms, keysym); 1222 | if (NULL == keyp) 1223 | { 1224 | fprintf(stderr, "mcwm: Couldn't look up key. Exiting.\n"); 1225 | exit(1); 1226 | return 0; 1227 | } 1228 | 1229 | key = *keyp; 1230 | free(keyp); 1231 | 1232 | return key; 1233 | } 1234 | 1235 | /* 1236 | * Set up all shortcut keys. 1237 | * 1238 | * Returns 0 on success, non-zero otherwise. 1239 | */ 1240 | int setupkeys(void) 1241 | { 1242 | xcb_key_symbols_t *keysyms; 1243 | unsigned i; 1244 | 1245 | /* Get all the keysymbols. */ 1246 | keysyms = xcb_key_symbols_alloc(conn); 1247 | 1248 | /* 1249 | * Find out what keys generates our MODKEY mask. Unfortunately it 1250 | * might be several keys. 1251 | */ 1252 | if (NULL != modkeys.keycodes) 1253 | { 1254 | free(modkeys.keycodes); 1255 | } 1256 | modkeys = getmodkeys(MODKEY); 1257 | 1258 | if (0 == modkeys.len) 1259 | { 1260 | fprintf(stderr, "We couldn't find any keycodes to our main modifier " 1261 | "key!\n"); 1262 | return -1; 1263 | } 1264 | 1265 | for (i = 0; i < modkeys.len; i ++) 1266 | { 1267 | /* 1268 | * Grab the keys that are bound to MODKEY mask with any other 1269 | * modifier. 1270 | */ 1271 | xcb_grab_key(conn, 1, screen->root, XCB_MOD_MASK_ANY, 1272 | modkeys.keycodes[i], 1273 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); 1274 | } 1275 | 1276 | /* Now grab the rest of the keys with the MODKEY modifier. */ 1277 | for (i = KEY_F; i < KEY_MAX; i ++) 1278 | { 1279 | if (XK_VoidSymbol == keys[i].keysym) 1280 | { 1281 | keys[i].keycode = 0; 1282 | continue; 1283 | } 1284 | 1285 | keys[i].keycode = keysymtokeycode(keys[i].keysym, keysyms); 1286 | if (0 == keys[i].keycode) 1287 | { 1288 | /* Couldn't set up keys! */ 1289 | 1290 | /* Get rid of key symbols. */ 1291 | xcb_key_symbols_free(keysyms); 1292 | 1293 | return -1; 1294 | } 1295 | 1296 | /* Grab other keys with a modifier mask. */ 1297 | xcb_grab_key(conn, 1, screen->root, MODKEY, keys[i].keycode, 1298 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); 1299 | 1300 | /* 1301 | * XXX Also grab it's shifted counterpart. A bit ugly here 1302 | * because we grab all of them not just the ones we want. 1303 | */ 1304 | xcb_grab_key(conn, 1, screen->root, MODKEY | SHIFTMOD, 1305 | keys[i].keycode, 1306 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); 1307 | } 1308 | 1309 | /* Need this to take effect NOW! */ 1310 | xcb_flush(conn); 1311 | 1312 | /* Get rid of the key symbols table. */ 1313 | xcb_key_symbols_free(keysyms); 1314 | 1315 | return 0; 1316 | } 1317 | 1318 | /* 1319 | * Walk through all existing windows and set them up. 1320 | * 1321 | * Returns 0 on success. 1322 | */ 1323 | int setupscreen(void) 1324 | { 1325 | xcb_query_tree_reply_t *reply; 1326 | xcb_query_pointer_reply_t *pointer; 1327 | int i; 1328 | int len; 1329 | xcb_window_t *children; 1330 | xcb_get_window_attributes_reply_t *attr; 1331 | struct client *client; 1332 | uint32_t ws; 1333 | 1334 | /* Get all children. */ 1335 | reply = xcb_query_tree_reply(conn, 1336 | xcb_query_tree(conn, screen->root), 0); 1337 | if (NULL == reply) 1338 | { 1339 | return -1; 1340 | } 1341 | 1342 | len = xcb_query_tree_children_length(reply); 1343 | children = xcb_query_tree_children(reply); 1344 | 1345 | /* Set up all windows on this root. */ 1346 | for (i = 0; i < len; i ++) 1347 | { 1348 | attr = xcb_get_window_attributes_reply( 1349 | conn, xcb_get_window_attributes(conn, children[i]), NULL); 1350 | 1351 | if (!attr) 1352 | { 1353 | fprintf(stderr, "Couldn't get attributes for window %d.", 1354 | children[i]); 1355 | continue; 1356 | } 1357 | 1358 | /* 1359 | * Don't set up or even bother windows in override redirect 1360 | * mode. 1361 | * 1362 | * This mode means they wouldn't have been reported to us 1363 | * with a MapRequest if we had been running, so in the 1364 | * normal case we wouldn't have seen them. 1365 | * 1366 | * Only handle visible windows. 1367 | */ 1368 | if (!attr->override_redirect 1369 | && attr->map_state == XCB_MAP_STATE_VIEWABLE) 1370 | { 1371 | client = setupwin(children[i]); 1372 | if (NULL != client) 1373 | { 1374 | /* 1375 | * Find the physical output this window will be on if 1376 | * RANDR is active. 1377 | */ 1378 | if (-1 != randrbase) 1379 | { 1380 | PDEBUG("Looking for monitor on %d x %d.\n", client->x, 1381 | client->y); 1382 | client->monitor = findmonbycoord(client->x, client->y); 1383 | #if DEBUG 1384 | if (NULL != client->monitor) 1385 | { 1386 | PDEBUG("Found client on monitor %s.\n", 1387 | client->monitor->name); 1388 | } 1389 | else 1390 | { 1391 | PDEBUG("Couldn't find client on any monitor.\n"); 1392 | } 1393 | #endif 1394 | } 1395 | 1396 | /* Fit window on physical screen. */ 1397 | fitonscreen(client); 1398 | 1399 | /* 1400 | * Check if this window has a workspace set already as 1401 | * a WM hint. 1402 | * 1403 | */ 1404 | ws = getwmdesktop(children[i]); 1405 | 1406 | if (ws == NET_WM_FIXED) 1407 | { 1408 | /* Add to current workspace. */ 1409 | addtoworkspace(client, curws); 1410 | /* Add to all other workspaces. */ 1411 | fixwindow(client, false); 1412 | } 1413 | else if (MCWM_NOWS != ws && ws < WORKSPACES) 1414 | { 1415 | addtoworkspace(client, ws); 1416 | /* If it's not our current workspace, hide it. */ 1417 | if (ws != curws) 1418 | { 1419 | xcb_unmap_window(conn, client->id); 1420 | } 1421 | } 1422 | else 1423 | { 1424 | /* 1425 | * No workspace hint at all. Just add it to our 1426 | * current workspace. 1427 | */ 1428 | addtoworkspace(client, curws); 1429 | } 1430 | } 1431 | } 1432 | 1433 | free(attr); 1434 | } 1435 | 1436 | changeworkspace(0); 1437 | 1438 | /* 1439 | * Get pointer position so we can set focus on any window which 1440 | * might be under it. 1441 | */ 1442 | pointer = xcb_query_pointer_reply( 1443 | conn, xcb_query_pointer(conn, screen->root), 0); 1444 | 1445 | if (NULL == pointer) 1446 | { 1447 | focuswin = NULL; 1448 | } 1449 | else 1450 | { 1451 | setfocus(findclient(pointer->child)); 1452 | free(pointer); 1453 | } 1454 | 1455 | xcb_flush(conn); 1456 | 1457 | free(reply); 1458 | 1459 | return 0; 1460 | } 1461 | 1462 | /* 1463 | * Set up RANDR extension. Get the extension base and subscribe to 1464 | * events. 1465 | */ 1466 | int setuprandr(void) 1467 | { 1468 | const xcb_query_extension_reply_t *extension; 1469 | int base; 1470 | 1471 | extension = xcb_get_extension_data(conn, &xcb_randr_id); 1472 | if (!extension->present) 1473 | { 1474 | PDEBUG("No RANDR extension.\n"); 1475 | return -1; 1476 | } 1477 | else 1478 | { 1479 | getrandr(); 1480 | } 1481 | 1482 | base = extension->first_event; 1483 | PDEBUG("randrbase is %d.\n", base); 1484 | 1485 | xcb_randr_select_input(conn, screen->root, 1486 | XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE | 1487 | XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE | 1488 | XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE | 1489 | XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY); 1490 | 1491 | xcb_flush(conn); 1492 | 1493 | return base; 1494 | } 1495 | 1496 | /* 1497 | * Get RANDR resources and figure out how many outputs there are. 1498 | */ 1499 | void getrandr(void) 1500 | { 1501 | xcb_randr_get_screen_resources_current_cookie_t rcookie; 1502 | xcb_randr_get_screen_resources_current_reply_t *res; 1503 | xcb_randr_output_t *outputs; 1504 | int len; 1505 | xcb_timestamp_t timestamp; 1506 | 1507 | rcookie = xcb_randr_get_screen_resources_current(conn, screen->root); 1508 | res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL); 1509 | if (NULL == res) 1510 | { 1511 | printf("No RANDR extension available.\n"); 1512 | return; 1513 | } 1514 | timestamp = res->config_timestamp; 1515 | 1516 | len = xcb_randr_get_screen_resources_current_outputs_length(res); 1517 | outputs = xcb_randr_get_screen_resources_current_outputs(res); 1518 | 1519 | PDEBUG("Found %d outputs.\n", len); 1520 | 1521 | /* Request information for all outputs. */ 1522 | getoutputs(outputs, len, timestamp); 1523 | 1524 | free(res); 1525 | } 1526 | 1527 | /* 1528 | * Walk through all the RANDR outputs (number of outputs == len) there 1529 | * was at time timestamp. 1530 | */ 1531 | void getoutputs(xcb_randr_output_t *outputs, int len, xcb_timestamp_t timestamp) 1532 | { 1533 | char *name; 1534 | xcb_randr_get_crtc_info_cookie_t icookie; 1535 | xcb_randr_get_crtc_info_reply_t *crtc = NULL; 1536 | xcb_randr_get_output_info_reply_t *output; 1537 | struct monitor *mon; 1538 | struct monitor *clonemon; 1539 | xcb_randr_get_output_info_cookie_t ocookie[len]; 1540 | int i; 1541 | 1542 | for (i = 0; i < len; i++) 1543 | { 1544 | ocookie[i] = xcb_randr_get_output_info(conn, outputs[i], timestamp); 1545 | } 1546 | 1547 | /* Loop through all outputs. */ 1548 | for (i = 0; i < len; i ++) 1549 | { 1550 | output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL); 1551 | 1552 | if (output == NULL) 1553 | { 1554 | continue; 1555 | } 1556 | 1557 | asprintf(&name, "%.*s", 1558 | xcb_randr_get_output_info_name_length(output), 1559 | xcb_randr_get_output_info_name(output)); 1560 | 1561 | PDEBUG("Name: %s\n", name); 1562 | PDEBUG("id: %d\n" , outputs[i]); 1563 | PDEBUG("Size: %d x %d mm.\n", output->mm_width, output->mm_height); 1564 | 1565 | if (XCB_NONE != output->crtc) 1566 | { 1567 | icookie = xcb_randr_get_crtc_info(conn, output->crtc, timestamp); 1568 | crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL); 1569 | if (NULL == crtc) 1570 | { 1571 | return; 1572 | } 1573 | 1574 | PDEBUG("CRTC: at %d, %d, size: %d x %d.\n", crtc->x, crtc->y, 1575 | crtc->width, crtc->height); 1576 | 1577 | /* Check if it's a clone. */ 1578 | clonemon = findclones(outputs[i], crtc->x, crtc->y); 1579 | if (NULL != clonemon) 1580 | { 1581 | PDEBUG("Monitor %s, id %d is a clone of %s, id %d. Skipping.\n", 1582 | name, outputs[i], 1583 | clonemon->name, clonemon->id); 1584 | continue; 1585 | } 1586 | 1587 | /* Do we know this monitor already? */ 1588 | if (NULL == (mon = findmonitor(outputs[i]))) 1589 | { 1590 | PDEBUG("Monitor not known, adding to list.\n"); 1591 | addmonitor(outputs[i], name, crtc->x, crtc->y, crtc->width, 1592 | crtc->height); 1593 | } 1594 | else 1595 | { 1596 | bool changed = false; 1597 | 1598 | /* 1599 | * We know this monitor. Update information. If it's 1600 | * smaller than before, rearrange windows. 1601 | */ 1602 | PDEBUG("Known monitor. Updating info.\n"); 1603 | 1604 | if (crtc->x != mon->x) 1605 | { 1606 | mon->x = crtc->x; 1607 | changed = true; 1608 | } 1609 | if (crtc->y != mon->y) 1610 | { 1611 | mon->y = crtc->y; 1612 | changed = true; 1613 | } 1614 | 1615 | if (crtc->width != mon->width) 1616 | { 1617 | mon->width = crtc->width; 1618 | changed = true; 1619 | } 1620 | if (crtc->height != mon->height) 1621 | { 1622 | mon->height = crtc->height; 1623 | changed = true; 1624 | } 1625 | 1626 | if (changed) 1627 | { 1628 | arrbymon(mon); 1629 | } 1630 | } 1631 | 1632 | free(crtc); 1633 | } 1634 | else 1635 | { 1636 | PDEBUG("Monitor not used at the moment.\n"); 1637 | /* 1638 | * Check if it was used before. If it was, do something. 1639 | */ 1640 | if ((mon = findmonitor(outputs[i]))) 1641 | { 1642 | struct item *item; 1643 | struct client *client; 1644 | 1645 | /* Check all windows on this monitor and move them to 1646 | * the next or to the first monitor if there is no 1647 | * next. 1648 | * 1649 | * FIXME: Use per monitor workspace list instead of 1650 | * global window list. 1651 | */ 1652 | for (item = winlist; item != NULL; item = item->next) 1653 | { 1654 | client = item->data; 1655 | if (client->monitor == mon) 1656 | { 1657 | if (NULL == client->monitor->item->next) 1658 | { 1659 | if (NULL == monlist) 1660 | { 1661 | client->monitor = NULL; 1662 | } 1663 | else 1664 | { 1665 | client->monitor = monlist->data; 1666 | } 1667 | } 1668 | else 1669 | { 1670 | client->monitor = 1671 | client->monitor->item->next->data; 1672 | } 1673 | 1674 | fitonscreen(client); 1675 | } 1676 | } 1677 | 1678 | /* It's not active anymore. Forget about it. */ 1679 | delmonitor(mon); 1680 | } 1681 | } 1682 | 1683 | free(output); 1684 | } 1685 | } 1686 | 1687 | void arrbymon(struct monitor *monitor) 1688 | { 1689 | struct item *item; 1690 | struct client *client; 1691 | 1692 | PDEBUG("arrbymon\n"); 1693 | /* 1694 | * Go through all windows on this monitor. If they don't fit on 1695 | * the new screen, move them around and resize them as necessary. 1696 | * 1697 | * FIXME: Use a per monitor workspace list instead of global 1698 | * windows list. 1699 | */ 1700 | for (item = winlist; item != NULL; item = item->next) 1701 | { 1702 | client = item->data; 1703 | if (client->monitor == monitor) 1704 | { 1705 | fitonscreen(client); 1706 | } 1707 | } 1708 | 1709 | } 1710 | 1711 | struct monitor *findmonitor(xcb_randr_output_t id) 1712 | { 1713 | struct item *item; 1714 | struct monitor *mon; 1715 | 1716 | for (item = monlist; item != NULL; item = item->next) 1717 | { 1718 | mon = item->data; 1719 | if (id == mon->id) 1720 | { 1721 | PDEBUG("findmonitor: Found it. Output ID: %d\n", mon->id); 1722 | return mon; 1723 | } 1724 | PDEBUG("findmonitor: Goint to %p.\n", item->next); 1725 | } 1726 | 1727 | return NULL; 1728 | } 1729 | 1730 | struct monitor *findclones(xcb_randr_output_t id, int16_t x, int16_t y) 1731 | { 1732 | struct monitor *clonemon; 1733 | struct item *item; 1734 | 1735 | for (item = monlist; item != NULL; item = item->next) 1736 | { 1737 | clonemon = item->data; 1738 | 1739 | PDEBUG("Monitor %s: x, y: %d--%d, %d--%d.\n", 1740 | clonemon->name, 1741 | clonemon->x, clonemon->x + clonemon->width, 1742 | clonemon->y, clonemon->y + clonemon->height); 1743 | 1744 | /* Check for same position. */ 1745 | if (id != clonemon->id && clonemon->x == x && clonemon->y == y) 1746 | { 1747 | return clonemon; 1748 | } 1749 | } 1750 | 1751 | return NULL; 1752 | } 1753 | 1754 | struct monitor *findmonbycoord(int16_t x, int16_t y) 1755 | { 1756 | struct item *item; 1757 | struct monitor *mon; 1758 | 1759 | for (item = monlist; item != NULL; item = item->next) 1760 | { 1761 | mon = item->data; 1762 | 1763 | PDEBUG("Monitor %s: x, y: %d--%d, %d--%d.\n", 1764 | mon->name, 1765 | mon->x, mon->x + mon->width, 1766 | mon->y, mon->y + mon->height); 1767 | 1768 | PDEBUG("Is %d,%d between them?\n", x, y); 1769 | 1770 | if (x >= mon->x && x <= mon->x + mon->width 1771 | && y >= mon->y && y <= mon->y + mon->height) 1772 | { 1773 | PDEBUG("findmonbycoord: Found it. Output ID: %d, name %s\n", 1774 | mon->id, mon->name); 1775 | return mon; 1776 | } 1777 | } 1778 | 1779 | return NULL; 1780 | } 1781 | 1782 | void delmonitor(struct monitor *mon) 1783 | { 1784 | PDEBUG("Deleting output %s.\n", mon->name); 1785 | free(mon->name); 1786 | freeitem(&monlist, NULL, mon->item); 1787 | } 1788 | 1789 | struct monitor *addmonitor(xcb_randr_output_t id, char *name, 1790 | uint32_t x, uint32_t y, uint16_t width, 1791 | uint16_t height) 1792 | { 1793 | struct item *item; 1794 | struct monitor *mon; 1795 | 1796 | if (NULL == (item = additem(&monlist))) 1797 | { 1798 | fprintf(stderr, "Out of memory.\n"); 1799 | return NULL; 1800 | } 1801 | 1802 | mon = malloc(sizeof (struct monitor)); 1803 | if (NULL == mon) 1804 | { 1805 | fprintf(stderr, "Out of memory.\n"); 1806 | return NULL; 1807 | } 1808 | 1809 | item->data = mon; 1810 | 1811 | mon->id = id; 1812 | mon->name = name; 1813 | mon->x = x; 1814 | mon->y = y; 1815 | mon->width = width; 1816 | mon->height = height; 1817 | mon->item = item; 1818 | 1819 | return mon; 1820 | } 1821 | 1822 | /* Raise window win to top of stack. */ 1823 | void raisewindow(xcb_drawable_t win) 1824 | { 1825 | uint32_t values[] = { XCB_STACK_MODE_ABOVE }; 1826 | 1827 | if (screen->root == win || 0 == win) 1828 | { 1829 | return; 1830 | } 1831 | 1832 | xcb_configure_window(conn, win, 1833 | XCB_CONFIG_WINDOW_STACK_MODE, 1834 | values); 1835 | xcb_flush(conn); 1836 | } 1837 | 1838 | /* 1839 | * Set window client to either top or bottom of stack depending on 1840 | * where it is now. 1841 | */ 1842 | void raiseorlower(struct client *client) 1843 | { 1844 | uint32_t values[] = { XCB_STACK_MODE_OPPOSITE }; 1845 | xcb_drawable_t win; 1846 | 1847 | if (NULL == client) 1848 | { 1849 | return; 1850 | } 1851 | 1852 | win = client->id; 1853 | 1854 | xcb_configure_window(conn, win, 1855 | XCB_CONFIG_WINDOW_STACK_MODE, 1856 | values); 1857 | xcb_flush(conn); 1858 | } 1859 | 1860 | void movelim(struct client *client) 1861 | { 1862 | int16_t mon_x; 1863 | int16_t mon_y; 1864 | uint16_t mon_width; 1865 | uint16_t mon_height; 1866 | 1867 | if (NULL == client->monitor) 1868 | { 1869 | mon_x = 0; 1870 | mon_y = 0; 1871 | mon_width = screen->width_in_pixels; 1872 | mon_height = screen->height_in_pixels; 1873 | } 1874 | else 1875 | { 1876 | mon_x = client->monitor->x; 1877 | mon_y = client->monitor->y; 1878 | mon_width = client->monitor->width; 1879 | mon_height = client->monitor->height; 1880 | } 1881 | 1882 | /* Is it outside the physical monitor? */ 1883 | if (client->x < mon_x) 1884 | { 1885 | client->x = mon_x; 1886 | } 1887 | if (client->y < mon_y) 1888 | { 1889 | client->y = mon_y; 1890 | } 1891 | 1892 | if (client->x + client->width > mon_x + mon_width - conf.borderwidth * 2) 1893 | { 1894 | client->x = (mon_x + mon_width - conf.borderwidth * 2) - client->width; 1895 | } 1896 | 1897 | if (client->y + client->height > mon_y + mon_height - conf.borderwidth * 2) 1898 | { 1899 | client->y = (mon_y + mon_height - conf.borderwidth * 2) 1900 | - client->height; 1901 | } 1902 | 1903 | movewindow(client->id, client->x, client->y); 1904 | } 1905 | 1906 | /* Move window win to root coordinates x,y. */ 1907 | void movewindow(xcb_drawable_t win, uint16_t x, uint16_t y) 1908 | { 1909 | uint32_t values[2]; 1910 | 1911 | if (screen->root == win || 0 == win) 1912 | { 1913 | /* Can't move root. */ 1914 | return; 1915 | } 1916 | 1917 | values[0] = x; 1918 | values[1] = y; 1919 | 1920 | xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_X 1921 | | XCB_CONFIG_WINDOW_Y, values); 1922 | 1923 | xcb_flush(conn); 1924 | } 1925 | 1926 | /* Change focus to next in window ring. */ 1927 | void focusnext(bool reverse) 1928 | { 1929 | struct client *client = NULL; 1930 | 1931 | #if DEBUG 1932 | if (NULL != focuswin) 1933 | { 1934 | PDEBUG("Focus now in win %d\n", focuswin->id); 1935 | } 1936 | #endif 1937 | 1938 | if (NULL == wslist[curws]) 1939 | { 1940 | PDEBUG("No windows to focus on in this workspace.\n"); 1941 | return; 1942 | } 1943 | 1944 | if (MCWM_TABBING != mode) 1945 | { 1946 | /* 1947 | * Remember what we last focused on. We need this when the 1948 | * MODKEY is released and we move the last focused window in 1949 | * the tabbing order list. 1950 | */ 1951 | lastfocuswin = focuswin; 1952 | mode = MCWM_TABBING; 1953 | 1954 | PDEBUG("Began tabbing.\n"); 1955 | } 1956 | 1957 | /* If we currently have no focus focus first in list. */ 1958 | if (NULL == focuswin || NULL == focuswin->wsitem[curws]) 1959 | { 1960 | PDEBUG("Focusing first in list: %p\n", wslist[curws]); 1961 | client = wslist[curws]->data; 1962 | 1963 | if (NULL != focuswin && NULL == focuswin->wsitem[curws]) 1964 | { 1965 | PDEBUG("XXX Our focused window %d isn't on this workspace!\n", 1966 | focuswin->id); 1967 | } 1968 | } 1969 | else 1970 | { 1971 | if (reverse) 1972 | { 1973 | if (NULL == focuswin->wsitem[curws]->prev) 1974 | { 1975 | /* 1976 | * We were at the head of list. Focusing on last 1977 | * window in list unless we were already there. 1978 | */ 1979 | struct item *last = wslist[curws]; 1980 | while (NULL != last->next) 1981 | last = last->next; 1982 | if (focuswin->wsitem[curws] != last->data) 1983 | { 1984 | PDEBUG("Beginning of list. Focusing last in list: %p\n", 1985 | last); 1986 | client = last->data; 1987 | } 1988 | } 1989 | else 1990 | { 1991 | /* Otherwise, focus the next in list. */ 1992 | PDEBUG("Tabbing. Focusing next: %p.\n", 1993 | focuswin->wsitem[curws]->prev); 1994 | client = focuswin->wsitem[curws]->prev->data; 1995 | } 1996 | } 1997 | else 1998 | { 1999 | if (NULL == focuswin->wsitem[curws]->next) 2000 | { 2001 | /* 2002 | * We were at the end of list. Focusing on first window in 2003 | * list unless we were already there. 2004 | */ 2005 | if (focuswin->wsitem[curws] != wslist[curws]->data) 2006 | { 2007 | PDEBUG("End of list. Focusing first in list: %p\n", 2008 | wslist[curws]); 2009 | client = wslist[curws]->data; 2010 | } 2011 | } 2012 | else 2013 | { 2014 | /* Otherwise, focus the next in list. */ 2015 | PDEBUG("Tabbing. Focusing next: %p.\n", 2016 | focuswin->wsitem[curws]->next); 2017 | client = focuswin->wsitem[curws]->next->data; 2018 | } 2019 | } 2020 | } 2021 | 2022 | if (NULL != client) 2023 | { 2024 | /* 2025 | * Raise window if it's occluded, then warp pointer into it and 2026 | * set keyboard focus to it. 2027 | */ 2028 | uint32_t values[] = { XCB_STACK_MODE_TOP_IF }; 2029 | 2030 | xcb_configure_window(conn, client->id, XCB_CONFIG_WINDOW_STACK_MODE, 2031 | values); 2032 | xcb_warp_pointer(conn, XCB_NONE, client->id, 0, 0, 0, 0, 2033 | client->width / 2, client->height / 2); 2034 | setfocus(client); 2035 | } 2036 | } 2037 | 2038 | /* Mark window win as unfocused. */ 2039 | void setunfocus(xcb_drawable_t win) 2040 | { 2041 | uint32_t values[1]; 2042 | 2043 | if (NULL == focuswin) 2044 | { 2045 | return; 2046 | } 2047 | 2048 | if (focuswin->id == screen->root) 2049 | { 2050 | return; 2051 | } 2052 | 2053 | /* Set new border colour. */ 2054 | values[0] = conf.unfocuscol; 2055 | xcb_change_window_attributes(conn, win, XCB_CW_BORDER_PIXEL, values); 2056 | 2057 | xcb_flush(conn); 2058 | } 2059 | 2060 | /* 2061 | * Find client with client->id win in global window list. 2062 | * 2063 | * Returns client pointer or NULL if not found. 2064 | */ 2065 | struct client *findclient(xcb_drawable_t win) 2066 | { 2067 | struct item *item; 2068 | struct client *client; 2069 | 2070 | for (item = winlist; item != NULL; item = item->next) 2071 | { 2072 | client = item->data; 2073 | if (win == client->id) 2074 | { 2075 | PDEBUG("findclient: Found it. Win: %d\n", client->id); 2076 | return client; 2077 | } 2078 | } 2079 | 2080 | return NULL; 2081 | } 2082 | 2083 | /* Set focus on window client. */ 2084 | void setfocus(struct client *client) 2085 | { 2086 | uint32_t values[1]; 2087 | 2088 | /* 2089 | * If client is NULL, we focus on whatever the pointer is on. 2090 | * 2091 | * This is a pathological case, but it will make the poor user 2092 | * able to focus on windows anyway, even though this window 2093 | * manager might be buggy. 2094 | */ 2095 | if (NULL == client) 2096 | { 2097 | PDEBUG("setfocus: client was NULL!\n"); 2098 | 2099 | focuswin = NULL; 2100 | 2101 | xcb_set_input_focus(conn, XCB_NONE, XCB_INPUT_FOCUS_POINTER_ROOT, 2102 | XCB_CURRENT_TIME); 2103 | xcb_flush(conn); 2104 | 2105 | return; 2106 | } 2107 | 2108 | /* 2109 | * Don't bother focusing on the root window or on the same window 2110 | * that already has focus. 2111 | */ 2112 | if (client->id == screen->root || client == focuswin) 2113 | { 2114 | return; 2115 | } 2116 | 2117 | /* Set new border colour. */ 2118 | if (client->fixed) 2119 | { 2120 | values[0] = conf.fixedcol; 2121 | } 2122 | else 2123 | { 2124 | values[0] = conf.focuscol; 2125 | } 2126 | 2127 | xcb_change_window_attributes(conn, client->id, XCB_CW_BORDER_PIXEL, 2128 | values); 2129 | 2130 | /* Unset last focus. */ 2131 | if (NULL != focuswin) 2132 | { 2133 | setunfocus(focuswin->id); 2134 | } 2135 | 2136 | /* Set new input focus. */ 2137 | 2138 | xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->id, 2139 | XCB_CURRENT_TIME); 2140 | 2141 | xcb_flush(conn); 2142 | 2143 | /* Remember the new window as the current focused window. */ 2144 | focuswin = client; 2145 | } 2146 | 2147 | int start(char *program) 2148 | { 2149 | pid_t pid; 2150 | 2151 | pid = fork(); 2152 | if (-1 == pid) 2153 | { 2154 | perror("fork"); 2155 | return -1; 2156 | } 2157 | else if (0 == pid) 2158 | { 2159 | char *argv[2]; 2160 | 2161 | /* In the child. */ 2162 | 2163 | /* 2164 | * Make this process a new process leader, otherwise the 2165 | * terminal will die when the wm dies. Also, this makes any 2166 | * SIGCHLD go to this process when we fork again. 2167 | */ 2168 | if (-1 == setsid()) 2169 | { 2170 | perror("setsid"); 2171 | exit(1); 2172 | } 2173 | 2174 | argv[0] = program; 2175 | argv[1] = NULL; 2176 | 2177 | if (-1 == execvp(program, argv)) 2178 | { 2179 | perror("execve"); 2180 | exit(1); 2181 | } 2182 | exit(0); 2183 | } 2184 | 2185 | return 0; 2186 | } 2187 | 2188 | /* Resize with limit. */ 2189 | void resizelim(struct client *client) 2190 | { 2191 | int16_t mon_x; 2192 | int16_t mon_y; 2193 | uint16_t mon_width; 2194 | uint16_t mon_height; 2195 | 2196 | if (NULL == client->monitor) 2197 | { 2198 | mon_x = 0; 2199 | mon_y = 0; 2200 | mon_width = screen->width_in_pixels; 2201 | mon_height = screen->height_in_pixels; 2202 | } 2203 | else 2204 | { 2205 | mon_x = client->monitor->x; 2206 | mon_y = client->monitor->y; 2207 | mon_width = client->monitor->width; 2208 | mon_height = client->monitor->height; 2209 | } 2210 | 2211 | /* Is it smaller than it wants to be? */ 2212 | if (0 != client->min_height && client->height < client->min_height) 2213 | { 2214 | client->height = client->min_height; 2215 | } 2216 | 2217 | if (0 != client->min_width && client->width < client->min_width) 2218 | { 2219 | client->width = client->min_width; 2220 | } 2221 | 2222 | if (client->x + client->width + conf.borderwidth * 2 > mon_x + mon_width) 2223 | { 2224 | client->width = mon_width - ((client->x - mon_x) + conf.borderwidth 2225 | * 2); 2226 | } 2227 | 2228 | if (client->y + client->height + conf.borderwidth * 2 > mon_y + mon_height) 2229 | { 2230 | client->height = mon_height - ((client->y - mon_y) + conf.borderwidth 2231 | * 2); 2232 | } 2233 | 2234 | resize(client->id, client->width, client->height); 2235 | } 2236 | 2237 | void moveresize(xcb_drawable_t win, uint16_t x, uint16_t y, 2238 | uint16_t width, uint16_t height) 2239 | { 2240 | uint32_t values[4]; 2241 | 2242 | if (screen->root == win || 0 == win) 2243 | { 2244 | /* Can't move or resize root. */ 2245 | return; 2246 | } 2247 | 2248 | PDEBUG("Moving to %d, %d, resizing to %d x %d.\n", x, y, width, height); 2249 | 2250 | values[0] = x; 2251 | values[1] = y; 2252 | values[2] = width; 2253 | values[3] = height; 2254 | 2255 | xcb_configure_window(conn, win, 2256 | XCB_CONFIG_WINDOW_X 2257 | | XCB_CONFIG_WINDOW_Y 2258 | | XCB_CONFIG_WINDOW_WIDTH 2259 | | XCB_CONFIG_WINDOW_HEIGHT, values); 2260 | xcb_flush(conn); 2261 | } 2262 | 2263 | /* Resize window win to width,height. */ 2264 | void resize(xcb_drawable_t win, uint16_t width, uint16_t height) 2265 | { 2266 | uint32_t values[2]; 2267 | 2268 | if (screen->root == win || 0 == win) 2269 | { 2270 | /* Can't resize root. */ 2271 | return; 2272 | } 2273 | 2274 | PDEBUG("Resizing to %d x %d.\n", width, height); 2275 | 2276 | values[0] = width; 2277 | values[1] = height; 2278 | 2279 | xcb_configure_window(conn, win, 2280 | XCB_CONFIG_WINDOW_WIDTH 2281 | | XCB_CONFIG_WINDOW_HEIGHT, values); 2282 | xcb_flush(conn); 2283 | } 2284 | 2285 | /* 2286 | * Resize window client in direction direction. Direction is: 2287 | * 2288 | * h = left, that is decrease width. 2289 | * 2290 | * j = down, that is, increase height. 2291 | * 2292 | * k = up, that is, decrease height. 2293 | * 2294 | * l = right, that is, increase width. 2295 | */ 2296 | void resizestep(struct client *client, char direction) 2297 | { 2298 | int step_x = MOVE_STEP; 2299 | int step_y = MOVE_STEP; 2300 | 2301 | if (NULL == client) 2302 | { 2303 | return; 2304 | } 2305 | 2306 | if (client->maxed) 2307 | { 2308 | /* Can't resize a fully maximized window. */ 2309 | return; 2310 | } 2311 | 2312 | raisewindow(client->id); 2313 | 2314 | if (client->width_inc > 1) 2315 | { 2316 | step_x = client->width_inc; 2317 | } 2318 | else 2319 | { 2320 | step_x = MOVE_STEP; 2321 | } 2322 | 2323 | if (client->height_inc > 1) 2324 | { 2325 | step_y = client->height_inc; 2326 | } 2327 | else 2328 | { 2329 | step_y = MOVE_STEP; 2330 | } 2331 | 2332 | switch (direction) 2333 | { 2334 | case 'h': 2335 | client->width = client->width - step_x; 2336 | break; 2337 | 2338 | case 'j': 2339 | client->height = client->height + step_y; 2340 | break; 2341 | 2342 | case 'k': 2343 | client->height = client->height - step_y; 2344 | break; 2345 | 2346 | case 'l': 2347 | client->width = client->width + step_x; 2348 | break; 2349 | 2350 | default: 2351 | PDEBUG("resizestep in unknown direction.\n"); 2352 | break; 2353 | } /* switch direction */ 2354 | 2355 | resizelim(client); 2356 | 2357 | /* If this window was vertically maximized, remember that it isn't now. */ 2358 | if (client->vertmaxed) 2359 | { 2360 | client->vertmaxed = false; 2361 | } 2362 | 2363 | xcb_warp_pointer(conn, XCB_NONE, client->id, 0, 0, 0, 0, 2364 | client->width / 2, client->height / 2); 2365 | xcb_flush(conn); 2366 | } 2367 | 2368 | /* 2369 | * Try to snap to other windows and monitor border 2370 | */ 2371 | static void snapwindow(struct client *client, int snap_mode) 2372 | { 2373 | struct item *item; 2374 | struct client *win; 2375 | int16_t mon_x; 2376 | int16_t mon_y; 2377 | uint16_t mon_width; 2378 | uint16_t mon_height; 2379 | 2380 | if (NULL == client->monitor) 2381 | { 2382 | mon_x = 0; 2383 | mon_y = 0; 2384 | mon_width = screen->width_in_pixels; 2385 | mon_height = screen->height_in_pixels; 2386 | } 2387 | else 2388 | { 2389 | mon_x = client->monitor->x; 2390 | mon_y = client->monitor->y; 2391 | mon_width = client->monitor->width; 2392 | mon_height = client->monitor->height; 2393 | } 2394 | /* 2395 | * Go through all windows on current workspace. 2396 | */ 2397 | for (item = wslist[curws]; item != NULL; item = item->next) 2398 | { 2399 | win = item->data; 2400 | 2401 | if (client == win) 2402 | { 2403 | continue; 2404 | } 2405 | 2406 | if (snap_mode == MCWM_MOVE) 2407 | { 2408 | if (abs((win->x +win->width) - client->x) < conf.snapmargin) 2409 | { 2410 | if (client->y + client->height > win->y 2411 | && client->y < win->y + win->height) 2412 | { 2413 | client->x = (win->x + win->width) + (2 * conf.borderwidth); 2414 | } 2415 | } 2416 | 2417 | if (abs((win->y +win->height) - client->y) < conf.snapmargin) 2418 | { 2419 | if (client->x + client->width >win->x 2420 | && client->x < win->x + win->width) 2421 | { 2422 | client->y = (win->y + win->height) + (2 * conf.borderwidth); 2423 | } 2424 | } 2425 | 2426 | if (abs((client->x + client->width) - win->x) < conf.snapmargin) 2427 | { 2428 | if (client->y + client->height > win->y 2429 | && client->y < win->y + win->height) 2430 | { 2431 | client->x = (win->x - client->width) 2432 | - (2 * conf.borderwidth); 2433 | } 2434 | } 2435 | 2436 | if (abs((client->y + client->height) - win->y) < conf.snapmargin) 2437 | { 2438 | if (client->x + client->width >win->x 2439 | && client->x < win->x + win->width) 2440 | { 2441 | client->y = (win->y - client->height) 2442 | - (2 * conf.borderwidth); 2443 | } 2444 | } 2445 | 2446 | } /* mcwm_move */ 2447 | else if (snap_mode == MCWM_RESIZE) 2448 | { 2449 | if (abs((client->x + client->width) - win->x) < conf.snapmargin) 2450 | { 2451 | if (client->y + client->height > win->y 2452 | && client->y < win->y + win->height) 2453 | { 2454 | client->width = (win->x - client->x) 2455 | - (2 * conf.borderwidth); 2456 | } 2457 | } 2458 | 2459 | if (abs((client->y + client->height) - win->y) < conf.snapmargin) 2460 | { 2461 | if (client->x + client->width >win->x 2462 | && client->x < win->x + win->width) 2463 | { 2464 | client->height = (win->y - client->y) 2465 | - (2 * conf.borderwidth); 2466 | } 2467 | } 2468 | } /* mcwm_resize */ 2469 | } 2470 | 2471 | /* monitor border */ 2472 | if (snap_mode == MCWM_MOVE) { 2473 | 2474 | if (abs(client->x - mon_x) < conf.snapmargin) 2475 | { 2476 | client->x = mon_x; 2477 | } 2478 | 2479 | if (abs(client->y - mon_y) < conf.snapmargin) 2480 | { 2481 | client->y = mon_y; 2482 | } 2483 | 2484 | if (abs((client->x + client->width) - mon_width) < conf.snapmargin) 2485 | { 2486 | client->x = (mon_width - client->width); 2487 | } 2488 | 2489 | if (abs((client->y + client->height) - mon_height) < conf.snapmargin) 2490 | { 2491 | client->y = (mon_height - client->height); 2492 | } 2493 | } 2494 | else if (snap_mode == MCWM_RESIZE) 2495 | { 2496 | if (abs((client->width + client->x) - mon_width) < conf.snapmargin) 2497 | { 2498 | client->width = mon_width; 2499 | } 2500 | if (abs((client->height + client->y) - mon_height) < conf.snapmargin) 2501 | { 2502 | client->height = mon_height; 2503 | } 2504 | } 2505 | } 2506 | 2507 | /* 2508 | * Move window win as a result of pointer motion to coordinates 2509 | * rel_x,rel_y. 2510 | */ 2511 | void mousemove(struct client *client, int rel_x, int rel_y) 2512 | { 2513 | client->x = rel_x; 2514 | client->y = rel_y; 2515 | 2516 | if (conf.snapmargin > 0) 2517 | { 2518 | snapwindow(client, MCWM_MOVE); 2519 | } 2520 | 2521 | movelim(client); 2522 | } 2523 | 2524 | void mouseresize(struct client *client, int rel_x, int rel_y) 2525 | { 2526 | 2527 | client->width = abs(rel_x - client->x); 2528 | client->height = abs(rel_y - client->y); 2529 | 2530 | client->width -= (client->width - client->base_width) % client->width_inc; 2531 | client->height -= (client->height - client->base_height) 2532 | % client->height_inc; 2533 | 2534 | PDEBUG("Trying to resize to %dx%d (%dx%d)\n", client->width, client->height, 2535 | (client->width - client->base_width) / client->width_inc, 2536 | (client->height - client->base_height) / client->height_inc); 2537 | 2538 | if (conf.snapmargin > 0) 2539 | { 2540 | snapwindow(client, MCWM_RESIZE); 2541 | } 2542 | 2543 | resizelim(client); 2544 | 2545 | /* If this window was vertically maximized, remember that it isn't now. */ 2546 | if (client->vertmaxed) 2547 | { 2548 | client->vertmaxed = false; 2549 | } 2550 | } 2551 | 2552 | void movestep(struct client *client, char direction) 2553 | { 2554 | int16_t start_x; 2555 | int16_t start_y; 2556 | 2557 | if (NULL == client) 2558 | { 2559 | return; 2560 | } 2561 | 2562 | if (client->maxed) 2563 | { 2564 | /* We can't move a fully maximized window. */ 2565 | return; 2566 | } 2567 | 2568 | /* Save pointer position so we can warp pointer here later. */ 2569 | if (!getpointer(client->id, &start_x, &start_y)) 2570 | { 2571 | return; 2572 | } 2573 | 2574 | raisewindow(client->id); 2575 | switch (direction) 2576 | { 2577 | case 'h': 2578 | client->x = client->x - MOVE_STEP; 2579 | break; 2580 | 2581 | case 'j': 2582 | client->y = client->y + MOVE_STEP; 2583 | break; 2584 | 2585 | case 'k': 2586 | client->y = client->y - MOVE_STEP; 2587 | break; 2588 | 2589 | case 'l': 2590 | client->x = client->x + MOVE_STEP; 2591 | break; 2592 | 2593 | default: 2594 | PDEBUG("movestep: Moving in unknown direction.\n"); 2595 | break; 2596 | } /* switch direction */ 2597 | 2598 | movelim(client); 2599 | 2600 | /* 2601 | * If the pointer was inside the window to begin with, move 2602 | * pointer back to where it was, relative to the window. 2603 | */ 2604 | if (start_x > 0 - conf.borderwidth && start_x < client->width 2605 | + conf.borderwidth && start_y > 0 - conf.borderwidth && start_y 2606 | < client->height + conf.borderwidth) 2607 | { 2608 | xcb_warp_pointer(conn, XCB_NONE, client->id, 0, 0, 0, 0, 2609 | start_x, start_y); 2610 | xcb_flush(conn); 2611 | } 2612 | } 2613 | 2614 | void setborders(struct client *client, int width) 2615 | { 2616 | uint32_t values[1]; 2617 | uint32_t mask = 0; 2618 | 2619 | values[0] = width; 2620 | 2621 | mask |= XCB_CONFIG_WINDOW_BORDER_WIDTH; 2622 | xcb_configure_window(conn, client->id, mask, &values[0]); 2623 | xcb_flush(conn); 2624 | } 2625 | 2626 | void unmax(struct client *client) 2627 | { 2628 | uint32_t values[5]; 2629 | uint32_t mask = 0; 2630 | 2631 | if (NULL == client) 2632 | { 2633 | PDEBUG("unmax: client was NULL!\n"); 2634 | return; 2635 | } 2636 | 2637 | client->x = client->origsize.x; 2638 | client->y = client->origsize.y; 2639 | client->width = client->origsize.width; 2640 | client->height = client->origsize.height; 2641 | 2642 | /* Restore geometry. */ 2643 | if (client->maxed) 2644 | { 2645 | values[0] = client->x; 2646 | values[1] = client->y; 2647 | values[2] = client->width; 2648 | values[3] = client->height; 2649 | 2650 | /* Set borders again. */ 2651 | values[4] = conf.borderwidth; 2652 | 2653 | mask = 2654 | XCB_CONFIG_WINDOW_X 2655 | | XCB_CONFIG_WINDOW_Y 2656 | | XCB_CONFIG_WINDOW_WIDTH 2657 | | XCB_CONFIG_WINDOW_HEIGHT 2658 | | XCB_CONFIG_WINDOW_BORDER_WIDTH; 2659 | } 2660 | else 2661 | { 2662 | values[0] = client->y; 2663 | values[1] = client->width; 2664 | values[2] = client->height; 2665 | 2666 | mask = XCB_CONFIG_WINDOW_Y 2667 | | XCB_CONFIG_WINDOW_WIDTH 2668 | | XCB_CONFIG_WINDOW_HEIGHT; 2669 | } 2670 | 2671 | xcb_configure_window(conn, client->id, mask, values); 2672 | 2673 | /* Warp pointer to window or we might lose it. */ 2674 | xcb_warp_pointer(conn, XCB_NONE, client->id, 0, 0, 0, 0, 2675 | client->width / 2, client->height / 2); 2676 | 2677 | xcb_flush(conn); 2678 | } 2679 | 2680 | void maximize(struct client *client) 2681 | { 2682 | uint32_t values[4]; 2683 | uint32_t mask = 0; 2684 | int16_t mon_x; 2685 | int16_t mon_y; 2686 | uint16_t mon_width; 2687 | uint16_t mon_height; 2688 | 2689 | if (NULL == client) 2690 | { 2691 | PDEBUG("maximize: client was NULL!\n"); 2692 | return; 2693 | } 2694 | 2695 | if (NULL == client->monitor) 2696 | { 2697 | mon_x = 0; 2698 | mon_y = 0; 2699 | mon_width = screen->width_in_pixels; 2700 | mon_height = screen->height_in_pixels; 2701 | } 2702 | else 2703 | { 2704 | mon_x = client->monitor->x; 2705 | mon_y = client->monitor->y; 2706 | mon_width = client->monitor->width; 2707 | mon_height = client->monitor->height; 2708 | } 2709 | 2710 | /* 2711 | * Check if maximized already. If so, revert to stored 2712 | * geometry. 2713 | */ 2714 | if (client->maxed) 2715 | { 2716 | unmax(client); 2717 | client->maxed = false; 2718 | return; 2719 | } 2720 | 2721 | /* Raise first. Pretty silly to maximize below something else. */ 2722 | raisewindow(client->id); 2723 | 2724 | /* FIXME: Store original geom in property as well? */ 2725 | client->origsize.x = client->x; 2726 | client->origsize.y = client->y; 2727 | client->origsize.width = client->width; 2728 | client->origsize.height = client->height; 2729 | 2730 | /* Remove borders. */ 2731 | values[0] = 0; 2732 | mask = XCB_CONFIG_WINDOW_BORDER_WIDTH; 2733 | xcb_configure_window(conn, client->id, mask, values); 2734 | 2735 | /* Move to top left and resize. */ 2736 | client->x = mon_x; 2737 | client->y = mon_y; 2738 | client->width = mon_width; 2739 | client->height = mon_height; 2740 | 2741 | values[0] = client->x; 2742 | values[1] = client->y; 2743 | values[2] = client->width; 2744 | values[3] = client->height; 2745 | xcb_configure_window(conn, client->id, XCB_CONFIG_WINDOW_X 2746 | | XCB_CONFIG_WINDOW_Y 2747 | | XCB_CONFIG_WINDOW_WIDTH 2748 | | XCB_CONFIG_WINDOW_HEIGHT, values); 2749 | 2750 | xcb_flush(conn); 2751 | 2752 | client->maxed = true; 2753 | } 2754 | 2755 | void maxvert(struct client *client) 2756 | { 2757 | uint32_t values[2]; 2758 | int16_t mon_y; 2759 | uint16_t mon_height; 2760 | 2761 | if (NULL == client) 2762 | { 2763 | PDEBUG("maxvert: client was NULL\n"); 2764 | return; 2765 | } 2766 | 2767 | if (NULL == client->monitor) 2768 | { 2769 | mon_y = 0; 2770 | mon_height = screen->height_in_pixels; 2771 | } 2772 | else 2773 | { 2774 | mon_y = client->monitor->y; 2775 | mon_height = client->monitor->height; 2776 | } 2777 | 2778 | /* 2779 | * Check if maximized already. If so, revert to stored geometry. 2780 | */ 2781 | if (client->vertmaxed) 2782 | { 2783 | unmax(client); 2784 | client->vertmaxed = false; 2785 | return; 2786 | } 2787 | 2788 | /* Raise first. Pretty silly to maximize below something else. */ 2789 | raisewindow(client->id); 2790 | 2791 | /* 2792 | * Store original coordinates and geometry. 2793 | * FIXME: Store in property as well? 2794 | */ 2795 | client->origsize.x = client->x; 2796 | client->origsize.y = client->y; 2797 | client->origsize.width = client->width; 2798 | client->origsize.height = client->height; 2799 | 2800 | client->y = mon_y; 2801 | /* Compute new height considering height increments and screen height. */ 2802 | client->height = mon_height - conf.borderwidth * 2; 2803 | client->height -= (client->height - client->base_height) 2804 | % client->height_inc; 2805 | 2806 | /* Move to top of screen and resize. */ 2807 | values[0] = client->y; 2808 | values[1] = client->height; 2809 | 2810 | xcb_configure_window(conn, client->id, XCB_CONFIG_WINDOW_Y 2811 | | XCB_CONFIG_WINDOW_HEIGHT, values); 2812 | xcb_flush(conn); 2813 | 2814 | /* Remember that this client is vertically maximized. */ 2815 | client->vertmaxed = true; 2816 | } 2817 | 2818 | void hide(struct client *client) 2819 | { 2820 | long data[] = { XCB_ICCCM_WM_STATE_ICONIC, XCB_NONE }; 2821 | 2822 | /* 2823 | * Unmap window and declare iconic. 2824 | * 2825 | * Unmapping will generate an UnmapNotify event so we can forget 2826 | * about the window later. 2827 | */ 2828 | xcb_unmap_window(conn, client->id); 2829 | xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->id, 2830 | wm_state, wm_state, 32, 2, data); 2831 | xcb_flush(conn); 2832 | } 2833 | 2834 | bool getpointer(xcb_drawable_t win, int16_t *x, int16_t *y) 2835 | { 2836 | xcb_query_pointer_reply_t *pointer; 2837 | 2838 | pointer 2839 | = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, win), 0); 2840 | if (NULL == pointer) 2841 | { 2842 | return false; 2843 | } 2844 | 2845 | *x = pointer->win_x; 2846 | *y = pointer->win_y; 2847 | 2848 | free(pointer); 2849 | 2850 | return true; 2851 | } 2852 | 2853 | bool getgeom(xcb_drawable_t win, int16_t *x, int16_t *y, uint16_t *width, 2854 | uint16_t *height) 2855 | { 2856 | xcb_get_geometry_reply_t *geom; 2857 | 2858 | geom 2859 | = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL); 2860 | if (NULL == geom) 2861 | { 2862 | return false; 2863 | } 2864 | 2865 | *x = geom->x; 2866 | *y = geom->y; 2867 | *width = geom->width; 2868 | *height = geom->height; 2869 | 2870 | free(geom); 2871 | 2872 | return true; 2873 | } 2874 | 2875 | void topleft(void) 2876 | { 2877 | int16_t pointx; 2878 | int16_t pointy; 2879 | int16_t mon_x; 2880 | int16_t mon_y; 2881 | 2882 | if (NULL == focuswin) 2883 | { 2884 | return; 2885 | } 2886 | 2887 | if (NULL == focuswin->monitor) 2888 | { 2889 | mon_x = 0; 2890 | mon_y = 0; 2891 | } 2892 | else 2893 | { 2894 | mon_x = focuswin->monitor->x; 2895 | mon_y = focuswin->monitor->y; 2896 | } 2897 | 2898 | raisewindow(focuswin->id); 2899 | 2900 | if (!getpointer(focuswin->id, &pointx, &pointy)) 2901 | { 2902 | return; 2903 | } 2904 | 2905 | focuswin->x = mon_x; 2906 | focuswin->y = mon_y; 2907 | movewindow(focuswin->id, focuswin->x, focuswin->y); 2908 | xcb_warp_pointer(conn, XCB_NONE, focuswin->id, 0, 0, 0, 0, 2909 | pointx, pointy); 2910 | xcb_flush(conn); 2911 | } 2912 | 2913 | void topright(void) 2914 | { 2915 | int16_t pointx; 2916 | int16_t pointy; 2917 | int16_t mon_x; 2918 | uint16_t mon_y; 2919 | uint16_t mon_width; 2920 | 2921 | if (NULL == focuswin) 2922 | { 2923 | return; 2924 | } 2925 | 2926 | if (NULL == focuswin->monitor) 2927 | { 2928 | mon_width = screen->width_in_pixels; 2929 | mon_x = 0; 2930 | mon_y = 0; 2931 | } 2932 | else 2933 | { 2934 | mon_width = focuswin->monitor->width; 2935 | mon_x = focuswin->monitor->x; 2936 | mon_y = focuswin->monitor->y; 2937 | } 2938 | 2939 | raisewindow(focuswin->id); 2940 | 2941 | if (!getpointer(focuswin->id, &pointx, &pointy)) 2942 | { 2943 | return; 2944 | } 2945 | 2946 | focuswin->x = mon_x + mon_width - 2947 | (focuswin->width + conf.borderwidth * 2); 2948 | 2949 | focuswin->y = mon_y; 2950 | 2951 | movewindow(focuswin->id, focuswin->x, focuswin->y); 2952 | 2953 | xcb_warp_pointer(conn, XCB_NONE, focuswin->id, 0, 0, 0, 0, 2954 | pointx, pointy); 2955 | xcb_flush(conn); 2956 | } 2957 | 2958 | void botleft(void) 2959 | { 2960 | int16_t pointx; 2961 | int16_t pointy; 2962 | int16_t mon_x; 2963 | int16_t mon_y; 2964 | uint16_t mon_height; 2965 | 2966 | if (NULL == focuswin) 2967 | { 2968 | return; 2969 | } 2970 | 2971 | if (NULL == focuswin->monitor) 2972 | { 2973 | mon_x = 0; 2974 | mon_y = 0; 2975 | mon_height = screen->height_in_pixels; 2976 | } 2977 | else 2978 | { 2979 | mon_x = focuswin->monitor->x; 2980 | mon_y = focuswin->monitor->y; 2981 | mon_height = focuswin->monitor->height; 2982 | } 2983 | 2984 | raisewindow(focuswin->id); 2985 | 2986 | if (!getpointer(focuswin->id, &pointx, &pointy)) 2987 | { 2988 | return; 2989 | } 2990 | 2991 | focuswin->x = mon_x; 2992 | focuswin->y = mon_y + mon_height - (focuswin->height + conf.borderwidth 2993 | * 2); 2994 | movewindow(focuswin->id, focuswin->x, focuswin->y); 2995 | xcb_warp_pointer(conn, XCB_NONE, focuswin->id, 0, 0, 0, 0, 2996 | pointx, pointy); 2997 | xcb_flush(conn); 2998 | } 2999 | 3000 | void botright(void) 3001 | { 3002 | int16_t pointx; 3003 | int16_t pointy; 3004 | int16_t mon_x; 3005 | int16_t mon_y; 3006 | uint16_t mon_width; 3007 | uint16_t mon_height; 3008 | 3009 | if (NULL == focuswin) 3010 | { 3011 | return; 3012 | } 3013 | 3014 | if (NULL == focuswin->monitor) 3015 | { 3016 | mon_x = 0; 3017 | mon_y = 0; 3018 | mon_width = screen->width_in_pixels;; 3019 | mon_height = screen->height_in_pixels; 3020 | } 3021 | else 3022 | { 3023 | mon_x = focuswin->monitor->x; 3024 | mon_y = focuswin->monitor->y; 3025 | mon_width = focuswin->monitor->width; 3026 | mon_height = focuswin->monitor->height; 3027 | } 3028 | 3029 | raisewindow(focuswin->id); 3030 | 3031 | if (!getpointer(focuswin->id, &pointx, &pointy)) 3032 | { 3033 | return; 3034 | } 3035 | 3036 | focuswin->x = mon_x + mon_width - (focuswin->width + conf.borderwidth * 2); 3037 | focuswin->y = mon_y + mon_height - (focuswin->height + conf.borderwidth 3038 | * 2); 3039 | movewindow(focuswin->id, focuswin->x, focuswin->y); 3040 | xcb_warp_pointer(conn, XCB_NONE, focuswin->id, 0, 0, 0, 0, 3041 | pointx, pointy); 3042 | xcb_flush(conn); 3043 | } 3044 | 3045 | void deletewin(void) 3046 | { 3047 | xcb_get_property_cookie_t cookie; 3048 | xcb_icccm_get_wm_protocols_reply_t protocols; 3049 | bool use_delete = false; 3050 | uint32_t i; 3051 | 3052 | if (NULL == focuswin) 3053 | { 3054 | return; 3055 | } 3056 | 3057 | /* Check if WM_DELETE is supported. */ 3058 | cookie = xcb_icccm_get_wm_protocols_unchecked(conn, focuswin->id, 3059 | wm_protocols); 3060 | if (xcb_icccm_get_wm_protocols_reply(conn, cookie, &protocols, NULL) == 1) 3061 | { 3062 | for (i = 0; i < protocols.atoms_len; i++) 3063 | { 3064 | if (protocols.atoms[i] == wm_delete_window) 3065 | { 3066 | use_delete = true; 3067 | } 3068 | } 3069 | } 3070 | 3071 | xcb_icccm_get_wm_protocols_reply_wipe(&protocols); 3072 | 3073 | if (use_delete) 3074 | { 3075 | xcb_client_message_event_t ev = { 3076 | .response_type = XCB_CLIENT_MESSAGE, 3077 | .format = 32, 3078 | .sequence = 0, 3079 | .window = focuswin->id, 3080 | .type = wm_protocols, 3081 | .data.data32 = { wm_delete_window, XCB_CURRENT_TIME } 3082 | }; 3083 | 3084 | xcb_send_event(conn, false, focuswin->id, 3085 | XCB_EVENT_MASK_NO_EVENT, (char *) &ev); 3086 | } 3087 | else 3088 | { 3089 | xcb_kill_client(conn, focuswin->id); 3090 | } 3091 | 3092 | xcb_flush(conn); 3093 | } 3094 | 3095 | void prevscreen(void) 3096 | { 3097 | struct item *item; 3098 | 3099 | if (NULL == focuswin || NULL == focuswin->monitor) 3100 | { 3101 | return; 3102 | } 3103 | 3104 | item = focuswin->monitor->item->prev; 3105 | 3106 | if (NULL == item) 3107 | { 3108 | return; 3109 | } 3110 | 3111 | focuswin->monitor = item->data; 3112 | 3113 | raisewindow(focuswin->id); 3114 | fitonscreen(focuswin); 3115 | movelim(focuswin); 3116 | 3117 | xcb_warp_pointer(conn, XCB_NONE, focuswin->id, 0, 0, 0, 0, 3118 | 0, 0); 3119 | xcb_flush(conn); 3120 | } 3121 | 3122 | void nextscreen(void) 3123 | { 3124 | struct item *item; 3125 | 3126 | if (NULL == focuswin || NULL == focuswin->monitor) 3127 | { 3128 | return; 3129 | } 3130 | 3131 | item = focuswin->monitor->item->next; 3132 | 3133 | if (NULL == item) 3134 | { 3135 | return; 3136 | } 3137 | 3138 | focuswin->monitor = item->data; 3139 | 3140 | raisewindow(focuswin->id); 3141 | fitonscreen(focuswin); 3142 | movelim(focuswin); 3143 | 3144 | xcb_warp_pointer(conn, XCB_NONE, focuswin->id, 0, 0, 0, 0, 3145 | 0, 0); 3146 | xcb_flush(conn); 3147 | } 3148 | 3149 | void handle_keypress(xcb_key_press_event_t *ev) 3150 | { 3151 | int i; 3152 | key_enum_t key; 3153 | 3154 | for (key = KEY_MAX, i = KEY_F; i < KEY_MAX; i ++) 3155 | { 3156 | if (ev->detail == keys[i].keycode && 0 != keys[i].keycode) 3157 | { 3158 | key = i; 3159 | break; 3160 | } 3161 | } 3162 | if (key == KEY_MAX) 3163 | { 3164 | PDEBUG("Unknown key pressed.\n"); 3165 | 3166 | /* 3167 | * We don't know what to do with this key. Send this key press 3168 | * event to the focused window. 3169 | */ 3170 | xcb_send_event(conn, false, XCB_SEND_EVENT_DEST_ITEM_FOCUS, 3171 | XCB_EVENT_MASK_NO_EVENT, (char *) ev); 3172 | xcb_flush(conn); 3173 | return; 3174 | } 3175 | 3176 | if (MCWM_TABBING == mode && key != KEY_TAB && key != KEY_BACKTAB) 3177 | { 3178 | /* First finish tabbing around. Then deal with the next key. */ 3179 | finishtabbing(); 3180 | } 3181 | 3182 | /* Is it shifted? */ 3183 | if (ev->state & SHIFTMOD) 3184 | { 3185 | switch (key) 3186 | { 3187 | case KEY_H: /* h */ 3188 | resizestep(focuswin, 'h'); 3189 | break; 3190 | 3191 | case KEY_J: /* j */ 3192 | resizestep(focuswin, 'j'); 3193 | break; 3194 | 3195 | case KEY_K: /* k */ 3196 | resizestep(focuswin, 'k'); 3197 | break; 3198 | 3199 | case KEY_L: /* l */ 3200 | resizestep(focuswin, 'l'); 3201 | break; 3202 | 3203 | case KEY_TAB: /* shifted tab counts as backtab */ 3204 | focusnext(true); 3205 | break; 3206 | 3207 | default: 3208 | /* Ignore other shifted keys. */ 3209 | break; 3210 | } 3211 | } 3212 | else 3213 | { 3214 | switch (key) 3215 | { 3216 | case KEY_RET: /* return */ 3217 | start(conf.terminal); 3218 | break; 3219 | 3220 | case KEY_F: /* f */ 3221 | fixwindow(focuswin, true); 3222 | break; 3223 | 3224 | case KEY_H: /* h */ 3225 | movestep(focuswin, 'h'); 3226 | break; 3227 | 3228 | case KEY_J: /* j */ 3229 | movestep(focuswin, 'j'); 3230 | break; 3231 | 3232 | case KEY_K: /* k */ 3233 | movestep(focuswin, 'k'); 3234 | break; 3235 | 3236 | case KEY_L: /* l */ 3237 | movestep(focuswin, 'l'); 3238 | break; 3239 | 3240 | case KEY_TAB: /* tab */ 3241 | focusnext(false); 3242 | break; 3243 | 3244 | case KEY_BACKTAB: /* backtab */ 3245 | focusnext(true); 3246 | break; 3247 | 3248 | case KEY_M: /* m */ 3249 | maxvert(focuswin); 3250 | break; 3251 | 3252 | case KEY_R: /* r*/ 3253 | raiseorlower(focuswin); 3254 | break; 3255 | 3256 | case KEY_X: /* x */ 3257 | maximize(focuswin); 3258 | break; 3259 | 3260 | case KEY_1: 3261 | changeworkspace(0); 3262 | break; 3263 | 3264 | case KEY_2: 3265 | changeworkspace(1); 3266 | break; 3267 | 3268 | case KEY_3: 3269 | changeworkspace(2); 3270 | break; 3271 | 3272 | case KEY_4: 3273 | changeworkspace(3); 3274 | break; 3275 | 3276 | case KEY_5: 3277 | changeworkspace(4); 3278 | break; 3279 | 3280 | case KEY_6: 3281 | changeworkspace(5); 3282 | break; 3283 | 3284 | case KEY_7: 3285 | changeworkspace(6); 3286 | break; 3287 | 3288 | case KEY_8: 3289 | changeworkspace(7); 3290 | break; 3291 | 3292 | case KEY_9: 3293 | changeworkspace(8); 3294 | break; 3295 | 3296 | case KEY_0: 3297 | changeworkspace(9); 3298 | break; 3299 | 3300 | case KEY_Y: 3301 | topleft(); 3302 | break; 3303 | 3304 | case KEY_U: 3305 | topright(); 3306 | break; 3307 | 3308 | case KEY_B: 3309 | botleft(); 3310 | break; 3311 | 3312 | case KEY_N: 3313 | botright(); 3314 | break; 3315 | 3316 | case KEY_END: 3317 | deletewin(); 3318 | break; 3319 | 3320 | case KEY_PREVSCR: 3321 | prevscreen(); 3322 | break; 3323 | 3324 | case KEY_NEXTSCR: 3325 | nextscreen(); 3326 | break; 3327 | 3328 | case KEY_ICONIFY: 3329 | if (conf.allowicons) 3330 | { 3331 | hide(focuswin); 3332 | } 3333 | break; 3334 | 3335 | case KEY_PREVWS: 3336 | if (curws > 0) 3337 | { 3338 | changeworkspace(curws - 1); 3339 | } 3340 | else 3341 | { 3342 | changeworkspace(WORKSPACES - 1); 3343 | } 3344 | break; 3345 | 3346 | case KEY_NEXTWS: 3347 | changeworkspace((curws + 1) % WORKSPACES); 3348 | break; 3349 | 3350 | default: 3351 | /* Ignore other keys. */ 3352 | break; 3353 | } /* switch unshifted */ 3354 | } 3355 | } /* handle_keypress() */ 3356 | 3357 | /* Helper function to configure a window. */ 3358 | void configwin(xcb_window_t win, uint16_t mask, struct winconf wc) 3359 | { 3360 | uint32_t values[7]; 3361 | int i = -1; 3362 | 3363 | if (mask & XCB_CONFIG_WINDOW_X) 3364 | { 3365 | mask |= XCB_CONFIG_WINDOW_X; 3366 | i ++; 3367 | values[i] = wc.x; 3368 | } 3369 | 3370 | if (mask & XCB_CONFIG_WINDOW_Y) 3371 | { 3372 | mask |= XCB_CONFIG_WINDOW_Y; 3373 | i ++; 3374 | values[i] = wc.y; 3375 | } 3376 | 3377 | if (mask & XCB_CONFIG_WINDOW_WIDTH) 3378 | { 3379 | mask |= XCB_CONFIG_WINDOW_WIDTH; 3380 | i ++; 3381 | values[i] = wc.width; 3382 | } 3383 | 3384 | if (mask & XCB_CONFIG_WINDOW_HEIGHT) 3385 | { 3386 | mask |= XCB_CONFIG_WINDOW_HEIGHT; 3387 | i ++; 3388 | values[i] = wc.height; 3389 | } 3390 | 3391 | if (mask & XCB_CONFIG_WINDOW_SIBLING) 3392 | { 3393 | mask |= XCB_CONFIG_WINDOW_SIBLING; 3394 | i ++; 3395 | values[i] = wc.sibling; 3396 | } 3397 | 3398 | if (mask & XCB_CONFIG_WINDOW_STACK_MODE) 3399 | { 3400 | mask |= XCB_CONFIG_WINDOW_STACK_MODE; 3401 | i ++; 3402 | values[i] = wc.stackmode; 3403 | } 3404 | 3405 | if (-1 != i) 3406 | { 3407 | xcb_configure_window(conn, win, mask, values); 3408 | xcb_flush(conn); 3409 | } 3410 | } 3411 | 3412 | void configurerequest(xcb_configure_request_event_t *e) 3413 | { 3414 | struct client *client; 3415 | struct winconf wc; 3416 | int16_t mon_x; 3417 | int16_t mon_y; 3418 | uint16_t mon_width; 3419 | uint16_t mon_height; 3420 | 3421 | PDEBUG("event: Configure request. mask = %d\n", e->value_mask); 3422 | 3423 | /* Find the client. */ 3424 | if ((client = findclient(e->window))) 3425 | { 3426 | /* Find monitor position and size. */ 3427 | if (NULL == client || NULL == client->monitor) 3428 | { 3429 | mon_x = 0; 3430 | mon_y = 0; 3431 | mon_width = screen->width_in_pixels; 3432 | mon_height = screen->height_in_pixels; 3433 | } 3434 | else 3435 | { 3436 | mon_x = client->monitor->x; 3437 | mon_y = client->monitor->y; 3438 | mon_width = client->monitor->width; 3439 | mon_height = client->monitor->height; 3440 | } 3441 | 3442 | #if 0 3443 | /* 3444 | * We ignore moves the user haven't initiated, that is do 3445 | * nothing on XCB_CONFIG_WINDOW_X and XCB_CONFIG_WINDOW_Y 3446 | * ConfigureRequests. 3447 | * 3448 | * Code here if we ever change our minds or if you, dear user, 3449 | * wants this functionality. 3450 | */ 3451 | 3452 | if (e->value_mask & XCB_CONFIG_WINDOW_X) 3453 | { 3454 | /* Don't move window if maximized. Don't move off the screen. */ 3455 | if (!client->maxed && e->x > 0) 3456 | { 3457 | client->x = e->x; 3458 | } 3459 | } 3460 | 3461 | if (e->value_mask & XCB_CONFIG_WINDOW_Y) 3462 | { 3463 | /* 3464 | * Don't move window if maximized. Don't move off the 3465 | * screen. 3466 | */ 3467 | if (!client->maxed && !client->vertmaxed && e->y > 0) 3468 | { 3469 | client->y = e->y; 3470 | } 3471 | } 3472 | #endif 3473 | 3474 | if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH) 3475 | { 3476 | /* Don't resize if maximized. */ 3477 | if (!client->maxed) 3478 | { 3479 | client->width = e->width; 3480 | } 3481 | } 3482 | 3483 | if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT) 3484 | { 3485 | /* Don't resize if maximized. */ 3486 | if (!client->maxed && !client->vertmaxed) 3487 | { 3488 | client->height = e->height; 3489 | } 3490 | } 3491 | 3492 | /* 3493 | * XXX Do we really need to pass on sibling and stack mode 3494 | * configuration? Do we want to? 3495 | */ 3496 | if (e->value_mask & XCB_CONFIG_WINDOW_SIBLING) 3497 | { 3498 | uint32_t values[1]; 3499 | 3500 | values[0] = e->sibling; 3501 | xcb_configure_window(conn, e->window, 3502 | XCB_CONFIG_WINDOW_SIBLING, 3503 | values); 3504 | xcb_flush(conn); 3505 | 3506 | } 3507 | 3508 | if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) 3509 | { 3510 | uint32_t values[1]; 3511 | 3512 | values[0] = e->stack_mode; 3513 | xcb_configure_window(conn, e->window, 3514 | XCB_CONFIG_WINDOW_STACK_MODE, 3515 | values); 3516 | xcb_flush(conn); 3517 | } 3518 | 3519 | /* Check if window fits on screen after resizing. */ 3520 | 3521 | if (client->x + client->width + 2 * conf.borderwidth 3522 | > mon_x + mon_width) 3523 | { 3524 | /* 3525 | * See if it fits if we move away the window from the 3526 | * right edge of the screen. 3527 | */ 3528 | client->x = mon_x + mon_width 3529 | - (client->width + 2 * conf.borderwidth); 3530 | 3531 | /* 3532 | * If we moved over the left screen edge, move back and 3533 | * fit exactly on screen. 3534 | */ 3535 | if (client->x < mon_x) 3536 | { 3537 | client->x = mon_x; 3538 | client->width = mon_width - 2 * conf.borderwidth; 3539 | } 3540 | } 3541 | 3542 | if (client->y + client->height + 2 * conf.borderwidth 3543 | > mon_y + mon_height) 3544 | { 3545 | /* 3546 | * See if it fits if we move away the window from the 3547 | * bottom edge. 3548 | */ 3549 | client->y = mon_y + mon_height 3550 | - (client->height + 2 * conf.borderwidth); 3551 | 3552 | /* 3553 | * If we moved over the top screen edge, move back and fit 3554 | * on screen. 3555 | */ 3556 | if (client->y < mon_y) 3557 | { 3558 | PDEBUG("over the edge: y < %d\n", mon_y); 3559 | client->y = mon_y; 3560 | client->height = mon_height - 2 * conf.borderwidth; 3561 | } 3562 | } 3563 | 3564 | moveresize(client->id, client->x, client->y, client->width, 3565 | client->height); 3566 | } 3567 | else 3568 | { 3569 | PDEBUG("We don't know about this window yet.\n"); 3570 | 3571 | /* 3572 | * Unmapped window. Just pass all options except border 3573 | * width. 3574 | */ 3575 | wc.x = e->x; 3576 | wc.y = e->y; 3577 | wc.width = e->width; 3578 | wc.height = e->height; 3579 | wc.sibling = e->sibling; 3580 | wc.stackmode = e->stack_mode; 3581 | 3582 | configwin(e->window, e->value_mask, wc); 3583 | } 3584 | } 3585 | 3586 | void events(void) 3587 | { 3588 | xcb_generic_event_t *ev; 3589 | 3590 | int16_t mode_x = 0; /* X coord when in special mode */ 3591 | int16_t mode_y = 0; /* Y coord when in special mode */ 3592 | int fd; /* Our X file descriptor */ 3593 | fd_set in; /* For select */ 3594 | int found; /* Ditto. */ 3595 | 3596 | /* Get the file descriptor so we can do select() on it. */ 3597 | fd = xcb_get_file_descriptor(conn); 3598 | 3599 | for (sigcode = 0; 0 == sigcode;) 3600 | { 3601 | /* Prepare for select(). */ 3602 | FD_ZERO(&in); 3603 | FD_SET(fd, &in); 3604 | 3605 | /* 3606 | * Check for events, again and again. When poll returns NULL 3607 | * (and it does that a lot), we block on select() until the 3608 | * event file descriptor gets readable again. 3609 | * 3610 | * We do it this way instead of xcb_wait_for_event() since 3611 | * select() will return if we were interrupted by a signal. We 3612 | * like that. 3613 | */ 3614 | ev = xcb_poll_for_event(conn); 3615 | if (NULL == ev) 3616 | { 3617 | PDEBUG("xcb_poll_for_event() returned NULL.\n"); 3618 | 3619 | /* 3620 | * Check if we have an unrecoverable connection error, 3621 | * like a disconnected X server. 3622 | */ 3623 | if (xcb_connection_has_error(conn)) 3624 | { 3625 | cleanup(0); 3626 | exit(1); 3627 | } 3628 | 3629 | found = select(fd + 1, &in, NULL, NULL, NULL); 3630 | if (-1 == found) 3631 | { 3632 | if (EINTR == errno) 3633 | { 3634 | /* We received a signal. Break out of loop. */ 3635 | break; 3636 | } 3637 | else 3638 | { 3639 | /* Something was seriously wrong with select(). */ 3640 | fprintf(stderr, "mcwm: select failed."); 3641 | cleanup(0); 3642 | exit(1); 3643 | } 3644 | } 3645 | else 3646 | { 3647 | /* We found more events. Goto start of loop. */ 3648 | continue; 3649 | } 3650 | } 3651 | 3652 | #ifdef DEBUG 3653 | if (ev->response_type <= MAXEVENTS) 3654 | { 3655 | PDEBUG("Event: %s\n", evnames[ev->response_type]); 3656 | } 3657 | else 3658 | { 3659 | PDEBUG("Event: #%d. Not known.\n", ev->response_type); 3660 | } 3661 | #endif 3662 | 3663 | /* Note that we ignore XCB_RANDR_NOTIFY. */ 3664 | if (ev->response_type 3665 | == randrbase + XCB_RANDR_SCREEN_CHANGE_NOTIFY) 3666 | { 3667 | PDEBUG("RANDR screen change notify. Checking outputs.\n"); 3668 | getrandr(); 3669 | free(ev); 3670 | continue; 3671 | } 3672 | 3673 | switch (ev->response_type & ~0x80) 3674 | { 3675 | case XCB_MAP_REQUEST: 3676 | { 3677 | xcb_map_request_event_t *e; 3678 | 3679 | PDEBUG("event: Map request.\n"); 3680 | e = (xcb_map_request_event_t *) ev; 3681 | newwin(e->window); 3682 | } 3683 | break; 3684 | 3685 | case XCB_DESTROY_NOTIFY: 3686 | { 3687 | xcb_destroy_notify_event_t *e; 3688 | 3689 | e = (xcb_destroy_notify_event_t *) ev; 3690 | 3691 | /* 3692 | * If we had focus or our last focus in this window, 3693 | * forget about the focus. 3694 | * 3695 | * We will get an EnterNotify if there's another window 3696 | * under the pointer so we can set the focus proper later. 3697 | */ 3698 | if (NULL != focuswin) 3699 | { 3700 | if (focuswin->id == e->window) 3701 | { 3702 | focuswin = NULL; 3703 | } 3704 | } 3705 | if (NULL != lastfocuswin) 3706 | { 3707 | if (lastfocuswin->id == e->window) 3708 | { 3709 | lastfocuswin = NULL; 3710 | } 3711 | } 3712 | 3713 | /* 3714 | * Find this window in list of clients and forget about 3715 | * it. 3716 | */ 3717 | forgetwin(e->window); 3718 | } 3719 | break; 3720 | 3721 | case XCB_BUTTON_PRESS: 3722 | { 3723 | xcb_button_press_event_t *e; 3724 | 3725 | e = (xcb_button_press_event_t *) ev; 3726 | PDEBUG("Button %d pressed in window %ld, subwindow %d " 3727 | "coordinates (%d,%d)\n", 3728 | e->detail, (long)e->event, e->child, e->event_x, 3729 | e->event_y); 3730 | 3731 | if (0 == e->child) 3732 | { 3733 | /* Mouse click on root window. Start programs? */ 3734 | 3735 | switch (e->detail) 3736 | { 3737 | case 1: /* Mouse button one. */ 3738 | start(MOUSE1); 3739 | break; 3740 | 3741 | case 2: /* Middle mouse button. */ 3742 | start(MOUSE2); 3743 | break; 3744 | 3745 | case 3: /* Mouse button three. */ 3746 | start(MOUSE3); 3747 | break; 3748 | 3749 | default: 3750 | break; 3751 | } /* switch */ 3752 | 3753 | /* Break out of event switch. */ 3754 | break; 3755 | } 3756 | 3757 | /* 3758 | * If we don't have any currently focused window, we can't 3759 | * do anything. We don't want to do anything if the mouse 3760 | * cursor is in the wrong window (root window or a panel, 3761 | * for instance). There is a limit to sloppy focus. 3762 | */ 3763 | if (NULL == focuswin || focuswin->id != e->child) 3764 | { 3765 | break; 3766 | } 3767 | 3768 | /* 3769 | * If middle button was pressed, raise window or lower 3770 | * it if it was already on top. 3771 | */ 3772 | if (2 == e->detail) 3773 | { 3774 | raiseorlower(focuswin); 3775 | } 3776 | else 3777 | { 3778 | int16_t pointx; 3779 | int16_t pointy; 3780 | 3781 | /* We're moving or resizing. */ 3782 | 3783 | /* 3784 | * Get and save pointer position inside the window 3785 | * so we can go back to it when we're done moving 3786 | * or resizing. 3787 | */ 3788 | if (!getpointer(focuswin->id, &pointx, &pointy)) 3789 | { 3790 | break; 3791 | } 3792 | 3793 | mode_x = pointx; 3794 | mode_y = pointy; 3795 | 3796 | /* Raise window. */ 3797 | raisewindow(focuswin->id); 3798 | 3799 | /* Mouse button 1 was pressed. */ 3800 | if (1 == e->detail) 3801 | { 3802 | mode = MCWM_MOVE; 3803 | 3804 | /* 3805 | * Warp pointer to upper left of window before 3806 | * starting move. 3807 | */ 3808 | xcb_warp_pointer(conn, XCB_NONE, focuswin->id, 0, 0, 0, 0, 3809 | 1, 1); 3810 | } 3811 | else 3812 | { 3813 | /* Mouse button 3 was pressed. */ 3814 | 3815 | mode = MCWM_RESIZE; 3816 | 3817 | /* Warp pointer to lower right. */ 3818 | xcb_warp_pointer(conn, XCB_NONE, focuswin->id, 0, 0, 0, 3819 | 0, focuswin->width, focuswin->height); 3820 | } 3821 | 3822 | /* 3823 | * Take control of the pointer in the root window 3824 | * and confine it to root. 3825 | * 3826 | * Give us events when the key is released or if 3827 | * any motion occurs with the key held down. 3828 | * 3829 | * Keep updating everything else. 3830 | * 3831 | * Don't use any new cursor. 3832 | */ 3833 | xcb_grab_pointer(conn, 0, screen->root, 3834 | XCB_EVENT_MASK_BUTTON_RELEASE 3835 | | XCB_EVENT_MASK_BUTTON_MOTION 3836 | | XCB_EVENT_MASK_POINTER_MOTION_HINT, 3837 | XCB_GRAB_MODE_ASYNC, 3838 | XCB_GRAB_MODE_ASYNC, 3839 | screen->root, 3840 | XCB_NONE, 3841 | XCB_CURRENT_TIME); 3842 | 3843 | xcb_flush(conn); 3844 | 3845 | PDEBUG("mode now : %d\n", mode); 3846 | } 3847 | } 3848 | break; 3849 | 3850 | case XCB_MOTION_NOTIFY: 3851 | { 3852 | xcb_query_pointer_reply_t *pointer; 3853 | 3854 | /* 3855 | * We can't do anything if we don't have a focused window 3856 | * or if it's fully maximized. 3857 | */ 3858 | if (NULL == focuswin || focuswin->maxed) 3859 | { 3860 | break; 3861 | } 3862 | 3863 | /* 3864 | * This is not really a real notify, but just a hint that 3865 | * the mouse pointer moved. This means we need to get the 3866 | * current pointer position ourselves. 3867 | */ 3868 | pointer = xcb_query_pointer_reply( 3869 | conn, xcb_query_pointer(conn, screen->root), 0); 3870 | 3871 | if (NULL == pointer) 3872 | { 3873 | PDEBUG("Couldn't get pointer position.\n"); 3874 | break; 3875 | } 3876 | 3877 | /* 3878 | * Our pointer is moving and since we even get this event 3879 | * we're either resizing or moving a window. 3880 | */ 3881 | if (mode == MCWM_MOVE) 3882 | { 3883 | mousemove(focuswin, pointer->root_x, pointer->root_y); 3884 | } 3885 | else if (mode == MCWM_RESIZE) 3886 | { 3887 | mouseresize(focuswin, pointer->root_x, pointer->root_y); 3888 | } 3889 | else 3890 | { 3891 | PDEBUG("Motion event when we're not moving our resizing!\n"); 3892 | } 3893 | 3894 | free(pointer); 3895 | } 3896 | 3897 | break; 3898 | 3899 | case XCB_BUTTON_RELEASE: 3900 | PDEBUG("Mouse button released! mode = %d\n", mode); 3901 | 3902 | if (0 == mode) 3903 | { 3904 | /* 3905 | * Mouse button released, but not in a saved mode. Do 3906 | * nothing. 3907 | */ 3908 | break; 3909 | } 3910 | else 3911 | { 3912 | int16_t x; 3913 | int16_t y; 3914 | 3915 | /* We're finished moving or resizing. */ 3916 | 3917 | if (NULL == focuswin) 3918 | { 3919 | /* 3920 | * We don't seem to have a focused window! Just 3921 | * ungrab and reset the mode. 3922 | */ 3923 | PDEBUG("No focused window when finished moving or " 3924 | "resizing!"); 3925 | 3926 | xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); 3927 | xcb_flush(conn); /* Important! */ 3928 | 3929 | mode = 0; 3930 | break; 3931 | } 3932 | 3933 | /* 3934 | * We will get an EnterNotify and focus another window 3935 | * if the pointer just happens to be on top of another 3936 | * window when we ungrab the pointer, so we have to 3937 | * warp the pointer before to prevent this. 3938 | * 3939 | * Move to saved position within window or if that 3940 | * position is now outside current window, move inside 3941 | * window. 3942 | */ 3943 | if (mode_x > focuswin->width) 3944 | { 3945 | x = focuswin->width / 2; 3946 | if (0 == x) 3947 | { 3948 | x = 1; 3949 | } 3950 | 3951 | } 3952 | else 3953 | { 3954 | x = mode_x; 3955 | } 3956 | 3957 | if (mode_y > focuswin->height) 3958 | { 3959 | y = focuswin->height / 2; 3960 | if (0 == y) 3961 | { 3962 | y = 1; 3963 | } 3964 | } 3965 | else 3966 | { 3967 | y = mode_y; 3968 | } 3969 | 3970 | xcb_warp_pointer(conn, XCB_NONE, focuswin->id, 0, 0, 0, 0, 3971 | x, y); 3972 | xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); 3973 | xcb_flush(conn); /* Important! */ 3974 | 3975 | mode = 0; 3976 | PDEBUG("mode now = %d\n", mode); 3977 | } 3978 | break; 3979 | 3980 | case XCB_KEY_PRESS: 3981 | { 3982 | xcb_key_press_event_t *e = (xcb_key_press_event_t *)ev; 3983 | 3984 | PDEBUG("Key %d pressed\n", e->detail); 3985 | 3986 | handle_keypress(e); 3987 | } 3988 | break; 3989 | 3990 | case XCB_KEY_RELEASE: 3991 | { 3992 | xcb_key_release_event_t *e = (xcb_key_release_event_t *)ev; 3993 | unsigned i; 3994 | 3995 | PDEBUG("Key %d released.\n", e->detail); 3996 | 3997 | if (MCWM_TABBING == mode) 3998 | { 3999 | /* 4000 | * Check if it's the that was released was a key 4001 | * generating the MODKEY mask. 4002 | */ 4003 | for (i = 0; i < modkeys.len; i ++) 4004 | { 4005 | PDEBUG("Is it %d?\n", modkeys.keycodes[i]); 4006 | 4007 | if (e->detail == modkeys.keycodes[i]) 4008 | { 4009 | finishtabbing(); 4010 | 4011 | /* Get out of for... */ 4012 | break; 4013 | } 4014 | } 4015 | } 4016 | } 4017 | break; 4018 | 4019 | case XCB_ENTER_NOTIFY: 4020 | { 4021 | xcb_enter_notify_event_t *e = (xcb_enter_notify_event_t *)ev; 4022 | struct client *client; 4023 | 4024 | PDEBUG("event: Enter notify eventwin %d, child %d, detail %d.\n", 4025 | e->event, 4026 | e->child, 4027 | e->detail); 4028 | 4029 | /* 4030 | * If this isn't a normal enter notify, don't bother. 4031 | * 4032 | * We also need ungrab events, since these will be 4033 | * generated on button and key grabs and if the user for 4034 | * some reason presses a button on the root and then moves 4035 | * the pointer to our window and releases the button, we 4036 | * get an Ungrab EnterNotify. 4037 | * 4038 | * The other cases means the pointer is grabbed and that 4039 | * either means someone is using it for menu selections or 4040 | * that we're moving or resizing. We don't want to change 4041 | * focus in those cases. 4042 | */ 4043 | if (e->mode == XCB_NOTIFY_MODE_NORMAL 4044 | || e->mode == XCB_NOTIFY_MODE_UNGRAB) 4045 | { 4046 | /* 4047 | * If we're entering the same window we focus now, 4048 | * then don't bother focusing. 4049 | */ 4050 | if (NULL == focuswin || e->event != focuswin->id) 4051 | { 4052 | /* 4053 | * Otherwise, set focus to the window we just 4054 | * entered if we can find it among the windows we 4055 | * know about. If not, just keep focus in the old 4056 | * window. 4057 | */ 4058 | client = findclient(e->event); 4059 | if (NULL != client) 4060 | { 4061 | if (MCWM_TABBING != mode) 4062 | { 4063 | /* 4064 | * We are focusing on a new window. Since 4065 | * we're not currently tabbing around the 4066 | * window ring, we need to update the 4067 | * current workspace window list: Move 4068 | * first the old focus to the head of the 4069 | * list and then the new focus to the head 4070 | * of the list. 4071 | */ 4072 | if (NULL != focuswin) 4073 | { 4074 | movetohead(&wslist[curws], 4075 | focuswin->wsitem[curws]); 4076 | lastfocuswin = NULL; 4077 | } 4078 | 4079 | movetohead(&wslist[curws], client->wsitem[curws]); 4080 | } 4081 | 4082 | setfocus(client); 4083 | } 4084 | } 4085 | } 4086 | 4087 | } 4088 | break; 4089 | 4090 | case XCB_CONFIGURE_NOTIFY: 4091 | { 4092 | xcb_configure_notify_event_t *e 4093 | = (xcb_configure_notify_event_t *)ev; 4094 | 4095 | if (e->window == screen->root) 4096 | { 4097 | /* 4098 | * When using RANDR or Xinerama, the root can change 4099 | * geometry when the user adds a new screen, tilts 4100 | * their screen 90 degrees or whatnot. We might need 4101 | * to rearrange windows to be visible. 4102 | * 4103 | * We might get notified for several reasons, not just 4104 | * if the geometry changed. If the geometry is 4105 | * unchanged we do nothing. 4106 | */ 4107 | PDEBUG("Notify event for root!\n"); 4108 | PDEBUG("Possibly a new root geometry: %dx%d\n", 4109 | e->width, e->height); 4110 | 4111 | if (e->width == screen->width_in_pixels 4112 | && e->height == screen->height_in_pixels) 4113 | { 4114 | /* Root geometry is really unchanged. Do nothing. */ 4115 | PDEBUG("Hey! Geometry didn't change.\n"); 4116 | } 4117 | else 4118 | { 4119 | screen->width_in_pixels = e->width; 4120 | screen->height_in_pixels = e->height; 4121 | 4122 | /* Check for RANDR. */ 4123 | if (-1 == randrbase) 4124 | { 4125 | /* We have no RANDR so we rearrange windows to 4126 | * the new root geometry here. 4127 | * 4128 | * With RANDR enabled, we handle this per 4129 | * screen getrandr() when we receive an 4130 | * XCB_RANDR_SCREEN_CHANGE_NOTIFY event. 4131 | */ 4132 | arrangewindows(); 4133 | } 4134 | } 4135 | } 4136 | } 4137 | break; 4138 | 4139 | case XCB_CONFIGURE_REQUEST: 4140 | configurerequest((xcb_configure_request_event_t *) ev); 4141 | break; 4142 | 4143 | case XCB_CLIENT_MESSAGE: 4144 | { 4145 | xcb_client_message_event_t *e 4146 | = (xcb_client_message_event_t *)ev; 4147 | 4148 | if (conf.allowicons) 4149 | { 4150 | if (e->type == wm_change_state 4151 | && e->format == 32 4152 | && e->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) 4153 | { 4154 | long data[] = { XCB_ICCCM_WM_STATE_ICONIC, XCB_NONE }; 4155 | 4156 | /* Unmap window and declare iconic. */ 4157 | 4158 | xcb_unmap_window(conn, e->window); 4159 | xcb_change_property(conn, XCB_PROP_MODE_REPLACE, e->window, 4160 | wm_state, wm_state, 32, 2, data); 4161 | xcb_flush(conn); 4162 | } 4163 | } 4164 | } 4165 | break; 4166 | 4167 | case XCB_CIRCULATE_REQUEST: 4168 | { 4169 | xcb_circulate_request_event_t *e 4170 | = (xcb_circulate_request_event_t *)ev; 4171 | 4172 | /* 4173 | * Subwindow e->window to parent e->event is about to be 4174 | * restacked. 4175 | * 4176 | * Just do what was requested, e->place is either 4177 | * XCB_PLACE_ON_TOP or _ON_BOTTOM. We don't care. 4178 | */ 4179 | xcb_circulate_window(conn, e->window, e->place); 4180 | } 4181 | break; 4182 | 4183 | case XCB_MAPPING_NOTIFY: 4184 | { 4185 | xcb_mapping_notify_event_t *e 4186 | = (xcb_mapping_notify_event_t *)ev; 4187 | 4188 | /* 4189 | * XXX Gah! We get a new notify message for *every* key! 4190 | * We want to know when the entire keyboard is finished. 4191 | * Impossible? Better handling somehow? 4192 | */ 4193 | 4194 | /* 4195 | * We're only interested in keys and modifiers, not 4196 | * pointer mappings, for instance. 4197 | */ 4198 | if (e->request != XCB_MAPPING_MODIFIER 4199 | && e->request != XCB_MAPPING_KEYBOARD) 4200 | { 4201 | break; 4202 | } 4203 | 4204 | /* Forget old key bindings. */ 4205 | xcb_ungrab_key(conn, XCB_GRAB_ANY, screen->root, XCB_MOD_MASK_ANY); 4206 | 4207 | /* Use the new ones. */ 4208 | setupkeys(); 4209 | } 4210 | break; 4211 | 4212 | case XCB_UNMAP_NOTIFY: 4213 | { 4214 | xcb_unmap_notify_event_t *e = 4215 | (xcb_unmap_notify_event_t *)ev; 4216 | struct item *item; 4217 | struct client *client; 4218 | 4219 | /* 4220 | * Find the window in our *current* workspace list, then 4221 | * forget about it. If it gets mapped, we add it to our 4222 | * lists again then. 4223 | * 4224 | * Note that we might not know about the window we got the 4225 | * UnmapNotify event for. It might be a window we just 4226 | * unmapped on *another* workspace when changing 4227 | * workspaces, for instance, or it might be a window with 4228 | * override redirect set. This is not an error. 4229 | * 4230 | * XXX We might need to look in the global window list, 4231 | * after all. Consider if a window is unmapped on our last 4232 | * workspace while changing workspaces... If we do this, 4233 | * we need to keep track of our own windows and ignore 4234 | * UnmapNotify on them. 4235 | */ 4236 | for (item = wslist[curws]; item != NULL; item = item->next) 4237 | { 4238 | client = item->data; 4239 | 4240 | if (client->id == e->window) 4241 | { 4242 | PDEBUG("Forgetting about %d\n", e->window); 4243 | if (focuswin == client) 4244 | { 4245 | focuswin = NULL; 4246 | } 4247 | 4248 | forgetclient(client); 4249 | /* We're finished. Break out of for loop. */ 4250 | break; 4251 | } 4252 | } 4253 | } 4254 | break; 4255 | 4256 | } 4257 | 4258 | /* Forget about this event. */ 4259 | free(ev); 4260 | } 4261 | } 4262 | 4263 | void printhelp(void) 4264 | { 4265 | printf("mcwm: Usage: mcwm [-b] [-s snapmargin] [-t terminal-program] " 4266 | "[-f colour] [-u colour] [-x colour] \n"); 4267 | printf(" -b means draw no borders\n"); 4268 | printf(" -s snapmargin in pixels\n"); 4269 | printf(" -t urxvt will start urxvt when MODKEY + Return is pressed\n"); 4270 | printf(" -f colour sets colour for focused window borders of focused " 4271 | "to a named color.\n"); 4272 | printf(" -u colour sets colour for unfocused window borders.\n"); 4273 | printf(" -x color sets colour for fixed window borders.\n"); 4274 | } 4275 | 4276 | void sigcatch(int sig) 4277 | { 4278 | sigcode = sig; 4279 | } 4280 | 4281 | /* 4282 | * Get a defined atom from the X server. 4283 | */ 4284 | xcb_atom_t getatom(char *atom_name) 4285 | { 4286 | xcb_intern_atom_cookie_t atom_cookie; 4287 | xcb_atom_t atom; 4288 | xcb_intern_atom_reply_t *rep; 4289 | 4290 | atom_cookie = xcb_intern_atom(conn, 0, strlen(atom_name), atom_name); 4291 | rep = xcb_intern_atom_reply(conn, atom_cookie, NULL); 4292 | if (NULL != rep) 4293 | { 4294 | atom = rep->atom; 4295 | free(rep); 4296 | return atom; 4297 | } 4298 | 4299 | /* 4300 | * XXX Note that we return 0 as an atom if anything goes wrong. 4301 | * Might become interesting. 4302 | */ 4303 | return 0; 4304 | } 4305 | 4306 | int main(int argc, char **argv) 4307 | { 4308 | uint32_t mask = 0; 4309 | uint32_t values[2]; 4310 | int ch; /* Option character */ 4311 | xcb_void_cookie_t cookie; 4312 | xcb_generic_error_t *error; 4313 | xcb_drawable_t root; 4314 | char *focuscol; 4315 | char *unfocuscol; 4316 | char *fixedcol; 4317 | int scrno, i; 4318 | xcb_screen_iterator_t iter; 4319 | 4320 | /* Install signal handlers. */ 4321 | 4322 | /* We ignore child exists. Don't create zombies. */ 4323 | if (SIG_ERR == signal(SIGCHLD, SIG_IGN)) 4324 | { 4325 | perror("mcwm: signal"); 4326 | exit(1); 4327 | } 4328 | 4329 | if (SIG_ERR == signal(SIGINT, sigcatch)) 4330 | { 4331 | perror("mcwm: signal"); 4332 | exit(1); 4333 | } 4334 | 4335 | if (SIG_ERR == signal(SIGTERM, sigcatch)) 4336 | { 4337 | perror("mcwm: signal"); 4338 | exit(1); 4339 | } 4340 | 4341 | /* Set up defaults. */ 4342 | 4343 | conf.borderwidth = BORDERWIDTH; 4344 | conf.snapmargin = SNAPMARGIN; 4345 | conf.terminal = TERMINAL; 4346 | conf.allowicons = ALLOWICONS; 4347 | focuscol = FOCUSCOL; 4348 | unfocuscol = UNFOCUSCOL; 4349 | fixedcol = FIXEDCOL; 4350 | 4351 | while (1) 4352 | { 4353 | ch = getopt(argc, argv, "b:s:it:f:u:x:"); 4354 | if (-1 == ch) 4355 | { 4356 | 4357 | /* No more options, break out of while loop. */ 4358 | break; 4359 | } 4360 | 4361 | switch (ch) 4362 | { 4363 | case 'b': 4364 | /* Border width */ 4365 | conf.borderwidth = atoi(optarg); 4366 | break; 4367 | 4368 | case 's': 4369 | /* Snap margin */ 4370 | conf.snapmargin = atoi(optarg); 4371 | break; 4372 | 4373 | case 'i': 4374 | conf.allowicons = true; 4375 | break; 4376 | 4377 | case 't': 4378 | conf.terminal = optarg; 4379 | break; 4380 | 4381 | case 'f': 4382 | focuscol = optarg; 4383 | break; 4384 | 4385 | case 'u': 4386 | unfocuscol = optarg; 4387 | break; 4388 | 4389 | case 'x': 4390 | fixedcol = optarg; 4391 | break; 4392 | 4393 | default: 4394 | printhelp(); 4395 | exit(0); 4396 | } /* switch */ 4397 | } 4398 | 4399 | /* 4400 | * Use $DISPLAY. After connecting scrno will contain the value of 4401 | * the display's screen. 4402 | */ 4403 | conn = xcb_connect(NULL, &scrno); 4404 | if (xcb_connection_has_error(conn)) 4405 | { 4406 | perror("xcb_connect"); 4407 | exit(1); 4408 | } 4409 | 4410 | /* Find our screen. */ 4411 | iter = xcb_setup_roots_iterator(xcb_get_setup(conn)); 4412 | for (i = 0; i < scrno; ++ i) 4413 | { 4414 | xcb_screen_next(&iter); 4415 | } 4416 | 4417 | screen = iter.data; 4418 | if (!screen) 4419 | { 4420 | fprintf (stderr, "mcwm: Can't get the current screen. Exiting.\n"); 4421 | xcb_disconnect(conn); 4422 | exit(1); 4423 | } 4424 | 4425 | root = screen->root; 4426 | 4427 | PDEBUG("Screen size: %dx%d\nRoot window: %d\n", screen->width_in_pixels, 4428 | screen->height_in_pixels, screen->root); 4429 | 4430 | /* Get some colours. */ 4431 | conf.focuscol = getcolor(focuscol); 4432 | conf.unfocuscol = getcolor(unfocuscol); 4433 | conf.fixedcol = getcolor(fixedcol); 4434 | 4435 | /* Get some atoms. */ 4436 | atom_desktop = getatom("_NET_WM_DESKTOP"); 4437 | wm_delete_window = getatom("WM_DELETE_WINDOW"); 4438 | wm_change_state = getatom("WM_CHANGE_STATE"); 4439 | wm_state = getatom("WM_STATE"); 4440 | wm_protocols = getatom("WM_PROTOCOLS"); 4441 | 4442 | /* Check for RANDR extension and configure. */ 4443 | randrbase = setuprandr(); 4444 | 4445 | /* Loop over all clients and set up stuff. */ 4446 | if (0 != setupscreen()) 4447 | { 4448 | fprintf(stderr, "mcwm: Failed to initialize windows. Exiting.\n"); 4449 | xcb_disconnect(conn); 4450 | exit(1); 4451 | } 4452 | 4453 | /* Set up key bindings. */ 4454 | if (0 != setupkeys()) 4455 | { 4456 | fprintf(stderr, "mcwm: Couldn't set up keycodes. Exiting."); 4457 | xcb_disconnect(conn); 4458 | exit(1); 4459 | } 4460 | 4461 | /* Grab mouse buttons. */ 4462 | 4463 | xcb_grab_button(conn, 0, root, XCB_EVENT_MASK_BUTTON_PRESS 4464 | | XCB_EVENT_MASK_BUTTON_RELEASE, 4465 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, 4466 | 1 /* left mouse button */, 4467 | MOUSEMODKEY); 4468 | 4469 | xcb_grab_button(conn, 0, root, XCB_EVENT_MASK_BUTTON_PRESS 4470 | | XCB_EVENT_MASK_BUTTON_RELEASE, 4471 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, 4472 | 2 /* middle mouse button */, 4473 | MOUSEMODKEY); 4474 | 4475 | xcb_grab_button(conn, 0, root, XCB_EVENT_MASK_BUTTON_PRESS 4476 | | XCB_EVENT_MASK_BUTTON_RELEASE, 4477 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, 4478 | 3 /* right mouse button */, 4479 | MOUSEMODKEY); 4480 | 4481 | /* Subscribe to events. */ 4482 | mask = XCB_CW_EVENT_MASK; 4483 | 4484 | values[0] = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT 4485 | | XCB_EVENT_MASK_STRUCTURE_NOTIFY 4486 | | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; 4487 | 4488 | cookie = 4489 | xcb_change_window_attributes_checked(conn, root, mask, values); 4490 | error = xcb_request_check(conn, cookie); 4491 | 4492 | xcb_flush(conn); 4493 | 4494 | if (NULL != error) 4495 | { 4496 | fprintf(stderr, "mcwm: Can't get SUBSTRUCTURE REDIRECT. " 4497 | "Error code: %d\n" 4498 | "Another window manager running? Exiting.\n", 4499 | error->error_code); 4500 | 4501 | xcb_disconnect(conn); 4502 | 4503 | exit(1); 4504 | } 4505 | 4506 | /* Loop over events. */ 4507 | events(); 4508 | 4509 | /* Die gracefully. */ 4510 | cleanup(sigcode); 4511 | 4512 | exit(0); 4513 | } 4514 | -------------------------------------------------------------------------------- /mcwm.man: -------------------------------------------------------------------------------- 1 | .TH mcwm 1 "Aug 8, 2013" "" "" 2 | .SH NAME 3 | mcwm \- MC's Window Manager for X11. 4 | .SH SYNOPSIS 5 | .B mcwm 6 | [ 7 | .B \-b 8 | width ] 9 | [ 10 | .B \-i 11 | ] 12 | [ 13 | .B \-s 14 | .I snapmargin 15 | ] 16 | [ 17 | .B \-t 18 | .I terminal-program 19 | ] 20 | [ 21 | .B \-f 22 | .I colour 23 | ] 24 | [ 25 | .B \-u 26 | .I colour 27 | ] 28 | [ 29 | .B \-x 30 | .I colour 31 | ] 32 | 33 | .SH DESCRIPTION 34 | .B mcwm\fP is a window manager for the X Window System. 35 | 36 | .SH OPTIONS 37 | .PP 38 | \-b width sets border width to this many pixels. 39 | .PP 40 | \-i turns on icons/hidden windows. 41 | .B Please note 42 | that there is no way from mcwm to get a hidden window back! You have 43 | to use an external program such as a panel or the mcicon or 9icon 44 | scripts (see below) to get the window mapped again. 45 | .PP 46 | \-s snapmargin turns on window snapping to borders and other windows 47 | within snapmargin pixels. 48 | .PP 49 | \-t urxvt will start urxvt when MODKEY + Return is pressed. Change to 50 | your prefered terminal program or something else entirely. 51 | .PP 52 | \-f colour sets border colour for focused window to a named colour, 53 | such as "red". 54 | .PP 55 | \-u colour sets border colour for unfocused windows. 56 | .PP 57 | \-x colour sets border colour for fixed windows, that is, windows that 58 | are visible on all workspaces. 59 | 60 | .SH USE 61 | Nota bene: For mcwm to be at all useful you need to know how what keys 62 | generate the Mod1 and Mod4 modifier masks (default). If you don't 63 | know, use 64 | .B xmodmap(1) 65 | with the \-pm option to list them. If you don't want to use Mod1 and 66 | Mod4, you can change the modifiers in the file config.h and recompile. 67 | 68 | With the the default configuration, use mcwm like this. 69 | .PP 70 | Mod1 + mouse buttons: 71 | .RS 72 | .IP \(bu 2 73 | .B 1 74 | move 75 | .IP \(bu 2 76 | .B 2 77 | raise or lower 78 | .IP \(bu 2 79 | .B 3 80 | resize window 81 | .RE 82 | .PP 83 | Note that the mouse cursor needs to be inside the window you want to 84 | move, raise/lower or resize even if it currently has the focus. This 85 | is a feature, not a bug. 86 | .PP 87 | Mod4 + key on focused window: 88 | .RS 89 | .IP \(bu 2 90 | .B r 91 | raise or lower (toggles). 92 | .IP \(bu 2 93 | .B x 94 | maximize (toggles). 95 | .IP \(bu 2 96 | .B m 97 | maximize vertically (toggles). 98 | .IP \(bu 2 99 | .B H 100 | resize left. 101 | .IP \(bu 2 102 | .B J 103 | resize down. 104 | .IP \(bu 2 105 | .B K 106 | resize up. 107 | .IP \(bu 2 108 | .B L 109 | resize right. 110 | .IP \(bu 2 111 | .B h 112 | move left. 113 | .IP \(bu 2 114 | .B j 115 | move down. 116 | .IP \(bu 2 117 | .B k 118 | move up. 119 | .IP \(bu 2 120 | .B l 121 | move right. 122 | .IP \(bu 2 123 | .B y 124 | move to upper left corner of monitor. 125 | .IP \(bu 2 126 | .B u 127 | move to upper right corner of monitor. 128 | .IP \(bu 2 129 | .B b 130 | move to lower left corner of monitor. 131 | .IP \(bu 2 132 | .B n 133 | move to lower right corner of monitor. 134 | .IP \(bu 2 135 | .B Return 136 | start terminal or whatever program you have configured with -t or in 137 | the config.h. 138 | .IP \(bu 2 139 | .B Tab 140 | go to next window in the current workspace window ring. If you release 141 | MODKEY or press another command key mcwm will change focus to the new 142 | window. A new press of MODKEY + Tab will bring you back to the window 143 | where you last had focus. 144 | .IP \(bu 2 145 | .B Shift-Tab 146 | go to previous window in the current workspace window ring. This is 147 | most useful while you are tabbing: if you accidentally pressed Tab one 148 | time too many, you can move back by pressing Shift + TAB (all the 149 | while holding down the MODKEY). 150 | .IP \(bu 2 151 | .B f 152 | fix window so it is visible on all workspaces (toggles). Note that 153 | this is also used to move windows between workspaces: First fix the 154 | window, change to the workspace you want, then unfix the window on the 155 | new workspace. 156 | .IP \(bu 2 157 | .B i 158 | iconify (or hide) window from the display. Only usable when mcwm has 159 | been started with -i. Currently there is no way to get a hidden window 160 | back. You have to use an external program such as a panel or the 161 | mcicon or 9icon script in the mcwm distribution. 162 | .IP \(bu 2 163 | .B 0\-9 164 | go to workspace n, 0-9. 165 | .IP \(bu 2 166 | .B c 167 | go to previous workspace. 168 | .IP \(bu 2 169 | .B v 170 | go to next workspace. 171 | .IP \(bu 2 172 | .B End 173 | close window. 174 | .IP \(bu 2 175 | .B , 176 | move window to previous monitor. 177 | .IP \(bu 2 178 | .B . 179 | move window to next monitor. 180 | .RE 181 | .PP 182 | Note that all functions activated from the keyboard work on the 183 | currently focused window regardless of the position of the mouse 184 | cursor. Of course, changing workspaces has nothing to do with the 185 | focused window. 186 | .PP 187 | If you don't like the default key bindings, border width, et cetera, 188 | look in the config.h file, change and recompile. In the config.h file 189 | you can also define mouse button actions on the root window. By 190 | default button 3 starts the command mcmenu. You can write your own 191 | mcmenu by using, for instance, 9menu, dmenu or ratmenu. 192 | .SH ENVIRONMENT 193 | .B mcwm\fP obeys the $DISPLAY variable. 194 | .SH STARTING 195 | Typically the window manager is started from a script, either run by 196 | .B startx(1) 197 | or a login manager such as 198 | .B xdm(1). 199 | .PP 200 | If you start from the console, you need an .xinitrc file. Here's a 201 | complete example: 202 | .sp 203 | .in +4 204 | .nf 205 | \&#! /bin/sh 206 | 207 | # Set nice background. 208 | xsetroot -solid grey20 209 | 210 | # Set nice pointer cursor. 211 | xsetroot \-cursor_name plus \-fg white \-bg black 212 | 213 | # Load resources. 214 | xrdb \-load ~/.Xresources 215 | 216 | # Start window manager in the background. If it dies, X still lives. 217 | mcwm & 218 | 219 | # If you want to allow windows to be hidden, use this instead: 220 | # mcwm -i & 221 | 222 | # Start a terminal in the foreground. If this dies, X dies. 223 | exec urxvt 224 | .fi 225 | .in -4 226 | .sp 227 | .SH SCRIPTS 228 | You may want to define a menu program for use with mcwm (see 229 | config.h). In the source distribution you can find an example as 230 | mcmenu (the default menu program in config.h) in the scripts 231 | directory. 232 | .PP 233 | Christian Neukirchen wrote a little script you can use to get 234 | iconified windows mapped again if you are running mcwm in allow icons 235 | mode (-i). You need awk, xdotool, xprop and xwininfo installed. You 236 | can find the script as scripts/9icon. 237 | .PP 238 | Inspired by Christian's work I wrote a small program, hidden(1), which 239 | is included with mcwm. You can use hidden(1) with the -c option 240 | together with 9menu. See scripts/mcicon for an example. 241 | .PP 242 | You might also be interested in the following shell function that 243 | might come in handy to give your terminal emulators good titles before 244 | hiding them. 245 | .sp 246 | .in +4 247 | .nf 248 | # Set the title and icon name of an xterm or clone. 249 | function title 250 | { 251 | # icon name 252 | echo -e '\\033]1;'$1'\\007' 253 | # title 254 | echo -e '\\033]2;'$1'\\007' 255 | } 256 | .fi 257 | .in -4 258 | .sp 259 | Use it like this: 260 | .sp 261 | .in +4 262 | .nf 263 | % title 'really descriptive title' 264 | .fi 265 | .in -4 266 | .sp 267 | .SH SEE ALSO 268 | .B hidden(1) 269 | .SH AUTHOR 270 | Michael Cardell Widerkrantz . 271 | -------------------------------------------------------------------------------- /scripts/9icon: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # 9icon - show 9menu of hidden windows for mapping 4 | # Originally by Christian Neukirchen and 5 | # slightly changed by MC. 6 | 7 | IFS=" 8 | " 9 | 10 | for win in $(xwininfo -root -children | awk '$1~/0x/ && $2~/"/ {print $1}'); do 11 | xprop -id $win WM_NAME WM_STATE | 12 | awk -F'"' -v win=$win ' 13 | /^WM_NAME/ { name=$2 } 14 | /window state: Iconic/ { 15 | print "'\''" name "'\''" "'\''" ":xdotool windowmap " win " windowraise " win "'\''" 16 | } 17 | ' 18 | done | xargs 9menu -popup -label 9icon -bg grey20 -fg grey80 -font fixed 19 | -------------------------------------------------------------------------------- /scripts/mcicon: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # mcicon - list iconified windows in menu and map the chosen one. 4 | # Needs hidden (distributed with mcwm), 9menu and xdotool. 5 | 6 | hidden -c | xargs 9menu -popup -label 9icon -bg grey20 -fg grey80 -font 9x15 7 | -------------------------------------------------------------------------------- /scripts/mcmenu: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # hide: gives you a new pointer and hides the window you click on. 4 | # 5 | # list hidden: Install mcicon somewhere in path so the "list hidden" 6 | # alternative work. It will then list any iconified windows in a 7 | # 9menu. Select one and it's mapped again. 8 | # 9 | # cpu: Change to the host you usally connect a lot to. Add more ssh 10 | # hosts as you wish. 11 | # 12 | # VGA On/Off: Makes X aware of external screen connected on VGA port. 13 | # 14 | # close: Closes the 9menu program. 15 | exec 9menu -bg grey20 -fg grey80 -font 9x15 -popup \ 16 | 'hide:xdotool selectwindow windowminimize' \ 17 | 'list hidden:mcicon' \ 18 | 'cpu:urxvt -e ssh cpu.example.org' \ 19 | ':' \ 20 | 'VGA On: xrandr --output VGA --on' \ 21 | 'VGA Off: xrandr --output VGA --off' \ 22 | ':' \ 23 | 'close:' 24 | --------------------------------------------------------------------------------