1 /* gxmessage - an xmessage clone using GTK
2  *
3  * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
4  * 2012, 2014, 2015 Timothy Richard Musson
5  *
6  * Email: Tim Musson <trmusson@gmail.com>
7  * WWW:   http://homepages.ihug.co.nz/~trmusson/programs.html#gxmessage
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  */
23 
24 #include <config.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <ctype.h>
29 #include <gtk/gtk.h>
30 #include <gdk/gdkkeysyms.h>
31 
32 
33 /* Details for Copyright and bug report messages: */
34 #define AUTHOR  "Timothy Richard Musson"
35 #define YEAR    "2015"
36 #define MAILTO  "<trmusson@gmail.com>"
37 
38 /* If the following file exists, the -noescape option will be allowed to work: */
39 #define ALLOW_NOESCAPE PACKAGE_DATA_DIR \
40             G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "allow_noescape"
41 
42 #define MAX_WINDOW_SIZE (G_MAXUINT16 / 2)
43 
44 #ifdef ENABLE_NLS
45 #  include <libintl.h>
46 #  define _(String) gettext(String)
47 #  ifdef gettext_noop
48 #    define N_(String) gettext_noop(String)
49 #  else
50 #    define N_(String) (String)
51 #  endif
52 #else
53 #  define _(String) (String)
54 #  define N_(String) (String)
55 #  define textdomain(String) (String)
56 #  define gettext(String) (String)
57 #  define dgettext(Domain,String) (String)
58 #  define dcgettext(Domain,String,Type) (String)
59 #  define bindtextdomain(Domain,Directory) (Domain)
60 #endif /* ENABLE_NLS */
61 
62 
63 typedef struct _Button Button;
64 
65 
66 struct {
67 	gchar             *message_text;
68 	gint              message_len;
69 	Button            *button_list;
70 	const gchar       *default_str;
71 	const gchar       *title_str;
72 	const gchar       *geom_str;
73 	const gchar       *font_str;
74 	const gchar       *color_fg;
75 	const gchar       *color_bg;
76 	const gchar       *encoding;
77 	const gchar       *entry_str;
78 	gint              timeout;
79 	guint             timeout_id;
80 	gboolean          do_iconify;
81 	gboolean          do_ontop;
82 	gboolean          do_print;
83 	gboolean          do_buttons;
84 	gboolean          do_borderless;
85 	gboolean          do_sticky;
86 	gboolean          do_focus;
87 	gboolean          allow_escape;
88 	GtkWrapMode       wrap_mode;
89 	GtkWindowPosition window_position;
90 	GtkWidget         *entry_widget;
91 	gint              exit_code;
92 } gx;
93 
94 
95 struct _Button {
96 	gboolean      is_default;
97 	gint          value;
98 	const gchar   *label;
99 	Button        *prev;
100 	Button        *next;
101 };
102 
103 
104 struct Option {
105 	gint     min_len; /* support -bu, -but, -butt, etc., as with xmessage */
106 	gboolean requires_arg;
107 	gchar    *opt_str;
108 };
109 
110 
111 static struct Option option[] = {
112 	{2, TRUE,  "buttons"},
113 	{1, FALSE, "center"},
114 	{2, TRUE,  "default"},
115 	{2, TRUE,  "file"},
116 	{2, FALSE, "nearmouse"},
117 	{1, FALSE, "print"},
118 	{3, TRUE,  "timeout"},
119 	{2, TRUE,  "fn"},
120 	{2, TRUE,  "font"},
121 	{1, TRUE,  "geometry"},
122 	{3, TRUE,  "title"},
123 	{2, TRUE,  "bg"},
124 	{2, TRUE,  "fg"},
125 	{2, TRUE,  "bd"},
126 	{2, TRUE,  "bw"},
127 	{1, FALSE, "iconic"},
128 	{1, FALSE, "ontop"},
129 	{2, TRUE,  "xrm"},
130 	{2, FALSE, "rv"},
131 	{2, FALSE, "reverse"},
132 	{2, TRUE,  "selectionTimeout"},
133 	{2, FALSE, "synchronous"},
134 	{2, TRUE,  "xnllanguage"},
135 	{2, TRUE,  "name"},
136 	{2, TRUE,  "display"},
137 	{2, FALSE, "borderless"},
138 	{2, FALSE, "sticky"},
139 	{1, FALSE, "wrap"},
140 	{3, TRUE,  "encoding"},
141 	{3, FALSE, "nofocus"},
142 	{3, FALSE, "noescape"},
143 	{6, TRUE,  "entrytext"},
144 	{3, FALSE, "entry"},
145 	{1, FALSE, "?"},
146 	{1, FALSE, "help"},
147 	{1, FALSE, "version"}
148 };
149 
150 enum {
151 	OPT_IS_UNKNOWN = -2,
152 	OPT_IS_MISSING_ARG,
153 	OPT_BUTTONS,
154 	OPT_CENTER,
155 	OPT_DEFAULT,
156 	OPT_FILE,
157 	OPT_NEARMOUSE,
158 	OPT_PRINT,
159 	OPT_TIMEOUT,
160 	OPT_FN,
161 	OPT_FONT,
162 	OPT_GEOMETRY,
163 	OPT_TITLE,
164 	OPT_BG,
165 	OPT_FG,
166 	OPT_BD,
167 	OPT_BW,
168 	OPT_ICONIC,
169 	OPT_ONTOP,
170 	OPT_XRM,
171 	OPT_RV,
172 	OPT_REVERSE,
173 	OPT_SELECTIONTIMEOUT,
174 	OPT_SYNCHRONOUS,
175 	OPT_XNLLANGUAGE,
176 	OPT_NAME,
177 	OPT_DISPLAY,
178 	OPT_BORDERLESS,
179 	OPT_STICKY,
180 	OPT_WRAP,
181 	OPT_ENCODING,
182 	OPT_FOCUS,
183 	OPT_NOESCAPE,
184 	OPT_ENTRYTEXT,
185 	OPT_ENTRY,
186 	OPT_HELP_Q,
187 	OPT_HELP,
188 	OPT_VERSION,
189 	N_OPTS
190 };
191 
192 
193 void prog_cleanup (void);
194 
195 
196 Button*
button_first(Button * button)197 button_first (Button *button)
198 {
199 	if (button != NULL) {
200 		while (button->prev != NULL) {
201 			button = button->prev;
202 		}
203 	}
204 	return button;
205 }
206 
207 
208 void
button_free_all(Button * button)209 button_free_all (Button *button)
210 {
211 	Button *next;
212 
213 	button = button_first (button);
214 	while (button != NULL) {
215 		next = button->next;
216 		g_free (button);
217 		button = next;
218 	}
219 }
220 
221 
222 Button*
button_append(Button * button,Button * button_new)223 button_append (Button *button, Button *button_new)
224 {
225 	if (button != NULL) {
226 		button_new->prev = button;
227 		button->next = button_new;
228 	}
229 	return button_new;
230 }
231 
232 
233 gint
parse_label_value_pair(gchar * str,gint value,gint * len,gboolean * end)234 parse_label_value_pair (gchar *str, gint value, gint *len, gboolean *end)
235 {
236 	gchar *colon = NULL;
237 
238 	*end = FALSE;
239 	*len = 0;
240 
241 	while (*str != '\0') {
242 		if (*str == '\\') {
243 			/* unescape */
244 			memmove (str, str + 1, strlen (str));
245 		}
246 		else if (*str == ':') {
247 			/* take note of the last colon found */
248 			colon = str;
249 		}
250 		else if (*str == ',') {
251 			/* end of pair */
252 			break;
253 		}
254 		str++;
255 		*len = *len + 1;
256 	}
257 
258 	if (*str == '\0') {
259 		*end = TRUE;
260 	}
261 	else {
262 		*str = '\0';
263 	}
264 
265 	if (colon) {
266 		/* replace default value with value from string */
267 		*colon = '\0';
268 		value = atoi (++colon);
269 	}
270 
271 	return value;
272 }
273 
274 
275 Button*
button_list_from_str(gchar * str)276 button_list_from_str (gchar *str)
277 {
278 	/* Split "LABEL:VALUE,LABEL:VALUE,..." into a list of buttons */
279 
280 	Button *button, *blist = NULL;
281 	gint len, value, default_value = 101;
282 	gboolean end;
283 
284 	if (str == NULL) return NULL;
285 
286 	do {
287 		value = parse_label_value_pair (str, default_value, &len, &end);
288 		gx.do_buttons |= len > 0;
289 		button = g_new0 (Button, 1);
290 		button->label = str;
291 		button->value = value;
292 		blist = button_append (blist, button);
293 		str = str + len + 1;
294 		default_value++;
295 	} while (!end);
296 
297 	/* return the last item */
298 	return blist;
299 }
300 
301 
302 void
button_set_default(Button * button,const gchar * str)303 button_set_default (Button *button, const gchar *str)
304 {
305 	/* Make button->is_default TRUE for each button whose label matches str */
306 
307 	if (str == NULL) return;
308 
309 	button = button_first (button);
310 	while (button != NULL) {
311 		button->is_default = strcmp (button->label, str) == 0;
312 		button = button->next;
313 	}
314 }
315 
316 
317 gint
my_get_opt(const gchar * str,gboolean not_last)318 my_get_opt (const gchar *str, gboolean not_last)
319 {
320 	/* Try to identify the command line option in str, returning a unique
321 	 * option/error code. The not_last variable specifies whether current
322 	 * option was followed by something else (a value or another option)
323 	 * on the command line.
324 	 */
325 
326 	gint opt, len;
327 
328 	if (strcmp (str, "+rv") == 0) return OPT_RV;
329 	if (*str != '-') return OPT_IS_UNKNOWN;
330 
331 	str++;
332 	if (*str == '-') str++;
333 	len = strlen (str);
334 
335 	if (len > 0) {
336 		for (opt = 0; opt < N_OPTS; opt++) {
337 			if (len >= option[opt].min_len &&
338 			    strncmp (str, option[opt].opt_str, len) == 0) {
339 				if (!option[opt].requires_arg || not_last) {
340 					return opt;
341 				}
342 				else {
343 					return OPT_IS_MISSING_ARG;
344 				}
345 			}
346 		}
347 	}
348 	return OPT_IS_UNKNOWN;
349 }
350 
351 
352 void
cb_window_destroy(GtkWidget * widget,GdkEvent * event,gpointer data)353 cb_window_destroy (GtkWidget *widget, GdkEvent *event, gpointer data)
354 {
355 	gtk_main_quit ();
356 }
357 
358 
359 gboolean
cb_key_press(GtkWidget * w,GdkEventKey * event,gpointer data)360 cb_key_press (GtkWidget *w, GdkEventKey *event, gpointer data)
361 {
362 	if (gx.allow_escape && (event->keyval == GDK_KEY_Escape)) {
363 		gtk_main_quit ();
364 	}
365 	return FALSE;
366 }
367 
368 
369 void
cb_button_clicked(GtkWidget * widget,gpointer data)370 cb_button_clicked (GtkWidget *widget, gpointer data)
371 {
372 	gx.exit_code = ((Button*)data)->value;
373 
374 	if (gx.do_print) {
375 		g_print ("%s\n", ((Button*)data)->label);
376 	}
377 	else if (gx.entry_str != NULL) {
378 		g_print ("%s\n", gtk_entry_get_text (GTK_ENTRY(gx.entry_widget)));
379 	}
380 	gtk_main_quit ();
381 }
382 
383 
384 void
cb_entry_activated(GtkWidget * widget,gpointer data)385 cb_entry_activated (GtkWidget *widget, gpointer data)
386 {
387 	gx.exit_code = 0;
388 	g_print ("%s\n", gtk_entry_get_text (GTK_ENTRY(gx.entry_widget)));
389 	gtk_main_quit ();
390 }
391 
392 
393 gboolean
cb_timeout(gint * timeout)394 cb_timeout (gint *timeout)
395 {
396 	static gint counter = 0;
397 
398 	if (++counter >= *timeout) {
399 		gx.exit_code = 0;
400 		gtk_main_quit ();
401 	}
402 	return TRUE;
403 }
404 
405 
406 gboolean
label_width_okay(const gchar * str)407 label_width_okay (const gchar *str)
408 {
409 	GtkWidget *dummy;
410 	PangoLayout *layout;
411 	int width;
412 
413 	dummy = gtk_label_new (NULL);
414 	layout = gtk_label_get_layout (GTK_LABEL(dummy));
415 	pango_layout_set_text (layout, str, -1);
416 	pango_layout_get_pixel_size (layout, &width, NULL);
417 	gtk_widget_destroy (dummy);
418 
419 	if (width > MAX_WINDOW_SIZE) {
420 		g_warning ("%s: button too wide\n", PACKAGE);
421 		return FALSE;
422 	}
423 
424 	return TRUE;
425 }
426 
427 
428 void
window_create(void)429 window_create (void)
430 {
431 	GtkWidget  *window;
432 	GtkWidget  *vbox, *vbox2;
433 	GtkWidget  *scroller;
434 	GtkWidget  *btn_box;
435 	GtkWidget  *btn;
436 	GtkWidget  *message_widget;
437 	Button     *button;
438 	GdkRGBA    color;
439 	GtkRequisition size_req;
440 	GtkTextBuffer *buf;
441 	GtkTextIter iter;
442 	gint       win_w, win_h;
443 	gint       max_w, max_h;
444 
445 
446     gtk_window_set_default_icon_name ("gxmessage");
447 
448 	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
449 
450 	g_signal_connect (G_OBJECT(window), "destroy",
451 	                  G_CALLBACK(cb_window_destroy), NULL);
452 
453 	g_signal_connect (G_OBJECT(window), "key_press_event",
454 	                  G_CALLBACK(cb_key_press), NULL);
455 
456 	if (gx.title_str != NULL) {
457 		gtk_window_set_title (GTK_WINDOW(window), gx.title_str);
458 	}
459 
460 	if (gx.do_iconify) {
461 		gtk_window_iconify (GTK_WINDOW(window));
462 	}
463 
464 	if (gx.do_sticky) {
465 		gtk_window_stick (GTK_WINDOW(window));
466 	}
467 
468 	if (gx.do_ontop) {
469 		gtk_window_set_keep_above (GTK_WINDOW(window), TRUE);
470 	}
471 
472 	if (gx.do_borderless) {
473 		gtk_window_set_decorated (GTK_WINDOW(window), FALSE);
474 	}
475 
476 	gtk_window_set_accept_focus (GTK_WINDOW(window), gx.do_focus);
477 
478 	/* window contents */
479 	gtk_container_set_border_width (GTK_CONTAINER(window), 12);
480 
481 	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
482 	gtk_container_add (GTK_CONTAINER(window), vbox);
483 
484 	vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
485 	gtk_box_pack_start (GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
486 	gtk_container_set_border_width (GTK_CONTAINER(vbox2), 0);
487 
488 	scroller = gtk_scrolled_window_new (NULL, NULL);
489 	gtk_container_set_border_width (GTK_CONTAINER(scroller), 0);
490 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(scroller), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
491 	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(scroller), GTK_SHADOW_ETCHED_IN);
492 	gtk_scrolled_window_set_placement (GTK_SCROLLED_WINDOW(scroller), GTK_CORNER_TOP_LEFT);
493 	gtk_box_pack_start (GTK_BOX(vbox2), scroller, TRUE, TRUE, 0);
494 
495 	/* the message */
496 	message_widget = gtk_text_view_new ();
497 	gtk_widget_set_name (message_widget, "gxmessage-textview");
498 	gtk_text_view_set_editable (GTK_TEXT_VIEW(message_widget), FALSE);
499 	gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW(message_widget), TRUE);
500 	gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW(message_widget), gx.wrap_mode);
501 
502 	if (gx.font_str != NULL) {
503 		PangoFontDescription *font_desc;
504 		font_desc = pango_font_description_from_string (gx.font_str);
505 		gtk_widget_override_font (message_widget, font_desc);
506 		pango_font_description_free (font_desc);
507 	}
508 
509 	gtk_container_add (GTK_CONTAINER(scroller), message_widget);
510 
511 	buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW(message_widget));
512 	gtk_text_buffer_set_text (buf, gx.message_text, strlen (gx.message_text));
513 
514 	gtk_text_buffer_get_start_iter (buf, &iter);
515 	gtk_text_buffer_place_cursor (buf, &iter);
516 
517 	if (gx.color_fg != NULL) {
518 		if (gdk_rgba_parse (&color, gx.color_fg)) {
519 			gtk_widget_override_color (message_widget,
520 			  GTK_STATE_NORMAL, &color);
521 		}
522 	}
523 	if (gx.color_bg != NULL) {
524 		if (gdk_rgba_parse (&color, gx.color_bg)) {
525 			gtk_widget_override_background_color (message_widget,
526 			  GTK_STATE_NORMAL, &color);
527 		}
528 	}
529 
530 
531 	/* text entry */
532 	if (gx.entry_str != NULL) {
533 		gx.entry_widget = gtk_entry_new ();
534 		gtk_widget_set_name (gx.entry_widget, "gxmessage-entry");
535 		gtk_editable_set_editable (GTK_EDITABLE(gx.entry_widget), TRUE);
536 		gtk_entry_set_text (GTK_ENTRY(gx.entry_widget), gx.entry_str);
537 		gtk_box_pack_start (GTK_BOX(vbox), gx.entry_widget, FALSE, FALSE, 5);
538 		gtk_widget_grab_focus (gx.entry_widget);
539 		if (!gx.do_buttons) {
540 			/* allow hitting <RETURN> to close the window */
541 			g_signal_connect (G_OBJECT(gx.entry_widget), "activate",
542 			                  G_CALLBACK(cb_entry_activated), (gpointer)0);
543 		}
544 	}
545 
546 
547 	/* add buttons */
548 	if (gx.do_buttons) {
549 
550 		button = button_first (gx.button_list);
551 
552 		btn_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
553 		gtk_button_box_set_layout (GTK_BUTTON_BOX(btn_box),
554 		                           GTK_BUTTONBOX_END);
555 		gtk_box_set_spacing (GTK_BOX(btn_box), 6);
556 		gtk_box_pack_end (GTK_BOX(vbox), btn_box, FALSE, FALSE, 0);
557 
558 		while (button != NULL) {
559 
560 			if (strcmp (button->label, "okay") == 0) {
561 				/* XXX: gtk_button_new_from_stock deprecated since GTK 3.10 */
562 				btn = gtk_button_new_from_stock ("gtk-ok");
563 			}
564 			else if (g_str_has_prefix (button->label, "GTK_STOCK_")) {
565 				gchar *s;
566 				gchar *p;
567 				p = g_ascii_strdown (button->label + 10, -1);
568 				s = p;
569 				while (*s != '\0') {
570 					if (*s == '_') *s = '-';
571 					s++;
572 				}
573 				s = g_strconcat ("gtk-", p, NULL);
574 				if (!label_width_okay (s)) {
575 					/* XXX: Truncate and carry on, or quit? */
576 					prog_cleanup ();
577 				}
578 				/* XXX: gtk_button_new_from_stock deprecated since GTK 3.10 */
579 				btn = gtk_button_new_from_stock (s);
580 				g_free (s);
581 				g_free (p);
582 			}
583 			else {
584 
585 				if (!label_width_okay (button->label)) {
586 					/* XXX: Truncate and carry on, or quit? */
587 					prog_cleanup ();
588 				}
589 				btn = gtk_button_new_with_mnemonic (button->label);
590 			}
591 
592 			g_signal_connect (G_OBJECT(btn), "clicked",
593 			                  G_CALLBACK(cb_button_clicked), (gpointer)button);
594 			gtk_box_pack_start (GTK_BOX(btn_box), btn, FALSE, FALSE, 0);
595 
596 			if (button->is_default) {
597 				gtk_widget_grab_focus (btn);
598 			}
599 
600 			button = button->next;
601 		}
602 	}
603 
604 
605 	/* window geometry */
606 
607 	max_w = gdk_screen_width () * 0.7;
608 	max_h = gdk_screen_height () * 0.7;
609 
610 	/* Render dummy text, to get an idea of its size. This is slow when
611 	 * there's a lot of text, so default to max_w and max_h in that case.
612 	 */
613 	if (gx.message_len > 20000) {
614 		win_w = max_w;
615 		win_h = max_h;
616 	}
617 	else {
618 
619 		GtkWidget *dummy = gtk_label_new (gx.message_text);
620 		GtkStyleContext *context;
621 		PangoFontDescription *font_desc;
622 
623 		context = gtk_widget_get_style_context (message_widget);
624 		gtk_style_context_get (context, GTK_STATE_FLAG_NORMAL,
625 		  GTK_STYLE_PROPERTY_FONT, &font_desc, NULL);
626 		gtk_widget_override_font (dummy, font_desc);
627 
628 		pango_font_description_free (font_desc);
629 
630 		gtk_container_add (GTK_CONTAINER(vbox), dummy);
631 		gtk_widget_show (dummy);
632 		gtk_widget_get_preferred_size (dummy, &size_req, NULL);
633 		gtk_widget_destroy (dummy);
634 		/* ~50 pixels for borders and scrollbar space */
635 		win_w = size_req.width + 50;
636 		win_h = size_req.height + 50;
637 		if (win_w > max_w) win_w = max_w;
638 		if (win_h > max_h) win_h = max_h;
639 	}
640 
641 	if (gx.entry_str != NULL) {
642 		gtk_widget_get_preferred_size (gx.entry_widget, &size_req, NULL);
643 		win_h = win_h + size_req.height + 12;
644 	}
645 
646 	if (gx.do_buttons) {
647 		gtk_widget_get_preferred_size (btn, &size_req, NULL);
648 		win_h = win_h + size_req.height + 12;
649 	}
650 
651 	gtk_window_set_position (GTK_WINDOW(window), gx.window_position);
652 	gtk_window_set_default_size (GTK_WINDOW(window), win_w, win_h);
653 
654 	if (gx.geom_str != NULL) {
655 		gtk_widget_show_all (vbox); /* must precede parse_geometry */
656 		gtk_window_parse_geometry (GTK_WINDOW(window), gx.geom_str);
657 	}
658 
659 	/* open the window */
660 	gtk_widget_show_all (window);
661 
662 
663 	/* begin timeout */
664 	if (gx.timeout != 0) {
665 		gx.timeout_id = g_timeout_add (1000, (GSourceFunc)cb_timeout,
666 		                               &gx.timeout);
667 	}
668 }
669 
670 
671 gchar*
read_stdin(void)672 read_stdin (void)
673 {
674 	GString *text;
675 	gchar *str;
676 	gint ch;
677 
678 	text = g_string_new ("");
679 
680 	while ( (ch = getc (stdin)) != EOF ) {
681 		g_string_append_c (text, ch);
682 	}
683 	str = text->str;
684 	g_string_free (text, FALSE);
685 	return str;
686 }
687 
688 
689 gchar*
message_to_utf8(const gchar * str)690 message_to_utf8 (const gchar *str)
691 {
692 	gchar *result;
693 	GError *error = NULL;
694 
695 	if (gx.encoding == NULL) {
696 		/* assume message encoding matches current locale */
697 		result = g_locale_to_utf8 (str, -1, NULL, NULL, NULL);
698 	}
699 	else {
700 		/* use encoding specified on command line */
701 		result = g_convert_with_fallback (str, -1, "UTF-8", gx.encoding,
702 		                                  NULL, NULL, NULL, NULL);
703 	}
704 
705 	if (result == NULL) {
706 		/* fall back to ISO-8859-1 as source encoding */
707 		result = g_convert_with_fallback (str, -1, "UTF-8", "ISO-8859-1",
708 		                                  NULL, NULL, NULL, &error);
709 		if (result == NULL) {
710 			if (error != NULL && error->message != NULL) {
711 				g_printerr (PACKAGE ": %s\n", error->message);
712 			}
713 			prog_cleanup ();
714 		}
715 	}
716 	return result;
717 }
718 
719 
720 gboolean
my_gtk_init(gint argc,gchar * argv[])721 my_gtk_init (gint argc, gchar *argv[])
722 {
723 	/* Let gtk_init see --display and --name, but no other options.
724 	 * Return FALSE if gtk_init fails.
725 	 */
726 
727 	gboolean ok;
728 	gchar *s, **av;
729 	gint i, len, n = 1;
730 
731 	av = g_malloc (sizeof(char*) * (argc + 1));
732 	av[0] = argv[0];
733 
734 	for (i = 1; i < argc; i++) {
735 		s = argv[i];
736 		if (s[0] != '-') continue;
737 		if (s[1] == '-') s++;
738 		len = strlen (s);
739 		if (len > 2 && i + 1 < argc) {
740 			if (strncmp ("-display", s, len) == 0) {
741 				av[n++] = "--display";
742 				av[n++] = argv[++i];
743 			}
744 			else if (strncmp ("-name", s, len) == 0) {
745 				av[n++] = "--name";
746 				av[n++] = argv[++i];
747 			}
748 		}
749 	}
750 	av[n] = NULL;
751 	ok = gtk_init_check (&n, &av);
752 	g_free (av);
753 	return ok;
754 }
755 
756 
757 void
usage(void)758 usage (void)
759 {
760 	g_print(_("\n%s - a GTK-based xmessage clone\n"), PACKAGE);
761 	g_print("\n");
762 	g_print(_("Usage: %s [OPTIONS] message ...\n"), PACKAGE);
763 	g_print(_("       %s [OPTIONS] -file FILENAME\n"), PACKAGE);
764 	g_print("\n");
765 	g_print(_("xmessage options:\n"));
766 	g_print(_("  -file FILENAME         Get message text from file, '-' for stdin\n"));
767 	g_print(_("  -buttons BUTTON_LIST   List of \"LABEL:EXIT_CODE\", comma separated\n"));
768 	g_print(_("  -default LABEL         Give keyboard focus to the specified button\n"));
769 	g_print(_("  -print                 Send the selected button's LABEL to stdout\n"));
770 	g_print(_("  -center                Open the window in the center of the screen\n"));
771 	g_print(_("  -nearmouse             Open the window near the mouse pointer\n"));
772 	g_print(_("  -timeout SECONDS       Exit with code 0 after SECONDS seconds\n"));
773 	g_print(_("  -display DISPLAY       X display to use\n"));
774 	g_print(_("  -fn FONT | -font FONT  Set message font (works with GTK font names)\n"));
775 	g_print(_("  -fg COLOR              Set message font color\n"));
776 	g_print(_("  -bg COLOR              Set message background color\n"));
777 	g_print(_("  -geometry GEOMETRY     Set window size (position will be ignored)\n"));
778 	g_print(_("  -iconic                Start iconified\n"));
779 	g_print(_("  -name NAME             Program name as used by the window manager\n"));
780 	g_print(_("  -title TITLE           Set window title to TITLE\n"));
781 	g_print("\n");
782 	g_print(_("Additional %s options:\n"), PACKAGE);
783 	g_print(_("  -borderless            Open the window without border decoration\n"));
784 	g_print(_("  -sticky                Make the window stick to all desktops\n"));
785 	g_print(_("  -ontop                 Keep window on top\n"));
786 	g_print(_("  -nofocus               Don't focus the window when it opens\n"));
787 	g_print(_("  -noescape              Don't allow pressing ESC to close the window\n"));
788 	g_print(_("  -encoding CHARSET      Expect CHARSET as the message encoding\n"));
789 	g_print(_("  -entry                 Prompt for text to be sent to stdout\n"));
790 	g_print(_("  -entrytext TEXT        Same as -entry, but with TEXT as default text\n"));
791 	g_print(_("  -wrap                  Wrap lines of text to fit window width\n"));
792 	g_print(_("  -help | -?             Show this usage information\n"));
793 	g_print(_("  -version               Show gxmessage version and Copyright details\n"));
794 	g_print("\n");
795 	g_print(_("Please report bugs to %s.\n"), MAILTO);
796 	g_print("\n");
797 
798 	prog_cleanup ();
799 }
800 
801 
802 void
prog_cleanup(void)803 prog_cleanup (void)
804 {
805 	button_free_all (gx.button_list);
806 	if (gx.message_text != NULL) {
807 		g_free (gx.message_text);
808 	}
809 	if (gx.timeout_id != 0) {
810 		g_source_remove (gx.timeout_id);
811 	}
812 	exit (gx.exit_code);
813 }
814 
815 
816 int
main(gint argc,gchar * argv[])817 main (gint argc, gchar *argv[])
818 {
819 	GString *gstr = NULL;
820 	gchar *ch = NULL, *tmpstr;
821 	const gchar *fname = NULL;
822 	gint opt, arg = 1;
823 	gboolean ok;
824 	gchar bu_default[] = "okay:0";
825 
826 	/* The default "okay:0" string is intentionally hard-wired, to avoid
827 	 * breaking scripts that make use of xmessage's -print option.
828 	 * It must not be changed or gettextize'd. */
829 
830 #ifdef ENABLE_NLS
831 	bindtextdomain (PACKAGE, PACKAGE_LOCALE_DIR);
832 	bind_textdomain_codeset (PACKAGE, "UTF-8");
833 	textdomain (PACKAGE);
834 #endif
835 
836 	ok = my_gtk_init (argc, argv);
837 
838 	gx.exit_code       = 1;
839 	gx.do_ontop        = FALSE;
840 	gx.do_focus        = TRUE;
841 	gx.allow_escape    = TRUE;
842 	gx.wrap_mode       = GTK_WRAP_NONE;
843 	gx.window_position = GTK_WIN_POS_NONE;
844 
845 	while (arg < argc) {
846 		opt = my_get_opt (argv[arg], arg + 1 < argc);
847 		switch (opt) {
848 		case OPT_HELP:
849 		case OPT_HELP_Q:
850 			gx.exit_code = 0;
851 			usage ();
852 		case OPT_VERSION:
853 			g_print (PACKAGE "-" VERSION "\n");
854 			g_print("Copyright (C) %s %s\n"
855 			  "This is free software. You may redistribute copies of it under " \
856 			  "the terms of the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.\n" \
857 			  "There is NO WARRANTY, to the extent permitted by law.\n",
858 			  YEAR, AUTHOR);
859 			exit (0);
860 		case OPT_ENTRY:
861 		case OPT_ENTRYTEXT:
862 			if (gx.do_print) {
863 				g_printerr (_("%s: can't have both -entry and -print\n"), PACKAGE);
864 				prog_cleanup ();
865 			}
866 			if (gx.timeout) {
867 				/* -entry disables -timeout */
868 				gx.timeout = 0;
869 			}
870 			if (opt == OPT_ENTRY) {
871 				gx.entry_str = "";
872 			}
873 			else {
874 				gx.entry_str = argv[++arg];
875 			}
876 			break;
877 		case OPT_BUTTONS:
878 			button_free_all (gx.button_list);
879 			gx.button_list = button_list_from_str (argv[++arg]);
880 			break;
881 		case OPT_CENTER:
882 			gx.window_position = GTK_WIN_POS_CENTER;
883 			break;
884 		case OPT_DEFAULT:
885 			gx.default_str = argv[++arg];
886 			break;
887 		case OPT_FILE:
888 			if (gstr != NULL) {
889 				g_printerr (_("%s: can't get message from both -file and command line\n"), PACKAGE);
890 				prog_cleanup ();
891 			}
892 			fname = argv[++arg];
893 			break;
894 		case OPT_NEARMOUSE:
895 			/* -center takes priority over -nearmouse */
896 			if (gx.window_position != GTK_WIN_POS_CENTER) {
897 				gx.window_position = GTK_WIN_POS_MOUSE;
898 			}
899 			break;
900 		case OPT_PRINT:
901 			if (gx.entry_str != NULL) {
902 				g_printerr (_("%s: can't have both -entry and -print\n"), PACKAGE);
903 				prog_cleanup ();
904 			}
905 			gx.do_print = TRUE;
906 			break;
907 		case OPT_TIMEOUT:
908 			gx.timeout = strtol (argv[++arg], &ch, 10);
909 			if (*ch) {
910 				g_printerr (_("%s: integer -timeout value expected\n"), PACKAGE);
911 				/* continue anyway */
912 			}
913 			if (gx.timeout < 0 || gx.entry_str != NULL) {
914 				/* -entry disables -timeout */
915 				gx.timeout = 0;
916 			}
917 			break;
918 		case OPT_TITLE:
919 			gx.title_str = argv[++arg];
920 			break;
921 		case OPT_GEOMETRY:
922 			gx.geom_str = argv[++arg];
923 			break;
924 		case OPT_FN:
925 		case OPT_FONT:
926 			gx.font_str = argv[++arg];
927 			break;
928 		case OPT_RV:
929 		case OPT_REVERSE:
930 		case OPT_SYNCHRONOUS:
931 			/* not implemented - ignore */
932 			break;
933 		case OPT_BG:
934 			gx.color_bg = argv[++arg];
935 			break;
936 		case OPT_FG:
937 			gx.color_fg = argv[++arg];
938 			break;
939 		case OPT_NAME:
940 		case OPT_DISPLAY:
941 			/* already handled by my_gtk_init - ignore and skip arg */
942 		case OPT_BD:
943 		case OPT_BW:
944 		case OPT_XRM:
945 		case OPT_SELECTIONTIMEOUT:
946 		case OPT_XNLLANGUAGE:
947 			/* not implemented - ignore and skip arg */
948 			arg++;
949 			break;
950 		case OPT_ICONIC:
951 			gx.do_iconify = TRUE;
952 			break;
953 		case OPT_ONTOP:
954 			gx.do_ontop = TRUE;
955 			break;
956 		case OPT_STICKY:
957 			gx.do_sticky = TRUE;
958 			break;
959 		case OPT_BORDERLESS:
960 			gx.do_borderless = TRUE;
961 			break;
962 		case OPT_WRAP:
963 			gx.wrap_mode = GTK_WRAP_WORD;
964 			break;
965 		case OPT_ENCODING:
966 			gx.encoding = argv[++arg];
967 			break;
968 		case OPT_FOCUS:
969 			gx.do_focus = FALSE;
970 			break;
971 		case OPT_NOESCAPE:
972             if (g_file_test (ALLOW_NOESCAPE, G_FILE_TEST_EXISTS)) {
973     			gx.allow_escape = FALSE;
974             }
975 			break;
976 		case OPT_IS_MISSING_ARG:
977 			/* in this case, xmessage treats the "option" as normal text */
978 		case OPT_IS_UNKNOWN:
979 		default:
980 			if (fname != NULL) {
981 				g_printerr (_("%s: can't get message from both -file and command line\n"), PACKAGE);
982 				prog_cleanup ();
983 			}
984 			if (gstr == NULL) {
985 				gstr = g_string_new ("");
986 			}
987 			else {
988 				gstr = g_string_append_c (gstr, ' ');
989 			}
990 			gstr = g_string_append (gstr, argv[arg]);
991 			break;
992 		}
993 		arg++;
994 	}
995 
996 	if (!ok) {
997 		g_printerr ("%s: unable to initialize GTK\n", PACKAGE);
998 		prog_cleanup ();
999 	}
1000 
1001 	if (fname != NULL) {
1002 		if (strcmp ("-", fname) == 0) {
1003 			tmpstr = read_stdin ();
1004 		}
1005 		else if (!g_file_get_contents (fname, &tmpstr, NULL, NULL)) {
1006 			g_printerr (_("%s: unable to read file\n"), PACKAGE);
1007 			prog_cleanup ();
1008 		}
1009 		gx.message_text = message_to_utf8 (tmpstr);
1010 		gx.message_len = strlen (tmpstr);
1011 		g_free (tmpstr);
1012 	}
1013 	else if (gstr != NULL) {
1014 		gx.message_text = message_to_utf8 (gstr->str);
1015 		gx.message_len = gstr->len;
1016 		g_string_free (gstr, TRUE);
1017 	}
1018 	else {
1019 		g_printerr (_("%s: message text is required\n"), PACKAGE);
1020 		g_printerr (_("Try `%s --help' for more information\n"), PACKAGE);
1021 		prog_cleanup ();
1022 	}
1023 
1024 	if (gx.button_list == NULL) {
1025 		gx.button_list = button_list_from_str (bu_default);
1026 	}
1027 
1028 	button_set_default (gx.button_list, gx.default_str);
1029 
1030 	window_create ();
1031 	gtk_main ();
1032 
1033 	prog_cleanup ();
1034 	return 0;
1035 }
1036 
1037