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