1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
|
/* evilwm - minimalist window manager for X11
* Copyright (C) 1999-2022 Ciaran Anscomb <evilwm@6809.org.uk>
* see README for license and other details. */
// Miscellaneous utility functions
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <unistd.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xproto.h>
#include "client.h"
#include "display.h"
#include "events.h"
#include "evilwm.h"
#include "log.h"
#include "screen.h"
#include "util.h"
// For get_property()
#define MAXIMUM_PROPERTY_LENGTH 4096
// Error handler interaction
int ignore_xerror = 0;
volatile Window initialising = None;
// Spawn a subprocess by fork()ing twice so we don't have to worry about
// SIGCHLDs.
void spawn(const char *const cmd[]) {
struct screen *current_screen = find_current_screen();
pid_t pid;
if (current_screen && current_screen->display)
putenv(current_screen->display);
if (!(pid = fork())) {
// Put first fork in a new session
setsid();
switch (fork()) {
// execvp()'s prototype is (char *const *) suggesting that it
// modifies the contents of the strings. The prototype is this
// way due to SUS maintaining compatability with older code.
// However, execvp guarantees not to modify argv, so the following
// cast is valid.
case 0: execvp(cmd[0], (char *const *)cmd); break;
default: _exit(0);
}
}
if (pid > 0)
wait(NULL);
}
// When something we do raises an X error, we get sent here. There are several
// specific types of error that we know we want to ignore, or that indicate a
// fatal error. For the rest, cease managing the client, as it should indicate
// that the window has disappeared.
int handle_xerror(Display *dsply, XErrorEvent *e) {
struct client *c;
(void)dsply; // unused
LOG_ENTER("handle_xerror(error=%d, request=%d/%d, resourceid=%lx)", e->error_code, e->request_code, e->minor_code, e->resourceid);
// Some parts of the code deliberately disable error checking.
if (ignore_xerror) {
LOG_DEBUG("ignoring...\n");
LOG_LEAVE();
return 0;
}
// client_manage_new() sets initialising to non-None to test if a
// window still exists. If we end up here, the test failed, so
// indicate that by setting it back to None.
if (initialising != None && e->resourceid == initialising) {
LOG_DEBUG("error caught while initialising window=%lx\n", (unsigned long)initialising);
initialising = None;
LOG_LEAVE();
return 0;
}
// This error is generally raised when trying to start evilwm while
// another window manager is running.
if (e->error_code == BadAccess && e->request_code == X_ChangeWindowAttributes) {
LOG_ERROR("root window unavailable (maybe another wm is running?)\n");
exit(1);
}
// Batching of events sometimes leads to us calling XSetInputFocus()
// for enter events to windows that were subsequently deleted. Ignore
// these errors.
if (e->request_code == X_SetInputFocus) {
LOG_DEBUG("ignoring harmless error caused by possible race\n");
LOG_LEAVE();
return 0;
}
// For any other raised error, remove the client that triggered it from
// management, as it's probably gone.
c = find_client(e->resourceid);
if (c) {
LOG_DEBUG("flagging client for removal\n");
c->remove = 1;
need_client_tidy = 1;
} else {
LOG_DEBUG("unknown error: not handling\n");
}
LOG_LEAVE();
return 0;
}
// Simplify calls to XQueryPointer(), and make destination pointers optional
Bool get_pointer_root_xy(Window w, int *x, int *y) {
Window root_r, child_r;
int root_x_r, root_y_r;
int win_x_r, win_y_r;
unsigned mask_r;
if (!x)
x = &root_x_r;
if (!y)
y = &root_y_r;
return XQueryPointer(display.dpy, w, &root_r, &child_r, x, y, &win_x_r, &win_y_r, &mask_r);
}
// Wraps XGetWindowProperty()
void *get_property(Window w, Atom property, Atom req_type,
unsigned long *nitems_return) {
Atom actual_type;
int actual_format;
unsigned long bytes_after;
unsigned char *prop;
if (XGetWindowProperty(display.dpy, w, property,
0L, MAXIMUM_PROPERTY_LENGTH / 4, False,
req_type, &actual_type, &actual_format,
nitems_return, &bytes_after, &prop) == Success) {
if (actual_type == req_type)
return (void *)prop;
XFree(prop);
}
return NULL;
}
// Determine the normal border size for a window. MWM hints seem to be the
// only way clients can signal they don't want a border.
int window_normal_border(Window w) {
int bw = option.bw;
PropMwmHints *mprop;
unsigned long nitems;
if ( (mprop = get_property(w, X_ATOM(_MOTIF_WM_HINTS), X_ATOM(_MOTIF_WM_HINTS), &nitems)) ) {
if (nitems >= PROP_MWM_HINTS_ELEMENTS
&& (mprop->flags & MWM_HINTS_DECORATIONS)
&& !(mprop->decorations & MWM_DECOR_ALL)
&& !(mprop->decorations & MWM_DECOR_BORDER)) {
bw = 0;
}
XFree(mprop);
}
return bw;
}
// interruptibleXNextEvent() is taken from the Blender source and comes with
// the following copyright notice:
//
// Copyright (c) Mark J. Kilgard, 1994, 1995, 1996.
//
// This program is freely distributable without licensing fees and is provided
// without guarantee or warrantee expressed or implied. This program is -not-
// in the public domain.
// Unlike XNextEvent, if a signal arrives, interruptibleXNextEvent will return
// zero.
int interruptibleXNextEvent(XEvent *event) {
fd_set fds;
int rc;
int dpy_fd = ConnectionNumber(display.dpy);
for (;;) {
if (XPending(display.dpy)) {
XNextEvent(display.dpy, event);
return 1;
}
FD_ZERO(&fds);
FD_SET(dpy_fd, &fds);
rc = select(dpy_fd + 1, &fds, NULL, NULL, NULL);
if (rc < 0) {
if (errno == EINTR) {
return 0;
} else {
LOG_ERROR("interruptibleXNextEvent(): select()\n");
}
}
}
}
// Remove enter events from the queue, preserving only the last one
// corresponding to "except"s parent.
void discard_enter_events(struct client *except) {
XEvent tmp, putback_ev;
int putback = 0;
XSync(display.dpy, False);
while (XCheckMaskEvent(display.dpy, EnterWindowMask, &tmp)) {
if (tmp.xcrossing.window == except->parent) {
memcpy(&putback_ev, &tmp, sizeof(XEvent));
putback = 1;
}
}
if (putback) {
XPutBackEvent(display.dpy, &putback_ev);
}
}
|