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