File: util.c

package info (click to toggle)
evilwm 1.4.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 516 kB
  • sloc: ansic: 3,893; perl: 551; sh: 100; makefile: 99
file content (230 lines) | stat: -rw-r--r-- 6,250 bytes parent folder | download | duplicates (2)
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);
	}
}