├── name ├── bin ├── go ├── cargo └── firefox ├── lx-dwm ├── lx-dwm.png ├── util.h ├── util.c ├── config.mk ├── Makefile ├── transient.c ├── README ├── drw.h ├── LICENSE ├── config.def.h ├── lx-dwm.1 ├── drw.c └── lx-dwm.c ├── procports.h ├── mkfile.clt ├── mkfile.srv ├── IDEAS.md ├── mkfile ├── man ├── lx.1 └── lx.6 ├── procports.c ├── README.md ├── lx.c └── lxsrv.c /name: -------------------------------------------------------------------------------- 1 | lx 2 | -------------------------------------------------------------------------------- /bin/go: -------------------------------------------------------------------------------- 1 | #!/bin/rc 2 | exec lx -D go $* 3 | -------------------------------------------------------------------------------- /bin/cargo: -------------------------------------------------------------------------------- 1 | #!/bin/rc 2 | exec lx -D cargo $* 3 | -------------------------------------------------------------------------------- /lx-dwm/lx-dwm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perpen/lx/HEAD/lx-dwm/lx-dwm.png -------------------------------------------------------------------------------- /procports.h: -------------------------------------------------------------------------------- 1 | int* portsbusy(void); 2 | int localportconn(int port); 3 | int hanguplocalport(int port); 4 | -------------------------------------------------------------------------------- /mkfile.clt: -------------------------------------------------------------------------------- 1 | (B) ? (A) : (B)) 4 | #define MIN(A, B) ((A) < (B) ? (A) : (B)) 5 | #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) 6 | 7 | void die(const char *fmt, ...); 8 | void *ecalloc(size_t nmemb, size_t size); 9 | -------------------------------------------------------------------------------- /bin/firefox: -------------------------------------------------------------------------------- 1 | #!/bin/rc 2 | # Opens the parameter in a new tab 3 | # Assumes a running firefox instance 4 | 5 | flag e + 6 | 7 | # On 9 8 | if(! test -d /9){ 9 | exec lx /9$0 $* 10 | } 11 | 12 | # On Linux 13 | pid=`{pgrep -f '/usr/lib/firefox/firefox' | tail -1} 14 | if(~ $pid ''){ 15 | echo $0: no firefox process 16 | exit 1 17 | } 18 | DISPLAY=`{cat /proc/$pid/environ | tr '\0' '\n' \ 19 | | grep '^DISPLAY=' | sed 's/.*=//'} 20 | exec firefox $* 21 | -------------------------------------------------------------------------------- /mkfile.srv: -------------------------------------------------------------------------------- 1 | # for building the server from linux, using p9p 2 | 3 | <$PLAN9/src/mkhdr 4 | 5 | NAME=`{cat name} 6 | TARG=${NAME}srv 7 | OFILES=lxsrv.$O 8 | BINDIR=$PLAN9/bin 9 | #CFLAGS='-fsanitize=address' 10 | #LDFLAGS='-lasan' 11 | 12 | <$PLAN9/src/mkone 13 | 14 | install:V: $BIN/$TARG wm.install 15 | 16 | $BIN/$TARG: o.$TARG 17 | test -n "$NAME" 18 | pkill $TARG || true 19 | cp $prereq $BIN/$TARG 20 | sudo setcap cap_sys_admin+eip $BIN/$TARG 21 | 22 | wm.%:V: 23 | cd lx-dwm 24 | make $stem 25 | -------------------------------------------------------------------------------- /IDEAS.md: -------------------------------------------------------------------------------- 1 | Bugs: 2 | - 1 time out of ~50, firefox doesn't start. Retry works. 3 | - acme-lsp: 9fans go lib doesn't support /mnt/acme on linux 4 | - lx2srv not working from srvloop? if run remotely?? 5 | - fix slay lx2 on bash -i, server should cleanup on connection break 6 | 7 | Maybe: 8 | - plumber integration 9 | - stats(1) for p9p 10 | - ports: openbsd doesn't have mount namespaces. freebsd? 11 | 12 | ``` 13 | % aux/listen1 -tv tcp!*!2000 exportfs -dr / <[10=0] >[11=1] >[12=2] 14 | $ rc -i >/9/fd/11 2>>/9/fd/12 15 | ``` 16 | -------------------------------------------------------------------------------- /lx-dwm/util.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "util.h" 8 | 9 | void * 10 | ecalloc(size_t nmemb, size_t size) 11 | { 12 | void *p; 13 | 14 | if (!(p = calloc(nmemb, size))) 15 | die("calloc:"); 16 | return p; 17 | } 18 | 19 | void 20 | die(const char *fmt, ...) { 21 | va_list ap; 22 | 23 | va_start(ap, fmt); 24 | vfprintf(stderr, fmt, ap); 25 | va_end(ap); 26 | 27 | if (fmt[0] && fmt[strlen(fmt)-1] == ':') { 28 | fputc(' ', stderr); 29 | perror(NULL); 30 | } else { 31 | fputc('\n', stderr); 32 | } 33 | 34 | exit(1); 35 | } 36 | -------------------------------------------------------------------------------- /lx-dwm/config.mk: -------------------------------------------------------------------------------- 1 | # lx-dwm version 2 | VERSION = 6.2 3 | 4 | # Customize below to fit your system 5 | 6 | # paths 7 | PREFIX = ${PLAN9} 8 | MANPREFIX = ${PREFIX}/share/man 9 | 10 | X11INC = /usr/X11R6/include 11 | X11LIB = /usr/X11R6/lib 12 | 13 | # freetype 14 | FREETYPELIBS = -lfontconfig -lXft 15 | FREETYPEINC = /usr/include/freetype2 16 | # OpenBSD (uncomment) 17 | #FREETYPEINC = ${X11INC}/freetype2 18 | 19 | # includes and libs 20 | INCS = -I${X11INC} -I${FREETYPEINC} 21 | LIBS = -L${X11LIB} -lX11 ${FREETYPELIBS} 22 | 23 | # flags 24 | CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=2 -DVERSION=\"${VERSION}\" 25 | #CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} 26 | CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS} 27 | LDFLAGS = ${LIBS} 28 | 29 | # Solaris 30 | #CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" 31 | #LDFLAGS = ${LIBS} 32 | 33 | # compiler and linker 34 | CC = cc 35 | -------------------------------------------------------------------------------- /lx-dwm/Makefile: -------------------------------------------------------------------------------- 1 | # lx-dwm - dynamic window manager 2 | # See LICENSE file for copyright and license details. 3 | 4 | include config.mk 5 | 6 | SRC = drw.c lx-dwm.c util.c 7 | OBJ = ${SRC:.c=.o} 8 | 9 | all: lx-dwm 10 | 11 | .c.o: 12 | ${CC} -c ${CFLAGS} $< 13 | 14 | ${OBJ}: config.h config.mk 15 | 16 | config.h: 17 | cp config.def.h $@ 18 | 19 | lx-dwm: ${OBJ} 20 | ${CC} -o $@ ${OBJ} ${LDFLAGS} 21 | 22 | clean: 23 | rm -f lx-dwm ${OBJ} lx-dwm-${VERSION}.tar.gz 24 | 25 | dist: clean 26 | mkdir -p lx-dwm-${VERSION} 27 | cp -R LICENSE Makefile README config.def.h config.mk\ 28 | lx-dwm.1 drw.h util.h ${SRC} lx-dwm.png transient.c lx-dwm-${VERSION} 29 | tar -cf lx-dwm-${VERSION}.tar lx-dwm-${VERSION} 30 | gzip lx-dwm-${VERSION}.tar 31 | rm -rf lx-dwm-${VERSION} 32 | 33 | install: all 34 | cp -f lx-dwm ${DESTDIR}${PREFIX}/bin 35 | chmod 755 ${DESTDIR}${PREFIX}/bin/lx-dwm 36 | 37 | uninstall: 38 | rm -f ${DESTDIR}${PREFIX}/bin/lx-dwm 39 | 40 | .PHONY: all options clean dist install uninstall 41 | -------------------------------------------------------------------------------- /lx-dwm/transient.c: -------------------------------------------------------------------------------- 1 | /* cc transient.c -o transient -lX11 */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main(void) { 9 | Display *d; 10 | Window r, f, t = None; 11 | XSizeHints h; 12 | XEvent e; 13 | 14 | d = XOpenDisplay(NULL); 15 | if (!d) 16 | exit(1); 17 | r = DefaultRootWindow(d); 18 | 19 | f = XCreateSimpleWindow(d, r, 100, 100, 400, 400, 0, 0, 0); 20 | h.min_width = h.max_width = h.min_height = h.max_height = 400; 21 | h.flags = PMinSize | PMaxSize; 22 | XSetWMNormalHints(d, f, &h); 23 | XStoreName(d, f, "floating"); 24 | XMapWindow(d, f); 25 | 26 | XSelectInput(d, f, ExposureMask); 27 | while (1) { 28 | XNextEvent(d, &e); 29 | 30 | if (t == None) { 31 | sleep(5); 32 | t = XCreateSimpleWindow(d, r, 50, 50, 100, 100, 0, 0, 0); 33 | XSetTransientForHint(d, t, f); 34 | XStoreName(d, t, "transient"); 35 | XMapWindow(d, t); 36 | XSelectInput(d, t, ExposureMask); 37 | } 38 | } 39 | 40 | XCloseDisplay(d); 41 | exit(0); 42 | } 43 | -------------------------------------------------------------------------------- /mkfile: -------------------------------------------------------------------------------- 1 | usage:VQ: 2 | echo 'usage: mk clt.TARGET or srv.TARGET or both.TARGET' 3 | echo ' e.g. to install the client: "mk clt.install"' 4 | echo 'This mkfile is meant to be used from Plan 9, not Linux' 5 | echo 'The srv.* targets require a working lx installation.' 6 | 7 | #both.%:V: clt.% srv.% 8 | both.%:V: 9 | mk -f mkfile.clt $stem 10 | lx mk -f mkfile.srv $stem 11 | 12 | clt.%:V: 13 | mk -f mkfile.clt $stem 14 | 15 | srv.%:V: 16 | lx mk -f mkfile.srv $stem 17 | 18 | #### The targets below are helpers used during development 19 | 20 | # Run install on change 21 | watch:V: 22 | while() { 23 | newsum=`{ls -q mkfile* *.[ch] | sum | awk '{print $1}'} 24 | if(! ~ $newsum $oldsum) { 25 | oldsum=$newsum 26 | echo '########' `{date} 27 | mk both.install || echo oops 28 | } 29 | sleep 2 30 | } 31 | 32 | # Restart the server on build, see mkfile.srv:/pkill 33 | # This assumes the current name is not 'lx' 34 | srvloop:V: 35 | FIXME weirdly 9pfuse fails when lx2srv is started from here 36 | exe=`{cat name}^srv 37 | lx pkill $exe || echo -n 38 | while(){ echo '####' `{date}; lx $exe -p 8000 || sleep 1 } 39 | 40 | gitpush:V: 41 | # Do not commit ./name as its content must stay 'lx' in the 42 | # master branch. 43 | git/commit `{git/walk | awk '{print $2}' | grep -v '^name$'} 44 | git/push 45 | 46 | gitmerge:V: 47 | git/branch master 48 | echo lx > name 49 | git/merge beta 50 | echo lx > name 51 | git/push 52 | git/branch beta 53 | echo lx2 > name 54 | -------------------------------------------------------------------------------- /lx-dwm/README: -------------------------------------------------------------------------------- 1 | Modification for use with lx 2 | ============================ 3 | This is a simple customisation of dwm-6.2: 4 | - Removed the tag buttons 5 | - The status text (top right) is hardcoded to "X". Click with button 1 6 | closes the current window, with button 3 quits lx-dwm. 7 | - Renamed to lx-dwm, to allow coexistence with original dwm 8 | 9 | Below is the original dwm README content. 10 | 11 | 12 | lx-dwm - dynamic window manager 13 | ============================ 14 | lx-dwm is an extremely fast, small, and dynamic window manager for X. 15 | 16 | 17 | Requirements 18 | ------------ 19 | In order to build lx-dwm you need the Xlib header files. 20 | 21 | 22 | Installation 23 | ------------ 24 | Edit config.mk to match your local setup (lx-dwm is installed into 25 | the /usr/local namespace by default). 26 | 27 | Afterwards enter the following command to build and install lx-dwm (if 28 | necessary as root): 29 | 30 | make clean install 31 | 32 | 33 | Running lx-dwm 34 | ----------- 35 | Add the following line to your .xinitrc to start lx-dwm using startx: 36 | 37 | exec lx-dwm 38 | 39 | In order to connect lx-dwm to a specific display, make sure that 40 | the DISPLAY environment variable is set correctly, e.g.: 41 | 42 | DISPLAY=foo.bar:1 exec lx-dwm 43 | 44 | (This will start lx-dwm on display :1 of the host foo.bar.) 45 | 46 | In order to display status info in the bar, you can do something 47 | like this in your .xinitrc: 48 | 49 | while xsetroot -name "`date` `uptime | sed 's/.*,//'`" 50 | do 51 | sleep 1 52 | done & 53 | exec lx-dwm 54 | 55 | 56 | Configuration 57 | ------------- 58 | The configuration of lx-dwm is done by creating a custom config.h 59 | and (re)compiling the source code. 60 | -------------------------------------------------------------------------------- /lx-dwm/drw.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | typedef struct { 4 | Cursor cursor; 5 | } Cur; 6 | 7 | typedef struct Fnt { 8 | Display *dpy; 9 | unsigned int h; 10 | XftFont *xfont; 11 | FcPattern *pattern; 12 | struct Fnt *next; 13 | } Fnt; 14 | 15 | enum { ColFg, ColBg, ColBorder }; /* Clr scheme index */ 16 | typedef XftColor Clr; 17 | 18 | typedef struct { 19 | unsigned int w, h; 20 | Display *dpy; 21 | int screen; 22 | Window root; 23 | Drawable drawable; 24 | GC gc; 25 | Clr *scheme; 26 | Fnt *fonts; 27 | } Drw; 28 | 29 | /* Drawable abstraction */ 30 | Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); 31 | void drw_resize(Drw *drw, unsigned int w, unsigned int h); 32 | void drw_free(Drw *drw); 33 | 34 | /* Fnt abstraction */ 35 | Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); 36 | void drw_fontset_free(Fnt* set); 37 | unsigned int drw_fontset_getwidth(Drw *drw, const char *text); 38 | void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); 39 | 40 | /* Colorscheme abstraction */ 41 | void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); 42 | Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); 43 | 44 | /* Cursor abstraction */ 45 | Cur *drw_cur_create(Drw *drw, int shape); 46 | void drw_cur_free(Drw *drw, Cur *cursor); 47 | 48 | /* Drawing context manipulation */ 49 | void drw_setfontset(Drw *drw, Fnt *set); 50 | void drw_setscheme(Drw *drw, Clr *scm); 51 | 52 | /* Drawing functions */ 53 | void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); 54 | int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); 55 | 56 | /* Map functions */ 57 | void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); 58 | -------------------------------------------------------------------------------- /lx-dwm/LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2006-2019 Anselm R Garbe 4 | © 2006-2009 Jukka Salmi 5 | © 2006-2007 Sander van Dijk 6 | © 2007-2011 Peter Hartlich 7 | © 2007-2009 Szabolcs Nagy 8 | © 2007-2009 Christof Musik 9 | © 2007-2009 Premysl Hruby 10 | © 2007-2008 Enno Gottox Boland 11 | © 2008 Martin Hurton 12 | © 2008 Neale Pickett 13 | © 2009 Mate Nagy 14 | © 2010-2016 Hiltjo Posthuma 15 | © 2010-2012 Connor Lane Smith 16 | © 2011 Christoph Lohmann <20h@r-36.net> 17 | © 2015-2016 Quentin Rameau 18 | © 2015-2016 Eric Pruitt 19 | © 2016-2017 Markus Teich 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a 22 | copy of this software and associated documentation files (the "Software"), 23 | to deal in the Software without restriction, including without limitation 24 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 25 | and/or sell copies of the Software, and to permit persons to whom the 26 | Software is furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in 29 | all copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 34 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 36 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 37 | DEALINGS IN THE SOFTWARE. 38 | -------------------------------------------------------------------------------- /man/lx.1: -------------------------------------------------------------------------------- 1 | .TH LX 1 2 | .SH NAME 3 | lx, lxsrv \- run a command on a Linux host 4 | .SH SYNOPSIS 5 | .B lx 6 | [ 7 | .B -Dd 8 | ] [ 9 | .B -c 10 | .I dir 11 | ] [ 12 | .B -h 13 | .I host 14 | ] [ 15 | .B -m 16 | .I mounts 17 | ] [ 18 | .I cmd args ... 19 | ] 20 | .PP 21 | .B lxsrv 22 | [ 23 | .B -i 24 | .I address 25 | ] [ 26 | .B -p 27 | .I port 28 | ] 29 | .SH DESCRIPTION 30 | .I Lx 31 | is run from Plan 9 to execute a command 32 | on a Linux server, with the standard file descriptors 0, 1, 2 33 | connected to the remote process, and with the local namespace 34 | available from Linux under mount point 35 | .BR /9 . 36 | .PP 37 | .B Lx 38 | attempts to preserve the Plan 9 current directory when starting 39 | the Linux command. 40 | .PP 41 | When 42 | .IR lx 43 | receives in a short interval 1 (resp. 2, 3) interrupt notes, it 44 | sends signal INT (resp. HUP, KILL) to the remote process group. 45 | .PP 46 | If a command is not specified, a default command 47 | from the configuration file will be run. 48 | .PP 49 | The options to 50 | .IR lx 51 | are: 52 | .TP 53 | .B -d 54 | Enable debug mode. Some extra information is printed 55 | to stderr, originating from both client and server. 56 | .TP 57 | .B -h 58 | Connect to the specified Linux host, which must be mentioned 59 | in the file described in lx(6). 60 | .TP 61 | .B -c 62 | Change to the specified directory before running the command. 63 | By default the Linux server will try changing the working 64 | directory to the Plan 9 one, but a chdir 65 | failure will not prevent the command execution unless option 66 | .B -D 67 | is used. 68 | .TP 69 | .B -m 70 | The parameter specifies the bind-mounts that should be performed 71 | before running the process. The syntax for this parameter is 72 | described under option 73 | .IR mounts 74 | in lx(6). 75 | .PP 76 | .IR Lxsrv 77 | runs on the Linux host, and supports the following options: 78 | .TP 79 | .B -i 80 | IP address to listen on, default is localhost. 81 | .TP 82 | .B -p 83 | Port to listen on, default is 9000. 84 | .SH EXAMPLES 85 | .ft L 86 | .nf 87 | % lx 88 | % lx firefox 89 | % ls -l | lx gawk '{print $4}' 90 | % lx go build 91 | % lx python -i 92 | .fi 93 | .SH FILES 94 | $home/lib/lx \- Configuration file 95 | .SH SEE ALSO 96 | lx(6) 97 | .PP 98 | See the README for https://github.com/perpen/lx 99 | .SH DIAGNOSTICS, BUGS 100 | See the README for https://github.com/perpen/lx 101 | -------------------------------------------------------------------------------- /man/lx.6: -------------------------------------------------------------------------------- 1 | .TH LX 6 2 | .SH NAME 3 | lx \- configuration file for lx 4 | .SH DESCRIPTION 5 | The configuration is loaded from 6 | .B $home/lib/lx. 7 | .PP 8 | Some options have two forms: 9 | .I option=value 10 | and 11 | .I HOST.option=value. 12 | The first form provides a default value, applicable to any 13 | host; the second is used to override the default for a specific 14 | host. 15 | .I HOST 16 | can be a unqualified host name, an FQDN or an IP address. 17 | .TP 18 | .B default-host 19 | Specifies the Linux host to connect to if option 20 | .IR -h 21 | is not provided. 22 | .TP 23 | .B minport, maxport 24 | Specifies the port range to be used for serving the Plan 9 25 | namespace to Linux. The width of this range will be the maximum 26 | number of 27 | .IR lx 28 | commands that can be run simultaneously. On startup `lx` will 29 | use the first unused port in this range. 30 | .TP 31 | .B mounts, HOST.mounts 32 | Lists the bind-mounts to be performed before running the 33 | process. This should be a comma-separated list of colon-separated 34 | pairs of directories. For example for the value 35 | .L /a:/b,/c:/d 36 | the files under 37 | .L /a 38 | (resp. 39 | .L /c 40 | ) 41 | on Plan 9 will be available under 42 | .L /b 43 | (resp. 44 | .L /d 45 | ) to the Linux process. The directories must exist and be 46 | permissioned correctly. 47 | .TP 48 | .B command, HOST.command 49 | The command that should be run if none is provided on the 50 | .IR lx 51 | command line. Single quotes may be used. 52 | .TP 53 | .B port, HOST.port 54 | The port to listen on \- defaults to 9000 55 | .SH EXAMPLE 56 | .TP 57 | Assuming the file contains: 58 | .ft L 59 | .nf 60 | default-host=rabbit 61 | minport=3000 62 | maxport=3049 63 | mounts=/usr/roger:/usr/roger 64 | command=rc -il 65 | rabbit.port=7000 66 | turtle.port=8000 67 | turtle.mounts=/usr/roger:/usr/roger,/sys/src:/usr/src/plan9 68 | .fi 69 | 70 | .PP 71 | Then the command 72 | .B lx 73 | will connect to machine 74 | .B rabbit 75 | on port 7000 and run the command 76 | .B rc -il 77 | in a Linux namespace where 78 | .B /usr/roger 79 | refers to the Plan 9 directory 80 | .B /usr/roger. 81 | .PP 82 | The command 83 | .B lx -h turtle 84 | will connect to machine 85 | .B turtle 86 | on port 8000 and run the command 87 | .B rc -il 88 | in a Linux namespace where 89 | .B /usr/roger 90 | refers to the Plan 9 directory 91 | .B /usr/roger, 92 | and 93 | .B /usr/src/plan9 94 | refers to the Plan 9 directory 95 | .B /sys/src. 96 | .PP 97 | In both cases the Plan 9 namespace will be exported by running 98 | exportfs(1) on a port between 3000 and 3049. 99 | .SH SEE ALSO 100 | lx(1) 101 | .PP 102 | See the README for https://github.com/perpen/lx 103 | -------------------------------------------------------------------------------- /procports.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "procports.h" 6 | 7 | // FIXME general - several of these functions could be implemented 8 | // by having a connection iterator, parameterised by a handling 9 | // function?? 10 | 11 | // FIXME only hangs up 1 connection, repeat if needed 12 | // Hangs up a random connection associated with given 13 | // local port. 14 | // -1 on error, sets errstr 15 | int 16 | hanguplocalport(int port) 17 | { 18 | char path[50]; 19 | int conn, fd; 20 | 21 | conn = localportconn(port); 22 | if(conn < 0) return 0; 23 | snprint(path, sizeof(path)-1, "/net/tcp/%d/ctl", conn); 24 | fd = open(path, OWRITE); 25 | if(fd < 0) return -1; 26 | if(write(fd, "hangup", 6) < 0) return -1; 27 | return 0; 28 | } 29 | 30 | // Stores in data the first 100 bytes from /net/tcp/NUM/THING 31 | void 32 | tcpinfo(char data[], int num, char *thing) 33 | { 34 | int fd, n; 35 | char path[50]; 36 | int max = 100; 37 | snprintf(path, sizeof(path)-1, "/net/tcp/%d/%s", num, thing); 38 | fd = open(path, OREAD); 39 | if(fd < 0) sysfatal("conninfo(%d) open %s: %r", num, path); 40 | n = read(fd, data, max); 41 | close(fd); 42 | if(n < 0) sysfatal("conninfo(%d) read %s: %r", num, path); 43 | assert(n <= max); 44 | data[n-1] = '\0'; 45 | } 46 | 47 | struct ConnInfo 48 | { 49 | int n; 50 | char *status; /* Listen|Closed|Established ... */ 51 | char *lsys; /* local system */ 52 | char *lserv; /* local service */ 53 | char *rsys; /* remote system */ 54 | char *rserv; /* remote service */ 55 | char *laddr; /* local address */ 56 | char *raddr; /* remote address */ 57 | }; 58 | typedef struct ConnInfo ConnInfo; 59 | 60 | // Free with freeconninfo() 61 | ConnInfo* 62 | conninfo(int num) 63 | { 64 | char data[100]; 65 | char *p; 66 | ConnInfo* ci = malloc(sizeof(ConnInfo)); 67 | assert(ci); 68 | ci->n = num; 69 | 70 | tcpinfo(data, num, "local"); 71 | ci->laddr = strdup(data); 72 | p = strchr(data, '!'); 73 | *p = '\0'; 74 | ci->lsys = strdup(data); 75 | ci->lserv = strdup(p+1); 76 | 77 | tcpinfo(data, num, "remote"); 78 | ci->raddr = strdup(data); 79 | p = strchr(data, '!'); 80 | *p = '\0'; 81 | ci->rsys = strdup(data); 82 | ci->rserv = strdup(p+1); 83 | 84 | tcpinfo(data, num, "status"); 85 | *strchr(data, ' ') = '\0'; 86 | ci->status = strdup(data); 87 | 88 | return ci; 89 | } 90 | 91 | void 92 | freeconninfo(ConnInfo* ci) 93 | { 94 | if(!ci) return; 95 | free(ci->lsys); 96 | free(ci->lserv); 97 | free(ci->rsys); 98 | free(ci->rserv); 99 | free(ci->laddr); 100 | free(ci->raddr); 101 | free(ci->status); 102 | } 103 | 104 | extern char *linuxhost; 105 | extern char *cbhost; 106 | 107 | // Looks under /net/tcp for conns with status Listen or Established. 108 | // returns malloced array of ints terminated with -1. 109 | int* 110 | portsbusy() 111 | { 112 | Dir *dirs; 113 | int fd; 114 | long count; 115 | int *ports, ports_count = 0; 116 | 117 | fd = open("/net/tcp", OREAD); 118 | if(fd < 0) sysfatal("portsbusy: open: %r"); 119 | count = dirreadall(fd, &dirs); 120 | if(count < 0) sysfatal("portsbusy: dirreadall: %r"); 121 | close(fd); 122 | ports = malloc(count * sizeof(int)); 123 | assert(ports); 124 | for(int i = 0; i < count; i++){ 125 | ConnInfo* ci; 126 | int num; 127 | Dir *dir = &dirs[i]; 128 | char *name = dir->name; 129 | if(!isdigit(*name)) continue; 130 | num = atoi(dir->name); 131 | ci = conninfo(num); 132 | if(strcmp(ci->status, "Listen") == 0 || 133 | strcmp(ci->status, "Established") == 0) { 134 | ports[ports_count++] = atoi(ci->lserv); 135 | } 136 | freeconninfo(ci); 137 | } 138 | free(dirs); 139 | ports = realloc(ports, (ports_count+1)*sizeof(int)); 140 | ports[ports_count] = -1; 141 | return ports; 142 | } 143 | 144 | // FIXME only returns 1 connection 145 | int 146 | localportconn(int port) 147 | { 148 | Dir *dirs; 149 | int fd, result = -1; 150 | long count; 151 | char port_s[10]; 152 | 153 | snprint(port_s, sizeof(port_s), "%d", port); 154 | 155 | fd = open("/net/tcp", OREAD); 156 | if(fd < 0) sysfatal("localportconn: open: %r"); 157 | count = dirreadall(fd, &dirs); 158 | if(count < 0) sysfatal("localportconn: dirreadall: %r"); 159 | close(fd); 160 | for(int i = 0; i < count; i++){ 161 | ConnInfo* ci; 162 | int num; 163 | Dir *dir = &dirs[i]; 164 | char *name = dir->name; 165 | if(!isdigit(*name)) continue; 166 | num = atoi(dir->name); 167 | ci = conninfo(num); 168 | if(strcmp(ci->lserv, port_s) == 0 && 169 | (strcmp(ci->status, "Listen") == 0 || 170 | strcmp(ci->status, "Established") == 0)) 171 | result = num; 172 | freeconninfo(ci); 173 | if(result >= 0) break; 174 | } 175 | free(dirs); 176 | return result; 177 | } 178 | 179 | -------------------------------------------------------------------------------- /lx-dwm/config.def.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | /* appearance */ 4 | static const unsigned int borderpx = 1; /* border pixel of windows */ 5 | static const unsigned int snap = 32; /* snap pixel */ 6 | static const int showbar = 1; /* 0 means no bar */ 7 | static const int topbar = 1; /* 0 means bottom bar */ 8 | static const char *fonts[] = { "monospace:size=10" }; 9 | static const char dmenufont[] = "monospace:size=10"; 10 | static const char col_gray1[] = "#222222"; 11 | static const char col_gray2[] = "#444444"; 12 | static const char col_gray3[] = "#bbbbbb"; 13 | static const char col_gray4[] = "#eeeeee"; 14 | static const char col_cyan[] = "#005577"; 15 | static const char *colors[][3] = { 16 | /* fg bg border */ 17 | [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, 18 | [SchemeSel] = { col_gray4, col_cyan, col_cyan }, 19 | }; 20 | 21 | /* tagging */ 22 | static const char *tags[] = { "1" }; 23 | 24 | static const Rule rules[] = { 25 | /* xprop(1): 26 | * WM_CLASS(STRING) = instance, class 27 | * WM_NAME(STRING) = title 28 | */ 29 | /* class instance title tags mask isfloating monitor */ 30 | { "Gimp", NULL, NULL, 0, 1, -1 }, 31 | { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, 32 | }; 33 | 34 | /* layout(s) */ 35 | static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */ 36 | static const int nmaster = 1; /* number of clients in master area */ 37 | static const int resizehints = 0; /* 1 means respect size hints in tiled resizals */ 38 | 39 | static const Layout layouts[] = { 40 | /* symbol arrange function */ 41 | { "[]=", tile }, /* first entry is default */ 42 | { "><>", NULL }, /* no layout function means floating behavior */ 43 | { "[M]", monocle }, 44 | }; 45 | 46 | /* key definitions */ 47 | #define MODKEY Mod1Mask 48 | 49 | /* helper for spawning shell commands in the pre dwm-5.0 fashion */ 50 | #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } 51 | 52 | /* commands */ 53 | static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ 54 | static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; 55 | static const char *termcmd[] = { "st", NULL }; 56 | 57 | static Key keys[] = { 58 | /* modifier key function argument */ 59 | { MODKEY, XK_p, spawn, {.v = dmenucmd } }, 60 | { MODKEY|ShiftMask, XK_Return, spawn, {.v = termcmd } }, 61 | { MODKEY, XK_b, togglebar, {0} }, 62 | { MODKEY, XK_j, focusstack, {.i = +1 } }, 63 | { MODKEY, XK_k, focusstack, {.i = -1 } }, 64 | { MODKEY, XK_i, incnmaster, {.i = +1 } }, 65 | { MODKEY, XK_d, incnmaster, {.i = -1 } }, 66 | { MODKEY, XK_h, setmfact, {.f = -0.05} }, 67 | { MODKEY, XK_l, setmfact, {.f = +0.05} }, 68 | { MODKEY, XK_Return, zoom, {0} }, 69 | { MODKEY, XK_Tab, view, {0} }, 70 | { MODKEY|ShiftMask, XK_c, killclient, {0} }, 71 | { MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, 72 | { MODKEY, XK_f, setlayout, {.v = &layouts[1]} }, 73 | { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, 74 | { MODKEY, XK_space, setlayout, {0} }, 75 | { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, 76 | { MODKEY, XK_0, view, {.ui = ~0 } }, 77 | { MODKEY|ShiftMask, XK_q, quit, {0} }, 78 | }; 79 | 80 | /* button definitions */ 81 | /* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ 82 | static Button buttons[] = { 83 | /* click event mask button function argument */ 84 | { ClkLtSymbol, 0, Button1, setlayout, {0} }, 85 | { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, 86 | { ClkWinTitle, 0, Button2, zoom, {0} }, 87 | { ClkStatusText, 0, Button1, killclient, {0} }, 88 | { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, 89 | { ClkStatusText, 0, Button3, quit, {0} }, 90 | { ClkClientWin, MODKEY, Button1, movemouse, {0} }, 91 | { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, 92 | { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, 93 | }; 94 | 95 | -------------------------------------------------------------------------------- /lx-dwm/lx-dwm.1: -------------------------------------------------------------------------------- 1 | .TH LX-DWM 1 lx-dwm\-VERSION 2 | .SH NAME 3 | lx-dwm \- dynamic window manager 4 | .SH SYNOPSIS 5 | .B lx-dwm 6 | .RB [ \-v ] 7 | .SH DESCRIPTION 8 | lx-dwm is a dynamic window manager for X. It manages windows in tiled, monocle 9 | and floating layouts. Either layout can be applied dynamically, optimising the 10 | environment for the application in use and the task performed. 11 | .P 12 | In tiled layouts windows are managed in a master and stacking area. The master 13 | area on the left contains one window by default, and the stacking area on the 14 | right contains all other windows. The number of master area windows can be 15 | adjusted from zero to an arbitrary number. In monocle layout all windows are 16 | maximised to the screen size. In floating layout windows can be resized and 17 | moved freely. Dialog windows are always managed floating, regardless of the 18 | layout applied. 19 | .P 20 | Windows are grouped by tags. Each window can be tagged with one or multiple 21 | tags. Selecting certain tags displays all windows with these tags. 22 | .P 23 | Each screen contains a small status bar which displays all available tags, the 24 | layout, the title of the focused window, and the text read from the root window 25 | name property, if the screen is focused. A floating window is indicated with an 26 | empty square and a maximised floating window is indicated with a filled square 27 | before the windows title. The selected tags are indicated with a different 28 | color. The tags of the focused window are indicated with a filled square in the 29 | top left corner. The tags which are applied to one or more windows are 30 | indicated with an empty square in the top left corner. 31 | .P 32 | lx-dwm draws a small border around windows to indicate the focus state. 33 | .SH OPTIONS 34 | .TP 35 | .B \-v 36 | prints version information to standard output, then exits. 37 | .SH USAGE 38 | .SS Status bar 39 | .TP 40 | .B X root window name 41 | is read and displayed in the status text area. It can be set with the 42 | .BR xsetroot (1) 43 | command. 44 | .TP 45 | .B Button1 46 | click on a tag label to display all windows with that tag, click on the layout 47 | label toggles between tiled and floating layout. 48 | .TP 49 | .B Button3 50 | click on a tag label adds/removes all windows with that tag to/from the view. 51 | .TP 52 | .B Mod1\-Button1 53 | click on a tag label applies that tag to the focused window. 54 | .TP 55 | .B Mod1\-Button3 56 | click on a tag label adds/removes that tag to/from the focused window. 57 | .SS Keyboard commands 58 | .TP 59 | .B Mod1\-Shift\-Return 60 | Start 61 | .BR st(1). 62 | .TP 63 | .B Mod1\-p 64 | Spawn 65 | .BR dmenu(1) 66 | for launching other programs. 67 | .TP 68 | .B Mod1\-, 69 | Focus previous screen, if any. 70 | .TP 71 | .B Mod1\-. 72 | Focus next screen, if any. 73 | .TP 74 | .B Mod1\-Shift\-, 75 | Send focused window to previous screen, if any. 76 | .TP 77 | .B Mod1\-Shift\-. 78 | Send focused window to next screen, if any. 79 | .TP 80 | .B Mod1\-b 81 | Toggles bar on and off. 82 | .TP 83 | .B Mod1\-t 84 | Sets tiled layout. 85 | .TP 86 | .B Mod1\-f 87 | Sets floating layout. 88 | .TP 89 | .B Mod1\-m 90 | Sets monocle layout. 91 | .TP 92 | .B Mod1\-space 93 | Toggles between current and previous layout. 94 | .TP 95 | .B Mod1\-j 96 | Focus next window. 97 | .TP 98 | .B Mod1\-k 99 | Focus previous window. 100 | .TP 101 | .B Mod1\-i 102 | Increase number of windows in master area. 103 | .TP 104 | .B Mod1\-d 105 | Decrease number of windows in master area. 106 | .TP 107 | .B Mod1\-l 108 | Increase master area size. 109 | .TP 110 | .B Mod1\-h 111 | Decrease master area size. 112 | .TP 113 | .B Mod1\-Return 114 | Zooms/cycles focused window to/from master area (tiled layouts only). 115 | .TP 116 | .B Mod1\-Shift\-c 117 | Close focused window. 118 | .TP 119 | .B Mod1\-Shift\-space 120 | Toggle focused window between tiled and floating state. 121 | .TP 122 | .B Mod1\-Tab 123 | Toggles to the previously selected tags. 124 | .TP 125 | .B Mod1\-Shift\-[1..n] 126 | Apply nth tag to focused window. 127 | .TP 128 | .B Mod1\-Shift\-0 129 | Apply all tags to focused window. 130 | .TP 131 | .B Mod1\-Control\-Shift\-[1..n] 132 | Add/remove nth tag to/from focused window. 133 | .TP 134 | .B Mod1\-[1..n] 135 | View all windows with nth tag. 136 | .TP 137 | .B Mod1\-0 138 | View all windows with any tag. 139 | .TP 140 | .B Mod1\-Control\-[1..n] 141 | Add/remove all windows with nth tag to/from the view. 142 | .TP 143 | .B Mod1\-Shift\-q 144 | Quit lx-dwm. 145 | .SS Mouse commands 146 | .TP 147 | .B Mod1\-Button1 148 | Move focused window while dragging. Tiled windows will be toggled to the floating state. 149 | .TP 150 | .B Mod1\-Button2 151 | Toggles focused window between floating and tiled state. 152 | .TP 153 | .B Mod1\-Button3 154 | Resize focused window while dragging. Tiled windows will be toggled to the floating state. 155 | .SH CUSTOMIZATION 156 | lx-dwm is customized by creating a custom config.h and (re)compiling the source 157 | code. This keeps it fast, secure and simple. 158 | .SH SEE ALSO 159 | .BR dmenu (1), 160 | .BR st (1) 161 | .SH ISSUES 162 | Java applications which use the XToolkit/XAWT backend may draw grey windows 163 | only. The XToolkit/XAWT backend breaks ICCCM-compliance in recent JDK 1.5 and early 164 | JDK 1.6 versions, because it assumes a reparenting window manager. Possible workarounds 165 | are using JDK 1.4 (which doesn't contain the XToolkit/XAWT backend) or setting the 166 | environment variable 167 | .BR AWT_TOOLKIT=MToolkit 168 | (to use the older Motif backend instead) or running 169 | .B xprop -root -f _NET_WM_NAME 32a -set _NET_WM_NAME LG3D 170 | or 171 | .B wmname LG3D 172 | (to pretend that a non-reparenting window manager is running that the 173 | XToolkit/XAWT backend can recognize) or when using OpenJDK setting the environment variable 174 | .BR _JAVA_AWT_WM_NONREPARENTING=1 . 175 | .SH BUGS 176 | Send all bug reports with a patch to hackers@suckless.org. 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lx - running Linux commands from Plan 9 2 | ======================================= 3 | 4 | `lx` is an attempt at using Linux machines as cpu servers, 5 | providing a user experience analogous to using the Plan 9 6 | command `cpu(1)`: 7 | - The `lx` stdin/out/err are connected to the remote process, and 8 | notes received by `lx` are translated into signals to the 9 | Linux process 10 | - The specific namespace of the Plan 9 client is available 11 | to the Linux process under `/9`; with configuration it is 12 | possible to have some directories available from Linux under 13 | the same path as on Plan 9 (e.g. `$home`). 14 | When possible the working directory is preserved when running 15 | the remote process 16 | - If `lx` is run from a rio window and an X11 client is started, 17 | it will be displayed in this window 18 | 19 | The intent is to leverage the larger range of software available 20 | on Linux without compromising too much the native Plan 9 user 21 | experience. 22 | 23 | No authentication or encryption is implemented, as this is not 24 | necessary for my current use cases. 25 | 26 | I run Linux commands on the host running my Plan 9 kvm, but 27 | it should be possible to use a `vmx` VM instead. 28 | 29 | Use cases / examples 30 | -------------------- 31 | 32 | - `rc` shell: `lx` 33 | - Use Linux commands: `ls -l | lx gawk '{print $4}'` 34 | - Browser: `lx firefox`. For integration with plumber, see 35 | `bin/firefox` 36 | - Write Linux programs from the comfort of Plan 9 37 | - Write Linux programs accessing e.g. `/dev/draw` or `/dev/net`. 38 | Then in a way `lx` can be seen as implementing a new objtype: 39 | uglier, slower and limited, but at least giving us access to 40 | more programming languages than before. 41 | 42 | Mechanism 43 | --------- 44 | 45 | To take a concrete example, here is an approximation of what 46 | happens when I run `lx gcc blah.c` from Plan 9 directory 47 | `/usr/henri/src/blah`: 48 | - The current namespace is exported by calling `exportfs -r /` 49 | - `lx` connects to server `lxsrv` running on Linux, which 50 | creates a detached process to handle the session. 51 | - This process creates a new mount namespace using `unshare(2)` 52 | and mounts the fs exported from Plan 9 on `/9` 53 | - It then bind-mounts `/9/usr/henri` (my home directory) onto 54 | `/usr/henri` 55 | - It chdirs to the working directory of `lx`, i.e. 56 | `/usr/henri/src/blah` 57 | - It runs `gcc blah.c`, with its stdin/out/err connected to the 58 | corresponding file descriptors of the `lx` process. 59 | - Pressing the delete key once (resp. twice, thrice) sends SIGINT 60 | (resp. SIGHUP, SIGKILL) to the Linux process (actually its 61 | process group). 62 | 63 | X11 clients are supported using a VNC server. Each 64 | time `lx` is invoked, a "fake" X11 socket is created. 65 | The Linux command is started with the DISPLAY 66 | environment variable pointing to this socket. If it gets 67 | opened, a VNC server is started on Linux, `vncv` is started 68 | on Plan 9, and traffic is proxied from the proxy 69 | socket to the actual VNC server socket. 70 | 71 | Requirements 72 | ------------ 73 | 74 | - On Linux: Plan 9 from User Space 75 | - On Linux: A VNC server if X11 clients will be run 76 | - Connectivity: the client `lx` needs to connect to the port served 77 | by the Linux server `lxsrv`; The Linux host needs to connect 78 | to a range of ports connected to `exportfs` servers. 79 | 80 | Build 81 | ----- 82 | 83 | #### Bootstrapping build 84 | 85 | - Define the `PLAN9` environment variable pointing to the p9p 86 | base directory, add `$PLAN9/bin` to your `PATH`. 87 | - Clone the repo on both Plan 9 and Linux 88 | - On Plan 9: `mk -f mkfile.clt install` 89 | - On Linux: `mk -f mkfile.srv install` 90 | 91 | #### Further builds 92 | 93 | Once you have a working setup and assuming you cloned the repo 94 | in a bind-mounted directory (via the `mounts` config setting 95 | or `lx` option `-m`), you can build client and server 96 | by running `mk both.install` from Plan 9. 97 | This will use `lx` for the linux-side build. 98 | 99 | Linux Setup 100 | ----------- 101 | 102 | Skip the VNC sections below if you will not be running X11 clients. 103 | 104 | #### Prepare the mount points 105 | ``` 106 | $ sudo mkdir /9 && sudo chown $USER:$USER /9 107 | # Optionally, if you want a bind mount of /9/usr/USER on /usr/USER 108 | # This assumes your username is the same on both sides 109 | $ sudo mkdir /usr/$USER && sudo chown $USER:$USER /usr/$USER 110 | ``` 111 | 112 | #### Install Plan 9 from User Space 113 | The namespace exported from Plan 9 is mounted on Linux 114 | using the `9pfuse` command provided by p9p. 115 | Environment variable `PLAN9` must be defined when starting 116 | `lxsrv`, pointing to the p9p base directory. 117 | 118 | P9p is required as well to build the server executable, simply 119 | because I decided to use the Plan 9-style p9p libraries for 120 | implementing the server. 121 | 122 | #### Running rc on Linux 123 | For commands `"` and `""` to work, you can add at the beginning 124 | of `$PLAN9/bin/wintext` the following line: 125 | ``` 126 | [ -f /9/dev/text ] && exec cat /9/dev/text 127 | ``` 128 | 129 | #### VNC server 130 | `lx` has only been tested with `tigervnc`, but other implementations 131 | are expected to work with little or no code change. 132 | 133 | Once you have confirmed X11 clients work with `lx`, you can 134 | eliminate an overlong hardcoded wait for the VNC server: 135 | In the script `/usr/bin/tigervnc` I changed line 136 | ``` 137 | sleep(3); 138 | ``` 139 | into 140 | ``` 141 | use Time::HiRes qw(usleep); usleep(200000); 142 | ``` 143 | If this sleep is too short, you will get an error like 144 | `/tmp/.X11-unix/X212: No such file or directory` 145 | 146 | #### VNC startup file 147 | Create/edit `~/.vnc/xstartup` with this content: 148 | ``` 149 | #!/bin/sh 150 | unset SESSION_MANAGER 151 | unset DBUS_SESSION_BUS_ADDRESS 152 | xsetroot -solid grey 153 | xhost +local: 154 | lx-dwm # or any other window manager 155 | ``` 156 | 157 | #### Window manager 158 | The VNC startup file must run a window manager. Any such 159 | program can be used, but a version of `dwm` is provided 160 | here, which hides unnecessary tag buttons and 161 | provides another to close the currently selected 162 | client. To allow coexistence with an existing `dwm` 163 | it is installed as `lx-dwm`. 164 | 165 | It will be installed along with `lxsrv` when running the `mk` 166 | command mentioned in the Build section above. 167 | 168 | Plan 9 setup 169 | ------------ 170 | 171 | `lx` reads its configuration from path `$home/lib/lx`. 172 | 173 | Here is an example allowing connection to a single Linux host 174 | called `rabbit`: 175 | ``` 176 | default-host=rabbit 177 | minport=3000 178 | maxport=3049 179 | mounts=/usr/roger:/usr/roger 180 | command=rc -il 181 | ``` 182 | If more customisation is required, refer to man page `lx(6)`. 183 | 184 | Bugs 185 | ---- 186 | 187 | `lx` has only been tested in one environment and with a few 188 | commands. These are the known problems: 189 | - If the `lx` client is slayed, a few processes are leaked 190 | - If an X11 client launched directly from `lx` prints to 191 | stdout/stderr (e.g. `xev`), the messages will corrupt the 192 | `vncv` display. Redirect the output if this is a problem. 193 | - The p9p graphical programs do not work, because they do not work 194 | with VNC server (try others?) 195 | - Some X11 clients (only `llpp` so far) insist on connecting to 196 | the X server using an abstract socket. But our proxy socket 197 | is created using p9p's `announce`, which does not support 198 | abstract. To fix this we'd need to use the linux `socket` 199 | syscall directly. 200 | - Passing environment variables to a Linux command is currently 201 | difficult and requires clunky command lines like 202 | `lx bash -c 'X=1; echo $X'`. 203 | - ~.6s lag on each `lx` invocation, due to running `exportfs` 204 | and `9pfuse` each time: 205 | If that ever became a problem for some use case 206 | it should be possible to maintain a persistent session, e.g. 207 | from a Plan 9 fs. 208 | - The p9p command `mc` does not know the width of the Plan 9 209 | window, and uses the width of the terminal that `lxsrv` was 210 | started from. 211 | - Insecure: no auth on server request; no encryption. 212 | 213 | ### Workaround for filesystem issues 214 | 215 | Some Linux commands do file operations not supported on Plan 216 | 9 (symlinks, lockfiles), or not supported from a Linux mount 217 | namespace (Linux `rename(2)`) (FIXME wasn't it, test with 218 | simple program) 219 | 220 | I encountered such problems with `go`, `rustc`, `git`. 221 | 222 | The only solution is to run such commands on the native 223 | Linux fs. To still get decent integration with Plan 9, I mount 224 | my Linux home directory on Plan 9 using 225 | [u9fs](https://bitbucket.org/plan9-from-bell-labs/u9fs/src/master/) 226 | onto the same path as on Linux, so `/home/henri`. 227 | Then if for example I do rust work, I run acme and shells 228 | from `/home/henri` instead of `/usr/henri`, and from there 229 | `lx cargo` works as expected. Not elegant but it works. 230 | 231 | For this I installed u9fs on Linux, and I added the following 232 | to my Plan 9 `$lib/profile`: 233 | ``` 234 | aux/stub -d /home 235 | srv tcp!mylinuxhost!49151 u9fs # not using 9fs b/c of my custom port 236 | mkdir -p /mnt/u9fs 237 | mount -c /srv/u9fs /mnt/u9fs 238 | bind /mnt/u9fs/home /home 239 | ``` 240 | 241 | Because you will forget to type `lx` before `go` or `cargo`, 242 | you can create e.g. a `go` script which invokes `lx go`. See 243 | example scripts under `./bin`. 244 | 245 | Note that `git9` seems to be working fine on u9fs. 246 | 247 | Troubleshooting 248 | --------------- 249 | 250 | Using `lx` option `-d` will show debugging information that can 251 | help troubleshoot problems. 252 | 253 | The client and server maintain session log files 254 | `/tmp/lx.$user/*.log`, resp. 255 | `/tmp/lxsrv.$user/*.log` which contain the 256 | same information printed out by the client when run with 257 | option `-d`, plus some events occurring when there is no established 258 | connection to the client's stderr. If you are having a problem with 259 | VNC you may want to check its log under `~/.vnc`. 260 | 261 | `lx` does not provide a pty - if you want a unix shell session or 262 | terminal emulation, use `ssh(1)` and `vt(1)`. 263 | 264 | `can't open display`: it may be that 265 | the VNC server is too slow to start. Increase VNCWAITMS in 266 | lxsrv.c. Note that this will increase the delay before get- 267 | ting the X11 client displayed. 268 | 269 | `unable to find a free vnc proxy port`: you killed the server 270 | with SIGKILL or you encountered a bug. You can run on Linux the 271 | command `rm /tmp/.X11-unix/X1??` to delete all VNC proxy sockets. 272 | This may break X11 clients currently running under`lx`. 273 | 274 | The VNC proxy sockets are `/tmp/.X11-unix/Xn`, with 100 <= n <= 199; 275 | the real VNC sockets have 200 <= n <= 299. 276 | 277 | Hacking 278 | ------- 279 | 280 | This has been tested on a 9front kvm running on Arch Linux. 281 | 282 | When working on `lx` I concurrently use two versions: 283 | a stable version, built from the master branch; and a dev version, 284 | built from another branch, in which the binaries have different 285 | names. This allows me to use the stable version to build and 286 | run the dev version. 287 | 288 | The name `lx` is actually read from file `./name`. When working 289 | on the dev branch, this file contains the string `lx2`, so that 290 | the deployed binaries are `lx2` and `lx2srv`. 291 | 292 | I keep two windows open: 293 | - One running `mk watch`, to watch the files for change and 294 | trigger rebuilds 295 | - Another running `mk srvloop`, which continuously runs `lx2srv` 296 | on port 8000, so as not to conflict with `lxsrv` which uses the 297 | default port of 9000. FIXME 9pfuse breaks if master lxsrv is 298 | already running?? Until fixed run from Linux: 299 | ``while(){ echo '####' `{date}; lx2srv -p 8000 || sleep 1 }`` 300 | -------------------------------------------------------------------------------- /lx-dwm/drw.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "drw.h" 9 | #include "util.h" 10 | 11 | #define UTF_INVALID 0xFFFD 12 | #define UTF_SIZ 4 13 | 14 | static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 15 | static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 16 | static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 17 | static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 18 | 19 | static long 20 | utf8decodebyte(const char c, size_t *i) 21 | { 22 | for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) 23 | if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) 24 | return (unsigned char)c & ~utfmask[*i]; 25 | return 0; 26 | } 27 | 28 | static size_t 29 | utf8validate(long *u, size_t i) 30 | { 31 | if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 32 | *u = UTF_INVALID; 33 | for (i = 1; *u > utfmax[i]; ++i) 34 | ; 35 | return i; 36 | } 37 | 38 | static size_t 39 | utf8decode(const char *c, long *u, size_t clen) 40 | { 41 | size_t i, j, len, type; 42 | long udecoded; 43 | 44 | *u = UTF_INVALID; 45 | if (!clen) 46 | return 0; 47 | udecoded = utf8decodebyte(c[0], &len); 48 | if (!BETWEEN(len, 1, UTF_SIZ)) 49 | return 1; 50 | for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { 51 | udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 52 | if (type) 53 | return j; 54 | } 55 | if (j < len) 56 | return 0; 57 | *u = udecoded; 58 | utf8validate(u, len); 59 | 60 | return len; 61 | } 62 | 63 | Drw * 64 | drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) 65 | { 66 | Drw *drw = ecalloc(1, sizeof(Drw)); 67 | 68 | drw->dpy = dpy; 69 | drw->screen = screen; 70 | drw->root = root; 71 | drw->w = w; 72 | drw->h = h; 73 | drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); 74 | drw->gc = XCreateGC(dpy, root, 0, NULL); 75 | XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); 76 | 77 | return drw; 78 | } 79 | 80 | void 81 | drw_resize(Drw *drw, unsigned int w, unsigned int h) 82 | { 83 | if (!drw) 84 | return; 85 | 86 | drw->w = w; 87 | drw->h = h; 88 | if (drw->drawable) 89 | XFreePixmap(drw->dpy, drw->drawable); 90 | drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); 91 | } 92 | 93 | void 94 | drw_free(Drw *drw) 95 | { 96 | XFreePixmap(drw->dpy, drw->drawable); 97 | XFreeGC(drw->dpy, drw->gc); 98 | free(drw); 99 | } 100 | 101 | /* This function is an implementation detail. Library users should use 102 | * drw_fontset_create instead. 103 | */ 104 | static Fnt * 105 | xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) 106 | { 107 | Fnt *font; 108 | XftFont *xfont = NULL; 109 | FcPattern *pattern = NULL; 110 | 111 | if (fontname) { 112 | /* Using the pattern found at font->xfont->pattern does not yield the 113 | * same substitution results as using the pattern returned by 114 | * FcNameParse; using the latter results in the desired fallback 115 | * behaviour whereas the former just results in missing-character 116 | * rectangles being drawn, at least with some fonts. */ 117 | if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { 118 | fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); 119 | return NULL; 120 | } 121 | if (!(pattern = FcNameParse((FcChar8 *) fontname))) { 122 | fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); 123 | XftFontClose(drw->dpy, xfont); 124 | return NULL; 125 | } 126 | } else if (fontpattern) { 127 | if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { 128 | fprintf(stderr, "error, cannot load font from pattern.\n"); 129 | return NULL; 130 | } 131 | } else { 132 | die("no font specified."); 133 | } 134 | 135 | /* Do not allow using color fonts. This is a workaround for a BadLength 136 | * error from Xft with color glyphs. Modelled on the Xterm workaround. See 137 | * https://bugzilla.redhat.com/show_bug.cgi?id=1498269 138 | * https://lists.suckless.org/dev/1701/30932.html 139 | * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349 140 | * and lots more all over the internet. 141 | */ 142 | FcBool iscol; 143 | if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) { 144 | XftFontClose(drw->dpy, xfont); 145 | return NULL; 146 | } 147 | 148 | font = ecalloc(1, sizeof(Fnt)); 149 | font->xfont = xfont; 150 | font->pattern = pattern; 151 | font->h = xfont->ascent + xfont->descent; 152 | font->dpy = drw->dpy; 153 | 154 | return font; 155 | } 156 | 157 | static void 158 | xfont_free(Fnt *font) 159 | { 160 | if (!font) 161 | return; 162 | if (font->pattern) 163 | FcPatternDestroy(font->pattern); 164 | XftFontClose(font->dpy, font->xfont); 165 | free(font); 166 | } 167 | 168 | Fnt* 169 | drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) 170 | { 171 | Fnt *cur, *ret = NULL; 172 | size_t i; 173 | 174 | if (!drw || !fonts) 175 | return NULL; 176 | 177 | for (i = 1; i <= fontcount; i++) { 178 | if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { 179 | cur->next = ret; 180 | ret = cur; 181 | } 182 | } 183 | return (drw->fonts = ret); 184 | } 185 | 186 | void 187 | drw_fontset_free(Fnt *font) 188 | { 189 | if (font) { 190 | drw_fontset_free(font->next); 191 | xfont_free(font); 192 | } 193 | } 194 | 195 | void 196 | drw_clr_create(Drw *drw, Clr *dest, const char *clrname) 197 | { 198 | if (!drw || !dest || !clrname) 199 | return; 200 | 201 | if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), 202 | DefaultColormap(drw->dpy, drw->screen), 203 | clrname, dest)) 204 | die("error, cannot allocate color '%s'", clrname); 205 | } 206 | 207 | /* Wrapper to create color schemes. The caller has to call free(3) on the 208 | * returned color scheme when done using it. */ 209 | Clr * 210 | drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) 211 | { 212 | size_t i; 213 | Clr *ret; 214 | 215 | /* need at least two colors for a scheme */ 216 | if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) 217 | return NULL; 218 | 219 | for (i = 0; i < clrcount; i++) 220 | drw_clr_create(drw, &ret[i], clrnames[i]); 221 | return ret; 222 | } 223 | 224 | void 225 | drw_setfontset(Drw *drw, Fnt *set) 226 | { 227 | if (drw) 228 | drw->fonts = set; 229 | } 230 | 231 | void 232 | drw_setscheme(Drw *drw, Clr *scm) 233 | { 234 | if (drw) 235 | drw->scheme = scm; 236 | } 237 | 238 | void 239 | drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) 240 | { 241 | if (!drw || !drw->scheme) 242 | return; 243 | XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); 244 | if (filled) 245 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 246 | else 247 | XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); 248 | } 249 | 250 | int 251 | drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) 252 | { 253 | char buf[1024]; 254 | int ty; 255 | unsigned int ew; 256 | XftDraw *d = NULL; 257 | Fnt *usedfont, *curfont, *nextfont; 258 | size_t i, len; 259 | int utf8strlen, utf8charlen, render = x || y || w || h; 260 | long utf8codepoint = 0; 261 | const char *utf8str; 262 | FcCharSet *fccharset; 263 | FcPattern *fcpattern; 264 | FcPattern *match; 265 | XftResult result; 266 | int charexists = 0; 267 | 268 | if (!drw || (render && !drw->scheme) || !text || !drw->fonts) 269 | return 0; 270 | 271 | if (!render) { 272 | w = ~w; 273 | } else { 274 | XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); 275 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 276 | d = XftDrawCreate(drw->dpy, drw->drawable, 277 | DefaultVisual(drw->dpy, drw->screen), 278 | DefaultColormap(drw->dpy, drw->screen)); 279 | x += lpad; 280 | w -= lpad; 281 | } 282 | 283 | usedfont = drw->fonts; 284 | while (1) { 285 | utf8strlen = 0; 286 | utf8str = text; 287 | nextfont = NULL; 288 | while (*text) { 289 | utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); 290 | for (curfont = drw->fonts; curfont; curfont = curfont->next) { 291 | charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); 292 | if (charexists) { 293 | if (curfont == usedfont) { 294 | utf8strlen += utf8charlen; 295 | text += utf8charlen; 296 | } else { 297 | nextfont = curfont; 298 | } 299 | break; 300 | } 301 | } 302 | 303 | if (!charexists || nextfont) 304 | break; 305 | else 306 | charexists = 0; 307 | } 308 | 309 | if (utf8strlen) { 310 | drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); 311 | /* shorten text if necessary */ 312 | for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) 313 | drw_font_getexts(usedfont, utf8str, len, &ew, NULL); 314 | 315 | if (len) { 316 | memcpy(buf, utf8str, len); 317 | buf[len] = '\0'; 318 | if (len < utf8strlen) 319 | for (i = len; i && i > len - 3; buf[--i] = '.') 320 | ; /* NOP */ 321 | 322 | if (render) { 323 | ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; 324 | XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], 325 | usedfont->xfont, x, ty, (XftChar8 *)buf, len); 326 | } 327 | x += ew; 328 | w -= ew; 329 | } 330 | } 331 | 332 | if (!*text) { 333 | break; 334 | } else if (nextfont) { 335 | charexists = 0; 336 | usedfont = nextfont; 337 | } else { 338 | /* Regardless of whether or not a fallback font is found, the 339 | * character must be drawn. */ 340 | charexists = 1; 341 | 342 | fccharset = FcCharSetCreate(); 343 | FcCharSetAddChar(fccharset, utf8codepoint); 344 | 345 | if (!drw->fonts->pattern) { 346 | /* Refer to the comment in xfont_create for more information. */ 347 | die("the first font in the cache must be loaded from a font string."); 348 | } 349 | 350 | fcpattern = FcPatternDuplicate(drw->fonts->pattern); 351 | FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); 352 | FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); 353 | FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); 354 | 355 | FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); 356 | FcDefaultSubstitute(fcpattern); 357 | match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); 358 | 359 | FcCharSetDestroy(fccharset); 360 | FcPatternDestroy(fcpattern); 361 | 362 | if (match) { 363 | usedfont = xfont_create(drw, NULL, match); 364 | if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { 365 | for (curfont = drw->fonts; curfont->next; curfont = curfont->next) 366 | ; /* NOP */ 367 | curfont->next = usedfont; 368 | } else { 369 | xfont_free(usedfont); 370 | usedfont = drw->fonts; 371 | } 372 | } 373 | } 374 | } 375 | if (d) 376 | XftDrawDestroy(d); 377 | 378 | return x + (render ? w : 0); 379 | } 380 | 381 | void 382 | drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) 383 | { 384 | if (!drw) 385 | return; 386 | 387 | XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); 388 | XSync(drw->dpy, False); 389 | } 390 | 391 | unsigned int 392 | drw_fontset_getwidth(Drw *drw, const char *text) 393 | { 394 | if (!drw || !drw->fonts || !text) 395 | return 0; 396 | return drw_text(drw, 0, 0, 0, 0, 0, text, 0); 397 | } 398 | 399 | void 400 | drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) 401 | { 402 | XGlyphInfo ext; 403 | 404 | if (!font || !text) 405 | return; 406 | 407 | XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); 408 | if (w) 409 | *w = ext.xOff; 410 | if (h) 411 | *h = font->h; 412 | } 413 | 414 | Cur * 415 | drw_cur_create(Drw *drw, int shape) 416 | { 417 | Cur *cur; 418 | 419 | if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) 420 | return NULL; 421 | 422 | cur->cursor = XCreateFontCursor(drw->dpy, shape); 423 | 424 | return cur; 425 | } 426 | 427 | void 428 | drw_cur_free(Drw *drw, Cur *cursor) 429 | { 430 | if (!cursor) 431 | return; 432 | 433 | XFreeCursor(drw->dpy, cursor->cursor); 434 | free(cursor); 435 | } 436 | -------------------------------------------------------------------------------- /lx.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "procports.h" 9 | 10 | #define FDOFF 20 11 | #define CONNTIMEOUTMS 2000 12 | 13 | void* emalloc(int n); 14 | int edup(int a, int b); 15 | int esnprint(char *tgt, int max, char *fmt, ...); 16 | void _dbg(int iserror, char *format, ...); 17 | void logsink(void *arg); 18 | void usage(void); 19 | void configlock(int take); 20 | int notehandle(void*, char *note); 21 | char* handlemsg(char *msg); 22 | void lxsend(char *msg); 23 | int getport(void); 24 | void startfs(void); 25 | void stopfs(void); 26 | char* remote(void); 27 | void vncviewer(int dpy); 28 | void parseconf(char *path); 29 | void config(int argc, char **argv); 30 | 31 | struct { 32 | int minport, maxport, fsport, mainfd, cmdlen, 33 | checkcwd, debug, dbgfd, lckfd; 34 | char *srvhost, *srvport, *cbhost, *defcmd, *defmounts, 35 | *cwd, *mounts, *progname, *tmpdir; 36 | char **cmd; 37 | } g; 38 | 39 | struct { 40 | int pid; // only this thread handles notes 41 | int count; 42 | vlong lasttime; 43 | } notectx; 44 | 45 | // Writes to log file. 46 | // Will be printed to stderr as well if it's an error msg or 47 | // in debug mode. 48 | void 49 | _dbg(int iserror, char *format, ...) 50 | { 51 | va_list arg; 52 | char buf[1000]; 53 | char *prefix = "client: "; 54 | int prelen = strlen(prefix); 55 | strcpy(buf, prefix); 56 | va_start(arg, format); 57 | vsnprint(buf + prelen, sizeof buf - prelen - 1, format, arg); 58 | va_end(arg); 59 | if(g.dbgfd != -1 && g.dbgfd != 2) fprint(g.dbgfd, buf); 60 | if(g.debug || iserror) fprint(2, buf); 61 | } 62 | 63 | #define dbg(...) do{ _dbg(0, __VA_ARGS__); }while(0) 64 | #define error(...) do{ _dbg(1, __VA_ARGS__); }while(0) 65 | 66 | void* 67 | emalloc(int n) 68 | { 69 | void *v = mallocz(n, 1); 70 | if(v == nil) sysfatal("out of memory allocating %d", n); 71 | return v; 72 | } 73 | 74 | int 75 | edup(int a, int b) 76 | { 77 | int x = dup(a, b); 78 | if(x < 0) sysfatal("dup: %r"); 79 | return x; 80 | } 81 | 82 | int 83 | esnprint(char *tgt, int max, char *fmt, ...) 84 | { 85 | va_list arg; 86 | int n; 87 | va_start(arg, fmt); 88 | n = vsnprint(tgt, max, fmt, arg); 89 | if(n == max) sysfatal("formatted string too long: '%s'", fmt); 90 | return n; 91 | } 92 | 93 | // Makes dir if doesn't exist, aborts on error 94 | void 95 | ensuredir(char *s, int mode) 96 | { 97 | int f; 98 | if(access(s, AEXIST) == 0) return; 99 | f = create(s, OREAD, DMDIR | mode); 100 | if(f < 0) sysfatal("ensuredir: can't create %s: %r\n", s); 101 | close(f); 102 | } 103 | 104 | void 105 | logsink(void *arg) 106 | { 107 | int infd = *(int*)arg, outfd; 108 | Ioproc *io = ioproc(); 109 | char outpath[1000]; 110 | 111 | esnprint(outpath, sizeof outpath, "%s/%s.%d.log", 112 | g.tmpdir, g.srvhost, g.fsport); 113 | outfd = create(outpath, OWRITE, 0600|DMAPPEND); 114 | if(outfd < 0) sysfatal("cannot create %s: %r", outpath); 115 | 116 | for(;;){ 117 | char buf[100]; 118 | int n = ioread(io, infd, buf, sizeof(buf)-1); 119 | if (n < 0){ 120 | fprint(2, "logsink: read: %r\n"); 121 | break; 122 | } 123 | if(n == 0) break; 124 | int w = iowrite(io, outfd, buf, n); 125 | if(w != n) 126 | fprint(2, "logsink: wrote %d instead of %d\n", w, n); 127 | } 128 | closeioproc(io); 129 | } 130 | 131 | // Sends message to server 132 | void 133 | lxsend(char *msg) 134 | { 135 | int n; 136 | dbg("lxsend '%s'\n", msg); 137 | if(g.mainfd < 0) return; 138 | n = write(g.mainfd, msg, strlen(msg)); 139 | if(n != strlen(msg)) sysfatal("lxsend: unable to send '%s'", msg); 140 | } 141 | 142 | // Only notes against the main thread are handled 143 | int 144 | notehandle(void*, char *note) 145 | { 146 | long ns = 1000000000/3; // third of a second 147 | vlong t; 148 | 149 | if(getpid() != notectx.pid) return 1; 150 | if(g.mainfd == -1 && strcmp(note, "alarm") == 0) 151 | sysfatal("connection timed out"); 152 | if(strcmp(note, "interrupt") != 0) return 1; 153 | dbg("notehandle: pid=%d note %s\n", getpid(), note); 154 | 155 | t = nsec(); 156 | if(t - notectx.lasttime > ns){ 157 | notectx.count = 0; 158 | notectx.lasttime = t; 159 | } 160 | notectx.count++; 161 | switch(notectx.count){ 162 | case 1: 163 | lxsend("int"); 164 | break; 165 | case 2: 166 | fprint(2, "sending SIGHUP\n"); 167 | lxsend("hup"); 168 | notectx.lasttime = t; 169 | break; 170 | case 3: 171 | fprint(2, "sending SIGKILL\n"); 172 | lxsend("kill"); 173 | break; 174 | } 175 | return 1; 176 | } 177 | 178 | void 179 | vncviewer(int dpy) 180 | { 181 | char vncaddr[50]; 182 | esnprint(vncaddr, sizeof(vncaddr)-1, "%s:%d", g.srvhost, dpy); 183 | 184 | switch(fork()){ 185 | case -1: 186 | sysfatal("vncviewer: fork: %r"); 187 | case 0: 188 | dbg("vncviewer: vnc addr=%s\n", vncaddr); 189 | edup(g.dbgfd, 1); 190 | edup(g.dbgfd, 2); 191 | execl("/bin/vncv", "vncv", vncaddr, NULL); 192 | sysfatal("vncviewer: exec vncv %s: %r", vncaddr); 193 | } 194 | } 195 | 196 | // Handles message from server 197 | // Returns the exit message if the remote process exited, else nil 198 | char* 199 | handlemsg(char *msg) 200 | { 201 | char buf[200]; 202 | int dpy; 203 | 204 | // dbg("handlemsg: received '%s'\n", msg); 205 | if(strlen(msg) >= sizeof(buf)-1) 206 | error("handlemsg: msg longer than %d\n", sizeof buf); 207 | 208 | if(sscanf(msg, "vnc %d", &dpy) == 1) 209 | vncviewer(dpy); 210 | else if(sscanf(msg, "exit %s", buf) == 1) 211 | return strdup((strcmp(msg, "exit 0") == 0) ? "" : msg); 212 | else if(sscanf(msg, "fatal: %s", buf) == 1){ 213 | fprint(2, "%s: %s\n", g.progname, msg); 214 | return strdup(msg); 215 | }else 216 | fprint(2, "%s: bug: malformed message: %s\n", 217 | g.progname, msg); 218 | return nil; 219 | } 220 | 221 | // Comms with server 222 | // Returns the remote command exit message 223 | char* 224 | remote(void) 225 | { 226 | Ioproc *io = ioproc(); 227 | char *addr, *exitmsg = nil; 228 | 229 | addr = netmkaddr(g.srvhost, nil, g.srvport); 230 | g.mainfd = -1; 231 | alarm(CONNTIMEOUTMS); 232 | g.mainfd = dial(addr, nil, nil, nil); 233 | if(g.mainfd < 0) sysfatal("remote: dial %s: %r", addr); 234 | 235 | // push params and command 236 | fprint(g.mainfd, "%q\n%d\n%d\n%q\n%d\n%q\n", 237 | g.cbhost, g.fsport, g.debug, g.cwd, g.checkcwd, g.mounts); 238 | for(int i = 0; i < g.cmdlen; i++) 239 | fprint(g.mainfd, "%s\n", g.cmd[i]); 240 | fprint(g.mainfd, "LXEND\n"); 241 | 242 | for(;;){ 243 | // dbg("remote: read loop\n"); 244 | char buf[200]; 245 | int n = ioread(io, g.mainfd, buf, sizeof(buf)-1); 246 | if(n <= 0){ 247 | // Don't leave the loop if interrupted by a note, 248 | char err[ERRMAX] = {0}; 249 | errstr(err, sizeof err); 250 | dbg("remote: read err='%s'\n", err); 251 | if(*err && strcmp(err, "interrupted")) break; 252 | continue; 253 | } 254 | buf[n] = '\0'; 255 | if(n == sizeof(buf)) sysfatal("message too big: '%s'", buf); 256 | dbg("remote: received '%s'\n", buf); 257 | exitmsg = handlemsg(buf); 258 | if(exitmsg != nil) break; 259 | } 260 | dbg("remote: post-read\n"); 261 | close(g.mainfd); 262 | closeioproc(io); 263 | return exitmsg; 264 | } 265 | 266 | // sysfatal if can't get port 267 | int 268 | getport(void) 269 | { 270 | int *busyports; 271 | int port; 272 | configlock(1); 273 | busyports = portsbusy(); 274 | for(port = g.minport; port <= g.maxport+1; port++){ 275 | int busy = 0; 276 | for(int *busyp = busyports; *busyp != -1; busyp++){ 277 | if(*busyp == port){ 278 | busy = 1; 279 | break; 280 | } 281 | } 282 | if(!busy) break; 283 | } 284 | free(busyports); 285 | configlock(0); 286 | 287 | if(port > g.maxport) sysfatal("no available port"); 288 | return port; 289 | } 290 | 291 | // Listen for exportfs connection 292 | void 293 | startfs(void) 294 | { 295 | char addr[32]; 296 | switch(fork()){ 297 | case -1: 298 | sysfatal("startfs: fork: %r"); 299 | case 0: 300 | esnprint(addr, sizeof(addr), "tcp!*!%d", g.fsport); 301 | dbg("startfs: listen1 on %s\n", addr); 302 | edup(g.dbgfd, 1); 303 | edup(g.dbgfd, 2); 304 | execl("/bin/aux/listen1", "aux/listen1", "-tv1", addr, 305 | "/bin/exportfs", "-r", "/", 306 | NULL); 307 | sysfatal("startfs: exec: %r"); 308 | } 309 | } 310 | 311 | void 312 | stopfs(void) 313 | { 314 | dbg("stopfs\n"); 315 | if(getpid() != notectx.pid) return; 316 | if(g.fsport >= 0 && hanguplocalport(g.fsport) < 0) 317 | sysfatal("cannot hangup exportfs conn: %r"); 318 | } 319 | 320 | // Pass 1 to take, 0 to release 321 | void 322 | configlock(int take) 323 | { 324 | char path[40]; 325 | esnprint(path, sizeof(path), "%s/lock", g.tmpdir); 326 | if(take){ 327 | assert(g.lckfd == -1); 328 | // Try for 10s at least 329 | for(int i = 0; i < 200; i++){ 330 | g.lckfd = create(path, OREAD, 0600|DMEXCL); 331 | if(g.lckfd >= 0) break; 332 | sleep(50); 333 | } 334 | if(g.lckfd < 0) 335 | sysfatal("configlock: timed out waiting %s", path); 336 | }else{ 337 | assert(g.lckfd != -1); 338 | close(g.lckfd); 339 | g.lckfd = -1; 340 | } 341 | } 342 | 343 | // sysfatal on parsing error or missing config entry 344 | void 345 | parseconf(char *path) 346 | { 347 | char *line; 348 | Reprog *rx = regcomp("^((([^=]+)(\\.))?([^.=]+))(=)(.*)$"); 349 | assert(rx); 350 | 351 | // First pass for default values, second for host overrides 352 | for(int pass = 1; pass <= 2; pass++){ 353 | Biobuf* bbuf = Bopen(path, OREAD); 354 | if(bbuf == nil) sysfatal("config Bopen: %r"); 355 | while(line = Brdline(bbuf, '\n')){ 356 | Resub match[10]; 357 | int len; 358 | char *host, *name, *val; 359 | 360 | if(line[0] == '#') continue; 361 | len = Blinelen(bbuf); 362 | line[len-1] = '\0'; 363 | 364 | memset(match, 0, 10*sizeof(Resub)); 365 | if(regexec(rx, line, match, 8) != 1) 366 | sysfatal("invalid line in %s: %s", path, line); 367 | if(match[4].sp != nil) *match[4].sp = '\0'; 368 | *match[6].sp = '\0'; 369 | host = match[3].sp; 370 | name = match[5].sp; 371 | val = match[7].sp; 372 | 373 | if(pass == 1 && host != nil) 374 | continue; 375 | if(pass == 2 && 376 | (host == nil || strcmp(host, g.srvhost) != 0)) 377 | continue; 378 | 379 | if(strcmp(name, "default-host") == 0 && g.srvhost == nil) 380 | g.srvhost = strdup(val); 381 | else if(strcmp(name, "mounts") == 0) 382 | g.defmounts = strdup(val); 383 | else if(strcmp(name, "port") == 0) 384 | g.srvport = strdup(val); 385 | else if(strcmp(name, "minport") == 0) 386 | g.minport = atoi(val); 387 | else if(strcmp(name, "maxport") == 0) 388 | g.maxport = atoi(val); 389 | else if(strcmp(name, "command") == 0) 390 | g.defcmd = strdup(val); 391 | else if(strcmp(name, "callback-host") == 0) 392 | g.cbhost = strdup(val); 393 | } 394 | Bterm(bbuf); 395 | } 396 | 397 | if(g.defmounts == nil) sysfatal("missing mounts config"); 398 | if(g.srvport == 0) sysfatal("missing port config"); 399 | if(g.minport == 0) sysfatal("missing minport config"); 400 | if(g.maxport == 0) sysfatal("missing maxport config"); 401 | if(g.defcmd == nil) sysfatal("missing command config"); 402 | // if(g.cbhost == nil) sysfatal("missing callback-host config"); 403 | 404 | free(rx); 405 | } 406 | 407 | // Parses command line and $home/lib/lx, populates global vars 408 | void 409 | config(int argc, char **argv) 410 | { 411 | char *home, *slash; 412 | char path[100], cwd2[1000]; 413 | g.srvhost = nil; 414 | 415 | ARGBEGIN { 416 | default: 417 | usage(); 418 | case 'd': 419 | g.debug = 1; 420 | break; 421 | case 'D': 422 | g.checkcwd = 1; 423 | break; 424 | case 'h': 425 | g.srvhost = strdup(EARGF(usage())); 426 | break; 427 | case 'c': 428 | g.cwd = strdup(EARGF(usage())); 429 | break; 430 | case 'm': 431 | g.mounts = strdup(EARGF(usage())); 432 | break; 433 | } ARGEND 434 | 435 | slash = strrchr(argv0, '/'); 436 | if(slash != nil) 437 | g.progname = slash + 1; 438 | else 439 | g.progname = argv0; 440 | 441 | if(g.cwd == nil){ 442 | if(getwd(cwd2, sizeof(cwd2)-1) == nil) 443 | sysfatal("config getwd: %r"); 444 | g.cwd = strdup(cwd2); 445 | } 446 | 447 | home = getenv("home"); 448 | esnprint(path, sizeof(path), "%s/lib/%s", home, g.progname); 449 | free(home); 450 | parseconf(path); 451 | 452 | if(g.cbhost == nil){ 453 | char buf[20]; 454 | int n, fd = open("/dev/sysname", OREAD); 455 | if(fd < 0) sysfatal("cannot open /dev/sysname: %r"); 456 | n = read(fd, buf, sizeof buf - 1); 457 | if(n < 0) 458 | sysfatal("cannot read /dev/sysname: %r"); 459 | buf[n] = '\0'; 460 | g.cbhost = strdup(buf); 461 | } 462 | if(g.mounts == nil) g.mounts = g.defmounts; 463 | 464 | if(argc > 0){ 465 | g.cmd = malloc(argc * sizeof(char*)); 466 | assert(g.cmd); 467 | for(int i = 0; i < argc; i++) 468 | g.cmd[i] = strdup(argv[i]); 469 | g.cmdlen = argc; 470 | }else{ 471 | // use default command 472 | int maxargs = 100; 473 | g.cmd = malloc((maxargs)*sizeof(char*)); 474 | assert(g.cmd); 475 | g.cmdlen = tokenize(g.defcmd, g.cmd, maxargs); 476 | if(g.cmdlen == maxargs) 477 | sysfatal("more than %d args", maxargs-1); 478 | } 479 | assert(g.cmdlen); 480 | } 481 | 482 | void 483 | usage(void) 484 | { 485 | fprint(2, "usage: %s [-Dd] [-c dir] [-h host] [-m mounts] cmd ...\n", 486 | argv0); 487 | threadexitsall("usage"); 488 | } 489 | 490 | void 491 | threadmain(int argc, char **argv) 492 | { 493 | char *exitmsg; 494 | char tmp[100]; 495 | int in = FDOFF, out = in+1, err = in+2; 496 | int logpipe[2]; 497 | 498 | if(rfork(RFFDG|RFNAMEG) < 0) sysfatal("threadmain: rfork: %r"); 499 | 500 | quotefmtinstall(); 501 | 502 | g.mainfd = -1; 503 | g.lckfd = -1; 504 | g.dbgfd = 2; // until log file is setup, log to stderr 505 | 506 | assert(dup(0, in) == in); 507 | assert(dup(1, out) == out); 508 | assert(dup(2, err) == err); 509 | 510 | config(argc, argv); 511 | 512 | esnprint(tmp, sizeof tmp, "/root/tmp/%s.%s", 513 | g.progname, getuser()); 514 | g.tmpdir = tmp; 515 | ensuredir(g.tmpdir, 0700); 516 | 517 | g.fsport = getport(); 518 | 519 | pipe(logpipe); 520 | g.dbgfd = logpipe[0]; 521 | proccreate(logsink, &logpipe[1], 8*1024); 522 | 523 | notectx.pid = getpid(); 524 | notectx.lasttime = 0; 525 | startfs(); 526 | atexit(stopfs); 527 | assert(atnotify(notehandle, 1) > 0); 528 | exitmsg = remote(); 529 | threadexitsall(exitmsg); 530 | } 531 | -------------------------------------------------------------------------------- /lxsrv.c: -------------------------------------------------------------------------------- 1 | // for unshare(2) 2 | #define _GNU_SOURCE 3 | 4 | #include 5 | // unix start 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | extern int mkdir(const char *pathname, mode_t mode); 12 | // unix end 13 | #include 14 | #include 15 | 16 | #define DEFAULTPORT "9000" 17 | #define NINEMNT "/9" 18 | #define FDOFF 20 19 | #define VNCMAXCOUNT 100 20 | #define VNCMINDPY 100 21 | // Increase this if "can't open display" errors 22 | // FIXME make into a config param? plus can be machine-dependent 23 | #define VNCWAITMS 200 24 | 25 | char *progname, *plan9dir, *tmpdir, *mntbasedir; 26 | int lckfd; 27 | 28 | // Using this struct simply for grouping session-related globals 29 | struct { 30 | char *host, *pxydir, *tmp; 31 | int port, logfd, debug, fd9, pid, clientended; 32 | int pxyfd, pxydpy, vncdpy, x11count; 33 | QLock x11lck; 34 | } S; 35 | 36 | typedef struct { 37 | char *host, *cwd, *mounts; 38 | int port; 39 | char **argv; 40 | int debug, checkcwd; 41 | } Params; 42 | 43 | void session(); 44 | void usage(); 45 | void _dbg(int iserror, char *format, ...); 46 | void* emalloc(int n); 47 | void* erealloc(void *v, int n); 48 | void* estrdup(void *v); 49 | static long _iolisten(va_list *arg); 50 | int iolisten(Ioproc *io, char *a, char *b); 51 | static long _ioaccept(va_list *arg); 52 | int ioaccept(Ioproc *io, int fd, char *dir); 53 | static long _ioselect(va_list *arg); 54 | int ioselect(Ioproc *io, int maxfd, fd_set *fds, 55 | struct timeval *timeout); 56 | int esnprint(char *tgt, int max, char *fmt, ...); 57 | void mkdir_p(char *path, mode_t mode); 58 | void exit9(int unexpected, char *exitmsg); 59 | void sysfatal9(char *fmt, ...); 60 | int send9(char *msg); 61 | void logsink(void *arg); 62 | void getwinsize(char *out, int len); 63 | void killvnc(); 64 | int waitforvnc(); 65 | int proxychunk(Ioproc *io, int src, int tgt); 66 | void proxy(void *arg); 67 | void x11conn(void *arg); 68 | void x11listen(void *arg); 69 | void command(char *cwd, char **argv); 70 | Params getparams(); 71 | char* readparamsblock(int fd); 72 | void p9prun(char *a0, char *a1, char *a2); 73 | void setupns(char *host, int port, char *mounts); 74 | void setupio(); 75 | void handlenote(char *note); 76 | void control(void *arg); 77 | void cleanup(); 78 | int createpxysock(); 79 | 80 | #define dbg(...) do{ _dbg(0, __VA_ARGS__); }while(0) 81 | #define error(...) do{ _dbg(1, __VA_ARGS__); }while(0) 82 | 83 | void 84 | _dbg(int iserror, char *format, ...) 85 | { 86 | va_list arg; 87 | char buf[1000]; 88 | char *prefix = "server: "; 89 | int prelen = strlen(prefix); 90 | strcpy(buf, prefix); 91 | va_start(arg, format); 92 | vsnprint(buf + prelen, sizeof(buf) - prelen - 1, format, arg); 93 | va_end(arg); 94 | if(S.debug || iserror) 95 | // client must show this 96 | fprint(2, buf); 97 | if(S.logfd != 0) 98 | // offset b/c no need for prefix in log file 99 | fprint(S.logfd, buf+prelen); 100 | } 101 | 102 | void* 103 | emalloc(int n) 104 | { 105 | void *v = mallocz(n, 1); 106 | if(v == nil) sysfatal("out of memory allocating %d", n); 107 | return v; 108 | } 109 | 110 | void* 111 | erealloc(void *v, int n) 112 | { 113 | v = realloc(v, n); 114 | if(v == nil) sysfatal("out of memory reallocating %d", n); 115 | return v; 116 | } 117 | 118 | void* 119 | estrdup(void *v) 120 | { 121 | v = strdup(v); 122 | if(v == nil) sysfatal("out of memory strdup'ing %s", v); 123 | return v; 124 | } 125 | 126 | static long 127 | _iolisten(va_list *arg) 128 | { 129 | char *a, *b; 130 | a = va_arg(*arg, char*); 131 | b = va_arg(*arg, char*); 132 | return listen(a, b); 133 | } 134 | 135 | int 136 | iolisten(Ioproc *io, char *a, char *b) 137 | { 138 | return iocall(io, _iolisten, a, b); 139 | } 140 | 141 | static long 142 | _ioaccept(va_list *arg) 143 | { 144 | int fd; 145 | char *dir; 146 | fd = va_arg(*arg, int); 147 | dir = va_arg(*arg, char*); 148 | return accept(fd, dir); 149 | } 150 | 151 | int 152 | ioaccept(Ioproc *io, int fd, char *dir) 153 | { 154 | return iocall(io, _ioaccept, fd, dir); 155 | } 156 | 157 | static long 158 | _ioselect(va_list *arg) 159 | { 160 | int maxfd = va_arg(*arg, int); 161 | fd_set *fds = va_arg(*arg, fd_set *); 162 | struct timeval *timeout = va_arg(*arg, struct timeval *); 163 | return select(maxfd, fds, NULL, NULL, timeout); 164 | } 165 | 166 | int 167 | ioselect(Ioproc *io, int maxfd, fd_set *fds, struct timeval *timeout) 168 | { 169 | return iocall(io, _ioselect, maxfd, fds, timeout); 170 | } 171 | 172 | int 173 | esnprint(char *tgt, int max, char *fmt, ...) 174 | { 175 | va_list arg; 176 | int n; 177 | va_start(arg, fmt); 178 | n = vsnprint(tgt, max, fmt, arg); 179 | va_end(arg); 180 | if(n == max) 181 | sysfatal9("formatted string too long: '%s'", fmt); 182 | return n; 183 | } 184 | 185 | void 186 | session() 187 | { 188 | Params params = getparams(); 189 | 190 | S.debug = params.debug; 191 | S.host = params.host; 192 | S.port = params.port; 193 | 194 | int logpipe[2]; 195 | int *logfdin = emalloc(sizeof(int)); 196 | pipe(logpipe); 197 | S.logfd = logpipe[0]; 198 | *logfdin = logpipe[1]; 199 | threadcreate(logsink, logfdin, 32*1024); 200 | 201 | if(createpxysock() < 0) 202 | sysfatal9("unable to find a free vnc proxy port"); 203 | S.vncdpy = S.pxydpy + VNCMAXCOUNT; 204 | 205 | atexit(cleanup); 206 | setupns(params.host, params.port, params.mounts); 207 | setupio(); 208 | threadcreate(control, nil, 32*1024); 209 | threadcreate(x11listen, nil, 32*1024); 210 | if(chdir(params.cwd) < 0) 211 | if(params.checkcwd){ 212 | char msg[100]; 213 | snprintf(msg, sizeof msg, "cannot cd to %s", params.cwd); 214 | exit9(1, msg); 215 | return; 216 | }else 217 | fprint(2, "warning: cannot cd to %s\n", params.cwd); 218 | command(params.cwd, params.argv); 219 | dbg("session: done\n"); 220 | } 221 | 222 | // Tell client to exit with given message, truncated if too long. 223 | // If unexpected, we prefix the message with "fatal:", which tells 224 | // the client to print it on stderr before exiting. 225 | void 226 | exit9(int unexpected, char *exitmsg) 227 | { 228 | char buf[300]; 229 | esnprint(buf, sizeof(buf), "%s %s", 230 | unexpected ? "fatal:" : "exit", 231 | exitmsg); 232 | send9(buf); 233 | S.clientended = 1; 234 | } 235 | 236 | // Like sysfatal, but notifies the client before aborting 237 | void 238 | sysfatal9(char *fmt, ...) 239 | { 240 | char buf[300]; 241 | va_list arg; 242 | int n; 243 | va_start(arg, fmt); 244 | n = vsnprint(buf, sizeof(buf), fmt, arg); 245 | va_end(arg); 246 | if(n == sizeof(buf)) 247 | error("sysfatal9: formatted string too long: '%s'", fmt); 248 | if(S.fd9 >= 0) exit9(1, buf); 249 | sysfatal(buf); 250 | } 251 | 252 | void 253 | mkdir_p(char *path, mode_t mode) 254 | { 255 | char *slash = path; 256 | if(strlen(path) == 0) sysfatal9("mkdir_p empty path"); 257 | for(;;){ 258 | slash = strchr(slash+1, '/'); 259 | if(slash != nil) *slash = '\0'; 260 | if(mkdir(path, mode) < 0 && errno !=EEXIST) 261 | sysfatal9("mkdir_p: mkdir %s: %r", path); 262 | if(slash == nil) break; 263 | *slash = '/'; 264 | } 265 | } 266 | 267 | // Takes/release a lockfile unique to our progname and user 268 | // Pass 1 to take, 0 to release 269 | // Using a lock file so it works with multiple server instances 270 | void 271 | configlock(int take) 272 | { 273 | char path[40]; 274 | esnprint(path, sizeof(path), "%s/lock", tmpdir); 275 | if(take){ 276 | // Try for 10s at least 277 | for(int i = 0; i < 200; i++){ 278 | lckfd = create(path, OWRITE|OLOCK, 0600); 279 | if(lckfd >= 0) break; 280 | sleep(50); 281 | } 282 | if(lckfd < 0) 283 | sysfatal("configlock: timed out waiting %s", path); 284 | }else{ 285 | close(lckfd); 286 | } 287 | } 288 | 289 | // Looks in /tmp/.X11-unix/ for an avalaible socket in our range, 290 | // creates a socket, updates S.pxy* globals 291 | // Returns -1 on error. 292 | int 293 | createpxysock() 294 | { 295 | char sockpath[100]; 296 | struct stat st; 297 | int afd, dpy = -1; 298 | 299 | configlock(1); 300 | 301 | for(int i = VNCMINDPY; i <= VNCMINDPY + VNCMAXCOUNT - 1; i++){ 302 | esnprint(sockpath, sizeof(sockpath), 303 | "/tmp/.X11-unix/X%d", i); 304 | if(stat(sockpath, &st) < 0){ 305 | dpy = i; 306 | break; 307 | } 308 | } 309 | if(dpy < 0) return -1; 310 | 311 | char adir[40]; 312 | char pxyaddr[40]; 313 | esnprint(pxyaddr, sizeof(pxyaddr), 314 | "unix!/tmp/.X11-unix/X%d", dpy); 315 | afd = announce(pxyaddr, adir); 316 | dbg("createpxysock: announce %s - %s\n", pxyaddr, adir); 317 | 318 | configlock(0); 319 | 320 | S.pxyfd = afd; 321 | S.pxydpy = dpy; 322 | S.pxydir = strdup(adir); 323 | return 0; 324 | } 325 | 326 | // Reads from the log fd (passed as param) into the log file 327 | void 328 | logsink(void *arg) 329 | { 330 | char outpath[1000]; 331 | int outfd; 332 | int fd = *(int*)arg; 333 | Ioproc *io = ioproc(); 334 | 335 | esnprint(outpath, sizeof outpath, "%s/%s.%d.log", 336 | tmpdir, S.host, S.port); 337 | outfd = create(outpath, OWRITE|OAPPEND, 0600); 338 | if(outfd < 0) sysfatal9("cannot create %s: %r", outpath); 339 | 340 | for(;;){ 341 | char buf[100]; 342 | int n = ioread(io, fd, buf, sizeof(buf)-1); 343 | if(n <= 0) break; 344 | int w = iowrite(io, outfd, buf, n); 345 | if(w != n) 346 | fprint(2, "logsink: wrote %d instead of %d\n", w, n); 347 | } 348 | } 349 | 350 | // Messages the client on 9, -1 on error 351 | int 352 | send9(char *msg) 353 | { 354 | int status = 0; 355 | dbg("send9: '%s'\n", msg); 356 | Ioproc *io = ioproc(); 357 | int w = iowrite(io, S.fd9, msg, strlen(msg)+1); 358 | if(w < strlen(msg)+1){ 359 | error("send9: only wrote %d/%d !?\n", w, strlen(msg)+1); 360 | status = -1; 361 | } 362 | closeioproc(io); 363 | return status; 364 | } 365 | 366 | // Queries the window system on 9 367 | void 368 | getwinsize(char *out, int len) 369 | { 370 | int rioborder = 4; 371 | char wctl[30]; 372 | esnprint(wctl, sizeof(wctl), "%s/mnt/wsys/wctl", NINEMNT); 373 | FILE *f = fopen(wctl, "r"); 374 | if(f == nil) sysfatal9("getwinsize: fopen %s: %r", wctl); 375 | int x1, y1, x2, y2; 376 | assert(fscanf(f, "%d %d %d %d ", &x1, &y1, &x2, &y2) == 4); 377 | esnprint(out, len, "%dx%d", x2-x1-2*rioborder, y2-y1-2*rioborder); 378 | fclose(f); 379 | } 380 | 381 | // We can't just kill our vncserver process, Xvnc is a child of 1 382 | // So we use pkill. 383 | void 384 | killvnc() 385 | { 386 | dbg("killvnc\n"); 387 | char rx[50]; 388 | esnprint(rx, sizeof(rx), "Xvnc :%d ", S.vncdpy); 389 | int fds[] = { dup(0, -1), dup(S.logfd, -1), dup(S.logfd, -1) }; 390 | if(threadspawnl(fds, "/bin/pkill", "pkill", "-f", rx, NULL) < 0) 391 | error("killvnc: cannot start pkill: %r\n"); 392 | } 393 | 394 | // Waits for the vnc socket to appear, returns -1 on timeout 395 | int 396 | waitforvnc() 397 | { 398 | Ioproc *io = ioproc(); 399 | char vncsock[30]; 400 | esnprint(vncsock, sizeof(vncsock), "/tmp/.X11-unix/X%d", S.vncdpy); 401 | int result = -1; 402 | for(int i = 0; i < 50; i++){ 403 | struct stat st; 404 | if(stat(vncsock, &st) >= 0) { 405 | result = 0; 406 | break; 407 | } 408 | iosleep(io, 100); 409 | } 410 | if(1){ 411 | // FIXME fragile machine-specific value, should poll instead 412 | iosleep(io, VNCWAITMS); 413 | }else{ 414 | // FIXME not working 415 | char vncaddr[40]; 416 | esnprint(vncaddr, sizeof(vncaddr), "unix!%s", vncsock); 417 | int vncfd = iodial(io, vncaddr, 0, 0, 0); 418 | if(vncfd < 0) sysfatal9("LOOP: %r"); 419 | for(;;){ 420 | dbg("LOOP\n"); 421 | int w = iowrite(io, vncfd, "x", 1); 422 | dbg("LOOP w=%d\n", w); 423 | if(w == 1){ 424 | char buf[1]; 425 | ioread(io, vncfd, buf, 1); 426 | break; 427 | } 428 | perror("x"); 429 | iosleep(io, 10); 430 | } 431 | ioclose(io, vncfd); 432 | } 433 | closeioproc(io); 434 | return result; 435 | } 436 | 437 | // Forwards a readfull from src to tgt, returns -1 on eof or error 438 | int 439 | proxychunk(Ioproc *io, int src, int tgt) 440 | { 441 | char buf[8192]; 442 | int n = ioread(io, src, buf, sizeof(buf)); 443 | if(n < 0){ 444 | dbg("proxychunk: read: %r\n"); 445 | return -1; 446 | } 447 | if(n == 0){ 448 | // dbg("proxychunk: eof\n"); 449 | return -1; 450 | } 451 | int nw = iowrite(io, tgt, buf, n); 452 | if(nw != n) { 453 | error("proxychunk: write: %r\n"); 454 | return -1; 455 | } 456 | return 0; 457 | } 458 | 459 | typedef struct { 460 | int pxyfd, vncfd; 461 | Channel *chan; 462 | } ProxyContext; 463 | 464 | // Thread proxying from proxy to vnc sockets 465 | void 466 | proxy(void *arg) 467 | { 468 | ProxyContext *ctx = arg; 469 | Ioproc *io = ioproc(); 470 | fd_set rfds; 471 | struct timeval tv; 472 | int retval; 473 | int maxfd = (ctx->pxyfd > ctx->vncfd) ? ctx->pxyfd : ctx->vncfd; 474 | 475 | for(;;){ 476 | FD_ZERO(&rfds); 477 | FD_SET(ctx->pxyfd, &rfds); 478 | FD_SET(ctx->vncfd, &rfds); 479 | tv.tv_sec = 5; 480 | tv.tv_usec = 0; 481 | retval = ioselect(io, maxfd+1, &rfds, &tv); 482 | if(retval < 0){ 483 | error("proxy: select %d %d: %r\n", ctx->pxyfd, ctx->vncfd); 484 | break; // FIXME or continue? 485 | } 486 | if(retval == 0) continue; // select timeout 487 | if(FD_ISSET(ctx->pxyfd, &rfds)) 488 | if(proxychunk(io, ctx->pxyfd, ctx->vncfd) < 0) break; 489 | if(FD_ISSET(ctx->vncfd, &rfds)) 490 | if(proxychunk(io, ctx->vncfd, ctx->pxyfd) < 0) break; 491 | } 492 | ioclose(io, ctx->pxyfd); 493 | ioclose(io, ctx->vncfd); 494 | closeioproc(io); 495 | sendul(ctx->chan, 0); 496 | } 497 | 498 | typedef struct { 499 | int lfd; 500 | char ldir[40]; 501 | } X11connContext; 502 | 503 | // Handles a connection to the proxy socket: 504 | // - starts vncserver if first connection 505 | // - proxies to the vnc socket 506 | // - asks the 9 client to start the vnc viewer 507 | // - on closing the last connection, stops viewer and server 508 | void 509 | x11conn(void *arg) 510 | { 511 | X11connContext *ctx = arg; 512 | Ioproc *io = ioproc(); 513 | 514 | int cfd = ioaccept(io, ctx->lfd, ctx->ldir); 515 | qlock(&S.x11lck); // start vnc server once 516 | S.x11count++; 517 | if(cfd < 0){ 518 | error("x11conn: ioaccept %r\n"); 519 | qunlock(&S.x11lck); 520 | goto out; 521 | } 522 | 523 | // dbg("x11conn: lfd=%d x11count=%d\n", ctx->lfd, S.x11count); 524 | int startviewer = 0; 525 | if(S.x11count == 1){ 526 | startviewer = 1; 527 | char colondpy[10], winsize[20]; 528 | getwinsize(winsize, sizeof(winsize)); 529 | esnprint(colondpy, sizeof(colondpy), ":%d", S.vncdpy); 530 | int fds[] = { dup(0, -1), dup(S.logfd, -1), dup(S.logfd, -1) }; 531 | dbg("x11conn: spawning vncserver %s %s\n", 532 | colondpy, winsize); 533 | if(threadspawnl(fds, 534 | "/usr/bin/vncserver", "vncserver", 535 | colondpy, "-fg", "-autokill", "-geometry", winsize, 536 | NULL) < 0) 537 | sysfatal9("x11conn: error starting vncserver: %r"); 538 | } 539 | 540 | int novnc = (waitforvnc() == -1); 541 | qunlock(&S.x11lck); 542 | if(novnc){ 543 | error("x11conn: vnc socket not appearing\n"); 544 | // maybe we didn't wait enough, don't leave a vnc hanging 545 | killvnc(); 546 | goto out; 547 | } 548 | 549 | if(startviewer){ 550 | char msg[20]; 551 | esnprint(msg, sizeof(msg), "vnc %d", S.vncdpy); 552 | send9(msg); 553 | } 554 | 555 | char vncaddr[30]; 556 | esnprint(vncaddr, sizeof(vncaddr), "unix!/tmp/.X11-unix/X%d", 557 | S.vncdpy); 558 | int vncfd = iodial(io, vncaddr, 0, 0, 0); 559 | if(vncfd < 0) sysfatal9("x11conn: vnc dial %r"); 560 | 561 | Channel *donechan = chancreate(sizeof(int), 0); 562 | ProxyContext proxyctx = { 563 | .chan = donechan, 564 | .pxyfd = cfd, 565 | .vncfd = vncfd, 566 | }; 567 | threadcreate(proxy, &proxyctx, 16*1024); 568 | 569 | recvul(donechan); 570 | chanfree(donechan); 571 | ioclose(io, cfd); 572 | out: 573 | S.x11count--; 574 | dbg("x11conn: closing x11count=%d\n", S.x11count); 575 | if(S.x11count == 0) killvnc(); 576 | ioclose(io, ctx->lfd); 577 | closeioproc(io); 578 | free(ctx); 579 | } 580 | 581 | // Listens on the vnc proxy socket, delegates to x11conn() 582 | void 583 | x11listen(void *arg) 584 | { 585 | Ioproc *io = ioproc(); 586 | dbg("x11listen: pxydpy=%d vncdpy=%d\n", S.pxydpy, S.vncdpy); 587 | 588 | for(;;){ 589 | X11connContext *x11ctx = emalloc(sizeof(X11connContext)); 590 | x11ctx->lfd = iolisten(io, S.pxydir, x11ctx->ldir); 591 | if(x11ctx->lfd < 0){ 592 | error("x11listen: iolisten %r\n"); 593 | break; 594 | } 595 | threadcreate(x11conn, x11ctx, 8192); 596 | } 597 | closeioproc(io); 598 | } 599 | 600 | // Runs the Linux command, on exit sends the status to the 9 client 601 | void 602 | command(char *cwd, char **argv) 603 | { 604 | int unexpected = 1; 605 | char displayenv[10]; 606 | esnprint(displayenv, sizeof(displayenv), ":%d", S.pxydpy); 607 | putenv("DISPLAY", displayenv); 608 | 609 | int fds[] = { dup(0, -1), dup(1, -1), dup(2, -1) }; 610 | char msg[200]; 611 | S.pid = threadspawnd(fds, argv[0], argv, cwd); 612 | if(S.pid < 0){ 613 | dbg("command: threadspawnd: %r"); 614 | esnprint(msg, sizeof(msg), "cannot start %s", argv[0]); 615 | }else{ 616 | Channel *chan = threadwaitchan(); 617 | Waitmsg *w; 618 | for(;;){ 619 | w = recvp(chan); 620 | if(w == nil) sysfatal9("command: recvp: %r"); 621 | if(w->pid == S.pid){ 622 | dbg("command: process exit '%s'\n", w->msg); 623 | strncpy(msg, *w->msg ? w->msg : "0", sizeof(msg)); 624 | unexpected = 0; 625 | free(w); 626 | break; 627 | } 628 | free(w); 629 | } 630 | } 631 | exit9(unexpected, msg); 632 | } 633 | 634 | // Return's malloced buffer containing what's read from fd until 635 | // a terminating "LXEND\n" 636 | // Errors if input too large. 637 | // Returns nil on error, sets errstr. 638 | char* 639 | readparamsblock(int fd) 640 | { 641 | int blksz = 1000, usedsz = 0, maxsz = 20000; 642 | char *buf = emalloc(blksz); 643 | char *cur = buf; 644 | char *err = nil; 645 | 646 | for(;;){ 647 | int n = read(fd, cur, blksz-1); 648 | if(n < 0){ 649 | err = "readparamsblock: read: %r"; 650 | break; 651 | } 652 | if(n == 0){ 653 | err = "readparamsblock: read unexpected eof"; 654 | break; 655 | } 656 | cur[n] = '\0'; 657 | if(strstr(buf, "LXEND\n") != nil) break; 658 | usedsz += n; 659 | cur = buf + usedsz; 660 | if(usedsz > maxsz){ 661 | err = "readparamsblock: params too large"; 662 | break; 663 | } 664 | if(cur+blksz > buf+usedsz){ 665 | buf = erealloc(buf, usedsz+blksz); 666 | cur = buf+usedsz; 667 | } 668 | } 669 | 670 | if(err){ 671 | werrstr(err); 672 | free(buf); 673 | buf = nil; 674 | } 675 | return buf; 676 | } 677 | 678 | Params 679 | getparams() 680 | { 681 | char *input = readparamsblock(S.fd9); // leak, nm 682 | if(input == nil){ 683 | char msg[ERRMAX]; 684 | rerrstr(msg, sizeof(msg)); 685 | write(S.fd9, msg, strlen(msg)); 686 | sysfatal9("getparams: %s", msg); 687 | } 688 | dbg("getparams: params:%s\n", input); 689 | char *lines[1000]; 690 | int linescount = gettokens(input, lines, 1000, "\n"); 691 | lines[linescount-1] = nil; // replace LXEND with nil 692 | if(linescount < 7){ 693 | char *msg = "not enough params"; 694 | write(S.fd9, msg, strlen(msg)); 695 | sysfatal9("getparams: %s", msg); 696 | } 697 | 698 | Params params = { 699 | .host = lines[0], 700 | .port = atoi(lines[1]), 701 | .debug = atoi(lines[2]), 702 | .cwd = lines[3], 703 | .checkcwd = atoi(lines[4]), 704 | .mounts = lines[5], 705 | .argv = lines+6, 706 | }; 707 | return params; 708 | } 709 | 710 | // Mounts plan 9 fs on /9, then does bind mounts 711 | void 712 | setupns(char *host, int port, char *mounts) 713 | { 714 | if(strchr(mounts, ' ') != nil) 715 | sysfatal9("spaces disallowed in mounts: %s", mounts); 716 | 717 | // New mount namespace 718 | int status = unshare(CLONE_NEWNS); 719 | if(status < 0) sysfatal9("setupns: unshare: %r"); 720 | 721 | // Required for the mounts to work after unshare call, 722 | // on archlinux at least 723 | if(mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0) 724 | sysfatal9("setupns: mount /: %r"); 725 | 726 | // Mounts the plan9 fs on /9 by running p9p commands srv and 9pfuse 727 | { 728 | char addr[64]; 729 | char srvname[50]; 730 | char srvpath[100]; 731 | char p9pns[100]; 732 | dbg("fuse: file server %s:%d\n", host, port); 733 | 734 | esnprint(p9pns, sizeof p9pns, "%s/ns", tmpdir); 735 | mkdir_p(p9pns, 0700); 736 | putenv("NAMESPACE", p9pns); 737 | 738 | esnprint(srvname, sizeof(srvname), "%s_%d", host, port); 739 | esnprint(srvpath, sizeof(srvpath), "%s/%s", p9pns, srvname); 740 | 741 | esnprint(addr, sizeof(addr), "tcp!%s!%d", host, port); 742 | if(unlink(srvpath) < 0 && errno != ENOENT) 743 | sysfatal9("fuse: unlink %s: %r", srvpath); 744 | 745 | dbg("fuse: srv %s %s\n", addr, srvname); 746 | p9prun("srv", addr, srvname); 747 | dbg("fuse: 9pfuse %s %s\n", srvpath, NINEMNT); 748 | p9prun("9pfuse", srvpath, NINEMNT); 749 | } 750 | 751 | // Do the requested bind-mounts from /9/X to /X 752 | // "/A:/a,/B:/b" 753 | char *entries[10]; 754 | int n = getfields(mounts, entries, 10, 1, ","); 755 | for(int i = 0; i < n; i++){ 756 | char *dirs[2]; 757 | char *entry = entries[i]; // "/A:/a" 758 | int n2 = getfields(entry, dirs, 2, 0, ":"); 759 | if(n2 != 2) sysfatal9("malformed mount entry: %s", entry); 760 | char src[200]; 761 | esnprint(src, sizeof(src), "%s%s", NINEMNT, dirs[0]); 762 | char *tgt = dirs[1]; 763 | dbg("setupns: mount %s %s\n", src, tgt); 764 | if(mount(src, tgt, "ext4", MS_BIND|MS_PRIVATE, NULL) < 0) 765 | sysfatal9("setupns: mount: %r"); 766 | } 767 | } 768 | 769 | // Runs p9p command, sysfatal on error 770 | void 771 | p9prun(char *a0, char *a1, char *a2) 772 | { 773 | char bin[200]; 774 | esnprint(bin, sizeof bin, "%s/bin/%s", plan9dir, a0); 775 | Channel *chan = threadwaitchan(); 776 | int fds[] = { dup(0, -1), dup(S.logfd, -1), dup(S.logfd, -1) }; 777 | int srvpid = threadspawnl(fds, bin, a0, a1, a2, NULL); 778 | if(srvpid < 0) 779 | sysfatal9("p9prun: cannot run %s: %r", bin); 780 | Waitmsg *w = recvp(chan); 781 | if(w == nil) sysfatal9("p9prun: %s recvp: %r", a0); 782 | if(strlen(w->msg) > 0) 783 | sysfatal9("p9prun: %s failed with status %s", a0, w->msg); 784 | free(w); 785 | } 786 | 787 | // Connects 0, 1, 2 to /9/fd/20,21,22 788 | void 789 | setupio() 790 | { 791 | mode_t modes[] = { 792 | // modes to use for stdin/out/err 793 | OREAD, 794 | OWRITE|OAPPEND, 795 | OWRITE|OAPPEND, 796 | }; 797 | for(int fd = 0; fd < 3; fd++){ 798 | char path[40]; 799 | esnprint(path, sizeof(path), "%s/fd/%d", NINEMNT, FDOFF+fd); 800 | int newfd = open(path, modes[fd]); 801 | if(newfd < 0) sysfatal9("setupio: open %s: %r", path); 802 | if(dup(newfd, fd) < 0) sysfatal9("setupio: dup: %r"); 803 | close(newfd); 804 | } 805 | } 806 | 807 | // Handles a note forwarded by the 9 client 808 | void 809 | handlenote(char *note) 810 | { 811 | int sig = 0; 812 | dbg("handlenote: '%s'\n", note); 813 | if(strcmp("int", note) == 0) 814 | sig = SIGINT; 815 | else if(strcmp("hup", note) == 0) 816 | sig = SIGHUP; 817 | else if(strcmp("kill", note) == 0) 818 | sig = SIGKILL; 819 | else 820 | error("handlenote: unknown note '%s'\n", note); 821 | if(sig != 0){ 822 | dbg("handlenote: kill leaderpid=%d sig=%d\n", S.pid, sig); 823 | kill(-S.pid, sig); 824 | } 825 | } 826 | 827 | // Handles messages from the 9 client 828 | void 829 | control(void *arg) 830 | { 831 | char buf[100]; 832 | Ioproc *io = ioproc(); 833 | for(;;){ 834 | // dbg("control: loop\n"); 835 | int n = ioread(io, S.fd9, buf, sizeof(buf)); 836 | if(n < 0) sysfatal9("control: read: %r"); 837 | if(n == sizeof buf) sysfatal9("control: message too big"); 838 | if(n == 0){ 839 | dbg("control: eof\n"); 840 | break; 841 | } 842 | buf[n] = '\0'; 843 | dbg("control: received '%s'\n", buf); 844 | handlenote(buf); 845 | } 846 | closeioproc(io); 847 | } 848 | 849 | // Exit handler during request handling, may be invoked on normal 850 | // command exit, or on unexpected error (eg sysfatal). 851 | void 852 | cleanup() 853 | { 854 | dbg("cleanup\n"); 855 | 856 | killvnc(); 857 | char pxysock[40]; 858 | esnprint(pxysock, sizeof(pxysock), "/tmp/.X11-unix/X%d", S.pxydpy); 859 | if(unlink(pxysock) < 0 && errno != ENOENT) 860 | error("cleanup: unlink %s: %r\n", pxysock); 861 | 862 | // Paranoia 863 | if(!S.clientended) 864 | send9("exit unexpected error, check lx server logs"); 865 | 866 | dbg("cleanup: hasten 9pfuse death\n"); 867 | // We already told the client to exit, it should then be 868 | // stopping the fs on 9. To help 9pfuse realise the 869 | // remote server is dead and terminate, we access a random 870 | // path under the mount point. 871 | char path[40]; 872 | esnprint(path, sizeof(path), "%s/rc", NINEMNT); 873 | for(;;){ 874 | int fd = open(path, OREAD); 875 | close(fd); 876 | if(fd < 0) break; 877 | sleep(50); 878 | } 879 | } 880 | 881 | void 882 | usage() 883 | { 884 | fprint(2, "usage: %s [ -i interface ] [ -p port ]\n", argv0); 885 | threadexitsall("usage"); 886 | } 887 | 888 | void 889 | threadmain(int argc, char **argv) 890 | { 891 | char *interface = "localhost", *port = DEFAULTPORT; 892 | 893 | S.fd9 = -1; 894 | 895 | ARGBEGIN { 896 | default: 897 | usage(); 898 | case 'i': 899 | interface = strdup(EARGF(usage())); 900 | break; 901 | case 'p': 902 | port = EARGF(usage()); 903 | if(atoi(port) == 0) 904 | sysfatal("invalid port: %s", port); 905 | break; 906 | } ARGEND 907 | 908 | char *slash = strrchr(argv0, '/'); 909 | if(slash == nil) 910 | progname = argv0; 911 | else 912 | progname = slash + 1; 913 | 914 | plan9dir = getenv("PLAN9"); 915 | if(plan9dir == nil || strlen(plan9dir) == 0) 916 | sysfatal("PLAN9 env var not defined"); 917 | 918 | char tmp[100]; 919 | esnprint(tmp, sizeof tmp, "/tmp/%s.%s", progname, getuser()); 920 | tmpdir = tmp; 921 | mkdir_p(tmpdir, 0700); 922 | 923 | char adir[40], ldir[40], addr[50]; 924 | esnprint(addr, sizeof(addr), "tcp!%s!%s", interface, port); 925 | dbg("threadmain: listening on %s\n", addr); 926 | int acfd = announce(addr, adir); 927 | if(acfd < 0) sysfatal("threadmain: announce %s: %r", addr); 928 | for(;;){ 929 | int lcfd = listen(adir, ldir); 930 | if(lcfd < 0) sysfatal("threadmain: listen on %s: %r", addr); 931 | pid_t pid = rfork(RFFDG|RFPROC|RFNOWAIT|RFNOTEG); 932 | switch(pid){ 933 | case -1: 934 | sysfatal("threadmain: fork: %r"); 935 | case 0: 936 | S.fd9 = accept(lcfd, ldir); 937 | if(S.fd9 < 0) sysfatal("threadmain: accept: %r"); 938 | dbg("threadmain: S.fd9=%d\n", S.fd9); 939 | session(); 940 | close(S.fd9); 941 | threadexitsall(0); 942 | default: 943 | dbg("threadmain: forked to %d\n", pid); 944 | close(lcfd); 945 | } 946 | } 947 | } 948 | -------------------------------------------------------------------------------- /lx-dwm/lx-dwm.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. 2 | * 3 | * dynamic 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 lx-dwm are organized in an array which is accessed 10 | * whenever a new event has been fetched. This allows event dispatching 11 | * 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 linked client 15 | * list on each monitor, the focus history is remembered through a stack list 16 | * on each monitor. Each client contains a bit array to indicate the tags of a 17 | * 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 | 42 | #include "drw.h" 43 | #include "util.h" 44 | 45 | /* macros */ 46 | #define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) 47 | #define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask)) 48 | #define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \ 49 | * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy))) 50 | #define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags])) 51 | #define LENGTH(X) (sizeof X / sizeof X[0]) 52 | #define MOUSEMASK (BUTTONMASK|PointerMotionMask) 53 | #define WIDTH(X) ((X)->w + 2 * (X)->bw) 54 | #define HEIGHT(X) ((X)->h + 2 * (X)->bw) 55 | #define TAGMASK ((1 << LENGTH(tags)) - 1) 56 | #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) 57 | 58 | /* enums */ 59 | enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ 60 | enum { SchemeNorm, SchemeSel }; /* color schemes */ 61 | enum { NetSupported, NetWMName, NetWMState, NetWMCheck, 62 | NetWMFullscreen, NetActiveWindow, NetWMWindowType, 63 | NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ 64 | enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ 65 | enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, 66 | ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ 67 | 68 | typedef union { 69 | int i; 70 | unsigned int ui; 71 | float f; 72 | const void *v; 73 | } Arg; 74 | 75 | typedef struct { 76 | unsigned int click; 77 | unsigned int mask; 78 | unsigned int button; 79 | void (*func)(const Arg *arg); 80 | const Arg arg; 81 | } Button; 82 | 83 | typedef struct Monitor Monitor; 84 | typedef struct Client Client; 85 | struct Client { 86 | char name[256]; 87 | float mina, maxa; 88 | int x, y, w, h; 89 | int oldx, oldy, oldw, oldh; 90 | int basew, baseh, incw, inch, maxw, maxh, minw, minh; 91 | int bw, oldbw; 92 | unsigned int tags; 93 | int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen; 94 | Client *next; 95 | Client *snext; 96 | Monitor *mon; 97 | Window win; 98 | }; 99 | 100 | typedef struct { 101 | unsigned int mod; 102 | KeySym keysym; 103 | void (*func)(const Arg *); 104 | const Arg arg; 105 | } Key; 106 | 107 | typedef struct { 108 | const char *symbol; 109 | void (*arrange)(Monitor *); 110 | } Layout; 111 | 112 | struct Monitor { 113 | char ltsymbol[16]; 114 | float mfact; 115 | int nmaster; 116 | int num; 117 | int by; /* bar geometry */ 118 | int mx, my, mw, mh; /* screen size */ 119 | int wx, wy, ww, wh; /* window area */ 120 | unsigned int seltags; 121 | unsigned int sellt; 122 | unsigned int tagset[2]; 123 | int showbar; 124 | int topbar; 125 | Client *clients; 126 | Client *sel; 127 | Client *stack; 128 | Monitor *next; 129 | Window barwin; 130 | const Layout *lt[2]; 131 | }; 132 | 133 | typedef struct { 134 | const char *class; 135 | const char *instance; 136 | const char *title; 137 | unsigned int tags; 138 | int isfloating; 139 | int monitor; 140 | } Rule; 141 | 142 | /* function declarations */ 143 | static void applyrules(Client *c); 144 | static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact); 145 | static void arrange(Monitor *m); 146 | static void arrangemon(Monitor *m); 147 | static void attach(Client *c); 148 | static void attachstack(Client *c); 149 | static void buttonpress(XEvent *e); 150 | static void checkotherwm(void); 151 | static void cleanup(void); 152 | static void cleanupmon(Monitor *mon); 153 | static void clientmessage(XEvent *e); 154 | static void configure(Client *c); 155 | static void configurenotify(XEvent *e); 156 | static void configurerequest(XEvent *e); 157 | static Monitor *createmon(void); 158 | static void destroynotify(XEvent *e); 159 | static void detach(Client *c); 160 | static void detachstack(Client *c); 161 | static void drawbar(Monitor *m); 162 | static void drawbars(void); 163 | static void enternotify(XEvent *e); 164 | static void expose(XEvent *e); 165 | static void focus(Client *c); 166 | static void focusin(XEvent *e); 167 | static void focusstack(const Arg *arg); 168 | static int getrootptr(int *x, int *y); 169 | static long getstate(Window w); 170 | static int gettextprop(Window w, Atom atom, char *text, unsigned int size); 171 | static void grabbuttons(Client *c, int focused); 172 | static void grabkeys(void); 173 | static void incnmaster(const Arg *arg); 174 | static void keypress(XEvent *e); 175 | static void killclient(const Arg *arg); 176 | static void manage(Window w, XWindowAttributes *wa); 177 | static void mappingnotify(XEvent *e); 178 | static void maprequest(XEvent *e); 179 | static void monocle(Monitor *m); 180 | static void motionnotify(XEvent *e); 181 | static void movemouse(const Arg *arg); 182 | static Client *nexttiled(Client *c); 183 | static void pop(Client *); 184 | static void propertynotify(XEvent *e); 185 | static void quit(const Arg *arg); 186 | static Monitor *recttomon(int x, int y, int w, int h); 187 | static void resize(Client *c, int x, int y, int w, int h, int interact); 188 | static void resizeclient(Client *c, int x, int y, int w, int h); 189 | static void resizemouse(const Arg *arg); 190 | static void restack(Monitor *m); 191 | static void run(void); 192 | static void scan(void); 193 | static int sendevent(Client *c, Atom proto); 194 | static void sendmon(Client *c, Monitor *m); 195 | static void setclientstate(Client *c, long state); 196 | static void setfocus(Client *c); 197 | static void setfullscreen(Client *c, int fullscreen); 198 | static void setlayout(const Arg *arg); 199 | static void setmfact(const Arg *arg); 200 | static void setup(void); 201 | static void seturgent(Client *c, int urg); 202 | static void showhide(Client *c); 203 | static void sigchld(int unused); 204 | static void spawn(const Arg *arg); 205 | static void tile(Monitor *); 206 | static void togglebar(const Arg *arg); 207 | static void togglefloating(const Arg *arg); 208 | static void unfocus(Client *c, int setfocus); 209 | static void unmanage(Client *c, int destroyed); 210 | static void unmapnotify(XEvent *e); 211 | static void updatebarpos(Monitor *m); 212 | static void updatebars(void); 213 | static void updateclientlist(void); 214 | static int updategeom(void); 215 | static void updatenumlockmask(void); 216 | static void updatesizehints(Client *c); 217 | static void updatetitle(Client *c); 218 | static void updatewindowtype(Client *c); 219 | static void updatewmhints(Client *c); 220 | static void view(const Arg *arg); 221 | static Client *wintoclient(Window w); 222 | static Monitor *wintomon(Window w); 223 | static int xerror(Display *dpy, XErrorEvent *ee); 224 | static int xerrordummy(Display *dpy, XErrorEvent *ee); 225 | static int xerrorstart(Display *dpy, XErrorEvent *ee); 226 | static void zoom(const Arg *arg); 227 | 228 | /* variables */ 229 | static const char broken[] = "broken"; 230 | static char *stext = " X "; 231 | static int screen; 232 | static int sw, sh; /* X display screen geometry width, height */ 233 | static int bh, blw = 0; /* bar geometry */ 234 | static int lrpad; /* sum of left and right padding for text */ 235 | static int (*xerrorxlib)(Display *, XErrorEvent *); 236 | static unsigned int numlockmask = 0; 237 | static void (*handler[LASTEvent]) (XEvent *) = { 238 | [ButtonPress] = buttonpress, 239 | [ClientMessage] = clientmessage, 240 | [ConfigureRequest] = configurerequest, 241 | [ConfigureNotify] = configurenotify, 242 | [DestroyNotify] = destroynotify, 243 | [EnterNotify] = enternotify, 244 | [Expose] = expose, 245 | [FocusIn] = focusin, 246 | [KeyPress] = keypress, 247 | [MappingNotify] = mappingnotify, 248 | [MapRequest] = maprequest, 249 | [MotionNotify] = motionnotify, 250 | [PropertyNotify] = propertynotify, 251 | [UnmapNotify] = unmapnotify 252 | }; 253 | static Atom wmatom[WMLast], netatom[NetLast]; 254 | static int running = 1; 255 | static Cur *cursor[CurLast]; 256 | static Clr **scheme; 257 | static Display *dpy; 258 | static Drw *drw; 259 | static Monitor *mons, *selmon; 260 | static Window root, wmcheckwin; 261 | 262 | /* configuration, allows nested code to access above variables */ 263 | #include "config.h" 264 | 265 | /* compile-time check if all tags fit into an unsigned int bit array. */ 266 | struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; }; 267 | 268 | /* function implementations */ 269 | void 270 | applyrules(Client *c) 271 | { 272 | const char *class, *instance; 273 | unsigned int i; 274 | const Rule *r; 275 | Monitor *m; 276 | XClassHint ch = { NULL, NULL }; 277 | 278 | /* rule matching */ 279 | c->isfloating = 0; 280 | c->tags = 0; 281 | XGetClassHint(dpy, c->win, &ch); 282 | class = ch.res_class ? ch.res_class : broken; 283 | instance = ch.res_name ? ch.res_name : broken; 284 | 285 | for (i = 0; i < LENGTH(rules); i++) { 286 | r = &rules[i]; 287 | if ((!r->title || strstr(c->name, r->title)) 288 | && (!r->class || strstr(class, r->class)) 289 | && (!r->instance || strstr(instance, r->instance))) 290 | { 291 | c->isfloating = r->isfloating; 292 | c->tags |= r->tags; 293 | for (m = mons; m && m->num != r->monitor; m = m->next); 294 | if (m) 295 | c->mon = m; 296 | } 297 | } 298 | if (ch.res_class) 299 | XFree(ch.res_class); 300 | if (ch.res_name) 301 | XFree(ch.res_name); 302 | c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : c->mon->tagset[c->mon->seltags]; 303 | } 304 | 305 | int 306 | applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact) 307 | { 308 | int baseismin; 309 | Monitor *m = c->mon; 310 | 311 | /* set minimum possible */ 312 | *w = MAX(1, *w); 313 | *h = MAX(1, *h); 314 | if (interact) { 315 | if (*x > sw) 316 | *x = sw - WIDTH(c); 317 | if (*y > sh) 318 | *y = sh - HEIGHT(c); 319 | if (*x + *w + 2 * c->bw < 0) 320 | *x = 0; 321 | if (*y + *h + 2 * c->bw < 0) 322 | *y = 0; 323 | } else { 324 | if (*x >= m->wx + m->ww) 325 | *x = m->wx + m->ww - WIDTH(c); 326 | if (*y >= m->wy + m->wh) 327 | *y = m->wy + m->wh - HEIGHT(c); 328 | if (*x + *w + 2 * c->bw <= m->wx) 329 | *x = m->wx; 330 | if (*y + *h + 2 * c->bw <= m->wy) 331 | *y = m->wy; 332 | } 333 | if (*h < bh) 334 | *h = bh; 335 | if (*w < bh) 336 | *w = bh; 337 | if (resizehints || c->isfloating || !c->mon->lt[c->mon->sellt]->arrange) { 338 | /* see last two sentences in ICCCM 4.1.2.3 */ 339 | baseismin = c->basew == c->minw && c->baseh == c->minh; 340 | if (!baseismin) { /* temporarily remove base dimensions */ 341 | *w -= c->basew; 342 | *h -= c->baseh; 343 | } 344 | /* adjust for aspect limits */ 345 | if (c->mina > 0 && c->maxa > 0) { 346 | if (c->maxa < (float)*w / *h) 347 | *w = *h * c->maxa + 0.5; 348 | else if (c->mina < (float)*h / *w) 349 | *h = *w * c->mina + 0.5; 350 | } 351 | if (baseismin) { /* increment calculation requires this */ 352 | *w -= c->basew; 353 | *h -= c->baseh; 354 | } 355 | /* adjust for increment value */ 356 | if (c->incw) 357 | *w -= *w % c->incw; 358 | if (c->inch) 359 | *h -= *h % c->inch; 360 | /* restore base dimensions */ 361 | *w = MAX(*w + c->basew, c->minw); 362 | *h = MAX(*h + c->baseh, c->minh); 363 | if (c->maxw) 364 | *w = MIN(*w, c->maxw); 365 | if (c->maxh) 366 | *h = MIN(*h, c->maxh); 367 | } 368 | return *x != c->x || *y != c->y || *w != c->w || *h != c->h; 369 | } 370 | 371 | void 372 | arrange(Monitor *m) 373 | { 374 | if (m) 375 | showhide(m->stack); 376 | else for (m = mons; m; m = m->next) 377 | showhide(m->stack); 378 | if (m) { 379 | arrangemon(m); 380 | restack(m); 381 | } else for (m = mons; m; m = m->next) 382 | arrangemon(m); 383 | } 384 | 385 | void 386 | arrangemon(Monitor *m) 387 | { 388 | strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof m->ltsymbol); 389 | if (m->lt[m->sellt]->arrange) 390 | m->lt[m->sellt]->arrange(m); 391 | } 392 | 393 | void 394 | attach(Client *c) 395 | { 396 | c->next = c->mon->clients; 397 | c->mon->clients = c; 398 | } 399 | 400 | void 401 | attachstack(Client *c) 402 | { 403 | c->snext = c->mon->stack; 404 | c->mon->stack = c; 405 | } 406 | 407 | void 408 | buttonpress(XEvent *e) 409 | { 410 | unsigned int i, x, click; 411 | Arg arg = {0}; 412 | Client *c; 413 | Monitor *m; 414 | XButtonPressedEvent *ev = &e->xbutton; 415 | 416 | click = ClkRootWin; 417 | /* focus monitor if necessary */ 418 | if ((m = wintomon(ev->window)) && m != selmon) { 419 | unfocus(selmon->sel, 1); 420 | selmon = m; 421 | focus(NULL); 422 | } 423 | if (ev->window == selmon->barwin) { 424 | i = x = 0; 425 | // do 426 | // x += TEXTW(tags[i]); 427 | // while (ev->x >= x && ++i < LENGTH(tags)); 428 | if (i < LENGTH(tags)-1) { 429 | click = ClkTagBar; 430 | arg.ui = 1 << i; 431 | } else if (ev->x < x + blw) 432 | click = ClkLtSymbol; 433 | else if (ev->x > selmon->ww - TEXTW(stext)) 434 | click = ClkStatusText; 435 | else 436 | click = ClkWinTitle; 437 | fprintf(stderr, "buttonpress: ww=%d textw=%d evx=%d x=%d click=%d\n", 438 | selmon->ww, TEXTW(stext), ev->x, x, click); 439 | } else if ((c = wintoclient(ev->window))) { 440 | focus(c); 441 | restack(selmon); 442 | XAllowEvents(dpy, ReplayPointer, CurrentTime); 443 | click = ClkClientWin; 444 | } 445 | for (i = 0; i < LENGTH(buttons); i++) 446 | if (click == buttons[i].click && buttons[i].func && buttons[i].button == ev->button 447 | && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state)) 448 | buttons[i].func(click == ClkTagBar && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg); 449 | } 450 | 451 | void 452 | checkotherwm(void) 453 | { 454 | xerrorxlib = XSetErrorHandler(xerrorstart); 455 | /* this causes an error if some other window manager is running */ 456 | XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask); 457 | XSync(dpy, False); 458 | XSetErrorHandler(xerror); 459 | XSync(dpy, False); 460 | } 461 | 462 | void 463 | cleanup(void) 464 | { 465 | Arg a = {.ui = ~0}; 466 | Layout foo = { "", NULL }; 467 | Monitor *m; 468 | size_t i; 469 | 470 | view(&a); 471 | selmon->lt[selmon->sellt] = &foo; 472 | for (m = mons; m; m = m->next) 473 | while (m->stack) 474 | unmanage(m->stack, 0); 475 | XUngrabKey(dpy, AnyKey, AnyModifier, root); 476 | while (mons) 477 | cleanupmon(mons); 478 | for (i = 0; i < CurLast; i++) 479 | drw_cur_free(drw, cursor[i]); 480 | for (i = 0; i < LENGTH(colors); i++) 481 | free(scheme[i]); 482 | XDestroyWindow(dpy, wmcheckwin); 483 | drw_free(drw); 484 | XSync(dpy, False); 485 | XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); 486 | XDeleteProperty(dpy, root, netatom[NetActiveWindow]); 487 | } 488 | 489 | void 490 | cleanupmon(Monitor *mon) 491 | { 492 | Monitor *m; 493 | 494 | if (mon == mons) 495 | mons = mons->next; 496 | else { 497 | for (m = mons; m && m->next != mon; m = m->next); 498 | m->next = mon->next; 499 | } 500 | XUnmapWindow(dpy, mon->barwin); 501 | XDestroyWindow(dpy, mon->barwin); 502 | free(mon); 503 | } 504 | 505 | void 506 | clientmessage(XEvent *e) 507 | { 508 | XClientMessageEvent *cme = &e->xclient; 509 | Client *c = wintoclient(cme->window); 510 | 511 | if (!c) 512 | return; 513 | if (cme->message_type == netatom[NetWMState]) { 514 | if (cme->data.l[1] == netatom[NetWMFullscreen] 515 | || cme->data.l[2] == netatom[NetWMFullscreen]) 516 | setfullscreen(c, (cme->data.l[0] == 1 /* _NET_WM_STATE_ADD */ 517 | || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->isfullscreen))); 518 | } else if (cme->message_type == netatom[NetActiveWindow]) { 519 | if (c != selmon->sel && !c->isurgent) 520 | seturgent(c, 1); 521 | } 522 | } 523 | 524 | void 525 | configure(Client *c) 526 | { 527 | XConfigureEvent ce; 528 | 529 | ce.type = ConfigureNotify; 530 | ce.display = dpy; 531 | ce.event = c->win; 532 | ce.window = c->win; 533 | ce.x = c->x; 534 | ce.y = c->y; 535 | ce.width = c->w; 536 | ce.height = c->h; 537 | ce.border_width = c->bw; 538 | ce.above = None; 539 | ce.override_redirect = False; 540 | XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce); 541 | } 542 | 543 | void 544 | configurenotify(XEvent *e) 545 | { 546 | Monitor *m; 547 | Client *c; 548 | XConfigureEvent *ev = &e->xconfigure; 549 | int dirty; 550 | 551 | /* TODO: updategeom handling sucks, needs to be simplified */ 552 | if (ev->window == root) { 553 | dirty = (sw != ev->width || sh != ev->height); 554 | sw = ev->width; 555 | sh = ev->height; 556 | if (updategeom() || dirty) { 557 | drw_resize(drw, sw, bh); 558 | updatebars(); 559 | for (m = mons; m; m = m->next) { 560 | for (c = m->clients; c; c = c->next) 561 | if (c->isfullscreen) 562 | resizeclient(c, m->mx, m->my, m->mw, m->mh); 563 | XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, m->ww, bh); 564 | } 565 | focus(NULL); 566 | arrange(NULL); 567 | } 568 | } 569 | } 570 | 571 | void 572 | configurerequest(XEvent *e) 573 | { 574 | Client *c; 575 | Monitor *m; 576 | XConfigureRequestEvent *ev = &e->xconfigurerequest; 577 | XWindowChanges wc; 578 | 579 | if ((c = wintoclient(ev->window))) { 580 | if (ev->value_mask & CWBorderWidth) 581 | c->bw = ev->border_width; 582 | else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) { 583 | m = c->mon; 584 | if (ev->value_mask & CWX) { 585 | c->oldx = c->x; 586 | c->x = m->mx + ev->x; 587 | } 588 | if (ev->value_mask & CWY) { 589 | c->oldy = c->y; 590 | c->y = m->my + ev->y; 591 | } 592 | if (ev->value_mask & CWWidth) { 593 | c->oldw = c->w; 594 | c->w = ev->width; 595 | } 596 | if (ev->value_mask & CWHeight) { 597 | c->oldh = c->h; 598 | c->h = ev->height; 599 | } 600 | if ((c->x + c->w) > m->mx + m->mw && c->isfloating) 601 | c->x = m->mx + (m->mw / 2 - WIDTH(c) / 2); /* center in x direction */ 602 | if ((c->y + c->h) > m->my + m->mh && c->isfloating) 603 | c->y = m->my + (m->mh / 2 - HEIGHT(c) / 2); /* center in y direction */ 604 | if ((ev->value_mask & (CWX|CWY)) && !(ev->value_mask & (CWWidth|CWHeight))) 605 | configure(c); 606 | if (ISVISIBLE(c)) 607 | XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); 608 | } else 609 | configure(c); 610 | } else { 611 | wc.x = ev->x; 612 | wc.y = ev->y; 613 | wc.width = ev->width; 614 | wc.height = ev->height; 615 | wc.border_width = ev->border_width; 616 | wc.sibling = ev->above; 617 | wc.stack_mode = ev->detail; 618 | XConfigureWindow(dpy, ev->window, ev->value_mask, &wc); 619 | } 620 | XSync(dpy, False); 621 | } 622 | 623 | Monitor * 624 | createmon(void) 625 | { 626 | Monitor *m; 627 | 628 | m = ecalloc(1, sizeof(Monitor)); 629 | m->tagset[0] = m->tagset[1] = 1; 630 | m->mfact = mfact; 631 | m->nmaster = nmaster; 632 | m->showbar = showbar; 633 | m->topbar = topbar; 634 | m->lt[0] = &layouts[0]; 635 | m->lt[1] = &layouts[1 % LENGTH(layouts)]; 636 | strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); 637 | return m; 638 | } 639 | 640 | void 641 | destroynotify(XEvent *e) 642 | { 643 | Client *c; 644 | XDestroyWindowEvent *ev = &e->xdestroywindow; 645 | 646 | if ((c = wintoclient(ev->window))) 647 | unmanage(c, 1); 648 | } 649 | 650 | void 651 | detach(Client *c) 652 | { 653 | Client **tc; 654 | 655 | for (tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next); 656 | *tc = c->next; 657 | } 658 | 659 | void 660 | detachstack(Client *c) 661 | { 662 | Client **tc, *t; 663 | 664 | for (tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext); 665 | *tc = c->snext; 666 | 667 | if (c == c->mon->sel) { 668 | for (t = c->mon->stack; t && !ISVISIBLE(t); t = t->snext); 669 | c->mon->sel = t; 670 | } 671 | } 672 | 673 | void 674 | drawbar(Monitor *m) 675 | { 676 | int x, w, sw = 0; 677 | int boxs = drw->fonts->h / 9; 678 | int boxw = drw->fonts->h / 6 + 2; 679 | unsigned int i, occ = 0, urg = 0; 680 | Client *c; 681 | 682 | /* draw status first so it can be overdrawn by tags later */ 683 | if (m == selmon) { /* status is only drawn on selected monitor */ 684 | drw_setscheme(drw, scheme[SchemeNorm]); 685 | sw = TEXTW(stext) - lrpad + 2; /* 2px right padding */ 686 | drw_text(drw, m->ww - sw, 0, sw, bh, 0, stext, 0); 687 | } 688 | 689 | for (c = m->clients; c; c = c->next) { 690 | occ |= c->tags; 691 | if (c->isurgent) 692 | urg |= c->tags; 693 | } 694 | x = 0; 695 | if(0) for (i = 0; i < LENGTH(tags); i++) { 696 | w = TEXTW(tags[i]); 697 | drw_setscheme(drw, scheme[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); 698 | drw_text(drw, x, 0, w, bh, lrpad / 2, tags[i], urg & 1 << i); 699 | if (occ & 1 << i) 700 | drw_rect(drw, x + boxs, boxs, boxw, boxw, 701 | m == selmon && selmon->sel && selmon->sel->tags & 1 << i, 702 | urg & 1 << i); 703 | x += w; 704 | } 705 | w = blw = TEXTW(m->ltsymbol); 706 | drw_setscheme(drw, scheme[SchemeNorm]); 707 | x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0); 708 | 709 | if ((w = m->ww - sw - x) > bh) { 710 | if (m->sel) { 711 | drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]); 712 | drw_text(drw, x, 0, w, bh, lrpad / 2, m->sel->name, 0); 713 | if (m->sel->isfloating) 714 | drw_rect(drw, x + boxs, boxs, boxw, boxw, m->sel->isfixed, 0); 715 | } else { 716 | drw_setscheme(drw, scheme[SchemeNorm]); 717 | drw_rect(drw, x, 0, w, bh, 1, 1); 718 | } 719 | } 720 | drw_map(drw, m->barwin, 0, 0, m->ww, bh); 721 | } 722 | 723 | void 724 | drawbars(void) 725 | { 726 | Monitor *m; 727 | 728 | for (m = mons; m; m = m->next) 729 | drawbar(m); 730 | } 731 | 732 | void 733 | enternotify(XEvent *e) 734 | { 735 | Client *c; 736 | Monitor *m; 737 | XCrossingEvent *ev = &e->xcrossing; 738 | 739 | if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != root) 740 | return; 741 | c = wintoclient(ev->window); 742 | m = c ? c->mon : wintomon(ev->window); 743 | if (m != selmon) { 744 | unfocus(selmon->sel, 1); 745 | selmon = m; 746 | } else if (!c || c == selmon->sel) 747 | return; 748 | focus(c); 749 | } 750 | 751 | void 752 | expose(XEvent *e) 753 | { 754 | Monitor *m; 755 | XExposeEvent *ev = &e->xexpose; 756 | 757 | if (ev->count == 0 && (m = wintomon(ev->window))) 758 | drawbar(m); 759 | } 760 | 761 | void 762 | focus(Client *c) 763 | { 764 | if (!c || !ISVISIBLE(c)) 765 | for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext); 766 | if (selmon->sel && selmon->sel != c) 767 | unfocus(selmon->sel, 0); 768 | if (c) { 769 | if (c->mon != selmon) 770 | selmon = c->mon; 771 | if (c->isurgent) 772 | seturgent(c, 0); 773 | detachstack(c); 774 | attachstack(c); 775 | grabbuttons(c, 1); 776 | XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel); 777 | setfocus(c); 778 | } else { 779 | XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); 780 | XDeleteProperty(dpy, root, netatom[NetActiveWindow]); 781 | } 782 | selmon->sel = c; 783 | drawbars(); 784 | } 785 | 786 | /* there are some broken focus acquiring clients needing extra handling */ 787 | void 788 | focusin(XEvent *e) 789 | { 790 | XFocusChangeEvent *ev = &e->xfocus; 791 | 792 | if (selmon->sel && ev->window != selmon->sel->win) 793 | setfocus(selmon->sel); 794 | } 795 | 796 | void 797 | focusstack(const Arg *arg) 798 | { 799 | Client *c = NULL, *i; 800 | 801 | if (!selmon->sel) 802 | return; 803 | if (arg->i > 0) { 804 | for (c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next); 805 | if (!c) 806 | for (c = selmon->clients; c && !ISVISIBLE(c); c = c->next); 807 | } else { 808 | for (i = selmon->clients; i != selmon->sel; i = i->next) 809 | if (ISVISIBLE(i)) 810 | c = i; 811 | if (!c) 812 | for (; i; i = i->next) 813 | if (ISVISIBLE(i)) 814 | c = i; 815 | } 816 | if (c) { 817 | focus(c); 818 | restack(selmon); 819 | } 820 | } 821 | 822 | Atom 823 | getatomprop(Client *c, Atom prop) 824 | { 825 | int di; 826 | unsigned long dl; 827 | unsigned char *p = NULL; 828 | Atom da, atom = None; 829 | 830 | if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM, 831 | &da, &di, &dl, &dl, &p) == Success && p) { 832 | atom = *(Atom *)p; 833 | XFree(p); 834 | } 835 | return atom; 836 | } 837 | 838 | int 839 | getrootptr(int *x, int *y) 840 | { 841 | int di; 842 | unsigned int dui; 843 | Window dummy; 844 | 845 | return XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui); 846 | } 847 | 848 | long 849 | getstate(Window w) 850 | { 851 | int format; 852 | long result = -1; 853 | unsigned char *p = NULL; 854 | unsigned long n, extra; 855 | Atom real; 856 | 857 | if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState], 858 | &real, &format, &n, &extra, (unsigned char **)&p) != Success) 859 | return -1; 860 | if (n != 0) 861 | result = *p; 862 | XFree(p); 863 | return result; 864 | } 865 | 866 | int 867 | gettextprop(Window w, Atom atom, char *text, unsigned int size) 868 | { 869 | char **list = NULL; 870 | int n; 871 | XTextProperty name; 872 | 873 | if (!text || size == 0) 874 | return 0; 875 | text[0] = '\0'; 876 | if (!XGetTextProperty(dpy, w, &name, atom) || !name.nitems) 877 | return 0; 878 | if (name.encoding == XA_STRING) 879 | strncpy(text, (char *)name.value, size - 1); 880 | else { 881 | if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success && n > 0 && *list) { 882 | strncpy(text, *list, size - 1); 883 | XFreeStringList(list); 884 | } 885 | } 886 | text[size - 1] = '\0'; 887 | XFree(name.value); 888 | return 1; 889 | } 890 | 891 | void 892 | grabbuttons(Client *c, int focused) 893 | { 894 | updatenumlockmask(); 895 | { 896 | unsigned int i, j; 897 | unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; 898 | XUngrabButton(dpy, AnyButton, AnyModifier, c->win); 899 | if (!focused) 900 | XGrabButton(dpy, AnyButton, AnyModifier, c->win, False, 901 | BUTTONMASK, GrabModeSync, GrabModeSync, None, None); 902 | for (i = 0; i < LENGTH(buttons); i++) 903 | if (buttons[i].click == ClkClientWin) 904 | for (j = 0; j < LENGTH(modifiers); j++) 905 | XGrabButton(dpy, buttons[i].button, 906 | buttons[i].mask | modifiers[j], 907 | c->win, False, BUTTONMASK, 908 | GrabModeAsync, GrabModeSync, None, None); 909 | } 910 | } 911 | 912 | void 913 | grabkeys(void) 914 | { 915 | updatenumlockmask(); 916 | { 917 | unsigned int i, j; 918 | unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; 919 | KeyCode code; 920 | 921 | XUngrabKey(dpy, AnyKey, AnyModifier, root); 922 | for (i = 0; i < LENGTH(keys); i++) 923 | if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) 924 | for (j = 0; j < LENGTH(modifiers); j++) 925 | XGrabKey(dpy, code, keys[i].mod | modifiers[j], root, 926 | True, GrabModeAsync, GrabModeAsync); 927 | } 928 | } 929 | 930 | void 931 | incnmaster(const Arg *arg) 932 | { 933 | selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); 934 | arrange(selmon); 935 | } 936 | 937 | void 938 | keypress(XEvent *e) 939 | { 940 | unsigned int i; 941 | KeySym keysym; 942 | XKeyEvent *ev; 943 | 944 | ev = &e->xkey; 945 | keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0); 946 | for (i = 0; i < LENGTH(keys); i++) 947 | if (keysym == keys[i].keysym 948 | && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) 949 | && keys[i].func) 950 | keys[i].func(&(keys[i].arg)); 951 | } 952 | 953 | void 954 | killclient(const Arg *arg) 955 | { 956 | if (!selmon->sel) 957 | return; 958 | if (!sendevent(selmon->sel, wmatom[WMDelete])) { 959 | XGrabServer(dpy); 960 | XSetErrorHandler(xerrordummy); 961 | XSetCloseDownMode(dpy, DestroyAll); 962 | XKillClient(dpy, selmon->sel->win); 963 | XSync(dpy, False); 964 | XSetErrorHandler(xerror); 965 | XUngrabServer(dpy); 966 | } 967 | } 968 | 969 | void 970 | manage(Window w, XWindowAttributes *wa) 971 | { 972 | Client *c, *t = NULL; 973 | Window trans = None; 974 | XWindowChanges wc; 975 | 976 | c = ecalloc(1, sizeof(Client)); 977 | c->win = w; 978 | /* geometry */ 979 | c->x = c->oldx = wa->x; 980 | c->y = c->oldy = wa->y; 981 | c->w = c->oldw = wa->width; 982 | c->h = c->oldh = wa->height; 983 | c->oldbw = wa->border_width; 984 | 985 | updatetitle(c); 986 | if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) { 987 | c->mon = t->mon; 988 | c->tags = t->tags; 989 | } else { 990 | c->mon = selmon; 991 | applyrules(c); 992 | } 993 | 994 | if (c->x + WIDTH(c) > c->mon->mx + c->mon->mw) 995 | c->x = c->mon->mx + c->mon->mw - WIDTH(c); 996 | if (c->y + HEIGHT(c) > c->mon->my + c->mon->mh) 997 | c->y = c->mon->my + c->mon->mh - HEIGHT(c); 998 | c->x = MAX(c->x, c->mon->mx); 999 | /* only fix client y-offset, if the client center might cover the bar */ 1000 | c->y = MAX(c->y, ((c->mon->by == c->mon->my) && (c->x + (c->w / 2) >= c->mon->wx) 1001 | && (c->x + (c->w / 2) < c->mon->wx + c->mon->ww)) ? bh : c->mon->my); 1002 | c->bw = borderpx; 1003 | 1004 | wc.border_width = c->bw; 1005 | XConfigureWindow(dpy, w, CWBorderWidth, &wc); 1006 | XSetWindowBorder(dpy, w, scheme[SchemeNorm][ColBorder].pixel); 1007 | configure(c); /* propagates border_width, if size doesn't change */ 1008 | updatewindowtype(c); 1009 | updatesizehints(c); 1010 | updatewmhints(c); 1011 | XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); 1012 | grabbuttons(c, 0); 1013 | if (!c->isfloating) 1014 | c->isfloating = c->oldstate = trans != None || c->isfixed; 1015 | if (c->isfloating) 1016 | XRaiseWindow(dpy, c->win); 1017 | attach(c); 1018 | attachstack(c); 1019 | XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, 1020 | (unsigned char *) &(c->win), 1); 1021 | XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */ 1022 | setclientstate(c, NormalState); 1023 | if (c->mon == selmon) 1024 | unfocus(selmon->sel, 0); 1025 | c->mon->sel = c; 1026 | arrange(c->mon); 1027 | XMapWindow(dpy, c->win); 1028 | focus(NULL); 1029 | } 1030 | 1031 | void 1032 | mappingnotify(XEvent *e) 1033 | { 1034 | XMappingEvent *ev = &e->xmapping; 1035 | 1036 | XRefreshKeyboardMapping(ev); 1037 | if (ev->request == MappingKeyboard) 1038 | grabkeys(); 1039 | } 1040 | 1041 | void 1042 | maprequest(XEvent *e) 1043 | { 1044 | static XWindowAttributes wa; 1045 | XMapRequestEvent *ev = &e->xmaprequest; 1046 | 1047 | if (!XGetWindowAttributes(dpy, ev->window, &wa)) 1048 | return; 1049 | if (wa.override_redirect) 1050 | return; 1051 | if (!wintoclient(ev->window)) 1052 | manage(ev->window, &wa); 1053 | } 1054 | 1055 | void 1056 | monocle(Monitor *m) 1057 | { 1058 | unsigned int n = 0; 1059 | Client *c; 1060 | 1061 | for (c = m->clients; c; c = c->next) 1062 | if (ISVISIBLE(c)) 1063 | n++; 1064 | if (n > 0) /* override layout symbol */ 1065 | snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n); 1066 | for (c = nexttiled(m->clients); c; c = nexttiled(c->next)) 1067 | resize(c, m->wx, m->wy, m->ww - 2 * c->bw, m->wh - 2 * c->bw, 0); 1068 | } 1069 | 1070 | void 1071 | motionnotify(XEvent *e) 1072 | { 1073 | static Monitor *mon = NULL; 1074 | Monitor *m; 1075 | XMotionEvent *ev = &e->xmotion; 1076 | 1077 | if (ev->window != root) 1078 | return; 1079 | if ((m = recttomon(ev->x_root, ev->y_root, 1, 1)) != mon && mon) { 1080 | unfocus(selmon->sel, 1); 1081 | selmon = m; 1082 | focus(NULL); 1083 | } 1084 | mon = m; 1085 | } 1086 | 1087 | void 1088 | movemouse(const Arg *arg) 1089 | { 1090 | int x, y, ocx, ocy, nx, ny; 1091 | Client *c; 1092 | Monitor *m; 1093 | XEvent ev; 1094 | Time lasttime = 0; 1095 | 1096 | if (!(c = selmon->sel)) 1097 | return; 1098 | if (c->isfullscreen) /* no support moving fullscreen windows by mouse */ 1099 | return; 1100 | restack(selmon); 1101 | ocx = c->x; 1102 | ocy = c->y; 1103 | if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, 1104 | None, cursor[CurMove]->cursor, CurrentTime) != GrabSuccess) 1105 | return; 1106 | if (!getrootptr(&x, &y)) 1107 | return; 1108 | do { 1109 | XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); 1110 | switch(ev.type) { 1111 | case ConfigureRequest: 1112 | case Expose: 1113 | case MapRequest: 1114 | handler[ev.type](&ev); 1115 | break; 1116 | case MotionNotify: 1117 | if ((ev.xmotion.time - lasttime) <= (1000 / 60)) 1118 | continue; 1119 | lasttime = ev.xmotion.time; 1120 | 1121 | nx = ocx + (ev.xmotion.x - x); 1122 | ny = ocy + (ev.xmotion.y - y); 1123 | if (abs(selmon->wx - nx) < snap) 1124 | nx = selmon->wx; 1125 | else if (abs((selmon->wx + selmon->ww) - (nx + WIDTH(c))) < snap) 1126 | nx = selmon->wx + selmon->ww - WIDTH(c); 1127 | if (abs(selmon->wy - ny) < snap) 1128 | ny = selmon->wy; 1129 | else if (abs((selmon->wy + selmon->wh) - (ny + HEIGHT(c))) < snap) 1130 | ny = selmon->wy + selmon->wh - HEIGHT(c); 1131 | if (!c->isfloating && selmon->lt[selmon->sellt]->arrange 1132 | && (abs(nx - c->x) > snap || abs(ny - c->y) > snap)) 1133 | togglefloating(NULL); 1134 | if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) 1135 | resize(c, nx, ny, c->w, c->h, 1); 1136 | break; 1137 | } 1138 | } while (ev.type != ButtonRelease); 1139 | XUngrabPointer(dpy, CurrentTime); 1140 | if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { 1141 | sendmon(c, m); 1142 | selmon = m; 1143 | focus(NULL); 1144 | } 1145 | } 1146 | 1147 | Client * 1148 | nexttiled(Client *c) 1149 | { 1150 | for (; c && (c->isfloating || !ISVISIBLE(c)); c = c->next); 1151 | return c; 1152 | } 1153 | 1154 | void 1155 | pop(Client *c) 1156 | { 1157 | detach(c); 1158 | attach(c); 1159 | focus(c); 1160 | arrange(c->mon); 1161 | } 1162 | 1163 | void 1164 | propertynotify(XEvent *e) 1165 | { 1166 | Client *c; 1167 | Window trans; 1168 | XPropertyEvent *ev = &e->xproperty; 1169 | 1170 | if (ev->state == PropertyDelete) 1171 | return; /* ignore */ 1172 | else if ((c = wintoclient(ev->window))) { 1173 | switch(ev->atom) { 1174 | default: break; 1175 | case XA_WM_TRANSIENT_FOR: 1176 | if (!c->isfloating && (XGetTransientForHint(dpy, c->win, &trans)) && 1177 | (c->isfloating = (wintoclient(trans)) != NULL)) 1178 | arrange(c->mon); 1179 | break; 1180 | case XA_WM_NORMAL_HINTS: 1181 | updatesizehints(c); 1182 | break; 1183 | case XA_WM_HINTS: 1184 | updatewmhints(c); 1185 | drawbars(); 1186 | break; 1187 | } 1188 | if (ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) { 1189 | updatetitle(c); 1190 | if (c == c->mon->sel) 1191 | drawbar(c->mon); 1192 | } 1193 | if (ev->atom == netatom[NetWMWindowType]) 1194 | updatewindowtype(c); 1195 | } 1196 | } 1197 | 1198 | void 1199 | quit(const Arg *arg) 1200 | { 1201 | running = 0; 1202 | } 1203 | 1204 | Monitor * 1205 | recttomon(int x, int y, int w, int h) 1206 | { 1207 | Monitor *m, *r = selmon; 1208 | int a, area = 0; 1209 | 1210 | for (m = mons; m; m = m->next) 1211 | if ((a = INTERSECT(x, y, w, h, m)) > area) { 1212 | area = a; 1213 | r = m; 1214 | } 1215 | return r; 1216 | } 1217 | 1218 | void 1219 | resize(Client *c, int x, int y, int w, int h, int interact) 1220 | { 1221 | if (applysizehints(c, &x, &y, &w, &h, interact)) 1222 | resizeclient(c, x, y, w, h); 1223 | } 1224 | 1225 | void 1226 | resizeclient(Client *c, int x, int y, int w, int h) 1227 | { 1228 | XWindowChanges wc; 1229 | 1230 | c->oldx = c->x; c->x = wc.x = x; 1231 | c->oldy = c->y; c->y = wc.y = y; 1232 | c->oldw = c->w; c->w = wc.width = w; 1233 | c->oldh = c->h; c->h = wc.height = h; 1234 | wc.border_width = c->bw; 1235 | XConfigureWindow(dpy, c->win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc); 1236 | configure(c); 1237 | XSync(dpy, False); 1238 | } 1239 | 1240 | void 1241 | resizemouse(const Arg *arg) 1242 | { 1243 | int ocx, ocy, nw, nh; 1244 | Client *c; 1245 | Monitor *m; 1246 | XEvent ev; 1247 | Time lasttime = 0; 1248 | 1249 | if (!(c = selmon->sel)) 1250 | return; 1251 | if (c->isfullscreen) /* no support resizing fullscreen windows by mouse */ 1252 | return; 1253 | restack(selmon); 1254 | ocx = c->x; 1255 | ocy = c->y; 1256 | if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, 1257 | None, cursor[CurResize]->cursor, CurrentTime) != GrabSuccess) 1258 | return; 1259 | XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); 1260 | do { 1261 | XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); 1262 | switch(ev.type) { 1263 | case ConfigureRequest: 1264 | case Expose: 1265 | case MapRequest: 1266 | handler[ev.type](&ev); 1267 | break; 1268 | case MotionNotify: 1269 | if ((ev.xmotion.time - lasttime) <= (1000 / 60)) 1270 | continue; 1271 | lasttime = ev.xmotion.time; 1272 | 1273 | nw = MAX(ev.xmotion.x - ocx - 2 * c->bw + 1, 1); 1274 | nh = MAX(ev.xmotion.y - ocy - 2 * c->bw + 1, 1); 1275 | if (c->mon->wx + nw >= selmon->wx && c->mon->wx + nw <= selmon->wx + selmon->ww 1276 | && c->mon->wy + nh >= selmon->wy && c->mon->wy + nh <= selmon->wy + selmon->wh) 1277 | { 1278 | if (!c->isfloating && selmon->lt[selmon->sellt]->arrange 1279 | && (abs(nw - c->w) > snap || abs(nh - c->h) > snap)) 1280 | togglefloating(NULL); 1281 | } 1282 | if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) 1283 | resize(c, c->x, c->y, nw, nh, 1); 1284 | break; 1285 | } 1286 | } while (ev.type != ButtonRelease); 1287 | XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); 1288 | XUngrabPointer(dpy, CurrentTime); 1289 | while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); 1290 | if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { 1291 | sendmon(c, m); 1292 | selmon = m; 1293 | focus(NULL); 1294 | } 1295 | } 1296 | 1297 | void 1298 | restack(Monitor *m) 1299 | { 1300 | Client *c; 1301 | XEvent ev; 1302 | XWindowChanges wc; 1303 | 1304 | drawbar(m); 1305 | if (!m->sel) 1306 | return; 1307 | if (m->sel->isfloating || !m->lt[m->sellt]->arrange) 1308 | XRaiseWindow(dpy, m->sel->win); 1309 | if (m->lt[m->sellt]->arrange) { 1310 | wc.stack_mode = Below; 1311 | wc.sibling = m->barwin; 1312 | for (c = m->stack; c; c = c->snext) 1313 | if (!c->isfloating && ISVISIBLE(c)) { 1314 | XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc); 1315 | wc.sibling = c->win; 1316 | } 1317 | } 1318 | XSync(dpy, False); 1319 | while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); 1320 | } 1321 | 1322 | void 1323 | run(void) 1324 | { 1325 | XEvent ev; 1326 | /* main event loop */ 1327 | XSync(dpy, False); 1328 | while (running && !XNextEvent(dpy, &ev)) 1329 | if (handler[ev.type]) 1330 | handler[ev.type](&ev); /* call handler */ 1331 | } 1332 | 1333 | void 1334 | scan(void) 1335 | { 1336 | unsigned int i, num; 1337 | Window d1, d2, *wins = NULL; 1338 | XWindowAttributes wa; 1339 | 1340 | if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) { 1341 | for (i = 0; i < num; i++) { 1342 | if (!XGetWindowAttributes(dpy, wins[i], &wa) 1343 | || wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1)) 1344 | continue; 1345 | if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState) 1346 | manage(wins[i], &wa); 1347 | } 1348 | for (i = 0; i < num; i++) { /* now the transients */ 1349 | if (!XGetWindowAttributes(dpy, wins[i], &wa)) 1350 | continue; 1351 | if (XGetTransientForHint(dpy, wins[i], &d1) 1352 | && (wa.map_state == IsViewable || getstate(wins[i]) == IconicState)) 1353 | manage(wins[i], &wa); 1354 | } 1355 | if (wins) 1356 | XFree(wins); 1357 | } 1358 | } 1359 | 1360 | void 1361 | sendmon(Client *c, Monitor *m) 1362 | { 1363 | if (c->mon == m) 1364 | return; 1365 | unfocus(c, 1); 1366 | detach(c); 1367 | detachstack(c); 1368 | c->mon = m; 1369 | c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */ 1370 | attach(c); 1371 | attachstack(c); 1372 | focus(NULL); 1373 | arrange(NULL); 1374 | } 1375 | 1376 | void 1377 | setclientstate(Client *c, long state) 1378 | { 1379 | long data[] = { state, None }; 1380 | 1381 | XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32, 1382 | PropModeReplace, (unsigned char *)data, 2); 1383 | } 1384 | 1385 | int 1386 | sendevent(Client *c, Atom proto) 1387 | { 1388 | int n; 1389 | Atom *protocols; 1390 | int exists = 0; 1391 | XEvent ev; 1392 | 1393 | if (XGetWMProtocols(dpy, c->win, &protocols, &n)) { 1394 | while (!exists && n--) 1395 | exists = protocols[n] == proto; 1396 | XFree(protocols); 1397 | } 1398 | if (exists) { 1399 | ev.type = ClientMessage; 1400 | ev.xclient.window = c->win; 1401 | ev.xclient.message_type = wmatom[WMProtocols]; 1402 | ev.xclient.format = 32; 1403 | ev.xclient.data.l[0] = proto; 1404 | ev.xclient.data.l[1] = CurrentTime; 1405 | XSendEvent(dpy, c->win, False, NoEventMask, &ev); 1406 | } 1407 | return exists; 1408 | } 1409 | 1410 | void 1411 | setfocus(Client *c) 1412 | { 1413 | if (!c->neverfocus) { 1414 | XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); 1415 | XChangeProperty(dpy, root, netatom[NetActiveWindow], 1416 | XA_WINDOW, 32, PropModeReplace, 1417 | (unsigned char *) &(c->win), 1); 1418 | } 1419 | sendevent(c, wmatom[WMTakeFocus]); 1420 | } 1421 | 1422 | void 1423 | setfullscreen(Client *c, int fullscreen) 1424 | { 1425 | if (fullscreen && !c->isfullscreen) { 1426 | XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, 1427 | PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1); 1428 | c->isfullscreen = 1; 1429 | c->oldstate = c->isfloating; 1430 | c->oldbw = c->bw; 1431 | c->bw = 0; 1432 | c->isfloating = 1; 1433 | resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh); 1434 | XRaiseWindow(dpy, c->win); 1435 | } else if (!fullscreen && c->isfullscreen){ 1436 | XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, 1437 | PropModeReplace, (unsigned char*)0, 0); 1438 | c->isfullscreen = 0; 1439 | c->isfloating = c->oldstate; 1440 | c->bw = c->oldbw; 1441 | c->x = c->oldx; 1442 | c->y = c->oldy; 1443 | c->w = c->oldw; 1444 | c->h = c->oldh; 1445 | resizeclient(c, c->x, c->y, c->w, c->h); 1446 | arrange(c->mon); 1447 | } 1448 | } 1449 | 1450 | void 1451 | setlayout(const Arg *arg) 1452 | { 1453 | if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) 1454 | selmon->sellt ^= 1; 1455 | if (arg && arg->v) 1456 | selmon->lt[selmon->sellt] = (Layout *)arg->v; 1457 | strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol); 1458 | if (selmon->sel) 1459 | arrange(selmon); 1460 | else 1461 | drawbar(selmon); 1462 | } 1463 | 1464 | /* arg > 1.0 will set mfact absolutely */ 1465 | void 1466 | setmfact(const Arg *arg) 1467 | { 1468 | float f; 1469 | 1470 | if (!arg || !selmon->lt[selmon->sellt]->arrange) 1471 | return; 1472 | f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0; 1473 | if (f < 0.1 || f > 0.9) 1474 | return; 1475 | selmon->mfact = f; 1476 | arrange(selmon); 1477 | } 1478 | 1479 | void 1480 | setup(void) 1481 | { 1482 | int i; 1483 | XSetWindowAttributes wa; 1484 | Atom utf8string; 1485 | 1486 | /* clean up any zombies immediately */ 1487 | sigchld(0); 1488 | 1489 | /* init screen */ 1490 | screen = DefaultScreen(dpy); 1491 | sw = DisplayWidth(dpy, screen); 1492 | sh = DisplayHeight(dpy, screen); 1493 | root = RootWindow(dpy, screen); 1494 | drw = drw_create(dpy, screen, root, sw, sh); 1495 | if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) 1496 | die("no fonts could be loaded."); 1497 | lrpad = drw->fonts->h; 1498 | bh = drw->fonts->h + 2; 1499 | updategeom(); 1500 | /* init atoms */ 1501 | utf8string = XInternAtom(dpy, "UTF8_STRING", False); 1502 | wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); 1503 | wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); 1504 | wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False); 1505 | wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False); 1506 | netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); 1507 | netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); 1508 | netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); 1509 | netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False); 1510 | netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False); 1511 | netatom[NetWMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); 1512 | netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); 1513 | netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); 1514 | netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); 1515 | /* init cursors */ 1516 | cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr); 1517 | cursor[CurResize] = drw_cur_create(drw, XC_sizing); 1518 | cursor[CurMove] = drw_cur_create(drw, XC_fleur); 1519 | /* init appearance */ 1520 | scheme = ecalloc(LENGTH(colors), sizeof(Clr *)); 1521 | for (i = 0; i < LENGTH(colors); i++) 1522 | scheme[i] = drw_scm_create(drw, colors[i], 3); 1523 | /* init bars */ 1524 | updatebars(); 1525 | /* supporting window for NetWMCheck */ 1526 | wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0); 1527 | XChangeProperty(dpy, wmcheckwin, netatom[NetWMCheck], XA_WINDOW, 32, 1528 | PropModeReplace, (unsigned char *) &wmcheckwin, 1); 1529 | XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8, 1530 | PropModeReplace, (unsigned char *) "lx-dwm", 3); 1531 | XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32, 1532 | PropModeReplace, (unsigned char *) &wmcheckwin, 1); 1533 | /* EWMH support per view */ 1534 | XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32, 1535 | PropModeReplace, (unsigned char *) netatom, NetLast); 1536 | XDeleteProperty(dpy, root, netatom[NetClientList]); 1537 | /* select events */ 1538 | wa.cursor = cursor[CurNormal]->cursor; 1539 | wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask 1540 | |ButtonPressMask|PointerMotionMask|EnterWindowMask 1541 | |LeaveWindowMask|StructureNotifyMask|PropertyChangeMask; 1542 | XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa); 1543 | XSelectInput(dpy, root, wa.event_mask); 1544 | grabkeys(); 1545 | focus(NULL); 1546 | } 1547 | 1548 | 1549 | void 1550 | seturgent(Client *c, int urg) 1551 | { 1552 | XWMHints *wmh; 1553 | 1554 | c->isurgent = urg; 1555 | if (!(wmh = XGetWMHints(dpy, c->win))) 1556 | return; 1557 | wmh->flags = urg ? (wmh->flags | XUrgencyHint) : (wmh->flags & ~XUrgencyHint); 1558 | XSetWMHints(dpy, c->win, wmh); 1559 | XFree(wmh); 1560 | } 1561 | 1562 | void 1563 | showhide(Client *c) 1564 | { 1565 | if (!c) 1566 | return; 1567 | if (ISVISIBLE(c)) { 1568 | /* show clients top down */ 1569 | XMoveWindow(dpy, c->win, c->x, c->y); 1570 | if ((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) && !c->isfullscreen) 1571 | resize(c, c->x, c->y, c->w, c->h, 0); 1572 | showhide(c->snext); 1573 | } else { 1574 | /* hide clients bottom up */ 1575 | showhide(c->snext); 1576 | XMoveWindow(dpy, c->win, WIDTH(c) * -2, c->y); 1577 | } 1578 | } 1579 | 1580 | void 1581 | sigchld(int unused) 1582 | { 1583 | if (signal(SIGCHLD, sigchld) == SIG_ERR) 1584 | die("can't install SIGCHLD handler:"); 1585 | while (0 < waitpid(-1, NULL, WNOHANG)); 1586 | } 1587 | 1588 | void 1589 | spawn(const Arg *arg) 1590 | { 1591 | if (arg->v == dmenucmd) 1592 | dmenumon[0] = '0' + selmon->num; 1593 | if (fork() == 0) { 1594 | if (dpy) 1595 | close(ConnectionNumber(dpy)); 1596 | setsid(); 1597 | execvp(((char **)arg->v)[0], (char **)arg->v); 1598 | fprintf(stderr, "lx-dwm: execvp %s", ((char **)arg->v)[0]); 1599 | perror(" failed"); 1600 | exit(EXIT_SUCCESS); 1601 | } 1602 | } 1603 | 1604 | void 1605 | tile(Monitor *m) 1606 | { 1607 | unsigned int i, n, h, mw, my, ty; 1608 | Client *c; 1609 | 1610 | for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++); 1611 | if (n == 0) 1612 | return; 1613 | 1614 | if (n > m->nmaster) 1615 | mw = m->nmaster ? m->ww * m->mfact : 0; 1616 | else 1617 | mw = m->ww; 1618 | for (i = my = ty = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) 1619 | if (i < m->nmaster) { 1620 | h = (m->wh - my) / (MIN(n, m->nmaster) - i); 1621 | resize(c, m->wx, m->wy + my, mw - (2*c->bw), h - (2*c->bw), 0); 1622 | my += HEIGHT(c); 1623 | } else { 1624 | h = (m->wh - ty) / (n - i); 1625 | resize(c, m->wx + mw, m->wy + ty, m->ww - mw - (2*c->bw), h - (2*c->bw), 0); 1626 | ty += HEIGHT(c); 1627 | } 1628 | } 1629 | 1630 | void 1631 | togglebar(const Arg *arg) 1632 | { 1633 | selmon->showbar = !selmon->showbar; 1634 | updatebarpos(selmon); 1635 | XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh); 1636 | arrange(selmon); 1637 | } 1638 | 1639 | void 1640 | togglefloating(const Arg *arg) 1641 | { 1642 | if (!selmon->sel) 1643 | return; 1644 | if (selmon->sel->isfullscreen) /* no support for fullscreen windows */ 1645 | return; 1646 | selmon->sel->isfloating = !selmon->sel->isfloating || selmon->sel->isfixed; 1647 | if (selmon->sel->isfloating) 1648 | resize(selmon->sel, selmon->sel->x, selmon->sel->y, 1649 | selmon->sel->w, selmon->sel->h, 0); 1650 | arrange(selmon); 1651 | } 1652 | 1653 | void 1654 | unfocus(Client *c, int setfocus) 1655 | { 1656 | if (!c) 1657 | return; 1658 | grabbuttons(c, 0); 1659 | XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel); 1660 | if (setfocus) { 1661 | XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); 1662 | XDeleteProperty(dpy, root, netatom[NetActiveWindow]); 1663 | } 1664 | } 1665 | 1666 | void 1667 | unmanage(Client *c, int destroyed) 1668 | { 1669 | Monitor *m = c->mon; 1670 | XWindowChanges wc; 1671 | 1672 | detach(c); 1673 | detachstack(c); 1674 | if (!destroyed) { 1675 | wc.border_width = c->oldbw; 1676 | XGrabServer(dpy); /* avoid race conditions */ 1677 | XSetErrorHandler(xerrordummy); 1678 | XConfigureWindow(dpy, c->win, CWBorderWidth, &wc); /* restore border */ 1679 | XUngrabButton(dpy, AnyButton, AnyModifier, c->win); 1680 | setclientstate(c, WithdrawnState); 1681 | XSync(dpy, False); 1682 | XSetErrorHandler(xerror); 1683 | XUngrabServer(dpy); 1684 | } 1685 | free(c); 1686 | focus(NULL); 1687 | updateclientlist(); 1688 | arrange(m); 1689 | } 1690 | 1691 | void 1692 | unmapnotify(XEvent *e) 1693 | { 1694 | Client *c; 1695 | XUnmapEvent *ev = &e->xunmap; 1696 | 1697 | if ((c = wintoclient(ev->window))) { 1698 | if (ev->send_event) 1699 | setclientstate(c, WithdrawnState); 1700 | else 1701 | unmanage(c, 0); 1702 | } 1703 | } 1704 | 1705 | void 1706 | updatebars(void) 1707 | { 1708 | Monitor *m; 1709 | XSetWindowAttributes wa = { 1710 | .override_redirect = True, 1711 | .background_pixmap = ParentRelative, 1712 | .event_mask = ButtonPressMask|ExposureMask 1713 | }; 1714 | XClassHint ch = {"lx-dwm", "lx-dwm"}; 1715 | for (m = mons; m; m = m->next) { 1716 | if (m->barwin) 1717 | continue; 1718 | m->barwin = XCreateWindow(dpy, root, m->wx, m->by, m->ww, bh, 0, DefaultDepth(dpy, screen), 1719 | CopyFromParent, DefaultVisual(dpy, screen), 1720 | CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); 1721 | XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor); 1722 | XMapRaised(dpy, m->barwin); 1723 | XSetClassHint(dpy, m->barwin, &ch); 1724 | } 1725 | } 1726 | 1727 | void 1728 | updatebarpos(Monitor *m) 1729 | { 1730 | m->wy = m->my; 1731 | m->wh = m->mh; 1732 | if (m->showbar) { 1733 | m->wh -= bh; 1734 | m->by = m->topbar ? m->wy : m->wy + m->wh; 1735 | m->wy = m->topbar ? m->wy + bh : m->wy; 1736 | } else 1737 | m->by = -bh; 1738 | } 1739 | 1740 | void 1741 | updateclientlist() 1742 | { 1743 | Client *c; 1744 | Monitor *m; 1745 | 1746 | XDeleteProperty(dpy, root, netatom[NetClientList]); 1747 | for (m = mons; m; m = m->next) 1748 | for (c = m->clients; c; c = c->next) 1749 | XChangeProperty(dpy, root, netatom[NetClientList], 1750 | XA_WINDOW, 32, PropModeAppend, 1751 | (unsigned char *) &(c->win), 1); 1752 | } 1753 | 1754 | int 1755 | updategeom(void) 1756 | { 1757 | int dirty = 0; 1758 | 1759 | { /* default monitor setup */ 1760 | if (!mons) 1761 | mons = createmon(); 1762 | if (mons->mw != sw || mons->mh != sh) { 1763 | dirty = 1; 1764 | mons->mw = mons->ww = sw; 1765 | mons->mh = mons->wh = sh; 1766 | updatebarpos(mons); 1767 | } 1768 | } 1769 | if (dirty) { 1770 | selmon = mons; 1771 | selmon = wintomon(root); 1772 | } 1773 | return dirty; 1774 | } 1775 | 1776 | void 1777 | updatenumlockmask(void) 1778 | { 1779 | unsigned int i, j; 1780 | XModifierKeymap *modmap; 1781 | 1782 | numlockmask = 0; 1783 | modmap = XGetModifierMapping(dpy); 1784 | for (i = 0; i < 8; i++) 1785 | for (j = 0; j < modmap->max_keypermod; j++) 1786 | if (modmap->modifiermap[i * modmap->max_keypermod + j] 1787 | == XKeysymToKeycode(dpy, XK_Num_Lock)) 1788 | numlockmask = (1 << i); 1789 | XFreeModifiermap(modmap); 1790 | } 1791 | 1792 | void 1793 | updatesizehints(Client *c) 1794 | { 1795 | long msize; 1796 | XSizeHints size; 1797 | 1798 | if (!XGetWMNormalHints(dpy, c->win, &size, &msize)) 1799 | /* size is uninitialized, ensure that size.flags aren't used */ 1800 | size.flags = PSize; 1801 | if (size.flags & PBaseSize) { 1802 | c->basew = size.base_width; 1803 | c->baseh = size.base_height; 1804 | } else if (size.flags & PMinSize) { 1805 | c->basew = size.min_width; 1806 | c->baseh = size.min_height; 1807 | } else 1808 | c->basew = c->baseh = 0; 1809 | if (size.flags & PResizeInc) { 1810 | c->incw = size.width_inc; 1811 | c->inch = size.height_inc; 1812 | } else 1813 | c->incw = c->inch = 0; 1814 | if (size.flags & PMaxSize) { 1815 | c->maxw = size.max_width; 1816 | c->maxh = size.max_height; 1817 | } else 1818 | c->maxw = c->maxh = 0; 1819 | if (size.flags & PMinSize) { 1820 | c->minw = size.min_width; 1821 | c->minh = size.min_height; 1822 | } else if (size.flags & PBaseSize) { 1823 | c->minw = size.base_width; 1824 | c->minh = size.base_height; 1825 | } else 1826 | c->minw = c->minh = 0; 1827 | if (size.flags & PAspect) { 1828 | c->mina = (float)size.min_aspect.y / size.min_aspect.x; 1829 | c->maxa = (float)size.max_aspect.x / size.max_aspect.y; 1830 | } else 1831 | c->maxa = c->mina = 0.0; 1832 | c->isfixed = (c->maxw && c->maxh && c->maxw == c->minw && c->maxh == c->minh); 1833 | } 1834 | 1835 | void 1836 | lxwindowlabel(char *s) 1837 | { 1838 | char *path ="/9/dev/label"; 1839 | int fd = open(path, O_WRONLY|O_APPEND); 1840 | if (fd < 0) { 1841 | fprintf(stderr, "unable to open %s\n", path); 1842 | return; 1843 | } 1844 | int n = write(fd, s, strlen(s)); 1845 | if (n != strlen(s)) { 1846 | fprintf(stderr, "wrote %d bytes only from [%s]", n, s); 1847 | } 1848 | } 1849 | 1850 | void 1851 | updatetitle(Client *c) 1852 | { 1853 | if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name)) 1854 | gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name); 1855 | if (c->name[0] == '\0') /* hack to mark broken clients */ 1856 | strcpy(c->name, broken); 1857 | lxwindowlabel(c->name); 1858 | } 1859 | 1860 | void 1861 | updatewindowtype(Client *c) 1862 | { 1863 | Atom state = getatomprop(c, netatom[NetWMState]); 1864 | Atom wtype = getatomprop(c, netatom[NetWMWindowType]); 1865 | 1866 | if (state == netatom[NetWMFullscreen]) 1867 | setfullscreen(c, 1); 1868 | if (wtype == netatom[NetWMWindowTypeDialog]) 1869 | c->isfloating = 1; 1870 | } 1871 | 1872 | void 1873 | updatewmhints(Client *c) 1874 | { 1875 | XWMHints *wmh; 1876 | 1877 | if ((wmh = XGetWMHints(dpy, c->win))) { 1878 | if (c == selmon->sel && wmh->flags & XUrgencyHint) { 1879 | wmh->flags &= ~XUrgencyHint; 1880 | XSetWMHints(dpy, c->win, wmh); 1881 | } else 1882 | c->isurgent = (wmh->flags & XUrgencyHint) ? 1 : 0; 1883 | if (wmh->flags & InputHint) 1884 | c->neverfocus = !wmh->input; 1885 | else 1886 | c->neverfocus = 0; 1887 | XFree(wmh); 1888 | } 1889 | } 1890 | 1891 | void 1892 | view(const Arg *arg) 1893 | { 1894 | if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) 1895 | return; 1896 | selmon->seltags ^= 1; /* toggle sel tagset */ 1897 | if (arg->ui & TAGMASK) 1898 | selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; 1899 | focus(NULL); 1900 | arrange(selmon); 1901 | } 1902 | 1903 | Client * 1904 | wintoclient(Window w) 1905 | { 1906 | Client *c; 1907 | Monitor *m; 1908 | 1909 | for (m = mons; m; m = m->next) 1910 | for (c = m->clients; c; c = c->next) 1911 | if (c->win == w) 1912 | return c; 1913 | return NULL; 1914 | } 1915 | 1916 | Monitor * 1917 | wintomon(Window w) 1918 | { 1919 | int x, y; 1920 | Client *c; 1921 | Monitor *m; 1922 | 1923 | if (w == root && getrootptr(&x, &y)) 1924 | return recttomon(x, y, 1, 1); 1925 | for (m = mons; m; m = m->next) 1926 | if (w == m->barwin) 1927 | return m; 1928 | if ((c = wintoclient(w))) 1929 | return c->mon; 1930 | return selmon; 1931 | } 1932 | 1933 | /* There's no way to check accesses to destroyed windows, thus those cases are 1934 | * ignored (especially on UnmapNotify's). Other types of errors call Xlibs 1935 | * default error handler, which may call exit. */ 1936 | int 1937 | xerror(Display *dpy, XErrorEvent *ee) 1938 | { 1939 | if (ee->error_code == BadWindow 1940 | || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) 1941 | || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable) 1942 | || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable) 1943 | || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable) 1944 | || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) 1945 | || (ee->request_code == X_GrabButton && ee->error_code == BadAccess) 1946 | || (ee->request_code == X_GrabKey && ee->error_code == BadAccess) 1947 | || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) 1948 | return 0; 1949 | fprintf(stderr, "lx-dwm: fatal error: request code=%d, error code=%d\n", 1950 | ee->request_code, ee->error_code); 1951 | return xerrorxlib(dpy, ee); /* may call exit */ 1952 | } 1953 | 1954 | int 1955 | xerrordummy(Display *dpy, XErrorEvent *ee) 1956 | { 1957 | return 0; 1958 | } 1959 | 1960 | /* Startup Error handler to check if another window manager 1961 | * is already running. */ 1962 | int 1963 | xerrorstart(Display *dpy, XErrorEvent *ee) 1964 | { 1965 | die("lx-dwm: another window manager is already running"); 1966 | return -1; 1967 | } 1968 | 1969 | void 1970 | zoom(const Arg *arg) 1971 | { 1972 | Client *c = selmon->sel; 1973 | 1974 | if (!selmon->lt[selmon->sellt]->arrange 1975 | || (selmon->sel && selmon->sel->isfloating)) 1976 | return; 1977 | if (c == nexttiled(selmon->clients)) 1978 | if (!c || !(c = nexttiled(c->next))) 1979 | return; 1980 | pop(c); 1981 | } 1982 | 1983 | int 1984 | main(int argc, char *argv[]) 1985 | { 1986 | if (argc == 2 && !strcmp("-v", argv[1])) 1987 | die("lx-dwm-"VERSION); 1988 | else if (argc != 1) 1989 | die("usage: lx-dwm [-v]"); 1990 | if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) 1991 | fputs("warning: no locale support\n", stderr); 1992 | if (!(dpy = XOpenDisplay(NULL))) 1993 | die("lx-dwm: cannot open display"); 1994 | checkotherwm(); 1995 | setup(); 1996 | #ifdef __OpenBSD__ 1997 | if (pledge("stdio rpath proc exec", NULL) == -1) 1998 | die("pledge"); 1999 | #endif /* __OpenBSD__ */ 2000 | scan(); 2001 | run(); 2002 | cleanup(); 2003 | XCloseDisplay(dpy); 2004 | return EXIT_SUCCESS; 2005 | } 2006 | --------------------------------------------------------------------------------