├── COPYING ├── Makefile ├── README ├── mf ├── Makefile.FreeBSD ├── Makefile.Linux ├── Makefile.NetBSD ├── Makefile.OpenBSD ├── Makefile.SunOS └── Makefile.generic └── src ├── XmSm.ad.src ├── XmToolbox.ad.src ├── common.c ├── common.h ├── common.mf ├── smconf.h ├── smglobal.h ├── smmain.c ├── tbmain.c ├── tbparse.c ├── tbparse.h ├── toolboxrc ├── xbm ├── toolbox.xbm └── toolbox_m.xbm ├── xmsession.src ├── xmsm.1 └── xmtoolbox.1 /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018-2024 alx@fastestcode.org 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @if [ -e src/Makefile ]; then \ 3 | $(MAKE) -C src $(MAKEFLAGS); \ 4 | else \ 5 | if [ -e mf/Makefile.$$(uname) ]; then \ 6 | ln -s ../mf/Makefile.$$(uname) src/Makefile && \ 7 | $(MAKE) -C src $(MAKEFLAGS); \ 8 | else \ 9 | echo "Run: make " && \ 10 | echo "Available targets are:" && \ 11 | ls mf/ | sed 's/Makefile\.//g'; \ 12 | fi \ 13 | fi 14 | 15 | .PHONY: clean install distclean 16 | 17 | install: 18 | $(MAKE) -C src $(MAKEFLAGS) install 19 | 20 | uninstall: 21 | $(MAKE) -C src $(MAKEFLAGS) uninstall 22 | 23 | clean: 24 | $(MAKE) -C src $(MAKEFLAGS) clean 25 | 26 | distclean: 27 | -$(MAKE) -C src $(MAKEFLAGS) clean 28 | -rm src/Makefile 29 | 30 | .DEFAULT: 31 | @if [ -e src/Makefile ]; then rm src/Makefile; fi 32 | @if ! [ -f mf/Makefile.$@ ]; then \ 33 | echo "Invalid target name: $@" && exit 1; fi 34 | ln -s ../mf/Makefile.$@ src/Makefile 35 | $(MAKE) -C src $(MAKEFLAGS) 36 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | UTILITIES FOR EMWM 2 | ================== 3 | xmsm is simple session manager that provides session configuration and screen 4 | locking and also launches the window manager and the toolbox. 5 | 6 | xmtoolbox is an application launcher/menu configured with a simple text file. 7 | It also communicates with the session manager to provide lock, log out, and 8 | shutdown commands. 9 | 10 | Example toolbox configuration file (toolboxrc) is provided in the src directory, 11 | and also installed into the X11 config directory. 12 | 13 | BUILDING AND INSTALLING 14 | ======================= 15 | X11 and Motif libraries and headers are required to build all utilities. 16 | 17 | To modify installation prefix and other options, edit the target platform 18 | specific makefile in the "mf" subdirectory (The target platform name is picked 19 | from uname(1)). 20 | 21 | Run 'make' in the top-level directory of the source distribution. 22 | After the build process finishes, run 'make install' as root. 23 | 24 | NOTES 25 | ======================= 26 | The session manager may be run from XDM by setting the DisplayManager*session 27 | X resource in xdm-config to xmsession, or by execing xmsm from ~/.Xprofile. 28 | Just make sure to test whether it works by running 'xinit xmsession' before 29 | doing any of the above. Note that 'xmsession' is just a shell script that sets 30 | up the environment for xmsm, and is not needed when this has been done already. 31 | - 32 | xmsm is installed suid root, this is required for screen locking and running 33 | sbin commands. Privileges are dropped at startup and are reacquired only when 34 | necessary. 35 | - 36 | System shutdown, reboot and suspend commands can be altered at compile time 37 | only. Edit constants in smconf.h, or override them in CFLAGS if necessary. 38 | -------------------------------------------------------------------------------- /mf/Makefile.FreeBSD: -------------------------------------------------------------------------------- 1 | # FreeBSD Makefile 2 | 3 | PREFIX ?= /usr/local 4 | MANDIR = $(PREFIX)/man 5 | RCDIR = $(PREFIX)/etc/X11 6 | APPLRESDIR = $(PREFIX)/etc/X11/app-defaults 7 | 8 | INCDIRS = -I/usr/local/include 9 | LIBDIRS = -L/usr/local/lib 10 | 11 | CFLAGS += -Wall -DPREFIX='"$(PREFIX)"' $(INCDIRS) 12 | SYSLIBS = -lcrypt 13 | 14 | .include "common.mf" 15 | 16 | install: common_install 17 | -------------------------------------------------------------------------------- /mf/Makefile.Linux: -------------------------------------------------------------------------------- 1 | PREFIX = /usr 2 | MANDIR = $(PREFIX)/share/man 3 | RCDIR = /etc/X11 4 | APPLRESDIR = /etc/X11/app-defaults 5 | 6 | INCDIRS = -I/usr/local/include 7 | LIBDIRS = -L/usr/local/lib 8 | 9 | CFLAGS += -Wall -DPREFIX='"$(PREFIX)"' $(INCDIRS) 10 | SYSLIBS = -lcrypt 11 | 12 | include common.mf 13 | 14 | install: common_install 15 | 16 | -include .depend 17 | -------------------------------------------------------------------------------- /mf/Makefile.NetBSD: -------------------------------------------------------------------------------- 1 | # NetBSD Makefile 2 | 3 | PREFIX = /usr 4 | MANDIR = /usr/share/man 5 | RCDIR = /usr/pkg/lib/X11 6 | APPLRESDIR = /usr/pkg/lib/X11/app-defaults 7 | 8 | INCDIRS = -I/usr/X11R7/include -I/usr/pkg/include 9 | LIBDIRS = -L/usr/X11R7/lib -L/usr/pkg/lib 10 | LDFLAGS = -Wl,-R/usr/X11R7/lib,-R/usr/pkg/lib 11 | SYSLIBS = -lcrypt 12 | 13 | CFLAGS += -Wall -DPREFIX='"$(PREFIX)"' $(INCDIRS) 14 | 15 | .include "common.mf" 16 | 17 | .c.o: 18 | $(CC) $(CFLAGS) $(DEFINES) $(INCDIRS) -c -o $(<:.c=.o) $> 19 | 20 | install: common_install 21 | 22 | -------------------------------------------------------------------------------- /mf/Makefile.OpenBSD: -------------------------------------------------------------------------------- 1 | # OpenBSD Makefile 2 | 3 | PREFIX = /usr/local 4 | MANDIR = /usr/local/man 5 | RCDIR = /usr/X11R6/lib/X11 6 | APPLRESDIR = /usr/X11R6/lib/X11/app-defaults 7 | 8 | INCDIRS = -I./Xm -I/usr/X11R6/include -I/usr/local/include 9 | LIBDIRS = -L/usr/X11R6/lib -L/usr/local/lib 10 | 11 | CFLAGS = -O2 -Wall $(INCDIRS) 12 | 13 | include common.mf 14 | 15 | install: common_install 16 | -------------------------------------------------------------------------------- /mf/Makefile.SunOS: -------------------------------------------------------------------------------- 1 | # SunOS Makefile 2 | 3 | PREFIX = /usr 4 | MANDIR = /usr/share/man 5 | RCDIR = /usr/lib/X11 6 | APPLRESDIR = /usr/lib/X11/app-defaults 7 | 8 | CC = gcc 9 | CFLAGS = -O2 -Wall -DSHUTDOWN_CMD='"/usr/sbin/poweroff"' \ 10 | -DREBOOT_CMD='"/usr/sbin/reboot"' \ 11 | -DSUSPEND_CMD='"/usr/bin/sys-suspend -x"' 12 | 13 | include common.mf 14 | 15 | .c.o: 16 | $(CC) $(CFLAGS) $(DEFINES) $(INCDIRS) -c -o $(^:.c=.o) $^ 17 | 18 | install: 19 | install -m 775 -f $(PREFIX)/bin xmsession 20 | install -m 4775 -u 0 -f $(PREFIX)/bin xmsm 21 | install -m 775 -f $(PREFIX)/bin xmtoolbox 22 | install -m 664 -f $(MANDIR)/man1 xmsm.1 23 | install -m 664 -f $(MANDIR)/man1 xmtoolbox.1 24 | install -m 775 -d $(RCDIR) 25 | install -m 664 -f $(RCDIR) toolboxrc 26 | # If Tribblix packages Xft enabled Motif 27 | # cp XmSm.ad XmSm && install -m 664 -f $(APPLRESDIR) XmSm 28 | # cp XmToolbox.ad XmToolbox && install -m 664 -f $(APPLRESDIR) XmToolbox 29 | -------------------------------------------------------------------------------- /mf/Makefile.generic: -------------------------------------------------------------------------------- 1 | PREFIX = /usr 2 | MANDIR = $(PREFIX)/share/man 3 | RCDIR = /etc/X11 4 | APPLRESDIR = /etc/X11/app-defaults 5 | 6 | INCDIRS = -I./Xm -I/usr/local/include -I/usr/X11R6/include 7 | LIBDIRS = -L/usr/local/lib -L/usr/X11R6/lib 8 | 9 | CFLAGS += -Wall -DPREFIX='"$(PREFIX)"' $(INCDIRS) 10 | SYSLIBS = -lcrypt 11 | 12 | include common.mf 13 | 14 | install: common_install 15 | 16 | -include .depend 17 | -------------------------------------------------------------------------------- /src/XmSm.ad.src: -------------------------------------------------------------------------------- 1 | ! XmSm app-defaults 2 | 3 | *windowManager: PREFIX/bin/emwm 4 | *launcher: PREFIX/bin/xmtoolbox 5 | 6 | *enableShade: True 7 | *enableLocking: True 8 | *enableSuspend: True 9 | *blankOnLock: True 10 | 11 | *blankTimeout: 400 12 | *lockTimeout: 600 13 | *lockOnSuspend: False 14 | *unlockScreenTimeout: 16 15 | 16 | *numLockState: KEEP 17 | 18 | *workspaceBackgroundColor: #516286 19 | *lockBackgroundColor: #516286 20 | ! *workspaceBackgroundImage: 25_foreground 21 | ! *lockBackgroundImage: 50_foreground 22 | 23 | *showShutdown: True 24 | *showReboot: True 25 | *primaryXineramaScreen: 0 26 | 27 | *password.renderTable: variable 28 | *renderTable.variable.fontType: FONT_IS_XFT 29 | *renderTable.variable.fontName: Liberation Sans 30 | *renderTable.variable*fontSize: 14 31 | 32 | *lockedBy.renderTable: serif 33 | *renderTable.serif.fontType: FONT_IS_XFT 34 | *renderTable.serif.fontName: Liberation Serif 35 | *renderTable.serif.fontSize: 20 36 | *renderTable.serif.fontStyle: Italic 37 | 38 | *renderTable: dialog 39 | *renderTable.dialog.fontType: FONT_IS_XFT 40 | *renderTable.dialog.fontName: Liberation Sans 41 | *renderTable.dialog.fontSize: 10 42 | *renderTable: dialog 43 | -------------------------------------------------------------------------------- /src/XmToolbox.ad.src: -------------------------------------------------------------------------------- 1 | ! XmToolbox app-defaults 2 | 3 | ! Initial position 4 | XmToolbox.x: 8 5 | XmToolbox.y: 28 6 | 7 | ! Default title is login@host 8 | ! *title: Toolbox 9 | ! *hotkey: Super_L 10 | 11 | *dateTimeDisplay: True 12 | *dateTimeFormat: %D %l:%M %p 13 | 14 | ! Some common values for mwmDecorations 15 | ! Borderless, title buttons: 56 16 | ! Borderless, no title buttons: 8 17 | ! Borders and title buttons: 58 18 | ! Borders, no title buttons: 10 19 | *mwmDecorations: 58 20 | 21 | ! Thicker frame shadows for borderless decorations 22 | ! *mainFrame.shadowThickness: 2 23 | 24 | ! Horizontal layout 25 | *horizontal: False 26 | 27 | ! Show separators between launcher, session and date time parts 28 | *separators: True 29 | 30 | ! Fonts 31 | *XmPushButtonGadget.renderTable: menu 32 | *renderTable.menu.fontType: FONT_IS_XFT 33 | *renderTable.menu.fontName: Liberation Sans 34 | *renderTable.menu.fontSize: 10 35 | 36 | *XmCascadeButtonGadget*renderTable: cascade 37 | *renderTable.cascade.fontType: FONT_IS_XFT 38 | *renderTable.cascade.fontName: Liberation Sans 39 | *renderTable.cascade.fontStyle: Italic 40 | *renderTable.cascade.fontSize: 10 41 | 42 | *dateTime.renderTable: small 43 | *renderTable.small.fontType: FONT_IS_XFT 44 | *renderTable.small.fontName: Liberation Sans 45 | *renderTable.small.fontSize: 9 46 | 47 | *renderTable: default 48 | *renderTable.default.fontType: FONT_IS_XFT 49 | *renderTable.default.fontName: Liberation Sans 50 | *renderTable.default.fontSize: 10 51 | *renderTable: default 52 | -------------------------------------------------------------------------------- /src/common.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 alx@fastestcode.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "common.h" 32 | 33 | /* Reliable signal handling (using POSIX sigaction) */ 34 | sigfunc_t rsignal(int sig, sigfunc_t handler) 35 | { 36 | struct sigaction set, ret; 37 | 38 | set.sa_handler = handler; 39 | sigemptyset(&set.sa_mask); 40 | set.sa_flags = SA_NOCLDSTOP; 41 | 42 | if(sig == SIGALRM) { 43 | #ifdef SA_INTERRUPT 44 | set.sa_flags |= SA_INTERRUPT; 45 | #endif 46 | } else { 47 | set.sa_flags |= SA_RESTART; 48 | } 49 | if(sigaction(sig, &set, &ret) < 0) 50 | return SIG_ERR; 51 | 52 | return ret.sa_handler; 53 | } 54 | 55 | /* 56 | * Expands sh style environment variables found in 'in' and returns the 57 | * expanded string in 'out', which is allocated from the heap and must be 58 | * freed by the caller. Returns zero on success, errno otherwise. 59 | */ 60 | int expand_env_vars(const char *in, char **out) 61 | { 62 | char *src; 63 | char *buf; 64 | char **parts; 65 | size_t nparts = 0; 66 | char *s, *p; 67 | int res = 0; 68 | 69 | src = strdup(in); 70 | if(!src) return ENOMEM; 71 | 72 | s = p = src; 73 | 74 | /* count parts and allocate temporary storage */ 75 | while(*p) { 76 | if((*p == '$') && (p[1] != '$')) nparts++; 77 | p++; 78 | } 79 | 80 | if(!nparts) { 81 | *out = src; 82 | return 0; 83 | } 84 | 85 | parts = calloc((nparts + 1) * 2, sizeof(char*)); 86 | if(!parts) { 87 | free(src); 88 | return ENOMEM; 89 | } 90 | 91 | /* reset for parsing */ 92 | nparts = 0; 93 | s = p = src; 94 | 95 | while(*p) { 96 | if(*p == '$') { 97 | char vc = *p; 98 | 99 | /* double special char stands for literal */ 100 | if(p[1] == vc) { 101 | memmove(p, p + 1, strlen(p)); 102 | p++; 103 | continue; 104 | } 105 | 106 | /* starting point of the next part */ 107 | parts[nparts++] = s; 108 | 109 | /* explicit scope ${...} */ 110 | if(p[1] == '{') { 111 | p[0] = '\0'; 112 | p += 2; 113 | s = p; 114 | 115 | while(*p && *p != '}') p++; 116 | 117 | if(*p == '\0') { 118 | res = EINVAL; 119 | break; 120 | } 121 | 122 | if(!(p - s)) break; 123 | 124 | buf = malloc((p - s) + 1); 125 | if(!buf) { 126 | res = errno; 127 | break; 128 | } 129 | memcpy(buf, s, p - s); 130 | buf[p - s] = '\0'; 131 | 132 | s--; 133 | memmove(s, p + 1, strlen(p)); 134 | 135 | p = s; 136 | } else { 137 | /* implicit scope $... 138 | * (eventually terminated by a space, dot or slash) */ 139 | p[0] = '\0'; 140 | p++; 141 | s = p; 142 | 143 | while(*p && *p != ' ' && 144 | *p != '\t' && *p != '.' && *p != '/') p++; 145 | 146 | if(!(p - s)) { 147 | res = EINVAL; 148 | break; 149 | } 150 | 151 | buf = malloc((p - s) + 1); 152 | if(!buf) { 153 | res = errno; 154 | break; 155 | } 156 | memcpy(buf, s, p - s); 157 | buf[p - s] = '\0'; 158 | 159 | memmove(s , p, strlen(p) + 1); 160 | 161 | p = s; 162 | } 163 | 164 | parts[nparts] = getenv(buf); 165 | if(parts[nparts] == NULL) 166 | fprintf(stderr, "Undefined environment variable %s\n", buf); 167 | 168 | nparts++; 169 | free(buf); 170 | } 171 | p++; 172 | } 173 | 174 | if(!res) { 175 | size_t i, len = 1; 176 | 177 | /* add trailing part (if string didn't end with a variable) */ 178 | if(p != s) parts[nparts++] = s; 179 | p = src; 180 | 181 | for(i = 0; i < nparts; i++) { 182 | if(parts[i] && *parts[i]) len += strlen(parts[i]); 183 | } 184 | 185 | /* compose parts into a single string */ 186 | buf = malloc(len); 187 | if(buf) { 188 | buf[0] = '\0'; 189 | 190 | for(i = 0; i < nparts; i++) { 191 | if(parts[i] && *parts[i]) strcat(buf, parts[i]); 192 | } 193 | *out = buf; 194 | } else { 195 | res = ENOMEM; 196 | } 197 | } 198 | 199 | free(parts); 200 | free(src); 201 | return res; 202 | } 203 | 204 | char* get_login(void) 205 | { 206 | static char *login = NULL; 207 | 208 | if(!login) { 209 | struct passwd *pwd; 210 | pwd = getpwuid(getuid()); 211 | if(pwd) login = strdup(pwd->pw_name); 212 | } 213 | return login; 214 | } 215 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 alx@fastestcode.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | #ifndef COMMON_H 24 | #define COMMON_H 25 | 26 | /* Reliable signal handling (using POSIX sigaction) */ 27 | typedef void (*sigfunc_t)(int); 28 | sigfunc_t rsignal(int sig, sigfunc_t); 29 | 30 | /* 31 | * Expands sh style environment variables found in 'in' and returns the 32 | * expanded string in 'out', which is allocated from the heap and must be 33 | * freed by the caller. Returns zero on success, errno otherwise. 34 | */ 35 | int expand_env_vars(const char *in, char **out); 36 | 37 | char* get_login(void); 38 | 39 | #endif /* COMMON_H */ 40 | -------------------------------------------------------------------------------- /src/common.mf: -------------------------------------------------------------------------------- 1 | # Common Makefile part, included by platform specific makefiles 2 | 3 | CFLAGS += -DPREFIX='"$(PREFIX)"' -DRCDIR='"$(RCDIR)"' $(INCDIRS) 4 | toolbox_libs = -lXm -lXt -lX11 5 | xmsm_libs = -lXm -lXt -lXss -lXrandr -lXinerama -lX11 $(SYSLIBS) 6 | 7 | toolbox_objs = tbmain.o tbparse.o 8 | xmsm_objs = smmain.o 9 | common_objs = common.o 10 | 11 | app_defaults = XmSm.ad XmToolbox.ad 12 | 13 | executables = xmsm xmtoolbox xmsession 14 | 15 | all: $(executables) $(app_defaults) 16 | 17 | xmtoolbox: $(toolbox_objs) $(common_objs) 18 | $(CC) -o $@ $(LDFLAGS) $(LIBDIRS) $(toolbox_objs) $(common_objs) $(toolbox_libs) 19 | 20 | xmsm: $(xmsm_objs) $(common_objs) 21 | $(CC) -o $@ $(LDFLAGS) $(LIBDIRS) $(xmsm_objs) $(common_objs) $(xmsm_libs) 22 | 23 | xmsession: xmsession.src 24 | sed s%PREFIX%$(PREFIX)%g xmsession.src > $@ 25 | chmod 775 $@ 26 | 27 | XmSm.ad: XmSm.ad.src 28 | sed s%PREFIX%$(PREFIX)%g XmSm.ad.src > $@ 29 | 30 | XmToolbox.ad: XmToolbox.ad.src 31 | sed s%PREFIX%$(PREFIX)%g XmToolbox.ad.src > $@ 32 | 33 | .PHONY: clean install common_install 34 | 35 | common_install: 36 | install -m755 xmsession $(PREFIX)/bin/xmsession 37 | install -m755 xmtoolbox $(PREFIX)/bin/xmtoolbox 38 | install -m4755 xmsm $(PREFIX)/bin/xmsm 39 | install -m755 -d $(MANDIR)/man1 40 | install -m644 xmtoolbox.1 $(MANDIR)/man1/xmtoolbox.1 41 | install -m644 xmsm.1 $(MANDIR)/man1/xmsm.1 42 | install -m755 -d $(APPLRESDIR) 43 | install -m644 XmSm.ad $(APPLRESDIR)/XmSm 44 | install -m644 XmToolbox.ad $(APPLRESDIR)/XmToolbox 45 | install -m644 toolboxrc $(RCDIR)/toolboxrc 46 | 47 | uninstall: 48 | rm -f $(PREFIX)/bin/xmsm 49 | rm -f $(PREFIX)/bin/xmsession 50 | rm -f $(PREFIX)/bin/xmtoolbox 51 | rm -f $(MANDIR)/man1/xmtoolbox.1 52 | rm -f $(MANDIR)/man1/xmtoolbox.1 53 | rm -f $(APPLRESDIR)/XmSm 54 | rm -f $(APPLRESDIR)/XmToolbox 55 | rm -f $(RCDIR)/toolboxrc 56 | rmdir $(RCDIR) 57 | 58 | clean: 59 | -rm $(toolbox_objs) $(xmsm_objs) $(common_objs) $(executables) $(app_defaults) 60 | -rm .depend 61 | 62 | .depend: 63 | $(CC) -MM $(INCDIRS) $(toolbox_objs:.o=.c) $(xmsm_objs:.o=.c) $(common_objs:.o=.c) > $@ 64 | -------------------------------------------------------------------------------- /src/smconf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 alx@fastestcode.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | /* 24 | * System command paths 25 | */ 26 | 27 | #ifndef SHUTDOWN_CMD 28 | #ifdef __linux__ 29 | #define SHUTDOWN_CMD "/sbin/poweroff" 30 | #else 31 | #define SHUTDOWN_CMD "/sbin/shutdown -p now" 32 | #endif 33 | #endif /* SHUTDOWN_CMD */ 34 | 35 | #ifndef REBOOT_CMD 36 | #define REBOOT_CMD "/sbin/reboot" 37 | #endif 38 | 39 | #ifndef SUSPEND_CMD 40 | #ifdef __linux__ 41 | #define SUSPEND_CMD "/usr/sbin/pm-suspend" 42 | #else /* BSD */ 43 | #define SUSPEND_CMD "/usr/sbin/zzz" 44 | #endif /* _linux_ */ 45 | #endif /* SUSPEND_CMD */ 46 | -------------------------------------------------------------------------------- /src/smglobal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018-2024 alx@fastestcode.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | /* 24 | * Session manager IPC globals 25 | */ 26 | 27 | #define XMSM_ATOM_NAME "_XM_SESSION_MANAGER" 28 | #define XMSM_PID_ATOM_NAME "_XM_SESSION_MANAGER_PID" 29 | #define XMSM_CMD_ATOM_NAME "_XM_SESSION_MANAGER_CMD" 30 | #define XMSM_CFG_ATOM_NAME "_XM_SESSION_MANAGER_CFG" 31 | #define XMSM_LOGOUT_CMD "LOGOUT" 32 | #define XMSM_LOCK_CMD "LOCK" 33 | #define XMSM_SUSPEND_CMD "SUSPEND" 34 | 35 | #define XMSM_CFG_SUSPEND 0x0001 36 | #define XMSM_CFG_LOCK 0x0002 37 | -------------------------------------------------------------------------------- /src/smmain.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018-2024 alx@fastestcode.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | /* 24 | * A simple session manager for use with EMWM and xmtoolbox 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #if defined(__linux__) || defined(__svr4__) 61 | #include 62 | #include 63 | #endif 64 | #include "smglobal.h" 65 | #include "smconf.h" 66 | #include "common.h" 67 | 68 | /* Local prototypes */ 69 | static Boolean set_privileges(Boolean); 70 | static void init_session(void); 71 | static void sigchld_handler(int); 72 | static void sigusr_handler(int); 73 | static void create_locking_widgets(void); 74 | static void create_shade_widgets(void); 75 | static void get_screen_size(Screen*,Dimension*,Dimension*,Position*,Position*); 76 | static void lock_screen(void); 77 | static void unlock_screen(void); 78 | static void show_unlock_widget(void); 79 | static void show_covers(Boolean); 80 | static void reset_unlock_timer(void); 81 | static void set_unlock_message(const char*); 82 | static void unlock_widget_timeout_cb(XtPointer,XtIntervalId*); 83 | static void blank_delay_timeout_cb(XtPointer,XtIntervalId*); 84 | static void lock_timeout_cb(XtPointer,XtIntervalId*); 85 | static void passwd_modify_cb(Widget,XtPointer,XtPointer); 86 | static void passwd_enter_cb(Widget,XtPointer,XtPointer); 87 | static void exit_dialog_cb(Widget,XtPointer,XtPointer); 88 | static void covers_up_cb(Widget,XtPointer,XEvent*,Boolean*); 89 | static void register_screen_saver(void); 90 | static int launch_process(const char*); 91 | static void process_sessionetc(void); 92 | static void set_root_background(void); 93 | static void set_numlock_state(void); 94 | static int local_x_err_handler(Display*,XErrorEvent*); 95 | static void xt_sigusr1_handler(XtPointer,XtSignalId*); 96 | static void msg_property_handler(Widget, XtPointer, XEvent*, Boolean*); 97 | static void set_config_info(void); 98 | static void exit_session_dialog(void); 99 | static void error_dialog(void); 100 | static Boolean exec_sys_cmd(const char *command); 101 | static void reconfigure_widgets(XRRScreenChangeNotifyEvent *evt); 102 | static void exit_dialog_wm_offset_cb(Widget, XtPointer, XtPointer); 103 | 104 | 105 | /* Application resources */ 106 | struct session_res { 107 | Boolean enable_locking; 108 | Boolean enable_suspend; 109 | Boolean enable_shade; 110 | Boolean blank_on_lock; 111 | char *numlock_state; 112 | char *wkspace_bg_image; 113 | Pixel wkspace_bg_pixel; 114 | char *lock_bg_image; 115 | Pixel lock_bg_pixel; 116 | char *window_manager; 117 | char *launcher; 118 | unsigned int lock_timeout; 119 | unsigned int unlock_scr_timeout; 120 | unsigned int blank_timeout; 121 | unsigned int prim_xinerama_screen; 122 | Boolean show_shutdown; 123 | Boolean show_reboot; 124 | Boolean lock_on_suspend; 125 | Boolean silent; 126 | } app_res; 127 | 128 | #ifndef PREFIX 129 | /* Used to construct full paths to mwm and xmtoolbox */ 130 | #define PREFIX "/usr/local" 131 | #endif 132 | 133 | #define RES_FIELD(f) XtOffsetOf(struct session_res,f) 134 | XtResource xrdb_resources[]={ 135 | { "enableShade","EnableShade",XmRBoolean,sizeof(Boolean), 136 | RES_FIELD(enable_shade),XmRImmediate,(XtPointer)True 137 | }, 138 | { "enableLocking","EnableLocking",XmRBoolean,sizeof(Boolean), 139 | RES_FIELD(enable_locking),XmRImmediate,(XtPointer)True 140 | }, 141 | { "enableSuspend","EnableSuspend",XmRBoolean, 142 | sizeof(Boolean),RES_FIELD(enable_suspend), 143 | XmRImmediate,(XtPointer)True 144 | }, 145 | { "blankOnLock","BlankOnLock",XmRBoolean,sizeof(Boolean), 146 | RES_FIELD(blank_on_lock),XmRImmediate,(XtPointer)True 147 | }, 148 | { "numLockState","NumLockState",XmRString,sizeof(String), 149 | RES_FIELD(numlock_state),XmRImmediate,(XtPointer)"on" 150 | }, 151 | { "workspaceBackgroundImage","WorkspaceBackgroundImage", 152 | XmRString,sizeof(String), 153 | RES_FIELD(wkspace_bg_image),XmRImmediate,(XtPointer)NULL 154 | }, 155 | { "workspaceBackgroundColor","WorkspaceBackgroundColor", 156 | XmRPixel,sizeof(Pixel),RES_FIELD(wkspace_bg_pixel), 157 | XmRString, (XtPointer)"#4C719E" 158 | }, 159 | { "lockBackgroundImage","LockBackgroundImage", 160 | XmRString,sizeof(String), 161 | RES_FIELD(lock_bg_image),XmRImmediate,(XtPointer)NULL 162 | }, 163 | { "lockBackgroundColor","LockBackgroundColor", 164 | XmRPixel,sizeof(Pixel),RES_FIELD(lock_bg_pixel), 165 | XmRString, (XtPointer)"#4C719E" 166 | }, 167 | { "launcher","Launcher",XmRString,sizeof(String), 168 | RES_FIELD(launcher),XmRImmediate,(XtPointer)PREFIX"/bin/xmtoolbox" 169 | }, 170 | { "blankTimeout","BlankTimeout",XmRInt,sizeof(unsigned int), 171 | RES_FIELD(blank_timeout),XmRImmediate,(XtPointer)400 172 | }, 173 | { "lockTimeout","LockTimeout",XmRInt,sizeof(unsigned int), 174 | RES_FIELD(lock_timeout),XmRImmediate,(XtPointer)600 175 | }, 176 | { "lockOnSuspend","LockOnSuspend",XmRBoolean,sizeof(Boolean), 177 | RES_FIELD(lock_on_suspend),XmRImmediate,(XtPointer)False 178 | }, 179 | { "unlockScreenTimeout","UnlockScreenTimeout",XmRInt,sizeof(unsigned int), 180 | RES_FIELD(unlock_scr_timeout),XmRImmediate,(XtPointer)8 181 | }, 182 | { "primaryXineramaScreen","PrimaryXineramaScreen",XmRInt, 183 | sizeof(unsigned int),RES_FIELD(prim_xinerama_screen), 184 | XmRImmediate,(XtPointer)0 185 | }, 186 | { "showShutdown","ShowShutdown",XmRBoolean, 187 | sizeof(Boolean),RES_FIELD(show_shutdown), 188 | XmRImmediate,(XtPointer)True 189 | }, 190 | { "showReboot","ShowReboot",XmRBoolean, 191 | sizeof(Boolean),RES_FIELD(show_reboot), 192 | XmRImmediate,(XtPointer)True 193 | }, 194 | { "silent","Silent",XmRBoolean, 195 | sizeof(Boolean),RES_FIELD(silent), 196 | XmRImmediate,(XtPointer)False 197 | }, 198 | { "windowManager","WindowManager",XmRString,sizeof(String), 199 | RES_FIELD(window_manager),XmRImmediate,(XtPointer)PREFIX"/bin/emwm" 200 | } 201 | }; 202 | #undef RES_FIELD 203 | 204 | #define MSG_NOACCESS "Password Incorrect" 205 | #define APP_TITLE "XmSm" 206 | #define APP_NAME "xmsm" 207 | #define DEF_MAX_PASSWD 255 208 | 209 | #define log_msg(fmt,...) fprintf(stderr,"[XMSM] "fmt,##__VA_ARGS__) 210 | 211 | char *bin_name = NULL; 212 | static Atom xa_mgr; 213 | static Atom xa_pid; 214 | static Atom xa_cmd; 215 | static Atom xa_cfg; 216 | static Atom xa_MOTIF_WM_MESSAGES; 217 | static Atom xa_MOTIF_WM_OFFSET; 218 | XtAppContext app_context; 219 | Widget wshell; 220 | static Widget *wcovers; 221 | static Widget wunlock; 222 | static Widget wpasswd; 223 | static Widget wmessage; 224 | static Widget *wshades; 225 | static XtIntervalId unlock_widget_timer=None; 226 | static XtIntervalId lock_timer=None; 227 | static XtSignalId xt_sigusr1; 228 | static Boolean scr_locked = False; 229 | static Boolean covers_up = False; 230 | static Boolean unlock_up = False; 231 | static Boolean ptr_grabbed = False; 232 | static Boolean kbd_grabbed = False; 233 | static Boolean xrandr_present = False; 234 | static int xrandr_base_evt; 235 | static int xrandr_base_err; 236 | static XHostAddress *acl_hosts = NULL; 237 | static int num_acl_hosts; 238 | static Bool acl_state; 239 | static int xss_event_base = 0; 240 | static int xss_error_base = 0; 241 | static int (*def_x_err_handler)(Display*,XErrorEvent*)=NULL; 242 | 243 | 244 | int main(int argc, char **argv) 245 | { 246 | int rv; 247 | 248 | bin_name = argv[0]; 249 | 250 | set_privileges(False); 251 | 252 | rsignal(SIGCHLD, sigchld_handler); 253 | rsignal(SIGUSR1, sigusr_handler); 254 | rsignal(SIGUSR2, sigusr_handler); 255 | 256 | XtSetLanguageProc(NULL,NULL,NULL); 257 | XtToolkitInitialize(); 258 | 259 | wshell=XtVaAppInitialize(&app_context, 260 | APP_TITLE,NULL,0, 261 | &argc,argv,NULL, 262 | XmNiconName,APP_TITLE, 263 | XmNmwmFunctions,0, 264 | XmNmwmDecorations,0, 265 | XmNmappedWhenManaged,False, 266 | XmNoverrideRedirect,True,NULL); 267 | 268 | XtGetApplicationResources(wshell,&app_res,xrdb_resources, 269 | XtNumber(xrdb_resources),NULL,0); 270 | 271 | xa_mgr = XInternAtom(XtDisplay(wshell),XMSM_ATOM_NAME,False); 272 | xa_pid = XInternAtom(XtDisplay(wshell),XMSM_PID_ATOM_NAME,False); 273 | xa_cmd = XInternAtom(XtDisplay(wshell),XMSM_CMD_ATOM_NAME,False); 274 | xa_cfg = XInternAtom(XtDisplay(wshell),XMSM_CFG_ATOM_NAME,False); 275 | 276 | XtRealizeWidget(wshell); 277 | XDeleteProperty(XtDisplay(wshell), XtWindow(wshell), XA_WM_COMMAND); 278 | 279 | /* Initialize Xrandr and set up for screen change notifications */ 280 | if(XRRQueryExtension(XtDisplay(wshell), 281 | &xrandr_base_evt, &xrandr_base_err)){ 282 | xrandr_present = True; 283 | XRRSelectInput(XtDisplay(wshell), 284 | XtWindow(wshell), RRScreenChangeNotifyMask); 285 | } 286 | 287 | init_session(); 288 | set_root_background(); 289 | set_numlock_state(); 290 | set_config_info(); 291 | 292 | if(app_res.enable_locking) { 293 | register_screen_saver(); 294 | create_locking_widgets(); 295 | } 296 | if(app_res.enable_shade) { 297 | create_shade_widgets(); 298 | } 299 | 300 | rv = launch_process(app_res.window_manager); 301 | if(rv){ 302 | log_msg("Failed to exec the window manager (%s): %s\n", 303 | app_res.window_manager,strerror(rv)); 304 | return EXIT_FAILURE; 305 | } 306 | 307 | if(app_res.launcher){ 308 | rv = launch_process(app_res.launcher); 309 | if(rv){ 310 | log_msg("Failed to exec the launcher (%s): %s\n", 311 | app_res.launcher,strerror(rv)); 312 | return EXIT_FAILURE; 313 | } 314 | } 315 | process_sessionetc(); 316 | 317 | xt_sigusr1 = XtAppAddSignal(app_context,xt_sigusr1_handler,NULL); 318 | XtAddEventHandler(wshell, PropertyChangeMask, False, 319 | msg_property_handler, NULL); 320 | 321 | xa_MOTIF_WM_OFFSET = 322 | XInternAtom(XtDisplay(wshell), _XA_MOTIF_WM_OFFSET, False); 323 | xa_MOTIF_WM_MESSAGES = 324 | XInternAtom(XtDisplay(wshell), _XA_MOTIF_WM_MESSAGES, False); 325 | XmAddProtocols(wshell, xa_MOTIF_WM_MESSAGES, &xa_MOTIF_WM_OFFSET, 1); 326 | 327 | while(!XtAppGetExitFlag(app_context)) { 328 | XEvent evt; 329 | 330 | XtAppNextEvent(app_context,&evt); 331 | 332 | if(evt.type == KeyPress || evt.type == ButtonPress || 333 | evt.type == MotionNotify){ 334 | if(unlock_up){ 335 | reset_unlock_timer(); 336 | }else if(scr_locked){ 337 | show_unlock_widget(); 338 | /* discard this event, since its purpose was to 339 | * map the 'unlock' widget */ 340 | continue; 341 | } 342 | }else if(evt.type == xss_event_base){ 343 | XScreenSaverNotifyEvent *xsse=(XScreenSaverNotifyEvent*)&evt; 344 | 345 | if(!scr_locked && lock_timer == None && 346 | xsse->state == ScreenSaverOn && app_res.lock_timeout){ 347 | lock_timer = XtAppAddTimeOut(app_context, 348 | app_res.lock_timeout,lock_timeout_cb,NULL); 349 | }else if(xsse->state == ScreenSaverOff){ 350 | if(lock_timer) XtRemoveTimeOut(lock_timer); 351 | lock_timer = None; 352 | } 353 | } else if(xrandr_present && evt.type == 354 | (xrandr_base_evt + RRScreenChangeNotify)) { 355 | XRRUpdateConfiguration(&evt); 356 | reconfigure_widgets((XRRScreenChangeNotifyEvent*)&evt); 357 | } 358 | XtDispatchEvent(&evt); 359 | } 360 | 361 | return 0; 362 | } 363 | 364 | /* 365 | * Called on Xrandr screen change notification 366 | * Adjusts cover dimensions and the 'unlock' box position 367 | */ 368 | static void reconfigure_widgets(XRRScreenChangeNotifyEvent *evt) 369 | { 370 | Arg args[4]; 371 | unsigned int n = 0; 372 | int evt_scrn; 373 | Widget w; 374 | Dimension width, height; 375 | Dimension swidth, sheight; 376 | Position xoff, yoff; 377 | 378 | evt_scrn = XRRRootToScreen(evt->display, evt->root); 379 | 380 | XtResizeWidget(wcovers[evt_scrn], evt->width, evt->height, 0); 381 | 382 | w = XtNameToWidget(wcovers[evt_scrn],"*coverBackdrop"); 383 | assert(w); 384 | XtSetValues(w, args, n); 385 | 386 | get_screen_size(XtScreen(w), &swidth, &sheight, &xoff, &yoff); 387 | 388 | w = XtNameToWidget(w,"*unlock"); 389 | assert(w); 390 | 391 | n = 0; 392 | XtSetArg(args[n], XmNwidth, &width); n++; 393 | XtSetArg(args[n], XmNheight, &height); n++; 394 | XtGetValues(w, args, n); 395 | 396 | n = 0; 397 | XtMoveWidget(w, (xoff + (swidth - width) / 2), 398 | (yoff + (sheight - height) / 2)); 399 | XtSetValues(w, args, n); 400 | 401 | if(app_res.enable_shade) 402 | XtResizeWidget(wshades[evt_scrn], evt->width, evt->height, 0); 403 | } 404 | 405 | /* 406 | * Puts up covers and the unlock widget, removes ACL. 407 | */ 408 | static void lock_screen(void) 409 | { 410 | Boolean can_auth = False; 411 | char *login; 412 | struct passwd *passwd; 413 | 414 | /* make sure we can authenticate before locking */ 415 | login = get_login(); 416 | if(!login) { 417 | log_msg("Cannot retrieve login name\n"); 418 | error_dialog(); 419 | app_res.enable_locking = False; 420 | return; 421 | } 422 | 423 | if(set_privileges(True)) { 424 | 425 | passwd = getpwnam(login); 426 | if(passwd && passwd->pw_passwd[0] != '*') can_auth = True; 427 | 428 | #ifdef __OpenBSD__ 429 | if(passwd && passwd->pw_passwd[0] == '*'){ 430 | passwd = getpwnam_shadow(login); 431 | if(passwd) can_auth = True; 432 | } 433 | #endif /* __OpenBSD__ */ 434 | 435 | #if defined(__linux__) || defined(__svr4__) 436 | if(passwd && passwd->pw_passwd[0] == 'x'){ 437 | struct spwd *spwd = getspnam(login); 438 | if(spwd) can_auth = True; 439 | } 440 | #endif /* __linux__ / __svr4__*/ 441 | 442 | set_privileges(False); 443 | } 444 | 445 | if(!can_auth){ 446 | if(!app_res.silent) XBell(XtDisplay(wshell), 100); 447 | log_msg("Cannot authenticate. Screen locking disabled!\n"); 448 | error_dialog(); 449 | app_res.enable_locking = False; 450 | return; 451 | } 452 | 453 | show_covers(True); 454 | scr_locked = True; 455 | if(!unlock_up) show_unlock_widget(); 456 | 457 | acl_hosts = XListHosts(XtDisplay(wshell),&num_acl_hosts,&acl_state); 458 | if(acl_hosts) XRemoveHosts(XtDisplay(wshell),acl_hosts,num_acl_hosts); 459 | 460 | XFlush(XtDisplay(wshell)); 461 | } 462 | 463 | /* 464 | * Unmaps cover windows, ungrabs input and restores ACL 465 | */ 466 | static void unlock_screen(void) 467 | { 468 | assert(covers_up); 469 | 470 | if(ptr_grabbed){ 471 | XtUngrabPointer(wcovers[0],CurrentTime); 472 | ptr_grabbed = False; 473 | } 474 | if(kbd_grabbed){ 475 | XtUngrabKeyboard(wcovers[0],CurrentTime); 476 | kbd_grabbed = False; 477 | } 478 | 479 | show_covers(False); 480 | unlock_up = False; 481 | 482 | if(unlock_widget_timer){ 483 | XtRemoveTimeOut(unlock_widget_timer); 484 | unlock_widget_timer = None; 485 | } 486 | 487 | if(acl_hosts){ 488 | XAddHosts(XtDisplay(wshell),acl_hosts,num_acl_hosts); 489 | XFree(acl_hosts); 490 | } 491 | 492 | XFlush(XtDisplay(wshell)); 493 | } 494 | 495 | /* 496 | * This is the wcovers[0] visibility state change callback. 497 | * It grabs input devices when cover windows are mapped. 498 | */ 499 | static void covers_up_cb(Widget w, XtPointer p, XEvent *evt, Boolean *dsp) 500 | { 501 | if(evt->type != VisibilityFullyObscured){ 502 | if(!ptr_grabbed){ 503 | if(XtGrabPointer(w,True,ButtonPressMask|PointerMotionMask, 504 | GrabModeAsync,GrabModeAsync, 505 | None,None,CurrentTime) == GrabSuccess) ptr_grabbed = True; 506 | } 507 | if(!kbd_grabbed){ 508 | if(XtGrabKeyboard(w,True,GrabModeAsync, 509 | GrabModeAsync,CurrentTime) == GrabSuccess) kbd_grabbed = True; 510 | } 511 | if(!ptr_grabbed || !kbd_grabbed){ 512 | log_msg("Cannot lock! Failed to grab input devices.\n"); 513 | unlock_screen(); 514 | } 515 | } 516 | } 517 | 518 | /* 519 | * If 'show' is true, maps all cover windows, unmaps them otherwise. 520 | */ 521 | static void show_covers(Boolean show) 522 | { 523 | int i,ncovers = XScreenCount(XtDisplay(wshell)); 524 | 525 | if(show && !covers_up){ 526 | for(i=0; i:DrawingAreaInput()\n"); 618 | 619 | /* Fetch the RGB value of the background pixel so we can 620 | * make pixels for other screens */ 621 | bg_color.pixel = app_res.lock_bg_pixel; 622 | XQueryColor(dpy,DefaultColormap(dpy,DefaultScreen(dpy)),&bg_color); 623 | 624 | for(i=0; i < nscreens; i++){ 625 | Arg args[18]; 626 | int n = 0; 627 | Widget w; 628 | Pixmap bg_pixmap = XmUNSPECIFIED_PIXMAP; 629 | 630 | 631 | XAllocColor(dpy,DefaultColormap(dpy,i),&bg_color); 632 | 633 | if(app_res.lock_bg_image){ 634 | Pixel shadow, fnord; 635 | 636 | XmGetColors(ScreenOfDisplay(dpy,i),DefaultColormap(dpy,i), 637 | bg_color.pixel,&fnord,&fnord,&shadow,&fnord); 638 | 639 | bg_pixmap = XmGetPixmap(ScreenOfDisplay(dpy,i), 640 | app_res.lock_bg_image,shadow,bg_color.pixel); 641 | if(bg_pixmap == XmUNSPECIFIED_PIXMAP) 642 | log_msg("Failed to load pixmap: %s\n",app_res.lock_bg_image); 643 | } 644 | 645 | XtSetArg(args[n],XmNmwmDecorations,0); n++; 646 | XtSetArg(args[n],XmNmwmFunctions,0); n++; 647 | XtSetArg(args[n],XmNx,0); n++; 648 | XtSetArg(args[n],XmNy,0); n++; 649 | XtSetArg(args[n],XmNwidth,DisplayWidth(dpy,i)); n++; 650 | XtSetArg(args[n],XmNheight,DisplayHeight(dpy,i)); n++; 651 | XtSetArg(args[n],XmNuseAsyncGeometry,True); n++; 652 | XtSetArg(args[n],XmNmappedWhenManaged,False); n++; 653 | XtSetArg(args[n],XmNresizePolicy,XmRESIZE_NONE); n++; 654 | XtSetArg(args[n],XmNdepth,DefaultDepth(dpy,i)); n++; 655 | XtSetArg(args[n],XmNcolormap,DefaultColormap(dpy,i)); n++; 656 | XtSetArg(args[n],XmNscreen,XScreenOfDisplay(dpy,i)); n++; 657 | /* can be set at creation time only, so... */ 658 | if(i == 0) { 659 | XtSetArg(args[n],XmNmwmInputMode,MWM_INPUT_SYSTEM_MODAL); 660 | n++; 661 | } 662 | 663 | wcovers[i] = XtCreatePopupShell("coverShell", 664 | topLevelShellWidgetClass, wshell, args, n); 665 | 666 | n = 0; 667 | XtSetArg(args[n],XmNbackground,bg_color.pixel); n++; 668 | XtSetArg(args[n],XmNbackgroundPixmap,bg_pixmap); n++; 669 | w = XmCreateDrawingArea(wcovers[i],"coverBackdrop",args,n); 670 | XtAugmentTranslations(w,drawing_area_tt); 671 | XtManageChild(w); 672 | 673 | XtRealizeWidget(wcovers[i]); 674 | } 675 | 676 | XtAddEventHandler(wcovers[0],VisibilityChangeMask,False,covers_up_cb,NULL); 677 | 678 | wtmp = XtNameToWidget(wcovers[0],"*coverBackdrop"); 679 | assert(wtmp); 680 | 681 | wunlock = XmVaCreateManagedFrame(wtmp, 682 | "unlock",XmNshadowThickness,2,XmNshadowType,XmSHADOW_OUT, 683 | XmNmappedWhenManaged,False,NULL); 684 | 685 | wrowcol = XmVaCreateManagedRowColumn(wunlock,"rowColumn", 686 | XmNminWidth,420,XmNminHeight,240, 687 | XmNorientation,XmVERTICAL, 688 | XmNentryAlignment,XmALIGNMENT_CENTER, 689 | XmNmarginHeight,6,XmNmarginWidth,6,NULL); 690 | 691 | wtmp = XmCreateLabelGadget(wrowcol,"lockedBy",NULL,0); 692 | 693 | if(! (login = get_login()) ) 694 | login = "(unknown)"; 695 | gethostname(host,255); 696 | 697 | locked_by=malloc(strlen(login)+strlen(host)+2); 698 | sprintf(locked_by,"%s@%s",login,host); 699 | label = XmStringCreateLocalized(locked_by); 700 | free(locked_by); 701 | XtVaSetValues(wtmp,XmNlabelString,label,NULL); 702 | XmStringFree(label); 703 | XtManageChild(wtmp); 704 | 705 | /* Since we're disabling most of the input capabilities for the password 706 | * text field, install a more appropriate translation table */ 707 | passwd_input_tt = XtParseTranslationTable( 708 | ":focusIn()\n" 709 | ":focusOut()\n" 710 | "osfActivate:activate()\n" 711 | "Return:activate()\n" 712 | "osfBackSpace:delete-previous-character()\n" 713 | "BackSpace:delete-previous-character()\n" 714 | "osfDelete:delete-previous-character()\n" 715 | "Delete:delete-previous-character()\n" 716 | ":self-insert()\n"); 717 | wpasswd = XmVaCreateManagedTextField(wrowcol,"password", 718 | XmNtranslations,passwd_input_tt,NULL); 719 | 720 | /* Put the unlock box into the center of the screen */ 721 | get_screen_size(XtScreen(wunlock), &swidth, &sheight, &xoff, &yoff); 722 | XtVaGetValues(wunlock,XmNwidth,&width,XmNheight,&height,NULL); 723 | XtVaSetValues(wunlock,XmNx,xoff+(swidth-width)/2, 724 | XmNy,yoff+(sheight-height)/2,NULL); 725 | 726 | /* Allocate password entry buffer to be passed to the modifyVerifyCallback 727 | * where it receives entered characters that get replaced with asterisk 728 | * in the text field widget */ 729 | pwb_size = sysconf(_SC_GETPW_R_SIZE_MAX); 730 | if(pwb_size == (-1)) pwb_size = DEF_MAX_PASSWD; 731 | pwb = malloc(pwb_size+1); 732 | if(!pwb){ 733 | log_msg("malloc: %s\n", strerror(errno)); 734 | exit(EXIT_FAILURE); 735 | } 736 | pwb[0] = '\0'; 737 | 738 | XtVaSetValues(wpasswd,XmNmaxLength,(int)pwb_size,NULL); 739 | XtAddCallback(wpasswd,XmNmodifyVerifyCallback,passwd_modify_cb,pwb); 740 | XtAddCallback(wpasswd,XmNmotionVerifyCallback,passwd_modify_cb,pwb); 741 | XtAddCallback(wpasswd,XmNactivateCallback,passwd_enter_cb,pwb); 742 | 743 | wmessage = XmCreateLabelGadget(wrowcol,"message",NULL,0); 744 | XtVaSetValues(wmessage,XmNalignment,XmALIGNMENT_CENTER, 745 | XmNmappedWhenManaged,False,NULL); 746 | set_unlock_message(NULL); 747 | XtManageChild(wmessage); 748 | } 749 | 750 | static void create_shade_widgets(void) 751 | { 752 | Display *dpy = XtDisplay(wshell); 753 | int nscreens; 754 | int i; 755 | 756 | nscreens = XScreenCount(dpy); 757 | wshades = calloc(nscreens, sizeof(Widget)); 758 | if(!wshades) { 759 | log_msg("malloc: %s\n", strerror(errno)); 760 | app_res.enable_shade = False; 761 | return; 762 | } 763 | 764 | for(i=0; i < nscreens; i++){ 765 | Arg args[18]; 766 | int n = 0; 767 | 768 | XtSetArg(args[n], XmNmwmDecorations, 0); n++; 769 | XtSetArg(args[n], XmNmwmFunctions, 0); n++; 770 | XtSetArg(args[n], XmNx, 0); n++; 771 | XtSetArg(args[n], XmNy, 0); n++; 772 | XtSetArg(args[n], XmNwidth, DisplayWidth(dpy,i)); n++; 773 | XtSetArg(args[n], XmNheight, DisplayHeight(dpy,i)); n++; 774 | XtSetArg(args[n], XmNuseAsyncGeometry, True); n++; 775 | XtSetArg(args[n], XmNmappedWhenManaged, False); n++; 776 | XtSetArg(args[n], XmNresizePolicy, XmRESIZE_NONE); n++; 777 | XtSetArg(args[n], XmNdepth, DefaultDepth(dpy,i)); n++; 778 | XtSetArg(args[n], XmNcolormap, DefaultColormap(dpy,i)); n++; 779 | XtSetArg(args[n], XmNscreen, XScreenOfDisplay(dpy,i)); n++; 780 | 781 | wshades[i] = XtCreatePopupShell("shade", 782 | topLevelShellWidgetClass, wshell, args, n); 783 | 784 | XtRealizeWidget(wshades[i]); 785 | } 786 | } 787 | 788 | 789 | /* 790 | * Creates a root window property containing the window handle of the 791 | * session manager shell, which in turn gets a property containing 792 | * the instance pid. Terminates if another instance is running. 793 | */ 794 | void init_session(void) 795 | { 796 | Display *dpy = XtDisplay(wshell); 797 | Window root = DefaultRootWindow(dpy); 798 | Window shell; 799 | pid_t pid; 800 | Atom ret_type; 801 | int ret_format; 802 | unsigned long ret_items; 803 | unsigned long left_items; 804 | unsigned char *prop_data; 805 | 806 | XGetWindowProperty(dpy,root,xa_mgr,0,sizeof(Window),False,XA_WINDOW, 807 | &ret_type,&ret_format,&ret_items,&left_items,&prop_data); 808 | 809 | if(ret_type == XA_WINDOW){ 810 | shell = *((Window*)prop_data); 811 | XFree(prop_data); 812 | 813 | XSync(dpy,False); 814 | def_x_err_handler = XSetErrorHandler(local_x_err_handler); 815 | 816 | XGetWindowProperty(dpy,shell,xa_pid,0,sizeof(Window),False,XA_INTEGER, 817 | &ret_type,&ret_format,&ret_items,&left_items,&prop_data); 818 | XSync(dpy,False); 819 | 820 | XSetErrorHandler(def_x_err_handler); 821 | 822 | if(ret_type == XA_INTEGER){ 823 | pid = *((pid_t*)prop_data); 824 | log_msg("%s is already running as PID %lu\n", 825 | bin_name, (unsigned long)pid); 826 | XFree(prop_data); 827 | exit(EXIT_SUCCESS); 828 | } 829 | } 830 | 831 | shell = XtWindow(wshell); 832 | XChangeProperty(dpy,root,xa_mgr,XA_WINDOW,32, 833 | PropModeReplace,(unsigned char*)&shell,1); 834 | 835 | pid = getpid(); 836 | XChangeProperty(dpy,shell,xa_pid,XA_INTEGER,32, 837 | PropModeReplace,(unsigned char*)&pid,1); 838 | } 839 | 840 | /* 841 | * This is temporarily set in init_session just to catch BadWindow errors 842 | * originating from a window handle, stored on root in MGR_ATOM_NAME by the 843 | * previous instance, that is no longer valid. 844 | */ 845 | static int local_x_err_handler(Display *dpy, XErrorEvent *evt) 846 | { 847 | if(evt->error_code == BadWindow) return 0; 848 | return def_x_err_handler(dpy,evt); 849 | } 850 | 851 | /* 852 | * Tries to authenticate using the entered password 853 | */ 854 | static void passwd_enter_cb(Widget w, 855 | XtPointer client_data, XtPointer call_data) 856 | { 857 | struct passwd *passwd; 858 | char *login; 859 | char *pwb = (char*)client_data; 860 | char *cpw = NULL; 861 | char *upw = NULL; 862 | 863 | login = get_login(); 864 | 865 | set_privileges(True); 866 | 867 | passwd = getpwnam(login); 868 | upw = passwd->pw_passwd; 869 | 870 | #ifdef __OpenBSD__ 871 | if(passwd && passwd->pw_passwd[0] == '*'){ 872 | passwd = getpwnam_shadow(login); 873 | if(passwd) upw = passwd->pw_passwd; 874 | } 875 | #endif /* __OpenBSD__ */ 876 | 877 | #if defined(__linux__) || defined(__svr4__) 878 | if(!passwd || passwd->pw_passwd[0] == 'x'){ 879 | struct spwd *spwd; 880 | spwd = getspnam(login); 881 | if(spwd) upw = spwd->sp_pwdp; 882 | } 883 | #endif /* __linux__ / __svr4__ */ 884 | 885 | set_privileges(False); 886 | 887 | if(!upw || !(cpw = crypt(pwb,upw))){ 888 | log_msg("Failed to retrieve login credentials.\n"); 889 | exit(EXIT_FAILURE); 890 | } 891 | 892 | if(!strcmp(cpw,upw)){ 893 | unlock_screen(); 894 | set_unlock_message(NULL); 895 | }else{ 896 | if(!app_res.silent) XBell(XtDisplay(w),100); 897 | set_unlock_message(MSG_NOACCESS); 898 | } 899 | 900 | memset(pwb,0,strlen(pwb)); 901 | XmTextFieldSetString(wpasswd,""); 902 | } 903 | 904 | /* 905 | * Called on password text field modification and cursor movement. 906 | * Stores entered values in a separate buffer while placing 907 | * asterisk characters in the text field. Discards any cursor placement. 908 | */ 909 | static void passwd_modify_cb(Widget w, 910 | XtPointer client_data, XtPointer call_data) 911 | { 912 | char *pwb = (char*)client_data; 913 | XmTextVerifyCallbackStruct *cbs = 914 | (XmTextVerifyCallbackStruct*)call_data; 915 | 916 | if(cbs->reason == XmCR_MODIFYING_TEXT_VALUE){ 917 | if(!cbs->text->length){ 918 | pwb[cbs->startPos] = '\0'; 919 | }else if(cbs->text->length == 1){ 920 | pwb[cbs->currInsert] = *cbs->text->ptr; 921 | *cbs->text->ptr = '*'; 922 | pwb[cbs->currInsert+1] = '\0'; 923 | }else{ 924 | cbs->doit = False; 925 | } 926 | }else if(cbs->reason == XmCR_MOVING_INSERT_CURSOR){ 927 | if(cbs->newInsert != XmTextFieldGetLastPosition(w)) 928 | cbs->doit = False; 929 | } 930 | } 931 | 932 | /* 933 | * This timeout is set when X screen saver activates 934 | */ 935 | static void lock_timeout_cb(XtPointer ptr, XtIntervalId *id) 936 | { 937 | lock_screen(); 938 | lock_timer = None; 939 | } 940 | 941 | /* 942 | * Returns size and x/y offsets of the primary xinerama screen, 943 | * or just the screen size if xinerama isn't active. 944 | */ 945 | static void get_screen_size(Screen *scr, Dimension *pwidth, Dimension *pheight, 946 | Position *px, Position *py) 947 | { 948 | if(XineramaIsActive(XtDisplay(wshell))){ 949 | int nxis; 950 | XineramaScreenInfo *xis; 951 | 952 | xis=XineramaQueryScreens(XtDisplay(wshell),&nxis); 953 | 954 | if(app_res.prim_xinerama_screen >= nxis){ 955 | log_msg("Primary Xinerama screen index %d " 956 | "is out of range.\n",app_res.prim_xinerama_screen); 957 | app_res.prim_xinerama_screen = 0; 958 | } 959 | 960 | *pwidth=xis[app_res.prim_xinerama_screen].width; 961 | *pheight=xis[app_res.prim_xinerama_screen].height; 962 | *px=xis[app_res.prim_xinerama_screen].x_org; 963 | *py=xis[app_res.prim_xinerama_screen].y_org; 964 | 965 | XFree(xis); 966 | return; 967 | }else{ 968 | *pwidth=XWidthOfScreen(scr); 969 | *pheight=XHeightOfScreen(scr); 970 | *px=0; 971 | *py=0; 972 | } 973 | } 974 | 975 | /* 976 | * Drops/restores process privileges. Issues a warning if initial 977 | * effective uid isn't zero. Any syscall failure is treated as fatal 978 | * and will terminate the process. 979 | */ 980 | static Boolean set_privileges(Boolean elevate) 981 | { 982 | static Boolean initialized = False; 983 | static Boolean can_elevate = False; 984 | static uid_t orig_uid; 985 | static gid_t orig_gid; 986 | int res = 0; 987 | 988 | if(!initialized){ 989 | orig_uid = geteuid(); 990 | orig_gid = getegid(); 991 | 992 | if(orig_uid != 0){ 993 | log_msg("%s must be setuid root to enable " 994 | "screen locking capabilities.\n",bin_name); 995 | initialized = True; 996 | can_elevate = False; 997 | return False; 998 | } 999 | initialized = True; 1000 | can_elevate = True; 1001 | } 1002 | 1003 | if(!can_elevate) return False; 1004 | 1005 | if(elevate){ 1006 | res = seteuid(orig_uid); 1007 | res |= setegid(orig_gid); 1008 | }else{ 1009 | gid_t newgid = getgid(); 1010 | 1011 | res = setegid(newgid); 1012 | res |= seteuid(getuid()); 1013 | } 1014 | 1015 | if(res){ 1016 | perror("setgid/uid"); 1017 | exit(EXIT_FAILURE); 1018 | } 1019 | return True; 1020 | } 1021 | 1022 | /* 1023 | * Register with the XScreenSaver extension for timed locking. 1024 | * Returns True on success. 1025 | */ 1026 | static void register_screen_saver(void) 1027 | { 1028 | Display *dpy = XtDisplay(wshell); 1029 | int i,n; 1030 | 1031 | if(!XScreenSaverQueryExtension(dpy,&xss_event_base,&xss_error_base)){ 1032 | log_msg("No XScreenSaver extension available. " 1033 | "Timed locking disabled.\n"); 1034 | return; 1035 | } 1036 | 1037 | n = XScreenCount(XtDisplay(wshell)); 1038 | 1039 | for(i = 0; i < n; i++){ 1040 | XScreenSaverRegister(dpy,i,XtWindow(wshell),XA_WINDOW); 1041 | XScreenSaverSelectInput(dpy,RootWindow(dpy,i),ScreenSaverNotifyMask); 1042 | } 1043 | 1044 | XSetScreenSaver(dpy,app_res.blank_timeout,0, 1045 | PreferBlanking,DefaultExposures); 1046 | } 1047 | 1048 | /* 1049 | * Forks and execvs the specified binary. 1050 | * Returns zero on success, errno otherwise. 1051 | */ 1052 | static int launch_process(const char *path) 1053 | { 1054 | pid_t pid; 1055 | volatile int errval = 0; 1056 | char *p = (char*)path; 1057 | char *str = NULL; 1058 | size_t argc = 1; 1059 | char **argv; 1060 | 1061 | while((p = strchr(p, ' '))) { 1062 | while(*p && *p == ' ') p++; 1063 | argc++; 1064 | } 1065 | 1066 | argv = calloc(argc + 1, sizeof(char*)); 1067 | if(!argv) return errno; 1068 | 1069 | if(argc > 1) { 1070 | size_t i = 0; 1071 | char *str = strdup(path); 1072 | if(!str) { 1073 | free(argv); 1074 | return ENOMEM; 1075 | } 1076 | p = strtok(str, " "); 1077 | argv[i++] = p; 1078 | while((p = strtok(NULL, " "))) argv[i++] = p; 1079 | } else { 1080 | argv[0] = (char*)path; 1081 | } 1082 | argv[argc] = NULL; 1083 | 1084 | pid = vfork(); 1085 | if(pid == 0){ 1086 | pid_t fpid = getpid(); 1087 | 1088 | #if defined(__linux__) || defined(__svr4__) 1089 | setpgid(fpid,fpid); 1090 | #else 1091 | setpgrp(fpid,fpid); 1092 | #endif /* __linux__ || __svr4__ */ 1093 | 1094 | setuid(geteuid()); 1095 | setgid(getegid()); 1096 | 1097 | if(execv(argv[0], argv) == (-1)) errval = errno; 1098 | _exit(0); 1099 | }else if(pid == -1){ 1100 | errval=errno; 1101 | } 1102 | 1103 | free(argv); 1104 | if(str) free(str); 1105 | 1106 | return errval; 1107 | } 1108 | 1109 | /* 1110 | * Launch a shell to run stuff from ~/.sessionetc, if any. 1111 | */ 1112 | static void process_sessionetc(void) 1113 | { 1114 | char *home; 1115 | char fname[]=".sessionetc"; 1116 | char *path; 1117 | pid_t pid; 1118 | volatile int errval = 0; 1119 | 1120 | home=getenv("HOME"); 1121 | if(!home){ 1122 | log_msg("HOME is not set\n"); 1123 | return; 1124 | } 1125 | 1126 | path=malloc(strlen(home)+strlen(fname)+2); 1127 | if(!path){ 1128 | perror("malloc"); 1129 | return; 1130 | } 1131 | sprintf(path,"%s/%s",home,fname); 1132 | if(access(path,R_OK) == (-1)){ 1133 | if(errno == EACCES) 1134 | log_msg("No read permission for %s\n",path); 1135 | free(path); 1136 | return; 1137 | } 1138 | pid=vfork(); 1139 | if(pid == 0){ 1140 | char *argv[] = {"sh", path, NULL}; 1141 | pid_t fpid = getpid(); 1142 | 1143 | #if defined(__linux__) || defined(__svr4__) 1144 | setpgid(fpid,fpid); 1145 | #else 1146 | setpgrp(fpid,fpid); 1147 | #endif /* __linux__ || __svr4__ */ 1148 | 1149 | if(execvp("sh",argv) == (-1)) errval = errno; 1150 | _exit(0); 1151 | }else if(pid == (-1)){ 1152 | errval = errno; 1153 | } 1154 | if(errval){ 1155 | log_msg("shell execution failed with: %s\n",strerror(errval)); 1156 | } 1157 | free(path); 1158 | } 1159 | 1160 | /* 1161 | * Sets the root cursor, background color and, if specified, 1162 | * image on all screens. 1163 | */ 1164 | static void set_root_background(void) 1165 | { 1166 | Cursor cursor; 1167 | XColor bg_color; 1168 | int nscreens, i; 1169 | Display *dpy = XtDisplay(wshell); 1170 | 1171 | cursor = XCreateFontCursor(dpy,XC_left_ptr); 1172 | bg_color.pixel = app_res.wkspace_bg_pixel; 1173 | XQueryColor(dpy,DefaultColormap(dpy,DefaultScreen(dpy)),&bg_color); 1174 | 1175 | nscreens = XScreenCount(dpy); 1176 | 1177 | for(i=0; inames->vmods[i] && 1244 | (mod_name = XGetAtomName(dpy,desc->names->vmods[i])) && 1245 | !strcmp(mod_name,"NumLock")){ 1246 | 1247 | if(!XkbVirtualModsToReal(desc,(1<state != PropertyNewValue || pev->atom != xa_cmd) return; 1561 | 1562 | XGetTextProperty(XtDisplay(wshell), XtWindow(wshell), &prop, xa_cmd); 1563 | 1564 | if(prop.encoding != XA_STRING || prop.value == NULL) { 1565 | log_msg("Invalid command received\n"); 1566 | return; 1567 | } 1568 | 1569 | #ifdef DEBUG_CMD 1570 | log_msg("Received \"%s\" command\n", (char*)prop.value); 1571 | #endif 1572 | 1573 | if(!strcmp((char*)prop.value, XMSM_LOGOUT_CMD)) { 1574 | exit_session_dialog(); 1575 | } else if(!strcmp((char*)prop.value, XMSM_LOCK_CMD)) { 1576 | if(app_res.enable_locking){ 1577 | lock_screen(); 1578 | if(app_res.blank_on_lock) 1579 | XtAppAddTimeOut(app_context,1000,blank_delay_timeout_cb,NULL); 1580 | } else { 1581 | if(!app_res.silent) XBell(XtDisplay(wshell), 100); 1582 | log_msg("Can't lock. Locking is disabled\n"); 1583 | error_dialog(); 1584 | } 1585 | } else if(!strcmp((char*)prop.value, XMSM_SUSPEND_CMD)) { 1586 | if(app_res.lock_on_suspend) { 1587 | if(app_res.enable_locking) 1588 | lock_screen(); 1589 | else 1590 | log_msg("Can't lock. Locking is disabled\n"); 1591 | } 1592 | if(app_res.enable_suspend) { 1593 | if(!exec_sys_cmd(SUSPEND_CMD)) 1594 | error_dialog(); 1595 | } else { 1596 | log_msg("Can't suspend. Command is disabled\n"); 1597 | } 1598 | } 1599 | 1600 | XtFree((char*)prop.value); 1601 | XFlush(XtDisplay(wshell)); 1602 | } 1603 | 1604 | /* 1605 | * Sets the _XM_SESSION_MANAGER_CFG property 1606 | */ 1607 | static void set_config_info(void) 1608 | { 1609 | Display *dpy = XtDisplay(wshell); 1610 | Window root = DefaultRootWindow(dpy); 1611 | unsigned long state = 1612 | (app_res.enable_locking ? XMSM_CFG_LOCK : 0) | 1613 | (app_res.enable_suspend ? XMSM_CFG_SUSPEND : 0); 1614 | 1615 | XChangeProperty(dpy, root, xa_cfg, XA_INTEGER, 32, 1616 | PropModeReplace, (unsigned char*)&state, 1); 1617 | } 1618 | 1619 | /* 1620 | * MWM client offset message handler for the exit dialog shell. 1621 | * Positions the exit dialog centered on the screen. 1622 | */ 1623 | static void exit_dialog_wm_offset_cb(Widget w, 1624 | XtPointer client_data, XtPointer call_data) 1625 | { 1626 | Position x, y; 1627 | Dimension width, height; 1628 | Dimension swidth, sheight; 1629 | XmAnyCallbackStruct *cbs = (XmAnyCallbackStruct*)call_data; 1630 | Position off_x = (Position)cbs->event->xclient.data.l[1]; 1631 | Position off_y = (Position)cbs->event->xclient.data.l[2]; 1632 | 1633 | get_screen_size(XtScreen(wshell), &swidth, &sheight, &x, &y); 1634 | 1635 | XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL); 1636 | x += (swidth - width) / 2 - off_x; 1637 | y += (sheight - height) / 2 - off_y; 1638 | XtMoveWidget(w, x, y); 1639 | } 1640 | 1641 | /* Button press handler for xt_sigterm_handler confirmation dialog */ 1642 | static void exit_dialog_cb(Widget w, 1643 | XtPointer client_data, XtPointer call_data) 1644 | { 1645 | *((Widget*)client_data) = w; 1646 | } 1647 | 1648 | static void xt_sigusr1_handler(XtPointer ptr, XtSignalId *id) 1649 | { 1650 | if(app_res.enable_locking) { 1651 | lock_screen(); 1652 | if(app_res.blank_on_lock) 1653 | XForceScreenSaver(XtDisplay(wshell),ScreenSaverActive); 1654 | } else { 1655 | log_msg("Can't lock. Locking is disabled\n"); 1656 | } 1657 | } 1658 | 1659 | static void blank_delay_timeout_cb(XtPointer ptr, XtIntervalId *iid) 1660 | { 1661 | XForceScreenSaver(XtDisplay(wshell),ScreenSaverActive); 1662 | } 1663 | 1664 | /* 1665 | * Low level signal handlers. 1666 | * These just turn async signals into Xt callbacks. 1667 | */ 1668 | static void sigusr_handler(int sig) 1669 | { 1670 | if(sig == SIGUSR1) XtNoticeSignal(xt_sigusr1); 1671 | } 1672 | 1673 | static void sigchld_handler(int sig) 1674 | { 1675 | int status; 1676 | waitpid(-1, &status, WNOHANG); 1677 | } 1678 | -------------------------------------------------------------------------------- /src/tbmain.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018-2024 alx@fastestcode.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | /* 24 | * Toolbox initialization and GUI routines 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include "tbparse.h" 52 | #include "common.h" 53 | #include "smglobal.h" 54 | 55 | /* Forward declarations */ 56 | static char* find_rc_file(void); 57 | static Boolean construct_menu(void); 58 | static void create_utility_widgets(Widget); 59 | static void set_icon(Widget); 60 | static void setup_hotkeys(void); 61 | static int xgrabkey_err_handler(Display*,XErrorEvent*); 62 | static void handle_root_event(XEvent*); 63 | void raise_and_focus(Widget w); 64 | static void time_update_cb(XtPointer,XtIntervalId*); 65 | static int exec_command(const char*); 66 | static void report_exec_error(const char*,const char*,int); 67 | static void report_rcfile_error(const char*,const char*); 68 | static char* get_user_input(Widget,const char*); 69 | static Boolean message_dialog(Boolean,const char*); 70 | static void wait_state(Boolean); 71 | static void exec_cb(Widget,XtPointer,XtPointer); 72 | static void menu_command_cb(Widget,XtPointer,XtPointer); 73 | static void user_input_cb(Widget,XtPointer,XtPointer); 74 | static void message_dialog_cb(Widget,XtPointer,XtPointer); 75 | static void sigchld_handler(int); 76 | static void sigusr_handler(int); 77 | static void xt_sigusr1_handler(XtPointer,XtSignalId*); 78 | static void suspend_cb(Widget,XtPointer,XtPointer); 79 | static void logout_cb(Widget,XtPointer,XtPointer); 80 | static void lock_cb(Widget,XtPointer,XtPointer); 81 | static Boolean send_xmsm_cmd(const char *command); 82 | static int local_x_err_handler(Display*,XErrorEvent*); 83 | static Boolean get_xmsm_config(unsigned long*); 84 | 85 | 86 | struct tb_resources { 87 | char *title; 88 | Boolean show_date_time; 89 | char *date_time_fmt; 90 | char *rc_file; 91 | char *hotkey; 92 | unsigned int rcfile_check_time; 93 | Boolean horizontal; 94 | Boolean separators; 95 | } app_res; 96 | 97 | #define RES_FIELD(f) XtOffsetOf(struct tb_resources,f) 98 | XtResource xrdb_resources[]={ 99 | { "title","Title",XmRString,sizeof(String), 100 | RES_FIELD(title),XmRImmediate,(XtPointer)NULL 101 | }, 102 | { "dateTimeDisplay","DateTimeDisplay",XmRBoolean,sizeof(Boolean), 103 | RES_FIELD(show_date_time),XmRImmediate,(XtPointer)True 104 | }, 105 | { "dateTimeFormat","DateTimeFormat",XmRString,sizeof(String), 106 | RES_FIELD(date_time_fmt),XmRImmediate,(XtPointer)"%D %l:%M %p" 107 | }, 108 | { "rcFile","RcFile",XmRString,sizeof(String), 109 | RES_FIELD(rc_file),XmRImmediate,(XtPointer)NULL 110 | }, 111 | { "hotkey","Hotkey",XmRString,sizeof(String), 112 | RES_FIELD(hotkey),XmRImmediate,(XtPointer)NULL 113 | }, 114 | { "horizontal","Horizontal",XmRBoolean,sizeof(Boolean), 115 | RES_FIELD(horizontal),XmRImmediate,(XtPointer)False 116 | }, 117 | { "separators","Separators",XmRBoolean,sizeof(Boolean), 118 | RES_FIELD(separators),XmRImmediate,(XtPointer)True 119 | } 120 | }; 121 | #undef RES_FIELD 122 | 123 | static XrmOptionDescRec xrdb_options[]={ 124 | {"-title","title",XrmoptionSepArg,(caddr_t)NULL}, 125 | {"-rcfile","rcFile",XrmoptionSepArg,(caddr_t)NULL}, 126 | {"-hotkey","hotkey",XrmoptionSepArg,(caddr_t)NULL}, 127 | {"-horizontal", "horizontal", XrmoptionNoArg, (caddr_t)"True"}, 128 | {"+horizontal", "horizontal", XrmoptionNoArg, (caddr_t)"False"} 129 | }; 130 | 131 | String fallback_res[]={ 132 | "XmToolbox.x: 8", 133 | "XmToolbox.y: 28", 134 | "XmToolbox.mwmDecorations: 58", 135 | "*mainFrame.shadowThickness: 1", 136 | NULL 137 | }; 138 | 139 | #define APP_TITLE "Toolbox" 140 | #define APP_NAME "xmtoolbox" 141 | #define RC_NAME "toolboxrc" 142 | 143 | Atom xa_xmsm_mgr=None; 144 | Atom xa_xmsm_pid=None; 145 | Atom xa_xmsm_cmd=None; 146 | Atom xa_xmsm_cfg=None; 147 | int (*def_x_err_handler)(Display*,XErrorEvent*)=NULL; 148 | const char xmsm_cmd_err[] = 149 | "Cannot retrieve session manager PID.\nxmsm not running?"; 150 | 151 | XtAppContext app_context; 152 | Widget wshell; 153 | Widget wmain=None; 154 | Widget wdate_time=None; 155 | Widget wmenu=None; 156 | String rc_file_path=NULL; 157 | XtSignalId xt_sigusr1; 158 | KeyCode hotkey_code=0; 159 | unsigned int hotkey_mods=0; 160 | unsigned long xmsm_cfg = 0; 161 | 162 | int main(int argc, char **argv) 163 | { 164 | Window root_window; 165 | Widget wframe; 166 | 167 | rsignal(SIGUSR1, sigusr_handler); 168 | rsignal(SIGUSR2, sigusr_handler); 169 | rsignal(SIGCHLD, sigchld_handler); 170 | 171 | XtSetLanguageProc(NULL,NULL,NULL); 172 | XtToolkitInitialize(); 173 | 174 | wshell=XtVaAppInitialize(&app_context, "XmToolbox", 175 | xrdb_options,XtNumber(xrdb_options), &argc,argv, fallback_res, 176 | XmNiconName, APP_TITLE, XmNallowShellResize, True, 177 | XmNmwmFunctions, MWM_FUNC_MOVE|MWM_FUNC_MINIMIZE, NULL); 178 | 179 | XtGetApplicationResources(wshell,&app_res,xrdb_resources, 180 | XtNumber(xrdb_resources),NULL,0); 181 | 182 | xa_xmsm_mgr = XInternAtom(XtDisplay(wshell), XMSM_ATOM_NAME, True); 183 | xa_xmsm_pid = XInternAtom(XtDisplay(wshell), XMSM_PID_ATOM_NAME, True); 184 | xa_xmsm_cmd = XInternAtom(XtDisplay(wshell), XMSM_CMD_ATOM_NAME, True); 185 | xa_xmsm_cfg = XInternAtom(XtDisplay(wshell), XMSM_CFG_ATOM_NAME, True); 186 | 187 | if(!get_xmsm_config(&xmsm_cfg)) 188 | message_dialog(False, xmsm_cmd_err); 189 | 190 | if(!app_res.title){ 191 | char *title; 192 | char *login; 193 | char host[256]="localhost"; 194 | 195 | if( (login = get_login()) ) { 196 | gethostname(host,255); 197 | 198 | title=malloc(strlen(login)+strlen(host)+2); 199 | if(!title){ 200 | perror("malloc"); 201 | return EXIT_FAILURE; 202 | } 203 | sprintf(title,"%s@%s",login,host); 204 | XtVaSetValues(wshell,XmNtitle,title,NULL); 205 | free(title); 206 | } 207 | } 208 | 209 | wframe=XmVaCreateManagedFrame(wshell,"mainFrame", 210 | XmNshadowType, XmSHADOW_OUT, NULL); 211 | 212 | wmain=XmVaCreateManagedRowColumn(wframe, "main", 213 | XmNmarginWidth, 0, 214 | XmNmarginHeight, 0, 215 | XmNspacing, 0, 216 | XmNorientation, (app_res.horizontal ? XmHORIZONTAL:XmVERTICAL), 217 | NULL); 218 | 219 | rc_file_path=(app_res.rc_file)?app_res.rc_file:find_rc_file(); 220 | 221 | if(rc_file_path){ 222 | if(access(rc_file_path, R_OK) == -1){ 223 | message_dialog(False, "Cannot access RC file. Exiting!"); 224 | perror(rc_file_path); 225 | return EXIT_FAILURE; 226 | } 227 | if(!construct_menu()) return EXIT_FAILURE; 228 | }else{ 229 | message_dialog(False, "RC file not found, nor specified. Exiting!"); 230 | fprintf(stderr,"%s not found, nor specified.\n",RC_NAME); 231 | return EXIT_FAILURE; 232 | } 233 | 234 | create_utility_widgets(wmain); 235 | 236 | xt_sigusr1 = XtAppAddSignal(app_context,xt_sigusr1_handler,NULL); 237 | 238 | XtRealizeWidget(wshell); 239 | set_icon(wshell); 240 | setup_hotkeys(); 241 | 242 | root_window = XDefaultRootWindow(XtDisplay(wshell)); 243 | 244 | for(;;) { 245 | XEvent evt; 246 | XtAppNextEvent(app_context,&evt); 247 | if(evt.xany.window==root_window) 248 | handle_root_event(&evt); 249 | else 250 | XtDispatchEvent(&evt); 251 | } 252 | 253 | return 0; 254 | } 255 | 256 | static void set_icon(Widget wshell) 257 | { 258 | Pixmap image; 259 | Pixmap mask; 260 | Window root; 261 | Display *dpy = XtDisplay(wshell); 262 | int depth, screen; 263 | Screen *pscreen; 264 | 265 | #include "xbm/toolbox.xbm" 266 | #include "xbm/toolbox_m.xbm" 267 | 268 | pscreen = XDefaultScreenOfDisplay(dpy); 269 | screen = XScreenNumberOfScreen(pscreen); 270 | root = RootWindowOfScreen(pscreen); 271 | depth = DefaultDepth(dpy, screen); 272 | 273 | image = XCreatePixmapFromBitmapData(dpy, root, 274 | (char*)toolbox_xbm_bits, 275 | toolbox_xbm_width, 276 | toolbox_xbm_height, 277 | BlackPixel(dpy, screen), 278 | WhitePixel(dpy, screen), depth); 279 | 280 | mask = XCreatePixmapFromBitmapData(dpy, root, 281 | (char*)toolbox_m_xbm_bits, 282 | toolbox_m_xbm_width, 283 | toolbox_m_xbm_height, 1, 0, 1); 284 | 285 | XtVaSetValues(wshell, XmNiconPixmap, image, XmNiconMask, mask, NULL); 286 | } 287 | 288 | /* 289 | * Parse the specified hotkey combination, grab the key and attach a callback. 290 | */ 291 | static void setup_hotkeys(void) 292 | { 293 | Window root_window; 294 | char *buf; 295 | char *token; 296 | KeySym key_sym=NoSymbol; 297 | static int (*def_x_err_handler)(Display*,XErrorEvent*)=NULL; 298 | 299 | if(!app_res.hotkey || !strcasecmp(app_res.hotkey,"none")) return; 300 | 301 | hotkey_code=0; 302 | hotkey_mods=0; 303 | 304 | buf=strdup(app_res.hotkey); 305 | token=strtok(buf," \t+"); 306 | if(token){ 307 | while(token){ 308 | if(!strcasecmp(token,"alt")){ 309 | hotkey_mods|=Mod1Mask; 310 | }else if(!strcasecmp(token,"ctrl") || 311 | !strcasecmp(token,"control")){ 312 | hotkey_mods|=ControlMask; 313 | }else if(!strcasecmp(token,"shift")){ 314 | hotkey_mods|=ShiftMask; 315 | }else{ 316 | key_sym=XStringToKeysym(token); 317 | break; 318 | } 319 | token=strtok(NULL," \t+"); 320 | } 321 | }else{ 322 | key_sym=XStringToKeysym(buf); 323 | } 324 | free(buf); 325 | 326 | if(key_sym == NoSymbol){ 327 | fputs("Invalid hotkey specification\n",stderr); 328 | return; 329 | } 330 | hotkey_code = XKeysymToKeycode(XtDisplay(wshell),key_sym); 331 | 332 | root_window = XDefaultRootWindow(XtDisplay(wshell)); 333 | 334 | XSync(XtDisplay(wshell),False); 335 | def_x_err_handler = XSetErrorHandler(xgrabkey_err_handler); 336 | 337 | /* We need to grab all possible combinations of specified modifiers + 338 | * any lock modifiers that may be active. I'm not aware of any better 339 | * way to get this working */ 340 | XGrabKey(XtDisplay(wshell),hotkey_code,hotkey_mods, 341 | root_window,False,GrabModeAsync,GrabModeAsync); 342 | XGrabKey(XtDisplay(wshell),hotkey_code,hotkey_mods|Mod2Mask, 343 | root_window,False,GrabModeAsync,GrabModeAsync); 344 | XGrabKey(XtDisplay(wshell),hotkey_code,hotkey_mods|LockMask, 345 | root_window,False,GrabModeAsync,GrabModeAsync); 346 | XGrabKey(XtDisplay(wshell),hotkey_code,hotkey_mods|LockMask|Mod2Mask, 347 | root_window,False,GrabModeAsync,GrabModeAsync); 348 | 349 | XSync(XtDisplay(wshell),False); 350 | XSetErrorHandler(def_x_err_handler); 351 | 352 | XSelectInput(XtDisplay(wshell),root_window,KeyPressMask); 353 | } 354 | 355 | /* 356 | * Temporarily set in setup_hotkeys to catch BadAccess errors produced 357 | * by XGrabKey when an already grabbed key is specified. 358 | */ 359 | static int xgrabkey_err_handler(Display *dpy, XErrorEvent *evt) 360 | { 361 | if(evt->error_code == BadAccess){ 362 | fputs("Cannot setup hotkey. " 363 | "Specified key code is used by another application.\n",stderr); 364 | return 0; 365 | } 366 | exit(EXIT_FAILURE); /* shouldn't normally happen */ 367 | } 368 | 369 | /* 370 | * Called whenever the hotkey handler widget receives a KeyPress event 371 | */ 372 | static void handle_root_event(XEvent *evt) 373 | { 374 | XKeyEvent *e = (XKeyEvent*)evt; 375 | 376 | if(e->type == KeyRelease && e->keycode == hotkey_code && 377 | ((e->state & hotkey_mods) || !hotkey_mods)){ 378 | raise_and_focus(wshell); 379 | } 380 | } 381 | 382 | /* 383 | * Raise and focus the given shell widget. 384 | */ 385 | void raise_and_focus(Widget w) 386 | { 387 | static Atom XaNET_ACTIVE_WINDOW=None; 388 | static Atom XaWM_STATE=None; 389 | static Atom XaWM_CHANGE_STATE=None; 390 | Atom ret_type; 391 | int ret_fmt; 392 | unsigned long ret_items; 393 | unsigned long ret_bytes; 394 | uint32_t *state=NULL; 395 | XClientMessageEvent evt; 396 | Display *dpy = XtDisplay(wshell); 397 | 398 | if(XaWM_STATE==None){ 399 | XaWM_STATE=XInternAtom(dpy,"WM_STATE",True); 400 | XaWM_CHANGE_STATE=XInternAtom(dpy,"WM_CHANGE_STATE",True); 401 | XaNET_ACTIVE_WINDOW=XInternAtom(dpy,"_NET_ACTIVE_WINDOW",True); 402 | } 403 | 404 | if(XaWM_STATE==None) return; 405 | 406 | if(XGetWindowProperty(dpy,XtWindow(w),XaWM_STATE,0,1, 407 | False,XaWM_STATE,&ret_type,&ret_fmt,&ret_items, 408 | &ret_bytes,(unsigned char**)&state)!=Success) return; 409 | if(ret_type==XaWM_STATE && ret_fmt && *state==IconicState){ 410 | evt.type=ClientMessage; 411 | evt.send_event=True; 412 | evt.message_type=XaWM_CHANGE_STATE; 413 | evt.display=dpy; 414 | evt.window=XtWindow(w); 415 | evt.format=32; 416 | evt.data.l[0]=NormalState; 417 | XSendEvent(dpy,XDefaultRootWindow(dpy),True, 418 | SubstructureNotifyMask|SubstructureRedirectMask, 419 | (XEvent*)&evt); 420 | }else{ 421 | if(XaNET_ACTIVE_WINDOW){ 422 | evt.type=ClientMessage, 423 | evt.send_event=True; 424 | evt.serial=0; 425 | evt.display=dpy; 426 | evt.window=XtWindow(w); 427 | evt.message_type=XaNET_ACTIVE_WINDOW; 428 | evt.format=32; 429 | 430 | XSendEvent(dpy,XDefaultRootWindow(dpy),False, 431 | SubstructureNotifyMask|SubstructureRedirectMask,(XEvent*)&evt); 432 | }else{ 433 | XRaiseWindow(dpy,XtWindow(w)); 434 | XSync(dpy,False); 435 | XSetInputFocus(dpy,XtWindow(w),RevertToParent,CurrentTime); 436 | } 437 | } 438 | XFree((char*)state); 439 | } 440 | 441 | /* 442 | * Build menu structure from the rc file 443 | */ 444 | static Boolean construct_menu(void) 445 | { 446 | Widget *wlevel; 447 | unsigned int nlevels=1; 448 | struct tb_entry *entries, *cur; 449 | int err; 450 | 451 | if((err=tb_parse_config(rc_file_path,&entries))){ 452 | report_rcfile_error(rc_file_path, 453 | tb_parser_error_string()?tb_parser_error_string():strerror(err)); 454 | return False; 455 | } 456 | 457 | if(!entries){ 458 | report_rcfile_error(rc_file_path, 459 | "File doesn't seem to contain any entries."); 460 | return False; 461 | } 462 | 463 | cur=entries; 464 | 465 | while(cur){ 466 | nlevels=(cur->level > nlevels)?cur->level:nlevels; 467 | cur=cur->next; 468 | } 469 | 470 | if(wmenu){ 471 | XtUnmanageChild(wmenu); 472 | XtDestroyWidget(wmenu); 473 | } 474 | 475 | wmenu=XmVaCreateManagedRowColumn(wmain,"menu", 476 | XmNshadowThickness, 0, 477 | XmNspacing, 1, 478 | XmNmarginWidth, 0, 479 | XmNorientation, (app_res.horizontal ? XmHORIZONTAL:XmVERTICAL), 480 | XmNrowColumnType, XmMENU_BAR, 481 | XmNpacking, (app_res.horizontal ? XmPACK_TIGHT:XmPACK_COLUMN), 482 | XmNpositionIndex, 0, 483 | NULL); 484 | 485 | #ifdef DEBUG_MENU 486 | printf("Max %d cascade levels\n",nlevels); 487 | #endif 488 | 489 | wlevel=calloc(nlevels+1,sizeof(Widget)); 490 | if(!wlevel){ 491 | perror("malloc"); 492 | return False; 493 | } 494 | 495 | cur=entries; 496 | wlevel[0]=wmenu; 497 | 498 | while(cur){ 499 | Widget w; 500 | XmString title; 501 | Arg args[10]; 502 | int n; 503 | 504 | if(cur->type == TBE_CASCADE && cur->next){ 505 | Widget new_pulldown, new_cascade; 506 | 507 | #ifdef DEBUG_MENU 508 | printf("Adding Cascade: %s; Level: %d\n",cur->title,cur->level); 509 | #endif 510 | new_pulldown=XmCreatePulldownMenu( 511 | wlevel[cur->level],"commandPulldown",NULL,0); 512 | 513 | title=XmStringCreateLocalized(cur->title); 514 | n=0; 515 | XtSetArg(args[n],XmNlabelString,title); n++; 516 | XtSetArg(args[n],XmNmnemonic,(KeySym)cur->mnemonic); n++; 517 | XtSetArg(args[n],XmNsubMenuId,new_pulldown); n++; 518 | new_cascade=XmCreateCascadeButtonGadget( 519 | wlevel[cur->level],"cascadeButton",args,n); 520 | XmStringFree(title); 521 | 522 | wlevel[cur->next->level]=new_pulldown; 523 | 524 | XtManageChild(new_cascade); 525 | 526 | }else if(cur->type == TBE_COMMAND){ 527 | XtCallbackRec push_callback[]={ 528 | {(XtCallbackProc)menu_command_cb,(XtPointer)cur->command}, 529 | {(XtCallbackProc)NULL,(XtPointer)NULL} 530 | }; 531 | #ifdef DEBUG_MENU 532 | printf("Adding Command: %s; Level: %d\n",cur->title,cur->level); 533 | #endif 534 | title=XmStringCreateLocalized(cur->title); 535 | n=0; 536 | XtSetArg(args[n],XmNlabelString,title); n++; 537 | if(cur->mnemonic){ 538 | XtSetArg(args[n],XmNmnemonic,(KeySym)cur->mnemonic); 539 | n++; 540 | } 541 | XtSetArg(args[n],XmNactivateCallback,push_callback); n++; 542 | w=XmCreatePushButtonGadget(wlevel[cur->level], 543 | "menuButton",args,n); 544 | XmStringFree(title); 545 | XtManageChild(w); 546 | }else if(cur->type == TBE_SEPARATOR){ 547 | w=XmCreateSeparatorGadget(wlevel[cur->level],"separator",NULL,0); 548 | XtManageChild(w); 549 | } 550 | cur=cur->next; 551 | } 552 | 553 | free(wlevel); 554 | 555 | return True; 556 | } 557 | 558 | static void report_rcfile_error(const char *rc_file, const char *err_desc) 559 | { 560 | char *buffer; 561 | char err_msg[]="Error while parsing RC file:"; 562 | size_t msg_len; 563 | 564 | msg_len=strlen(err_msg)+strlen(err_desc)+strlen(rc_file)+10; 565 | buffer=malloc(msg_len); 566 | if(!buffer){ 567 | perror("malloc"); 568 | return; 569 | } 570 | 571 | sprintf(buffer,"%s %s\n%s.",err_msg,rc_file,err_desc); 572 | message_dialog(False,buffer); 573 | free(buffer); 574 | } 575 | 576 | static void create_utility_widgets(Widget wparent) 577 | { 578 | Widget wmenu; 579 | Widget wpulldown; 580 | Widget wcascade; 581 | Widget w; 582 | XmString title; 583 | Arg args[10]; 584 | int n; 585 | 586 | XtSetArg(args[0], XmNorientation, 587 | (app_res.horizontal ? XmVERTICAL:XmHORIZONTAL)); 588 | w = XmCreateSeparatorGadget(wparent, "separator", args, 1); 589 | if(app_res.separators) XtManageChild(w); 590 | 591 | /* 'Session' menu */ 592 | wmenu=XmVaCreateManagedRowColumn(wparent, "menu", 593 | XmNshadowThickness, 0, 594 | XmNspacing, 1, 595 | XmNmarginWidth, 0, 596 | XmNorientation, (app_res.horizontal ? XmHORIZONTAL:XmVERTICAL), 597 | XmNrowColumnType, XmMENU_BAR, NULL); 598 | 599 | wpulldown=XmCreatePulldownMenu(wmenu,"sessionPulldown",NULL,0); 600 | 601 | title=XmStringCreateLocalized("Session"); 602 | n=0; 603 | XtSetArg(args[n],XmNlabelString,title); n++; 604 | XtSetArg(args[n],XmNmnemonic,(KeySym)'S'); n++; 605 | XtSetArg(args[n],XmNsubMenuId,wpulldown); n++; 606 | wcascade=XmCreateCascadeButtonGadget(wmenu,"cascadeButton",args,n); 607 | XmStringFree(title); 608 | XtManageChild(wcascade); 609 | 610 | n=0; 611 | title=XmStringCreateLocalized("Execute..."); 612 | XtSetArg(args[n],XmNlabelString,title); n++; 613 | XtSetArg(args[n],XmNmnemonic,(KeySym)'E'); n++; 614 | w=XmCreatePushButtonGadget(wpulldown,"execMenuButton",args,n); 615 | XtAddCallback(w,XmNactivateCallback,exec_cb,NULL); 616 | XmStringFree(title); 617 | XtManageChild(w); 618 | 619 | w=XmCreateSeparatorGadget(wpulldown,"separator",NULL,0); 620 | XtManageChild(w); 621 | 622 | if(xmsm_cfg & XMSM_CFG_LOCK) { 623 | n=0; 624 | title=XmStringCreateLocalized("Lock"); 625 | XtSetArg(args[n],XmNlabelString,title); n++; 626 | XtSetArg(args[n],XmNmnemonic,(KeySym)'L'); n++; 627 | w=XmCreatePushButtonGadget(wpulldown,"lockMenuButton",args,n); 628 | XtAddCallback(w,XmNactivateCallback,lock_cb,NULL); 629 | XmStringFree(title); 630 | XtManageChild(w); 631 | } 632 | 633 | n=0; 634 | title=XmStringCreateLocalized("Logout..."); 635 | XtSetArg(args[n],XmNlabelString,title); n++; 636 | XtSetArg(args[n],XmNmnemonic,(KeySym)'o'); n++; 637 | w=XmCreatePushButtonGadget(wpulldown,"logoutMenuButton",args,n); 638 | XtAddCallback(w,XmNactivateCallback,logout_cb,NULL); 639 | XmStringFree(title); 640 | XtManageChild(w); 641 | 642 | if(xmsm_cfg & XMSM_CFG_SUSPEND) { 643 | n=0; 644 | title=XmStringCreateLocalized("Suspend"); 645 | XtSetArg(args[n],XmNlabelString,title); n++; 646 | XtSetArg(args[n],XmNmnemonic,(KeySym)'S'); n++; 647 | w=XmCreatePushButtonGadget(wpulldown,"suspendMenuButton",args,n); 648 | XtAddCallback(w,XmNactivateCallback,suspend_cb,NULL); 649 | XmStringFree(title); 650 | XtManageChild(w); 651 | } 652 | 653 | /* The time-date display */ 654 | XtSetArg(args[0], XmNorientation, 655 | (app_res.horizontal ? XmVERTICAL:XmHORIZONTAL)); 656 | w = XmCreateSeparatorGadget(wparent, "separator", args, 1); 657 | 658 | n=0; 659 | XtSetArg(args[n],XmNalignment,XmALIGNMENT_CENTER); n++; 660 | XtSetArg(args[n],XmNmarginWidth,6); n++; 661 | XtSetArg(args[n],XmNmarginHeight,6); n++; 662 | wdate_time=XmCreateLabelGadget(wparent,"dateTime",args,n); 663 | if(app_res.show_date_time){ 664 | if(app_res.separators) XtManageChild(w); 665 | XtManageChild(wdate_time); 666 | time_update_cb(NULL,NULL); 667 | } 668 | 669 | } 670 | 671 | /* 672 | * Search home and system directories for the RC file. 673 | * Returns a malloc()ed full path to the RC file on success, 674 | * or NULL otherwise. 675 | */ 676 | #ifndef RCDIR 677 | #define RCDIR "/usr/local/etc/X11" 678 | #endif 679 | 680 | static char* find_rc_file(void) 681 | { 682 | char *home=NULL; 683 | char *lang=NULL; 684 | char *path; 685 | int i; 686 | char *sys_paths[32]={ 687 | RCDIR, 688 | "/etc/X11", 689 | "/usr/lib/X11", 690 | "/usr/local/lib/X11", 691 | NULL 692 | }; 693 | 694 | home=getenv("HOME"); 695 | lang=getenv("LANG"); 696 | 697 | if(!home){ 698 | fprintf(stderr,"HOME is not set!\n"); 699 | return NULL; 700 | } 701 | 702 | path=malloc(strlen(home)+ 703 | strlen(RC_NAME)+((lang)?strlen(lang):0)+36); 704 | if(!path){ 705 | perror("malloc"); 706 | return NULL; 707 | } 708 | 709 | sprintf(path,"%s/.%s",home,RC_NAME); 710 | if(!access(path,R_OK)) return path; 711 | 712 | for(i=0; sys_paths[i]!=NULL; i++){ 713 | if(lang){ 714 | sprintf(path,"%s/%s/%s",sys_paths[i],lang,RC_NAME); 715 | if(!access(path,R_OK)) return path; 716 | } 717 | sprintf(path,"%s/%s",sys_paths[i],RC_NAME); 718 | if(!access(path,R_OK)) return path; 719 | } 720 | 721 | free(path); 722 | return NULL; 723 | } 724 | 725 | /* 726 | * Disable/Enable widget sensitivity and set pointer according 727 | * to the given wait state. 728 | */ 729 | static void wait_state(Boolean enter_wait) 730 | { 731 | static Cursor watch_cursor=None; 732 | Display *dpy=XtDisplay(wshell); 733 | 734 | if(watch_cursor==None) 735 | watch_cursor=XCreateFontCursor(dpy,XC_watch); 736 | 737 | if(enter_wait){ 738 | XtSetSensitive(wmain,False); 739 | XDefineCursor(dpy,XtWindow(wmain),watch_cursor); 740 | }else{ 741 | XtSetSensitive(wmain,True); 742 | XUndefineCursor(dpy,XtWindow(wmain)); 743 | } 744 | while(XtAppPending(app_context) & XtIMXEvent) { 745 | XtAppProcessEvent(app_context,XtIMXEvent); 746 | XFlush(dpy); 747 | } 748 | } 749 | 750 | /* 751 | * Time display update interval function 752 | */ 753 | static void time_update_cb(XtPointer client_data, XtIntervalId *id) 754 | { 755 | char time_str[256]; 756 | time_t secs; 757 | struct tm *the_time; 758 | XmString xm_str; 759 | 760 | time(&secs); 761 | the_time=localtime(&secs); 762 | strftime(time_str,255,app_res.date_time_fmt,the_time); 763 | xm_str=XmStringCreateLocalized(time_str); 764 | XtVaSetValues(wdate_time,XmNlabelString,xm_str,NULL); 765 | XmStringFree(xm_str); 766 | 767 | XtAppAddTimeOut(app_context, 768 | 60000-(the_time->tm_sec*1000),time_update_cb,NULL); 769 | } 770 | 771 | /* 772 | * SIGUSR1 RC file reload request handler 773 | */ 774 | static void xt_sigusr1_handler(XtPointer client_data, XtSignalId *id) 775 | { 776 | wait_state(True); 777 | /* on parse error, the previous configuration remains active 778 | * and the user is informed about */ 779 | construct_menu(); 780 | wait_state(False); 781 | 782 | } 783 | 784 | /* 785 | * Fetches a string from an input dialog. 786 | * Returns heap allocated string, or NULL if user cancels the dialog. 787 | */ 788 | static char* get_user_input(Widget wshell, const char *prompt_str) 789 | { 790 | static Widget wdlg = None; 791 | static Widget wtext = None; 792 | XmString xm_prompt_str; 793 | Arg args[5]; 794 | int n=0; 795 | char *result_str=NULL; 796 | 797 | if(wdlg == None){ 798 | XmString xm_title; 799 | XtCallbackRec callback[]={ 800 | {(XtCallbackProc)user_input_cb,(XtPointer)&result_str}, 801 | {(XtCallbackProc)NULL,(XtPointer)NULL} 802 | }; 803 | 804 | n=0; 805 | xm_title=XmStringCreateLocalized(APP_TITLE); 806 | XtSetArg(args[n],XmNdialogTitle,xm_title); n++; 807 | XtSetArg(args[n],XmNokCallback,callback); n++; 808 | XtSetArg(args[n],XmNcancelCallback,callback); n++; 809 | wdlg = XmCreatePromptDialog(wshell, "promptDialog", args, n); 810 | XmStringFree(xm_title); 811 | wtext = XmSelectionBoxGetChild(wdlg, XmDIALOG_TEXT); 812 | XtUnmanageChild(XmSelectionBoxGetChild(wdlg, XmDIALOG_HELP_BUTTON)); 813 | } else { 814 | char *text; 815 | size_t len; 816 | 817 | text = XmTextFieldGetString(wtext); 818 | if( (len = strlen(text)) ) { 819 | XmTextFieldSetSelection(wtext, 0, len, 820 | XtLastTimestampProcessed(XtDisplay(wtext))); 821 | } 822 | XtFree(text); 823 | } 824 | xm_prompt_str=XmStringCreateLocalized((char*)prompt_str); 825 | 826 | n = 0; 827 | XtSetArg(args[n], XmNselectionLabelString, xm_prompt_str); n++; 828 | XtSetValues(wdlg, args, n); 829 | XmStringFree(xm_prompt_str); 830 | XtManageChild(wdlg); 831 | 832 | while(!result_str){ 833 | XtAppProcessEvent(app_context,XtIMAll); 834 | } 835 | 836 | return (result_str[0]=='\0')?NULL:result_str; 837 | } 838 | 839 | /* 840 | * get_user_input dialog callback 841 | */ 842 | static void user_input_cb(Widget w, XtPointer client_data, XtPointer call_data) 843 | { 844 | XmSelectionBoxCallbackStruct *cbs= 845 | (XmSelectionBoxCallbackStruct*)call_data; 846 | char **result=(char**)client_data; 847 | 848 | if(cbs->reason==XmCR_CANCEL) 849 | *result="\0"; 850 | else 851 | *result=(char*)XmStringUnparse(cbs->value,NULL,0, 852 | XmCHARSET_TEXT,NULL,0,XmOUTPUT_ALL); 853 | } 854 | 855 | /* 856 | * Displays a blocking message dialog. If 'confirm' is True, the dialog will 857 | * have OK+Cancel buttons, OK only otherwise. Returns True if OK was chosen. 858 | */ 859 | static Boolean message_dialog(Boolean confirm, const char *message_str) 860 | { 861 | Widget wdlg; 862 | XmString xm_message_str; 863 | Arg args[8]; 864 | int n=0; 865 | int result=(-1); 866 | XmString xm_title; 867 | XtCallbackRec callback[]={ 868 | {(XtCallbackProc)message_dialog_cb,(XtPointer)&result}, 869 | {(XtCallbackProc)NULL,(XtPointer)NULL} 870 | }; 871 | 872 | xm_message_str=XmStringCreateLocalized((char*)message_str); 873 | xm_title=XmStringCreateLocalized(APP_TITLE); 874 | 875 | wdlg = XmCreateMessageDialog(wshell, "messageDialog", NULL, 0); 876 | 877 | n = 0; 878 | XtSetArg(args[n],XmNdialogTitle,xm_title); n++; 879 | XtSetArg(args[n],XmNokCallback,callback); n++; 880 | XtSetArg(args[n],XmNcancelCallback,callback); n++; 881 | XtSetArg(args[n],XmNdialogType, 882 | confirm?XmDIALOG_QUESTION:XmDIALOG_INFORMATION); n++; 883 | XtSetArg(args[n],XmNdefaultButtonType, 884 | confirm?XmDIALOG_CANCEL_BUTTON:XmDIALOG_OK_BUTTON); n++; 885 | XtSetArg(args[n],XmNmessageString,xm_message_str); n++; 886 | 887 | XtSetValues(wdlg, args, n); 888 | 889 | XmStringFree(xm_title); 890 | XmStringFree(xm_message_str); 891 | 892 | if(!confirm) XtUnmanageChild( 893 | XmMessageBoxGetChild(wdlg,XmDIALOG_CANCEL_BUTTON)); 894 | XtUnmanageChild(XmMessageBoxGetChild(wdlg, XmDIALOG_HELP_BUTTON)); 895 | 896 | XtManageChild(wdlg); 897 | 898 | while(result == (-1)){ 899 | XtAppProcessEvent(app_context,XtIMAll); 900 | } 901 | 902 | XtDestroyWidget(wdlg); 903 | return (Boolean)result; 904 | } 905 | 906 | /* 907 | * message_dialog dialog callback 908 | */ 909 | static void message_dialog_cb(Widget w, XtPointer client_data, 910 | XtPointer call_data) 911 | { 912 | XmSelectionBoxCallbackStruct *cbs= 913 | (XmSelectionBoxCallbackStruct*)call_data; 914 | char *result=(Boolean*)client_data; 915 | 916 | if(cbs->reason==XmCR_OK) 917 | *result=1; 918 | else 919 | *result=0; 920 | } 921 | 922 | static int exec_command(const char *cmd_spec) 923 | { 924 | pid_t pid; 925 | char *str; 926 | char *p,*t; 927 | char pc=0; 928 | int done=0; 929 | char **argv=NULL; 930 | size_t argv_size=0; 931 | unsigned int argc=0; 932 | volatile int errval=0; 933 | 934 | str = strdup(cmd_spec); 935 | 936 | p=str; 937 | t=NULL; 938 | 939 | /* split the command string into separate arguments */ 940 | while(!done){ 941 | if(!t){ 942 | while(*p && isblank(*p)) p++; 943 | if(*p == '\0') break; 944 | t = p; 945 | } 946 | 947 | if(*p == '\"' || *p == '\''){ 948 | if(pc == '\\'){ 949 | /* literal " or ' */ 950 | memmove(p - 1, p, strlen(p) + 1); 951 | }else{ 952 | /* quotation marks, remove them ignoring blanks within */ 953 | memmove(p, p + 1, strlen(p)); 954 | while(*p != '\"' && *p != '\''){ 955 | if(*p == '\0'){ 956 | if(argv) free(argv); 957 | return EINVAL; 958 | } 959 | p++; 960 | } 961 | memmove(p, p + 1, strlen(p)); 962 | } 963 | } 964 | if(isblank(*p) || *p == '\0'){ 965 | if(*p == '\0') done = 1; 966 | if(argv_size < argc+1){ 967 | char **new_ptr; 968 | new_ptr = realloc(argv, (argv_size += 64) * sizeof(char*)); 969 | if(!new_ptr){ 970 | free(str); 971 | if(argv) free(argv); 972 | return ENOMEM; 973 | } 974 | argv=new_ptr; 975 | } 976 | *p = '\0'; 977 | argv[argc] = t; 978 | 979 | #ifdef DEBUG_EXEC 980 | printf("argv[%d]: %s\n",argc,argv[argc]); 981 | #endif 982 | 983 | t=NULL; 984 | argc++; 985 | } 986 | pc = *p; 987 | p++; 988 | } 989 | 990 | if(!argc) return EINVAL; 991 | argv[argc]=NULL; 992 | 993 | pid=vfork(); 994 | if(pid == 0){ 995 | setsid(); 996 | 997 | if(execvp(argv[0],argv) == (-1)) 998 | errval=errno; 999 | _exit(0); 1000 | }else if(pid == -1){ 1001 | errval=errno; 1002 | } 1003 | 1004 | free(str); 1005 | free(argv); 1006 | return errval; 1007 | } 1008 | 1009 | /* 1010 | * Display a message dialog containing the failed command name and 1011 | * the system error string. 1012 | */ 1013 | static void report_exec_error(const char *err_msg, 1014 | const char *command, int errno_value) 1015 | { 1016 | char *errno_str=strerror(errno_value); 1017 | char *buffer; 1018 | 1019 | buffer=malloc(strlen(err_msg)+strlen(command)+strlen(errno_str)+10); 1020 | if(!err_msg){ 1021 | perror("malloc"); 1022 | return; 1023 | } 1024 | sprintf(buffer,"%s \'%s\'.\n%s.",err_msg,command,errno_str); 1025 | message_dialog(False,buffer); 1026 | free(buffer); 1027 | } 1028 | 1029 | static void menu_command_cb(Widget w, 1030 | XtPointer client_data, XtPointer call_data) 1031 | { 1032 | int errval; 1033 | char *cmd = (char*) client_data; 1034 | char *exp_cmd; 1035 | 1036 | errval = expand_env_vars(cmd, &exp_cmd); 1037 | if(errval) { 1038 | report_exec_error("Failed to parse command string", cmd, errval); 1039 | return; 1040 | } 1041 | 1042 | if((errval = exec_command(exp_cmd))) 1043 | report_exec_error("Error executing command", exp_cmd, errval); 1044 | 1045 | free(exp_cmd); 1046 | } 1047 | 1048 | /* 1049 | * Sends a command message to XmSm. Returns True on success. 1050 | */ 1051 | static Boolean send_xmsm_cmd(const char *command) 1052 | { 1053 | Display *dpy = XtDisplay(wshell); 1054 | Window root = DefaultRootWindow(dpy); 1055 | Window shell; 1056 | Atom ret_type; 1057 | int ret_format; 1058 | unsigned long ret_items; 1059 | unsigned long left_items; 1060 | unsigned char *prop_data; 1061 | XTextProperty text_prop = { 1062 | (unsigned char*)command, XA_STRING, 8, strlen(command) 1063 | }; 1064 | 1065 | if(xa_xmsm_mgr == None || xa_xmsm_pid == None) return False; 1066 | 1067 | XGetWindowProperty(dpy,root,xa_xmsm_mgr,0,sizeof(Window),False,XA_WINDOW, 1068 | &ret_type,&ret_format,&ret_items,&left_items,&prop_data); 1069 | 1070 | if(ret_type == XA_WINDOW){ 1071 | shell = *((Window*)prop_data); 1072 | XFree(prop_data); 1073 | 1074 | def_x_err_handler = XSetErrorHandler(local_x_err_handler); 1075 | 1076 | XGetWindowProperty(dpy, shell, xa_xmsm_pid, 0, sizeof(pid_t), 1077 | False, XA_INTEGER, &ret_type, &ret_format, 1078 | &ret_items, &left_items, &prop_data); 1079 | if(ret_items) XFree(prop_data); 1080 | 1081 | if(ret_type == XA_INTEGER){ 1082 | XSetTextProperty(dpy, shell, &text_prop, xa_xmsm_cmd); 1083 | XSync(dpy, False); 1084 | 1085 | XSetErrorHandler(def_x_err_handler); 1086 | 1087 | return True; 1088 | } 1089 | XSync(dpy, False); 1090 | XSetErrorHandler(def_x_err_handler); 1091 | } 1092 | return False; 1093 | } 1094 | 1095 | /* 1096 | * Retrieves xmsm configuration state. Returns True on success. 1097 | */ 1098 | static Boolean get_xmsm_config(unsigned long *flags) 1099 | { 1100 | Display *dpy = XtDisplay(wshell); 1101 | Window root = DefaultRootWindow(dpy); 1102 | Atom ret_type; 1103 | int ret_format; 1104 | unsigned long ret_items; 1105 | unsigned long left_items; 1106 | unsigned char *prop_data; 1107 | 1108 | if(xa_xmsm_cfg == None) return False; 1109 | 1110 | XGetWindowProperty(dpy, root, xa_xmsm_cfg, 0, sizeof(unsigned long), 1111 | False, XA_INTEGER, &ret_type, &ret_format, &ret_items, 1112 | &left_items, &prop_data); 1113 | if(ret_items) { 1114 | *flags = *prop_data; 1115 | XFree(prop_data); 1116 | return True; 1117 | } 1118 | return False; 1119 | } 1120 | 1121 | /* 1122 | * This is temporarily set in send_xmsm_pid just to catch BadWindow errors 1123 | * originating from a window handle stored on root in MGR_ATOM_NAME by 1124 | * xmsm process that is no longer active 1125 | */ 1126 | static int local_x_err_handler(Display *dpy, XErrorEvent *evt) 1127 | { 1128 | if(evt->error_code == BadWindow) return 0; 1129 | return def_x_err_handler(dpy,evt); 1130 | } 1131 | 1132 | static void exec_cb(Widget w, XtPointer client_data, XtPointer call_data) 1133 | { 1134 | char *command; 1135 | char *exp_cmd; 1136 | int errval; 1137 | 1138 | command = get_user_input(wshell,"Command to execute"); 1139 | if(!command) return; 1140 | 1141 | errval = expand_env_vars(command, &exp_cmd); 1142 | if(errval) { 1143 | report_exec_error("Failed to parse command string", command, errval); 1144 | free(command); 1145 | return; 1146 | } 1147 | 1148 | if((errval = exec_command(exp_cmd))) 1149 | report_exec_error("Error executing command", exp_cmd, errval); 1150 | 1151 | free(command); 1152 | free(exp_cmd); 1153 | } 1154 | 1155 | static void suspend_cb(Widget w, XtPointer client_data, XtPointer call_data) 1156 | { 1157 | if(!send_xmsm_cmd(XMSM_SUSPEND_CMD)){ 1158 | message_dialog(False, xmsm_cmd_err); 1159 | } 1160 | } 1161 | 1162 | static void lock_cb(Widget w, XtPointer client_data, XtPointer call_data) 1163 | { 1164 | if(!send_xmsm_cmd(XMSM_LOCK_CMD)){ 1165 | message_dialog(False, xmsm_cmd_err); 1166 | } 1167 | } 1168 | 1169 | static void logout_cb(Widget w, XtPointer client_data, XtPointer call_data) 1170 | { 1171 | if(!send_xmsm_cmd(XMSM_LOGOUT_CMD)){ 1172 | message_dialog(False, xmsm_cmd_err); 1173 | } 1174 | } 1175 | 1176 | static void sigchld_handler(int sig) 1177 | { 1178 | int status; 1179 | waitpid(-1, &status, WNOHANG); 1180 | } 1181 | 1182 | static void sigusr_handler(int sig) 1183 | { 1184 | if(sig == SIGUSR1) XtNoticeSignal(xt_sigusr1); 1185 | } 1186 | -------------------------------------------------------------------------------- /src/tbparse.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 alx@fastestcode.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | /* 24 | * Toolbox configuration file parser 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include "tbparse.h" 34 | 35 | static char* get_line(void); 36 | static char* skip_blanks(char *p); 37 | static void parse_line(char *line, struct tb_entry *e); 38 | static void set_parse_error(int line, const char *text); 39 | static struct tb_entry* add_entry(const struct tb_entry *ent); 40 | static int parse_buffer(void); 41 | 42 | #define MAX_PARSE_ERROR 256 43 | static char parse_error[MAX_PARSE_ERROR]; 44 | 45 | static char *buffer=NULL; 46 | static char *buf_ptr=NULL; 47 | size_t buf_size=0; 48 | struct tb_entry *entries=NULL; 49 | 50 | /* Get next line from global buffer */ 51 | static char* get_line(void) 52 | { 53 | char *p, *cur=buf_ptr; 54 | 55 | if(*buf_ptr == '\0') return NULL; 56 | 57 | p=strchr(buf_ptr,'\n'); 58 | 59 | if(p){ 60 | buf_ptr = p+1; 61 | *p = '\0'; 62 | }else{ 63 | while(*buf_ptr != '\0') buf_ptr++; 64 | } 65 | return cur; 66 | } 67 | 68 | static char* skip_blanks(char *p) 69 | { 70 | while(*p == '\t' || *p == ' ') p++; 71 | return p; 72 | } 73 | 74 | /* Parses a line into the given entry structure */ 75 | static void parse_line(char *line, struct tb_entry *e) 76 | { 77 | char *p=line; 78 | 79 | memset(e,0,sizeof(struct tb_entry)); 80 | 81 | if(!strcmp(line,"SEPARATOR")){ 82 | e->type=TBE_SEPARATOR; 83 | return; 84 | } 85 | 86 | e->title=line; 87 | 88 | while(*p != '\0'){ 89 | if(*p == '\\' && (p[1] == '\\' || p[1] == '&' || p[1] == ':')){ 90 | memmove(p,p+1,strlen(p+1)+1); 91 | }else if(*p == '&'){ 92 | e->mnemonic=p[1]; 93 | memmove(p,p+1,strlen(p+1)+1); 94 | }else if(*p == ':'){ 95 | e->command=skip_blanks(p+1); 96 | *p='\0'; 97 | break; 98 | } 99 | p++; 100 | } 101 | if(e->command) 102 | e->type=TBE_COMMAND; 103 | else 104 | e->type=TBE_CASCADE; 105 | } 106 | 107 | static void set_parse_error(int line, const char *text) 108 | { 109 | snprintf(parse_error,MAX_PARSE_ERROR,"Line %d: %s",line,text); 110 | } 111 | 112 | /* Duplicates the given entry and adds it to the global list */ 113 | static struct tb_entry* add_entry(const struct tb_entry *ent) 114 | { 115 | static struct tb_entry *last; 116 | struct tb_entry *new; 117 | 118 | new=malloc(sizeof(struct tb_entry)); 119 | if(!new) return NULL; 120 | memcpy(new,ent,sizeof(struct tb_entry)); 121 | 122 | if(!entries){ 123 | entries=new; 124 | last=new; 125 | }else{ 126 | last->next=new; 127 | last=new; 128 | } 129 | return new; 130 | } 131 | 132 | /* Parses the global buffer */ 133 | static int parse_buffer(void) 134 | { 135 | char *line; 136 | struct tb_entry tmp; 137 | struct tb_entry *prev=NULL; 138 | int nlevel=0; 139 | int iline=0; 140 | 141 | while((line = get_line())){ 142 | iline++; 143 | line=skip_blanks(line); 144 | 145 | if(*line == '\0' || *line == '#'){ 146 | continue; 147 | }else if(*line == '{'){ 148 | if(!prev || prev->type != TBE_CASCADE){ 149 | set_parse_error(iline, 150 | "Delimiter \'{\' must follow a cascade entry"); 151 | return -1; 152 | } 153 | nlevel++; 154 | continue; 155 | }else if(*line == '}'){ 156 | if(!nlevel || prev->type != TBE_COMMAND){ 157 | set_parse_error(iline,"Delimiter \'}\' out of scope"); 158 | return -1; 159 | } 160 | nlevel--; 161 | continue; 162 | }else if(prev && prev->type == TBE_CASCADE && prev->level == nlevel){ 163 | set_parse_error(iline,"Cascade entry must have a menu scope"); 164 | return -1; 165 | } 166 | 167 | parse_line(line,&tmp); 168 | if(tmp.type == TBE_COMMAND) { 169 | if(nlevel < 1){ 170 | set_parse_error(iline, 171 | "Command entries must reside within a menu scope"); 172 | return -1; 173 | } 174 | if(!strlen(tmp.command)) { 175 | set_parse_error(iline, 176 | "Command string expected after ':' "); 177 | return -1; 178 | } 179 | } 180 | 181 | tmp.level=nlevel; 182 | if((prev=add_entry(&tmp))==NULL) return ENOMEM; 183 | } 184 | return 0; 185 | } 186 | 187 | 188 | /* 189 | * Parses a toolbox menu file. 190 | * Returns zero on success or errno otherwise. 191 | * If EINVAL is returned, the file contains syntax errors, and 192 | * TbParseErrorString() may be used to obtain detailed information. 193 | */ 194 | int tb_parse_config(const char *filename, struct tb_entry **ent_root) 195 | { 196 | FILE *file; 197 | struct stat st; 198 | int err; 199 | 200 | parse_error[0]='\0'; 201 | 202 | if(stat(filename,&st)<0) return errno; 203 | if(st.st_size == 0) return 0; 204 | 205 | if(entries){ 206 | struct tb_entry *tmp, *cur=entries; 207 | while(cur){ 208 | tmp=cur; 209 | cur=cur->next; 210 | free(tmp); 211 | } 212 | entries=NULL; 213 | } 214 | 215 | if(buf_size < st.st_size){ 216 | char *p; 217 | if(!(p=realloc(buffer,st.st_size+1))) return errno; 218 | buffer=p; 219 | } 220 | buf_ptr=buffer; 221 | 222 | file=fopen(filename,"r"); 223 | if(!file){ 224 | err=errno; 225 | free(buffer); 226 | return err; 227 | } 228 | 229 | if(fread(buffer,1,st.st_size,file) "$LOGFILE" 2>&1 16 | 17 | if [ -f "$XPROFILE" ]; then 18 | echo "Sourcing $XPROFILE" 19 | . "$XPROFILE" 20 | fi 21 | 22 | if [ -f "$XDEFAULTS" ]; then 23 | echo "Merging $XDEFAULTS" 24 | xrdb -merge "$XDEFAULTS" 25 | fi 26 | 27 | if [ -f "$XRESOURCES" ]; then 28 | echo "Merging $XRESOURCES" 29 | xrdb -merge "$XRESOURCES" 30 | fi 31 | 32 | echo "Starting the session manager" 33 | exec PREFIX/bin/xmsm 34 | -------------------------------------------------------------------------------- /src/xmsm.1: -------------------------------------------------------------------------------- 1 | .\" Copyright (C) 2018-2024 alx@fastestcode.org 2 | .\" 3 | .\" Permission is hereby granted, free of charge, to any person obtaining a 4 | .\" copy of this software and associated documentation files (the "Software"), 5 | .\" to deal in the Software without restriction, including without limitation 6 | .\" the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | .\" and/or sell copies of the Software, and to permit persons to whom the 8 | .\" Software is furnished to do so, subject to the following conditions: 9 | .\" 10 | .\" The above copyright notice and this permission notice shall be included in 11 | .\" all copies or substantial portions of the Software. 12 | .\" 13 | .\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | .\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | .\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | .\" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | .\" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | .\" FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | .\" DEALINGS IN THE SOFTWARE. 20 | .TH XMSM 1 21 | .SH NAME 22 | xmsm/xmsession \- X session manager 23 | .SH SYNOPSIS 24 | xmsm [options] 25 | .br 26 | xinit xmsession 27 | .br 28 | xdm \-session xmsession 29 | .SH DESCRIPTION 30 | XmSm is a Motif based session manager for EMWM. In provides screen locking, 31 | workspace configuration settings and shutdown options\. It is also responsible 32 | for starting the window manager (emwm), the application launcher (xmtoolbox) 33 | and other, user defined applications\. 34 | .SH USAGE 35 | XmSm is normally started by the accompanying xmsession initialization 36 | script that sets up the environment, redirects stdout and stderr to 37 | ~/.xmsession\.log, loads ~/\.Xresources and sources ~/\.Xprofile before running 38 | the session manager\. 39 | .PP 40 | The xmsession script itself should be invoked either by a display/login manager 41 | like xdm, or the xinit utility. 42 | .PP 43 | ~/.Xprofile is the startup file for X sessions and analogous to the ~/\.profile 44 | file where user specific environment variables may be set\. 45 | .PP 46 | ~/.sessionetc is a shell script that is processed in a separate shell, 47 | and may be used to start user\-specific programs\. 48 | .SH RESOURCES 49 | .TP 50 | \fBblankOnLock\fP \fIBoolean\fP 51 | Immediately activate X screen saver in blanking mode when 52 | screen lock is user engaged. Default is \fBTrue\fp\. 53 | .TP 54 | \fBblankTimeout\fP \fISeconds\fP 55 | Period of inactivity after which X screen saver is activated in 56 | blanking mode. Set to 0 to disable blanking. 57 | Default is 480 seconds\. See also: \fBlockTimeout\fP\. 58 | .TP 59 | \fBenableLocking\fP \fIBoolean\fP 60 | Enable screen locking capability\. 61 | This affects \fBlockTimeout\fP and the \fISession\fP sub-menu in xmtoolbox(1)\. 62 | Default is \fBTrue\fp\. 63 | .TP 64 | \fBenableShade\fP \fIBoolean\fP 65 | Enable shading of workspace contents when the Logout dialog is displayed\. 66 | Default is \fBTrue\fp\. 67 | .TP 68 | \fBenableSuspend\fP \fIBoolean\fP 69 | If True, enables the \fISuspend\fP command. 70 | This affects the \fISession\fP sub-menu in xmtoolbox(1)\. 71 | Default is \fITrue\fP\. 72 | .TP 73 | \fBlauncher\fP \fIString\fP 74 | Full path to the Launcher executable name\. 75 | Default is (installation prefix)/xmtoolbox 76 | .TP 77 | \fBlockBackgroundColor\fP \fIColor\fP 78 | Background color of the screen lock\. 79 | .TP 80 | \fBlockBackgroundImage\fP \fIFile Name\fP 81 | An image to be used as a tiled background on the lock screen\. 82 | Also see the \fBIMAGE FILES\fP section\. 83 | .TP 84 | \fBlockTimeout\fP \fISeconds\fP 85 | Period of inactivity after which screen lock is engaged\. 86 | Set to 0 to disable timed locking\. 87 | Default is 600 seconds\. See also: \fBblankTimeout\fP, fBenableLocking\fP\. 88 | .TP 89 | \fBlockOnSuspend\fP \fIBoolean\fP 90 | If True, locks the screen before executing the suspend command\. 91 | Default is \fIFalse\fP\. 92 | .TP 93 | \fBnumLockState\fP ON|OFF|KEEP 94 | Set the keyboard NumLock state\. Default is \fBKEEP\fP. 95 | .TP 96 | \fBunlockScreenTimeout\fP \fISeconds\fP 97 | Period on inactivity after which the password input dialog on the lock 98 | screen is hidden. It appears again as soon as user activity is detected\. 99 | .TP 100 | \fBprimaryXineramaScreen\fP \fIInteger\fP 101 | Index of a xinerama screen to be used for input and message dialogs\. 102 | .TP 103 | \fBshowShutdown\fP \fIBoolean\fP 104 | Show the Shutdown option in the exit options dialog. Default is \fBTrue\fP\. 105 | .TP 106 | \fBshowReboot\fP \fIBoolean\fP 107 | Show the Reboot option in the exit options dialog. Default is \fBTrue\fP\. 108 | .TP 109 | \fBsilent\fP \fIBoolean\fP 110 | If set to True, xmsm wont give any audible feedback. Default is False\. 111 | .TP 112 | \fBworkspaceBackgroundColor\fP \fIColor\fP 113 | Background color of the root window\. 114 | .TP 115 | \fBworkspaceBackgroundImage\fP \fIFile Name\fP 116 | An image to be used as tiled background on the root window\. 117 | Also see the \fBIMAGE FILES\fP section\. 118 | .TP 119 | \fBwindowManager\fP \fIString\fP 120 | Full path to the window manager executable name\. 121 | Default is (installation prefix)/emwm 122 | .SH IMAGE FILE FORMATS 123 | Any image file formats supported by the Motif widget toolkit may be specified\. 124 | At the time of writing these include X pixmap/bitmap, PNG and JPEG images\. 125 | .PP 126 | X Bitmaps are drawn by coloring white bits in the specified background color 127 | (workspace/lockBackgroundColor resources) and black bits in a darker shade 128 | of that color\. 129 | .SH FILES 130 | .nf 131 | ~/\.sessionetc 132 | ~/\.xmsession\.log 133 | ~/\.Xprofile 134 | ~/\.Xresources 135 | .fi 136 | .SH SIGNALS 137 | .PP 138 | xmsm responds to SIGUSR1 by engaging screen lock\. 139 | .SH CAVEATS 140 | The xmsm binary must be setuid root for screen locking and shutdown/reboot 141 | functionality, it discards root privileges at startup and restores them only 142 | during password check and when executing system commands\. 143 | .PP 144 | XmSm does not implement the ICCCM session save/restore protocol. 145 | .PP 146 | The X screen saver extension is used for timed locking and blanking, 147 | but there are no graphical screensavers provided by XmSm\. 148 | .SH SEE ALSO 149 | emwm(1) xmtoolbox(1) 150 | .SH AUTHORS 151 | .PP 152 | XmSm was written and is maintained by alx@fastestcode\.org 153 | -------------------------------------------------------------------------------- /src/xmtoolbox.1: -------------------------------------------------------------------------------- 1 | .\" Copyright (C) 2018-2024 alx@fastestcode.org 2 | .\" Permission is hereby granted, free of charge, to any person obtaining a 3 | .\" copy of this software and associated documentation files (the "Software"), 4 | .\" to deal in the Software without restriction, including without limitation 5 | .\" the rights to use, copy, modify, merge, publish, distribute, sublicense, 6 | .\" and/or sell copies of the Software, and to permit persons to whom the 7 | .\" Software is furnished to do so, subject to the following conditions: 8 | .\" 9 | .\" The above copyright notice and this permission notice shall be included in 10 | .\" all copies or substantial portions of the Software. 11 | .\" 12 | .\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | .\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | .\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | .\" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | .\" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 17 | .\" FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | .\" DEALINGS IN THE SOFTWARE. 19 | .\" 20 | .\" 21 | .TH XMTOOLBOX 1 22 | .SH NAME 23 | xmtoolbox - application launcher 24 | .SH SYNOPSIS 25 | xmtoolbox [-rcfile ] [-horizontal] [-hotkey [modifier[+\.\.\.]]+key] 26 | .SH DESCRIPTION 27 | XmToolbox displays a user defined, multi\-level menu of application groups 28 | and applications. It also interfaces with the xmsm(1) session manager to 29 | provide screen locking and session commands. 30 | .SH OPTIONS 31 | .TP 32 | \fB\-rcfile\fP \fI\fP 33 | Specify a configuration file to use. See \fBCONFIGURATION\fP for details. 34 | .TP 35 | \fB\-title\fP \fIString\fP 36 | Specify toolbox window title. Default is user's login name and the host name. 37 | .TP 38 | \fB\-hotkey\fP \fI[Modifier ...] Key\fP 39 | Hotkey to raise and focus the toolbox window\. 40 | See the resource description below\. 41 | .TP 42 | \fB\-horizontal\fP 43 | Specifies whether the top\-level menu should be laid out horizontally, 44 | rather than vertically. 45 | .SH CONFIGURATION 46 | .PP 47 | XmToolbox reads its menu configuration from text files. If not specified 48 | on command line with the \fB-rcfile\fP option, its location defaults to 49 | \fB.toolboxrc\fP in user's home directory and \fBtoolboxrc\fP in any of system 50 | configuration directories e\.g\. /etc/X11, /usr/lib/X11\. 51 | .PP 52 | A menu is defined by specifying the menu title followed by one or more menu item 53 | definitions enclosed in {\.\.\.} brackets. Menu items are specified one per line, 54 | starting with the title string separated by the : character from the command\(dg 55 | to be executed. A horizontal line may be added between menu items with the 56 | special identifier SEPARATOR. Any character in the title string may be prefixed 57 | with the ampersand character to specify a mnemonic. Comments may be entered 58 | following the # character at the beginning of a line. Sub\-menus may be defined 59 | by placing them within the scope of the parent menu. Menu scope {} delimiters 60 | may only be placed at the beginning of an otherwise empty line. 61 | .PP 62 | Literal & and : characters in title strings may be specified by prefixing them 63 | with the \\ escape character. Literal whitespace characters in the command 64 | string may be specified either by escaping them with \\, or enclosing the part 65 | of the string in quotation marks\. 66 | .PP 67 | The command string may contain sh(1) like ($\.\.\. and ${\.\.\.}) environment 68 | variables, which will be expanded accordingly. A literal $ may be specified 69 | with $$. Undefined variables are not treated as error, and expand to nothing, 70 | though a warning is printed to stderr\. 71 | .PP 72 | .nf 73 | # Comment 74 | 75 | Menu Title 76 | { 77 | Title: Command 78 | SEPARATOR 79 | \.\.\. 80 | 81 | Submenu Title 82 | { 83 | Title: Command 84 | \.\.\. 85 | } 86 | } 87 | ... 88 | .fi 89 | .SH RESOURCES 90 | .TP 91 | \fBtitle\fP \fIString\fP 92 | This resource is used to specify a custom window title\. 93 | Defaults to user's login name and the host name: @\. 94 | .TP 95 | \fBdateTimeDisplay\fP \fIBoolean\fP 96 | Display date and time\. Default is \fITrue\fP\. 97 | .TP 98 | \fBdateTimeFormat\fP \fIString\fP 99 | Date and time format string\. See strftime(3). Defaults to "%D %l:%M %p"\. 100 | .TP 101 | \fBhotkey\fP [\fIModifier\fP \.\.\.] \fIKey\fP | None 102 | One or more modifiers followed by a key name (X KeySym names, separated by 103 | whitespace or +) defining the hotkey to raise and focus the toolbox window at 104 | any time, or \fINone\fP if no hotkey assignment is desired\. Defaults to None\. 105 | .TP 106 | \fBhorizontal\fP \fIBoolean\fP 107 | Specifies whether the top\-level menu should be laid out horizontally, 108 | rather than vertically. Default is False. 109 | .TP 110 | \fBrcFile\fP \fIString\fP 111 | Full path to the configuration file\. See \fBCONFIGURATION\fP for details\. 112 | .TP 113 | \fBseparators\fP \fIBoolean\fP 114 | If true, separators will be displayed between launcher, session and time/date 115 | display parts\. Default is \fITrue\fP\. 116 | .SH SIGNALS 117 | .PP 118 | XmToolbox responds to SIGUSR1 signal by reparsing the menu configuration file\. 119 | .SH SEE ALSO 120 | xmsm(1) emwm(1) 121 | .SH AUTHORS 122 | .PP 123 | Toolbox was written and is maintained by alx@fastestcode\.org 124 | --------------------------------------------------------------------------------