├── LICENSE ├── Makefile ├── README ├── TODO ├── close.xbm ├── config.h ├── config.mk ├── debian ├── changelog ├── compat ├── control ├── copyright ├── docs ├── menu ├── rules └── watch ├── draw.c ├── echinus.1 ├── echinus.c ├── echinus.h ├── echinusrc ├── ewmh.c ├── iconify.xbm ├── max.xbm ├── parse.c └── tests ├── Makefile ├── config.mk ├── ewmhpanel.c ├── util.c └── util.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2006-2008 Anselm R. Garbe 4 | © 2006-2008 Sander van Dijk 5 | © 2006-2008 Jukka Salmi 6 | © 2008 Premysl Hruby 7 | © 2008 Szabolcs Nagy 8 | © 2008 Christof Musik 9 | © 2008 Jan Christoph Ebersbach 10 | © 2008 Henrik Holst 11 | © 2008-2010 Alexander Polakov 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a 14 | copy of this software and associated documentation files (the "Software"), 15 | to deal in the Software without restriction, including without limitation 16 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 17 | and/or sell copies of the Software, and to permit persons to whom the 18 | Software is furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in 21 | all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 26 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 29 | DEALINGS IN THE SOFTWARE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # echinus wm - a window manager 2 | # © 2006-2007 Anselm R. Garbe, Sander van Dijk 3 | # © 2008 Alexander Polakov 4 | 5 | include config.mk 6 | 7 | PIXMAPS = close.xbm iconify.xbm max.xbm 8 | SRC = draw.c echinus.c ewmh.c parse.c 9 | HEADERS = config.h echinus.h 10 | OBJ = ${SRC:.c=.o} 11 | 12 | all: options echinus ${HEADERS} 13 | 14 | options: 15 | @echo echinus build options: 16 | @echo "CFLAGS = ${CFLAGS}" 17 | @echo "LDFLAGS = ${LDFLAGS}" 18 | @echo "CC = ${CC}" 19 | 20 | .c.o: 21 | @echo CC $< 22 | @${CC} -c ${CFLAGS} $< 23 | 24 | ${OBJ}: config.mk ${HEADERS} 25 | 26 | echinus: ${OBJ} ${SRC} ${HEADERS} 27 | @echo CC -o $@ 28 | @${CC} -o $@ ${OBJ} ${LDFLAGS} 29 | 30 | clean: 31 | @echo cleaning 32 | @rm -f echinus ${OBJ} echinus-${VERSION}.tar.gz *~ 33 | 34 | dist: clean 35 | @echo creating dist tarball 36 | @mkdir -p echinus-${VERSION} 37 | @cp -R LICENSE Makefile README config.mk \ 38 | echinus.1 echinusrc ${SRC} ${HEADERS} ${PIXMAPS} echinus-${VERSION} 39 | @tar -cf echinus-${VERSION}.tar echinus-${VERSION} 40 | @gzip echinus-${VERSION}.tar 41 | @rm -rf echinus-${VERSION} 42 | 43 | install: all 44 | @echo installing executable file to ${DESTDIR}${BINPREFIX} 45 | @mkdir -p ${DESTDIR}${BINPREFIX} 46 | @cp -f echinus ${DESTDIR}${BINPREFIX} 47 | @chmod 755 ${DESTDIR}${BINPREFIX}/echinus 48 | @echo installing configuration file and pixmaps to ${DESTDIR}${CONFPREFIX}/echinus 49 | @mkdir -p ${DESTDIR}${CONFPREFIX}/echinus 50 | @cp echinusrc ${DESTDIR}${CONFPREFIX}/echinus 51 | @cp ${PIXMAPS} ${DESTDIR}${CONFPREFIX}/echinus 52 | @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 53 | @mkdir -p ${DESTDIR}${MANPREFIX}/man1 54 | @sed "s/VERSION/${VERSION}/g;s|CONFDIR|${DESTDIR}${CONF}|g" < echinus.1 > ${DESTDIR}${MANPREFIX}/man1/echinus.1 55 | @echo installing README to ${DESTDIR}${DOCPREFIX}/echinus 56 | @mkdir -p ${DESTDIR}${DOCPREFIX}/echinus 57 | @sed "s|CONFDIR|${CONF}|" < README > ${DESTDIR}${DOCPREFIX}/echinus/README 58 | 59 | uninstall: 60 | @echo removing executable file from ${DESTDIR}${BINPREFIX}/bin 61 | @rm -f ${DESTDIR}${BINPREFIX}/bin/echinus 62 | @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 63 | @rm -f ${DESTDIR}${MANPREFIX}/man1/echinus.1 64 | @echo removing configuration file and pixmaps from ${DESTDIR}${CONFPREFIX} 65 | @rm -rf ${DESTDIR}${CONFPREFIX} 66 | 67 | .PHONY: all options clean dist install uninstall 68 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | echinus wm. 2 | ========== 3 | 4 | WARNING: this project is not maintained anymore. 5 | 6 | 0.Installation 7 | 1.Configuration file 8 | 2.config.h header 9 | 3.About panels and pagers 10 | 11 | 12 | 0.Installation 13 | -------------- 14 | 15 | You need X11 and Xft headers to compile echinus wm and the pkg-config tool. 16 | Packages containing this stuff are probably named libx11-dev and libxft-dev 17 | (note "-dev" suffix). You need libxrandr for multihead support (can be 18 | disabled in config.mk if not needed). XRandr-enabled binary still works 19 | with single monitor configurations. 20 | 21 | # make 22 | # make install 23 | 24 | On new install: 25 | 26 | % mkdir ~/.echinus 27 | % cp -r CONFDIR ~/.echinus 28 | 29 | 1.Configuration file 30 | -------------------- 31 | 32 | Name: ~/.echinus/echinusrc 33 | 34 | Main settings 35 | 36 | Echinus*deflayout 37 | Layout applied to tag if not explicitly set by tags.layout later. 38 | One of: i - ifloating, f - floating, t - tiled, b - bottomstack, m - maximized. 39 | 40 | Echinus*gap 41 | 42 | Gap size between tiling windows in pixels. 43 | 44 | Echinus*mwfact 45 | 46 | Space occupied by master window in tiled layout (fraction of 1). 47 | 48 | Echinus*nmaster 49 | 50 | Number of clients in master area in tiled layout. 51 | 52 | Echinus*sloppy 53 | 54 | 0 - Click to focus 55 | 1 - Sloppy focus for floating clients 56 | 2 - Sloppy focus for everything 57 | 3 - Sloppy focus + raise on focus 58 | 59 | Echinus*modkey 60 | 61 | Choose modkey. A for alt, W - winkey, S - shift, C - control 62 | 63 | Title settings 64 | 65 | Echinus*decoratetiled - boolean (1 or 0) 66 | 67 | Draw titles in tiled mode 68 | 69 | Echinus*titleposition 70 | Echinus*tagbar 71 | Echinus*tbpos 72 | 73 | Were removed for more general interface, see below 74 | 75 | Echinus*titlelayout - string 76 | 77 | Titlebar consists of 3 parts separated with dashes or 78 | spaces. Left is aligned to left, center to center and 79 | right to right (obviously). 80 | 81 | Default value is "N IMC" meaning name on the left 82 | iconify, maximize and close buttons on right (note 83 | 2 spaces). 84 | 85 | - or ' ' (space) - skip space (space can't be 86 | used as first character) 87 | N - window name 88 | I - iconify 89 | M - maximize 90 | C - close 91 | T - tagbar 92 | 93 | Examples: 94 | 95 | -T-C - title centered, close button on right 96 | 97 | 98 | Color settings 99 | 100 | Echinus*selected.border 101 | Echinus*normal.border 102 | Echinus*selected.bg 103 | Echinus*normal.bg 104 | Echinus*selected.fg 105 | Echinus*normal.fg 106 | Echinus*selected.button 107 | Echinus*normal.button 108 | 109 | border - color of a small border around each window 110 | bg - titlebar background 111 | fg - titlebar foreground 112 | button - button foreground 113 | 114 | normal - unfocused window 115 | selected - window in focus 116 | 117 | Border settings 118 | 119 | Echinus*border 120 | 121 | width of the border, in pixels 122 | 123 | Button settings 124 | 125 | Echinus*button.iconify.pixmap (was: Echinus*button.left.pixmap) 126 | Echinus*button.maximize.pixmap (was: Echinus*button.center.pixmap) 127 | Echinus*button.close.pixmap (was: Echinus*button.right.pixmap) 128 | 129 | buttons in the titlebar, left iconifies window, center switch 130 | to monocle (fullscreen) mode, right - close. 131 | ~ are not allowed in pathnames. 132 | 133 | Opacity 134 | 135 | Echinus*opacity 136 | 137 | Opacity value for inactive windows, you need xcompmgr running to 138 | see the result. 139 | 140 | Terminal app 141 | 142 | Echinus*command (was: Echinus*terminal) 143 | 144 | Application to run on right window click on root window. 145 | 146 | Title 147 | 148 | Echinus*title 149 | 150 | Titlebar height 151 | 152 | Echinus*font 153 | 154 | Titlebar font 155 | 156 | Tags 157 | 158 | Echinus*tags.number 159 | 160 | Number of tags 161 | 162 | Echinus*tags.name{...} 163 | 164 | Names for the tags 165 | 166 | Echinus*tags.layout{...} 167 | 168 | Layout per tag on start. See deflayout for possible 169 | values 170 | 171 | Hacks 172 | 173 | Echinus*hidebastards 174 | 175 | Set to 1 to hide panels, pagers and others with 176 | togglestruts function. 177 | 178 | Keybindings 179 | 180 | Format is "[ASCW] + key", where: 181 | A - Alt (mod1) 182 | S - Shift 183 | C - Control 184 | W - Windows key 185 | You can find the list of available keys in keysym.h 186 | and XF86keysym.h files (usually located in 187 | /usr/include/X11/), remove XK_ prefix before use. 188 | 189 | Don't miss ' ' (space). 190 | 191 | Echinus*togglestruts 192 | 193 | Echinus has no bar, but this binding switches the area 194 | on top or bottom which won't be covered by windows in 195 | tiled or monocle mode. 196 | 197 | 198 | Echinus*togglemonitor 199 | 200 | Switch from one monitor to another. 201 | 202 | Echinus*focusnext 203 | Echinus*focusprev 204 | 205 | Focus next and previous window 206 | 207 | Echinus*viewprevtag 208 | 209 | View previous tag set 210 | 211 | Echinus*quit 212 | 213 | Leave echinus 214 | 215 | Echinus*restart 216 | 217 | Restart echinus 218 | 219 | Echinus*killclient 220 | 221 | Close window in focus 222 | 223 | Echinus*togglefloating 224 | 225 | Toggle floating/tiled 226 | 227 | Echinus*view# 228 | 229 | View tag number # 230 | 231 | Echinus*tag# 232 | 233 | Tag current window with tag number # 234 | 235 | Echinus*toggleview# 236 | 237 | Toggle view number # 238 | 239 | Echinus*focusview# 240 | 241 | Toggle view number # and focus the first client 242 | from it. 243 | 244 | Echinus*viewlefttag 245 | Echinus*viewrighttag 246 | 247 | View tag on the left(right) of current 248 | 249 | Echinus*spawn#: = program 250 | 251 | Run specified program. # is less than 64. 252 | 253 | Echinus*moveright 254 | Echinus*moveleft 255 | Echinus*moveup 256 | Echinus*movedown 257 | 258 | Syntax for this is rather untrivial: x y w h. For example move right for five pixels: 259 | 260 | Echinus*moveright: AS + l = +5 0 0 0 261 | 262 | Echinus*resizedecx 263 | Echinus*resizedecy 264 | Echinus*resizeincy 265 | Echinus*resizeincx 266 | 267 | Same as above, but change second group of values. Resize by y for 5 pixels: 268 | 269 | Echinus*resizedecy: AS + v = 0 0 0 -5 270 | 271 | Echinus*rule# 272 | 273 | Format is " " 274 | 275 | 2.config.h header 276 | ----------------- 277 | 278 | Contains default values of options. You probably don't have 279 | to edit it. 280 | 281 | 282 | 3.About panels and pagers 283 | ------------------------- 284 | 285 | Echinus supports some parts of EWMH, so if the author of the pager knows 286 | what this magical letters mean it *should* work fine. 287 | 288 | Known to work: 289 | fbpanel 290 | ipager 291 | ourico 292 | 293 | Known to NOT work: 294 | pypanel 295 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * bug reports 2 | * fix GNOME (and other DE's) compatibility 3 | * fix issues with tagging on multihead (anything left?) 4 | * fix focus problems 5 | * do code cleanup 6 | * ... 7 | -------------------------------------------------------------------------------- /close.xbm: -------------------------------------------------------------------------------- 1 | #define close_width 6 2 | #define close_height 6 3 | static unsigned char close_bits[] = { 4 | 0x00, 0x11, 0x0a, 0x04, 0x0a, 0x11 }; 5 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #define BORDERPX 1 3 | #define NF_OPACITY 0.9 4 | #define FONT "fixed-9" 5 | #define NORMBORDERCOLOR "#cccccc" 6 | #define NORMBUTTONCOLOR "#cccccc" 7 | #define NORMBGCOLOR "#cccccc" 8 | #define NORMFGCOLOR "#000000" 9 | #define SELBORDERCOLOR "#ff9900" 10 | #define SELBUTTONCOLOR "#ff9900" 11 | #define SELBGCOLOR "#ff9900" 12 | #define SELFGCOLOR "#ffffff" 13 | #define ICONPIXMAP "iconify.xbm" 14 | #define MAXPIXMAP "max.xbm" 15 | #define CLOSEPIXMAP "close.xbm" 16 | #define MINWIDTH 12 17 | #define MINHEIGHT 12 18 | #define TITLEHEIGHT 0 19 | #define COMMAND "xterm" 20 | #define DEFMWFACT 0.6 /* master width factor [0.1 .. 0.9] */ 21 | #define DEFNMASTER 1 /* number of windows in master area */ 22 | #define SNAP 5 /* snap pixel */ 23 | #define DECORATETILED 0 /* set to 1 to draw titles in tiled layouts */ 24 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # echinus wm version 2 | VERSION = 0.4.9 3 | 4 | # Customize below to fit your system 5 | 6 | # paths 7 | PREFIX?= /usr/local 8 | BINPREFIX?= ${PREFIX}/bin 9 | MANPREFIX?= ${PREFIX}/share/man 10 | CONFPREFIX?= ${PREFIX}/share/examples 11 | DOCPREFIX?= ${PREFIX}/share/doc 12 | CONF?= ${CONFPREFIX} 13 | 14 | X11INC?= /usr/X11R6/include 15 | X11LIB?= /usr/X11R6/lib 16 | 17 | # includes and libs 18 | INCS = -I. -I/usr/include -I${X11INC} `pkg-config --cflags xft` 19 | LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 `pkg-config --libs xft` 20 | 21 | DEFS = -DVERSION=\"${VERSION}\" -DSYSCONFPATH=\"${CONF}\" 22 | 23 | # flags 24 | CFLAGS = -Os ${INCS} ${DEFS} 25 | LDFLAGS = -s ${LIBS} 26 | # debug flags 27 | CFLAGS = -g3 -ggdb3 -std=c99 -pedantic -O0 ${INCS} -DDEBUG ${DEFS} 28 | LDFLAGS = -g3 -ggdb3 ${LIBS} 29 | 30 | # DEBUG: Show warnings (if any). Comment out to disable. 31 | #CFLAGS += -Wall -Wpadded 32 | # mostly useless warnings 33 | #CFLAGS += -W -Wcast-qual -Wshadow -Wwrite-strings 34 | #CFLAGS += -Werror # Treat warnings as errors. 35 | #CFLAGS += -save-temps # Keep precompiler output (great for debugging). 36 | 37 | # XRandr (multihead support). Comment out to disable. 38 | CFLAGS += -DXRANDR=1 39 | LIBS += -lXrandr 40 | 41 | # Solaris 42 | #CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" 43 | #LDFLAGS = ${LIBS} 44 | #CFLAGS += -xtarget=ultra 45 | 46 | # compiler and linker 47 | #CC = cc 48 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | echinus (0.4.1-git01302010) unstable; urgency=low 2 | 3 | * Initial release (Closes: #551123) 4 | 5 | -- Alexander Polakov Thu, 15 Oct 2009 23:07:32 +0400 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: echinus 2 | Section: x11 3 | Priority: extra 4 | Maintainer: Alexander Polakov 5 | Build-Depends: debhelper (>= 7.0.50), libx11-dev, libxrandr-dev, libxft-dev 6 | Standards-Version: 3.8.3 7 | Homepage: http://rootshell.be/~polachok/code 8 | 9 | Package: echinus 10 | Architecture: any 11 | Depends: ${shlibs:Depends}, ${misc:Depends} 12 | Recommends: dwm-tools, x11-xserver-utils 13 | Provides: x-window-manager 14 | Description: lightweight tiling window manager 15 | echinus is a dynamic window manager for X11 supporting 16 | managing windows in floating, tiled and maximized 17 | layouts based on dwm. All the configuration is made 18 | via config file in Xresources format, so it is not 19 | necessary to recompile echinus every time you change 20 | something. 21 | echinus supports a small subset of EWMH to be 22 | compatible with external panels and pagers. 23 | It draws a border around windows and also an 24 | optional title bar. The goal of development is 25 | a small, fast window manager without features not 26 | strictly related to window management. 27 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | This work was packaged for Debian by: 2 | 3 | Alexander Polakov on Thu, 15 Oct 2009 23:07:32 +0400 4 | 5 | It was downloaded from http://rootshell.be/~polachok/code 6 | 7 | Upstream Author(s): 8 | 9 | Alexander Polakov 10 | 11 | Copyright: 12 | 13 | Anselm R. Garbe 14 | Sander van Dijk 15 | Jukka Salmi 16 | Premysl Hruby 17 | Szabolcs Nagy 18 | Christof Musik 19 | Jan Christoph Ebersbach 20 | Henrik Holst 21 | Alexander Polakov 22 | 23 | License: MIT 24 | 25 | Permission is hereby granted, free of charge, to any person obtaining a 26 | copy of this software and associated documentation files (the "Software"), 27 | to deal in the Software without restriction, including without limitation 28 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 29 | and/or sell copies of the Software, and to permit persons to whom the 30 | Software is furnished to do so, subject to the following conditions: 31 | 32 | The above copyright notice and this permission notice shall be included in 33 | all copies or substantial portions of the Software. 34 | 35 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 36 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 37 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 38 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 39 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 40 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 41 | DEALINGS IN THE SOFTWARE. 42 | 43 | The Debian packaging is: 44 | 45 | Copyright (C) 2009 Alexander Polakov 46 | 47 | Permission is hereby granted, free of charge, to any person obtaining a 48 | copy of this software and associated documentation files (the "Software"), 49 | to deal in the Software without restriction, including without limitation 50 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 51 | and/or sell copies of the Software, and to permit persons to whom the 52 | Software is furnished to do so, subject to the following conditions: 53 | 54 | The above copyright notice and this permission notice shall be included in 55 | all copies or substantial portions of the Software. 56 | 57 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 58 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 59 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 60 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 61 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 62 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 63 | DEALINGS IN THE SOFTWARE. 64 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README 2 | -------------------------------------------------------------------------------- /debian/menu: -------------------------------------------------------------------------------- 1 | ?package(echinus):needs="wm" section="Window Managers"\ 2 | title="echinus" command="/usr/bin/echinus" 3 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | # Sample debian/rules that uses debhelper. 4 | # This file was originally written by Joey Hess and Craig Small. 5 | # As a special exception, when this file is copied by dh-make into a 6 | # dh-make output file, you may use that output file without restriction. 7 | # This special exception was added by Craig Small in version 0.37 of dh-make. 8 | 9 | # Uncomment this to turn on verbose mode. 10 | #export DH_VERBOSE=1 11 | 12 | %: 13 | dh $@ 14 | 15 | override_dh_auto_build: 16 | CFLAGS="$(CFLAGS)" $(MAKE) PREFIX=/usr CONF=/share/doc/echinus/examples CONFPREFIX=/usr/share/doc/echinus/examples 17 | 18 | override_dh_auto_install: 19 | sed -i '/Copy example/ d' echinus.1 20 | $(MAKE) install DESTDIR=$(CURDIR)/debian/echinus PREFIX=/usr CONF=/share/doc/echinus/examples CONFPREFIX=/usr/share/doc/echinus/examples 21 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | # Example watch control file for uscan 2 | # Rename this file to "watch" and then you can run the "uscan" command 3 | # to check for upstream updates and more. 4 | # See uscan(1) for format 5 | 6 | # Compulsory line, this is a version 3 file 7 | version=3 8 | 9 | # Uncomment to examine a Webpage 10 | # 11 | #http://www.example.com/downloads.php echinus-(.*)\.tar\.gz 12 | 13 | # Uncomment to examine a Webserver directory 14 | http://rootshell.be/~polachok/code/echinus-(.*)\.tar\.gz 15 | 16 | # Uncommment to examine a FTP server 17 | #ftp://ftp.example.com/pub/echinus-(.*)\.tar\.gz debian uupdate 18 | 19 | # Uncomment to find new files on sourceforge, for devscripts >= 2.9 20 | # http://sf.net/echinus/echinus-(.*)\.tar\.gz 21 | 22 | # Uncomment to find new files on GooglePages 23 | # http://example.googlepages.com/foo.html echinus-(.*)\.tar\.gz 24 | -------------------------------------------------------------------------------- /draw.c: -------------------------------------------------------------------------------- 1 | /* 2 | * echinus wm written by Alexander Polakov 3 | * this file contains code related to drawing 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "echinus.h" 14 | #include "config.h" 15 | 16 | enum { Normal, Selected }; 17 | enum { AlignLeft, AlignCenter, AlignRight }; /* title position */ 18 | 19 | static unsigned int textnw(const char *text, unsigned int len); 20 | static unsigned int textw(const char *text); 21 | 22 | typedef struct { 23 | unsigned int x, y, w, h; 24 | struct { 25 | XGlyphInfo *extents; 26 | int ascent; 27 | int descent; 28 | int height; 29 | int width; 30 | } font; 31 | GC gc; 32 | } DC; /* draw context */ 33 | 34 | DC dc; 35 | 36 | static int 37 | drawtext(const char *text, Drawable drawable, XftDraw *xftdrawable, 38 | unsigned long col[ColLast], int x, int y, int mw) { 39 | int w, h; 40 | char buf[256]; 41 | unsigned int len, olen; 42 | 43 | if (!text) 44 | return 0; 45 | olen = len = strlen(text); 46 | w = 0; 47 | if (len >= sizeof buf) 48 | len = sizeof buf - 1; 49 | memcpy(buf, text, len); 50 | buf[len] = 0; 51 | h = style.titleheight; 52 | y = dc.h / 2 + dc.font.ascent / 2 - 1 - style.outline; 53 | x += dc.font.height / 2; 54 | /* shorten text if necessary */ 55 | while (len && (w = textnw(buf, len)) > mw) { 56 | buf[--len] = 0; 57 | } 58 | if (len < olen) { 59 | if (len > 1) 60 | buf[len - 1] = '.'; 61 | if (len > 2) 62 | buf[len - 2] = '.'; 63 | if (len > 3) 64 | buf[len - 3] = '.'; 65 | } 66 | if (w > mw) 67 | return 0; /* too long */ 68 | while (x <= 0) 69 | x = dc.x++; 70 | XSetForeground(dpy, dc.gc, col[ColBG]); 71 | XFillRectangle(dpy, drawable, dc.gc, x - dc.font.height / 2, 0, 72 | w + dc.font.height, h); 73 | XftDrawStringUtf8(xftdrawable, 74 | (col == style.color.norm) ? style.color.font[Normal] : style.color.font[Selected], 75 | style.font, x, y, (unsigned char *) buf, len); 76 | return w + dc.font.height; 77 | } 78 | 79 | static int 80 | drawbutton(Drawable d, Button btn, unsigned long col[ColLast], int x, int y) { 81 | if (btn.action == NULL) 82 | return 0; 83 | XSetForeground(dpy, dc.gc, col[ColBG]); 84 | XFillRectangle(dpy, d, dc.gc, x, 0, dc.h, dc.h); 85 | XSetForeground(dpy, dc.gc, btn.pressed ? col[ColFG] : col[ColButton]); 86 | XSetBackground(dpy, dc.gc, col[ColBG]); 87 | XCopyPlane(dpy, btn.pm, d, dc.gc, 0, 0, button[Iconify].pw, 88 | button[Iconify].ph, x, y + button[Iconify].py, 1); 89 | return dc.h; 90 | } 91 | 92 | static int 93 | drawelement(char which, int x, int position, Client *c) { 94 | int w; 95 | unsigned int j; 96 | unsigned long *color = c == sel ? style.color.sel : style.color.norm; 97 | 98 | switch (which) { 99 | case 'T': 100 | w = 0; 101 | for (j = 0; j < ntags; j++) { 102 | if (c->tags[j]) 103 | w += drawtext(tags[j], c->drawable, c->xftdraw, 104 | color, dc.x, dc.y, dc.w); 105 | } 106 | break; 107 | case '|': 108 | XSetForeground(dpy, dc.gc, color[ColBorder]); 109 | XDrawLine(dpy, c->drawable, dc.gc, dc.x + dc.h / 4, 0, 110 | dc.x + dc.h / 4, dc.h); 111 | w = dc.h / 2; 112 | break; 113 | case 'N': 114 | w = drawtext(c->name, c->drawable, c->xftdraw, color, dc.x, dc.y, dc.w); 115 | break; 116 | case 'I': 117 | button[Iconify].x = dc.x; 118 | w = drawbutton(c->drawable, button[Iconify], color, 119 | dc.x, dc.h / 2 - button[Iconify].ph / 2); 120 | break; 121 | case 'M': 122 | button[Maximize].x = dc.x; 123 | w = drawbutton(c->drawable, button[Maximize], color, 124 | dc.x, dc.h / 2 - button[Maximize].ph / 2); 125 | break; 126 | case 'C': 127 | button[Close].x = dc.x; 128 | w = drawbutton(c->drawable, button[Close], color, dc.x, 129 | dc.h / 2 - button[Maximize].ph / 2); 130 | break; 131 | default: 132 | w = 0; 133 | break; 134 | } 135 | return w; 136 | } 137 | 138 | static int 139 | elementw(char which, Client *c) { 140 | int w; 141 | unsigned int j; 142 | 143 | switch (which) { 144 | case 'I': 145 | case 'M': 146 | case 'C': 147 | return dc.h; 148 | case 'N': 149 | return textw(c->name); 150 | case 'T': 151 | w = 0; 152 | for (j = 0; j < ntags; j++) { 153 | if (c->tags[j]) 154 | w += textw(tags[j]); 155 | } 156 | return w; 157 | case '|': 158 | return dc.h / 2; 159 | } 160 | return 0; 161 | } 162 | 163 | void 164 | drawclient(Client *c) { 165 | size_t i; 166 | 167 | if (style.opacity) { 168 | setopacity(c, c == sel ? OPAQUE : style.opacity); 169 | } 170 | if (!isvisible(c, NULL)) 171 | return; 172 | if (!c->title) 173 | return; 174 | dc.x = dc.y = 0; 175 | dc.w = c->w; 176 | dc.h = style.titleheight; 177 | XftDrawChange(c->xftdraw, c->drawable); 178 | XSetForeground(dpy, dc.gc, c == sel ? style.color.sel[ColBG] : style.color.norm[ColBG]); 179 | XSetLineAttributes(dpy, dc.gc, style.border, LineSolid, CapNotLast, JoinMiter); 180 | XFillRectangle(dpy, c->drawable, dc.gc, dc.x, dc.y, dc.w, dc.h); 181 | if (dc.w < textw(c->name)) { 182 | dc.w -= dc.h; 183 | button[Close].x = dc.w; 184 | drawtext(c->name, c->drawable, c->xftdraw, 185 | c == sel ? style.color.sel : style.color.norm, dc.x, dc.y, dc.w); 186 | drawbutton(c->drawable, button[Close], 187 | c == sel ? style.color.sel : style.color.norm, dc.w, 188 | dc.h / 2 - button[Close].ph / 2); 189 | goto end; 190 | } 191 | /* Left */ 192 | for (i = 0; i < strlen(style.titlelayout); i++) { 193 | if (style.titlelayout[i] == ' ' || style.titlelayout[i] == '-') 194 | break; 195 | dc.x += drawelement(style.titlelayout[i], dc.x, AlignLeft, c); 196 | } 197 | if (i == strlen(style.titlelayout) || dc.x >= dc.w) 198 | goto end; 199 | /* Center */ 200 | dc.x = dc.w / 2; 201 | for (i++; i < strlen(style.titlelayout); i++) { 202 | if (style.titlelayout[i] == ' ' || style.titlelayout[i] == '-') 203 | break; 204 | dc.x -= elementw(style.titlelayout[i], c) / 2; 205 | dc.x += drawelement(style.titlelayout[i], 0, AlignCenter, c); 206 | } 207 | if (i == strlen(style.titlelayout) || dc.x >= dc.w) 208 | goto end; 209 | /* Right */ 210 | dc.x = dc.w; 211 | for (i = strlen(style.titlelayout); i-- ; ) { 212 | if (style.titlelayout[i] == ' ' || style.titlelayout[i] == '-') 213 | break; 214 | dc.x -= elementw(style.titlelayout[i], c); 215 | drawelement(style.titlelayout[i], 0, AlignRight, c); 216 | } 217 | end: 218 | if (style.outline) { 219 | XSetForeground(dpy, dc.gc, 220 | c == sel ? style.color.sel[ColBorder] : style.color.norm[ColBorder]); 221 | XDrawLine(dpy, c->drawable, dc.gc, 0, dc.h - 1, dc.w, dc.h - 1); 222 | } 223 | XCopyArea(dpy, c->drawable, c->title, dc.gc, 0, 0, c->w, dc.h, 0, 0); 224 | } 225 | 226 | static unsigned long 227 | getcolor(const char *colstr) { 228 | XColor color; 229 | 230 | if (!XAllocNamedColor(dpy, DefaultColormap(dpy, screen), colstr, &color, &color)) 231 | eprint("error, cannot allocate color '%s'\n", colstr); 232 | return color.pixel; 233 | } 234 | 235 | static int 236 | initpixmap(const char *file, Button *b) { 237 | b->pm = XCreatePixmap(dpy, root, style.titleheight, style.titleheight, 1); 238 | if (BitmapSuccess == XReadBitmapFile(dpy, root, file, &b->pw, &b->ph, 239 | &b->pm, &b->px, &b->py)) { 240 | if (b->px == -1 || b->py == -1) 241 | b->px = b->py = 0; 242 | return 0; 243 | } else 244 | return 1; 245 | } 246 | 247 | static void 248 | initbuttons() { 249 | button[Iconify].action = iconify; 250 | button[Maximize].action = togglemax; 251 | button[Close].action = killclient; 252 | button[Iconify].x = button[Close].x = button[Maximize].x = -1; 253 | XSetForeground(dpy, dc.gc, style.color.norm[ColButton]); 254 | XSetBackground(dpy, dc.gc, style.color.norm[ColBG]); 255 | if (initpixmap(getresource("button.iconify.pixmap", ICONPIXMAP), 256 | &button[Iconify])) 257 | button[Iconify].action = NULL; 258 | if (initpixmap(getresource("button.maximize.pixmap", MAXPIXMAP), 259 | &button[Maximize])) 260 | button[Maximize].action = NULL; 261 | if (initpixmap(getresource("button.close.pixmap", CLOSEPIXMAP), 262 | &button[Close])) 263 | button[Close].action = NULL; 264 | } 265 | 266 | static void 267 | initfont(const char *fontstr) { 268 | style.font = NULL; 269 | style.font = XftFontOpenXlfd(dpy, screen, fontstr); 270 | if (!style.font) 271 | style.font = XftFontOpenName(dpy, screen, fontstr); 272 | if (!style.font) 273 | eprint("error, cannot load font: '%s'\n", fontstr); 274 | dc.font.extents = emallocz(sizeof(XGlyphInfo)); 275 | XftTextExtentsUtf8(dpy, style.font, 276 | (const unsigned char *) fontstr, strlen(fontstr), dc.font.extents); 277 | dc.font.height = style.font->ascent + style.font->descent + 1; 278 | dc.font.ascent = style.font->ascent; 279 | dc.font.descent = style.font->descent; 280 | } 281 | 282 | void 283 | initstyle() { 284 | style.color.norm[ColBorder] = getcolor(getresource("normal.border", NORMBORDERCOLOR)); 285 | style.color.norm[ColBG] = getcolor(getresource("normal.bg", NORMBGCOLOR)); 286 | style.color.norm[ColFG] = getcolor(getresource("normal.fg", NORMFGCOLOR)); 287 | style.color.norm[ColButton] = getcolor(getresource("normal.button", NORMBUTTONCOLOR)); 288 | 289 | style.color.sel[ColBorder] = getcolor(getresource("selected.border", SELBORDERCOLOR)); 290 | style.color.sel[ColBG] = getcolor(getresource("selected.bg", SELBGCOLOR)); 291 | style.color.sel[ColFG] = getcolor(getresource("selected.fg", SELFGCOLOR)); 292 | style.color.sel[ColButton] = getcolor(getresource("selected.button", SELBUTTONCOLOR)); 293 | 294 | style.color.font[Selected] = emallocz(sizeof(XftColor)); 295 | style.color.font[Normal] = emallocz(sizeof(XftColor)); 296 | XftColorAllocName(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, 297 | screen), getresource("selected.fg", SELFGCOLOR), style.color.font[Selected]); 298 | XftColorAllocName(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, 299 | screen), getresource("normal.fg", NORMFGCOLOR), style.color.font[Normal]); 300 | if (!style.color.font[Normal] || !style.color.font[Normal]) 301 | eprint("error, cannot allocate colors\n"); 302 | initfont(getresource("font", FONT)); 303 | style.border = atoi(getresource("border", STR(BORDERPX))); 304 | style.opacity = OPAQUE * atof(getresource("opacity", STR(NF_OPACITY))); 305 | style.outline = atoi(getresource("outline", "0")); 306 | strncpy(style.titlelayout, getresource("titlelayout", "N IMC"), 307 | LENGTH(style.titlelayout)); 308 | style.titlelayout[LENGTH(style.titlelayout) - 1] = '\0'; 309 | style.titleheight = atoi(getresource("title", STR(TITLEHEIGHT))); 310 | if (!style.titleheight) 311 | style.titleheight = dc.font.height + 2; 312 | dc.gc = XCreateGC(dpy, root, 0, 0); 313 | initbuttons(); 314 | } 315 | 316 | void 317 | deinitstyle() { 318 | /* XXX: more to do */ 319 | XftColorFree(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, 320 | screen), style.color.font[Normal]); 321 | XftColorFree(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, 322 | screen), style.color.font[Selected]); 323 | XftFontClose(dpy, style.font); 324 | free(dc.font.extents); 325 | XFreeGC(dpy, dc.gc); 326 | } 327 | 328 | static unsigned int 329 | textnw(const char *text, unsigned int len) { 330 | XftTextExtentsUtf8(dpy, style.font, 331 | (const unsigned char *) text, len, dc.font.extents); 332 | return dc.font.extents->xOff; 333 | } 334 | 335 | static unsigned int 336 | textw(const char *text) { 337 | return textnw(text, strlen(text)) + dc.font.height; 338 | } 339 | -------------------------------------------------------------------------------- /echinus.1: -------------------------------------------------------------------------------- 1 | .Dd $Mdocdate$ 2 | .Dt ECHINUS 1 3 | .Os 4 | .Sh NAME 5 | .Nm echinus 6 | .Nd a lightweight window manager for X11 7 | .Sh SYNOPSIS 8 | .Nm echinus 9 | .Bk -words 10 | .Op Fl v 11 | .Op Fl f Ar conf 12 | .Ek 13 | .Sh DESCRIPTION 14 | .Nm 15 | is a lightweight window manager for X based on dwm's source code. 16 | It manages windows in maximized, floating and tiled layouts. 17 | Either layout can be applied dynamically, optimizing the environment for the 18 | application in use and the task performed. 19 | .Pp 20 | In maximized layout only one window can be seen at the moment, but you 21 | can switch between them with hotkeys. 22 | In floating layout windows can be resized and moved freely. 23 | Dialog windows are always managed floating, regardless of the layout applied. 24 | .Pp 25 | Windows are grouped by tags. Each window can be tagged with one or 26 | multiple tags. Selecting certain tags displays all windows with these tags. 27 | .Pp 28 | .Nm 29 | draws a small border around windows to indicate the focus state and an 30 | optional titlebar. 31 | .Pp 32 | The options are as follows: 33 | .Bl -tag -width "XXXXXXXXXXXX" 34 | .It Fl v 35 | Prints version information to standard output, then exits. 36 | .It Fl f Ar conf 37 | Specifies an alternative configuration file. 38 | By default, 39 | .Nm 40 | loads the configuration file from 41 | .Pa ~/.echinus/echinusrc, 42 | if present, then looks for the system-wide configuration file 43 | .Pa CONFDIR/echinusrc . 44 | .El 45 | .Pp 46 | The following notation is used in this page: 47 | .Pp 48 | .Bl -tag -width Ds -offset indent -compact 49 | .It Ic A 50 | Alt. 51 | .It Ic C 52 | Control. 53 | .It Ic S 54 | Shift. 55 | .It Ic W 56 | Windows key. 57 | .It Ic M1 58 | Left mouse button. 59 | .It Ic M2 60 | Middle mouse button. 61 | .It Ic M3 62 | Right mouse button. 63 | .El 64 | .Sh KEY BINDINGS 65 | The default key bindings are: 66 | .Pp 67 | .Bl -tag -width "XXXXXXXXXXXX" -offset indent -compact 68 | .It Ic A-[F1..Fn] 69 | Views all windows with n-th tag. 70 | .It Ic A-Return 71 | Zooms/cycles current window to/from master area (tiled layout only). 72 | .It Ic A-Space 73 | Toggles between tiled and floating layout (affects all windows). 74 | .It Ic A-Tab 75 | Toggles to the previously selected tags. 76 | .It Ic A-b 77 | Toggles on/off statusbar space. 78 | .It Ic A-f 79 | Enables floating mode. 80 | .It Ic A-h 81 | Decreases the master area width about 5% (tiled layout only). 82 | .It Ic A-i 83 | Enables floating mode. 84 | .It Ic A-j 85 | Focuses next window. 86 | .It Ic A-k 87 | Focuses previous window. 88 | .It Ic A-l 89 | Increases the master are width about 5% (tiled layout only). 90 | .It Ic A-m 91 | Enables maximized (monocle) mode. 92 | .It Ic A-r 93 | Enables tiled mode. 94 | .It Ic A-t 95 | Starts xterm. 96 | .It Ic A-w 97 | Enables bottom-tiled mode. 98 | .It Ic A-C-[1..n] 99 | Adds/removes all windows with n-th tag to/from the view. 100 | .It Ic A-C-q 101 | Quits 102 | .Nm . 103 | .It Ic A-C-S-[1..n] 104 | Adds/removes n-th tag to/from current window. 105 | .It Ic A-S-[1..n] 106 | Applies n-th tag to current window. 107 | .It Ic A-S-c 108 | Closes focused window. 109 | .El 110 | .Sh MOUSE BINDINGS 111 | The default mouse bindings are: 112 | .Pp 113 | .Bl -tag -width "XXXXXXXX" -offset indent -compact 114 | .It Ic M1 115 | When pressed on the titlebar, focuses window and raises it on top of the stack. 116 | .It Ic M2 117 | When pressed on the titlebar, resizes window. 118 | .It Ic M3 119 | When pressed on the root window, draws a rectangle to start a terminal in it. 120 | .It Ic A-M1 121 | Moves current window while dragging. 122 | Tiled windows will be toggled to the floating state. 123 | .It Ic A-M2 124 | Zooms/cycles current window to/from master area. 125 | If it is floating (but not fixed) it will be toggled to the tiled state, 126 | instead. 127 | .It Ic A-M3 128 | Resizes current window while dragging. 129 | Tiled windows will be toggled to the floating state. 130 | .El 131 | .Sh CUSTOMIZATION 132 | The configuration file is a set of X resources (with a class of Echinus), 133 | which are described in the sections below. 134 | .Pp 135 | Every relative path specified is relative to the directory in which the 136 | configuration file is in. 137 | .Sh MAIN SETTINGS 138 | .Bl -tag -width Ds 139 | .It Ic attachaside 140 | Set new windows as slave or master. 141 | .Bl -column "Character" "Meaning" -offset indent 142 | .It Sy "Value" Ta Sy "Meaning" 143 | .It Li "0" Ta "New windows as master" 144 | .It Li "1" Ta "New masters as slave" 145 | .El 146 | .It Ic deflayout 147 | Layout applied to tag if not explicitly set by tags.layout later. 148 | .Bl -column "Character" "Meaning" -offset indent 149 | .It Sy "Value" Ta Sy "Meaning" 150 | .It Li "b" Ta "Bottom Stack" 151 | .It Li "f" Ta "Floating" 152 | .It Li "i" Ta "Ifloating" 153 | .It Li "m" Ta "Maximized" 154 | .It Li "t" Ta "Tiled" 155 | .El 156 | .It Ic gap 157 | Gap size between tiling windows in pixels. 158 | .It Ic modkey 159 | Choose modkey. 160 | .It Ic mwfact 161 | Space occupied by master window in tiled layout (fraction of 1). 162 | .It Ic nmaster 163 | Number of clients in master area in tiled layout. 164 | .It Ic sloppy 165 | Sets the type of sloppy focus to use. 166 | .Bl -column "Number" "Meaning" -offset indent 167 | .It Sy "Value" Ta Sy "Meaning" 168 | .It Li "0" Ta "Click to focus" 169 | .It Li "1" Ta "Sloppy focus for floating clients" 170 | .It Li "2" Ta "Sloppy focus for everything" 171 | .It Li "3" Ta "Sloppy focus and raise on focus" 172 | .El 173 | .El 174 | .Sh BORDER SETTINGS 175 | .Bl -tag -width Ds 176 | .It Ic border 177 | Width of the border (in pixels). 178 | .El 179 | .Sh BUTTON SETTINGS 180 | .Bl -tag -width Ds 181 | .It Ic button.close.pixmap 182 | .It Ic button.iconify.pixmap 183 | .It Ic button.maximize.pixmap 184 | Sets the close, iconify and maximize buttons in the titlebar. 185 | .El 186 | .Sh COLOR SETTINGS 187 | .Bl -tag -width Ds 188 | .It Ic normal.bg 189 | Sets the titlebar background for unfocused windows. 190 | .It Ic normal.border 191 | Sets the color of a small border around each unfocused window. 192 | .It Ic normal.button 193 | Sets the button foreground for unfocused windows. 194 | .It Ic normal.fg 195 | Sets the titlebar foreground for unfocused windows. 196 | .It Ic selected.bg 197 | .It Ic selected.border 198 | .It Ic selected.button 199 | .It Ic selected.fg 200 | Sets color options for focused windows. 201 | .El 202 | .Sh HACKS SETTINGS 203 | .Bl -tag -width Ds 204 | .It Ic hidebastards 205 | Hide panels, pagers and others with togglestruts function. 206 | .El 207 | .Sh KEYBINDINS SETTINGS 208 | .Bl -tag -width Ds 209 | .It Ic ACSW + key [ = options ] 210 | Binds key to the specified command (see 211 | .Sx COMMANDS ) 212 | .Ns . Spaces are mandatory. 213 | .Pp 214 | Key is taken from the X11 files (usually located under an include directory) 215 | .Pa keysym.h 216 | and, 217 | .Pa XF86keysym.h 218 | and removing the XK_ prefix before use. 219 | .Pp 220 | Commands options are specified after an equal sign. 221 | .El 222 | .Sh MISC SETTINGS 223 | .Bl -tag -width Ds 224 | .It Ic command 225 | Application to run on right click on root window. 226 | .It Ic opacity 227 | Opacity value for inactive windows (xcompmgr needed). 228 | .El 229 | .Sh TAGS SETTINGS 230 | .Bl -tag -width Ds 231 | .It Ic tags.layout{...} 232 | Layout per tag on start (see 233 | .Ic deflayout ) 234 | .Ns . 235 | .It Ic tags.name{...} 236 | Names of the tags. 237 | .It Ic tags.number 238 | Number of tags. 239 | .El 240 | .Sh TITLE SETTINGS 241 | .Bl -tag -width Ds 242 | .It Ic decoratetiled 243 | If one, draw titles in tiled mode. 244 | .It Ic font 245 | Titlebar font. 246 | .It Ic title 247 | Titlebar height. 248 | .It Ic titlelayout 249 | Titlebar consists of 3 parts separated with dashes or spaces. 250 | Left is aligned to left, center to center and right to right (obviously). 251 | Default value is "N IMC" meaning name on the left iconify, maximize and 252 | close buttons on right (note 2 spaces). 253 | .Bl -column "Character" "Meaning" -offset indent 254 | .It Sy "Value" Ta Sy "Meaning" 255 | .It Li "dash" Ta "skip space (space can't be used as first character)" 256 | .It Li "space" Ta "Same as the above" 257 | .It Li "C" Ta "Close" 258 | .It Li "I" Ta "Iconify" 259 | .It Li "M" Ta "Maximize" 260 | .It Li "N" Ta "Window name" 261 | .It Li "T" Ta "Tagbar" 262 | .El 263 | .El 264 | .Sh COMMANDS 265 | .Bl -tag -width Ds 266 | .It Ic focusnext 267 | .It Ic focusprev 268 | Focuses next or previous window. 269 | .It Ic focusview# 270 | Toggles view number # and focuses the first client in it. 271 | .It Ic killclient 272 | Closes current focused window. 273 | .It Ic movedown 274 | .It Ic moveleft 275 | .It Ic moveright 276 | .It Ic moveup Ar x y w h 277 | Moves the window by the specified number of pixels in the specified direction. 278 | .It Ic restart 279 | Restarts 280 | .Nm . 281 | .It Ic quit 282 | Exits 283 | .Nm . 284 | .It Ic resizedecx 285 | .It Ic resizedecy 286 | .It Ic resizeincx 287 | .It Ic rezizeincy Ar x y w h 288 | Resizes the window by the specified number of pixels in the specified 289 | direction. 290 | .It Ic rule# Ar class.title tag isfloating hastitle 291 | Sets a rule for the specified window class or title. 292 | NULL indicates that no tag is needed. 293 | .It Ic spawn# 294 | Runs specified program (upto 64). 295 | .It Ic tag# 296 | Tags current window with tag number #. 297 | .It Ic togglefloating 298 | Toggles floating or tiled mode. 299 | .It Ic togglemonitor 300 | Switches from one monitor to another. 301 | .It Ic togglestruts 302 | .Nm 303 | has no bar, but this command switches the area on top or bottom which won't 304 | be covered by windows in tiled on monocle mode. 305 | .It Ic toggleview# 306 | Toggles view number #. 307 | .It Ic view# 308 | Views tag number #. 309 | .It Ic viewlefttag 310 | .It Ic viewrighttag 311 | Views tag on left or right. 312 | .It Ic viewprevtag 313 | View previous tag set. 314 | .El 315 | .Sh EXAMPLES 316 | To move a window five pixels to the right: 317 | .Pp 318 | .Dl Echinus*moveright: AS + l = +5 0 0 0 319 | .Pp 320 | To resize a window five pixels down: 321 | .Pp 322 | .Dl Echinus*resizedecy: AS + v = 0 0 0 -5 323 | .Pp 324 | To make mplayer float and with a title by default: 325 | .Dl Echinus*rule0: Mplayer.* NULL 1 1 326 | .Pp 327 | To spawn an xterm when A + T is pressed: 328 | .Dl Echinus*spawn0: A + t = xterm 329 | .Pp 330 | To show the title centered and the close button on the right: 331 | .Dl Echinus*titlelayout: -T-C 332 | .Sh CONFORMING TO 333 | .Nm 334 | is partially EWMH (NetWM) compliant, so you can use your favourite panel or 335 | pager. 336 | .Sh SEE ALSO 337 | .Xr dmenu 1 338 | .Xr dwm 1 339 | .Sh BUGS 340 | parsekey() doesn't handle non-alphanumeric keysyms. 341 | .Pp 342 | You should find lots of them. If you did please contact me, my mail is 343 | . 344 | -------------------------------------------------------------------------------- /echinus.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. 2 | * 3 | * echinus window manager is designed like any other X client as well. It is 4 | * driven through handling X events. In contrast to other X clients, a window 5 | * manager selects for SubstructureRedirectMask on the root window, to receive 6 | * events about window (dis-)appearance. Only one X connection at a time is 7 | * allowed to select for this event mask. 8 | * 9 | * The event handlers of echinus are organized in an 10 | * array which is accessed whenever a new event has been fetched. This allows 11 | * event dispatching in O(1) time. 12 | * 13 | * Each child of the root window is called a client, except windows which have 14 | * set the override_redirect flag. Clients are organized in a global 15 | * doubly-linked client list, the focus history is remembered through a global 16 | * stack list. Each client contains an array of Bools of the same size as the 17 | * global tags array to indicate the tags of a client. 18 | * 19 | * Keys and tagging rules are organized as arrays and defined in config.h. 20 | * 21 | * To understand everything else, start reading main(). 22 | */ 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #ifdef XRANDR 50 | #include 51 | #include 52 | #endif 53 | #include "echinus.h" 54 | 55 | /* macros */ 56 | #define BUTTONMASK (ButtonPressMask | ButtonReleaseMask) 57 | #define CLEANMASK(mask) (mask & ~(numlockmask | LockMask)) 58 | #define MOUSEMASK (BUTTONMASK | PointerMotionMask) 59 | #define CLIENTMASK (PropertyChangeMask | StructureNotifyMask | FocusChangeMask) 60 | #define CLIENTNOPROPAGATEMASK (BUTTONMASK | ButtonMotionMask) 61 | #define FRAMEMASK (MOUSEMASK | SubstructureRedirectMask | SubstructureNotifyMask | EnterWindowMask | LeaveWindowMask) 62 | 63 | 64 | /* enums */ 65 | enum { StrutsOn, StrutsOff, StrutsHide }; /* struts position */ 66 | enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ 67 | enum { Clk2Focus, SloppyFloat, AllSloppy, SloppyRaise }; /* focus model */ 68 | 69 | /* function declarations */ 70 | void applyatoms(Client * c); 71 | void applyrules(Client * c); 72 | void arrange(Monitor * m); 73 | void attach(Client * c, Bool attachaside); 74 | void attachstack(Client * c); 75 | void ban(Client * c); 76 | void buttonpress(XEvent * e); 77 | void bstack(Monitor * m); 78 | void checkotherwm(void); 79 | void cleanup(void); 80 | void compileregs(void); 81 | void configure(Client * c); 82 | void configurenotify(XEvent * e); 83 | void configurerequest(XEvent * e); 84 | void destroynotify(XEvent * e); 85 | void detach(Client * c); 86 | void detachstack(Client * c); 87 | void *emallocz(unsigned int size); 88 | void enternotify(XEvent * e); 89 | void eprint(const char *errstr, ...); 90 | void expose(XEvent * e); 91 | void iconify(const char *arg); 92 | void incnmaster(const char *arg); 93 | void focus(Client * c); 94 | void focusnext(const char *arg); 95 | void focusprev(const char *arg); 96 | Client *getclient(Window w, Client * list, int part); 97 | const char *getresource(const char *resource, const char *defval); 98 | long getstate(Window w); 99 | Bool gettextprop(Window w, Atom atom, char *text, unsigned int size); 100 | void getpointer(int *x, int *y); 101 | Monitor *getmonitor(int x, int y); 102 | Monitor *curmonitor(); 103 | Monitor *clientmonitor(Client * c); 104 | int idxoftag(const char *tag); 105 | Bool isvisible(Client * c, Monitor * m); 106 | void initmonitors(XEvent * e); 107 | void keypress(XEvent * e); 108 | void killclient(const char *arg); 109 | void leavenotify(XEvent * e); 110 | void focusin(XEvent * e); 111 | void manage(Window w, XWindowAttributes * wa); 112 | void mappingnotify(XEvent * e); 113 | void monocle(Monitor * m); 114 | void maprequest(XEvent * e); 115 | void mousemove(Client * c); 116 | void mouseresize(Client * c); 117 | void moveresizekb(const char *arg); 118 | Client *nexttiled(Client * c, Monitor * m); 119 | Client *prevtiled(Client * c, Monitor * m); 120 | void place(Client *c); 121 | void propertynotify(XEvent * e); 122 | void reparentnotify(XEvent * e); 123 | void quit(const char *arg); 124 | void restart(const char *arg); 125 | void resize(Client * c, int x, int y, int w, int h, Bool sizehints); 126 | void restack(Monitor * m); 127 | void run(void); 128 | void save(Client * c); 129 | void scan(void); 130 | void setclientstate(Client * c, long state); 131 | void setlayout(const char *arg); 132 | void setmwfact(const char *arg); 133 | void setup(char *); 134 | void spawn(const char *arg); 135 | void tag(const char *arg); 136 | void tile(Monitor * m); 137 | void togglestruts(const char *arg); 138 | void togglefloating(const char *arg); 139 | void togglemax(const char *arg); 140 | void togglefill(const char *arg); 141 | void toggletag(const char *arg); 142 | void toggleview(const char *arg); 143 | void togglemonitor(const char *arg); 144 | void focusview(const char *arg); 145 | void unban(Client * c); 146 | void unmanage(Client * c); 147 | void updategeom(Monitor * m); 148 | void updatestruts(Monitor * m); 149 | void unmapnotify(XEvent * e); 150 | void updatesizehints(Client * c); 151 | void updateframe(Client * c); 152 | void updatetitle(Client * c); 153 | void view(const char *arg); 154 | void viewprevtag(const char *arg); /* views previous selected tags */ 155 | void viewlefttag(const char *arg); 156 | void viewrighttag(const char *arg); 157 | int xerror(Display * dpy, XErrorEvent * ee); 158 | int xerrordummy(Display * dsply, XErrorEvent * ee); 159 | int xerrorstart(Display * dsply, XErrorEvent * ee); 160 | int (*xerrorxlib) (Display *, XErrorEvent *); 161 | void zoom(const char *arg); 162 | 163 | /* variables */ 164 | char **cargv; 165 | Display *dpy; 166 | int screen; 167 | Window root; 168 | XrmDatabase xrdb; 169 | Bool otherwm; 170 | Bool running = True; 171 | Bool selscreen = True; 172 | Monitor *monitors; 173 | Client *clients; 174 | Client *sel; 175 | Client *stack; 176 | Cursor cursor[CurLast]; 177 | Style style; 178 | Button button[LastBtn]; 179 | View *views; 180 | Key **keys; 181 | Rule **rules; 182 | char **tags; 183 | unsigned int ntags; 184 | unsigned int nkeys; 185 | unsigned int nrules; 186 | unsigned int modkey; 187 | unsigned int numlockmask; 188 | /* configuration, allows nested code to access above variables */ 189 | #include "config.h" 190 | 191 | struct { 192 | Bool attachaside; 193 | Bool dectiled; 194 | Bool hidebastards; 195 | int focus; 196 | int gap; 197 | int snap; 198 | char command[255]; 199 | } options; 200 | 201 | Layout layouts[] = { 202 | /* function symbol features */ 203 | { NULL, 'i', OVERLAP }, 204 | { tile, 't', MWFACT | NMASTER | ZOOM }, 205 | { bstack, 'b', MWFACT | ZOOM }, 206 | { monocle, 'm', 0 }, 207 | { NULL, 'f', OVERLAP }, 208 | { NULL, '\0', 0 }, 209 | }; 210 | 211 | void (*handler[LASTEvent]) (XEvent *) = { 212 | [ButtonPress] = buttonpress, 213 | [ButtonRelease] = buttonpress, 214 | [ConfigureRequest] = configurerequest, 215 | [ConfigureNotify] = configurenotify, 216 | [DestroyNotify] = destroynotify, 217 | [EnterNotify] = enternotify, 218 | [LeaveNotify] = leavenotify, 219 | [FocusIn] = focusin, 220 | [Expose] = expose, 221 | [KeyPress] = keypress, 222 | [MappingNotify] = mappingnotify, 223 | [MapRequest] = maprequest, 224 | [PropertyNotify] = propertynotify, 225 | [ReparentNotify] = reparentnotify, 226 | [UnmapNotify] = unmapnotify, 227 | [ClientMessage] = clientmessage, 228 | #ifdef XRANDR 229 | [RRScreenChangeNotify] = initmonitors, 230 | #endif 231 | }; 232 | 233 | /* function implementations */ 234 | void 235 | applyatoms(Client * c) { 236 | unsigned int *t; 237 | unsigned long n; 238 | int i; 239 | 240 | /* restore tag number from atom */ 241 | t = (unsigned int*)getatom(c->win, atom[WindowDesk], &n); 242 | if (n != 0) { 243 | if (*t >= ntags) 244 | return; 245 | for (i = 0; i < ntags; i++) 246 | c->tags[i] = (i == *t) ? 1 : 0; 247 | } 248 | } 249 | 250 | void 251 | applyrules(Client * c) { 252 | static char buf[512]; 253 | unsigned int i, j; 254 | regmatch_t tmp; 255 | Bool matched = False; 256 | XClassHint ch = { 0 }; 257 | 258 | /* rule matching */ 259 | XGetClassHint(dpy, c->win, &ch); 260 | snprintf(buf, sizeof(buf), "%s:%s:%s", 261 | ch.res_class ? ch.res_class : "", ch.res_name ? ch.res_name : "", c->name); 262 | buf[LENGTH(buf)-1] = 0; 263 | for (i = 0; i < nrules; i++) 264 | if (rules[i]->propregex && !regexec(rules[i]->propregex, buf, 1, &tmp, 0)) { 265 | c->isfloating = rules[i]->isfloating; 266 | c->title = rules[i]->hastitle; 267 | for (j = 0; rules[i]->tagregex && j < ntags; j++) { 268 | if (!regexec(rules[i]->tagregex, tags[j], 1, &tmp, 0)) { 269 | matched = True; 270 | c->tags[j] = True; 271 | } 272 | } 273 | } 274 | if (ch.res_class) 275 | XFree(ch.res_class); 276 | if (ch.res_name) 277 | XFree(ch.res_name); 278 | if (!matched) 279 | memcpy(c->tags, curseltags, ntags * sizeof(curseltags[0])); 280 | } 281 | 282 | void 283 | arrangefloats(Monitor * m) { 284 | Client *c; 285 | Monitor *om; 286 | int dx, dy; 287 | 288 | for (c = stack; c; c = c->snext) { 289 | if (isvisible(c, m) && !c->isbastard && 290 | (c->isfloating || MFEATURES(m, OVERLAP)) 291 | && !c->ismax && !c->isicon) { 292 | DPRINTF("%d %d\n", c->rx, c->ry); 293 | if (!(om = getmonitor(c->rx + c->rw/2, 294 | c->ry + c->rh/2))) 295 | continue; 296 | dx = om->sx + om->sw - c->rx; 297 | dy = om->sy + om->sh - c->ry; 298 | if (dx > m->sw) 299 | dx = m->sw; 300 | if (dy > m->sh) 301 | dy = m->sh; 302 | resize(c, m->sx + m->sw - dx, m->sy + m->sh - dy, c->rw, c->rh, True); 303 | save(c); 304 | } 305 | } 306 | } 307 | 308 | void 309 | arrangemon(Monitor * m) { 310 | Client *c; 311 | 312 | if (views[m->curtag].layout->arrange) 313 | views[m->curtag].layout->arrange(m); 314 | arrangefloats(m); 315 | restack(m); 316 | for (c = stack; c; c = c->snext) { 317 | if ((clientmonitor(c) == m) && ((!c->isbastard && !c->isicon) || 318 | (c->isbastard && views[m->curtag].barpos == StrutsOn))) { 319 | unban(c); 320 | } 321 | } 322 | 323 | for (c = stack; c; c = c->snext) { 324 | if ((clientmonitor(c) == NULL) || (!c->isbastard && c->isicon) || 325 | (c->isbastard && views[m->curtag].barpos == StrutsHide)) { 326 | ban(c); 327 | } 328 | } 329 | } 330 | 331 | void 332 | arrange(Monitor * m) { 333 | Monitor *i; 334 | 335 | if (!m) { 336 | for (i = monitors; i; i = i->next) 337 | arrangemon(i); 338 | } else 339 | arrangemon(m); 340 | } 341 | 342 | void 343 | attach(Client * c, Bool attachaside) { 344 | if (attachaside) { 345 | if (clients) { 346 | Client * lastClient = clients; 347 | while (lastClient->next) 348 | lastClient = lastClient->next; 349 | c->prev = lastClient; 350 | lastClient->next = c; 351 | } 352 | else 353 | clients = c; 354 | } 355 | else { 356 | if (clients) 357 | clients->prev = c; 358 | c->next = clients; 359 | clients = c; 360 | } 361 | } 362 | 363 | void 364 | attachstack(Client * c) { 365 | c->snext = stack; 366 | stack = c; 367 | } 368 | 369 | void 370 | ban(Client * c) { 371 | if (c->isbanned) 372 | return; 373 | c->ignoreunmap++; 374 | setclientstate(c, IconicState); 375 | XSelectInput(dpy, c->win, CLIENTMASK & ~(StructureNotifyMask | EnterWindowMask)); 376 | XSelectInput(dpy, c->frame, NoEventMask); 377 | XUnmapWindow(dpy, c->frame); 378 | XUnmapWindow(dpy, c->win); 379 | XSelectInput(dpy, c->win, CLIENTMASK); 380 | XSelectInput(dpy, c->frame, FRAMEMASK); 381 | c->isbanned = True; 382 | } 383 | 384 | void 385 | buttonpress(XEvent * e) { 386 | Client *c; 387 | int i; 388 | XButtonPressedEvent *ev = &e->xbutton; 389 | 390 | if (!curmonitor()) 391 | return; 392 | if (ev->window == root) { 393 | if (ev->type != ButtonRelease) 394 | return; 395 | switch (ev->button) { 396 | case Button3: 397 | spawn(options.command); 398 | break; 399 | case Button4: 400 | viewlefttag(NULL); 401 | break; 402 | case Button5: 403 | viewrighttag(NULL); 404 | break; 405 | } 406 | return; 407 | } 408 | if ((c = getclient(ev->window, clients, ClientTitle))) { 409 | DPRINTF("TITLE %s: 0x%x\n", c->name, (int) ev->window); 410 | focus(c); 411 | for (i = 0; i < LastBtn; i++) { 412 | if (button[i].action == NULL) 413 | continue; 414 | if ((ev->x > button[i].x) 415 | && ((int)ev->x < (int)(button[i].x + style.titleheight)) 416 | && (button[i].x != -1) && (int)ev->y < style.titleheight) { 417 | if (ev->type == ButtonPress) { 418 | DPRINTF("BUTTON %d PRESSED\n", i); 419 | button[i].pressed = 1; 420 | } else { 421 | DPRINTF("BUTTON %d RELEASED\n", i); 422 | button[i].pressed = 0; 423 | button[i].action(NULL); 424 | } 425 | drawclient(c); 426 | return; 427 | } 428 | } 429 | for (i = 0; i < LastBtn; i++) 430 | button[i].pressed = 0; 431 | drawclient(c); 432 | if (ev->type == ButtonRelease) 433 | return; 434 | restack(curmonitor()); 435 | if (ev->button == Button1) 436 | mousemove(c); 437 | else if (ev->button == Button3) 438 | mouseresize(c); 439 | } else if ((c = getclient(ev->window, clients, ClientWindow))) { 440 | DPRINTF("WINDOW %s: 0x%x\n", c->name, (int) ev->window); 441 | focus(c); 442 | restack(curmonitor()); 443 | if (CLEANMASK(ev->state) != modkey) { 444 | XAllowEvents(dpy, ReplayPointer, CurrentTime); 445 | return; 446 | } 447 | if (ev->button == Button1) { 448 | if (!FEATURES(curlayout, OVERLAP) && !c->isfloating) 449 | togglefloating(NULL); 450 | if (c->ismax) 451 | togglemax(NULL); 452 | mousemove(c); 453 | } else if (ev->button == Button2) { 454 | if (!FEATURES(curlayout, OVERLAP) && c->isfloating) 455 | togglefloating(NULL); 456 | else 457 | zoom(NULL); 458 | } else if (ev->button == Button3) { 459 | if (!FEATURES(curlayout, OVERLAP) && !c->isfloating) 460 | togglefloating(NULL); 461 | if (c->ismax) 462 | togglemax(NULL); 463 | mouseresize(c); 464 | } 465 | } else if ((c = getclient(ev->window, clients, ClientFrame))) { 466 | DPRINTF("FRAME %s: 0x%x\n", c->name, (int) ev->window); 467 | /* Not supposed to happen */ 468 | } 469 | } 470 | 471 | void 472 | checkotherwm(void) { 473 | otherwm = False; 474 | XSetErrorHandler(xerrorstart); 475 | 476 | /* this causes an error if some other window manager is running */ 477 | XSelectInput(dpy, root, SubstructureRedirectMask); 478 | XSync(dpy, False); 479 | if (otherwm) 480 | eprint("echinus: another window manager is already running\n"); 481 | XSync(dpy, False); 482 | XSetErrorHandler(NULL); 483 | xerrorxlib = XSetErrorHandler(xerror); 484 | XSync(dpy, False); 485 | } 486 | 487 | void 488 | cleanup(void) { 489 | while (stack) { 490 | unban(stack); 491 | unmanage(stack); 492 | } 493 | free(tags); 494 | free(keys); 495 | initmonitors(NULL); 496 | /* free resource database */ 497 | XrmDestroyDatabase(xrdb); 498 | deinitstyle(); 499 | XUngrabKey(dpy, AnyKey, AnyModifier, root); 500 | XFreeCursor(dpy, cursor[CurNormal]); 501 | XFreeCursor(dpy, cursor[CurResize]); 502 | XFreeCursor(dpy, cursor[CurMove]); 503 | XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); 504 | XSync(dpy, False); 505 | } 506 | 507 | void 508 | configure(Client * c) { 509 | XConfigureEvent ce; 510 | 511 | ce.type = ConfigureNotify; 512 | ce.display = dpy; 513 | ce.event = c->win; 514 | ce.window = c->win; 515 | ce.x = c->x; 516 | ce.y = c->y; 517 | ce.width = c->w; 518 | ce.height = c->h - c->th; 519 | ce.border_width = 0; 520 | ce.above = None; 521 | ce.override_redirect = False; 522 | XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *) & ce); 523 | } 524 | 525 | void 526 | configurenotify(XEvent * e) { 527 | XConfigureEvent *ev = &e->xconfigure; 528 | Monitor *m; 529 | Client *c; 530 | 531 | if (ev->window == root) { 532 | #ifdef XRANDR 533 | if (XRRUpdateConfiguration((XEvent *) ev)) { 534 | #endif 535 | initmonitors(e); 536 | for (c = clients; c; c = c->next) { 537 | if (c->isbastard) { 538 | m = getmonitor(c->x + c->w/2, c->y); 539 | c->tags = m->seltags; 540 | updatestruts(m); 541 | } 542 | } 543 | for (m = monitors; m; m = m->next) 544 | updategeom(m); 545 | arrange(NULL); 546 | #ifdef XRANDR 547 | } 548 | #endif 549 | } 550 | } 551 | 552 | void 553 | configurerequest(XEvent * e) { 554 | Client *c; 555 | XConfigureRequestEvent *ev = &e->xconfigurerequest; 556 | XWindowChanges wc; 557 | Monitor *cm; 558 | int x, y, w, h; 559 | 560 | if ((c = getclient(ev->window, clients, ClientWindow))) { 561 | cm = clientmonitor(c); 562 | if (ev->value_mask & CWBorderWidth) 563 | c->border = ev->border_width; 564 | if (c->isfixed || c->isfloating || MFEATURES(clientmonitor(c), OVERLAP)) { 565 | if (ev->value_mask & CWX) 566 | x = ev->x; 567 | if (ev->value_mask & CWY) 568 | y = ev->y; 569 | if (ev->value_mask & CWWidth) 570 | w = ev->width; 571 | if (ev->value_mask & CWHeight) 572 | h = ev->height + c->th; 573 | cm = getmonitor(x, y); 574 | if (!(ev->value_mask & (CWX | CWY)) /* resize request */ 575 | && (ev->value_mask & (CWWidth | CWHeight))) { 576 | DPRINTF("RESIZE %s (%d,%d)->(%d,%d)\n", c->name, c->w, c->h, w, h); 577 | resize(c, c->x, c->y, w, h, True); 578 | save(c); 579 | } else if ((ev->value_mask & (CWX | CWY)) /* move request */ 580 | && !(ev->value_mask & (CWWidth | CWHeight))) { 581 | DPRINTF("MOVE %s (%d,%d)->(%d,%d)\n", c->name, c->x, c->y, x, y); 582 | resize(c, x, y, c->w, c->h, True); 583 | save(c); 584 | } else if ((ev->value_mask & (CWX | CWY)) /* move and resize request */ 585 | && (ev->value_mask & (CWWidth | CWHeight))) { 586 | DPRINTF("MOVE&RESIZE(MOVE) %s (%d,%d)->(%d,%d)\n", c->name, c->x, c->y, ev->x, ev->y); 587 | DPRINTF("MOVE&RESIZE(RESIZE) %s (%d,%d)->(%d,%d)\n", c->name, c->w, c->h, ev->width, ev->height); 588 | resize(c, x, y, w, h, True); 589 | save(c); 590 | } else if ((ev->value_mask & CWStackMode)) { 591 | DPRINTF("RESTACK %s ignoring\n", c->name); 592 | configure(c); 593 | } 594 | } else { 595 | configure(c); 596 | } 597 | } else { 598 | wc.x = ev->x; 599 | wc.y = ev->y; 600 | wc.width = ev->width; 601 | wc.height = ev->height; 602 | wc.border_width = ev->border_width; 603 | wc.sibling = ev->above; 604 | wc.stack_mode = ev->detail; 605 | XConfigureWindow(dpy, ev->window, ev->value_mask, &wc); 606 | } 607 | XSync(dpy, False); 608 | } 609 | 610 | void 611 | destroynotify(XEvent * e) { 612 | Client *c; 613 | XDestroyWindowEvent *ev = &e->xdestroywindow; 614 | 615 | if (!(c = getclient(ev->window, clients, ClientWindow))) 616 | return; 617 | unmanage(c); 618 | updateatom[ClientList] (NULL); 619 | } 620 | 621 | void 622 | detach(Client * c) { 623 | if (c->prev) 624 | c->prev->next = c->next; 625 | if (c->next) 626 | c->next->prev = c->prev; 627 | if (c == clients) 628 | clients = c->next; 629 | c->next = c->prev = NULL; 630 | } 631 | 632 | void 633 | detachstack(Client * c) { 634 | Client **tc; 635 | 636 | for (tc = &stack; *tc && *tc != c; tc = &(*tc)->snext); 637 | *tc = c->snext; 638 | } 639 | 640 | void * 641 | emallocz(unsigned int size) { 642 | void *res = calloc(1, size); 643 | 644 | if (!res) 645 | eprint("fatal: could not malloc() %u bytes\n", size); 646 | return res; 647 | } 648 | 649 | void 650 | enternotify(XEvent * e) { 651 | XCrossingEvent *ev = &e->xcrossing; 652 | Client *c; 653 | 654 | if (ev->mode != NotifyNormal || ev->detail == NotifyInferior) 655 | return; 656 | if (!curmonitor()) 657 | return; 658 | if ((c = getclient(ev->window, clients, ClientFrame))) { 659 | if (c->isbastard) 660 | return; 661 | /* focus when switching monitors */ 662 | if (!isvisible(sel, curmonitor())) 663 | focus(c); 664 | switch (options.focus) { 665 | case Clk2Focus: 666 | break; 667 | case SloppyFloat: 668 | if (FEATURES(curlayout, OVERLAP) || c->isfloating) 669 | focus(c); 670 | break; 671 | case AllSloppy: 672 | focus(c); 673 | break; 674 | case SloppyRaise: 675 | focus(c); 676 | restack(curmonitor()); 677 | break; 678 | } 679 | } else if (ev->window == root) { 680 | selscreen = True; 681 | focus(NULL); 682 | } 683 | } 684 | 685 | void 686 | eprint(const char *errstr, ...) { 687 | va_list ap; 688 | 689 | va_start(ap, errstr); 690 | vfprintf(stderr, errstr, ap); 691 | va_end(ap); 692 | exit(EXIT_FAILURE); 693 | } 694 | 695 | void 696 | focusin(XEvent * e) { 697 | XFocusChangeEvent *ev = &e->xfocus; 698 | Client *c; 699 | 700 | if (sel && ((c = getclient(ev->window, clients, ClientWindow)) != sel)) 701 | XSetInputFocus(dpy, sel->win, RevertToPointerRoot, CurrentTime); 702 | else if (!c) 703 | fprintf(stderr, "Caught FOCUSIN for unknown window 0x%x\n", ev->window); 704 | } 705 | 706 | void 707 | expose(XEvent * e) { 708 | XExposeEvent *ev = &e->xexpose; 709 | XEvent tmp; 710 | Client *c; 711 | 712 | while (XCheckWindowEvent(dpy, ev->window, ExposureMask, &tmp)); 713 | if ((c = getclient(ev->window, clients, ClientTitle))) 714 | drawclient(c); 715 | } 716 | 717 | void 718 | givefocus(Client * c) { 719 | XEvent ce; 720 | if (checkatom(c->win, atom[WMProto], atom[WMTakeFocus])) { 721 | ce.xclient.type = ClientMessage; 722 | ce.xclient.message_type = atom[WMProto]; 723 | ce.xclient.display = dpy; 724 | ce.xclient.window = c->win; 725 | ce.xclient.format = 32; 726 | ce.xclient.data.l[0] = atom[WMTakeFocus]; 727 | ce.xclient.data.l[1] = CurrentTime; /* incorrect */ 728 | ce.xclient.data.l[2] = 0l; 729 | ce.xclient.data.l[3] = 0l; 730 | ce.xclient.data.l[4] = 0l; 731 | XSendEvent(dpy, c->win, False, NoEventMask, &ce); 732 | } 733 | } 734 | 735 | void 736 | focus(Client * c) { 737 | Client *o; 738 | 739 | o = sel; 740 | if ((!c && selscreen) || (c && (c->isbastard || !isvisible(c, curmonitor())))) 741 | for (c = stack; 742 | c && (c->isbastard || c->isicon || !isvisible(c, curmonitor())); c = c->snext); 743 | if (sel && sel != c) { 744 | XSetWindowBorder(dpy, sel->frame, style.color.norm[ColBorder]); 745 | } 746 | if (c) { 747 | detachstack(c); 748 | attachstack(c); 749 | /* unban(c); */ 750 | } 751 | sel = c; 752 | if (!selscreen) 753 | return; 754 | if (c) { 755 | setclientstate(c, NormalState); 756 | if (c->isfocusable) { 757 | XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); 758 | givefocus(c); 759 | } 760 | XSetWindowBorder(dpy, sel->frame, style.color.sel[ColBorder]); 761 | drawclient(c); 762 | } else { 763 | XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); 764 | } 765 | if (o) 766 | drawclient(o); 767 | updateatom[ActiveWindow] (sel); 768 | updateatom[ClientList] (NULL); 769 | updateatom[CurDesk] (NULL); 770 | } 771 | 772 | void 773 | focusicon(const char *arg) { 774 | Client *c; 775 | 776 | for (c = clients; c && (!c->isicon || !isvisible(c, curmonitor())); c = c->next); 777 | if (!c) 778 | return; 779 | c->isicon = False; 780 | focus(c); 781 | arrange(curmonitor()); 782 | } 783 | 784 | void 785 | focusnext(const char *arg) { 786 | Client *c; 787 | 788 | if (!sel) 789 | return; 790 | for (c = sel->next; 791 | c && (c->isbastard || c->isicon || !isvisible(c, curmonitor())); c = c->next); 792 | if (!c) 793 | for (c = clients; 794 | c && (c->isbastard || c->isicon 795 | || !isvisible(c, curmonitor())); c = c->next); 796 | if (c) { 797 | focus(c); 798 | restack(curmonitor()); 799 | } 800 | } 801 | 802 | void 803 | focusprev(const char *arg) { 804 | Client *c; 805 | 806 | if (!sel) 807 | return; 808 | for (c = sel->prev; 809 | c && (c->isbastard || c->isicon || !isvisible(c, curmonitor())); c = c->prev); 810 | if (!c) { 811 | for (c = clients; c && c->next; c = c->next); 812 | for (; 813 | c && (c->isbastard || c->isicon 814 | || !isvisible(c, curmonitor())); c = c->prev); 815 | } 816 | if (c) { 817 | focus(c); 818 | restack(curmonitor()); 819 | } 820 | } 821 | 822 | void 823 | iconify(const char *arg) { 824 | Client *c; 825 | if (!sel) 826 | return; 827 | c = sel; 828 | focusnext(NULL); 829 | ban(c); 830 | c->isicon = True; 831 | arrange(curmonitor()); 832 | } 833 | 834 | void 835 | incnmaster(const char *arg) { 836 | unsigned int i; 837 | 838 | if (!FEATURES(curlayout, NMASTER)) 839 | return; 840 | if (!arg) { 841 | views[curmontag].nmaster = DEFNMASTER; 842 | } else { 843 | i = atoi(arg); 844 | if ((views[curmontag].nmaster + i) < 1 845 | || curwah / (views[curmontag].nmaster + i) <= 2 * style.border) 846 | return; 847 | views[curmontag].nmaster += i; 848 | } 849 | if (sel) 850 | arrange(curmonitor()); 851 | } 852 | 853 | Client * 854 | getclient(Window w, Client * list, int part) { 855 | Client *c; 856 | 857 | #define ClientPart(_c, _part) (((_part) == ClientWindow) ? (_c)->win : \ 858 | ((_part) == ClientTitle) ? (_c)->title : \ 859 | ((_part) == ClientFrame) ? (_c)->frame : 0) 860 | 861 | for (c = list; c && (ClientPart(c, part)) != w; c = c->next); 862 | 863 | return c; 864 | } 865 | 866 | long 867 | getstate(Window w) { 868 | long ret = -1; 869 | long *p = NULL; 870 | unsigned long n; 871 | 872 | p = (long*)getatom(w, atom[WMState], &n); 873 | if (n != 0) 874 | ret = *p; 875 | XFree(p); 876 | return ret; 877 | } 878 | 879 | const char * 880 | getresource(const char *resource, const char *defval) { 881 | static char name[256], class[256], *type; 882 | XrmValue value; 883 | 884 | snprintf(name, sizeof(name), "%s.%s", RESNAME, resource); 885 | snprintf(class, sizeof(class), "%s.%s", RESCLASS, resource); 886 | XrmGetResource(xrdb, name, class, &type, &value); 887 | if (value.addr) 888 | return value.addr; 889 | return defval; 890 | } 891 | 892 | Bool 893 | gettextprop(Window w, Atom atom, char *text, unsigned int size) { 894 | char **list = NULL; 895 | int n; 896 | XTextProperty name; 897 | 898 | if (!text || size == 0) 899 | return False; 900 | text[0] = '\0'; 901 | XGetTextProperty(dpy, w, &name, atom); 902 | if (!name.nitems) 903 | return False; 904 | if (name.encoding == XA_STRING) { 905 | strncpy(text, (char *) name.value, size - 1); 906 | } else { 907 | if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success 908 | && n > 0 && *list) { 909 | strncpy(text, *list, size - 1); 910 | XFreeStringList(list); 911 | } 912 | } 913 | text[size - 1] = '\0'; 914 | XFree(name.value); 915 | return True; 916 | } 917 | 918 | int 919 | idxoftag(const char *tag) { 920 | unsigned int i; 921 | 922 | for (i = 0; (i < ntags) && strcmp(tag, tags[i]); i++); 923 | return (i < ntags) ? i : 0; 924 | } 925 | 926 | Bool 927 | isvisible(Client * c, Monitor * m) { 928 | unsigned int i; 929 | 930 | if (!c) 931 | return False; 932 | if (!m) { 933 | for (m = monitors; m; m = m->next) { 934 | for (i = 0; i < ntags; i++) 935 | if (c->tags[i] && m->seltags[i]) 936 | return True; 937 | } 938 | } else { 939 | for (i = 0; i < ntags; i++) 940 | if (c->tags[i] && m->seltags[i]) 941 | return True; 942 | } 943 | return False; 944 | } 945 | 946 | void 947 | grabkeys(void) { 948 | unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; 949 | unsigned int i, j; 950 | KeyCode code; 951 | XUngrabKey(dpy, AnyKey, AnyModifier, root); 952 | for (i = 0; i < nkeys; i++) { 953 | if ((code = XKeysymToKeycode(dpy, keys[i]->keysym))) { 954 | for (j = 0; j < LENGTH(modifiers); j++) 955 | XGrabKey(dpy, code, keys[i]->mod | modifiers[j], root, 956 | True, GrabModeAsync, GrabModeAsync); 957 | } 958 | } 959 | } 960 | 961 | void 962 | keypress(XEvent * e) { 963 | unsigned int i; 964 | KeySym keysym; 965 | XKeyEvent *ev; 966 | 967 | if (!curmonitor()) 968 | return; 969 | ev = &e->xkey; 970 | keysym = XKeycodeToKeysym(dpy, (KeyCode) ev->keycode, 0); 971 | for (i = 0; i < nkeys; i++) 972 | if (keysym == keys[i]->keysym 973 | && CLEANMASK(keys[i]->mod) == CLEANMASK(ev->state)) { 974 | if (keys[i]->func) 975 | keys[i]->func(keys[i]->arg); 976 | XUngrabKeyboard(dpy, CurrentTime); 977 | } 978 | } 979 | 980 | void 981 | killclient(const char *arg) { 982 | XEvent ev; 983 | 984 | if (!sel) 985 | return; 986 | if (checkatom(sel->win, atom[WMProto], atom[WMDelete])) { 987 | ev.type = ClientMessage; 988 | ev.xclient.window = sel->win; 989 | ev.xclient.message_type = atom[WMProto]; 990 | ev.xclient.format = 32; 991 | ev.xclient.data.l[0] = atom[WMDelete]; 992 | ev.xclient.data.l[1] = CurrentTime; 993 | XSendEvent(dpy, sel->win, False, NoEventMask, &ev); 994 | } else { 995 | XKillClient(dpy, sel->win); 996 | } 997 | } 998 | 999 | void 1000 | leavenotify(XEvent * e) { 1001 | XCrossingEvent *ev = &e->xcrossing; 1002 | 1003 | if ((ev->window == root) && !ev->same_screen) { 1004 | selscreen = False; 1005 | focus(NULL); 1006 | } 1007 | } 1008 | 1009 | void 1010 | manage(Window w, XWindowAttributes * wa) { 1011 | Client *c, *t = NULL; 1012 | Monitor *cm = NULL; 1013 | Window trans; 1014 | XWindowChanges wc; 1015 | XSetWindowAttributes twa; 1016 | XWMHints *wmh; 1017 | unsigned long mask = 0; 1018 | 1019 | c = emallocz(sizeof(Client)); 1020 | c->win = w; 1021 | if (checkatom(c->win, atom[WindowType], atom[WindowTypeDesk]) || 1022 | checkatom(c->win, atom[WindowType], atom[WindowTypeDock])) { 1023 | c->isbastard = True; 1024 | c->isfloating = True; 1025 | c->isfixed = True; 1026 | } 1027 | if (checkatom(c->win, atom[WindowType], atom[WindowTypeDialog])) { 1028 | c->isfloating = True; 1029 | c->isfixed = True; 1030 | } 1031 | 1032 | cm = curmonitor(); 1033 | c->isicon = False; 1034 | c->title = c->isbastard ? (Window) NULL : 1; 1035 | c->tags = emallocz(ntags * sizeof(cm->seltags[0])); 1036 | c->isfocusable = c->isbastard ? False : True; 1037 | c->border = c->isbastard ? 0 : style.border; 1038 | c->oldborder = c->isbastard ? 0 : wa->border_width; /* XXX: why? */ 1039 | /* XReparentWindow() unmaps *mapped* windows */ 1040 | c->ignoreunmap = wa->map_state == IsViewable ? 1 : 0; 1041 | mwm_process_atom(c); 1042 | updatesizehints(c); 1043 | 1044 | updatetitle(c); 1045 | applyrules(c); 1046 | applyatoms(c); 1047 | 1048 | if (XGetTransientForHint(dpy, w, &trans)) { 1049 | if (t = getclient(trans, clients, ClientWindow)) { 1050 | memcpy(c->tags, t->tags, ntags * sizeof(cm->seltags[0])); 1051 | c->isfloating = True; 1052 | } 1053 | } 1054 | 1055 | c->th = c->title ? style.titleheight : 0; 1056 | 1057 | if (!c->isfloating) 1058 | c->isfloating = c->isfixed; 1059 | 1060 | if ((wmh = XGetWMHints(dpy, c->win))) { 1061 | c->isfocusable = !(wmh->flags & InputHint) || wmh->input; 1062 | XFree(wmh); 1063 | } 1064 | 1065 | c->x = c->rx = wa->x; 1066 | c->y = c->ry = wa->y; 1067 | c->w = c->rw = wa->width; 1068 | c->h = c->rh = wa->height + c->th; 1069 | 1070 | if (!wa->x && !wa->y && !c->isbastard) 1071 | place(c); 1072 | 1073 | cm = c->isbastard ? getmonitor(wa->x, wa->y) : clientmonitor(c); 1074 | if (!cm) { 1075 | DPRINTF("Cannot find monitor for window 0x%x," 1076 | "requested coordinates %d,%d\n", w, wa->x, wa->y); 1077 | cm = curmonitor(); 1078 | } 1079 | c->hasstruts = getstruts(c); 1080 | if (c->isbastard) { 1081 | free(c->tags); 1082 | c->tags = cm->seltags; 1083 | } 1084 | #if 0 1085 | if (c->w == cm->sw && c->h == cm->sh) { 1086 | c->x = 0; 1087 | c->y = 0; 1088 | } else if (!c->isbastard) { 1089 | if (c->x + c->w > cm->wax + cm->waw) 1090 | c->x = cm->waw - c->w; 1091 | if (c->y + c->h > cm->way + cm->wah) 1092 | c->y = cm->wah - c->h; 1093 | if (c->x < cm->wax) 1094 | c->x = cm->wax; 1095 | if (c->y < cm->way) 1096 | c->y = cm->way; 1097 | } 1098 | #endif 1099 | 1100 | XGrabButton(dpy, AnyButton, AnyModifier, c->win, True, 1101 | ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); 1102 | twa.override_redirect = True; 1103 | twa.event_mask = FRAMEMASK; 1104 | mask = CWOverrideRedirect | CWEventMask; 1105 | if (wa->depth == 32) { 1106 | mask |= CWColormap | CWBorderPixel | CWBackPixel; 1107 | twa.colormap = XCreateColormap(dpy, root, wa->visual, AllocNone); 1108 | twa.background_pixel = BlackPixel(dpy, screen); 1109 | twa.border_pixel = BlackPixel(dpy, screen); 1110 | } 1111 | c->frame = 1112 | XCreateWindow(dpy, root, c->x, c->y, c->w, 1113 | c->h, c->border, wa->depth == 32 ? 32 : DefaultDepth(dpy, screen), 1114 | InputOutput, wa->depth == 32 ? wa->visual : DefaultVisual(dpy, 1115 | screen), mask, &twa); 1116 | 1117 | wc.border_width = c->border; 1118 | XConfigureWindow(dpy, c->frame, CWBorderWidth, &wc); 1119 | XSetWindowBorder(dpy, c->frame, style.color.norm[ColBorder]); 1120 | 1121 | twa.event_mask = ExposureMask | MOUSEMASK; 1122 | /* we create title as root's child as a workaround for 32bit visuals */ 1123 | if (c->title) { 1124 | c->title = XCreateWindow(dpy, root, 0, 0, c->w, c->th, 1125 | 0, DefaultDepth(dpy, screen), CopyFromParent, 1126 | DefaultVisual(dpy, screen), CWEventMask, &twa); 1127 | c->drawable = 1128 | XCreatePixmap(dpy, root, c->w, c->th, DefaultDepth(dpy, screen)); 1129 | c->xftdraw = 1130 | XftDrawCreate(dpy, c->drawable, DefaultVisual(dpy, screen), 1131 | DefaultColormap(dpy, screen)); 1132 | } else { 1133 | c->title = (Window) NULL; 1134 | } 1135 | 1136 | attach(c, options.attachaside); 1137 | attachstack(c); 1138 | 1139 | twa.event_mask = CLIENTMASK; 1140 | twa.do_not_propagate_mask = CLIENTNOPROPAGATEMASK; 1141 | XChangeWindowAttributes(dpy, c->win, CWEventMask|CWDontPropagate, &twa); 1142 | XSelectInput(dpy, c->win, CLIENTMASK); 1143 | 1144 | XReparentWindow(dpy, c->win, c->frame, 0, c->th); 1145 | XReparentWindow(dpy, c->title, c->frame, 0, 0); 1146 | XAddToSaveSet(dpy, c->win); 1147 | XMapWindow(dpy, c->win); 1148 | wc.border_width = 0; 1149 | XConfigureWindow(dpy, c->win, CWBorderWidth, &wc); 1150 | configure(c); /* propagates border_width, if size doesn't change */ 1151 | if (checkatom(c->win, atom[WindowState], atom[WindowStateFs])) 1152 | ewmh_process_state_atom(c, atom[WindowStateFs], 1); 1153 | ban(c); 1154 | updateatom[ClientList] (NULL); 1155 | updateatom[WindowDesk] (c); 1156 | updateframe(c); 1157 | if (!cm) 1158 | return; 1159 | if (c->hasstruts) 1160 | updategeom(cm); 1161 | arrange(cm); 1162 | if (!checkatom(c->win, atom[WindowType], atom[WindowTypeDesk])) 1163 | focus(NULL); 1164 | } 1165 | 1166 | void 1167 | mappingnotify(XEvent * e) { 1168 | XMappingEvent *ev = &e->xmapping; 1169 | 1170 | XRefreshKeyboardMapping(ev); 1171 | if (ev->request == MappingKeyboard) 1172 | grabkeys(); 1173 | } 1174 | 1175 | void 1176 | maprequest(XEvent * e) { 1177 | static XWindowAttributes wa; 1178 | Client *c; 1179 | XMapRequestEvent *ev = &e->xmaprequest; 1180 | 1181 | if (!XGetWindowAttributes(dpy, ev->window, &wa)) 1182 | return; 1183 | if (wa.override_redirect) 1184 | return; 1185 | if (!(c = getclient(ev->window, clients, ClientWindow))) 1186 | manage(ev->window, &wa); 1187 | } 1188 | 1189 | void 1190 | monocle(Monitor * m) { 1191 | Client *c; 1192 | 1193 | for (c = nexttiled(clients, m); c; c = nexttiled(c->next, m)) { 1194 | if (views[m->curtag].barpos != StrutsOn) 1195 | resize(c, m->wax - c->border, 1196 | m->way - c->border, m->waw, m->wah, False); 1197 | else 1198 | resize(c, m->wax, m->way, 1199 | m->waw - 2 * c->border, 1200 | m->wah - 2 * c->border, False); 1201 | } 1202 | } 1203 | 1204 | void 1205 | moveresizekb(const char *arg) { 1206 | int dw, dh, dx, dy; 1207 | 1208 | dw = dh = dx = dy = 0; 1209 | if (!sel) 1210 | return; 1211 | if (!sel->isfloating) 1212 | return; 1213 | sscanf(arg, "%d %d %d %d", &dx, &dy, &dw, &dh); 1214 | if (dw && (dw < sel->incw)) 1215 | dw = (dw / abs(dw)) * sel->incw; 1216 | if (dh && (dh < sel->inch)) 1217 | dh = (dh / abs(dh)) * sel->inch; 1218 | resize(sel, sel->x + dx, sel->y + dy, sel->w + dw, 1219 | sel->h + dh, True); 1220 | } 1221 | 1222 | void 1223 | getpointer(int *x, int *y) { 1224 | int di; 1225 | unsigned int dui; 1226 | Window dummy; 1227 | 1228 | XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui); 1229 | } 1230 | 1231 | Monitor * 1232 | getmonitor(int x, int y) { 1233 | Monitor *m; 1234 | 1235 | for (m = monitors; m; m = m->next) { 1236 | if ((x >= m->sx && x <= m->sx + m->sw) && 1237 | (y >= m->sy && y <= m->sy + m->sh)) 1238 | return m; 1239 | } 1240 | return NULL; 1241 | } 1242 | 1243 | Monitor * 1244 | clientmonitor(Client * c) { 1245 | Monitor *m; 1246 | 1247 | assert(c != NULL); 1248 | for (m = monitors; m; m = m->next) 1249 | if (isvisible(c, m)) 1250 | return m; 1251 | return NULL; 1252 | } 1253 | 1254 | Monitor * 1255 | curmonitor() { 1256 | int x, y; 1257 | getpointer(&x, &y); 1258 | return getmonitor(x, y); 1259 | } 1260 | 1261 | void 1262 | mousemove(Client * c) { 1263 | int x1, y1, ocx, ocy, nx, ny; 1264 | unsigned int i; 1265 | XEvent ev; 1266 | Monitor *m, *nm; 1267 | 1268 | if (c->isbastard) 1269 | return; 1270 | m = curmonitor(); 1271 | ocx = c->x; 1272 | ocy = c->y; 1273 | if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, 1274 | GrabModeAsync, None, cursor[CurMove], CurrentTime) != GrabSuccess) 1275 | return; 1276 | getpointer(&x1, &y1); 1277 | for (;;) { 1278 | XMaskEvent(dpy, MOUSEMASK | ExposureMask | SubstructureRedirectMask, &ev); 1279 | switch (ev.type) { 1280 | case ButtonRelease: 1281 | XUngrabPointer(dpy, CurrentTime); 1282 | return; 1283 | case ConfigureRequest: 1284 | case Expose: 1285 | case MapRequest: 1286 | handler[ev.type] (&ev); 1287 | break; 1288 | case MotionNotify: 1289 | XSync(dpy, False); 1290 | /* we are probably moving to a different monitor */ 1291 | if (!(nm = curmonitor())) 1292 | break; 1293 | nx = ocx + (ev.xmotion.x_root - x1); 1294 | ny = ocy + (ev.xmotion.y_root - y1); 1295 | if (abs(nx - nm->wax) < options.snap) 1296 | nx = nm->wax; 1297 | else if (abs((nm->wax + nm->waw) - (nx + c->w + 1298 | 2 * c->border)) < options.snap) 1299 | nx = nm->wax + nm->waw - c->w - 2 * c->border; 1300 | if (abs(ny - nm->way) < options.snap) 1301 | ny = nm->way; 1302 | else if (abs((nm->way + nm->wah) - (ny + c->h + 1303 | 2 * c->border)) < options.snap) 1304 | ny = nm->way + nm->wah - c->h - 2 * c->border; 1305 | resize(c, nx, ny, c->w, c->h, True); 1306 | save(c); 1307 | if (m != nm) { 1308 | for (i = 0; i < ntags; i++) 1309 | c->tags[i] = nm->seltags[i]; 1310 | updateatom[WindowDesk] (c); 1311 | drawclient(c); 1312 | arrange(NULL); 1313 | m = nm; 1314 | } 1315 | break; 1316 | } 1317 | } 1318 | } 1319 | 1320 | void 1321 | mouseresize(Client * c) { 1322 | int ocx, ocy, nw, nh; 1323 | Monitor *cm; 1324 | XEvent ev; 1325 | 1326 | if (c->isbastard || c->isfixed) 1327 | return; 1328 | cm = curmonitor(); 1329 | 1330 | ocx = c->x; 1331 | ocy = c->y; 1332 | if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, 1333 | GrabModeAsync, None, cursor[CurResize], CurrentTime) != GrabSuccess) 1334 | return; 1335 | c->ismax = False; 1336 | XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->border - 1, 1337 | c->h + c->border - 1); 1338 | for (;;) { 1339 | XMaskEvent(dpy, MOUSEMASK | ExposureMask | SubstructureRedirectMask, &ev); 1340 | switch (ev.type) { 1341 | case ButtonRelease: 1342 | XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, 1343 | c->w + c->border - 1, c->h + c->border - 1); 1344 | XUngrabPointer(dpy, CurrentTime); 1345 | while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); 1346 | return; 1347 | case ConfigureRequest: 1348 | case Expose: 1349 | case MapRequest: 1350 | handler[ev.type] (&ev); 1351 | break; 1352 | case MotionNotify: 1353 | XSync(dpy, False); 1354 | if ((nw = ev.xmotion.x - ocx - 2 * c->border + 1) <= 0) 1355 | nw = MINWIDTH; 1356 | if ((nh = ev.xmotion.y - ocy - 2 * c->border + 1) <= 0) 1357 | nh = MINHEIGHT; 1358 | resize(c, c->x, c->y, nw, nh, True); 1359 | save(c); 1360 | break; 1361 | } 1362 | } 1363 | } 1364 | 1365 | Client * 1366 | nexttiled(Client * c, Monitor * m) { 1367 | for (; c && (c->isfloating || !isvisible(c, m) || c->isbastard 1368 | || c->isicon); c = c->next); 1369 | return c; 1370 | } 1371 | 1372 | Client * 1373 | prevtiled(Client * c, Monitor * m) { 1374 | for (; c && (c->isfloating || !isvisible(c, m) || c->isbastard 1375 | || c->isicon); c = c->prev); 1376 | return c; 1377 | } 1378 | 1379 | void 1380 | reparentnotify(XEvent * e) { 1381 | Client *c; 1382 | XReparentEvent *ev = &e->xreparent; 1383 | 1384 | if ((c = getclient(ev->window, clients, ClientWindow))) 1385 | if (ev->parent != c->frame) 1386 | unmanage(c); 1387 | } 1388 | 1389 | void 1390 | place(Client *c) { 1391 | int x, y; 1392 | Monitor *m; 1393 | int d = style.titleheight; 1394 | 1395 | /* XXX: do something better */ 1396 | getpointer(&x, &y); 1397 | DPRINTF("%d %d\n", x, y); 1398 | m = getmonitor(x, y); 1399 | x = x + rand()%d - c->w/2; 1400 | y = y + rand()%d - c->h/2; 1401 | if (x < m->wax) 1402 | x = m->wax; 1403 | DPRINTF("%d %d\n", x, y); 1404 | if (y < m->way) 1405 | y = m->way; 1406 | DPRINTF("%d+%d > %d+%d\n", x, c->w, m->wax, m->waw); 1407 | if (x + c->w > m->wax + m->waw) 1408 | x = m->wax + m->waw - c->w - rand()%d; 1409 | DPRINTF("%d %d\n", x, y); 1410 | if (y + c->h > m->way + m->wah) 1411 | y = m->way + m->wah - c->h - rand()%d; 1412 | DPRINTF("%d %d\n", x, y); 1413 | 1414 | c->rx = c->x = x; 1415 | c->ry = c->y = y; 1416 | } 1417 | 1418 | void 1419 | propertynotify(XEvent * e) { 1420 | Client *c; 1421 | Window trans; 1422 | XPropertyEvent *ev = &e->xproperty; 1423 | 1424 | if ((c = getclient(ev->window, clients, ClientWindow))) { 1425 | if (ev->atom == atom[StrutPartial]) { 1426 | c->hasstruts = getstruts(c); 1427 | updategeom(clientmonitor(c)); 1428 | arrange(clientmonitor(c)); 1429 | } 1430 | if (ev->state == PropertyDelete) 1431 | return; 1432 | if (ev->atom == atom[WindowName]) { 1433 | updatetitle(c); 1434 | drawclient(c); 1435 | } 1436 | switch (ev->atom) { 1437 | case XA_WM_TRANSIENT_FOR: 1438 | XGetTransientForHint(dpy, c->win, &trans); 1439 | if (!c->isfloating 1440 | && (c->isfloating = 1441 | (getclient(trans, clients, ClientWindow) != NULL))) 1442 | arrange(clientmonitor(c)); 1443 | break; 1444 | case XA_WM_NORMAL_HINTS: 1445 | updatesizehints(c); 1446 | break; 1447 | case XA_WM_NAME: 1448 | updatetitle(c); 1449 | drawclient(c); 1450 | break; 1451 | } 1452 | } 1453 | } 1454 | 1455 | void 1456 | quit(const char *arg) { 1457 | running = False; 1458 | if (arg) { 1459 | cleanup(); 1460 | execvp(cargv[0], cargv); 1461 | eprint("Can't exec: %s\n", strerror(errno)); 1462 | } 1463 | } 1464 | 1465 | void 1466 | resize(Client * c, int x, int y, int w, int h, Bool sizehints) { 1467 | XWindowChanges wc; 1468 | 1469 | if (sizehints) { 1470 | h -= c->th; 1471 | /* set minimum possible */ 1472 | if (w < 1) 1473 | w = 1; 1474 | if (h < 1) 1475 | h = 1; 1476 | 1477 | /* temporarily remove base dimensions */ 1478 | w -= c->basew; 1479 | h -= c->baseh; 1480 | 1481 | /* adjust for aspect limits */ 1482 | if (c->minay > 0 && c->maxay > 0 && c->minax > 0 && c->maxax > 0) { 1483 | if (w * c->maxay > h * c->maxax) 1484 | w = h * c->maxax / c->maxay; 1485 | else if (w * c->minay < h * c->minax) 1486 | h = w * c->minay / c->minax; 1487 | } 1488 | 1489 | /* adjust for increment value */ 1490 | if (c->incw) 1491 | w -= w % c->incw; 1492 | if (c->inch) 1493 | h -= h % c->inch; 1494 | 1495 | /* restore base dimensions */ 1496 | w += c->basew; 1497 | h += c->baseh; 1498 | 1499 | if (c->minw > 0 && w < c->minw) 1500 | w = c->minw; 1501 | if (c->minh > 0 && h - c->th < c->minh) 1502 | h = c->minh + c->th; 1503 | if (c->maxw > 0 && w > c->maxw) 1504 | w = c->maxw; 1505 | if (c->maxh > 0 && h - c->th > c->maxh) 1506 | h = c->maxh + c->th; 1507 | h += c->th; 1508 | } 1509 | if (w <= 0 || h <= 0) 1510 | return; 1511 | /* offscreen appearance fixes */ 1512 | if (x > DisplayWidth(dpy, screen)) 1513 | x = DisplayWidth(dpy, screen) - w - 2 * c->border; 1514 | if (y > DisplayHeight(dpy, screen)) 1515 | y = DisplayHeight(dpy, screen) - h - 2 * c->border; 1516 | if (w != c->w && c->th) { 1517 | XMoveResizeWindow(dpy, c->title, 0, 0, w, c->th); 1518 | XFreePixmap(dpy, c->drawable); 1519 | c->drawable = 1520 | XCreatePixmap(dpy, root, w, c->th, DefaultDepth(dpy, screen)); 1521 | drawclient(c); 1522 | } 1523 | if (c->x != x || c->y != y || c->w != w || c->h != h /* || sizehints */) { 1524 | c->x = x; 1525 | c->y = y; 1526 | c->w = w; 1527 | c->h = h; 1528 | DPRINTF("x = %d y = %d w = %d h = %d\n", c->x, c->y, c->w, c->h); 1529 | XMoveResizeWindow(dpy, c->frame, c->x, c->y, c->w, c->h); 1530 | XMoveResizeWindow(dpy, c->win, 0, c->th, c->w, c->h - c->th); 1531 | configure(c); 1532 | XSync(dpy, False); 1533 | } 1534 | } 1535 | 1536 | void 1537 | restack(Monitor * m) { 1538 | Client *c; 1539 | XEvent ev; 1540 | Window *wl; 1541 | int i, n; 1542 | 1543 | if (!sel) 1544 | return; 1545 | #if 0 1546 | if (MFEATURES(m, OVERLAP)) { 1547 | XRaiseWindow(dpy, stack->frame); 1548 | goto end; 1549 | } 1550 | #endif 1551 | for (n = 0, c = stack; c; c = c->snext) { 1552 | if (isvisible(c, m) && !c->isicon) { 1553 | n++; 1554 | } 1555 | } 1556 | if (!n) 1557 | return; 1558 | wl = malloc(sizeof(Window) * n); 1559 | i = 0; 1560 | for (c = stack; c && i < n; c = c->snext) 1561 | if (isvisible(c, m) && !c->isicon) 1562 | if (!c->isbastard && c->isfloating) 1563 | wl[i++] = c->frame; 1564 | for (c = stack; c && i < n; c = c->snext) 1565 | if (isvisible(c, m) && !c->isicon && c->isbastard && 1566 | !checkatom(c->win, atom[WindowType], atom[WindowTypeDesk])) 1567 | wl[i++] = c->frame; 1568 | for (c = stack; c && i < n; c = c->snext) 1569 | if (isvisible(c, m) && !c->isicon) 1570 | if (!c->isfloating && !c->isbastard) 1571 | wl[i++] = c->frame; 1572 | for (c = stack; c && i < n; c = c->snext) 1573 | if (isvisible(c, m) && !c->isicon && c->isbastard && 1574 | checkatom(c->win, atom[WindowType], atom[WindowTypeDesk])) 1575 | wl[i++] = c->frame; 1576 | assert(i == n); 1577 | XRestackWindows(dpy, wl, n); 1578 | free(wl); 1579 | XSync(dpy, False); 1580 | while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); 1581 | } 1582 | 1583 | void 1584 | run(void) { 1585 | fd_set rd; 1586 | int xfd; 1587 | XEvent ev; 1588 | 1589 | /* main event loop */ 1590 | XSync(dpy, False); 1591 | xfd = ConnectionNumber(dpy); 1592 | while (running) { 1593 | FD_ZERO(&rd); 1594 | FD_SET(xfd, &rd); 1595 | if (select(xfd + 1, &rd, NULL, NULL, NULL) == -1) { 1596 | if (errno == EINTR) 1597 | continue; 1598 | eprint("select failed\n"); 1599 | } 1600 | while (XPending(dpy)) { 1601 | XNextEvent(dpy, &ev); 1602 | if (handler[ev.type]) 1603 | (handler[ev.type]) (&ev); /* call handler */ 1604 | } 1605 | } 1606 | } 1607 | 1608 | void 1609 | save(Client *c) { 1610 | c->rx = c->x; 1611 | c->ry = c->y; 1612 | c->rw = c->w; 1613 | c->rh = c->h; 1614 | } 1615 | 1616 | void 1617 | scan(void) { 1618 | unsigned int i, num; 1619 | Window *wins, d1, d2; 1620 | XWindowAttributes wa; 1621 | 1622 | wins = NULL; 1623 | if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) { 1624 | for (i = 0; i < num; i++) { 1625 | if (!XGetWindowAttributes(dpy, wins[i], &wa) || 1626 | wa.override_redirect 1627 | || XGetTransientForHint(dpy, wins[i], &d1)) 1628 | continue; 1629 | if (wa.map_state == IsViewable 1630 | || getstate(wins[i]) == IconicState 1631 | || getstate(wins[i]) == NormalState) 1632 | manage(wins[i], &wa); 1633 | } 1634 | for (i = 0; i < num; i++) { /* now the transients */ 1635 | if (!XGetWindowAttributes(dpy, wins[i], &wa)) 1636 | continue; 1637 | if (XGetTransientForHint(dpy, wins[i], &d1) 1638 | && (wa.map_state == IsViewable 1639 | || getstate(wins[i]) == IconicState 1640 | || getstate(wins[i]) == NormalState)) 1641 | manage(wins[i], &wa); 1642 | } 1643 | } 1644 | if (wins) 1645 | XFree(wins); 1646 | } 1647 | 1648 | void 1649 | setclientstate(Client * c, long state) { 1650 | long data[] = { state, None }; 1651 | long winstate[2]; 1652 | 1653 | XChangeProperty(dpy, c->win, atom[WMState], atom[WMState], 32, 1654 | PropModeReplace, (unsigned char *) data, 2); 1655 | if (state == NormalState) { 1656 | c->isicon = False; 1657 | XDeleteProperty(dpy, c->win, atom[WindowState]); 1658 | } else { 1659 | winstate[0] = atom[WindowStateHidden]; 1660 | XChangeProperty(dpy, c->win, atom[WindowState], XA_ATOM, 32, 1661 | PropModeReplace, (unsigned char *) winstate, 1); 1662 | } 1663 | } 1664 | 1665 | void 1666 | setlayout(const char *arg) { 1667 | unsigned int i; 1668 | Client *c; 1669 | Bool wasfloat; 1670 | 1671 | wasfloat = FEATURES(curlayout, OVERLAP); 1672 | 1673 | if (arg) { 1674 | for (i = 0; i < LENGTH(layouts); i++) 1675 | if (*arg == layouts[i].symbol) 1676 | break; 1677 | if (i == LENGTH(layouts)) 1678 | return; 1679 | views[curmontag].layout = &layouts[i]; 1680 | } 1681 | if (sel) { 1682 | for (c = clients; c; c = c->next) { 1683 | if (isvisible(c, curmonitor())) { 1684 | if (wasfloat) 1685 | save(c); 1686 | if (wasfloat != FEATURES(curlayout, OVERLAP)) 1687 | updateframe(c); 1688 | } 1689 | } 1690 | arrange(curmonitor()); 1691 | } 1692 | updateatom[ELayout] (NULL); 1693 | } 1694 | 1695 | void 1696 | setmwfact(const char *arg) { 1697 | double delta; 1698 | 1699 | if (!FEATURES(curlayout, MWFACT)) 1700 | return; 1701 | /* arg handling, manipulate mwfact */ 1702 | if (arg == NULL) 1703 | views[curmontag].mwfact = DEFMWFACT; 1704 | else if (sscanf(arg, "%lf", &delta) == 1) { 1705 | if (arg[0] == '+' || arg[0] == '-') 1706 | views[curmontag].mwfact += delta; 1707 | else 1708 | views[curmontag].mwfact = delta; 1709 | if (views[curmontag].mwfact < 0.1) 1710 | views[curmontag].mwfact = 0.1; 1711 | else if (views[curmontag].mwfact > 0.9) 1712 | views[curmontag].mwfact = 0.9; 1713 | } 1714 | arrange(curmonitor()); 1715 | } 1716 | 1717 | void 1718 | initlayouts() { 1719 | unsigned int i, j; 1720 | char conf[32], ltname; 1721 | float mwfact; 1722 | int nmaster; 1723 | const char *deflayout; 1724 | 1725 | /* init layouts */ 1726 | mwfact = atof(getresource("mwfact", STR(DEFMWFACT))); 1727 | nmaster = atoi(getresource("nmaster", STR(DEFNMASTER))); 1728 | deflayout = getresource("deflayout", "i"); 1729 | if (!nmaster) 1730 | nmaster = 1; 1731 | for (i = 0; i < ntags; i++) { 1732 | views[i].layout = &layouts[0]; 1733 | snprintf(conf, sizeof(conf), "tags.layout%d", i); 1734 | strncpy(<name, getresource(conf, deflayout), 1); 1735 | for (j = 0; j < LENGTH(layouts); j++) { 1736 | if (layouts[j].symbol == ltname) { 1737 | views[i].layout = &layouts[j]; 1738 | break; 1739 | } 1740 | } 1741 | views[i].mwfact = mwfact; 1742 | views[i].nmaster = nmaster; 1743 | views[i].barpos = StrutsOn; 1744 | } 1745 | updateatom[ELayout] (NULL); 1746 | } 1747 | 1748 | void 1749 | initmonitors(XEvent * e) { 1750 | Monitor *m; 1751 | #ifdef XRANDR 1752 | Monitor *t; 1753 | XRRCrtcInfo *ci; 1754 | XRRScreenResources *sr; 1755 | int c, n; 1756 | int ncrtc = 0; 1757 | int dummy1, dummy2, major, minor; 1758 | 1759 | /* free */ 1760 | if (monitors) { 1761 | m = monitors; 1762 | do { 1763 | t = m->next; 1764 | free(m->seltags); 1765 | free(m->prevtags); 1766 | free(m); 1767 | m = t; 1768 | } while (m); 1769 | monitors = NULL; 1770 | } 1771 | if (!running) 1772 | return; 1773 | /* initial Xrandr setup */ 1774 | if (XRRQueryExtension(dpy, &dummy1, &dummy2)) 1775 | if (XRRQueryVersion(dpy, &major, &minor) && major < 1) 1776 | goto no_xrandr; 1777 | 1778 | /* map virtual screens onto physical screens */ 1779 | sr = XRRGetScreenResources(dpy, root); 1780 | if (sr == NULL) 1781 | goto no_xrandr; 1782 | else 1783 | ncrtc = sr->ncrtc; 1784 | 1785 | for (c = 0, n = 0, ci = NULL; c < ncrtc; c++) { 1786 | ci = XRRGetCrtcInfo(dpy, sr, sr->crtcs[c]); 1787 | if (ci->noutput == 0) 1788 | continue; 1789 | 1790 | if (ci != NULL && ci->mode == None) 1791 | fprintf(stderr, "???\n"); 1792 | else { 1793 | /* If monitor is a mirror, we don't care about it */ 1794 | if (n && ci->x == monitors->sx && ci->y == monitors->sy) 1795 | continue; 1796 | m = emallocz(sizeof(Monitor)); 1797 | m->sx = m->wax = ci->x; 1798 | m->sy = m->way = ci->y; 1799 | m->sw = m->waw = ci->width; 1800 | m->sh = m->wah = ci->height; 1801 | m->mx = m->sx + m->sw/2; 1802 | m->my = m->sy + m->sh/2; 1803 | m->curtag = n; 1804 | m->prevtags = emallocz(ntags * sizeof(Bool)); 1805 | m->seltags = emallocz(ntags * sizeof(Bool)); 1806 | m->seltags[n] = True; 1807 | m->next = monitors; 1808 | monitors = m; 1809 | n++; 1810 | } 1811 | XRRFreeCrtcInfo(ci); 1812 | } 1813 | XRRFreeScreenResources(sr); 1814 | updateatom[WorkArea] (NULL); 1815 | return; 1816 | no_xrandr: 1817 | #endif 1818 | m = emallocz(sizeof(Monitor)); 1819 | m->sx = m->wax = 0; 1820 | m->sy = m->way = 0; 1821 | m->sw = m->waw = DisplayWidth(dpy, screen); 1822 | m->sh = m->wah = DisplayHeight(dpy, screen); 1823 | m->mx = m->sx + m->sw/2; 1824 | m->my = m->sy + m->sh/2; 1825 | m->curtag = 0; 1826 | m->prevtags = emallocz(ntags * sizeof(Bool)); 1827 | m->seltags = emallocz(ntags * sizeof(Bool)); 1828 | m->seltags[0] = True; 1829 | m->next = NULL; 1830 | monitors = m; 1831 | updateatom[WorkArea] (NULL); 1832 | } 1833 | 1834 | void 1835 | inittags() { 1836 | unsigned int i; 1837 | char tmp[25] = "\0"; 1838 | 1839 | ntags = atoi(getresource("tags.number", "5")); 1840 | views = emallocz(ntags * sizeof(View)); 1841 | tags = emallocz(ntags * sizeof(char *)); 1842 | for (i = 0; i < ntags; i++) { 1843 | tags[i] = emallocz(25); 1844 | snprintf(tmp, sizeof(tmp), "tags.name%d", i); 1845 | snprintf(tags[i], sizeof(tags[i]), "%s", getresource(tmp, 1846 | "null")); 1847 | } 1848 | } 1849 | 1850 | void 1851 | sighandler(int signum) { 1852 | if (signum == SIGHUP) 1853 | quit("HUP!"); 1854 | else 1855 | quit(NULL); 1856 | } 1857 | 1858 | void 1859 | setup(char *conf) { 1860 | int d; 1861 | int i, j; 1862 | unsigned int mask; 1863 | Window w; 1864 | Monitor *m; 1865 | XModifierKeymap *modmap; 1866 | XSetWindowAttributes wa; 1867 | char oldcwd[256], path[256] = "/"; 1868 | char *home, *slash; 1869 | /* configuration files to open (%s gets converted to $HOME) */ 1870 | const char *confs[] = { 1871 | conf, 1872 | "%s/.echinus/echinusrc", 1873 | SYSCONFPATH "/echinusrc", 1874 | NULL 1875 | }; 1876 | 1877 | /* init cursors */ 1878 | cursor[CurNormal] = XCreateFontCursor(dpy, XC_left_ptr); 1879 | cursor[CurResize] = XCreateFontCursor(dpy, XC_bottom_right_corner); 1880 | cursor[CurMove] = XCreateFontCursor(dpy, XC_fleur); 1881 | 1882 | /* init modifier map */ 1883 | modmap = XGetModifierMapping(dpy); 1884 | for (i = 0; i < 8; i++) 1885 | for (j = 0; j < modmap->max_keypermod; j++) { 1886 | if (modmap->modifiermap[i * modmap->max_keypermod + j] 1887 | == XKeysymToKeycode(dpy, XK_Num_Lock)) 1888 | numlockmask = (1 << i); 1889 | } 1890 | XFreeModifiermap(modmap); 1891 | 1892 | /* select for events */ 1893 | wa.event_mask = SubstructureRedirectMask | SubstructureNotifyMask 1894 | | EnterWindowMask | LeaveWindowMask | StructureNotifyMask | 1895 | ButtonPressMask | ButtonReleaseMask; 1896 | wa.cursor = cursor[CurNormal]; 1897 | XChangeWindowAttributes(dpy, root, CWEventMask | CWCursor, &wa); 1898 | XSelectInput(dpy, root, wa.event_mask); 1899 | 1900 | /* init resource database */ 1901 | XrmInitialize(); 1902 | 1903 | home = getenv("HOME"); 1904 | if (!home) 1905 | *home = '/'; 1906 | if (!getcwd(oldcwd, sizeof(oldcwd))) 1907 | eprint("echinus: getcwd error: %s\n", strerror(errno)); 1908 | 1909 | for (i = 0; confs[i] != NULL; i++) { 1910 | if (*confs[i] == '\0') 1911 | continue; 1912 | snprintf(conf, 255, confs[i], home); 1913 | /* retrieve path to chdir(2) to it */ 1914 | slash = strrchr(conf, '/'); 1915 | if (slash) 1916 | snprintf(path, slash - conf + 1, "%s", conf); 1917 | chdir(path); 1918 | xrdb = XrmGetFileDatabase(conf); 1919 | /* configuration file loaded successfully; break out */ 1920 | if (xrdb) 1921 | break; 1922 | } 1923 | if (!xrdb) 1924 | fprintf(stderr, "echinus: no configuration file found, using defaults\n"); 1925 | 1926 | /* init EWMH atom */ 1927 | initewmh(); 1928 | 1929 | /* init tags */ 1930 | inittags(); 1931 | /* init geometry */ 1932 | initmonitors(NULL); 1933 | 1934 | /* init modkey */ 1935 | initrules(); 1936 | initkeys(); 1937 | initlayouts(); 1938 | updateatom[NumberOfDesk] (NULL); 1939 | updateatom[DeskNames] (NULL); 1940 | updateatom[CurDesk] (NULL); 1941 | 1942 | grabkeys(); 1943 | 1944 | /* init appearance */ 1945 | initstyle(); 1946 | options.attachaside = atoi(getresource("attachaside", "1")); 1947 | strncpy(options.command, getresource("command", COMMAND), LENGTH(options.command)); 1948 | options.command[LENGTH(options.command) - 1] = '\0'; 1949 | options.dectiled = atoi(getresource("decoratetiled", STR(DECORATETILED))); 1950 | options.hidebastards = atoi(getresource("hidebastards", "0")); 1951 | options.focus = atoi(getresource("sloppy", "0")); 1952 | options.gap = atoi(getresource("gap", STR(DEFGAP))); 1953 | options.snap = atoi(getresource("snap", STR(SNAP))); 1954 | 1955 | for (m = monitors; m; m = m->next) { 1956 | m->struts[RightStrut] = m->struts[LeftStrut] = 1957 | m->struts[TopStrut] = m->struts[BotStrut] = 0; 1958 | updategeom(m); 1959 | } 1960 | 1961 | chdir(oldcwd); 1962 | 1963 | /* multihead support */ 1964 | selscreen = XQueryPointer(dpy, root, &w, &w, &d, &d, &d, &d, &mask); 1965 | } 1966 | 1967 | void 1968 | spawn(const char *arg) { 1969 | static char shell[] = "/bin/sh"; 1970 | 1971 | if (!arg) 1972 | return; 1973 | /* The double-fork construct avoids zombie processes and keeps the code 1974 | * clean from stupid signal handlers. */ 1975 | if (fork() == 0) { 1976 | if (fork() == 0) { 1977 | if (dpy) 1978 | close(ConnectionNumber(dpy)); 1979 | setsid(); 1980 | execl(shell, shell, "-c", arg, (char *) NULL); 1981 | fprintf(stderr, "echinus: execl '%s -c %s'", shell, arg); 1982 | perror(" failed"); 1983 | } 1984 | exit(0); 1985 | } 1986 | wait(0); 1987 | } 1988 | 1989 | void 1990 | tag(const char *arg) { 1991 | unsigned int i; 1992 | 1993 | if (!sel) 1994 | return; 1995 | for (i = 0; i < ntags; i++) 1996 | sel->tags[i] = (NULL == arg); 1997 | sel->tags[idxoftag(arg)] = True; 1998 | updateatom[WindowDesk] (sel); 1999 | updateframe(sel); 2000 | arrange(NULL); 2001 | focus(NULL); 2002 | } 2003 | 2004 | void 2005 | bstack(Monitor * m) { 2006 | int i, n, nx, ny, nw, nh, mh, tw; 2007 | Client *c, *mc; 2008 | 2009 | for (n = 0, c = nexttiled(clients, m); c; c = nexttiled(c->next, m)) 2010 | n++; 2011 | 2012 | mh = (n == 1) ? m->wah : views[m->curtag].mwfact * m->wah; 2013 | tw = (n > 1) ? m->waw / (n - 1) : 0; 2014 | 2015 | nx = m->wax; 2016 | ny = m->way; 2017 | nh = 0; 2018 | for (i = 0, c = mc = nexttiled(clients, m); c; c = nexttiled(c->next, m), i++) { 2019 | c->ismax = False; 2020 | if (i == 0) { 2021 | nh = mh - 2 * c->border; 2022 | nw = m->waw - 2 * c->border; 2023 | nx = m->wax; 2024 | } else { 2025 | if (i == 1) { 2026 | nx = m->wax; 2027 | ny += mc->h + c->border; 2028 | nh = (m->way + m->wah) - ny - 2 * c->border; 2029 | } 2030 | if (i + 1 == n) 2031 | nw = (m->wax + m->waw) - nx - 2 * c->border; 2032 | else 2033 | nw = tw - c->border; 2034 | } 2035 | resize(c, nx, ny, nw, nh, False); 2036 | if (n > 1 && tw != curwaw) 2037 | nx = c->x + c->w + c->border; 2038 | } 2039 | } 2040 | 2041 | void 2042 | tile(Monitor * m) { 2043 | int nx, ny, nw, nh, mw, mh, g; 2044 | unsigned int i, n, th; 2045 | Client *c, *mc; 2046 | 2047 | for (n = 0, c = nexttiled(clients, m); c; c = nexttiled(c->next, m)) 2048 | n++; 2049 | 2050 | /* window geoms */ 2051 | mh = (n <= views[m->curtag].nmaster) ? m->wah / (n > 2052 | 0 ? n : 1) : m->wah / (views[m->curtag].nmaster ? views[m->curtag].nmaster : 1); 2053 | mw = (n <= views[m->curtag].nmaster) ? m->waw : views[m->curtag].mwfact * m->waw; 2054 | th = (n > views[m->curtag].nmaster) ? m->wah / (n - views[m->curtag].nmaster) : 0; 2055 | if (n > views[m->curtag].nmaster && th < style.titleheight) 2056 | th = m->wah; 2057 | 2058 | nx = m->wax; 2059 | ny = m->way; 2060 | nw = 0; 2061 | g = 0; 2062 | for (i = 0, c = mc = nexttiled(clients, m); c; c = nexttiled(c->next, m), i++) { 2063 | c->ismax = False; 2064 | if (i < views[m->curtag].nmaster) { /* master */ 2065 | ny = m->way + i * (mh - c->border); 2066 | nw = mw - 2 * c->border; 2067 | nh = mh; 2068 | if (i + 1 == (n < views[m->curtag].nmaster ? n : views[m->curtag].nmaster)) /* remainder */ 2069 | nh = m->way + m->wah - ny; 2070 | nh -= 2 * c->border; 2071 | /* gaps */ 2072 | ny = ny + options.gap; 2073 | nx = nx + options.gap; 2074 | nh = nh - options.gap * 2; 2075 | nw = nw - options.gap * 2; 2076 | } else { /* tile window */ 2077 | if (i == views[m->curtag].nmaster) { 2078 | ny = m->way; 2079 | nx += mc->w + mc->border; 2080 | nw = m->waw - nx - 2 * c->border + m->wax; 2081 | } else 2082 | ny -= c->border; 2083 | if (i + 1 == n) /* remainder */ 2084 | nh = (m->way + m->wah) - ny - 2 * c->border; 2085 | else 2086 | nh = th - 2 * c->border; 2087 | /* gaps */ 2088 | if (g == 0) { 2089 | g = 1; 2090 | nx = nx + options.gap; 2091 | nw = nw - options.gap * 2; 2092 | } 2093 | ny = ny + options.gap; 2094 | nh = nh - options.gap * 2; 2095 | } 2096 | resize(c, nx, ny, nw, nh, False); 2097 | if (n > views[m->curtag].nmaster && th != (unsigned int)m->wah) { 2098 | ny = c->y + c->h + 2 * c->border; 2099 | } 2100 | } 2101 | 2102 | } 2103 | 2104 | void 2105 | togglestruts(const char *arg) { 2106 | views[curmontag].barpos = 2107 | (views[curmontag].barpos == 2108 | StrutsOn) ? (options.hidebastards ? StrutsHide : StrutsOff) : StrutsOn; 2109 | updategeom(curmonitor()); 2110 | arrange(curmonitor()); 2111 | } 2112 | 2113 | void 2114 | togglefloating(const char *arg) { 2115 | if (!sel) 2116 | return; 2117 | 2118 | if (FEATURES(curlayout, OVERLAP)) 2119 | return; 2120 | 2121 | sel->isfloating = !sel->isfloating; 2122 | updateframe(sel); 2123 | if (sel->isfloating) { 2124 | /* restore last known float dimensions */ 2125 | resize(sel, sel->rx, sel->ry, sel->rw, sel->rh, False); 2126 | } else { 2127 | /* save last known float dimensions */ 2128 | save(sel); 2129 | } 2130 | arrange(curmonitor()); 2131 | } 2132 | 2133 | void 2134 | togglefill(const char *arg) { 2135 | XEvent ev; 2136 | Monitor *m = curmonitor(); 2137 | Client *c; 2138 | int x1, x2, y1, y2, w, h; 2139 | 2140 | x1 = m->wax; 2141 | x2 = m->sw; 2142 | y1 = m->way; 2143 | y2 = m->sh; 2144 | 2145 | if (!sel || sel->isfixed || !sel->isfloating || MFEATURES(m, OVERLAP)) 2146 | return; 2147 | for (c = clients; c; c = c->next) { 2148 | if (isvisible(c, m) && (c != sel) && !c->isbastard 2149 | && (c->isfloating || MFEATURES(m, OVERLAP))) { 2150 | if (c->y + c->h > sel->y && c->y < sel->y + sel->h) { 2151 | if (c->x < sel->x) 2152 | x1 = max(x1, c->x + c->w + style.border); 2153 | else 2154 | x2 = min(x2, c->x - style.border); 2155 | } 2156 | if (c->x + c->w > sel->x && c->x < sel->x + sel->w) { 2157 | if (c->y < sel->y) 2158 | y1 = max(y1, c->y + c->h + style.border); 2159 | else 2160 | y2 = max(y2, c->y - style.border); 2161 | } 2162 | } 2163 | DPRINTF("x1 = %d x2 = %d y1 = %d y2 = %d\n", x1, x2, y1, y2); 2164 | } 2165 | w = x2 - x1; 2166 | h = y2 - y1; 2167 | DPRINTF("x1 = %d w = %d y1 = %d h = %d\n", x1, w, y1, h); 2168 | if ((w < sel->w) || (h < sel->h)) 2169 | return; 2170 | 2171 | if ((sel->isfill = !sel->isfill)) { 2172 | save(sel); 2173 | resize(sel, x1, y1, w, h, True); 2174 | } else { 2175 | resize(sel, sel->rx, sel->ry, sel->rw, sel->rh, True); 2176 | } 2177 | while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); 2178 | } 2179 | 2180 | void 2181 | togglemax(const char *arg) { 2182 | XEvent ev; 2183 | Monitor *m = curmonitor(); 2184 | 2185 | if (!sel || sel->isfixed || !sel->isfloating || MFEATURES(m, OVERLAP)) 2186 | return; 2187 | sel->ismax = !sel->ismax; 2188 | updateframe(sel); 2189 | if (sel->ismax) { 2190 | save(sel); 2191 | resize(sel, m->sx - sel->border, 2192 | m->sy - sel->border - sel->th, m->sw, m->sh + sel->th, False); 2193 | } else { 2194 | resize(sel, sel->rx, sel->ry, sel->rw, sel->rh, True); 2195 | } 2196 | while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); 2197 | } 2198 | 2199 | void 2200 | toggletag(const char *arg) { 2201 | unsigned int i, j; 2202 | 2203 | if (!sel) 2204 | return; 2205 | i = idxoftag(arg); 2206 | sel->tags[i] = !sel->tags[i]; 2207 | for (j = 0; j < ntags && !sel->tags[j]; j++); 2208 | if (j == ntags) 2209 | sel->tags[i] = True; /* at least one tag must be enabled */ 2210 | drawclient(sel); 2211 | arrange(NULL); 2212 | } 2213 | 2214 | void 2215 | togglemonitor(const char *arg) { 2216 | Monitor *m, *cm; 2217 | int x, y; 2218 | 2219 | getpointer(&x, &y); 2220 | if (!(cm = getmonitor(x, y))) 2221 | return; 2222 | cm->mx = x; 2223 | cm->my = y; 2224 | for (m = monitors; m == cm && m && m->next; m = m->next); 2225 | if (!m) 2226 | return; 2227 | XWarpPointer(dpy, None, root, 0, 0, 0, 0, m->mx, m->my); 2228 | focus(NULL); 2229 | } 2230 | 2231 | void 2232 | toggleview(const char *arg) { 2233 | unsigned int i, j; 2234 | Monitor *m, *cm; 2235 | 2236 | i = idxoftag(arg); 2237 | cm = curmonitor(); 2238 | 2239 | memcpy(cm->prevtags, cm->seltags, ntags * sizeof(cm->seltags[0])); 2240 | cm->seltags[i] = !cm->seltags[i]; 2241 | for (m = monitors; m; m = m->next) { 2242 | if (m->seltags[i] && m != cm) { 2243 | memcpy(m->prevtags, m->seltags, ntags * sizeof(m->seltags[0])); 2244 | m->seltags[i] = False; 2245 | for (j = 0; j < ntags && !m->seltags[j]; j++); 2246 | if (j == ntags) { 2247 | m->seltags[i] = True; /* at least one tag must be viewed */ 2248 | cm->seltags[i] = False; /* can't toggle */ 2249 | j = i; 2250 | } 2251 | if (m->curtag == i) 2252 | m->curtag = j; 2253 | arrange(m); 2254 | } 2255 | } 2256 | arrange(cm); 2257 | focus(NULL); 2258 | updateatom[CurDesk] (NULL); 2259 | } 2260 | 2261 | void 2262 | focusview(const char *arg) { 2263 | Client *c; 2264 | int i; 2265 | 2266 | toggleview(arg); 2267 | i = idxoftag(arg); 2268 | if (!curseltags[i]) 2269 | return; 2270 | for (c = stack; c; c = c->snext) { 2271 | if (c->tags[i] && !c->isbastard) { 2272 | focus(c); 2273 | break; 2274 | } 2275 | } 2276 | restack(curmonitor()); 2277 | } 2278 | 2279 | void 2280 | unban(Client * c) { 2281 | if (!c->isbanned) 2282 | return; 2283 | XSelectInput(dpy, c->win, CLIENTMASK & ~(StructureNotifyMask | EnterWindowMask)); 2284 | XSelectInput(dpy, c->frame, NoEventMask); 2285 | XMapWindow(dpy, c->win); 2286 | XMapWindow(dpy, c->frame); 2287 | XSelectInput(dpy, c->win, CLIENTMASK); 2288 | XSelectInput(dpy, c->frame, FRAMEMASK); 2289 | setclientstate(c, NormalState); 2290 | c->isbanned = False; 2291 | } 2292 | 2293 | void 2294 | unmanage(Client * c) { 2295 | Monitor *m; 2296 | XWindowChanges wc; 2297 | Bool doarrange, dostruts; 2298 | Window trans; 2299 | 2300 | m = clientmonitor(c); 2301 | doarrange = !(c->isfloating || c->isfixed 2302 | || XGetTransientForHint(dpy, c->win, &trans)) || c->isbastard; 2303 | dostruts = c->hasstruts; 2304 | /* The server grab construct avoids race conditions. */ 2305 | XGrabServer(dpy); 2306 | XSelectInput(dpy, c->frame, NoEventMask); 2307 | XUnmapWindow(dpy, c->frame); 2308 | XSetErrorHandler(xerrordummy); 2309 | if (c->title) { 2310 | XftDrawDestroy(c->xftdraw); 2311 | XFreePixmap(dpy, c->drawable); 2312 | XDestroyWindow(dpy, c->title); 2313 | c->title = (Window) NULL; 2314 | } 2315 | XSelectInput(dpy, c->win, CLIENTMASK & ~(StructureNotifyMask | EnterWindowMask)); 2316 | XUngrabButton(dpy, AnyButton, AnyModifier, c->win); 2317 | XReparentWindow(dpy, c->win, root, c->x, c->y); 2318 | XMoveWindow(dpy, c->win, c->x, c->y); 2319 | if (!running) 2320 | XMapWindow(dpy, c->win); 2321 | wc.border_width = c->oldborder; 2322 | XConfigureWindow(dpy, c->win, CWBorderWidth, &wc); /* restore border */ 2323 | detach(c); 2324 | detachstack(c); 2325 | if (sel == c) 2326 | focus(NULL); 2327 | setclientstate(c, WithdrawnState); 2328 | XDestroyWindow(dpy, c->frame); 2329 | /* c->tags points to monitor */ 2330 | if (!c->isbastard) 2331 | free(c->tags); 2332 | free(c); 2333 | XSync(dpy, False); 2334 | XSetErrorHandler(xerror); 2335 | XUngrabServer(dpy); 2336 | if (dostruts) { 2337 | updatestruts(m); 2338 | updategeom(m); 2339 | } 2340 | if (doarrange) 2341 | arrange(m); 2342 | updateatom[ClientList] (NULL); 2343 | } 2344 | 2345 | void 2346 | updategeom(Monitor * m) { 2347 | m->wax = m->sx; 2348 | m->way = m->sy; 2349 | m->waw = m->sw; 2350 | m->wah = m->sh; 2351 | switch (views[m->curtag].barpos) { 2352 | default: 2353 | m->wax += m->struts[LeftStrut]; 2354 | m->way += m->struts[TopStrut]; 2355 | m->waw -= (m->struts[RightStrut] + m->struts[LeftStrut]); 2356 | m->wah = min(m->wah - m->struts[TopStrut], 2357 | (DisplayHeight(dpy, screen) - (m->struts[BotStrut] + m->struts[TopStrut]))); 2358 | break; 2359 | case StrutsHide: 2360 | case StrutsOff: 2361 | break; 2362 | } 2363 | updateatom[WorkArea] (NULL); 2364 | } 2365 | 2366 | void 2367 | updatestruts(Monitor *m) { 2368 | Client *c; 2369 | 2370 | m->struts[RightStrut] = m->struts[LeftStrut] = m->struts[TopStrut] = 2371 | m->struts[BotStrut] = 0; 2372 | for (c = clients; c; c = c->next) 2373 | if (c->hasstruts) 2374 | getstruts(c); 2375 | } 2376 | 2377 | void 2378 | unmapnotify(XEvent * e) { 2379 | Client *c; 2380 | XUnmapEvent *ev = &e->xunmap; 2381 | 2382 | if ((c = getclient(ev->window, clients, ClientWindow)) /* && ev->send_event */) { 2383 | if (c->ignoreunmap--) 2384 | return; 2385 | DPRINTF("killing self-unmapped window (%s)\n", c->name); 2386 | unmanage(c); 2387 | } 2388 | } 2389 | 2390 | void 2391 | updateframe(Client * c) { 2392 | int i, f = 0; 2393 | 2394 | if (!c->title) 2395 | return; 2396 | 2397 | for (i = 0; i < ntags; i++) { 2398 | if (c->tags[i]) 2399 | f += FEATURES(views[i].layout, OVERLAP); 2400 | } 2401 | c->th = !c->ismax && (c->isfloating || options.dectiled || f) ? 2402 | style.titleheight : 0; 2403 | if (!c->th) 2404 | XUnmapWindow(dpy, c->title); 2405 | else 2406 | XMapRaised(dpy, c->title); 2407 | } 2408 | 2409 | void 2410 | updatesizehints(Client * c) { 2411 | long msize; 2412 | XSizeHints size; 2413 | 2414 | if (!XGetWMNormalHints(dpy, c->win, &size, &msize) || !size.flags) 2415 | size.flags = PSize; 2416 | c->flags = size.flags; 2417 | if (c->flags & PBaseSize) { 2418 | c->basew = size.base_width; 2419 | c->baseh = size.base_height; 2420 | } else if (c->flags & PMinSize) { 2421 | c->basew = size.min_width; 2422 | c->baseh = size.min_height; 2423 | } else 2424 | c->basew = c->baseh = 0; 2425 | if (c->flags & PResizeInc) { 2426 | c->incw = size.width_inc; 2427 | c->inch = size.height_inc; 2428 | } else 2429 | c->incw = c->inch = 0; 2430 | if (c->flags & PMaxSize) { 2431 | c->maxw = size.max_width; 2432 | c->maxh = size.max_height; 2433 | } else 2434 | c->maxw = c->maxh = 0; 2435 | if (c->flags & PMinSize) { 2436 | c->minw = size.min_width; 2437 | c->minh = size.min_height; 2438 | } else if (c->flags & PBaseSize) { 2439 | c->minw = size.base_width; 2440 | c->minh = size.base_height; 2441 | } else 2442 | c->minw = c->minh = 0; 2443 | if (c->flags & PAspect) { 2444 | c->minax = size.min_aspect.x; 2445 | c->maxax = size.max_aspect.x; 2446 | c->minay = size.min_aspect.y; 2447 | c->maxay = size.max_aspect.y; 2448 | } else 2449 | c->minax = c->maxax = c->minay = c->maxay = 0; 2450 | c->isfixed = (c->maxw && c->minw && c->maxh && c->minh 2451 | && c->maxw == c->minw && c->maxh == c->minh); 2452 | } 2453 | 2454 | void 2455 | updatetitle(Client * c) { 2456 | if (!gettextprop(c->win, atom[WindowName], c->name, sizeof(c->name))) 2457 | gettextprop(c->win, atom[WMName], c->name, sizeof(c->name)); 2458 | } 2459 | 2460 | /* There's no way to check accesses to destroyed windows, thus those cases are 2461 | * ignored (ebastardly on UnmapNotify's). Other types of errors call Xlibs 2462 | * default error handler, which may call exit. */ 2463 | int 2464 | xerror(Display * dsply, XErrorEvent * ee) { 2465 | if (ee->error_code == BadWindow 2466 | || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) 2467 | || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable) 2468 | || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable) 2469 | || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable) 2470 | || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) 2471 | || (ee->request_code == X_GrabKey && ee->error_code == BadAccess) 2472 | || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) 2473 | return 0; 2474 | fprintf(stderr, 2475 | "echinus: fatal error: request code=%d, error code=%d\n", 2476 | ee->request_code, ee->error_code); 2477 | return xerrorxlib(dsply, ee); /* may call exit */ 2478 | } 2479 | 2480 | int 2481 | xerrordummy(Display * dsply, XErrorEvent * ee) { 2482 | return 0; 2483 | } 2484 | 2485 | /* Startup Error handler to check if another window manager 2486 | * is already running. */ 2487 | int 2488 | xerrorstart(Display * dsply, XErrorEvent * ee) { 2489 | otherwm = True; 2490 | return -1; 2491 | } 2492 | 2493 | void 2494 | view(const char *arg) { 2495 | int i, j; 2496 | Monitor *m, *cm; 2497 | int prevtag; 2498 | 2499 | i = idxoftag(arg); 2500 | cm = curmonitor(); 2501 | 2502 | if (cm->seltags[i]) 2503 | return; 2504 | 2505 | memcpy(cm->prevtags, cm->seltags, ntags * sizeof(cm->seltags[0])); 2506 | 2507 | for (j = 0; j < ntags; j++) 2508 | cm->seltags[j] = (arg == NULL); 2509 | cm->seltags[i] = True; 2510 | prevtag = cm->curtag; 2511 | cm->curtag = i; 2512 | for (m = monitors; m; m = m->next) { 2513 | if (m->seltags[i] && m != cm) { 2514 | m->curtag = prevtag; 2515 | memcpy(m->prevtags, m->seltags, ntags * sizeof(m->seltags[0])); 2516 | memcpy(m->seltags, cm->prevtags, ntags * sizeof(cm->seltags[0])); 2517 | updategeom(m); 2518 | arrange(m); 2519 | } 2520 | } 2521 | updategeom(cm); 2522 | arrange(cm); 2523 | focus(NULL); 2524 | updateatom[CurDesk] (NULL); 2525 | } 2526 | 2527 | void 2528 | viewprevtag(const char *arg) { 2529 | Bool tmptags[ntags]; 2530 | unsigned int i = 0; 2531 | int prevcurtag; 2532 | 2533 | while (i < ntags - 1 && !curprevtags[i]) 2534 | i++; 2535 | prevcurtag = curmontag; 2536 | curmontag = i; 2537 | 2538 | memcpy(tmptags, curseltags, ntags * sizeof(curseltags[0])); 2539 | memcpy(curseltags, curprevtags, ntags * sizeof(curseltags[0])); 2540 | memcpy(curprevtags, tmptags, ntags * sizeof(curseltags[0])); 2541 | if (views[prevcurtag].barpos != views[curmontag].barpos) 2542 | updategeom(curmonitor()); 2543 | arrange(NULL); 2544 | focus(NULL); 2545 | updateatom[CurDesk] (NULL); 2546 | } 2547 | 2548 | void 2549 | viewlefttag(const char *arg) { 2550 | unsigned int i; 2551 | 2552 | for (i = 0; i < ntags; i++) { 2553 | if (i && curseltags[i]) { 2554 | view(tags[i - 1]); 2555 | break; 2556 | } 2557 | } 2558 | } 2559 | 2560 | void 2561 | viewrighttag(const char *arg) { 2562 | unsigned int i; 2563 | 2564 | for (i = 0; i < ntags - 1; i++) { 2565 | if (curseltags[i]) { 2566 | view(tags[i + 1]); 2567 | break; 2568 | } 2569 | } 2570 | } 2571 | 2572 | void 2573 | zoom(const char *arg) { 2574 | Client *c; 2575 | 2576 | if (!sel || !FEATURES(curlayout, ZOOM) || sel->isfloating) 2577 | return; 2578 | if ((c = sel) == nexttiled(clients, curmonitor())) 2579 | if (!(c = nexttiled(c->next, curmonitor()))) 2580 | return; 2581 | detach(c); 2582 | attach(c, 0); 2583 | arrange(curmonitor()); 2584 | focus(c); 2585 | } 2586 | 2587 | int 2588 | main(int argc, char *argv[]) { 2589 | char conf[256] = "\0"; 2590 | 2591 | if (argc == 3 && !strcmp("-f", argv[1])) 2592 | snprintf(conf, sizeof(conf), "%s", argv[2]); 2593 | else if (argc == 2 && !strcmp("-v", argv[1])) 2594 | eprint("echinus-" VERSION " (c) 2011 Alexander Polakov\n"); 2595 | else if (argc != 1) 2596 | eprint("usage: echinus [-v] [-f conf]\n"); 2597 | 2598 | setlocale(LC_CTYPE, ""); 2599 | if (!(dpy = XOpenDisplay(0))) 2600 | eprint("echinus: cannot open display\n"); 2601 | signal(SIGHUP, sighandler); 2602 | signal(SIGINT, sighandler); 2603 | signal(SIGQUIT, sighandler); 2604 | cargv = argv; 2605 | screen = DefaultScreen(dpy); 2606 | root = RootWindow(dpy, screen); 2607 | 2608 | checkotherwm(); 2609 | setup(conf); 2610 | scan(); 2611 | run(); 2612 | cleanup(); 2613 | 2614 | XCloseDisplay(dpy); 2615 | return 0; 2616 | } 2617 | -------------------------------------------------------------------------------- /echinus.h: -------------------------------------------------------------------------------- 1 | /* enums */ 2 | 3 | enum { ClientList, ActiveWindow, WindowDesk, 4 | NumberOfDesk, DeskNames, CurDesk, ELayout, WorkArea, 5 | ClientListStacking, WindowOpacity, WindowType, 6 | WindowTypeDesk, WindowTypeDock, WindowTypeDialog, StrutPartial, 7 | Strut, ESelTags, 8 | WindowName, WindowState, WindowStateFs, WindowStateModal, 9 | WindowStateHidden, WMCheck, CloseWindow, 10 | Utf8String, Supported, WMProto, WMDelete, WMName, WMState, WMChangeState, 11 | WMTakeFocus, MWMHints, NATOMS 12 | }; /* keep in sync with atomnames[][] in ewmh.c */ 13 | 14 | enum { LeftStrut, RightStrut, TopStrut, BotStrut, LastStrut }; /* ewmh struts */ 15 | enum { ColFG, ColBG, ColBorder, ColButton, ColLast }; /* colors */ 16 | enum { ClientWindow, ClientTitle, ClientFrame }; /* client parts */ 17 | enum { Iconify, Maximize, Close, LastBtn }; /* window buttons */ 18 | 19 | /* typedefs */ 20 | typedef struct Monitor Monitor; 21 | struct Monitor { 22 | int sx, sy, sw, sh, wax, way, waw, wah; 23 | unsigned long struts[LastStrut]; 24 | Bool *seltags; 25 | Bool *prevtags; 26 | Monitor *next; 27 | int mx, my; 28 | unsigned int curtag; 29 | }; 30 | 31 | typedef struct { 32 | void (*arrange) (Monitor * m); 33 | char symbol; 34 | int features; 35 | #define BIT(_i) (1 << (_i)) 36 | #define MWFACT BIT(0) 37 | #define NMASTER BIT(1) 38 | #define ZOOM BIT(2) 39 | #define OVERLAP BIT(3) 40 | } Layout; 41 | 42 | #define FEATURES(_layout, _which) (!(!((_layout)->features & (_which)))) 43 | #define M2LT(_mon) (views[(_mon)->curtag].layout) 44 | #define MFEATURES(_monitor, _which) ((_monitor) && FEATURES(M2LT(_monitor), (_which))) 45 | 46 | typedef struct Client Client; 47 | struct Client { 48 | char name[256]; 49 | int x, y, w, h; 50 | int rx, ry, rw, rh; /* revert geometry */ 51 | int th; /* title height */ 52 | int basew, baseh, incw, inch, maxw, maxh, minw, minh; 53 | int minax, maxax, minay, maxay; 54 | int ignoreunmap; 55 | long flags; 56 | int border, oldborder; 57 | Bool isbanned, ismax, isfloating, wasfloating; 58 | Bool isicon, isfill; 59 | Bool isfixed, isbastard, isfocusable, hasstruts; 60 | Bool *tags; 61 | Client *next; 62 | Client *prev; 63 | Client *snext; 64 | Window win; 65 | Window title; 66 | Window frame; 67 | Pixmap drawable; 68 | XftDraw *xftdraw; 69 | }; 70 | 71 | typedef struct View { 72 | int barpos; 73 | int nmaster; 74 | double mwfact; 75 | Layout *layout; 76 | } View; /* per-tag settings */ 77 | 78 | typedef struct { 79 | Pixmap pm; 80 | int px, py; 81 | unsigned int pw, ph; 82 | int x; 83 | int pressed; 84 | void (*action) (const char *arg); 85 | } Button; /* window buttons */ 86 | 87 | typedef struct { 88 | unsigned int border; 89 | unsigned int outline; 90 | unsigned int titleheight; 91 | unsigned int opacity; 92 | char titlelayout[32]; 93 | struct { 94 | unsigned long norm[ColLast]; 95 | unsigned long sel[ColLast]; 96 | XftColor *font[2]; 97 | } color; 98 | XftFont *font; 99 | } Style; 100 | 101 | typedef struct { 102 | unsigned long mod; 103 | KeySym keysym; 104 | void (*func) (const char *arg); 105 | const char *arg; 106 | } Key; /* keyboard shortcuts */ 107 | 108 | typedef struct { 109 | char *prop; 110 | char *tags; 111 | Bool isfloating; 112 | Bool hastitle; 113 | regex_t *propregex; 114 | regex_t *tagregex; 115 | } Rule; /* window matching rules */ 116 | 117 | /* ewmh.c */ 118 | Bool checkatom(Window win, Atom bigatom, Atom smallatom); 119 | void clientmessage(XEvent * e); 120 | void ewmh_process_state_atom(Client * c, Atom state, int set); 121 | void *getatom(Window win, Atom atom, unsigned long *nitems); 122 | void initewmh(void); 123 | void mwm_process_atom(Client * c); 124 | void setopacity(Client * c, unsigned int opacity); 125 | extern void (*updateatom[]) (void *); 126 | int getstruts(Client * c); 127 | 128 | /* main */ 129 | void arrange(Monitor * m); 130 | Monitor *clientmonitor(Client * c); 131 | Monitor *curmonitor(); 132 | void *emallocz(unsigned int size); 133 | void eprint(const char *errstr, ...); 134 | const char *getresource(const char *resource, const char *defval); 135 | Client *getclient(Window w, Client * list, int part); 136 | Monitor *getmonitor(int x, int y); 137 | void iconify(const char *arg); 138 | void incnmaster(const char *arg); 139 | Bool isvisible(Client * c, Monitor * m); 140 | void focus(Client * c); 141 | void focusicon(const char *arg); 142 | void focusnext(const char *arg); 143 | void focusprev(const char *arg); 144 | void focusview(const char *arg); 145 | void killclient(const char *arg); 146 | void moveresizekb(const char *arg); 147 | void quit(const char *arg); 148 | void restart(const char *arg); 149 | void setmwfact(const char *arg); 150 | void setlayout(const char *arg); 151 | void spawn(const char *arg); 152 | void tag(const char *arg); 153 | void togglestruts(const char *arg); 154 | void togglefloating(const char *arg); 155 | void togglefill(const char *arg); 156 | void togglemax(const char *arg); 157 | void togglemonitor(const char *arg); 158 | void toggletag(const char *arg); 159 | void toggleview(const char *arg); 160 | void updateframe(Client *c); 161 | void view(const char *arg); 162 | void viewlefttag(const char *arg); 163 | void viewprevtag(const char *arg); 164 | void viewrighttag(const char *arg); 165 | void zoom(const char *arg); 166 | 167 | /* parse.c */ 168 | void initrules(); 169 | int initkeys(); 170 | 171 | /* draw.c */ 172 | void drawclient(Client * c); 173 | void deinitstyle(); 174 | void initstyle(); 175 | 176 | /* XXX: this block of defines must die */ 177 | #define curseltags curmonitor()->seltags 178 | #define curprevtags curmonitor()->prevtags 179 | #define cursx curmonitor()->sx 180 | #define cursy curmonitor()->sy 181 | #define cursh curmonitor()->sh 182 | #define cursw curmonitor()->sw 183 | #define curwax curmonitor()->wax 184 | #define curway curmonitor()->way 185 | #define curwaw curmonitor()->waw 186 | #define curwah curmonitor()->wah 187 | #define curmontag curmonitor()->curtag 188 | #define curstruts curmonitor()->struts 189 | #define curlayout views[curmontag].layout 190 | 191 | #define LENGTH(x) (sizeof(x) / sizeof x[0]) 192 | #ifdef DEBUG 193 | #define DPRINT fprintf(stderr, "%s: %s() %d\n",__FILE__,__func__, __LINE__); 194 | #define DPRINTF(format, ...) fprintf(stderr, "%s %s():%d " format, __FILE__, __func__, __LINE__, __VA_ARGS__) 195 | #else 196 | #define DPRINT ; 197 | #define DPRINTF(format, ...) 198 | #endif 199 | #define DPRINTCLIENT(c) DPRINTF("%s: x: %d y: %d w: %d h: %d th: %d f: %d b: %d m: %d\n", \ 200 | c->name, c->x, c->y, c->w, c->h, c->th, c->isfloating, c->isbastard, c->ismax) 201 | 202 | #define OPAQUE 0xffffffff 203 | #define RESNAME "echinus" 204 | #define RESCLASS "Echinus" 205 | #define STR(_s) TOSTR(_s) 206 | #define TOSTR(_s) #_s 207 | #define min(_a, _b) ((_a) < (_b) ? (_a) : (_b)) 208 | #define max(_a, _b) ((_a) > (_b) ? (_a) : (_b)) 209 | 210 | /* globals */ 211 | extern Atom atom[NATOMS]; 212 | extern Display *dpy; 213 | extern Window root; 214 | extern Client *clients; 215 | extern Monitor *monitors; 216 | extern Client *sel; 217 | extern Client *stack; 218 | extern unsigned int ntags; 219 | extern unsigned int nkeys; 220 | extern unsigned int nrules; 221 | extern int screen; 222 | extern Style style; 223 | extern Button button[LastBtn]; 224 | extern char **tags; 225 | extern Key **keys; 226 | extern Rule **rules; 227 | extern Layout layouts[]; 228 | extern unsigned int modkey; 229 | extern View *views; 230 | -------------------------------------------------------------------------------- /echinusrc: -------------------------------------------------------------------------------- 1 | Echinus*selected.border: #262626 2 | Echinus*selected.button: #d3d7cf 3 | Echinus*selected.bg: #262626 4 | Echinus*selected.fg: #d3d7cf 5 | 6 | Echinus*normal.border: #262626 7 | Echinus*normal.button: #262626 8 | Echinus*normal.bg: #262626 9 | Echinus*normal.fg: #b0b4ac 10 | 11 | Echinus*border: 1 12 | 13 | Echinus*button.iconify.pixmap: iconify.xbm 14 | Echinus*button.maximize.pixmap: max.xbm 15 | Echinus*button.close.pixmap: close.xbm 16 | 17 | Echinus*attachaside: 0 18 | Echinus*sloppy: 0 19 | Echinus*opacity: 0.8 20 | Echinus*decoratetiled: 0 21 | Echinus*hidebastards: 0 22 | Echinus*mwfact: 0.6 23 | Echinus*nmaster: 1 24 | 25 | Echinus*font: fixed-9 26 | Echinus*modkey: A 27 | 28 | Echinus*deflayout: i 29 | 30 | Echinus*gap: 16 31 | 32 | Echinus*tags.number: 7 33 | Echinus*tags.name0: main 34 | Echinus*tags.name1: web 35 | Echinus*tags.name2: doc 36 | Echinus*tags.name3: dev 37 | Echinus*tags.name4: scr 38 | Echinus*tags.name5: gfx 39 | Echinus*tags.name6: misc 40 | 41 | Echinus*tags.layout1: m 42 | Echinus*tags.layout3: b 43 | 44 | Echinus*togglestruts: A + b 45 | Echinus*togglemonitor: A + grave 46 | Echinus*focusnext: A + j 47 | Echinus*focusprev: A + k 48 | Echinus*viewprevtag: A + Tab 49 | Echinus*viewlefttag: AS + Left 50 | Echinus*viewrighttag: AS + Right 51 | Echinus*quit: CA + q 52 | Echinus*restart: AS + q = echinus 53 | Echinus*killclient: AS + c 54 | Echinus*togglefloating: A + space 55 | Echinus*zoom: A + Return 56 | 57 | Echinus*view0: A + F1 58 | Echinus*view1: A + F2 59 | Echinus*view2: A + F3 60 | Echinus*view3: A + F4 61 | Echinus*view4: A + F5 62 | Echinus*view5: A + F6 63 | 64 | Echinus*tag0: AS + 1 65 | Echinus*tag1: AS + 2 66 | Echinus*tag2: AS + 3 67 | Echinus*tag3: AS + 4 68 | Echinus*tag4: AS + 5 69 | Echinus*tag5: AS + 6 70 | 71 | Echinus*toggletag0: CAS + 1 72 | Echinus*toggletag1: CAS + 2 73 | Echinus*toggletag2: CAS + 3 74 | Echinus*toggletag3: CAS + 4 75 | Echinus*toggletag4: CAS + 5 76 | Echinus*toggletag5: CAS + 6 77 | 78 | Echinus*toggleview0: CA + 1 79 | Echinus*toggleview1: CA + 1 80 | Echinus*toggleview2: CA + 1 81 | Echinus*toggleview3: CA + 1 82 | Echinus*toggleview4: CA + 1 83 | Echinus*toggleview5: CA + 1 84 | 85 | Echinus*focusview6: A + s 86 | 87 | Echinus*setlayoutm: A + m 88 | Echinus*setlayoutf: A + f 89 | Echinus*setlayouti: A + i 90 | Echinus*setlayoutt: A + r 91 | Echinus*setlayoutb: A + w 92 | 93 | Echinus*moveright: A + d = 5 94 | Echinus*moveleft: A + a = -5 95 | Echinus*moveup: A + w = 0 -5 96 | Echinus*movedown: A + s = 0 5 97 | Echinus*resizedecx: AS + a = 0 0 -5 0 98 | Echinus*resizedecy: AS + s = 0 0 0 -5 99 | Echinus*resizeincx: AS + d = 0 0 5 0 100 | Echinus*resizeincy: AS + w = 0 0 0 5 101 | 102 | Echinus*spawn0: A + t = xterm 103 | 104 | Echinus*decmwfact: A + h = -0.05 105 | Echinus*incmwfact: A + l = +0.05 106 | Echinus*decnmaster: AS + j = -1 107 | Echinus*incnmaster: AS + k = +1 108 | 109 | Echinus*command: xterm 110 | 111 | Echinus*rule0: Firefox.* web 0 1 112 | Echinus*rule4: Mplayer.* NULL 1 1 113 | Echinus*rule5: Gimp.* gfx 1 1 114 | -------------------------------------------------------------------------------- /ewmh.c: -------------------------------------------------------------------------------- 1 | /* 2 | * EWMH atom support. initial implementation borrowed from 3 | * awesome wm, then partially reworked. 4 | * 5 | * Copyright © 2007-2008 Julien Danjou 6 | * Copyright © 2008 Alexander Polakov 7 | * 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "echinus.h" 17 | #include "config.h" 18 | 19 | Atom atom[NATOMS]; 20 | 21 | /* keep in sync with enum in echinus.h */ 22 | const char *atomnames[NATOMS][1] = { 23 | { "_NET_CLIENT_LIST" }, 24 | { "_NET_ACTIVE_WINDOW" }, 25 | { "_NET_WM_DESKTOP" }, 26 | { "_NET_NUMBER_OF_DESKTOPS" }, 27 | { "_NET_DESKTOP_NAMES" }, 28 | { "_NET_CURRENT_DESKTOP" }, 29 | { "_ECHINUS_LAYOUT" }, 30 | { "_NET_WORKAREA" }, 31 | { "_NET_CLIENT_LIST_STACKING" }, 32 | { "_NET_WM_WINDOW_OPACITY" }, 33 | { "_NET_WM_WINDOW_TYPE" }, 34 | { "_NET_WM_WINDOW_TYPE_DESKTOP" }, 35 | { "_NET_WM_WINDOW_TYPE_DOCK" }, 36 | { "_NET_WM_WINDOW_TYPE_DIALOG" }, 37 | { "_NET_WM_STRUT_PARTIAL" }, 38 | { "_NET_WM_STRUT" }, 39 | { "_ECHINUS_SELTAGS" }, 40 | { "_NET_WM_NAME" }, 41 | { "_NET_WM_STATE" }, 42 | { "_NET_WM_STATE_FULLSCREEN" }, 43 | { "_NET_WM_STATE_MODAL" }, 44 | { "_NET_WM_STATE_HIDDEN" }, 45 | { "_NET_SUPPORTING_WM_CHECK" }, 46 | { "_NET_CLOSE_WINDOW" }, 47 | { "UTF8_STRING" }, 48 | { "_NET_SUPPORTED" }, 49 | { "WM_PROTOCOLS" }, 50 | { "WM_DELETE_WINDOW" }, 51 | { "WM_NAME" }, 52 | { "WM_STATE" }, 53 | { "WM_CHANGE_STATE" }, 54 | { "WM_TAKE_FOCUS" }, 55 | { "_MOTIF_WM_HINTS" }, 56 | }; 57 | 58 | #define _NET_WM_STATE_REMOVE 0 59 | #define _NET_WM_STATE_ADD 1 60 | #define _NET_WM_STATE_TOGGLE 2 61 | 62 | void 63 | initewmh(void) { 64 | int i; 65 | char name[] = "echinus"; 66 | XSetWindowAttributes wa; 67 | Window win; 68 | 69 | for (i = 0; i < NATOMS; i++) 70 | atom[i] = XInternAtom(dpy, atomnames[i][0], False); 71 | XChangeProperty(dpy, root, 72 | atom[Supported], XA_ATOM, 32, 73 | PropModeReplace, (unsigned char *) atom, NATOMS); 74 | 75 | wa.override_redirect = True; 76 | win = XCreateWindow(dpy, root, -100, 0, 1, 1, 77 | 0, DefaultDepth(dpy, screen), CopyFromParent, 78 | DefaultVisual(dpy, screen), CWOverrideRedirect, &wa); 79 | XChangeProperty(dpy, win, atom[WindowName], atom[Utf8String], 8, 80 | PropModeReplace, (unsigned char*)name, strlen(name)); 81 | XChangeProperty(dpy, root, atom[WMCheck], XA_WINDOW, 32, 82 | PropModeReplace, (unsigned char*)&win, 1); 83 | } 84 | 85 | void 86 | update_echinus_layout_name(void *p) { 87 | XChangeProperty(dpy, root, atom[ELayout], 88 | XA_STRING, 8, PropModeReplace, 89 | (const unsigned char *) &views[curmontag].layout->symbol, 1L); 90 | } 91 | 92 | void 93 | ewmh_update_net_client_list(void *p) { 94 | Window *wins = NULL; 95 | Client *c; 96 | int i, n = 0; 97 | 98 | for (c = stack; c; c = c->snext) 99 | n++; 100 | if (!n) { 101 | XChangeProperty(dpy, root, atom[ClientList], XA_WINDOW, 32, 102 | PropModeReplace, (unsigned char *) wins, n); 103 | XChangeProperty(dpy, root, atom[ClientListStacking], XA_WINDOW, 104 | 32, PropModeReplace, (unsigned char *) wins, n); 105 | return; 106 | } 107 | wins = malloc(sizeof(Window) * n); 108 | for (i = 0, c = stack; c; c = c->snext) 109 | wins[i++] = c->win; 110 | XChangeProperty(dpy, root, 111 | atom[ClientListStacking], XA_WINDOW, 32, PropModeReplace, 112 | (unsigned char *) wins, n); 113 | for (i = 0, c = clients; c; c = c->next) 114 | wins[i++] = c->win; 115 | XChangeProperty(dpy, root, 116 | atom[ClientList], XA_WINDOW, 32, PropModeReplace, (unsigned char *) wins, n); 117 | free(wins); 118 | XFlush(dpy); 119 | } 120 | 121 | void 122 | ewmh_update_net_number_of_desktops(void *p) { 123 | XChangeProperty(dpy, root, 124 | atom[NumberOfDesk], XA_CARDINAL, 32, PropModeReplace, 125 | (unsigned char *) &ntags, 1); 126 | } 127 | 128 | void 129 | ewmh_update_net_current_desktop(void *p) { 130 | Monitor *m; 131 | unsigned long *seltags; 132 | unsigned int i; 133 | 134 | seltags = emallocz(ntags * sizeof(unsigned long)); 135 | for (m = monitors; m != NULL; m = m->next) { 136 | for (i = 0; i < ntags; i++) 137 | seltags[i] |= m->seltags[i]; 138 | } 139 | XChangeProperty(dpy, root, 140 | atom[ESelTags], XA_CARDINAL, 32, PropModeReplace, 141 | (unsigned char *) seltags, ntags); 142 | XChangeProperty(dpy, root, atom[CurDesk], XA_CARDINAL, 32, 143 | PropModeReplace, (unsigned char *) &curmontag, 1); 144 | update_echinus_layout_name(NULL); 145 | free(seltags); 146 | } 147 | 148 | void 149 | ewmh_update_net_window_desktop(void *p) { 150 | unsigned long i; 151 | Client *c = (Client *)p; 152 | 153 | for (i = 0; i < ntags && !c->tags[i]; i++); 154 | XChangeProperty(dpy, c->win, 155 | atom[WindowDesk], XA_CARDINAL, 32, PropModeReplace, (unsigned char *) &i, 1); 156 | } 157 | 158 | void 159 | ewmh_update_net_work_area(void *p) { 160 | unsigned long *geoms; 161 | Monitor *m = monitors; 162 | int i, x, y, w, h; 163 | 164 | geoms = malloc(sizeof(unsigned long)*4*ntags); 165 | x = m->wax - m->sx; 166 | y = m->way - m->sy; 167 | w = m->waw; 168 | h = m->wah; 169 | for (m = m->next; m != NULL; m = m->next) { 170 | x = max(x, m->wax - m->sx); 171 | y = max(y, m->way - m->sy); 172 | w = min(w, m->waw); 173 | h = min(h, m->wah); 174 | } 175 | for (i = 0; i < ntags; i++) { 176 | geoms[i*4] = x; 177 | geoms[i*4+1] = y; 178 | geoms[i*4+2] = w; 179 | geoms[i*4+3] = h; 180 | } 181 | XChangeProperty(dpy, root, 182 | atom[WorkArea], XA_CARDINAL, 32, PropModeReplace, (unsigned char *) geoms, ntags*4); 183 | free(geoms); 184 | } 185 | 186 | void 187 | ewmh_update_net_desktop_names(void *p) { 188 | char buf[1024], *pos; 189 | unsigned int i; 190 | int len = 0; 191 | 192 | pos = buf; 193 | for (i = 0; i < ntags; i++) { 194 | snprintf(pos, strlen(tags[i]) + 1, "%s", tags[i]); 195 | pos += (strlen(tags[i]) + 1); 196 | } 197 | len = pos - buf; 198 | 199 | XChangeProperty(dpy, root, 200 | atom[DeskNames], atom[Utf8String], 8, PropModeReplace, 201 | (unsigned char *) buf, len); 202 | } 203 | 204 | void 205 | ewmh_update_net_active_window(void *p) { 206 | Window win; 207 | 208 | win = sel ? sel->win : None; 209 | XChangeProperty(dpy, root, 210 | atom[ActiveWindow], XA_WINDOW, 32, PropModeReplace, 211 | (unsigned char *) &win, 1); 212 | } 213 | 214 | void 215 | mwm_process_atom(Client *c) { 216 | Atom real; 217 | int format; 218 | unsigned char *data = NULL; 219 | CARD32 *hint; 220 | unsigned long n, extra; 221 | #define MWM_HINTS_ELEMENTS 5 222 | #define MWM_DECOR_ALL(x) ((x) & (1L << 0)) 223 | #define MWM_DECOR_TITLE(x) ((x) & (1L << 3)) 224 | #define MWM_DECOR_BORDER(x) ((x) & (1L << 1)) 225 | #define MWM_HINTS_DECOR(x) ((x) & (1L << 1)) 226 | if (XGetWindowProperty(dpy, c->win, atom[MWMHints], 0L, 20L, False, 227 | atom[MWMHints], &real, &format, &n, &extra, 228 | (unsigned char **) &data) == Success && n >= MWM_HINTS_ELEMENTS) { 229 | hint = (CARD32 *) data; 230 | if (MWM_HINTS_DECOR(hint[0]) && !(MWM_DECOR_ALL(hint[2]))) { 231 | c->title = MWM_DECOR_TITLE(hint[2]) ? root : (Window) NULL; 232 | c->border = MWM_DECOR_BORDER(hint[2]) ? style.border : 0; 233 | } 234 | } 235 | XFree(data); 236 | } 237 | 238 | void 239 | ewmh_process_state_atom(Client *c, Atom state, int set) { 240 | CARD32 data[2]; 241 | 242 | data[1] = None; 243 | if (state == atom[WindowStateFs]) { 244 | focus(c); 245 | if ((set == _NET_WM_STATE_ADD || set == _NET_WM_STATE_TOGGLE) 246 | && !c->ismax) { 247 | c->wasfloating = c->isfloating; 248 | if (!c->isfloating) 249 | togglefloating(NULL); 250 | togglemax(NULL); 251 | data[0] = state; 252 | } else if ((set == _NET_WM_STATE_REMOVE || 253 | set == _NET_WM_STATE_TOGGLE) && c->ismax) { 254 | togglemax(NULL); 255 | if (!c->wasfloating) 256 | togglefloating(NULL); 257 | data[0] = None; 258 | } 259 | XChangeProperty(dpy, c->win, atom[WindowState], XA_ATOM, 32, 260 | PropModeReplace, (unsigned char *) data, 2); 261 | DPRINT; 262 | arrange(curmonitor()); 263 | DPRINTF("%s: x%d y%d w%d h%d\n", c->name, c->x, c->y, c->w, c->h); 264 | } 265 | if (state == atom[WindowStateModal]) 266 | focus(c); 267 | } 268 | 269 | void 270 | clientmessage(XEvent *e) { 271 | XClientMessageEvent *ev = &e->xclient; 272 | Client *c; 273 | 274 | if (ev->message_type == atom[CloseWindow]) { 275 | if ((c = getclient(ev->window, clients, ClientWindow))) 276 | killclient(c); 277 | } 278 | else if (ev->message_type == atom[ActiveWindow]) { 279 | if ((c = getclient(ev->window, clients, ClientWindow))) { 280 | c->isicon = False; 281 | focus(c); 282 | arrange(curmonitor()); 283 | } 284 | } else if (ev->message_type == atom[CurDesk]) { 285 | view(tags[ev->data.l[0]]); 286 | } else if (ev->message_type == atom[WindowState]) { 287 | if ((c = getclient(ev->window, clients, ClientWindow))) { 288 | ewmh_process_state_atom(c, (Atom) ev->data.l[1], ev->data.l[0]); 289 | if (ev->data.l[2]) 290 | ewmh_process_state_atom(c, 291 | (Atom) ev->data.l[2], ev->data.l[0]); 292 | } 293 | } else if (ev->message_type == atom[WMChangeState]) { 294 | if ((c = getclient(ev->window, clients, ClientWindow))) { 295 | if (ev->data.l[0] == IconicState) { 296 | focus(c); 297 | iconify(NULL); 298 | } 299 | } 300 | } 301 | } 302 | 303 | void 304 | setopacity(Client *c, unsigned int opacity) { 305 | if (opacity == OPAQUE) { 306 | XDeleteProperty(dpy, c->win, atom[WindowOpacity]); 307 | XDeleteProperty(dpy, c->frame, atom[WindowOpacity]); 308 | } else { 309 | XChangeProperty(dpy, c->win, atom[WindowOpacity], 310 | XA_CARDINAL, 32, PropModeReplace, (unsigned char *) &opacity, 1L); 311 | XChangeProperty(dpy, c->frame, atom[WindowOpacity], 312 | XA_CARDINAL, 32, PropModeReplace, (unsigned char *) &opacity, 1L); 313 | 314 | } 315 | } 316 | 317 | void * 318 | getatom(Window win, Atom atom, unsigned long *nitems) { 319 | int format, status; 320 | unsigned char *ret = NULL; 321 | unsigned long extra; 322 | Atom real; 323 | 324 | status = XGetWindowProperty(dpy, win, atom, 0L, 64L, False, AnyPropertyType, 325 | &real, &format, nitems, &extra, (unsigned char **)&ret); 326 | if (status != Success) { 327 | *nitems = 0; 328 | return NULL; 329 | } 330 | 331 | return ret; 332 | } 333 | 334 | Bool 335 | checkatom(Window win, Atom bigatom, Atom smallatom) { 336 | Atom *state; 337 | unsigned long i, n; 338 | Bool ret = False; 339 | 340 | state = (Atom*)getatom(win, bigatom, &n); 341 | for (i = 0; i < n; i++) { 342 | if (state[i] == smallatom) 343 | ret = True; 344 | } 345 | XFree(state); 346 | return ret; 347 | } 348 | 349 | int 350 | getstrut(Client *c, Atom strut) { 351 | unsigned long *state; 352 | int ret = 0; 353 | Monitor *m; 354 | unsigned long i, n; 355 | 356 | if (!(m = clientmonitor(c))) 357 | return ret; 358 | 359 | state = (unsigned long*)getatom(c->win, strut, &n); 360 | if (n) { 361 | for (i = LeftStrut; i < LastStrut; i++) 362 | m->struts[i] = max(state[i], m->struts[i]); 363 | ret = 1; 364 | } 365 | XFree(state); 366 | return ret; 367 | } 368 | 369 | int getstruts(Client *c) { 370 | return (getstrut(c, atom[StrutPartial]) || getstrut(c, atom[Strut])); 371 | } 372 | 373 | void (*updateatom[]) (void *) = { 374 | [ClientList] = ewmh_update_net_client_list, 375 | [ActiveWindow] = ewmh_update_net_active_window, 376 | [WindowDesk] = ewmh_update_net_window_desktop, 377 | [NumberOfDesk] = ewmh_update_net_number_of_desktops, 378 | [DeskNames] = ewmh_update_net_desktop_names, 379 | [CurDesk] = ewmh_update_net_current_desktop, 380 | [ELayout] = update_echinus_layout_name, 381 | [WorkArea] = ewmh_update_net_work_area, 382 | }; 383 | -------------------------------------------------------------------------------- /iconify.xbm: -------------------------------------------------------------------------------- 1 | #define iconify_width 6 2 | #define iconify_height 6 3 | static unsigned char iconify_bits[] = { 4 | 0x00, 0x00, 0x22, 0x14, 0x08, 0x00 }; 5 | -------------------------------------------------------------------------------- /max.xbm: -------------------------------------------------------------------------------- 1 | #define max_width 6 2 | #define max_height 6 3 | static unsigned char max_bits[] = { 4 | 0x00, 0x00, 0x08, 0x14, 0x22, 0x00 }; 5 | -------------------------------------------------------------------------------- /parse.c: -------------------------------------------------------------------------------- 1 | /* 2 | * echinus wm written by Alexander Polakov 3 | * this file contains code to parse rules and keybindings 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "echinus.h" 13 | #include "config.h" 14 | 15 | typedef struct { 16 | const char *name; 17 | void (*action) (const char *arg); 18 | } KeyItem; 19 | 20 | static KeyItem KeyItems[] = { 21 | { "togglestruts", togglestruts }, 22 | { "focusicon", focusicon }, 23 | { "focusnext", focusnext }, 24 | { "focusprev", focusprev }, 25 | { "viewprevtag", viewprevtag }, 26 | { "viewlefttag", viewlefttag }, 27 | { "viewrighttag", viewrighttag }, 28 | { "quit", quit }, 29 | { "restart", quit }, 30 | { "killclient", killclient }, 31 | { "togglefloating", togglefloating }, 32 | { "decmwfact", setmwfact }, 33 | { "incmwfact", setmwfact }, 34 | { "incnmaster", incnmaster }, 35 | { "decnmaster", incnmaster }, 36 | { "iconify", iconify }, 37 | { "zoom", zoom }, 38 | { "moveright", moveresizekb }, 39 | { "moveleft", moveresizekb }, 40 | { "moveup", moveresizekb }, 41 | { "movedown", moveresizekb }, 42 | { "resizedecx", moveresizekb }, 43 | { "resizeincx", moveresizekb }, 44 | { "resizedecy", moveresizekb }, 45 | { "resizeincy", moveresizekb }, 46 | { "togglemonitor", togglemonitor }, 47 | { "togglefill", togglefill }, 48 | }; 49 | 50 | static KeyItem KeyItemsByTag[] = { 51 | { "view", view }, 52 | { "toggleview", toggleview }, 53 | { "focusview", focusview }, 54 | { "tag", tag }, 55 | { "toggletag", toggletag }, 56 | }; 57 | 58 | static void 59 | parsekey(const char *s, Key *k) { 60 | int l = strlen(s); 61 | unsigned long modmask = 0; 62 | char *pos, *opos; 63 | const char *stmp; 64 | char *tmp; 65 | int i; 66 | 67 | pos = strchr(s, '+'); 68 | if ((pos - s) && pos) { 69 | for (i = 0, stmp = s; stmp < pos; i++, stmp++) { 70 | switch(*stmp) { 71 | case 'A': 72 | modmask = modmask | Mod1Mask; 73 | break; 74 | case 'S': 75 | modmask = modmask | ShiftMask; 76 | break; 77 | case 'C': 78 | modmask = modmask | ControlMask; 79 | break; 80 | case 'W': 81 | modmask = modmask | Mod4Mask; 82 | break; 83 | } 84 | } 85 | } else 86 | pos = (char *) s; 87 | opos = pos; 88 | k->mod = modmask; 89 | pos = strchr(s, '='); 90 | if (pos) { 91 | if (pos - opos < 0) 92 | opos = (char *) s; 93 | tmp = emallocz(pos - opos); 94 | for (; !isalnum(opos[0]); opos++); 95 | strncpy(tmp, opos, pos - opos - 1); 96 | k->keysym = XStringToKeysym(tmp); 97 | free(tmp); 98 | tmp = emallocz((s + l - pos + 1)); 99 | for (pos++; !isgraph(pos[0]); pos++); 100 | strncpy(tmp, pos, s + l - pos); 101 | k->arg = tmp; 102 | } else { 103 | tmp = emallocz((s + l - opos)); 104 | for (opos++; !isalnum(opos[0]); opos++); 105 | strncpy(tmp, opos, s + l - opos); 106 | k->keysym = XStringToKeysym(tmp); 107 | free(tmp); 108 | } 109 | } 110 | 111 | static void 112 | initmodkey() { 113 | char tmp; 114 | 115 | strncpy(&tmp, getresource("modkey", "A"), 1); 116 | switch (tmp) { 117 | case 'S': 118 | modkey = ShiftMask; 119 | break; 120 | case 'C': 121 | modkey = ControlMask; 122 | break; 123 | case 'W': 124 | modkey = Mod4Mask; 125 | break; 126 | default: 127 | modkey = Mod1Mask; 128 | } 129 | } 130 | 131 | int 132 | initkeys() { 133 | unsigned int i, j; 134 | const char *tmp; 135 | char t[64]; 136 | 137 | initmodkey(); 138 | keys = malloc(sizeof(Key *) * LENGTH(KeyItems)); 139 | /* global functions */ 140 | for (i = 0; i < LENGTH(KeyItems); i++) { 141 | tmp = getresource(KeyItems[i].name, NULL); 142 | if (!tmp) 143 | continue; 144 | keys[nkeys] = malloc(sizeof(Key)); 145 | keys[nkeys]->func = KeyItems[i].action; 146 | keys[nkeys]->arg = NULL; 147 | parsekey(tmp, keys[nkeys]); 148 | nkeys++; 149 | } 150 | /* per tag functions */ 151 | for (j = 0; j < LENGTH(KeyItemsByTag); j++) { 152 | for (i = 0; i < ntags; i++) { 153 | snprintf(t, sizeof(t), "%s%d", KeyItemsByTag[j].name, i); 154 | tmp = getresource(t, NULL); 155 | if (!tmp) 156 | continue; 157 | keys = realloc(keys, sizeof(Key *) * (nkeys + 1)); 158 | keys[nkeys] = malloc(sizeof(Key)); 159 | keys[nkeys]->func = KeyItemsByTag[j].action; 160 | keys[nkeys]->arg = tags[i]; 161 | parsekey(tmp, keys[nkeys]); 162 | nkeys++; 163 | } 164 | } 165 | /* layout setting */ 166 | for (i = 0; layouts[i].symbol != '\0'; i++) { 167 | snprintf(t, sizeof(t), "setlayout%c", layouts[i].symbol); 168 | tmp = getresource(t, NULL); 169 | if (!tmp) 170 | continue; 171 | keys = realloc(keys, sizeof(Key *) * (nkeys + 1)); 172 | keys[nkeys] = malloc(sizeof(Key)); 173 | keys[nkeys]->func = setlayout; 174 | keys[nkeys]->arg = &layouts[i].symbol; 175 | parsekey(tmp, keys[nkeys]); 176 | nkeys++; 177 | } 178 | /* spawn */ 179 | for (i = 0; i < 64; i++) { 180 | snprintf(t, sizeof(t), "spawn%d", i); 181 | tmp = getresource(t, NULL); 182 | if (!tmp) 183 | continue; 184 | keys = realloc(keys, sizeof(Key *) * (nkeys + 1)); 185 | keys[nkeys] = malloc(sizeof(Key)); 186 | keys[nkeys]->func = spawn; 187 | keys[nkeys]->arg = NULL; 188 | parsekey(tmp, keys[nkeys]); 189 | nkeys++; 190 | } 191 | return 0; 192 | } 193 | 194 | static void 195 | parserule(const char *s, Rule *r) { 196 | r->prop = emallocz(128); 197 | r->tags = emallocz(64); 198 | sscanf(s, "%s %s %d %d", r->prop, r->tags, &r->isfloating, &r->hastitle); 199 | } 200 | 201 | static void 202 | compileregs(void) { 203 | unsigned int i; 204 | regex_t *reg; 205 | 206 | for (i = 0; i < nrules; i++) { 207 | if (rules[i]->prop) { 208 | reg = emallocz(sizeof(regex_t)); 209 | if (regcomp(reg, rules[i]->prop, REG_EXTENDED)) 210 | free(reg); 211 | else 212 | rules[i]->propregex = reg; 213 | } 214 | if (rules[i]->tags) { 215 | reg = emallocz(sizeof(regex_t)); 216 | if (regcomp(reg, rules[i]->tags, REG_EXTENDED)) 217 | free(reg); 218 | else 219 | rules[i]->tagregex = reg; 220 | } 221 | } 222 | } 223 | 224 | void 225 | initrules() { 226 | int i; 227 | char t[64]; 228 | const char *tmp; 229 | rules = emallocz(64 * sizeof(Rule *)); 230 | for (i = 0; i < 64; i++) { 231 | snprintf(t, sizeof(t), "rule%d", i); 232 | tmp = getresource(t, NULL); 233 | if (!tmp) 234 | continue; 235 | rules[nrules] = emallocz(sizeof(Rule)); 236 | parserule(tmp, rules[nrules]); 237 | nrules++; 238 | } 239 | rules = realloc(rules, nrules * sizeof(Rule *)); 240 | compileregs(); 241 | } 242 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | include config.mk 2 | 3 | SRC = ewmhpanel.c util.c 4 | OBJ = ${SRC:.c=.o} 5 | 6 | all: options tests 7 | 8 | options: 9 | @echo echinus tests build options: 10 | @echo "CFLAGS = ${CFLAGS}" 11 | @echo "LDFLAGS = ${LDFLAGS}" 12 | @echo "CC = ${CC}" 13 | 14 | .c.o: 15 | @echo CC $< 16 | @${CC} -c ${CFLAGS} $< 17 | 18 | ${OBJ}: config.mk 19 | 20 | ewmhpanel: ${OBJ} 21 | @echo CC -o $@ 22 | @${CC} -o $@ ${OBJ} ${LDFLAGS} 23 | 24 | tests: ewmhpanel 25 | 26 | clean: 27 | @echo cleaning 28 | @rm -f ewmhpanel 29 | @rm -f *.o 30 | 31 | .PHONY: all options clean dist install uninstall 32 | -------------------------------------------------------------------------------- /tests/config.mk: -------------------------------------------------------------------------------- 1 | # Customize below to fit your system 2 | 3 | # paths 4 | PREFIX = /usr/local 5 | MANPREFIX = ${PREFIX}/share/man 6 | CONF = /share/examples/echinus 7 | 8 | X11INC = /usr/X11R6/include 9 | X11LIB = /usr/X11R6/lib 10 | 11 | # includes and libs 12 | INCS = -I. -I/usr/include -I${X11INC} `pkg-config --cflags xft` 13 | LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 `pkg-config --libs xft` 14 | 15 | # flags 16 | CFLAGS = -Os ${INCS} 17 | LDFLAGS = -s ${LIBS} 18 | CFLAGS = -g3 -ggdb3 -std=c99 -pedantic -O0 ${INCS} 19 | LDFLAGS = -g3 -ggdb3 ${LIBS} 20 | 21 | # DEBUG: Show warnings (if any). Comment out to disable. 22 | CFLAGS += -Wall 23 | # mostly useless warnings 24 | #CFLAGS += -Wall -W -Wcast-qual -Wshadow -Wwrite-strings 25 | #CFLAGS += -Werror # Treat warnings as errors. 26 | #CFLAGS += -save-temps # Keep precompiler output (great for debugging). 27 | 28 | # XRandr (multihead support). Comment out to disable. 29 | CFLAGS += -DXRANDR=1 30 | LIBS += -lXrandr 31 | 32 | # Solaris 33 | #CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" 34 | #LDFLAGS = ${LIBS} 35 | #CFLAGS += -xtarget=ultra 36 | 37 | # compiler and linker 38 | #CC = cc 39 | -------------------------------------------------------------------------------- /tests/ewmhpanel.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "util.h" 11 | 12 | int screen; 13 | Display *dpy; 14 | unsigned int mw = 0; 15 | unsigned int mh = 0; 16 | unsigned int mx = 0; 17 | unsigned int my = 0; 18 | unsigned int numlockmask = 0; 19 | Bool running = True; 20 | Bool bottom = False; 21 | Window root, win; 22 | enum { Left, Right, Top, Bottom, LeftStartY, LeftEndY, RightStartY, RightEndY, TopStartX, TopEndX, BotStartX, BotEndX }; 23 | 24 | void 25 | setstruts(Bool autohide) { 26 | int *struts; 27 | Atom net_wm_strut_partial; 28 | Atom net_wm_window_type; 29 | Atom net_wm_window_type_dock; 30 | XWindowAttributes wa; 31 | 32 | net_wm_strut_partial = XInternAtom(dpy, "_NET_WM_STRUT_PARTIAL", False); 33 | net_wm_window_type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); 34 | net_wm_window_type_dock = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False); 35 | 36 | struts = emallocz(12*sizeof(int)); 37 | XGetWindowAttributes(dpy, win, &wa); 38 | int x, y, w, h, b, d; 39 | XGetGeometry(dpy, win, &root, &x, &y, &w, &h, &b, &d); 40 | fprintf(stderr, "%d %d\n", x, y); 41 | fprintf(stderr, "x:%d y:%d ht: %d\n", wa.x, wa.y, DisplayHeight(dpy, screen)); 42 | if(bottom){ 43 | struts[Bottom] = DisplayHeight(dpy, screen) - my + mh; 44 | fprintf(stderr, "BotStrut=%d\n", struts[Bottom]); 45 | struts[BotStartX] = mx; 46 | struts[BotEndX] = mx+mw; 47 | } 48 | else { 49 | struts[Top] = mh + my; 50 | struts[TopStartX] = mx; 51 | struts[TopEndX] = mx+mw; 52 | } 53 | XChangeProperty(dpy, win, net_wm_strut_partial, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)struts, 12); 54 | XChangeProperty(dpy, win, net_wm_window_type, XA_ATOM, 32, PropModeReplace, (unsigned char*)&net_wm_window_type_dock, 1); 55 | free(struts); 56 | } 57 | 58 | void 59 | setup() { 60 | unsigned int i, j; 61 | XModifierKeymap *modmap; 62 | XSetWindowAttributes wa; 63 | 64 | /* init modifier map */ 65 | modmap = XGetModifierMapping(dpy); 66 | for(i = 0; i < 8; i++) 67 | for(j = 0; j < modmap->max_keypermod; j++) { 68 | if(modmap->modifiermap[i * modmap->max_keypermod + j] 69 | == XKeysymToKeycode(dpy, XK_Num_Lock)) 70 | numlockmask = (1 << i); 71 | } 72 | XFreeModifiermap(modmap); 73 | wa.override_redirect = 0; 74 | wa.background_pixmap = ParentRelative; 75 | wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask; 76 | mw = DisplayWidth(dpy, screen)-mx; 77 | my = bottom ? DisplayHeight(dpy, screen) - mh : 0; 78 | mh = 20; 79 | win = XCreateWindow(dpy, root, mx, 80 | my, mw, mh, 0, 81 | DefaultDepth(dpy, screen), CopyFromParent, 82 | DefaultVisual(dpy, screen), 83 | CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); 84 | setstruts(0); 85 | XMapRaised(dpy, win); 86 | XMoveWindow(dpy, win, mx, my); 87 | } 88 | 89 | void 90 | drawme() { 91 | GC gc; 92 | gc = XCreateGC(dpy, root, 0, 0); 93 | XRectangle r = { 0, 0, mw, mh }; 94 | XSetForeground(dpy, gc, BlackPixel(dpy, screen)); 95 | XSetBackground(dpy, gc, WhitePixel(dpy, screen)); 96 | XFillRectangles(dpy, win, gc, &r, 1); 97 | setstruts(0); 98 | } 99 | 100 | void 101 | run(void) { 102 | XEvent ev; 103 | 104 | XSetErrorHandler(xerrordummy); 105 | 106 | /* main event loop */ 107 | XSelectInput(dpy, root, PropertyChangeMask); 108 | while(running && !XNextEvent(dpy, &ev)) 109 | switch (ev.type) { 110 | default: /* ignore all crap */ 111 | break; 112 | case Expose: 113 | if(ev.xexpose.count == 0) 114 | drawme(); 115 | break; 116 | } 117 | } 118 | 119 | int 120 | main(int argc, char *argv[]) { 121 | if(argc == 2 && !strcmp("-b", argv[1])) 122 | bottom = True; 123 | dpy = XOpenDisplay(0); 124 | if(!dpy) 125 | eprint("ewmhpanel: cannot open display\n"); 126 | screen = DefaultScreen(dpy); 127 | root = RootWindow(dpy, screen); 128 | 129 | setup(bottom); 130 | XSync(dpy, False); 131 | run(); 132 | XCloseDisplay(dpy); 133 | return 0; 134 | } 135 | -------------------------------------------------------------------------------- /tests/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "util.h" 11 | 12 | char * 13 | estrdup(const char *str) { 14 | char *res = strdup(str); 15 | 16 | if(!res) 17 | eprint("fatal: could not malloc() %u bytes\n", strlen(str)); 18 | return res; 19 | } 20 | 21 | int 22 | xerrordummy(Display *dpy, XErrorEvent *ee) { 23 | return 0; 24 | } 25 | 26 | void * 27 | emalloc(unsigned int size) { 28 | void *res = malloc(size); 29 | 30 | if(!res) 31 | eprint("fatal: could not malloc() %u bytes\n", size); 32 | return res; 33 | } 34 | 35 | void * 36 | emallocz(unsigned int size) { 37 | void *res = calloc(1, size); 38 | 39 | if(!res) 40 | eprint("fatal: could not malloc() %u bytes\n", size); 41 | return res; 42 | } 43 | 44 | void 45 | eprint(const char *errstr, ...) { 46 | va_list ap; 47 | 48 | va_start(ap, errstr); 49 | vfprintf(stderr, errstr, ap); 50 | va_end(ap); 51 | exit(EXIT_FAILURE); 52 | } 53 | 54 | unsigned long 55 | getcolor(const char *colstr) { 56 | Colormap cmap = DefaultColormap(dpy, screen); 57 | XColor color; 58 | 59 | if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color)) 60 | eprint("error, cannot allocate color '%s'\n", colstr); 61 | return color.pixel; 62 | } 63 | 64 | char * 65 | getresource(const char *resource, char *defval, XrmDatabase xrdb) { 66 | static char name[256], class[256], *type; 67 | XrmValue value; 68 | snprintf(name, sizeof(name), "%s.%s", RESNAME, resource); 69 | snprintf(class, sizeof(class), "%s.%s", RESCLASS, resource); 70 | XrmGetResource(xrdb, name, class, &type, &value); 71 | if(value.addr) 72 | return value.addr; 73 | return defval; 74 | } 75 | 76 | void* 77 | getatom(Window w, Atom atom, unsigned long *n) { 78 | int format, status; 79 | unsigned char *p = NULL; 80 | unsigned long tn, extra; 81 | Atom real; 82 | 83 | status = XGetWindowProperty(dpy, w, atom, 0L, 64L, False, AnyPropertyType, 84 | &real, &format, &tn, &extra, (unsigned char **)&p); 85 | if(status == BadWindow) 86 | return NULL; 87 | if(n!=NULL) 88 | *n = tn; 89 | return p; 90 | } 91 | #if 0 92 | char ** 93 | getutf8prop(Window win, Atom atom, int *count) { 94 | Atom type; 95 | int format, i; 96 | unsigned long nitems; 97 | unsigned long bytes_after; 98 | char *s, **retval = NULL; 99 | int result; 100 | unsigned char *tmp = NULL; 101 | 102 | *count = 0; 103 | result = XGetWindowProperty(dpy, win, atom, 0, 64L, False, 104 | utf8_string, &type, &format, &nitems, 105 | &bytes_after, &tmp); 106 | if (result != Success || type != utf8_string || tmp == NULL) 107 | return NULL; 108 | 109 | if (nitems) { 110 | char *val = (char *) tmp; 111 | for (i = 0; i < nitems; i++) { 112 | if (!val[i]) 113 | (*count)++; 114 | } 115 | retval = emalloc((*count + 2)*sizeof(char*)); 116 | for (i = 0, s = val; i < *count; i++, s = s + strlen (s) + 1) { 117 | retval[i] = estrdup(s); 118 | } 119 | if (val[nitems-1]) { 120 | result = nitems - (s - val); 121 | memmove(s - 1, s, result); 122 | val[nitems-1] = 0; 123 | retval[i] = estrdup(s - 1); 124 | (*count)++; 125 | } 126 | } 127 | XFree (tmp); 128 | 129 | return retval; 130 | 131 | } 132 | #endif 133 | 134 | Bool 135 | gettextprop(Window w, Atom atom, char *text, unsigned int size) { 136 | char **list = NULL; 137 | int n; 138 | XTextProperty name; 139 | 140 | if(!text || size == 0) 141 | return False; 142 | text[0] = '\0'; 143 | if (BadWindow == XGetTextProperty(dpy, w, &name, atom)) 144 | return False; 145 | if(!name.nitems) 146 | return False; 147 | if(name.encoding == XA_STRING) 148 | strncpy(text, (char *)name.value, size - 1); 149 | else { 150 | if(XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success 151 | && n > 0 && *list) { 152 | strncpy(text, *list, size - 1); 153 | XFreeStringList(list); 154 | } 155 | } 156 | text[size - 1] = '\0'; 157 | XFree(name.value); 158 | return True; 159 | } 160 | -------------------------------------------------------------------------------- /tests/util.h: -------------------------------------------------------------------------------- 1 | /* forward declarations */ 2 | char* estrdup(const char *str); 3 | int xerrordummy(Display *dpy, XErrorEvent *ee); 4 | void* emalloc(unsigned int size); 5 | void eprint(const char *errstr, ...); 6 | unsigned long getcolor(const char *colstr); 7 | char *getresource(const char *resource, char *defval, XrmDatabase xrdb); 8 | void* getatom(Window w, Atom atom, unsigned long *n); 9 | #if 0 10 | char** getutf8prop(Window win, Atom atom, int *count); 11 | #endif 12 | Bool gettextprop(Window w, Atom atom, char *text, unsigned int size); 13 | unsigned int textnw(const char *text, unsigned int len); 14 | unsigned int textw(const char *text); 15 | #ifndef RESNAME 16 | #define RESNAME "" 17 | #endif 18 | 19 | #ifndef RESCLASS 20 | #define RESCLASS "" 21 | #endif 22 | 23 | extern Display *dpy; 24 | extern int screen; 25 | extern Window root; 26 | --------------------------------------------------------------------------------