xprop.c revision c8df0c59
1/*
2
3Copyright 1990, 1998  The Open Group
4Copyright (c) 2000  The XFree86 Project, Inc.
5
6Permission to use, copy, modify, distribute, and sell this software and its
7documentation for any purpose is hereby granted without fee, provided that
8the above copyright notice appear in all copies and that both that
9copyright notice and this permission notice appear in supporting
10documentation.
11
12The above copyright notice and this permission notice shall be included
13in all copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR
19OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
20ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
21OTHER DEALINGS IN THE SOFTWARE.
22
23Except as contained in this notice, the name of The Open Group shall
24not be used in advertising or otherwise to promote the sale, use or
25other dealings in this Software without prior written authorization
26from The Open Group.
27
28*/
29
30#include "config.h"
31
32#include <X11/Xlib.h>
33#include <X11/Xos.h>
34#include <X11/Xfuncs.h>
35#include <X11/Xutil.h>
36#include <sys/ioctl.h>
37#include <stdlib.h>
38#include <stdio.h>
39#include <ctype.h>
40#ifdef HAVE_WCHAR_H
41#include <wchar.h>
42#endif
43#ifdef HAVE_WCTYPE_H
44#include <wctype.h>
45#endif
46#include <locale.h>
47#ifdef HAVE_LANGINFO_H
48#include <langinfo.h>
49#endif
50
51#ifndef HAVE_WCTYPE_H
52#define iswprint(x) isprint(x)
53#endif
54
55#include <X11/Xatom.h>
56
57#include "dsimple.h"
58
59#define MAXSTR 500000
60#define MAXELEMENTS 64
61
62#ifndef min
63#define min(a,b)  ((a) < (b) ? (a) : (b))
64#endif
65
66/* isprint() in "C" locale */
67#define c_isprint(c) ((c) >= 0x20 && (c) < 0x7f)
68
69static unsigned int term_width = 144 + 8;
70
71/*
72 *
73 * The Thunk Manager - routines to create, add to, and free thunk lists
74 *
75 */
76
77typedef struct {
78  int thunk_count;
79  const char *propname;
80  long value;
81  Atom extra_encoding;
82  const char *extra_value;
83  const char *format;
84  const char *dformat;
85} thunk;
86
87static thunk *
88Create_Thunk_List (void)
89{
90    thunk *tptr;
91
92    tptr = malloc(sizeof(thunk));
93    if (!tptr)
94	Fatal_Error("Out of memory!");
95
96    tptr->thunk_count = 0;
97
98    return tptr;
99}
100
101static thunk *
102Add_Thunk (thunk *list, thunk t)
103{
104    int i;
105
106    i = list->thunk_count;
107
108    list = realloc(list, (i+1)*sizeof(thunk));
109    if (!list)
110	Fatal_Error("Out of memory!");
111
112    list[i++] = t;
113    list->thunk_count = i;
114
115    return list;
116}
117
118/*
119 * Misc. routines
120 */
121
122static int
123Read_Char (FILE *stream)
124{
125    int c;
126
127    c = getc(stream);
128    if (c == EOF)
129	Fatal_Error("Bad format file: Unexpected EOF.");
130    return c;
131}
132
133static void
134Read_White_Space (FILE *stream)
135{
136    int c;
137
138    while ((c = getc(stream)) == ' ' || c == '\n' || c == '\t');
139    ungetc(c, stream);
140}
141
142static char _large_buffer[MAXSTR+10];
143
144static char *
145Read_Quoted (FILE *stream)
146{
147    char *ptr;
148    int length;
149
150    Read_White_Space(stream);
151    if (Read_Char(stream)!='\'')
152	Fatal_Error("Bad format file format: missing dformat.");
153
154    ptr = _large_buffer; length = MAXSTR;
155    for (;;) {
156	int c;
157
158	if (length < 0)
159	    Fatal_Error("Bad format file format: dformat too long.");
160	c = Read_Char(stream);
161	if (c == (int) '\'')
162	    break;
163	ptr++[0] = c; length--;
164	if (c == (int) '\\') {
165	    c = Read_Char(stream);
166	    if (c == '\n') {
167		ptr--; length++;
168	    } else {
169		ptr++[0] = c; length--;
170	    }
171	}
172    }
173    ptr++[0] = '\0';
174
175    ptr = strdup(_large_buffer);
176    if (!ptr)
177	Fatal_Error("Out of memory!");
178    return ptr;
179}
180
181/*
182 *
183 * Parsing Routines: a group of routines to parse strings into values
184 *
185 * Routines: Parse_Atom, Scan_Long, Skip_Past_Right_Paren, Scan_Octal
186 *
187 * Routines of the form Parse_XXX take a string which is parsed to a value.
188 * Routines of the form Scan_XXX take a string, parse the beginning to a value,
189 * and return the rest of the string.  The value is returned via. the last
190 * parameter.  All numeric values are longs!
191 *
192 */
193
194static const char *
195Skip_Digits (const char *string)
196{
197    while (isdigit((unsigned char) string[0])) string++;
198    return string;
199}
200
201static const char *
202Scan_Long (const char *string, long *value)
203{
204    if (!isdigit((unsigned char) *string))
205	Fatal_Error("Bad number: %s.", string);
206
207    *value = atol(string);
208    return Skip_Digits(string);
209}
210
211static const char *
212Scan_Octal (const char *string, unsigned long *value)
213{
214    if (sscanf(string, "%lo", value)!=1)
215	Fatal_Error("Bad octal number: %s.", string);
216    return Skip_Digits(string);
217}
218
219static Atom
220Parse_Atom (const char *name, int only_if_exists)
221{
222    /* may return None = 0 */
223    return XInternAtom(dpy, name, only_if_exists);
224}
225
226static const char *
227Skip_Past_Right_Paren (const char *string)
228{
229    char c;
230    int nesting = 0;
231
232    while (c = string++[0], c != ')' || nesting)
233	switch (c) {
234	  case '\0':
235	    Fatal_Error("Missing ')'.");
236	  case '(':
237	    nesting++;
238	    break;
239	  case ')':
240	    nesting--;
241	    break;
242	  case '\\':
243	    string++;
244	    break;
245	}
246    return string;
247}
248
249/*
250 *
251 * Atom to format, dformat mapping Manager
252 *
253 */
254
255#define D_FORMAT "0x"              /* Default format for properties */
256#define D_DFORMAT " = $0+\n"       /* Default display pattern for properties */
257
258static thunk *_property_formats = NULL;   /* Holds mapping */
259
260static void
261Apply_Default_Formats (const char **format, const char **dformat)
262{
263    if (!*format)
264	*format = D_FORMAT;
265    if (!*dformat)
266	*dformat = D_DFORMAT;
267}
268
269static void
270Lookup_Formats (Atom atom, const char **format, const char **dformat)
271{
272    if (_property_formats)
273	for (int i = _property_formats->thunk_count-1; i >= 0; i--) {
274	    if (_property_formats[i].value == (long) atom) {
275		if (!*format)
276		    *format = _property_formats[i].format;
277		if (!*dformat)
278		    *dformat = _property_formats[i].dformat;
279		break;
280	    }
281	}
282}
283
284static void
285Add_Mapping (Atom atom, const char *format, const char *dformat)
286{
287    thunk t = {0};
288
289    if (!_property_formats)
290	_property_formats = Create_Thunk_List();
291
292    t.value = atom;
293    t.format = format;
294    t.dformat = dformat;
295
296    _property_formats = Add_Thunk(_property_formats, t);
297}
298
299/*
300 *
301 * Setup_Mapping: Routine to setup default atom to format, dformat mapping:
302 *
303 */
304
305typedef struct _propertyRec {
306    const char *	name;
307    Atom		atom;
308    const char *	format;
309    const char *	dformat;
310} propertyRec;
311
312#define ARC_DFORMAT	":\n"\
313"\t\tarc at $0, $1\n"\
314"\t\tsize: $2 by $3\n"\
315"\t\tfrom angle $4 to angle $5\n"
316
317#define RECTANGLE_DFORMAT	":\n"\
318"\t\tupper left corner: $0, $1\n"\
319"\t\tsize: $2 by $3\n"
320
321#define RGB_COLOR_MAP_DFORMAT	":\n"\
322"\t\tcolormap id #: $0\n"\
323"\t\tred-max: $1\n"\
324"\t\tred-mult: $2\n"\
325"\t\tgreen-max: $3\n"\
326"\t\tgreen-mult: $4\n"\
327"\t\tblue-max: $5\n"\
328"\t\tblue-mult: $6\n"\
329"\t\tbase-pixel: $7\n"\
330"\t\tvisual id #: $8\n"\
331"\t\tkill id #: $9\n"
332
333#define WM_HINTS_DFORMAT	":\n"\
334"?m0(\t\tClient accepts input or input focus: $1\n)"\
335"?m1(\t\tInitial state is "\
336"?$2=0(Don't Care State)"\
337"?$2=1(Normal State)"\
338"?$2=2(Zoomed State)"\
339"?$2=3(Iconic State)"\
340"?$2=4(Inactive State)"\
341".\n)"\
342"?m2(\t\tbitmap id # to use for icon: $3\n)"\
343"?m5(\t\tbitmap id # of mask for icon: $7\n)"\
344"?m3(\t\twindow id # to use for icon: $4\n)"\
345"?m4(\t\tstarting position for icon: $5, $6\n)"\
346"?m6(\t\twindow id # of group leader: $8\n)"\
347"?m8(\t\tThe urgency hint bit is set\n)"
348
349#define WM_ICON_SIZE_DFORMAT	":\n"\
350"\t\tminimum icon size: $0 by $1\n"\
351"\t\tmaximum icon size: $2 by $3\n"\
352"\t\tincremental size change: $4 by $5\n"
353
354#define WM_SIZE_HINTS_DFORMAT ":\n"\
355"?m0(\t\tuser specified location: $1, $2\n)"\
356"?m2(\t\tprogram specified location: $1, $2\n)"\
357"?m1(\t\tuser specified size: $3 by $4\n)"\
358"?m3(\t\tprogram specified size: $3 by $4\n)"\
359"?m4(\t\tprogram specified minimum size: $5 by $6\n)"\
360"?m5(\t\tprogram specified maximum size: $7 by $8\n)"\
361"?m6(\t\tprogram specified resize increment: $9 by $10\n)"\
362"?m7(\t\tprogram specified minimum aspect ratio: $11/$12\n"\
363"\t\tprogram specified maximum aspect ratio: $13/$14\n)"\
364"?m8(\t\tprogram specified base size: $15 by $16\n)"\
365"?m9(\t\twindow gravity: "\
366"?$17=0(Forget)"\
367"?$17=1(NorthWest)"\
368"?$17=2(North)"\
369"?$17=3(NorthEast)"\
370"?$17=4(West)"\
371"?$17=5(Center)"\
372"?$17=6(East)"\
373"?$17=7(SouthWest)"\
374"?$17=8(South)"\
375"?$17=9(SouthEast)"\
376"?$17=10(Static)"\
377"\n)"
378
379#define WM_STATE_DFORMAT	 ":\n"\
380"\t\twindow state: ?$0=0(Withdrawn)?$0=1(Normal)?$0=3(Iconic)\n"\
381"\t\ticon window: $1\n"
382
383static propertyRec windowPropTable[] = {
384    {"ARC",		XA_ARC,		"16iiccii",   ARC_DFORMAT },
385    {"ATOM",		XA_ATOM,	 "32a",	      0 },
386    {"BITMAP",		XA_BITMAP,	 "32x",	      ": bitmap id # $0\n" },
387    {"CARDINAL",	XA_CARDINAL,	 "0c",	      0 },
388    {"COLORMAP",	XA_COLORMAP,	 "32x",	      ": colormap id # $0\n" },
389    {"CURSOR",		XA_CURSOR,	 "32x",	      ": cursor id # $0\n" },
390    {"DRAWABLE",	XA_DRAWABLE,	 "32x",	      ": drawable id # $0\n" },
391    {"FONT",		XA_FONT,	 "32x",	      ": font id # $0\n" },
392    {"INTEGER",		XA_INTEGER,	 "0i",	      0 },
393    {"PIXMAP",		XA_PIXMAP,	 "32x",	      ": pixmap id # $0\n" },
394    {"POINT",		XA_POINT,	 "16ii",      " = $0, $1\n" },
395    {"RECTANGLE",	XA_RECTANGLE,	 "16iicc",    RECTANGLE_DFORMAT },
396    {"RGB_COLOR_MAP",	XA_RGB_COLOR_MAP,"32xcccccccxx",RGB_COLOR_MAP_DFORMAT},
397    {"STRING",		XA_STRING,	 "8s",	      0 },
398    {"UTF8_STRING",		0,	 "8u",	      0 },
399    {"WINDOW",		XA_WINDOW,	 "32x",	      ": window id # $0+\n" },
400    {"VISUALID",	XA_VISUALID,	 "32x",	      ": visual id # $0\n" },
401    {"WM_COLORMAP_WINDOWS",	0,	 "32x",       ": window id # $0+\n"},
402    {"WM_COMMAND",	XA_WM_COMMAND,	 "8s",	      " = { $0+ }\n" },
403    {"WM_HINTS",	XA_WM_HINTS,	 "32mbcxxiixx",	WM_HINTS_DFORMAT },
404    {"WM_ICON_NAME",	XA_WM_ICON_NAME, "8t",	      0 },
405    {"WM_ICON_SIZE",	XA_WM_ICON_SIZE, "32cccccc",  WM_ICON_SIZE_DFORMAT},
406    {"WM_NAME",		XA_WM_NAME,	 "8t",	      0 },
407    {"WM_PROTOCOLS",		0,	 "32a",	      ": protocols  $0+\n"},
408    {"WM_SIZE_HINTS",	XA_WM_SIZE_HINTS,"32mii",     WM_SIZE_HINTS_DFORMAT },
409    {"_NET_WM_ICON",            0,       "32o",        0 },
410    {"WM_STATE",		0,	 "32cx",      WM_STATE_DFORMAT}
411};
412#undef ARC_DFORMAT
413#undef RECTANGLE_DFORMAT
414#undef RGB_COLOR_MAP_DFORMAT
415#undef WM_ICON_SIZE_DFORMAT
416#undef WM_HINTS_DFORMAT
417#undef WM_SIZE_HINTS_DFORMAT
418#undef WM_STATE_DFORMAT
419
420/*
421 * Font-specific mapping of property names to types:
422 */
423static propertyRec fontPropTable[] = {
424
425    /* XLFD name properties */
426
427    { "FOUNDRY",		0, 	 		"32a",	0 },
428    { "FAMILY_NAME",		XA_FAMILY_NAME,		"32a",	0 },
429    { "WEIGHT_NAME",		0,			"32a",	0 },
430    { "SLANT",			0,			"32a",	0 },
431    { "SETWIDTH_NAME",		0,			"32a",	0 },
432    { "ADD_STYLE_NAME",		0,			"32a",	0 },
433    { "PIXEL_SIZE",		0,			"32c",	0 },
434    { "POINT_SIZE",		XA_POINT_SIZE,		"32c",	0 },
435    { "RESOLUTION_X",		0,			"32c",	0 },
436    { "RESOLUTION_Y",		0,			"32c",	0 },
437    { "SPACING",		0,			"32a",	0 },
438    { "AVERAGE_WIDTH",		0,			"32c",	0 },
439    { "CHARSET_REGISTRY",	0,			"32a",	0 },
440    { "CHARSET_ENCODING",	0,			"32a",	0 },
441
442    /* other font properties referenced in the XLFD */
443
444    { "QUAD_WIDTH",		XA_QUAD_WIDTH,		"32i",	0 },
445    { "RESOLUTION",		XA_RESOLUTION,		"32c",	0 },
446    { "MIN_SPACE",		XA_MIN_SPACE,		"32c",	0 },
447    { "NORM_SPACE",		XA_NORM_SPACE,		"32c",	0 },
448    { "MAX_SPACE",		XA_MAX_SPACE,		"32c",	0 },
449    { "END_SPACE",		XA_END_SPACE,		"32c",	0 },
450    { "SUPERSCRIPT_X",		XA_SUPERSCRIPT_X,	"32i",	0 },
451    { "SUPERSCRIPT_Y",		XA_SUPERSCRIPT_Y,	"32i",	0 },
452    { "SUBSCRIPT_X",		XA_SUBSCRIPT_X,		"32i",	0 },
453    { "SUBSCRIPT_Y",		XA_SUBSCRIPT_Y,		"32i",	0 },
454    { "UNDERLINE_POSITION",	XA_UNDERLINE_POSITION,	"32i",	0 },
455    { "UNDERLINE_THICKNESS",	XA_UNDERLINE_THICKNESS,	"32i",	0 },
456    { "STRIKEOUT_ASCENT",	XA_STRIKEOUT_ASCENT,	"32i",	0 },
457    { "STRIKEOUT_DESCENT",	XA_STRIKEOUT_DESCENT,	"32i",	0 },
458    { "ITALIC_ANGLE",		XA_ITALIC_ANGLE,	"32i",	0 },
459    { "X_HEIGHT",		XA_X_HEIGHT,		"32i",	0 },
460    { "WEIGHT",			XA_WEIGHT,		"32i",	0 },
461    { "FACE_NAME",		0,			"32a",	0 },
462    { "COPYRIGHT",		XA_COPYRIGHT,		"32a",	0 },
463    { "AVG_CAPITAL_WIDTH",	0,			"32i",	0 },
464    { "AVG_LOWERCASE_WIDTH",	0,			"32i",	0 },
465    { "RELATIVE_SETWIDTH",	0,			"32c",	0 },
466    { "RELATIVE_WEIGHT",	0,			"32c",	0 },
467    { "CAP_HEIGHT",		XA_CAP_HEIGHT,		"32c",	0 },
468    { "SUPERSCRIPT_SIZE",	0,			"32c",	0 },
469    { "FIGURE_WIDTH",		0,			"32i",	0 },
470    { "SUBSCRIPT_SIZE",		0,			"32c",	0 },
471    { "SMALL_CAP_SIZE",		0,			"32i",	0 },
472    { "NOTICE",			XA_NOTICE,		"32a",	0 },
473    { "DESTINATION",		0,			"32c",	0 },
474
475    /* other font properties */
476
477    { "FONT",			XA_FONT,		"32a",	0 },
478    { "FONT_NAME",		XA_FONT_NAME,		"32a",	0 },
479};
480
481static int XpropMode;
482#define XpropWindowProperties 0
483#define XpropFontProperties   1
484
485static void
486Setup_Mapping (void)
487{
488    int n;
489    propertyRec *p;
490
491    if (XpropMode == XpropWindowProperties) {
492	n = sizeof(windowPropTable) / sizeof(propertyRec);
493	p = windowPropTable;
494    } else {
495	n = sizeof (fontPropTable) / sizeof (propertyRec);
496	p = fontPropTable;
497    }
498    for ( ; --n >= 0; p++) {
499	if (! p->atom) {
500	    p->atom = XInternAtom(dpy, p->name, True);
501	    if (p->atom == None)
502		continue;
503	}
504	Add_Mapping(p->atom, p->format, p->dformat);
505    }
506}
507
508static const char *
509GetAtomName (Atom atom)
510{
511    int n;
512    propertyRec *p;
513
514    if (XpropMode == XpropWindowProperties) {
515	n = sizeof(windowPropTable) / sizeof(propertyRec);
516	p = windowPropTable;
517    } else {
518	n = sizeof (fontPropTable) / sizeof (propertyRec);
519	p = fontPropTable;
520    }
521    for ( ; --n >= 0; p++)
522	if (p->atom == atom)
523	    return p->name;
524
525    return NULL;
526}
527
528/*
529 * Read_Mapping: routine to read in additional mappings from a stream
530 *               already open for reading.
531 */
532
533static void
534Read_Mappings (FILE *stream)
535{
536    char format_buffer[100];
537    char name[1000];
538    int count;
539
540    while ((count = fscanf(stream," %990s %90s ",name,format_buffer)) != EOF) {
541	const char *dformat, *format;
542	int c;
543	Atom atom;
544
545	if (count != 2)
546	    Fatal_Error("Bad format file format.");
547
548	atom = Parse_Atom(name, False);
549	format = strdup(format_buffer);
550	if (!format)
551	    Fatal_Error("Out of memory!");
552
553	Read_White_Space(stream);
554	dformat = D_DFORMAT;
555	c = getc(stream);
556	ungetc(c, stream);
557	if (c == (int) '\'')
558	    dformat = Read_Quoted(stream);
559
560	Add_Mapping(atom, format, dformat);
561    }
562}
563
564/*
565 *
566 * Formatting Routines: a group of routines to translate from various
567 *                      values to a static read-only string useful for output.
568 *
569 * Routines: Format_Hex, Format_Unsigned, Format_Signed, Format_Atom,
570 *           Format_Mask_Word, Format_Bool, Format_String, Format_Len_String.
571 *
572 * All of the above routines take a long except for Format_String and
573 * Format_Len_String.
574 *
575 */
576static char _formatting_buffer[MAXSTR+100];
577static char _formatting_buffer2[21];
578
579static const char *
580Format_Hex (long wrd)
581{
582    snprintf(_formatting_buffer2, sizeof(_formatting_buffer2), "0x%lx", wrd);
583    return _formatting_buffer2;
584}
585
586static const char *
587Format_Unsigned (long wrd)
588{
589    snprintf(_formatting_buffer2, sizeof(_formatting_buffer2), "%lu", wrd);
590    return _formatting_buffer2;
591}
592
593static const char *
594Format_Signed (long wrd)
595{
596    snprintf(_formatting_buffer2, sizeof(_formatting_buffer2), "%ld", wrd);
597    return _formatting_buffer2;
598}
599
600/*ARGSUSED*/
601static int
602ignore_errors (Display *display, XErrorEvent *ev)
603{
604    return 0;
605}
606
607static const char *
608Format_Atom (Atom atom)
609{
610    const char *found;
611    char *name;
612    XErrorHandler handler;
613
614    if ((found = GetAtomName(atom)) != NULL)
615	return found;
616
617    handler = XSetErrorHandler (ignore_errors);
618    name = XGetAtomName(dpy, atom);
619    XSetErrorHandler(handler);
620    if (! name)
621	snprintf(_formatting_buffer, sizeof(_formatting_buffer),
622		 "undefined atom # 0x%lx", atom);
623    else {
624	size_t namelen = strlen(name);
625	if (namelen > MAXSTR) namelen = MAXSTR;
626	memcpy(_formatting_buffer, name, namelen);
627	_formatting_buffer[namelen] = '\0';
628	XFree(name);
629    }
630    return _formatting_buffer;
631}
632
633static const char *
634Format_Mask_Word (long wrd)
635{
636    unsigned long bit_mask, bit;
637    int seen = 0;
638
639    strcpy(_formatting_buffer, "{MASK: ");
640    for (bit=0, bit_mask=1; bit <= sizeof(long)*8; bit++, bit_mask<<=1) {
641	if (bit_mask & wrd) {
642	    if (seen) {
643		strcat(_formatting_buffer, ", ");
644	    }
645	    seen = 1;
646	    strcat(_formatting_buffer, Format_Unsigned(bit));
647	}
648    }
649    strcat(_formatting_buffer, "}");
650
651    return _formatting_buffer;
652}
653
654static const char *
655Format_Bool (long value)
656{
657    if (!value)
658	return "False";
659
660    return "True";
661}
662
663static char *_buf_ptr;
664static int _buf_len;
665
666static void
667_put_char (char c)
668{
669    if (_buf_len <= 0) {
670	_buf_ptr[0] = '\0';
671	return;
672    }
673    _buf_ptr++[0] = c;
674    _buf_len--;
675}
676
677static void
678_format_char (char c, int unicode)
679{
680    switch (c) {
681      case '\\':
682      case '\"':
683	_put_char('\\');
684	_put_char(c);
685	break;
686      case '\n':
687	_put_char('\\');
688	_put_char('n');
689	break;
690      case '\t':
691	_put_char('\\');
692	_put_char('t');
693	break;
694      default:
695	if (!c_isprint(c)) {
696	    if (unicode && (c & 0x80)) {
697		_put_char(c);
698	    } else {
699		_put_char('\\');
700		snprintf(_buf_ptr, _buf_len, "%03o", (unsigned char) c);
701		_buf_ptr += 3;
702		_buf_len -= 3;
703	    }
704	} else
705	  _put_char(c);
706    }
707}
708
709static const char *
710Format_String (const char *string, int unicode)
711{
712    char c;
713
714    _buf_ptr = _formatting_buffer;
715    _buf_len = MAXSTR;
716    _put_char('\"');
717
718    while ((c = string++[0]))
719	_format_char(c, unicode);
720
721    *_buf_ptr++ = '"';
722    *_buf_ptr++ = '\0';
723    return _formatting_buffer;
724}
725
726static const char *
727Format_Len_String (const char *string, int len, int unicode)
728{
729    char *data;
730    const char *result;
731
732    data = malloc(len+1);
733    if (!data)
734	Fatal_Error("Out of memory!");
735
736    memcpy(data, string, len);
737    data[len] = '\0';
738
739    result = Format_String(data, unicode);
740    free(data);
741
742    return result;
743}
744
745static int
746is_utf8_locale (void)
747{
748#ifdef HAVE_LANGINFO_H
749    char *charmap = nl_langinfo (CODESET);
750
751    return charmap && strcmp (charmap, "UTF-8") == 0;
752#else
753    return 0;
754#endif
755}
756
757static int
758is_truecolor_term (void)
759{
760    char *colorterm = getenv( "COLORTERM" );
761
762    if (colorterm && !strcmp(colorterm,"truecolor"))
763	return 1;
764
765    return 0;
766}
767
768static const char *
769Format_Icons (const unsigned long *icon, int len)
770{
771    static char *result = NULL;
772    char *tail = NULL;
773    int alloced;
774    const unsigned long *end = icon + len / sizeof (unsigned long);
775
776    free(result);
777    result = NULL;
778
779    alloced = 0;
780
781    while (icon < end)
782    {
783	unsigned long width, height, display_width, display_height;
784	unsigned int icon_pixel_bytes;
785	unsigned int icon_line_bytes;
786	int offset;
787
788	width = display_width = *icon++;
789	height = display_height = *icon++;
790	if (is_truecolor_term())
791	    display_height /= 2; /* Two vertical icon pixels per character. */
792	else
793	    display_width *= 2;  /* Two horizontal characters per icon pixel. */
794
795	icon_pixel_bytes = 1;
796	if (is_truecolor_term())
797	    icon_pixel_bytes = 39; /* 21 control characters, and up to 18 chars of RGB. */
798	else if (is_utf8_locale())
799	    icon_pixel_bytes = 3; /* Up to 3 bytes per character in that mode. */
800
801	/* Initial tab, pixels, and newline. */
802	icon_line_bytes = 8 + display_width * icon_pixel_bytes + 1;
803
804	offset = (tail - result);
805
806	alloced += 80;				/* For the header, final newline, color reset */
807	alloced += icon_line_bytes * display_height;	/* For the rows */
808
809	result = realloc (result, alloced);
810	if (!result)
811	    Fatal_Error("Out of memory!");
812	tail = &result[offset];
813
814	if (end - icon < width * height)
815	    break;
816
817	tail += sprintf (tail, "\tIcon (%lu x %lu):\n", width, height);
818
819	if ((display_width + 8) > term_width || height > 144)
820	{
821	    tail += sprintf (tail, "\t(not shown)\n");
822	    icon += width * height;
823	    continue;
824	}
825
826	for (unsigned int h = 0; h < display_height; ++h)
827	{
828	    tail += sprintf (tail, "\t");
829
830	    for (unsigned int w = 0; w < width; ++w)
831	    {
832		unsigned char a, r, g, b;
833		unsigned long pixel = *icon++;
834		unsigned long brightness;
835
836		a = (pixel & 0xff000000) >> 24;
837		r = (pixel & 0x00ff0000) >> 16;
838		g = (pixel & 0x0000ff00) >> 8;
839		b = (pixel & 0x000000ff);
840
841		brightness =
842		    (a / 255.0) * (1000 - ((299 * (r / 255.0)) +
843					   (587 * (g / 255.0)) +
844					   (114 * (b / 255.0))));
845
846		if (is_truecolor_term())
847		{
848		    float opacity = a / 255.0;
849		    unsigned long pixel2 = *(icon + width - 1);
850
851		    r = r * opacity;
852		    g = g * opacity;
853		    b = b * opacity;
854		    tail += sprintf (tail, "\033[48;2;%d;%d;%d;", r, g, b);
855
856		    opacity = ((pixel2 & 0xff000000) >> 24) / 255.0;
857		    r = ((pixel2 & 0x00ff0000) >> 16) * opacity;
858		    g = ((pixel2 & 0x0000ff00) >>  8) * opacity;
859		    b = ((pixel2 & 0x000000ff) >>  0) * opacity;
860		    tail += sprintf (tail, "38;2;%d;%d;%dm\342\226\204", r, g, b);
861
862		}
863		else if (is_utf8_locale())
864		{
865		    static const char palette[][4] =
866		    {
867			" ",
868			"\342\226\221",		/* 25% */
869			"\342\226\222",		/* 50% */
870			"\342\226\223",		/* 75% */
871			"\342\226\210",		/* 100% */
872		    };
873		    int idx;
874
875		    idx = (brightness * ((sizeof (palette)/sizeof(palette[0])) - 1)) / 1000;
876
877		    tail += sprintf (tail, "%s%s", palette[idx], palette[idx]);
878		}
879		else
880		{
881		    static const char palette[] =
882			" .'`,^:\";~-_+<>i!lI?/\\|()1{}[]rcvunxzjftLCJUYXZO0Qoahkbdpqwm*WMB8&%$#@";
883		    int idx;
884
885		    idx = (brightness * (sizeof(palette) - 2)) / 1000;
886
887		    *tail++ = palette[idx];
888		    *tail++ = palette[idx];
889		}
890	    }
891
892	    if (is_truecolor_term()) {
893	        /* Reset colors before newline to avoid bleed. */
894	        tail += sprintf (tail, "\033[0m");
895	        icon += width; /* Advance past handled row. */
896	    }
897	    tail += sprintf (tail, "\n");
898	}
899
900	tail += sprintf (tail, "\n");
901    }
902
903    return result;
904}
905
906static const char *
907Format_Len_Text (const char *string, int len, Atom encoding)
908{
909    XTextProperty textprop;
910    char **start_list;
911    int count;
912
913    /* Try to convert to local encoding. */
914    textprop.encoding = encoding;
915    textprop.format = 8;
916    textprop.value = (unsigned char *) string;
917    textprop.nitems = len;
918    if (XmbTextPropertyToTextList(dpy, &textprop, &start_list, &count) == Success) {
919	char **list = start_list;
920	_buf_ptr = _formatting_buffer;
921	_buf_len = MAXSTR;
922	*_buf_ptr++ = '"';
923	while (count > 0) {
924	    string = *list++;
925	    len = strlen(string);
926	    while (len > 0) {
927		wchar_t wc;
928		int n = mbtowc(&wc, string, len);
929		if (n > 0 && iswprint(wc)) {
930		    if (_buf_len >= n) {
931			memcpy(_buf_ptr, string, n);
932			_buf_ptr += n;
933			_buf_len -= n;
934		    }
935		    string += n;
936		    len -= n;
937		} else {
938		    _put_char('\\');
939		    snprintf(_buf_ptr, _buf_len, "%03o", (unsigned char) *string);
940		    _buf_ptr += 3;
941		    _buf_len -= 3;
942		    string++;
943		    len--;
944		}
945	    }
946	    count--;
947	    if (count > 0) {
948		snprintf(_buf_ptr, _buf_len, "\\000");
949	        _buf_ptr += 4;
950		_buf_len -= 4;
951	    }
952	}
953	XFreeStringList(start_list);
954	*_buf_ptr++ = '"';
955	*_buf_ptr++ = '\0';
956	return _formatting_buffer;
957    } else
958	return Format_Len_String(string, len, 0);
959}
960
961/*
962 * Validate a string as UTF-8 encoded according to RFC 3629
963 *
964 * Simply, a unicode code point (up to 21-bits long) is encoded as follows:
965 *
966 *    Char. number range  |        UTF-8 octet sequence
967 *       (hexadecimal)    |              (binary)
968 *    --------------------+---------------------------------------------
969 *    0000 0000-0000 007F | 0xxxxxxx
970 *    0000 0080-0000 07FF | 110xxxxx 10xxxxxx
971 *    0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
972 *    0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
973 *
974 * Validation is done left-to-right, and an error condition, if any, refers to
975 * only the left-most problem in the string.
976 *
977 * Return values:
978 *   UTF8_VALID: Valid UTF-8 encoded string
979 *   UTF8_OVERLONG: Using more bytes than needed for a code point
980 *   UTF8_SHORT_TAIL: Not enough bytes in a multi-byte sequence
981 *   UTF8_LONG_TAIL: Too many bytes in a multi-byte sequence
982 *   UTF8_FORBIDDEN_VALUE: Forbidden prefix or code point outside 0x10FFFF
983 */
984#define UTF8_VALID 0
985#define UTF8_FORBIDDEN_VALUE 1
986#define UTF8_OVERLONG 2
987#define UTF8_SHORT_TAIL 3
988#define UTF8_LONG_TAIL 4
989static int
990is_valid_utf8 (const char *string, int len)
991{
992    unsigned long codepoint = 0;
993    int rem = 0;
994
995    for (int i = 0; i < len; i++) {
996	 unsigned char c = (unsigned char) string[i];
997
998	/* Order of type check:
999	 *   - Single byte code point
1000	 *   - Non-starting byte of multi-byte sequence
1001	 *   - Start of 2-byte sequence
1002	 *   - Start of 3-byte sequence
1003	 *   - Start of 4-byte sequence
1004	 */
1005	if (!(c & 0x80)) {
1006	    if (rem > 0) return UTF8_SHORT_TAIL;
1007	    rem = 0;
1008	    codepoint = c;
1009	} else if ((c & 0xC0) == 0x80) {
1010	    if (rem == 0) return UTF8_LONG_TAIL;
1011	    rem--;
1012	    codepoint |= (c & 0x3F) << (rem * 6);
1013	    if (codepoint == 0) return UTF8_OVERLONG;
1014	} else if ((c & 0xE0) == 0xC0) {
1015	    if (rem > 0) return UTF8_SHORT_TAIL;
1016	    rem = 1;
1017	    codepoint = (c & 0x1F) << 6;
1018	    if (codepoint == 0) return UTF8_OVERLONG;
1019	} else if ((c & 0xF0) == 0xE0) {
1020	    if (rem > 0) return UTF8_SHORT_TAIL;
1021	    rem = 2;
1022	    codepoint = (c & 0x0F) << 12;
1023	} else if ((c & 0xF8) == 0xF0) {
1024	    if (rem > 0) return UTF8_SHORT_TAIL;
1025	    rem = 3;
1026	    codepoint = (c & 0x07) << 18;
1027	    if (codepoint > 0x10FFFF) return UTF8_FORBIDDEN_VALUE;
1028	} else
1029	    return UTF8_FORBIDDEN_VALUE;
1030    }
1031
1032    return UTF8_VALID;
1033}
1034
1035static const char *
1036Format_Len_Unicode (const char *string, int len)
1037{
1038    int validity = is_valid_utf8(string, len);
1039
1040    if (validity != UTF8_VALID) {
1041	char *data;
1042	const char *result, *error;
1043
1044	switch (validity) {
1045	  case UTF8_FORBIDDEN_VALUE:
1046	    error = "<Invalid UTF-8 string: Forbidden value> "; break;
1047	  case UTF8_OVERLONG:
1048	    error = "<Invalid UTF-8 string: Overlong encoding> "; break;
1049	  case UTF8_SHORT_TAIL:
1050	    error = "<Invalid UTF-8 string: Tail too short> "; break;
1051	  case UTF8_LONG_TAIL:
1052	    error = "<Invalid UTF-8 string: Tail too long> "; break;
1053	  default:
1054	    error = "<Invalid UTF-8 string: Unknown error>"; break;
1055	}
1056
1057	result = Format_Len_String(string, len, 0);
1058	/* result is stored in _formatting_buffer, so make a temporary
1059	   copy before we overwrite _formatting_buffer with error */
1060	data = strdup(result);
1061	if (!data)
1062	    Fatal_Error("Out of memory!");
1063
1064	memcpy(_formatting_buffer, error, strlen(error)+1);
1065	strcat(_formatting_buffer, data);
1066	free(data);
1067
1068	return _formatting_buffer;
1069    }
1070
1071    return Format_Len_String(string, len, is_utf8_locale());
1072}
1073
1074/*
1075 *
1076 * The Format Manager: a group of routines to manage "formats"
1077 *
1078 */
1079
1080static int
1081Is_A_Format (const char *string)
1082{
1083    return isdigit((unsigned char) string[0]);
1084}
1085
1086static int
1087Get_Format_Size (const char *format)
1088{
1089    long size;
1090
1091    Scan_Long(format, &size);
1092
1093    /* Check for legal sizes */
1094    if (size != 0 && size != 8 && size != 16 && size != 32)
1095	Fatal_Error("bad format: %s", format);
1096
1097    return (int) size;
1098}
1099
1100static char
1101Get_Format_Char (const char *format, int i)
1102{
1103    long size;
1104
1105    /* Remove # at front of format */
1106    format = Scan_Long(format, &size);
1107    if (!*format)
1108	Fatal_Error("bad format: %s", format);
1109
1110    /* Last character repeats forever... */
1111    if (i >= (int)strlen(format))
1112	i = strlen(format)-1;
1113
1114    return format[i];
1115}
1116
1117static const char *
1118Format_Thunk (thunk t, char format_char)
1119{
1120    long value;
1121    value = t.value;
1122
1123    switch (format_char) {
1124      case 's':
1125	return Format_Len_String(t.extra_value, (int)t.value, 0);
1126      case 'u':
1127	return Format_Len_Unicode(t.extra_value, (int)t.value);
1128      case 't':
1129	return Format_Len_Text(t.extra_value, (int)t.value, t.extra_encoding);
1130      case 'x':
1131	return Format_Hex(value);
1132      case 'c':
1133	return Format_Unsigned(value);
1134      case 'i':
1135	return Format_Signed(value);
1136      case 'b':
1137	return Format_Bool(value);
1138      case 'm':
1139	return Format_Mask_Word(value);
1140      case 'a':
1141	return Format_Atom(value);
1142      case 'o':
1143	  return Format_Icons((const unsigned long *)t.extra_value, (int)t.value);
1144      default:
1145	Fatal_Error("bad format character: %c", format_char);
1146    }
1147}
1148
1149static const char *
1150Format_Thunk_I (thunk *thunks, const char *format, int i)
1151{
1152    if (i >= thunks->thunk_count)
1153	return "<field not available>";
1154
1155    return Format_Thunk(thunks[i], Get_Format_Char(format, i));
1156}
1157
1158static long
1159Mask_Word (thunk *thunks, const char *format)
1160{
1161    for (int j = 0; j  < (int)strlen(format); j++)
1162	if (Get_Format_Char(format, j) == 'm')
1163	    return thunks[j].value;
1164    return 0;
1165}
1166
1167/*
1168 *
1169 * The Display Format Manager:
1170 *
1171 */
1172
1173static int
1174Is_A_DFormat (const char *string)
1175{
1176    return string[0] && string[0] != '-'
1177	   && !(isalpha((unsigned char) string[0]) || string[0] == '_');
1178}
1179
1180static const char *
1181Handle_Backslash (const char *dformat)
1182{
1183    char c;
1184    unsigned long i;
1185
1186    if (!(c = *(dformat++)))
1187	return dformat;
1188
1189    switch (c) {
1190      case 'n':
1191	putchar('\n');
1192	break;
1193      case 't':
1194	putchar('\t');
1195	break;
1196      case '0':
1197      case '1':
1198      case '2':
1199      case '3':
1200      case '4':
1201      case '5':
1202      case '6':
1203      case '7':
1204	dformat = Scan_Octal(dformat, &i);
1205	putchar((int) i);
1206	break;
1207      default:
1208	putchar(c);
1209	break;
1210    }
1211    return dformat;
1212}
1213
1214static const char *
1215Handle_Dollar_sign (const char *dformat, thunk *thunks, const char *format)
1216{
1217    long i;
1218
1219    dformat = Scan_Long(dformat, &i);
1220
1221    if (dformat[0] == '+') {
1222	int seen = 0;
1223	dformat++;
1224	for (; i < thunks->thunk_count; i++) {
1225	    if (seen)
1226		printf(", ");
1227	    seen = 1;
1228	    printf("%s", Format_Thunk_I(thunks, format, (int) i));
1229	}
1230    } else
1231	printf("%s", Format_Thunk_I(thunks, format, (int) i));
1232
1233    return dformat;
1234}
1235
1236static int
1237Mask_Bit_I (thunk *thunks, const char *format, int i)
1238{
1239    long value;
1240
1241    value = Mask_Word(thunks, format);
1242
1243    value = value & (1L<<i);
1244    if (value)
1245	value = 1;
1246    return value;
1247}
1248
1249static const char *
1250Scan_Term (const char *string, thunk *thunks, const char *format, long *value)
1251{
1252    long i;
1253
1254    *value = 0;
1255
1256    if (isdigit((unsigned char) *string))
1257	string = Scan_Long(string, value);
1258    else if (*string == '$') {
1259	string = Scan_Long(++string, &i);
1260	if (i >= thunks->thunk_count)
1261	    i = thunks->thunk_count;
1262	*value = thunks[i].value;
1263    } else if (*string == 'm') {
1264	string = Scan_Long(++string, &i);
1265	*value = Mask_Bit_I(thunks, format, (int) i);
1266    } else
1267	Fatal_Error("Bad term: %s.", string);
1268
1269    return string;
1270}
1271
1272static const char *
1273Scan_Exp (const char *string, thunk *thunks, const char *format, long *value)
1274{
1275    long temp;
1276
1277    if (string[0] == '(') {
1278	string = Scan_Exp(++string, thunks, format, value);
1279	if (string[0]!=')')
1280	    Fatal_Error("Missing ')'");
1281	return ++string;
1282    }
1283    if (string[0] == '!') {
1284	string = Scan_Exp(++string, thunks, format, value);
1285	*value = !*value;
1286	return string;
1287    }
1288
1289    string = Scan_Term(string, thunks, format, value);
1290
1291    if (string[0] == '=') {
1292	string = Scan_Exp(++string, thunks, format, &temp);
1293	*value = *value == temp;
1294    }
1295
1296    return string;
1297}
1298
1299static const char *
1300Handle_Question_Mark (const char *dformat, thunk *thunks, const char *format)
1301{
1302    long is_true;
1303
1304    dformat = Scan_Exp(dformat, thunks, format, &is_true);
1305
1306    if (*dformat != '(')
1307	Fatal_Error("Bad conditional: '(' expected: %s.", dformat);
1308    ++dformat;
1309
1310    if (!is_true)
1311	dformat = Skip_Past_Right_Paren(dformat);
1312
1313    return dformat;
1314}
1315
1316static void
1317Display_Property (thunk *thunks, const char *dformat, const char *format)
1318{
1319    char c;
1320
1321    while ((c = *(dformat++)))
1322	switch (c) {
1323	  case ')':
1324	    continue;
1325	  case '\\':
1326	    dformat = Handle_Backslash(dformat);
1327	    continue;
1328	  case '$':
1329	    dformat = Handle_Dollar_sign(dformat, thunks, format);
1330	    continue;
1331	  case '?':
1332	    dformat = Handle_Question_Mark(dformat, thunks, format);
1333	    continue;
1334	  default:
1335	    putchar(c);
1336	    continue;
1337	}
1338}
1339
1340/*
1341 *
1342 * Routines to convert property data to thunks
1343 *
1344 */
1345
1346static long
1347Extract_Value (const char **pointer, int *length, int size, int signedp)
1348{
1349    long value;
1350
1351    switch (size) {
1352      case 8:
1353	if (signedp)
1354	    value = * (const signed char *) *pointer;
1355	else
1356	    value = * (const unsigned char *) *pointer;
1357	*pointer += 1;
1358	*length -= 1;
1359	break;
1360      case 16:
1361	if (signedp)
1362	    value = * (const short *) *pointer;
1363	else
1364	    value = * (const unsigned short *) *pointer;
1365	*pointer += sizeof(short);
1366	*length -= sizeof(short);
1367	break;
1368      case 32:
1369	if (signedp)
1370	    value = * (const long *) *pointer;
1371	else
1372	    value = * (const unsigned long *) *pointer & 0xffffffff;
1373	*pointer += sizeof(long);
1374	*length -= sizeof(long);
1375	break;
1376      default:
1377	abort();
1378    }
1379    return value;
1380}
1381
1382static long
1383Extract_Len_String (const char **pointer, int *length, int size, const char **string)
1384{
1385    int len;
1386
1387    if (size != 8)
1388	Fatal_Error("can't use format character 's' with any size except 8.");
1389    len = 0; *string = *pointer;
1390    while ((len++, --*length, *((*pointer)++)) && *length>0);
1391
1392    return len;
1393}
1394
1395static long
1396Extract_Icon (const char **pointer, int *length, int size, const char **icon)
1397{
1398    int len = 0;
1399
1400    if (size != 32)
1401	Fatal_Error("can't use format character 'o' with any size except 32.");
1402
1403    len = *length;
1404    *icon = *pointer;
1405    *length = 0;
1406    return len;
1407}
1408
1409static thunk *
1410Break_Down_Property (const char *pointer, int length, Atom type, const char *format, int size)
1411{
1412    thunk *thunks;
1413    thunk t = {0};
1414    int i;
1415
1416    thunks = Create_Thunk_List();
1417    i = 0;
1418
1419    while (length >= size/8) {
1420	char format_char = Get_Format_Char(format, i);
1421
1422	if (format_char == 's' || format_char == 'u')
1423	    t.value = Extract_Len_String(&pointer,&length,size,&t.extra_value);
1424	else if (format_char == 't') {
1425	    t.extra_encoding = type;
1426	    t.value = Extract_Len_String(&pointer,&length,size,&t.extra_value);
1427	}
1428	else if (format_char == 'o')
1429	    t.value = Extract_Icon (&pointer,&length,size,&t.extra_value);
1430	else
1431	    t.value = Extract_Value(&pointer,&length,size,format_char=='i');
1432	thunks = Add_Thunk(thunks, t);
1433	i++;
1434    }
1435
1436    return thunks;
1437}
1438
1439/*
1440 * Variables set by main()
1441 */
1442
1443static Window target_win = 0;
1444static int notype = 0;
1445static int max_len = MAXSTR;
1446static XFontStruct *font;
1447static unsigned long _font_prop;
1448
1449/*
1450 *
1451 * Other Stuff (temp.):
1452 *
1453 */
1454
1455static const char *
1456Get_Font_Property_Data_And_Type (Atom atom,
1457                                 long *length, Atom *type, int *size)
1458{
1459    *type = None;
1460
1461    for (int i = 0; i < font->n_properties; i++)
1462	if (atom == font->properties[i].name) {
1463	    _font_prop = font->properties[i].card32;
1464	    *length = sizeof(long);
1465	    *size = 32;
1466	    return (const char *) &_font_prop;
1467	}
1468    *size = 0;
1469    return NULL;
1470}
1471
1472static const char *
1473Get_Window_Property_Data_And_Type (Atom atom,
1474                                   long *length, Atom *type, int *size)
1475{
1476    Atom actual_type;
1477    int actual_format;
1478    unsigned long nitems;
1479    unsigned long nbytes;
1480    unsigned long bytes_after;
1481    static unsigned char *prop = NULL;
1482    int status;
1483
1484    if (prop)
1485    {
1486	XFree(prop);
1487	prop = NULL;
1488    }
1489
1490    status = XGetWindowProperty(dpy, target_win, atom, 0, (max_len+3)/4,
1491				False, AnyPropertyType, &actual_type,
1492				&actual_format, &nitems, &bytes_after,
1493				&prop);
1494    if (status == BadWindow)
1495	Fatal_Error("window id # 0x%lx does not exists!", target_win);
1496    if (status != Success)
1497	Fatal_Error("XGetWindowProperty failed!");
1498
1499    if (actual_format == 32)
1500	nbytes = sizeof(long);
1501    else if (actual_format == 16)
1502	nbytes = sizeof(short);
1503    else if (actual_format == 8)
1504	nbytes = 1;
1505    else if (actual_format == 0)
1506        nbytes = 0;
1507    else
1508	abort();
1509    *length = min(nitems * nbytes, max_len);
1510    *type = actual_type;
1511    *size = actual_format;
1512    return (const char *)prop;
1513}
1514
1515static const char *
1516Get_Property_Data_And_Type (Atom atom, long *length, Atom *type, int *size)
1517{
1518    if (target_win == (Window) -1)
1519	return Get_Font_Property_Data_And_Type(atom, length, type, size);
1520    else
1521	return Get_Window_Property_Data_And_Type(atom, length, type, size);
1522}
1523
1524static void
1525Show_Prop (const char *format, const char *dformat, const char *prop)
1526{
1527    const char *data;
1528    long length;
1529    Atom atom, type;
1530    thunk *thunks;
1531    int size, fsize;
1532
1533    printf("%s", prop);
1534    atom = Parse_Atom(prop, True);
1535    if (atom == None) {
1536	printf(":  no such atom on any window.\n");
1537	return;
1538    }
1539
1540    data = Get_Property_Data_And_Type(atom, &length, &type, &size);
1541    if (!size) {
1542	puts(":  not found.");
1543	return;
1544    }
1545
1546    if (!notype && type != None)
1547	printf("(%s)", Format_Atom(type));
1548
1549    Lookup_Formats(atom, &format, &dformat);
1550    if (type != None)
1551	Lookup_Formats(type, &format, &dformat);
1552    Apply_Default_Formats(&format, &dformat);
1553
1554    fsize = Get_Format_Size(format);
1555    if (fsize != size && fsize != 0) {
1556	printf(": Type mismatch: assumed size %d bits, actual size %d bits.\n",
1557	       fsize, size);
1558	return;
1559    }
1560
1561    thunks = Break_Down_Property(data, (int)length, type, format, size);
1562    Display_Property(thunks, dformat, format);
1563    free(thunks);
1564}
1565
1566static void
1567Show_All_Props (void)
1568{
1569    if (target_win != (Window) -1) {
1570	int count;
1571	Atom *atoms = XListProperties(dpy, target_win, &count);
1572	for (int i = 0; i < count; i++) {
1573	    const char *name = Format_Atom(atoms[i]);
1574	    Show_Prop(NULL, NULL, name);
1575	}
1576	XFree(atoms);
1577    } else {
1578	for (int i = 0; i < font->n_properties; i++) {
1579	    Atom atom = font->properties[i].name;
1580	    const char *name = Format_Atom(atom);
1581	    Show_Prop(NULL, NULL, name);
1582	}
1583    }
1584}
1585
1586static thunk *
1587Handle_Prop_Requests (int argc, char **argv)
1588{
1589    char *format, *dformat, *prop;
1590    thunk *thunks, t = {0};
1591
1592    /* if no prop referenced, by default list all properties for given window */
1593    if (!argc) {
1594	Show_All_Props();
1595	return NULL;
1596    }
1597
1598    thunks = Create_Thunk_List();
1599
1600    while (argc > 0) {
1601	format = NULL;
1602	dformat = NULL;
1603
1604	/* Get overriding formats, if any */
1605	if (Is_A_Format(argv[0])) {
1606	    format = argv++[0]; argc--;
1607	    if (!argc) usage("format specified without atom");
1608	}
1609	if (Is_A_DFormat(argv[0])) {
1610	    dformat = argv++[0]; argc--;
1611	    if (!argc) usage("dformat specified without atom");
1612	}
1613
1614	/* Get property name */
1615	prop = argv++[0]; argc--;
1616
1617	t.propname = prop;
1618	t.value = Parse_Atom(prop, True);
1619	t.format = format;
1620	t.dformat = dformat;
1621	if (t.value)
1622	    thunks = Add_Thunk(thunks, t);
1623	Show_Prop(format, dformat, prop);
1624    }
1625    return thunks;
1626}
1627
1628static void
1629Remove_Property (Display *display, Window w, const char *propname)
1630{
1631    Atom id = XInternAtom (display, propname, True);
1632
1633    if (id == None) {
1634	fprintf (stderr, "%s:  no such property \"%s\"\n",
1635		 program_name, propname);
1636	return;
1637    }
1638    XDeleteProperty (display, w, id);
1639}
1640
1641static void
1642Set_Property (Display *display, Window w, const char *propname, const char *value)
1643{
1644    Atom atom;
1645    const char *format;
1646    const char *dformat;
1647    int size;
1648    char format_char;
1649    Atom type = 0;
1650    const unsigned char *data = NULL;
1651    int nelements = 0;
1652
1653    atom = Parse_Atom(propname, False);
1654
1655    format = dformat = NULL;
1656    Lookup_Formats(atom, &format, &dformat);
1657    if (format == NULL)
1658	Fatal_Error("unsupported conversion for %s", propname);
1659
1660    size = Get_Format_Size(format);
1661
1662    format_char = Get_Format_Char(format, 0);
1663    switch (format_char) {
1664      case 's':
1665	if (size != 8)
1666	    Fatal_Error("can't use format character 's' with any size except 8.");
1667	type = XA_STRING;
1668	data = (const unsigned char *) value;
1669	nelements = strlen(value);
1670	break;
1671      case 'u':
1672	if (size != 8)
1673	    Fatal_Error("can't use format character 'u' with any size except 8.");
1674	type = XInternAtom(display, "UTF8_STRING", False);
1675	data = (const unsigned char *) value;
1676	nelements = strlen(value);
1677	break;
1678      case 't': {
1679	XTextProperty textprop;
1680	if (size != 8)
1681	    Fatal_Error("can't use format character 't' with any size except 8.");
1682	if (XmbTextListToTextProperty(display, (char **) &value, 1,
1683				      XStdICCTextStyle, &textprop) != Success) {
1684	    fprintf(stderr, "cannot convert %s argument to STRING or COMPOUND_TEXT.\n", propname);
1685	    return;
1686	}
1687	type = textprop.encoding;
1688	data = textprop.value;
1689	nelements = textprop.nitems;
1690	break;
1691      }
1692      case 'x':
1693      case 'c': {
1694	static unsigned char data8[MAXELEMENTS];
1695	static unsigned short data16[MAXELEMENTS];
1696	static unsigned long data32[MAXELEMENTS];
1697	unsigned long intvalue;
1698	char * value2 = strdup(value);
1699	char * tmp = strtok(value2,",");
1700	nelements = 1;
1701	intvalue = strtoul(tmp, NULL, 0);
1702	switch(size) {
1703	    case 8:
1704	        data8[0] = intvalue; data = (const unsigned char *) data8; break;
1705	    case 16:
1706	        data16[0] = intvalue; data = (const unsigned char *) data16; break;
1707	    case 32:
1708	        data32[0] = intvalue; data = (const unsigned char *) data32; break;
1709	}
1710	tmp = strtok(NULL,",");
1711	while(tmp != NULL){
1712	    intvalue = strtoul(tmp, NULL,0);
1713	    switch(size) {
1714		case 8:
1715	    	    data8[nelements] = intvalue; break;
1716		case 16:
1717	    	    data16[nelements] = intvalue; break;
1718		case 32:
1719	    	    data32[nelements] = intvalue; break;
1720	    }
1721	    nelements++;
1722	    if(nelements == MAXELEMENTS){
1723		fprintf(stderr, "Maximum number of elements reached (%d). List truncated.\n",MAXELEMENTS);
1724		break;
1725	    }
1726	    tmp = strtok(NULL,",");
1727	}
1728
1729	type = XA_CARDINAL;
1730	free(value2);
1731	break;
1732      }
1733      case 'i': {
1734	static unsigned char data8[MAXELEMENTS];
1735	static unsigned short data16[MAXELEMENTS];
1736	static unsigned long data32[MAXELEMENTS];
1737	unsigned long intvalue;
1738	char * value2 = strdup(value);
1739	char * tmp = strtok(value2,",");
1740	nelements = 1;
1741	intvalue = strtoul(tmp, NULL, 0);
1742	switch(size) {
1743	    case 8:
1744	        data8[0] = intvalue; data = (const unsigned char *) data8; break;
1745	    case 16:
1746	        data16[0] = intvalue; data = (const unsigned char *) data16; break;
1747	    case 32:
1748	        data32[0] = intvalue; data = (const unsigned char *) data32; break;
1749	}
1750	tmp = strtok(NULL,",");
1751	while(tmp != NULL){
1752	    intvalue = strtoul(tmp, NULL,0);
1753	    switch(size) {
1754		case 8:
1755	    	    data8[nelements] = intvalue; break;
1756		case 16:
1757	    	    data16[nelements] = intvalue; break;
1758		case 32:
1759	    	    data32[nelements] = intvalue; break;
1760	    }
1761	    nelements++;
1762	    if(nelements == MAXELEMENTS){
1763		fprintf(stderr, "Maximum number of elements reached (%d). List truncated.\n",MAXELEMENTS);
1764		break;
1765	    }
1766	    tmp = strtok(NULL,",");
1767	}
1768
1769	type = XA_INTEGER;
1770	free(value2);
1771	break;
1772      }
1773      case 'b': {
1774	unsigned long boolvalue;
1775	static unsigned char data8;
1776	static unsigned short data16;
1777	static unsigned long data32;
1778	if (!strcmp(value, "True"))
1779	    boolvalue = 1;
1780	else if (!strcmp(value, "False"))
1781	    boolvalue = 0;
1782	else {
1783	    fprintf(stderr, "cannot convert %s argument to Bool\n", propname);
1784	    return;
1785	}
1786	type = XA_INTEGER;
1787	switch (size) {
1788	  case 8:
1789	    data8 = boolvalue; data = (const unsigned char *) &data8; break;
1790	  case 16:
1791	    data16 = boolvalue; data = (const unsigned char *) &data16; break;
1792	  case 32: default:
1793	    data32 = boolvalue; data = (const unsigned char *) &data32; break;
1794	}
1795	nelements = 1;
1796	break;
1797      }
1798      case 'a': {
1799	static Atom avalue;
1800	avalue = Parse_Atom(value, False);
1801	type = XA_ATOM;
1802	data = (const unsigned char *) &avalue;
1803	nelements = 1;
1804	break;
1805      }
1806      case 'm':
1807	/* NYI */
1808      default:
1809	Fatal_Error("bad format character: %c", format_char);
1810    }
1811
1812    XChangeProperty(display, target_win, atom, type, size, PropModeReplace,
1813		    data, nelements);
1814}
1815
1816/*
1817 *
1818 * Routines for parsing command line:
1819 *
1820 */
1821
1822static void
1823print_help (void)
1824{
1825    static const char *help_message =
1826"where options include:\n"
1827"    -help                          print out a summary of command line options\n"
1828"    -grammar                       print out full grammar for command line\n"
1829"    -display host:dpy              the X server to contact\n"
1830"    -id id                         resource id of window to examine\n"
1831"    -name name                     name of window to examine\n"
1832"    -font name                     name of font to examine\n"
1833"    -remove propname               remove a property\n"
1834"    -set propname value            set a property to a given value\n"
1835"    -root                          examine the root window\n"
1836"    -len n                         display at most n bytes of any property\n"
1837"    -notype                        do not display the type field\n"
1838"    -fs filename                   where to look for formats for properties\n"
1839"    -frame                         don't ignore window manager frames\n"
1840"    -f propname format [dformat]   formats to use for property of given name\n"
1841"    -spy                           examine window properties forever\n"
1842"    -version                       print program version\n";
1843
1844
1845    fflush (stdout);
1846
1847    fprintf (stderr,
1848	     "usage:  %s [-options ...] [[format [dformat]] atom] ...\n\n",
1849	     program_name);
1850    fprintf (stderr, "%s\n", help_message);
1851}
1852
1853static inline void _X_NORETURN _X_COLD
1854help (void)
1855{
1856	print_help();
1857	exit(0);
1858}
1859
1860void _X_NORETURN _X_COLD
1861usage (const char *errmsg)
1862{
1863    if (errmsg != NULL)
1864	fprintf (stderr, "%s: %s\n\n", program_name, errmsg);
1865
1866    print_help();
1867    exit (1);
1868}
1869
1870static void _X_NORETURN _X_COLD
1871grammar (void)
1872{
1873    printf ("Grammar for xprop:\n\n");
1874    printf("\t%s [<disp>] [<select option>] <option>* <mapping>* <spec>*",
1875	   program_name);
1876    printf("\n\n\tdisp ::= -display host:dpy\
1877\n\tselect option ::= -root | -id <id> | -font <font> | -name <name>\
1878\n\toption ::= -len <n> | -notype | -spy | {-formats|-fs} <format file>\
1879\n\tmapping ::= {-f|-format} <atom> <format> [<dformat>]\
1880\n\t            | -remove <propname>\
1881\n\t            | -set <propname> <value>\
1882\n\tspec ::= [<format> [<dformat>]] <atom>\
1883\n\tformat ::= {0|8|16|32}{a|b|c|i|m|s|t|x}*\
1884\n\tdformat ::= <unit><unit>*             (can't start with a letter or '-')\
1885\n\tunit ::= ?<exp>(<unit>*) | $<n> | <display char>\
1886\n\texp ::= <term> | <term>=<exp> | !<exp>\
1887\n\tterm ::= <n> | $<n> | m<n>\
1888\n\tdisplay char ::= <normal char> | \\<non digit char> | \\<octal number>\
1889\n\tnormal char ::= <any char except a digit, $, ?, \\, or )>\
1890\n\n");
1891    exit(0);
1892}
1893
1894static void
1895Parse_Format_Mapping (int *argc, char ***argv)
1896{
1897#define ARGC (*argc)
1898#define ARGV (*argv)
1899#define OPTION ARGV[0]
1900#define NXTOPT if (++ARGV, --ARGC==0) usage("insufficient arguments for -format")
1901    char *type_name, *format, *dformat;
1902
1903    NXTOPT; type_name = OPTION;
1904
1905    NXTOPT; format = OPTION;
1906    if (!Is_A_Format(format))
1907	Fatal_Error("Bad format: %s.", format);
1908
1909    dformat = NULL;
1910    if (ARGC>1 && Is_A_DFormat(ARGV[1])) {
1911	ARGV++; ARGC--; dformat = OPTION;
1912    }
1913
1914    Add_Mapping(Parse_Atom(type_name, False), format, dformat);
1915}
1916
1917/*
1918 *
1919 * The Main Program:
1920 *
1921 */
1922
1923static int spy = 0;
1924
1925static int (*old_error_handler)(Display *display, XErrorEvent *ev);
1926
1927static int spy_error_handler(Display *display, XErrorEvent *ev)
1928{
1929    if (ev->error_code == BadWindow || ev->error_code == BadMatch) {
1930	/* Window was destroyed */
1931	puts("");
1932	exit(0);
1933    }
1934
1935    if (old_error_handler)
1936	return old_error_handler(display, ev);
1937
1938    return 0;
1939}
1940
1941int
1942main (int argc, char **argv)
1943{
1944    FILE *stream;
1945    char *name;
1946    thunk *props;
1947    thunk *remove_props = NULL;
1948    thunk *set_props = NULL;
1949    Bool frame_only = False;
1950    int n;
1951    char **nargv;
1952
1953#ifdef TIOCGWINSZ
1954    struct winsize ws;
1955    ws.ws_col = 0;
1956    if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) != -1 && ws.ws_col != 0)
1957	term_width = ws.ws_col;
1958#endif
1959
1960    INIT_NAME;
1961
1962    /* Set locale for XmbTextProptertyToTextList and iswprint(). */
1963    setlocale(LC_CTYPE, "");
1964
1965    /* Handle display name, opening the display */
1966    Setup_Display_And_Screen(&argc, argv);
1967
1968    /* Handle selecting the window to display properties for */
1969    target_win = Select_Window_Args(&argc, argv);
1970
1971    /* Set up default atom to format, dformat mapping */
1972    XpropMode = XpropWindowProperties;
1973    for (n = argc, nargv = argv; n; nargv++, n--)
1974	if (! strcmp(nargv[0], "-font")) {
1975	    XpropMode = XpropFontProperties;
1976	    break;
1977	}
1978    Setup_Mapping();
1979    if ((name = getenv("XPROPFORMATS"))) {
1980	if (!(stream = fopen(name, "r")))
1981	    Fatal_Error("unable to open file %s for reading.", name);
1982	Read_Mappings(stream);
1983	fclose(stream);
1984    }
1985
1986    /* Handle '-' options to setup xprop, select window to work on */
1987    while (argv++, --argc>0 && **argv == '-') {
1988	if (!strcmp(argv[0], "-"))
1989	    continue;
1990	if (!strcmp(argv[0], "-help")) {
1991	    help ();
1992	    /* NOTREACHED */
1993	}
1994	if (!strcmp(argv[0], "-grammar")) {
1995	    grammar ();
1996	    /* NOTREACHED */
1997	}
1998	if (!strcmp(argv[0], "-notype")) {
1999	    notype = 1;
2000	    continue;
2001	}
2002	if (!strcmp(argv[0], "-spy")) {
2003	    spy = 1;
2004	    continue;
2005	}
2006	if (!strcmp(argv[0], "-len")) {
2007	    if (++argv, --argc == 0) usage("-len requires an argument");
2008	    max_len = atoi(argv[0]);
2009	    continue;
2010	}
2011	if (!strcmp(argv[0], "-formats") || !strcmp(argv[0], "-fs")) {
2012	    if (++argv, --argc == 0) usage("-fs requires an argument");
2013	    if (!(stream = fopen(argv[0], "r")))
2014		Fatal_Error("unable to open file %s for reading.", argv[0]);
2015	    Read_Mappings(stream);
2016	    fclose(stream);
2017	    continue;
2018	}
2019	if (!strcmp(argv[0], "-font")) {
2020	    if (++argv, --argc == 0) usage("-font requires an argument");
2021	    font = Open_Font(argv[0]);
2022	    target_win = (Window) -1;
2023	    continue;
2024	}
2025	if (!strcmp(argv[0], "-remove")) {
2026	    thunk t = {0};
2027	    if (++argv, --argc == 0) usage("-remove requires an argument");
2028	    t.propname = argv[0];
2029	    if (remove_props == NULL) remove_props = Create_Thunk_List();
2030	    remove_props = Add_Thunk(remove_props, t);
2031	    continue;
2032	}
2033	if (!strcmp(argv[0], "-set")) {
2034	    thunk t = {0};
2035	    if (argc < 3) usage("insufficient arguments for -set");
2036	    t.propname = argv[1];
2037	    t.extra_value = argv[2];
2038	    argv += 3; argc -= 3;
2039	    if (set_props == NULL) set_props = Create_Thunk_List();
2040	    set_props = Add_Thunk(set_props, t);
2041	    continue;
2042	}
2043	if (!strcmp(argv[0], "-frame")) {
2044	    frame_only = True;
2045	    continue;
2046	}
2047	if (!strcmp(argv[0], "-f") || !strcmp(argv[0], "-format")) {
2048	    Parse_Format_Mapping(&argc, &argv);
2049	    continue;
2050	}
2051	if (!strcmp(argv[0], "-version")) {
2052	    puts(PACKAGE_STRING);
2053	    exit(0);
2054	}
2055	fprintf (stderr, "%s: unrecognized argument %s\n\n",
2056		 program_name, argv[0]);
2057	usage(NULL);
2058    }
2059
2060    if ((remove_props != NULL || set_props != NULL) && argc > 0) {
2061	fprintf (stderr, "%s: unrecognized argument %s\n\n",
2062		 program_name, argv[0]);
2063	usage(NULL);
2064    }
2065
2066    if (target_win == None)
2067	target_win = Select_Window(dpy, !frame_only);
2068
2069    if (remove_props != NULL) {
2070	int count;
2071
2072	if (target_win == (Window) -1)
2073	    Fatal_Error("-remove works only on windows, not fonts");
2074
2075	count = remove_props->thunk_count;
2076	for (; count > 0; remove_props++, count--)
2077	    Remove_Property (dpy, target_win, remove_props->propname);
2078    }
2079
2080    if (set_props != NULL) {
2081	int count;
2082
2083	if (target_win == (Window) -1)
2084	    Fatal_Error("-set works only on windows, not fonts");
2085
2086	count = set_props->thunk_count;
2087	for (; count > 0; set_props++, count--)
2088	    Set_Property (dpy, target_win, set_props->propname,
2089			  set_props->extra_value);
2090    }
2091
2092    if (remove_props != NULL || set_props != NULL) {
2093	XCloseDisplay (dpy);
2094	exit (0);
2095    }
2096
2097    props = Handle_Prop_Requests(argc, argv);
2098
2099    if (spy && target_win != (Window) -1) {
2100	XEvent event;
2101	const char *format, *dformat;
2102
2103	XSelectInput(dpy, target_win, PropertyChangeMask | StructureNotifyMask);
2104 	old_error_handler = XSetErrorHandler(spy_error_handler);
2105	for (;;) {
2106	    fflush(stdout);
2107	    XNextEvent(dpy, &event);
2108 	    if (event.type == DestroyNotify)
2109 		break;
2110 	    if (event.type != PropertyNotify)
2111 		continue;
2112	    format = dformat = NULL;
2113	    if (props) {
2114		int i;
2115		for (i = 0; i < props->thunk_count; i++)
2116		    if (props[i].value == (long) event.xproperty.atom)
2117			break;
2118		if (i >= props->thunk_count)
2119		    continue;
2120		format = props[i].format;
2121		dformat = props[i].dformat;
2122	    }
2123	    Show_Prop(format, dformat, Format_Atom(event.xproperty.atom));
2124	}
2125    }
2126    exit (0);
2127}
2128