1/*
2
3Copyright 1988, 1998  The Open Group
4
5Permission to use, copy, modify, distribute, and sell this software and its
6documentation for any purpose is hereby granted without fee, provided that
7the above copyright notice appear in all copies and that both that
8copyright notice and this permission notice appear in supporting
9documentation.
10
11The above copyright notice and this permission notice shall be included
12in all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR
18OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20OTHER DEALINGS IN THE SOFTWARE.
21
22Except as contained in this notice, the name of The Open Group shall
23not be used in advertising or otherwise to promote the sale, use or
24other dealings in this Software without prior written authorization
25from The Open Group.
26
27*/
28
29/*
30 * xkill - simple program for destroying unwanted clients
31 * Author:  Jim Fulton, MIT X Consortium; Dana Chee, Bellcore
32 */
33
34/*
35 * Warning, this is a very dangerous client....
36 */
37
38#ifdef HAVE_CONFIG_H
39# include "config.h"
40#endif
41
42#include <stdio.h>
43#include <stdlib.h>
44#include <ctype.h>
45
46#include <X11/Xos.h>
47#include <X11/Xlib.h>
48#include <X11/cursorfont.h>
49#include <X11/Xproto.h>
50
51#include <X11/Xmu/WinUtil.h>
52
53static char *ProgramName;
54
55#define SelectButtonAny (-1)
56#define SelectButtonFirst (-2)
57
58static int parse_button ( const char *s, int *buttonp );
59static XID get_window_id ( Display *dpy, int screen, int button, const char *msg );
60static int catch_window_errors ( Display *dpy, XErrorEvent *ev );
61static int kill_all_windows ( Display *dpy, int screenno, Bool top );
62static int verify_okay_to_kill ( Display *dpy, int screenno );
63static Bool wm_state_set ( Display *dpy, Window win );
64static Bool wm_running ( Display *dpy, int screenno );
65
66static void _X_NORETURN
67Exit(int code, Display *dpy)
68{
69    if (dpy) {
70	XCloseDisplay (dpy);
71    }
72    exit (code);
73}
74
75static void _X_NORETURN
76usage(const char *errmsg)
77{
78    const char *options =
79"where options include:\n"
80"    -display displayname    X server to contact\n"
81"    -id resource            resource whose client is to be killed\n"
82"    -frame                  don't ignore window manager frames\n"
83"    -button number          specific button to be pressed to select window\n"
84"    -all                    kill all clients with top level windows\n"
85"    -version                print version and exit\n"
86"\n";
87
88    if (errmsg != NULL)
89        fprintf (stderr, "%s: %s\n\n", ProgramName, errmsg);
90
91    fprintf (stderr, "usage:  %s [-option ...]\n%s",
92	     ProgramName, options);
93    Exit (1, NULL);
94}
95
96int
97main(int argc, char *argv[])
98{
99    Display *dpy = NULL;
100    char *displayname = NULL;		/* name of server to contact */
101    int screenno;			/* screen number of dpy */
102    XID id = None;			/* resource to kill */
103    char *button_name = NULL;		/* name of button for window select */
104    int button;				/* button number or negative for all */
105    Bool kill_all = False;
106    Bool top = False;
107
108    ProgramName = argv[0];
109    button = SelectButtonFirst;
110
111    for (int i = 1; i < argc; i++) {
112	char *arg = argv[i];
113
114	if (arg[0] == '-') {
115	    switch (arg[1]) {
116	      case 'd':			/* -display displayname */
117		if (++i >= argc) usage ("-display requires an argument");
118		displayname = argv[i];
119		continue;
120	      case 'i':			/* -id resourceid */
121		if (++i >= argc) usage ("-id requires an argument");
122		id = strtoul (argv[i], NULL, 0);
123		if (id == 0 || id >= 0xFFFFFFFFU) {
124		    fprintf (stderr, "%s:  invalid id \"%s\"\n",
125			     ProgramName, argv[i]);
126		    Exit (1, dpy);
127		}
128		continue;
129	      case 'b':			/* -button number */
130		if (++i >= argc) usage ("-button requires an argument");
131		button_name = argv[i];
132		continue;
133	      case 'f':			/* -frame */
134		top = True;
135		continue;
136	      case 'a':			/* -all */
137		kill_all = True;
138		continue;
139              case 'v':
140                puts(PACKAGE_STRING);
141                exit(0);
142	      default:
143                fprintf(stderr, "%s: unrecognized argument %s\n\n",
144                        ProgramName, arg);
145		usage (NULL);
146	    }
147	} else {
148            fprintf(stderr, "%s: unrecognized argument %s\n\n",
149                    ProgramName, arg);
150            usage (NULL);
151	}
152    }					/* end for */
153
154    dpy = XOpenDisplay (displayname);
155    if (!dpy) {
156	fprintf (stderr, "%s:  unable to open display \"%s\"\n",
157		 ProgramName, XDisplayName (displayname));
158	Exit (1, dpy);
159    }
160    screenno = DefaultScreen (dpy);
161
162    if (kill_all) {
163	if (verify_okay_to_kill (dpy, screenno))
164	  kill_all_windows (dpy, screenno, top);
165	Exit (0, dpy);
166    }
167
168    /*
169     * if no id was given, we need to choose a window
170     */
171
172    if (id == None) {
173	if (!button_name)
174	    button_name = XGetDefault (dpy, ProgramName, "Button");
175
176	if (button_name && !parse_button (button_name, &button)) {
177	    fprintf (stderr, "%s:  invalid button specification \"%s\"\n",
178		     ProgramName, button_name);
179	    Exit (1, dpy);
180	}
181
182	if (button >= 0 || button == SelectButtonFirst) {
183	    unsigned char pointer_map[256];	 /* 8 bits of pointer num */
184	    int count;
185	    unsigned int ub = (unsigned int) button;
186
187
188	    count = XGetPointerMapping (dpy, pointer_map, 256);
189	    if (count <= 0) {
190		fprintf (stderr,
191			 "%s:  no pointer mapping, can't select window\n",
192			 ProgramName);
193		Exit (1, dpy);
194	    }
195
196	    if (button >= 0) {			/* check button */
197		int j;
198
199		for (j = 0; j < count; j++) {
200		    if (ub == (unsigned int) pointer_map[j]) break;
201		}
202		if (j == count) {
203		    fprintf (stderr,
204	 "%s:  no button number %u in pointer map, can't select window\n",
205			     ProgramName, ub);
206		    Exit (1, dpy);
207	        }
208	    } else {				/* get first entry */
209		button = (int) ((unsigned int) pointer_map[0]);
210	    }
211	}
212	if ((id = get_window_id (dpy, screenno, button,
213				"the window whose client you wish to kill"))) {
214	    if (id == RootWindow(dpy,screenno)) id = None;
215	    else if (!top) {
216		XID indicated = id;
217		if ((id = XmuClientWindow(dpy, indicated)) == indicated) {
218
219		    /* Try not to kill the window manager when the user
220		     * indicates an icon to xkill.
221		     */
222
223		    if (! wm_state_set(dpy, id) && wm_running(dpy, screenno))
224			id = None;
225
226		}
227	    }
228	}
229    }
230
231    if (id != None) {
232	printf ("%s:  killing creator of resource 0x%lx\n", ProgramName, id);
233	XSync (dpy, 0);			/* give xterm a chance */
234	XKillClient (dpy, id);
235	XSync (dpy, 0);
236    }
237
238    Exit (0, dpy);
239    /*NOTREACHED*/
240    return 0;
241}
242
243static int
244parse_button(const char *s, int *buttonp)
245{
246    if (strcasecmp (s, "any") == 0) {
247	*buttonp = SelectButtonAny;
248	return (1);
249    }
250
251    /* check for non-numeric input */
252    for (const char *cp = s; *cp; cp++) {
253	if (!(isascii (*cp) && isdigit (*cp))) return (0);  /* bogus name */
254    }
255
256    *buttonp = atoi (s);
257    return (1);
258}
259
260static XID
261get_window_id(Display *dpy, int screen, int button, const char *msg)
262{
263    Cursor cursor;		/* cursor to use when selecting */
264    Window root;		/* the current root */
265    Window retwin = None;	/* the window that got selected */
266    int retbutton = -1;		/* button used to select window */
267    int pressed = 0;		/* count of number of buttons pressed */
268
269#define MASK (ButtonPressMask | ButtonReleaseMask)
270
271    root = RootWindow (dpy, screen);
272    cursor = XCreateFontCursor (dpy, XC_pirate);
273    if (cursor == None) {
274	fprintf (stderr, "%s:  unable to create selection cursor\n",
275		 ProgramName);
276	Exit (1, dpy);
277    }
278
279    printf ("Select %s with ", msg);
280    if (button == -1)
281      printf ("any button");
282    else
283      printf ("button %d", button);
284    printf ("....\n");
285    XSync (dpy, 0);			/* give xterm a chance */
286
287    if (XGrabPointer (dpy, root, False, MASK, GrabModeSync, GrabModeAsync,
288    		      None, cursor, CurrentTime) != GrabSuccess) {
289	fprintf (stderr, "%s:  unable to grab cursor\n", ProgramName);
290	Exit (1, dpy);
291    }
292
293    /* from dsimple.c in xwininfo */
294    while (retwin == None || pressed != 0) {
295	XEvent event;
296
297	XAllowEvents (dpy, SyncPointer, CurrentTime);
298	XWindowEvent (dpy, root, MASK, &event);
299	switch (event.type) {
300	  case ButtonPress:
301	    if (retwin == None) {
302		retbutton = event.xbutton.button;
303		retwin = ((event.xbutton.subwindow != None) ?
304			  event.xbutton.subwindow : root);
305	    }
306	    pressed++;
307	    continue;
308	  case ButtonRelease:
309	    if (pressed > 0) pressed--;
310	    continue;
311	}					/* end switch */
312    }						/* end for */
313
314    XUngrabPointer (dpy, CurrentTime);
315    XFreeCursor (dpy, cursor);
316    XSync (dpy, 0);
317
318    return ((button == -1 || retbutton == button) ? retwin : None);
319}
320
321
322static int
323catch_window_errors(_X_UNUSED Display *dpy, _X_UNUSED XErrorEvent *ev)
324{
325    return 0;
326}
327
328static int
329kill_all_windows(Display *dpy, int screenno, Bool top)
330{
331    Window root = RootWindow (dpy, screenno);
332    Window dummywindow;
333    Window *children;
334    unsigned int nchildren;
335    unsigned int i;
336    XWindowAttributes attr;
337
338    XSync (dpy, 0);
339    XSetErrorHandler (catch_window_errors);
340
341    nchildren = 0;
342    XQueryTree (dpy, root, &dummywindow, &dummywindow, &children, &nchildren);
343    if (!top) {
344	for (i = 0; i < nchildren; i++) {
345	    if (XGetWindowAttributes(dpy, children[i], &attr) &&
346		(attr.map_state == IsViewable))
347		children[i] = XmuClientWindow(dpy, children[i]);
348	    else
349		children[i] = 0;
350	}
351    }
352    for (i = 0; i < nchildren; i++) {
353	if (children[i])
354	    XKillClient (dpy, children[i]);
355    }
356    XFree ((char *)children);
357
358    XSync (dpy, 0);
359    XSetErrorHandler (NULL);		/* pretty stupid way to do things... */
360
361    return 0;
362}
363
364/*
365 * ask the user to press in the root with each button in succession
366 */
367static int
368verify_okay_to_kill(Display *dpy, int screenno)
369{
370    unsigned char pointer_map[256];
371    int count = XGetPointerMapping (dpy, pointer_map, 256);
372    const char *msg = "the root window";
373    Window root = RootWindow (dpy, screenno);
374    int okay = 0;
375
376    for (int i = 0; i < count; i++) {
377	int button = (int) pointer_map[i];
378	if (button == 0) continue;	/* disabled */
379	if (get_window_id (dpy, screenno, button, msg) != root) {
380	    okay = 0;
381	    break;
382	}
383	okay++;				/* must have at least one button */
384    }
385    if (okay) {
386	return 1;
387    } else {
388	printf ("Aborting.\n");
389	return 0;
390    }
391}
392
393/* Return True if the property WM_STATE is set on the window, otherwise
394 * return False.
395 */
396static Bool
397wm_state_set(Display *dpy, Window win)
398{
399    Atom wm_state;
400    Atom actual_type;
401    int success;
402    int actual_format;
403    unsigned long nitems, remaining;
404    unsigned char* prop = NULL;
405
406    wm_state = XInternAtom(dpy, "WM_STATE", True);
407    if (wm_state == None) return False;
408    success = XGetWindowProperty(dpy, win, wm_state, 0L, 0L, False,
409				 AnyPropertyType, &actual_type, &actual_format,
410				 &nitems, &remaining, &prop);
411    if (prop) XFree((char *) prop);
412    return (success == Success && actual_type != None && actual_format);
413}
414
415/* Using a heuristic method, return True if a window manager is running,
416 * otherwise, return False.
417 */
418
419static Bool
420wm_running(Display *dpy, int screenno)
421{
422    XWindowAttributes	xwa;
423    Status		status;
424
425    status = XGetWindowAttributes(dpy, RootWindow(dpy, screenno), &xwa);
426    return (status &&
427	    ((xwa.all_event_masks & SubstructureRedirectMask) ||
428	     (xwa.all_event_masks & SubstructureNotifyMask)));
429}
430