├── LICENSE ├── Makefile ├── README.md ├── demo.png ├── paginator.1 ├── paginator.c └── x.xpm /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2023 Lucas de Sena 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROG = paginator 2 | OBJS = ${PROG:=.o} 3 | SRCS = ${OBJS:.o=.c} 4 | MAN = ${PROG:=.1} 5 | 6 | PREFIX ?= /usr/local 7 | MANPREFIX ?= ${PREFIX}/share/man 8 | LOCALINC = /usr/local/include 9 | LOCALLIB = /usr/local/lib 10 | X11INC = /usr/X11R6/include 11 | X11LIB = /usr/X11R6/lib 12 | 13 | DEFS = -D_POSIX_C_SOURCE=200809L -D_GNU_SOURCE -D_BSD_SOURCE 14 | INCS = -I${LOCALINC} -I${X11INC} 15 | LIBS = -L${LOCALLIB} -L${X11LIB} -lX11 -lXrender -lXpm 16 | PROG_CFLAGS = -std=c99 -pedantic ${DEFS} ${INCS} ${CFLAGS} ${CPPFLAGS} 17 | PROG_LDFLAGS = ${LIBS} ${LDLIBS} ${LDFLAGS} 18 | 19 | bindir = ${DESTDIR}${PREFIX}/bin 20 | mandir = ${DESTDIR}${MANPREFIX}/man1 21 | 22 | all: ${PROG} 23 | 24 | ${PROG}: ${OBJS} 25 | ${CC} -o $@ ${OBJS} ${PROG_LDFLAGS} 26 | 27 | .c.o: 28 | ${CC} ${PROG_CFLAGS} -o $@ -c $< 29 | 30 | tags: ${SRCS} 31 | ctags ${SRCS} 32 | 33 | lint: ${SRCS} 34 | -mandoc -T lint -W warning ${MAN} 35 | -clang-tidy ${SRCS} -- -std=c99 ${PROG_CFLAGS} 36 | 37 | clean: 38 | rm -f ${OBJS} ${PROG} ${PROG:=.core} tags 39 | 40 | install: all 41 | mkdir -p ${bindir} 42 | mkdir -p ${mandir} 43 | install -m 755 ${PROG} ${bindir}/${PROG} 44 | install -m 644 ${MAN} ${mandir}/${MAN} 45 | 46 | uninstall: 47 | -rm ${bindir}/${PROG} 48 | -rm ${mandir}/${MAN} 49 | 50 | .PHONY: all clean install uninstall lint 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paginator 2 | 3 | ![demo](./demo.png) 4 | 5 | (Paginator is at the top right on the image.) 6 | 7 | Paginator is a desktop pager for EWMH-compliant X11 window managers that 8 | support docked applications (dockapps), such as WindowMaker and Fluxbox. 9 | 10 | Paginator shows a grid of desktop miniatures representing actual virtual 11 | desktops; each one having window miniatures representing actual windows. 12 | By manipulating those miniatures with the mouse, the user can change the 13 | active window, set the desktop of a window, and switch between desktops. 14 | 15 | Paginator is configured solely by X resources and command-line options. 16 | 17 | ## Options 18 | Paginator understand the following command-line options. 19 | 20 | * `-geometry geometry`: Specify the initial size for Paginator. 21 | * `-name name`: Specify a resource/instance name for Paginator. 22 | * `-xrm resources`: Specify X resources for Paginator. 23 | 24 | ## Customization 25 | Paginator can be customized by setting the following X resources. 26 | 27 | * `Paginator.activeBackground`: 28 | Color of the miniature of the active window. 29 | * `Paginator.activeBorderColor`: 30 | Color of the border of the miniature of the active window. 31 | * `Paginator.activeTopShadowColor`: 32 | Color of the light shadow of the miniature of the active window. 33 | * `Paginator.activeBottomShadowColor`: 34 | Color of the heavy shadow of the miniature of the active window. 35 | * `Paginator.borderWidth`: 36 | Width in pixels of the border around the miniatures of windows. 37 | * `Paginator.currentDesktopBackground`: 38 | Color of the current desktop miniature. 39 | * `Paginator.desktopBackground`: 40 | Color of a desktop miniature. 41 | * `Paginator.frameTopShadowColor`: 42 | Color of the light shadow around Paginator. 43 | * `Paginator.frameBottomShadowColor`: 44 | Color of the heavy shadow around Paginator. 45 | * `Paginator.frameShadowThickness`: 46 | Width in pixels of the 3D shadow frame around Paginator. 47 | * `Paginator.geometry`: 48 | Initial geometry of paginator. 49 | * `Paginator.inactiveBackground`: 50 | Color of the miniature of a regular window. 51 | * `Paginator.inactiveBorderColor`: 52 | Color of the border of the miniature of a regular window. 53 | * `Paginator.inactiveTopShadowColor`: 54 | Color of the light shadow of the miniature of a regular window. 55 | * `Paginator.inactiveBottomShadowColor`: 56 | Color of the heavy shadow of the miniature of a regular window. 57 | * `Paginator.urgentBackground`: 58 | Color of the miniature of an urgent window. 59 | * `Paginator.urgentBorderColor`: 60 | Color of the border of the miniature of an urgent window. 61 | * `Paginator.urgentTopShadowColor`: 62 | Color of the light shadow of the miniature of an urgent window. 63 | * `Paginator.urgentBottomShadowColor`: 64 | Color of the heavy shadow of the miniature of an urgent window. 65 | * `Paginator.separatorColor`: 66 | Color of the separator between desktop miniatures. 67 | * `Paginator.separatorWidth`: 68 | Width in pixels of the separator between desktop miniatures. 69 | * `Paginator.shadowThickness`: 70 | Width in pixels of the 3D shadows. 71 | 72 | ## Installation 73 | Run `make all` to build, and `make install` to install the binary and 74 | the manual into `${PREFIX}` (`/usr/local`). 75 | 76 | ## Usage 77 | Run `paginator` with a number of rows and columns: 78 | 79 | ``` 80 | $ paginator 2 3 81 | ``` 82 | 83 | This creates the following pager: 84 | 85 | ``` 86 | +-------+-------+-------+ 87 | | | | | 88 | | 1 | 2 | 3 | 89 | | | | | 90 | +-------+-------+-------+ 91 | | | | | 92 | | 4 | 5 | 6 | 93 | | | | | 94 | +-------+-------+-------+ 95 | ``` 96 | 97 | ## License 98 | The code and manual are under the MIT/X license. 99 | See `./LICENSE` for more information. 100 | 101 | ## Epilogue 102 | **Read the manual.** 103 | -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phillbush/paginator/060234f3981276e99a22e0281c83a4fced8fec01/demo.png -------------------------------------------------------------------------------- /paginator.1: -------------------------------------------------------------------------------- 1 | .Dd July 31, 2023 2 | .Dt PAGINATOR 1 3 | .Os 4 | .Sh NAME 5 | .Nm paginator 6 | .Nd X11 desktop pager 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl geometry Ar geometry 10 | .Op Fl name Ar name 11 | .Op Fl xrm Ar resources 12 | .Ar nrows ncols 13 | .Op Ar primary secondary 14 | .Sh DESCRIPTION 15 | .Nm 16 | is a desktop pager for X11 that provides to the user a graphical interface 17 | to change the current desktop or the current window. 18 | It displays a grid of desktop miniatures 19 | representing actual virtual desktops managed by the window manager; 20 | on each desktop miniature there are window miniatures 21 | representing actual client windows on the screen. 22 | Each window miniature is identified by the icon of the client window 23 | it represents. 24 | The user can change the desktop of a client by drag-and-dropping its 25 | window miniature to another desktop miniature on the pager. 26 | .Pp 27 | The options are as follows: 28 | .Bl -tag -width Ds 29 | .It Fl geometry Ar geometry 30 | Specify the initial size and location of the window. 31 | The format for the 32 | .Ar geometry 33 | argument is specified at 34 | .Xr XParseGeometry 3 . 35 | .It Fl name Ar name 36 | Specify the name of 37 | .Nm 38 | instance used to retrieve resources. 39 | If not specified, defaults to the value of the 40 | .Ev RESOURCES_NAME 41 | environment variable, or (if that variable is not set) 42 | to the basename of the command 43 | .Nm 44 | was invoked as. 45 | .It Fl xrm Ar resources 46 | Specify additional resources to merge on top of X11's resouces database. 47 | If not specified, defaults to the value of the 48 | .Ev RESOURCES_DATA 49 | environment variable, or (if that variable is not set) 50 | to the empty string. 51 | .El 52 | .Pp 53 | The first and second obligatory arguments, 54 | .Ar nrows 55 | and 56 | .Ar ncols 57 | are the number of rows and columns in the desktop grid. 58 | .Pp 59 | The first and second optional arguments, 60 | .Ar primary 61 | and 62 | .Ar secondary , 63 | must each be set to either 64 | .Cm top , 65 | .Cm bottom , 66 | .Cm left , 67 | or 68 | .Cm right . 69 | These values specify a top-to-bottom, bottom-to-top, left-to-right or right-to-left 70 | primary and secondary placement respectively. 71 | .Pp 72 | .Nm 73 | only works in cooperation with EWMH-compliant window managers which set the 74 | _NET_CLIENT_LIST_STACKING and _NET_NUMBER_OF_DESKTOPS 75 | properties. 76 | .Sh RESOURCES 77 | .Nm 78 | understands the following X resources. 79 | They must be prefixed with either the 80 | .Qq Ic Paginator 81 | class, or the name given with the 82 | .Fl name 83 | command-line option, followed by a period. 84 | .Bl -tag -width Ds 85 | .It Ic activeBackground 86 | The color of the background of the active window. 87 | .It Ic activeBorderColor 88 | The color of the border of the active window. 89 | .It Ic activeTopShadowColor , activeBottomShadowColor 90 | The light and dark colors of the Motif-like 3D shadow of the active window. 91 | .It Ic borderWidth 92 | The width in pixels of the borders of the window miniatures 93 | .It Ic currentDesktopBackground 94 | The color of the background for current desktop miniature. 95 | .It Ic desktopBackground 96 | The color of the background for desktop miniatures. 97 | .It Ic frameShadowThickness 98 | The width in pixels of the Motif-like 3D shadow frame around Paginator. 99 | .It Ic frameTopShadowColoror , frameBottomShadowColor 100 | The light and dark colors of the Motif-like 3D shadow of the frame around Paginator. 101 | .It Ic geometry 102 | The initial size and location of Paginator. 103 | .It Ic inactiveBackground 104 | The color of the background of the inactive window. 105 | .It Ic inactiveBorderColor 106 | The color of the border of the inactive window. 107 | .It Ic inactiveTopShadowColoror , inactiveBottomShadowColor 108 | The light and dark colors of the Motif-like 3D shadow of the inactive window. 109 | .It Ic separatorWidth 110 | The width in pixels of the separators between desktop miniatures. 111 | .It Ic shadowThickness 112 | The width in pixels of the Motif-like 3D shadow borders. 113 | .It Ic urgentBackground 114 | The color of the background of the urgent window. 115 | .It Ic urgentBorderColor 116 | The color of the border of the urgent window. 117 | .It Ic urgentTopShadowColoror , urgentBottomShadowColor 118 | The light and dark colors of the Motif-like 3D shadow of the urgent window. 119 | .El 120 | .Sh ENVIRONMENT 121 | The following environment variables affect the execution of 122 | .Nm 123 | .Bl -tag -width Ds 124 | .It DISPLAY 125 | The display to start 126 | .Nm 127 | on. 128 | .El 129 | .Sh EXAMPLES 130 | To create a pager with 6 virtual desktops distributed as follows... 131 | .Bd -literal -offset indent 132 | +-------+-------+-------+ 133 | | | | | 134 | | 6 | 4 | 2 | 135 | | | | | 136 | +-------+-------+-------+ 137 | | | | | 138 | | 5 | 3 | 1 | 139 | | | | | 140 | +-------+-------+-------+ 141 | .Ed 142 | .Pp 143 | \&...run the following command: 144 | .Bd -literal -offset indent 145 | $ paginator 2 3 bottom right 146 | .Ed 147 | .Pp 148 | The following command creates a pager with a 2x2 grid and default orientation: 149 | .Bd -literal -offset indent 150 | $ paginator 2 2 151 | .Ed 152 | .Sh SEE ALSO 153 | .Xr X 7 154 | -------------------------------------------------------------------------------- /paginator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "x.xpm" 16 | 17 | #define APP_CLASS "Paginator" 18 | #define APP_NAME "paginator" 19 | #define MAX_VALUE 32767 /* 2^15-1 */ 20 | #define ICON_SIZE 16 21 | #define ALLDESKTOPS 0xFFFFFFFF 22 | #define PAGER_ACTION 2 23 | #define FLAG(f, b) (((f) & (b)) == (b)) 24 | 25 | #define ATOMS \ 26 | X(WM_DELETE_WINDOW) \ 27 | X(_NET_ACTIVE_WINDOW) \ 28 | X(_NET_CLIENT_LIST_STACKING) \ 29 | X(_NET_CURRENT_DESKTOP) \ 30 | X(_NET_WM_DESKTOP) \ 31 | X(_NET_DESKTOP_LAYOUT) \ 32 | X(_NET_SHOWING_DESKTOP) \ 33 | X(_NET_MOVERESIZE_WINDOW) \ 34 | X(_NET_NUMBER_OF_DESKTOPS) \ 35 | X(_NET_WM_ICON) \ 36 | X(_NET_WM_STATE) \ 37 | X(_NET_WM_STATE_HIDDEN) \ 38 | X(_NET_WM_STATE_STICKY) \ 39 | X(_NET_WM_STATE_DEMANDS_ATTENTION) 40 | 41 | #define NCOLORS 17 /* number of color resources */ 42 | #define RESOURCES \ 43 | /* ENUM CLASS NAME DEFAULT */\ 44 | /* color resources MUST be listed first; values are RGB channels */\ 45 | X(RES_ACTIVE_BG, "Background", "activeBackground", 0x000000 )\ 46 | X(RES_ACTIVE_BOR, "BorderColor", "activeBorderColor", 0x000000 )\ 47 | X(RES_ACTIVE_TOP, "TopShadowColor", "activeTopShadowColor", 0xB6B6B6 )\ 48 | X(RES_ACTIVE_BOT, "BottomShadowColor", "activeBottomShadowColor", 0x616161 )\ 49 | X(RES_URGENT_BG, "Background", "urgentBackground", 0xFC6161 )\ 50 | X(RES_URGENT_BOR, "BorderColor", "urgentBorderColor", 0x000000 )\ 51 | X(RES_URGENT_TOP, "TopShadowColor", "urgentTopShadowColor", 0xF7D9D9 )\ 52 | X(RES_URGENT_BOT, "BottomShadowColor", "urgentBottomShadowColor", 0x9B1D1D )\ 53 | X(RES_INACTIVE_BG, "Background", "inactiveBackground", 0xAAAAAA )\ 54 | X(RES_INACTIVE_BOR, "BorderColor", "inactiveBorderColor", 0x000000 )\ 55 | X(RES_INACTIVE_TOP, "TopShadowColor", "inactiveTopShadowColor", 0xFFFFFF )\ 56 | X(RES_INACTIVE_BOT, "BottomShadowColor", "inactiveBottomShadowColor", 0x555555 )\ 57 | X(RES_DESK_BG, "Background", "desktopBackground", 0x505075 )\ 58 | X(RES_DESK_BOR, "SeparatorColor", "separatorColor", 0x000000 )\ 59 | X(RES_DESK_TOP, "TopShadowColor", "frameTopShadowColor", 0x000000 )\ 60 | X(RES_DESK_BOT, "BottomShadowColor", "frameBottomShadowColor", 0xFFFFFF )\ 61 | X(RES_DESK_FG, "Background", "currentDesktopBackground", 0xAAAAAA )\ 62 | /* width resources MUST be listed next; value is width in pixels */\ 63 | X(RES_FRAME, "ShadowThickness", "frameShadowThickness", 1 )\ 64 | X(RES_BORDER, "BorderWidth", "borderWidth", 1 )\ 65 | X(RES_SHADOW, "ShadowThickness", "shadowThickness", 1 )\ 66 | X(RES_SEPARATOR, "SeparatorWidth", "separatorWidth", 1 )\ 67 | /* geometry resources; values are width and height in pixels */\ 68 | X(RES_GEOMETRY, "Geometry", "geometry", 58 )\ 69 | 70 | #define MOUSEEVENTMASK (ButtonReleaseMask | PointerMotionMask) 71 | 72 | enum Atom { 73 | #define X(atom) atom, 74 | ATOMS 75 | NATOMS 76 | #undef X 77 | }; 78 | 79 | enum Resource { 80 | #define X(resource, class, name, value) resource, 81 | RESOURCES 82 | NRESOURCES 83 | #undef X 84 | }; 85 | 86 | enum Width { 87 | FRAME_WIDTH, 88 | BORDER_WIDTH, 89 | SHADOW_WIDTH, 90 | SEPARATOR_WIDTH, 91 | NBORDERS, 92 | }; 93 | 94 | enum Colors { 95 | COLOR_BG = 0, 96 | COLOR_BOR = 1, 97 | COLOR_TOP = 2, 98 | COLOR_BOT = 3, 99 | COLOR_FG = 4, /* extra, for DESKTOP only */ 100 | 101 | SCM_ACTIVE = 0, 102 | SCM_URGENT = 4, 103 | SCM_INACTIVE = 8, 104 | SCM_DESKTOP = 12, 105 | }; 106 | 107 | enum Orientation { 108 | _NET_WM_ORIENTATION_HORZ = 0, 109 | _NET_WM_ORIENTATION_VERT = 1, 110 | }; 111 | 112 | enum StartingCorner { 113 | _NET_WM_TOPLEFT = 0, 114 | _NET_WM_TOPRIGHT = 1, 115 | _NET_WM_BOTTOMRIGHT = 2, 116 | _NET_WM_BOTTOMLEFT = 3, 117 | }; 118 | 119 | typedef unsigned long Cardinal; 120 | 121 | typedef struct { 122 | Pixmap pixmap; 123 | Picture picture; 124 | XRenderColor channels; 125 | } Color; 126 | 127 | typedef struct { 128 | XrmClass class; 129 | XrmName name; 130 | } Resource; 131 | 132 | typedef struct { 133 | Window miniwin; 134 | XRectangle geometry; 135 | } Desktop; 136 | 137 | typedef struct { 138 | /* client window */ 139 | Window clientwin; 140 | XRectangle clientgeom; 141 | 142 | /* miniature windows (one for each desktop) */ 143 | Window *miniwins; 144 | XRectangle *minigeoms; 145 | 146 | Picture icon; 147 | Cardinal desk; 148 | bool ishidden; 149 | bool isurgent; 150 | } Client; 151 | 152 | typedef struct { 153 | Display *display; 154 | bool running; 155 | 156 | /* root window */ 157 | Window root; 158 | XRectangle rootgeom; 159 | 160 | /* pager window */ 161 | Window window; 162 | XRectangle geometry; 163 | int geomflags; 164 | int borders[NBORDERS]; 165 | 166 | /* graphics */ 167 | unsigned int depth; 168 | Colormap colormap; 169 | Visual *visual; 170 | XRenderPictFormat *format, *formatARGB; 171 | Color colors[NCOLORS]; 172 | Picture icon, mask; 173 | 174 | /* grid */ 175 | enum Orientation orient; 176 | enum StartingCorner corner; 177 | int nrows, ncols; 178 | 179 | /* atoms and resources */ 180 | const char *xrm; 181 | Resource application; 182 | Resource resources[NRESOURCES]; 183 | Atom atoms[NATOMS]; 184 | 185 | /* desktops */ 186 | Cardinal ndesktops; 187 | Cardinal activedesktop; 188 | bool showingdesk; 189 | Desktop *desktops; 190 | 191 | /* clients */ 192 | Cardinal nclients; 193 | Client *activeclient; 194 | Client **clients; 195 | } Pager; 196 | 197 | static void 198 | usage(void) 199 | { 200 | (void)fprintf( 201 | stderr, 202 | "usage: paginator nrows ncols [border border]\n" 203 | ); 204 | exit(EXIT_FAILURE); 205 | } 206 | 207 | static int 208 | xerror(Display *display, XErrorEvent *event) 209 | { 210 | char msg[128], number[128], req[128]; 211 | 212 | if (event->error_code == BadWindow) 213 | return 0; 214 | XGetErrorText(display, event->error_code, msg, sizeof(msg)); 215 | (void)snprintf(number, sizeof(number), "%d", event->request_code); 216 | XGetErrorDatabaseText( 217 | display, 218 | "XRequest", 219 | number, 220 | "", 221 | req, 222 | sizeof(req) 223 | ); 224 | errx(EXIT_FAILURE, "%s (0x%08lX): %s", req, event->resourceid, msg); 225 | return 0; /* unreachable */ 226 | } 227 | 228 | static void * 229 | emalloc(size_t size) 230 | { 231 | void *p; 232 | 233 | if ((p = malloc(size)) == NULL) 234 | err(1, "malloc"); 235 | return p; 236 | } 237 | 238 | static void * 239 | ecalloc(size_t nmemb, size_t size) 240 | { 241 | void *p; 242 | if ((p = calloc(nmemb, size)) == NULL) 243 | err(1, "calloc"); 244 | return p; 245 | } 246 | 247 | static void 248 | preparewin(Pager *pager, Window window) 249 | { 250 | XSelectInput(pager->display, window, StructureNotifyMask | PropertyChangeMask); 251 | } 252 | 253 | static int 254 | max(int x, int y) 255 | { 256 | return x > y ? x : y; 257 | } 258 | 259 | static char * 260 | gettextprop(Display *display, Window window, Atom prop, Bool delete) 261 | { 262 | char *text; 263 | unsigned char *p; 264 | unsigned long len; 265 | unsigned long dl; /* dummy variable */ 266 | int format, status; 267 | Atom type; 268 | 269 | text = NULL; 270 | status = XGetWindowProperty( 271 | display, 272 | window, 273 | prop, 274 | 0L, 275 | 0x1FFFFFFF, 276 | delete, 277 | AnyPropertyType, 278 | &type, &format, 279 | &len, &dl, 280 | &p 281 | ); 282 | if (status != Success || len == 0 || p == NULL) { 283 | goto done; 284 | } 285 | if ((text = emalloc(len + 1)) == NULL) { 286 | goto done; 287 | } 288 | memcpy(text, p, len); 289 | text[len] = '\0'; 290 | done: 291 | XFree(p); 292 | return text; 293 | } 294 | 295 | static Cardinal 296 | getcardprop(Pager *pager, Window window, Atom prop) 297 | { 298 | int di, status; 299 | Cardinal card; 300 | Cardinal dl; 301 | unsigned char *p = NULL; 302 | Atom da; 303 | 304 | card = 0; 305 | status = XGetWindowProperty( 306 | pager->display, 307 | window, 308 | prop, 309 | 0L, 1024, 310 | False, XA_CARDINAL, 311 | &da, &di, &dl, &dl, &p 312 | ); 313 | if (status == Success && p != NULL) 314 | card = *(Cardinal *)p; 315 | XFree(p); 316 | return card; 317 | } 318 | 319 | static Cardinal 320 | getatomprop(Pager *pager, Window window, Atom prop, Atom **atoms) 321 | { 322 | unsigned char *list; 323 | Cardinal len; 324 | unsigned long dl; /* dummy variable */ 325 | int di; /* dummy variable */ 326 | Atom da; /* dummy variable */ 327 | int status; 328 | 329 | list = NULL; 330 | status = XGetWindowProperty( 331 | pager->display, 332 | window, 333 | prop, 0L, 1024, False, XA_ATOM, 334 | &da, &di, &len, &dl, &list 335 | ); 336 | if (status != Success || list == NULL) { 337 | *atoms = NULL; 338 | return 0; 339 | } 340 | *atoms = (Atom *)list; 341 | return len; 342 | } 343 | 344 | static void 345 | clientmsg(Pager *pager, Window win, Atom atom, Cardinal cards[5]) 346 | { 347 | XEvent ev; 348 | long mask = SubstructureRedirectMask | SubstructureNotifyMask; 349 | 350 | ev.xclient.display = pager->display; 351 | ev.xclient.type = ClientMessage; 352 | ev.xclient.serial = 0; 353 | ev.xclient.send_event = True; 354 | ev.xclient.message_type = atom; 355 | ev.xclient.window = win; 356 | ev.xclient.format = 32; 357 | ev.xclient.data.l[0] = cards[0]; 358 | ev.xclient.data.l[1] = cards[1]; 359 | ev.xclient.data.l[2] = cards[2]; 360 | ev.xclient.data.l[3] = cards[3]; 361 | ev.xclient.data.l[4] = cards[4]; 362 | if (!XSendEvent(pager->display, pager->root, False, mask, &ev)) { 363 | errx(1, "could not send event"); 364 | } 365 | } 366 | 367 | static uint32_t 368 | prealpha(uint32_t p) 369 | { 370 | uint8_t a = p >> 24u; 371 | uint32_t rb = (a * (p & 0xFF00FFu)) >> 8u; 372 | uint32_t g = (a * (p & 0x00FF00u)) >> 8u; 373 | return (rb & 0xFF00FFu) | (g & 0x00FF00u) | (a << 24u); 374 | } 375 | 376 | static Cardinal 377 | getwinprop(Pager *pager, Window window, Atom prop, Window **wins) 378 | { 379 | unsigned char *list; 380 | Cardinal len; 381 | unsigned long dl; /* dummy variable */ 382 | int di; /* dummy variable */ 383 | Atom da; /* dummy variable */ 384 | int status; 385 | 386 | list = NULL; 387 | status = XGetWindowProperty( 388 | pager->display, 389 | window, 390 | prop, 391 | 0L, 1024, False, XA_WINDOW, 392 | &da, &di, &len, &dl, &list 393 | ); 394 | if (status != Success || list == NULL) { 395 | *wins = NULL; 396 | return 0; 397 | } 398 | *wins = (Window *)list; 399 | return len; 400 | } 401 | 402 | static Pixmap 403 | getewmhicon(Pager *pager, Window win, int *iconw, int *iconh, int *d) 404 | { 405 | XImage *img; 406 | GC gc; 407 | Pixmap pix = None; 408 | Atom da; 409 | size_t i, size; 410 | uint32_t *data32; 411 | unsigned long *q, *p, *end, *data = NULL; 412 | unsigned long len, dl; 413 | int diff, mindiff = INT_MAX; 414 | int w, h; 415 | int format; 416 | char *datachr = NULL; 417 | 418 | (void)d; 419 | if (XGetWindowProperty(pager->display, win, pager->atoms[_NET_WM_ICON], 0L, UINT32_MAX, False, AnyPropertyType, &da, &format, &len, &dl, (unsigned char **)&q) != Success) 420 | return None; 421 | if (q == NULL) 422 | return None; 423 | if (len == 0 || format != 32) 424 | goto done; 425 | *iconw = *iconh = 0; 426 | for (p = q, end = p + len; p < end; p += size) { 427 | w = *p++; 428 | h = *p++; 429 | size = w * h; 430 | if (w < 1 || h < 1 || p + size > end) 431 | break; 432 | diff = max(w, h) - ICON_SIZE; 433 | if (diff >= 0 && diff < mindiff) { 434 | *iconw = w; 435 | *iconh = h; 436 | data = p; 437 | if (diff == 0) { 438 | break; 439 | } 440 | } 441 | } 442 | if (data == NULL) 443 | goto done; 444 | size = *iconw * *iconh; 445 | data32 = (uint32_t *)data; 446 | for (i = 0; i < size; ++i) 447 | data32[i] = prealpha(data[i]); 448 | datachr = emalloc(size * sizeof(*data)); 449 | (void)memcpy(datachr, data32, size * sizeof(*data)); 450 | if ((img = XCreateImage(pager->display, pager->visual, 32, ZPixmap, 0, datachr, *iconw, *iconh, 32, 0)) == NULL) { 451 | free(datachr); 452 | goto done; 453 | } 454 | XInitImage(img); 455 | pix = XCreatePixmap(pager->display, pager->root, *iconw, *iconh, 32); 456 | gc = XCreateGC(pager->display, pix, 0, NULL); 457 | XPutImage(pager->display, pix, gc, img, 0, 0, 0, 0, *iconw, *iconh); 458 | XFreeGC(pager->display, gc); 459 | XDestroyImage(img); 460 | done: 461 | XFree(q); 462 | return pix; 463 | } 464 | 465 | static Picture 466 | geticonprop(Pager *pager, Window win) 467 | { 468 | Pixmap pix; 469 | Picture pic = None; 470 | XTransform xf; 471 | int iconw, iconh; 472 | int d; 473 | 474 | if ((pix = getewmhicon(pager, win, &iconw, &iconh, &d)) == None) 475 | return None; 476 | if (pix == None) 477 | return None; 478 | pic = XRenderCreatePicture(pager->display, pix, pager->formatARGB, 0, NULL); 479 | XFreePixmap(pager->display, pix); 480 | if (pic == None) 481 | return None; 482 | if (max(iconw, iconh) != ICON_SIZE) { 483 | XRenderSetPictureFilter(pager->display, pic, FilterBilinear, NULL, 0); 484 | xf.matrix[0][0] = (iconw << 16u) / ICON_SIZE; xf.matrix[0][1] = 0; xf.matrix[0][2] = 0; 485 | xf.matrix[1][0] = 0; xf.matrix[1][1] = (iconh << 16u) / ICON_SIZE; xf.matrix[1][2] = 0; 486 | xf.matrix[2][0] = 0; xf.matrix[2][1] = 0; xf.matrix[2][2] = 65536; 487 | XRenderSetPictureTransform(pager->display, pic, &xf); 488 | } 489 | return pic; 490 | } 491 | 492 | static bool 493 | hasstate(Pager *pager, Window window, Atom atom) 494 | { 495 | Atom *as; 496 | Cardinal natoms, i; 497 | int retval; 498 | 499 | /* return non-zero if window has given state */ 500 | retval = 0; 501 | if ((natoms = getatomprop(pager, window, pager->atoms[_NET_WM_STATE], &as)) != 0) { 502 | for (i = 0; i < natoms; i++) { 503 | if (as[i] == atom) { 504 | retval = 1; 505 | break; 506 | } 507 | } 508 | XFree(as); 509 | } 510 | return retval; 511 | } 512 | 513 | static bool 514 | isurgent(Pager *pager, Window win) 515 | { 516 | XWMHints *wmh; 517 | bool ret; 518 | 519 | ret = false; 520 | if ((wmh = XGetWMHints(pager->display, win)) != NULL) { 521 | ret = wmh->flags & XUrgencyHint; 522 | XFree(wmh); 523 | } 524 | return ret; 525 | } 526 | 527 | static void 528 | cleanclient(Pager *pager, Client *client) 529 | { 530 | Cardinal i; 531 | 532 | if (client == NULL) 533 | return; /* can be set to NULL by setclients() */ 534 | for (i = 0; i < pager->ndesktops; i++) 535 | XDestroyWindow(pager->display, client->miniwins[i]); 536 | if (client->icon != None) 537 | XRenderFreePicture(pager->display, client->icon); 538 | free(client->miniwins); 539 | free(client->minigeoms); 540 | free(client); 541 | } 542 | 543 | static void 544 | cleandesktops(Pager *pager) 545 | { 546 | Cardinal i; 547 | 548 | for (i = 0; i < pager->ndesktops; i++) { 549 | XDestroyWindow( 550 | pager->display, 551 | pager->desktops[i].miniwin 552 | ); 553 | } 554 | pager->ndesktops = 0; 555 | free(pager->desktops); 556 | } 557 | 558 | static void 559 | cleanclients(Pager *pager) 560 | { 561 | Cardinal i; 562 | 563 | for (i = 0; i < pager->nclients; i++) 564 | cleanclient(pager, pager->clients[i]); 565 | pager->nclients = 0; 566 | pager->activeclient = NULL; 567 | free(pager->clients); 568 | } 569 | 570 | static void 571 | drawshadows(Pager *pager, Picture picture, int scheme, XRectangle *geometry) 572 | { 573 | int i, w; 574 | 575 | if (scheme == SCM_DESKTOP) 576 | w = pager->borders[FRAME_WIDTH]; 577 | else 578 | w = pager->borders[SHADOW_WIDTH]; 579 | for(i = 0; i < w; i++) { 580 | /* draw light shadow */ 581 | XRenderFillRectangle( 582 | pager->display, 583 | PictOpSrc, 584 | picture, 585 | &pager->colors[scheme + COLOR_TOP].channels, 586 | i, i, 587 | 1, geometry->height - (i * 2 + 1) 588 | ); 589 | XRenderFillRectangle( 590 | pager->display, 591 | PictOpSrc, 592 | picture, 593 | &pager->colors[scheme + COLOR_TOP].channels, 594 | i, i, 595 | geometry->width - (i * 2 + 1), 1 596 | ); 597 | 598 | /* draw dark shadow */ 599 | XRenderFillRectangle( 600 | pager->display, 601 | PictOpSrc, 602 | picture, 603 | &pager->colors[scheme + COLOR_BOT].channels, 604 | geometry->width - 1 - i, i, 605 | 1, geometry->height - i * 2 606 | ); 607 | XRenderFillRectangle( 608 | pager->display, 609 | PictOpSrc, 610 | picture, 611 | &pager->colors[scheme + COLOR_BOT].channels, 612 | i, geometry->height - 1 - i, 613 | geometry->width - i * 2, 1 614 | ); 615 | } 616 | } 617 | 618 | static void 619 | drawclient(Pager *pager, Client *cp) 620 | { 621 | Pixmap pixmap; 622 | Picture picture, icon, mask; 623 | Cardinal i, scheme; 624 | 625 | if (cp->icon == None) { 626 | icon = pager->icon; 627 | mask = pager->mask; 628 | } else { 629 | icon = cp->icon; 630 | mask = None; 631 | } 632 | if (cp == pager->activeclient) 633 | scheme = SCM_ACTIVE; 634 | else if (cp->isurgent) 635 | scheme = SCM_URGENT; 636 | else 637 | scheme = SCM_INACTIVE; 638 | for (i = 0; i < pager->ndesktops; i++) { 639 | pixmap = XCreatePixmap( 640 | pager->display, 641 | pager->window, 642 | cp->minigeoms[i].width, 643 | cp->minigeoms[i].height, 644 | pager->depth 645 | ); 646 | picture = XRenderCreatePicture( 647 | pager->display, 648 | pixmap, 649 | pager->format, 650 | 0, NULL 651 | ); 652 | XRenderFillRectangle( 653 | pager->display, 654 | PictOpSrc, 655 | picture, 656 | &pager->colors[scheme + COLOR_BG].channels, 657 | 0, 0, 658 | cp->minigeoms[i].width, 659 | cp->minigeoms[i].height 660 | ); 661 | XRenderComposite( 662 | pager->display, 663 | PictOpOver, 664 | icon, mask, picture, 665 | 0, 0, 0, 0, 666 | (cp->minigeoms[i].width - ICON_SIZE) / 2, 667 | (cp->minigeoms[i].height - ICON_SIZE) / 2, 668 | ICON_SIZE, ICON_SIZE 669 | ); 670 | drawshadows(pager, picture, scheme, &cp->minigeoms[i]); 671 | XSetWindowBackgroundPixmap( 672 | pager->display, 673 | cp->miniwins[i], 674 | pixmap 675 | ); 676 | XSetWindowBorderPixmap( 677 | pager->display, 678 | cp->miniwins[i], 679 | pager->colors[scheme + COLOR_BOR].pixmap 680 | ); 681 | XClearWindow(pager->display, cp->miniwins[i]); 682 | XRenderFreePicture(pager->display, picture); 683 | XFreePixmap(pager->display, pixmap); 684 | } 685 | } 686 | 687 | static void 688 | drawpager(Pager *pager) 689 | { 690 | Picture picture; 691 | Pixmap pixmap; 692 | 693 | pixmap = XCreatePixmap( 694 | pager->display, 695 | pager->window, 696 | pager->geometry.width, 697 | pager->geometry.height, 698 | pager->depth 699 | ); 700 | picture = XRenderCreatePicture( 701 | pager->display, 702 | pixmap, 703 | pager->format, 704 | 0, NULL 705 | ); 706 | XRenderFillRectangle( 707 | pager->display, 708 | PictOpSrc, 709 | picture, 710 | &pager->colors[SCM_DESKTOP + COLOR_BOR].channels, 711 | 0, 0, 712 | pager->geometry.width, 713 | pager->geometry.height 714 | ); 715 | drawshadows(pager, picture, SCM_DESKTOP, &pager->geometry); 716 | XSetWindowBackgroundPixmap( 717 | pager->display, 718 | pager->window, 719 | pixmap 720 | ); 721 | XClearWindow(pager->display, pager->window); 722 | XRenderFreePicture(pager->display, picture); 723 | XFreePixmap(pager->display, pixmap); 724 | } 725 | 726 | static void 727 | drawdesktops(Pager *pager) 728 | { 729 | Cardinal i; 730 | Pixmap pixmap; 731 | 732 | for (i = 0; i < pager->ndesktops; i++) { 733 | if (i == pager->activedesktop) 734 | pixmap = pager->colors[SCM_DESKTOP + COLOR_FG].pixmap; 735 | else 736 | pixmap = pager->colors[SCM_DESKTOP + COLOR_BG].pixmap; 737 | XSetWindowBackgroundPixmap( 738 | pager->display, 739 | pager->desktops[i].miniwin, 740 | pixmap 741 | ); 742 | XClearWindow(pager->display, pager->desktops[i].miniwin); 743 | } 744 | } 745 | 746 | static void 747 | setdeskgeom(Pager *pager) 748 | { 749 | int x, y, w, h; 750 | int off, xi, yi; 751 | XRectangle *geometry; 752 | Cardinal i; 753 | 754 | off = pager->borders[FRAME_WIDTH]; 755 | w = pager->geometry.width; 756 | w -= off * 2; 757 | w -= pager->borders[SEPARATOR_WIDTH] * (pager->ncols - 1); 758 | if (w < 1) 759 | w = 1; 760 | h = pager->geometry.height; 761 | h -= off * 2; 762 | h -= pager->borders[SEPARATOR_WIDTH] * (pager->nrows - 1); 763 | if (h < 1) 764 | h = 1; 765 | for (i = 0; i < pager->ndesktops; i++) { 766 | xi = yi = i; 767 | if (pager->orient == _NET_WM_ORIENTATION_HORZ) 768 | yi /= pager->ncols; 769 | else 770 | xi /= pager->nrows; 771 | if (pager->corner == _NET_WM_TOPRIGHT) { 772 | x = pager->ncols - pager->borders[SEPARATOR_WIDTH] - xi % pager->ncols; 773 | y = yi % pager->nrows; 774 | } else if (pager->corner == _NET_WM_BOTTOMLEFT) { 775 | x = xi % pager->ncols; 776 | y = pager->nrows - pager->borders[SEPARATOR_WIDTH] - yi % pager->nrows; 777 | } else if (pager->corner == _NET_WM_BOTTOMRIGHT) { 778 | x = pager->ncols - pager->borders[SEPARATOR_WIDTH] - xi % pager->ncols; 779 | y = pager->nrows - pager->borders[SEPARATOR_WIDTH] - yi % pager->nrows; 780 | } else { 781 | x = xi % pager->ncols; 782 | y = yi % pager->nrows; 783 | } 784 | geometry = &pager->desktops[i].geometry; 785 | geometry->x = w * x / pager->ncols + x; 786 | geometry->y = h * y / pager->nrows + y; 787 | geometry->width = w * (x + 1) / pager->ncols - w * x / pager->ncols; 788 | if (geometry->width < 1) 789 | geometry->width = 1; 790 | geometry->height = h * (y + 1) / pager->nrows - h * y / pager->nrows; 791 | if (geometry->height < 1) 792 | geometry->height = 1; 793 | XMoveResizeWindow( 794 | pager->display, 795 | pager->desktops[i].miniwin, 796 | off + geometry->x, 797 | off + geometry->y, 798 | geometry->width, 799 | geometry->height 800 | ); 801 | } 802 | } 803 | 804 | static void 805 | unmapclient(Pager *pager, Client *cp) 806 | { 807 | Cardinal i; 808 | 809 | for (i = 0; i < pager->ndesktops; i++) { 810 | XUnmapWindow(pager->display, cp->miniwins[i]); 811 | } 812 | } 813 | 814 | static bool 815 | isatdesk(Client *cp, Cardinal desk) 816 | { 817 | if (cp->ishidden) 818 | return false; 819 | if (cp->desk == ALLDESKTOPS) 820 | return true; 821 | return (cp->desk == desk); 822 | } 823 | 824 | static void 825 | mapclient(Pager *pager, Client *cp) 826 | { 827 | Cardinal i; 828 | 829 | for (i = 0; i < pager->ndesktops; i++) { 830 | if (isatdesk(cp, i)) { 831 | XMapWindow(pager->display, cp->miniwins[i]); 832 | } else { 833 | XUnmapWindow(pager->display, cp->miniwins[i]); 834 | } 835 | } 836 | } 837 | 838 | static void 839 | setclientgeometry(Pager *pager, Client *cp) 840 | { 841 | Window dw; 842 | unsigned int du, b; 843 | int x, y; 844 | int cx, cy; 845 | unsigned int cw, ch; 846 | 847 | XGetGeometry( 848 | pager->display, 849 | cp->clientwin, 850 | &dw, &x, &y, &cw, &ch, &b, &du 851 | ); 852 | XTranslateCoordinates( 853 | pager->display, 854 | cp->clientwin, 855 | pager->root, 856 | x, y, &cx, &cy, &dw 857 | ); 858 | cp->clientgeom.x = cx; 859 | cp->clientgeom.y = cy; 860 | cp->clientgeom.width = cw; 861 | cp->clientgeom.height = ch; 862 | } 863 | 864 | static void 865 | configureclient(Pager *pager, int desk, Client *cp) 866 | { 867 | XRectangle *dp; 868 | 869 | dp = &pager->desktops[desk].geometry; 870 | cp->minigeoms[desk].x = cp->clientgeom.x * dp->width / pager->rootgeom.width; 871 | cp->minigeoms[desk].y = cp->clientgeom.y * dp->height / pager->rootgeom.height; 872 | cp->minigeoms[desk].width = cp->clientgeom.width * dp->width / pager->rootgeom.width; 873 | if (cp->minigeoms[desk].width < 1) 874 | cp->minigeoms[desk].width = 1; 875 | cp->minigeoms[desk].height = cp->clientgeom.height * dp->height / pager->rootgeom.height; 876 | if (cp->minigeoms[desk].height < 1) 877 | cp->minigeoms[desk].height = 1; 878 | XMoveResizeWindow( 879 | pager->display, 880 | cp->miniwins[desk], 881 | cp->minigeoms[desk].x, 882 | cp->minigeoms[desk].y, 883 | cp->minigeoms[desk].width, 884 | cp->minigeoms[desk].height 885 | ); 886 | } 887 | 888 | static void 889 | mapclients(Pager *pager) 890 | { 891 | Client *cp; 892 | Cardinal i; 893 | 894 | for (i = 0; i < pager->nclients; i++) { 895 | cp = pager->clients[i]; 896 | if (pager->showingdesk) { 897 | unmapclient(pager, cp); 898 | } else { 899 | mapclient(pager, cp); 900 | } 901 | } 902 | } 903 | 904 | static void 905 | redrawall(Pager *pager) 906 | { 907 | Cardinal i, j; 908 | 909 | setdeskgeom(pager); 910 | drawdesktops(pager); 911 | for (i = 0; i < pager->nclients; i++) { 912 | for (j = 0; j < pager->ndesktops; j++) 913 | configureclient(pager, j, pager->clients[i]); 914 | drawclient(pager, pager->clients[i]); 915 | } 916 | } 917 | 918 | static int 919 | setpagersize(Pager *pager, unsigned int w, unsigned int h) 920 | { 921 | int ret; 922 | 923 | ret = (pager->geometry.width != w || pager->geometry.height != h); 924 | pager->geometry.width = w; 925 | pager->geometry.height = h; 926 | return ret; 927 | } 928 | 929 | static Window 930 | createminiwindow(Pager *pager, Window parent, int border) 931 | { 932 | return XCreateWindow( 933 | pager->display, 934 | parent, 935 | 0, 0, 1, 1, 936 | border, 937 | CopyFromParent, CopyFromParent, CopyFromParent, 938 | CWEventMask, 939 | &(XSetWindowAttributes){ 940 | .event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask 941 | } 942 | ); 943 | } 944 | 945 | static void 946 | setndesktops(Pager *pager) 947 | { 948 | Cardinal i; 949 | 950 | cleandesktops(pager); 951 | cleanclients(pager); 952 | pager->nclients = 0; 953 | pager->clients = NULL; 954 | pager->ndesktops = getcardprop( 955 | pager, 956 | pager->root, 957 | pager->atoms[_NET_NUMBER_OF_DESKTOPS] 958 | ); 959 | if (pager->ndesktops < 1) 960 | return; 961 | pager->desktops = ecalloc(pager->ndesktops, sizeof(*pager->desktops)); 962 | for (i = 0; i < pager->ndesktops; i++) { 963 | pager->desktops[i].miniwin = createminiwindow(pager, pager->window, 0); 964 | XMapWindow(pager->display, pager->desktops[i].miniwin); 965 | } 966 | } 967 | 968 | static Client * 969 | getclient(Pager *pager, Window window) 970 | { 971 | Cardinal i; 972 | 973 | for (i = 0; i < pager->nclients; i++) 974 | if (pager->clients[i]->clientwin == window) 975 | return pager->clients[i]; 976 | return NULL; 977 | } 978 | 979 | static void 980 | sethidden(Pager *pager, Client *cp) 981 | { 982 | cp->ishidden = hasstate(pager, cp->clientwin, pager->atoms[_NET_WM_STATE_HIDDEN]); 983 | } 984 | 985 | static void 986 | seturgency(Pager *pager, Client *cp) 987 | { 988 | cp->isurgent = 0; 989 | if (hasstate(pager, cp->clientwin, pager->atoms[_NET_WM_STATE_DEMANDS_ATTENTION])) 990 | cp->isurgent = true; 991 | else if (isurgent(pager, cp->clientwin)) 992 | cp->isurgent = true; 993 | } 994 | 995 | static void 996 | setdesktop(Pager *pager, Client *cp) 997 | { 998 | if (hasstate(pager, cp->clientwin, pager->atoms[_NET_WM_STATE_STICKY])) { 999 | cp->desk = ALLDESKTOPS; 1000 | return; 1001 | } 1002 | cp->desk = getcardprop(pager, cp->clientwin, pager->atoms[_NET_WM_DESKTOP]); 1003 | } 1004 | 1005 | static void 1006 | raiseclients(Pager *pager) 1007 | { 1008 | Cardinal i, j; 1009 | 1010 | for (i = 0; i < pager->nclients; i++) { 1011 | for (j = 0; j < pager->ndesktops; j++) { 1012 | XRaiseWindow( 1013 | pager->display, 1014 | pager->clients[i]->miniwins[j] 1015 | ); 1016 | } 1017 | } 1018 | } 1019 | 1020 | static void 1021 | setclients(Pager *pager) 1022 | { 1023 | Client **clients = NULL; 1024 | Window *wins = NULL; 1025 | Cardinal nclients = 0; 1026 | Cardinal i, j; 1027 | 1028 | if (pager->ndesktops > 0) { 1029 | nclients = getwinprop( 1030 | pager, 1031 | pager->root, 1032 | pager->atoms[_NET_CLIENT_LIST_STACKING], 1033 | &wins 1034 | ); 1035 | } 1036 | if (nclients > 0) 1037 | clients = ecalloc(nclients, sizeof(*clients)); 1038 | for (i = 0; i < nclients; i++) { 1039 | clients[i] = NULL; 1040 | for (j = 0; j < pager->nclients; j++) { 1041 | if (pager->clients[j] == NULL) 1042 | continue; 1043 | if (pager->clients[j]->clientwin != wins[i]) 1044 | continue; 1045 | clients[i] = pager->clients[j]; 1046 | pager->clients[j] = NULL; 1047 | break; 1048 | } 1049 | if (clients[i] != NULL) 1050 | continue; 1051 | clients[i] = emalloc(sizeof(*clients[i])); 1052 | *clients[i] = (Client) { 0 }; 1053 | clients[i]->clientwin = wins[i]; 1054 | preparewin(pager, wins[i]); 1055 | clients[i]->icon = geticonprop(pager, wins[i]); 1056 | sethidden(pager, clients[i]); 1057 | setdesktop(pager, clients[i]); 1058 | seturgency(pager, clients[i]); 1059 | setclientgeometry(pager, clients[i]); 1060 | clients[i]->minigeoms = ecalloc( 1061 | pager->ndesktops, 1062 | sizeof(*clients[i]->minigeoms) 1063 | ); 1064 | clients[i]->miniwins = ecalloc( 1065 | pager->ndesktops, 1066 | sizeof(*clients[i]->miniwins) 1067 | ); 1068 | for (j = 0; j < pager->ndesktops; j++) { 1069 | clients[i]->miniwins[j] = createminiwindow( 1070 | pager, 1071 | pager->desktops[j].miniwin, 1072 | pager->borders[BORDER_WIDTH] 1073 | ); 1074 | configureclient(pager, j, clients[i]); 1075 | } 1076 | drawclient(pager, clients[i]); 1077 | } 1078 | cleanclients(pager); 1079 | pager->clients = clients; 1080 | pager->nclients = nclients; 1081 | XFree(wins); 1082 | raiseclients(pager); 1083 | mapclients(pager); 1084 | } 1085 | 1086 | static void 1087 | setshowingdesk(Pager *pager) 1088 | { 1089 | bool prevshowingdesk; 1090 | 1091 | prevshowingdesk = pager->showingdesk; 1092 | pager->showingdesk = getcardprop( 1093 | pager, 1094 | pager->root, 1095 | pager->atoms[_NET_SHOWING_DESKTOP] 1096 | ); 1097 | if (prevshowingdesk != pager->showingdesk) { 1098 | mapclients(pager); 1099 | } 1100 | } 1101 | 1102 | static void 1103 | setcurrdesktop(Pager *pager) 1104 | { 1105 | Cardinal prevdesktop; 1106 | 1107 | prevdesktop = pager->activedesktop; 1108 | pager->activedesktop = getcardprop( 1109 | pager, 1110 | pager->root, 1111 | pager->atoms[_NET_CURRENT_DESKTOP] 1112 | ); 1113 | if (prevdesktop != pager->activedesktop) { 1114 | drawdesktops(pager); 1115 | } 1116 | } 1117 | 1118 | static void 1119 | setactive(Pager *pager) 1120 | { 1121 | Client *prevactive; 1122 | Window *wins; 1123 | 1124 | prevactive = pager->activeclient; 1125 | pager->activeclient = NULL; 1126 | if (getwinprop(pager, pager->root, pager->atoms[_NET_ACTIVE_WINDOW], &wins) > 0) { 1127 | pager->activeclient = getclient(pager, *wins); 1128 | XFree(wins); 1129 | } 1130 | if (prevactive != pager->activeclient) { 1131 | if (prevactive != NULL) 1132 | drawclient(pager, prevactive); 1133 | if (pager->activeclient != NULL) 1134 | drawclient(pager, pager->activeclient); 1135 | } 1136 | } 1137 | 1138 | static bool 1139 | between(int pos, int from, int len) 1140 | { 1141 | return pos >= from && pos < from + len; 1142 | } 1143 | 1144 | static void 1145 | mousemove(Pager *pager, Client *cp, Window win, int dx, int dy, Time time) 1146 | { 1147 | XEvent ev; 1148 | XRectangle *desk; 1149 | Cardinal i, newdesk, olddesk; 1150 | int status, newx, newy; 1151 | Window dw; 1152 | 1153 | if (cp->desk == ALLDESKTOPS) { 1154 | clientmsg( 1155 | pager, 1156 | cp->clientwin, 1157 | pager->atoms[_NET_ACTIVE_WINDOW], 1158 | (Cardinal[]){ 2, time, 0, 0, 0 } 1159 | ); 1160 | return; 1161 | } 1162 | XTranslateCoordinates( 1163 | pager->display, 1164 | win, 1165 | pager->window, 1166 | 0 - pager->borders[BORDER_WIDTH], 1167 | 0 - pager->borders[BORDER_WIDTH], 1168 | &newx, &newy, 1169 | &dw 1170 | ); 1171 | olddesk = newdesk = cp->desk; 1172 | XReparentWindow(pager->display, win, pager->window, newx, newy); 1173 | XSync(pager->display, False); 1174 | status = XGrabPointer( 1175 | pager->display, 1176 | win, 1177 | False, 1178 | MOUSEEVENTMASK, 1179 | GrabModeAsync, GrabModeAsync, 1180 | None, None, 1181 | time 1182 | ); 1183 | if (status != GrabSuccess) 1184 | goto done; 1185 | while (!XMaskEvent(pager->display, MOUSEEVENTMASK, &ev)) switch (ev.type) { 1186 | case ButtonRelease: 1187 | newx += ev.xbutton.x; 1188 | newy += ev.xbutton.y; 1189 | for (i = 0; i < pager->ndesktops; i++) { 1190 | desk = &pager->desktops[i].geometry; 1191 | if (!between(newx, desk->x, desk->width)) 1192 | continue; 1193 | if (!between(newy, desk->y, desk->height)) 1194 | continue; 1195 | newdesk = i; 1196 | break; 1197 | } 1198 | XUngrabPointer(pager->display, ev.xbutton.time); 1199 | time = ev.xbutton.time; 1200 | goto done; 1201 | case MotionNotify: 1202 | newx += ev.xmotion.x - dx; 1203 | newy += ev.xmotion.y - dy; 1204 | XMoveWindow(pager->display, win, newx, newy); 1205 | break; 1206 | } 1207 | done: 1208 | XReparentWindow( 1209 | pager->display, 1210 | win, 1211 | pager->desktops[olddesk].miniwin, 1212 | cp->minigeoms[olddesk].x, 1213 | cp->minigeoms[olddesk].y 1214 | ); 1215 | if (newdesk != olddesk) { 1216 | clientmsg( 1217 | pager, 1218 | cp->clientwin, 1219 | pager->atoms[_NET_WM_DESKTOP], 1220 | (Atom[]){newdesk, PAGER_ACTION, 0, 0, 0} 1221 | ); 1222 | } else { 1223 | clientmsg( 1224 | pager, 1225 | cp->clientwin, 1226 | pager->atoms[_NET_ACTIVE_WINDOW], 1227 | (Atom[]){2, time, 0, 0, 0} 1228 | ); 1229 | } 1230 | } 1231 | 1232 | static void 1233 | setcolor(Pager *pager, const char *value, XRenderColor *color) 1234 | { 1235 | XColor xcolor; 1236 | 1237 | if (!XParseColor(pager->display, pager->colormap, value, &xcolor)) { 1238 | warnx("%s: unknown color name", value); 1239 | return; 1240 | } 1241 | color->red = (xcolor.flags & DoRed) ? xcolor.red : 0x0000; 1242 | color->green = (xcolor.flags & DoGreen) ? xcolor.green : 0x0000; 1243 | color->blue = (xcolor.flags & DoBlue) ? xcolor.blue : 0x0000; 1244 | color->alpha = 0xFFFF; 1245 | } 1246 | 1247 | static void 1248 | setnumber(const char *value, int *retval) 1249 | { 1250 | char *endp; 1251 | long n; 1252 | 1253 | n = strtol(value, &endp, 10); 1254 | if (*endp != '\0' || n < 0 || n >= MAX_VALUE) { 1255 | warnx("%s: number invalid or out of range", value); 1256 | return; 1257 | } 1258 | *retval = n; 1259 | } 1260 | 1261 | static int 1262 | setgeometry(const char *value, XRectangle *root, XRectangle *rect) 1263 | { 1264 | unsigned int w, h; 1265 | int retval, flags, x, y; 1266 | 1267 | retval = 0; 1268 | flags = XParseGeometry(value, &x, &y, &w, &h); 1269 | if (flags & WidthValue) { 1270 | rect->width = w; 1271 | retval |= USSize; 1272 | } 1273 | if (flags & HeightValue) { 1274 | rect->height = h; 1275 | retval |= USSize; 1276 | } 1277 | if (flags & XValue) { 1278 | if (flags & XNegative) { 1279 | x += root->width; 1280 | x -= rect->width; 1281 | } 1282 | rect->x = x; 1283 | retval |= USPosition; 1284 | } 1285 | if (flags & YValue) { 1286 | if (flags & YNegative) { 1287 | y += root->height; 1288 | y -= rect->height; 1289 | } 1290 | rect->y = y; 1291 | retval |= USPosition; 1292 | } 1293 | return retval; 1294 | } 1295 | 1296 | static XrmDatabase 1297 | newxdb(Pager *pager, const char *str) 1298 | { 1299 | XrmDatabase xdb, tmp; 1300 | 1301 | tmp = NULL; 1302 | if ((xdb = XrmGetStringDatabase(str)) == NULL) 1303 | return NULL; 1304 | if (pager->xrm != NULL) 1305 | tmp = XrmGetStringDatabase(pager->xrm); 1306 | if (tmp != NULL) 1307 | XrmMergeDatabases(tmp, &xdb); 1308 | return xdb; 1309 | } 1310 | 1311 | static const char * 1312 | getresource(Pager *pager, XrmDatabase xdb, enum Resource res) 1313 | { 1314 | XrmRepresentation tmp; 1315 | XrmValue xval; 1316 | Bool success; 1317 | 1318 | success = XrmQGetResource( 1319 | xdb, 1320 | (XrmName[]){ 1321 | pager->application.name, 1322 | pager->resources[res].name, 1323 | NULLQUARK, 1324 | }, 1325 | (XrmClass[]){ 1326 | pager->application.class, 1327 | pager->resources[res].class, 1328 | NULLQUARK, 1329 | }, 1330 | &tmp, 1331 | &xval 1332 | ); 1333 | return success ? xval.addr : NULL; 1334 | } 1335 | 1336 | static void 1337 | loadresources(Pager *pager, const char *str) 1338 | { 1339 | XrmDatabase xdb; 1340 | const char *value; 1341 | enum Resource res; 1342 | 1343 | if (str == NULL) 1344 | return; 1345 | if ((xdb = newxdb(pager, str)) == NULL) 1346 | return; 1347 | for (res = 0; res < NRESOURCES; res++) { 1348 | if ((value = getresource(pager, xdb, res)) == NULL) 1349 | continue; 1350 | if (value[0] == '\0') 1351 | continue; 1352 | if (res < NCOLORS) { 1353 | setcolor(pager, value, &pager->colors[res].channels); 1354 | } else if (res < NCOLORS + NBORDERS) { 1355 | setnumber(value, &pager->borders[res - NCOLORS]); 1356 | } else if (res == RES_GEOMETRY) { 1357 | pager->geomflags = setgeometry( 1358 | value, 1359 | &pager->rootgeom, 1360 | &pager->geometry 1361 | ); 1362 | } 1363 | } 1364 | XrmDestroyDatabase(xdb); 1365 | } 1366 | 1367 | static void 1368 | fillcolors(Pager *pager) 1369 | { 1370 | Color *color; 1371 | size_t i; 1372 | 1373 | for (i = 0; i < NCOLORS; i++) { 1374 | color = &pager->colors[i]; 1375 | XRenderFillRectangle( 1376 | pager->display, 1377 | PictOpSrc, 1378 | color->picture, 1379 | &color->channels, 1380 | 0, 0, 1, 1 1381 | ); 1382 | } 1383 | } 1384 | 1385 | static void 1386 | xeventbuttonpress(Pager *pager, XEvent *e) 1387 | { 1388 | XButtonEvent *ev; 1389 | size_t i, j; 1390 | 1391 | ev = &e->xbutton; 1392 | if (ev->button != Button1) 1393 | return; 1394 | for (i = 0; i < pager->ndesktops; i++) { 1395 | if (ev->window == pager->desktops[i].miniwin) { 1396 | clientmsg( 1397 | pager, 1398 | None, 1399 | pager->atoms[_NET_CURRENT_DESKTOP], 1400 | (Atom[]){i, CurrentTime, 0, 0, 0} 1401 | ); 1402 | return; 1403 | } 1404 | } 1405 | for (i = 0; i < pager->nclients; i++) { 1406 | if (pager->clients[i] == NULL) 1407 | continue; 1408 | for (j = 0; j < pager->ndesktops; j++) { 1409 | if (ev->window != pager->clients[i]->miniwins[j]) 1410 | continue; 1411 | mousemove( 1412 | pager, 1413 | pager->clients[i], 1414 | ev->window, 1415 | ev->x, 1416 | ev->y, 1417 | ev->time 1418 | ); 1419 | return; 1420 | } 1421 | } 1422 | } 1423 | 1424 | static void 1425 | xeventclientmessage(Pager *pager, XEvent *e) 1426 | { 1427 | XClientMessageEvent *ev; 1428 | 1429 | ev = &e->xclient; 1430 | if ((Atom)ev->data.l[0] == pager->atoms[WM_DELETE_WINDOW]) { 1431 | pager->running = false; 1432 | } 1433 | } 1434 | 1435 | static void 1436 | xeventconfigurenotify(Pager *pager, XEvent *e) 1437 | { 1438 | XConfigureEvent *ev; 1439 | Client *c; 1440 | Cardinal j; 1441 | 1442 | ev = &e->xconfigure; 1443 | if (ev->window == pager->root) { 1444 | /* screen size changed (eg' a new monitor was plugged-in) */ 1445 | pager->rootgeom.width = ev->width; 1446 | pager->rootgeom.height = ev->height; 1447 | redrawall(pager); 1448 | return; 1449 | } 1450 | if (ev->window == pager->window) { 1451 | /* the pager window may have been resized */ 1452 | setpagersize(pager, ev->width, ev->height); 1453 | redrawall(pager); 1454 | } 1455 | if ((c = getclient(pager, ev->window)) != NULL) { 1456 | /* a client window window may have been moved or resized */ 1457 | c->clientgeom.x = ev->x; 1458 | c->clientgeom.y = ev->y; 1459 | c->clientgeom.width = ev->width; 1460 | c->clientgeom.height = ev->height; 1461 | for (j = 0; j < pager->ndesktops; j++) 1462 | configureclient(pager, j, c); 1463 | drawclient(pager, c); 1464 | mapclient(pager, c); 1465 | } 1466 | } 1467 | 1468 | static void 1469 | xeventpropertynotify(Pager *pager, XEvent *e) 1470 | { 1471 | Client *cp; 1472 | XPropertyEvent *ev; 1473 | char *str; 1474 | 1475 | /* 1476 | * This routine is called when the value of a property has been 1477 | * reset. If a known property was detected to be the reset one, 1478 | * its internal value is reset (with a set*() function). 1479 | * 1480 | * Note that the value can by some reason not have changed, and 1481 | * be equal to the previously stored one. In that case, the set 1482 | * function will do nothing. 1483 | * 1484 | * But, if the value has been changed from the internal one, it 1485 | * will then call the proper remapping or redrawing function to 1486 | * remap or redraw the client and/or desktop miniwindows. 1487 | */ 1488 | ev = &e->xproperty; 1489 | if (ev->state != PropertyNewValue) 1490 | return; 1491 | if (ev->atom == pager->atoms[_NET_CLIENT_LIST_STACKING]) { 1492 | /* the list of windows was reset */ 1493 | setclients(pager); 1494 | setactive(pager); 1495 | } else if (ev->atom == pager->atoms[_NET_ACTIVE_WINDOW]) { 1496 | /* the active window value was reset */ 1497 | setactive(pager); 1498 | } else if (ev->atom == pager->atoms[_NET_CURRENT_DESKTOP]) { 1499 | /* the current desktop value was reset */ 1500 | setcurrdesktop(pager); 1501 | } else if (ev->atom == pager->atoms[_NET_SHOWING_DESKTOP]) { 1502 | /* the value of the "showing desktop" state was reset */ 1503 | setshowingdesk(pager); 1504 | } else if (ev->atom == pager->atoms[_NET_NUMBER_OF_DESKTOPS]) { 1505 | /* the number of desktops value was reset */ 1506 | setndesktops(pager); 1507 | setdeskgeom(pager); 1508 | drawdesktops(pager); 1509 | setclients(pager); 1510 | setactive(pager); 1511 | } else if (ev->atom == pager->atoms[_NET_WM_STATE]) { 1512 | /* the list of states of a window (which may or may not include a relevant state) was reset */ 1513 | if ((cp = getclient(pager, ev->window)) == NULL) 1514 | return; 1515 | sethidden(pager, cp); 1516 | setdesktop(pager, cp); 1517 | seturgency(pager, cp); 1518 | drawclient(pager, cp); 1519 | mapclient(pager, cp); 1520 | } else if (ev->atom == pager->atoms[_NET_WM_DESKTOP]) { 1521 | /* the desktop of a window was reset */ 1522 | if ((cp = getclient(pager, ev->window)) == NULL) 1523 | return; 1524 | setdesktop(pager, cp); 1525 | mapclient(pager, cp); 1526 | } else if (ev->atom == XA_WM_HINTS) { 1527 | /* the urgency state of a window was reset */ 1528 | if ((cp = getclient(pager, ev->window)) == NULL) 1529 | return; 1530 | seturgency(pager, cp); 1531 | drawclient(pager, cp); 1532 | } else if (ev->atom == pager->atoms[_NET_WM_ICON]) { 1533 | if ((cp = getclient(pager, ev->window)) == NULL) 1534 | return; 1535 | if (cp->icon != None) 1536 | XRenderFreePicture(pager->display, cp->icon); 1537 | cp->icon = geticonprop(pager, cp->clientwin); 1538 | drawclient(pager, cp); 1539 | } else if (ev->atom == XA_RESOURCE_MANAGER) { 1540 | if (ev->window != pager->root) 1541 | return; 1542 | str = gettextprop( 1543 | pager->display, 1544 | pager->root, 1545 | XA_RESOURCE_MANAGER, 1546 | False 1547 | ); 1548 | if (str == NULL) 1549 | return; 1550 | loadresources(pager, str); 1551 | free(str); 1552 | fillcolors(pager); 1553 | redrawall(pager); 1554 | drawpager(pager); 1555 | } 1556 | } 1557 | 1558 | static void 1559 | clean(Pager *pager) 1560 | { 1561 | size_t i; 1562 | Color *color; 1563 | 1564 | cleanclients(pager); 1565 | cleandesktops(pager); 1566 | for (i = 0; i < NCOLORS; i++) { 1567 | color = &pager->colors[i]; 1568 | if (color->picture != None) 1569 | XRenderFreePicture(pager->display, color->picture); 1570 | if (color->pixmap != None) 1571 | XFreePixmap(pager->display, color->pixmap); 1572 | } 1573 | if (pager->icon != None) 1574 | XRenderFreePicture(pager->display, pager->icon); 1575 | if (pager->mask != None) 1576 | XRenderFreePicture(pager->display, pager->mask); 1577 | if (pager->window != None) 1578 | XDestroyWindow(pager->display, pager->window); 1579 | XCloseDisplay(pager->display); 1580 | } 1581 | 1582 | #define RED(v) ((((v) & 0xFF0000) >> 8) | (((v) & 0xFF0000) >> 16)) 1583 | #define GREEN(v) ((((v) & 0x00FF00) ) | (((v) & 0x00FF00) >> 8)) 1584 | #define BLUE(v) ((((v) & 0x0000FF) << 8) | (((v) & 0x0000FF) )) 1585 | 1586 | static void 1587 | setup(Pager *pager, int argc, char *argv[], char *name, char *geomstr) 1588 | { 1589 | static char *atomnames[NATOMS] = { 1590 | #define X(atom) [atom] = #atom, 1591 | ATOMS 1592 | #undef X 1593 | }; 1594 | static struct { 1595 | const char *class, *name; 1596 | unsigned int value; 1597 | } resdefs[NRESOURCES] = { 1598 | #define X(i, c, n, v) [i] = { .class = c, .name = n, .value = v, }, 1599 | RESOURCES 1600 | #undef X 1601 | }; 1602 | XpmAttributes xpma; 1603 | XRenderPictFormat *maskformat; 1604 | Pixmap icon = None; 1605 | Pixmap mask = None; 1606 | Resource *resource; 1607 | Color *color; 1608 | size_t i; 1609 | int success, status, screen; 1610 | 1611 | if (name == NULL) 1612 | name = APP_NAME; 1613 | 1614 | /* connect to server */ 1615 | if ((pager->display = XOpenDisplay(NULL)) == NULL) { 1616 | warnx("could not connect to X server"); 1617 | goto error; 1618 | } 1619 | (void)XSetErrorHandler(xerror); 1620 | screen = DefaultScreen(pager->display); 1621 | pager->root = RootWindow(pager->display, screen); 1622 | pager->rootgeom.width = DisplayWidth(pager->display, screen); 1623 | pager->rootgeom.height = DisplayHeight(pager->display, screen); 1624 | (void)XSelectInput(pager->display, pager->root, PropertyChangeMask); 1625 | preparewin(pager, pager->root); 1626 | 1627 | /* get visual format */ 1628 | pager->colormap = XDefaultColormap(pager->display, screen); 1629 | pager->visual = DefaultVisual(pager->display, screen); 1630 | pager->depth = DefaultDepth(pager->display, screen); 1631 | pager->format = XRenderFindVisualFormat(pager->display, pager->visual); 1632 | if (pager->format == NULL) { 1633 | warnx("could not find XRender visual format"); 1634 | goto error; 1635 | } 1636 | maskformat = XRenderFindStandardFormat( 1637 | pager->display, 1638 | PictStandardA1 1639 | ); 1640 | if (maskformat == NULL) { 1641 | warnx("could not find XRender visual format"); 1642 | goto error; 1643 | } 1644 | pager->formatARGB = XRenderFindStandardFormat( 1645 | pager->display, 1646 | PictStandardARGB32 1647 | ); 1648 | if (pager->formatARGB == NULL) { 1649 | warnx("could not find XRender visual format"); 1650 | goto error; 1651 | } 1652 | 1653 | /* intern atoms */ 1654 | success = XInternAtoms( 1655 | pager->display, 1656 | atomnames, 1657 | NATOMS, 1658 | False, 1659 | pager->atoms 1660 | ); 1661 | if (!success) { 1662 | warnx("could not intern X atoms"); 1663 | goto error; 1664 | } 1665 | 1666 | /* intern quarks and set colors */ 1667 | XrmInitialize(); 1668 | pager->application.class = XrmPermStringToQuark(APP_CLASS); 1669 | pager->application.name = XrmPermStringToQuark(name); 1670 | for (i = 0; i < NRESOURCES; i++) { 1671 | resource = &pager->resources[i]; 1672 | resource->class = XrmPermStringToQuark(resdefs[i].class); 1673 | resource->name = XrmPermStringToQuark(resdefs[i].name); 1674 | if (i < NCOLORS) { 1675 | pager->colors[i].channels = (XRenderColor){ 1676 | .red = RED(resdefs[i].value), 1677 | .green = GREEN(resdefs[i].value), 1678 | .blue = BLUE(resdefs[i].value), 1679 | .alpha = 0xFFFF, 1680 | }; 1681 | } else if (i < NCOLORS + NBORDERS) { 1682 | pager->borders[i-NCOLORS] = resdefs[i].value; 1683 | } else if (i == RES_GEOMETRY) { 1684 | pager->geometry.width = resdefs[i].value; 1685 | pager->geometry.height = resdefs[i].value; 1686 | } 1687 | } 1688 | 1689 | /* create window */ 1690 | pager->window = XCreateWindow( 1691 | pager->display, 1692 | pager->root, 1693 | 0, 0, 1, 1, 0, /* x, y, width, height, border */ 1694 | CopyFromParent, CopyFromParent, CopyFromParent, 1695 | CWEventMask, 1696 | &(XSetWindowAttributes){ 1697 | .event_mask = StructureNotifyMask, 1698 | } 1699 | ); 1700 | if (pager->window == None) { 1701 | warnx("could not create window"); 1702 | goto error; 1703 | } 1704 | XSetWMProtocols( 1705 | pager->display, 1706 | pager->window, 1707 | (Atom[]) { /* protocols */ 1708 | pager->atoms[WM_DELETE_WINDOW], 1709 | }, 1710 | 1 /* number of protocols */ 1711 | ); 1712 | 1713 | /* create color layers */ 1714 | for (i = 0; i < NCOLORS; i++) { 1715 | color = &pager->colors[i]; 1716 | color->pixmap = XCreatePixmap( 1717 | pager->display, 1718 | pager->window, 1719 | 1, 1, 1720 | pager->depth 1721 | ); 1722 | if (color->pixmap == None) { 1723 | warnx("could not create pixmap"); 1724 | goto error; 1725 | } 1726 | color->picture = XRenderCreatePicture( 1727 | pager->display, 1728 | color->pixmap, 1729 | pager->format, 1730 | CPRepeat, 1731 | &(XRenderPictureAttributes){ 1732 | .repeat = RepeatNormal, 1733 | } 1734 | ); 1735 | if (color->pixmap == None) { 1736 | warnx("could not create picture"); 1737 | goto error; 1738 | } 1739 | } 1740 | 1741 | /* load X resources and fill color pictures */ 1742 | loadresources(pager, XResourceManagerString(pager->display)); 1743 | if (geomstr != NULL) { 1744 | pager->geomflags = setgeometry( 1745 | geomstr, 1746 | &pager->rootgeom, 1747 | &pager->geometry 1748 | ); 1749 | } 1750 | fillcolors(pager); 1751 | 1752 | /* set window size and properties */ 1753 | XMoveResizeWindow( 1754 | pager->display, 1755 | pager->window, 1756 | pager->geometry.x, 1757 | pager->geometry.y, 1758 | pager->geometry.width, 1759 | pager->geometry.height 1760 | ); 1761 | XmbSetWMProperties( 1762 | pager->display, 1763 | pager->window, 1764 | APP_CLASS, /* title name */ 1765 | APP_CLASS, /* icon name */ 1766 | argv, 1767 | argc, 1768 | &(XSizeHints){ 1769 | .flags = pager->geomflags, 1770 | }, 1771 | &(XWMHints){ 1772 | .flags = IconWindowHint | StateHint | WindowGroupHint, 1773 | .initial_state = WithdrawnState, 1774 | .window_group = pager->window, 1775 | .icon_window = pager->window, 1776 | }, 1777 | &(XClassHint){ 1778 | .res_class = APP_CLASS, 1779 | .res_name = name, 1780 | } 1781 | ); 1782 | 1783 | /* set default icon */ 1784 | memset(&xpma, 0, sizeof(xpma)); 1785 | xpma = (XpmAttributes){ 1786 | .valuemask = XpmVisual, 1787 | .visual = pager->visual, 1788 | }; 1789 | status = XpmCreatePixmapFromData( 1790 | pager->display, 1791 | pager->window, 1792 | x_xpm, 1793 | &icon, 1794 | &mask, 1795 | &xpma 1796 | ); 1797 | if (status != XpmSuccess) { 1798 | warnx("could not load xpm"); 1799 | goto error; 1800 | } 1801 | if (!FLAG(xpma.valuemask, XpmSize | XpmVisual)) { 1802 | warnx("could not load xpm"); 1803 | goto error; 1804 | } 1805 | pager->icon = XRenderCreatePicture( 1806 | pager->display, 1807 | icon, 1808 | pager->format, 1809 | 0, NULL 1810 | ); 1811 | pager->mask = XRenderCreatePicture( 1812 | pager->display, 1813 | mask, 1814 | maskformat, 1815 | 0, NULL 1816 | ); 1817 | XFreePixmap(pager->display, icon); 1818 | XFreePixmap(pager->display, mask); 1819 | 1820 | /* get clients and desktops */ 1821 | setndesktops(pager); 1822 | setdeskgeom(pager); 1823 | setcurrdesktop(pager); 1824 | setclients(pager); 1825 | setactive(pager); 1826 | drawdesktops(pager); 1827 | drawpager(pager); 1828 | mapclients(pager); 1829 | 1830 | XMapWindow(pager->display, pager->window); 1831 | return; 1832 | error: 1833 | if (icon != None) 1834 | XFreePixmap(pager->display, icon); 1835 | if (mask != None) 1836 | XFreePixmap(pager->display, mask); 1837 | clean(pager); 1838 | exit(EXIT_FAILURE); 1839 | } 1840 | 1841 | static void 1842 | setcorner(Pager *pager, char *grid[], char *borders[]) 1843 | { 1844 | setnumber(grid[0], &pager->nrows); 1845 | if (pager->nrows < 1) 1846 | errx(EXIT_FAILURE, "%s: invalid number of rows", grid[0]); 1847 | setnumber(grid[1], &pager->ncols); 1848 | if (pager->ncols < 1) 1849 | errx(EXIT_FAILURE, "%s: invalid number of columns", grid[1]); 1850 | if (borders == NULL) 1851 | return; 1852 | switch (borders[0][0]) { 1853 | case 'l': case 'L': 1854 | pager->orient = _NET_WM_ORIENTATION_HORZ; 1855 | switch (borders[1][0]) { 1856 | case 'l': case 'L': 1857 | case 'r': case 'R': 1858 | errx(EXIT_FAILURE, "%s: repeated border", borders[1]); 1859 | case 't': case 'T': 1860 | pager->corner = _NET_WM_TOPLEFT; 1861 | break; 1862 | case 'b': case 'B': 1863 | pager->corner = _NET_WM_BOTTOMLEFT; 1864 | break; 1865 | default: 1866 | errx(EXIT_FAILURE, "%s: invalid border", borders[1]); 1867 | } 1868 | break; 1869 | case 'r': case 'R': 1870 | pager->orient = _NET_WM_ORIENTATION_HORZ; 1871 | switch (borders[1][0]) { 1872 | case 'l': case 'L': 1873 | case 'r': case 'R': 1874 | errx(EXIT_FAILURE, "%s: repeated border", borders[1]); 1875 | case 't': case 'T': 1876 | pager->corner = _NET_WM_TOPRIGHT; 1877 | break; 1878 | case 'b': case 'B': 1879 | pager->corner = _NET_WM_BOTTOMRIGHT; 1880 | break; 1881 | default: 1882 | errx(EXIT_FAILURE, "%s: invalid border", borders[1]); 1883 | } 1884 | break; 1885 | case 't': case 'T': 1886 | pager->orient = _NET_WM_ORIENTATION_VERT; 1887 | switch (borders[1][0]) { 1888 | case 'l': case 'L': 1889 | pager->corner = _NET_WM_TOPLEFT; 1890 | break; 1891 | case 'r': case 'R': 1892 | pager->corner = _NET_WM_TOPRIGHT; 1893 | break; 1894 | case 't': case 'T': 1895 | case 'b': case 'B': 1896 | errx(EXIT_FAILURE, "%s: repeated border", borders[1]); 1897 | default: 1898 | errx(EXIT_FAILURE, "%s: invalid border", borders[1]); 1899 | } 1900 | pager->corner = _NET_WM_TOPLEFT; 1901 | break; 1902 | case 'b': case 'B': 1903 | pager->orient = _NET_WM_ORIENTATION_VERT; 1904 | switch (borders[1][0]) { 1905 | case 'l': case 'L': 1906 | pager->corner = _NET_WM_BOTTOMLEFT; 1907 | break; 1908 | case 'r': case 'R': 1909 | pager->corner = _NET_WM_BOTTOMRIGHT; 1910 | break; 1911 | case 't': case 'T': 1912 | case 'b': case 'B': 1913 | errx(EXIT_FAILURE, "%s: repeated border", borders[1]); 1914 | default: 1915 | errx(EXIT_FAILURE, "%s: invalid border", borders[1]); 1916 | } 1917 | break; 1918 | default: 1919 | errx(EXIT_FAILURE, "%s: invalid border", borders[0]); 1920 | } 1921 | } 1922 | 1923 | int 1924 | main(int argc, char *argv[]) 1925 | { 1926 | Pager pager = { 0 }; 1927 | XEvent ev; 1928 | void (*xevents[LASTEvent])(Pager *, XEvent *) = { 1929 | [ButtonPress] = xeventbuttonpress, 1930 | [ConfigureNotify] = xeventconfigurenotify, 1931 | [ClientMessage] = xeventclientmessage, 1932 | [PropertyNotify] = xeventpropertynotify, 1933 | }; 1934 | char *name; 1935 | char *geometry; 1936 | int i; 1937 | 1938 | geometry = NULL; 1939 | pager.xrm = getenv("RESOURCES_DATA"); 1940 | name = getenv("RESOURCES_NAME"); 1941 | if (name != NULL) 1942 | ; 1943 | else if (argv[0] == NULL || argv[0][0] == '\0') 1944 | name = NULL; 1945 | else if ((name = strrchr(argv[0], '/')) != NULL) 1946 | name++; 1947 | else 1948 | name = argv[0]; 1949 | for (i = 1; i < argc; i++) { 1950 | if (argv[i][0] != '-') 1951 | break; 1952 | if (strcmp(argv[i], "-name") == 0) { 1953 | name = argv[++i]; 1954 | } else if (strcmp(argv[i], "-xrm") == 0) { 1955 | pager.xrm = argv[++i]; 1956 | } else if (strcmp(argv[i], "-geometry") == 0) { 1957 | geometry = argv[++i]; 1958 | } else if (strcmp(argv[i], "--")) { 1959 | i++; 1960 | break; 1961 | } else { 1962 | usage(); 1963 | } 1964 | } 1965 | switch (argc - i) { 1966 | case 4: 1967 | setcorner(&pager, argv + i, argv + i + 2); 1968 | break; 1969 | case 2: 1970 | setcorner(&pager, argv + i, NULL); 1971 | break; 1972 | default: 1973 | usage(); 1974 | } 1975 | 1976 | setup(&pager, argc, argv, name, geometry); 1977 | pager.running = true; 1978 | while (pager.running && !XNextEvent(pager.display, &ev)) 1979 | if (ev.type < LASTEvent && xevents[ev.type] != NULL) 1980 | (*xevents[ev.type])(&pager, &ev); 1981 | clean(&pager); 1982 | return EXIT_SUCCESS; 1983 | } 1984 | -------------------------------------------------------------------------------- /x.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static char * x_xpm[] = { 3 | "16 16 5 1", 4 | " c None", 5 | ". c #A40000", 6 | "+ c #EF2929", 7 | "@ c #000000", 8 | "# c #CC0000", 9 | " ", 10 | " ", 11 | "..... ..", 12 | " .+++@ .#@", 13 | " .+#.@ .#@ ", 14 | " .+#.@ .#@ ", 15 | " .+#.@ .#@ ", 16 | " .#@ .#@ ", 17 | " .#@ .#@ ", 18 | " .#@ .+#.@ ", 19 | " .#@ .+#.@ ", 20 | " .#@ .+#.@ ", 21 | ".#@ ....@ ", 22 | "@@ @@@@@", 23 | " ", 24 | " "}; 25 | --------------------------------------------------------------------------------