summaryrefslogtreecommitdiff
path: root/src/app.vala
blob: e6ee934201c5a891134d418abe9bd81286ad7e68 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
/*
 * Copyright © 2001,2002 Red Hat, Inc.
 * Copyright © 2014 Christian Persch
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

namespace Test
{

[GtkTemplate (ui = "/org/gnome/vte/test/app/ui/search-popover.ui")]
class SearchPopover : Gtk.Popover
{
  public Vte.Terminal terminal { get; construct set; }

  [GtkChild] private Gtk.SearchEntry search_entry;
  [GtkChild] private Gtk.Button search_prev_button;
  [GtkChild] private Gtk.Button search_next_button;
  [GtkChild] private Gtk.Button close_button;
  [GtkChild] private Gtk.ToggleButton  match_case_checkbutton;
  [GtkChild] private Gtk.ToggleButton entire_word_checkbutton;
  [GtkChild] private Gtk.ToggleButton regex_checkbutton;
  [GtkChild] private Gtk.ToggleButton wrap_around_checkbutton;
  [GtkChild] private Gtk.Button reveal_button;
  [GtkChild] private Gtk.Revealer revealer;

  private GLib.RegexCompileFlags regex_flags = 0;
  private GLib.Regex? regex = null;

  public SearchPopover(Vte.Terminal term,
                       Gtk.Widget relative_to)
  {
    Object(relative_to: relative_to, terminal: term);

    close_button.clicked.connect(() => { hide(); });
    reveal_button.bind_property("active", revealer, "reveal-child");

#if GTK_3_16
    search_entry.next_match.connect(() => { search(false); });
    search_entry.previous_match.connect(() => { search(true); });
#endif
    search_entry.search_changed.connect(() => { update_regex(); });

    search_next_button.clicked.connect(() => { search(false); });
    search_prev_button.clicked.connect(() => { search(true); });

    match_case_checkbutton.toggled.connect(() => { update_regex(); });
    entire_word_checkbutton.toggled.connect(() => { update_regex(); });
    regex_checkbutton.toggled.connect(() => { update_regex(); });

    wrap_around_checkbutton.toggled.connect(() => {
        terminal.search_set_wrap_around(wrap_around_checkbutton.active);
      });

    update_sensitivity();
  }

  private void update_sensitivity()
  {
    bool can_search = regex != null;

    search_prev_button.set_sensitive(can_search);
    search_next_button.set_sensitive(can_search);
  }

  private void update_regex()
  {
    GLib.RegexCompileFlags flags;
    string search_text;
    string pattern;

    search_text = search_entry.get_text();
    flags = GLib.RegexCompileFlags.OPTIMIZE;

    if (!match_case_checkbutton.active)
      flags |= GLib.RegexCompileFlags.CASELESS;

    if (regex_checkbutton.active) {
      pattern = search_text;
      flags |= GLib.RegexCompileFlags.MULTILINE;
    } else {
      pattern = GLib.Regex.escape_string(search_text);
    }

    if (entire_word_checkbutton.active)
      pattern = "\\b" + pattern + "\\b";

    if (regex != null &&
        regex_flags == flags &&
        pattern == regex.get_pattern())
      return;

    regex_flags = flags;
    if (search_text.length != 0) {
      try {
        regex = new GLib.Regex(pattern, flags, 0);
      } catch (Error e) {
        regex = null;
      }
    } else {
      regex = null;
    }

    terminal.search_set_gregex(regex, 0);
    update_sensitivity();
  }

  private void search(bool backward)
  {
    if (regex == null)
      return;

    if (backward)
      terminal.search_find_previous();
    else
      terminal.search_find_next();
  }

} /* class SearchPopover */

[GtkTemplate (ui = "/org/gnome/vte/test/app/ui/window.ui")]
class Window : Gtk.ApplicationWindow
{
  [GtkChild] private Gtk.Scrollbar scrollbar;
  [GtkChild] private Gtk.Box terminal_box;
  /* [GtkChild] private Gtk.Box notifications_box; */
  [GtkChild] private Gtk.Widget readonly_emblem;
  /* [GtkChild] private Gtk.Button copy_button; */
  /* [GtkChild] private Gtk.Button paste_button; */
  [GtkChild] private Gtk.ToggleButton find_button;
  [GtkChild] private Gtk.MenuButton gear_button;

  private Vte.Terminal terminal;
  private Gtk.Clipboard clipboard;
  private GLib.Pid child_pid;
  private SearchPopover search_popover;

  private string[] builtin_dingus = {
    "(((gopher|news|telnet|nntp|file|http|ftp|https)://)|(www|ftp)[-A-Za-z0-9]*\\.)[-A-Za-z0-9\\.]+(:[0-9]*)?",
    "(((gopher|news|telnet|nntp|file|http|ftp|https)://)|(www|ftp)[-A-Za-z0-9]*\\.)[-A-Za-z0-9\\.]+(:[0-9]*)?/[-A-Za-z0-9_\\$\\.\\+\\!\\*\\(\\),;:@&=\\?/~\\#\\%]*[^]'\\.}>\\) ,\\\"]"
  };

  private const GLib.ActionEntry[] action_entries = {
    { "copy",        action_copy_cb            },
    { "copy-match",  action_copy_match_cb, "s" },
    { "paste",       action_paste_cb           },
    { "reset",       action_reset_cb,      "b" },
    { "find",        action_find_cb            },
    { "quit",        action_quit_cb            }
  };

  public Window(App app)
  {
    Object(application: app);

    /* Create terminal and connect scrollbar */
    terminal = new Vte.Terminal();
    scrollbar.set_adjustment(terminal.get_vadjustment());

    /* Create actions */
    add_action_entries (action_entries, this);

    /* Property actions */
    var action = new GLib.PropertyAction ("input-enabled", terminal, "input-enabled");
    add_action(action);
    action.notify["state"].connect((obj, pspec) => {
        GLib.Action a = (GLib.Action)obj;
        readonly_emblem.set_visible(!a.state.get_boolean());
      });

    /* Find */
    search_popover = new SearchPopover(terminal, find_button);
    search_popover.closed.connect(() => {
        if (find_button.active)
          find_button.set_active(false);
      });

    find_button.toggled.connect(() => {
        var active = find_button.active;
        if (search_popover.visible != active)
          search_popover.set_visible(active);
      });

    /* Gear menu */
    /* FIXME: figure out how to put this into the .ui file */
    var menu = new GLib.Menu();

    var section = new GLib.Menu();
    section.append("_Copy", "win.copy");
    section.append("_Paste", "win.paste");
    section.append("_Find…", "win.find");
    menu.append_section(null, section);

    section = new GLib.Menu();
    section.append("_Reset", "win.reset(false)");
    section.append("Reset and C_lear", "win.reset(true)");
    section.append("_Input enabled", "win.input-enabled");
    menu.append_section(null, section);

    section = new GLib.Menu();
    section.append("_Quit", "win.quit");
    menu.append_section(null, section);

    gear_button.set_menu_model(menu);

    /* set_resize_mode(Gtk.ResizeMode.IMMEDIATE); */

    clipboard = get_clipboard(Gdk.SELECTION_CLIPBOARD);
    clipboard.owner_change.connect(clipboard_owner_change_cb);

    title = "Terminal";

    /* Set ARGB visual */
    if (App.Options.transparency_percent != 0) {
      if (!App.Options.no_argb_visual) {
        var screen = get_screen();
        Gdk.Visual? visual = screen.get_rgba_visual();
        if (visual != null)
          set_visual(visual);
      }

      /* Without this transparency doesn't work; see bug #729884. */
      app_paintable = true;
    }

    /* Signals */
    terminal.button_press_event.connect(button_press_event_cb);
    terminal.char_size_changed.connect(char_size_changed_cb);
    terminal.child_exited.connect(child_exited_cb);
    terminal.decrease_font_size.connect(decrease_font_size_cb);
    terminal.deiconify_window.connect(deiconify_window_cb);
    terminal.icon_title_changed.connect(icon_title_changed_cb);
    terminal.iconify_window.connect(iconify_window_cb);
    terminal.increase_font_size.connect(increase_font_size_cb);
    terminal.lower_window.connect(lower_window_cb);
    terminal.maximize_window.connect(maximize_window_cb);
    terminal.move_window.connect(move_window_cb);
    terminal.raise_window.connect(raise_window_cb);
    terminal.realize.connect(realize_cb);
    terminal.refresh_window.connect(refresh_window_cb);
    terminal.resize_window.connect(resize_window_cb);
    terminal.restore_window.connect(restore_window_cb);
    terminal.selection_changed.connect(selection_changed_cb);
    terminal.window_title_changed.connect(window_title_changed_cb);
    if (App.Options.object_notifications)
      terminal.notify.connect(notify_cb);

    /* Settings */
    if (App.Options.no_double_buffer)
      terminal.set_double_buffered(true);

    if (App.Options.encoding != null) {
      try {
        terminal.set_encoding(App.Options.encoding);
      } catch (Error e) {
        printerr("Failed to set encoding: %s\n", e.message);
      }
    }

    if (App.Options.word_char_exceptions != null)
      terminal.set_word_char_exceptions(App.Options.word_char_exceptions);

    terminal.set_audible_bell(App.Options.audible);
    terminal.set_cjk_ambiguous_width(App.Options.get_cjk_ambiguous_width());
    terminal.set_cursor_blink_mode(App.Options.get_cursor_blink_mode());
    terminal.set_cursor_shape(App.Options.get_cursor_shape());
    terminal.set_mouse_autohide(true);
    terminal.set_rewrap_on_resize(!App.Options.no_rewrap);
    terminal.set_scroll_on_output(false);
    terminal.set_scroll_on_keystroke(true);
    terminal.set_scrollback_lines(App.Options.scrollback_lines);

    /* Style */
    if (App.Options.font_string != null) {
      var desc = Pango.FontDescription.from_string(App.Options.font_string);
      terminal.set_font(desc);
    }

    terminal.set_colors(App.Options.get_color_fg(),
                        App.Options.get_color_bg(),
                        null);
    terminal.set_color_cursor(App.Options.get_color_cursor());
    terminal.set_color_highlight(App.Options.get_color_hl_bg());
    terminal.set_color_highlight_foreground(App.Options.get_color_hl_fg());

    /* Dingus */
    if (!App.Options.no_builtin_dingus)
      add_dingus(builtin_dingus);
    if (App.Options.dingus != null)
      add_dingus(App.Options.dingus);

    /* Done! */
    terminal_box.pack_start(terminal);
    terminal.show();

    update_paste_sensitivity();
    update_copy_sensitivity();

    terminal.grab_focus();

    assert(!get_realized());
  }

  private void add_dingus(string[] dingus)
  {
    const Gdk.CursorType cursors[] = { Gdk.CursorType.GUMBY, Gdk.CursorType.HAND1 };

    for (int i = 0; i < dingus.length; ++i) {
      try {
        GLib.Regex regex;
        int tag;

        regex = new GLib.Regex(dingus[i], GLib.RegexCompileFlags.OPTIMIZE, 0);
        tag = terminal.match_add_gregex(regex, 0);
        terminal.match_set_cursor_type(tag, cursors[i % cursors.length]);
      } catch (Error e) {
        printerr("Failed to compile regex \"%s\": %s\n", dingus[i], e.message);
      }
    }
  }

  private void adjust_font_size(double factor)
  {
    var columns = terminal.get_column_count();
    var rows = terminal.get_row_count();

    terminal.set_font_scale(terminal.get_font_scale() * factor);

    update_geometry();
    resize_to_geometry((int)columns, (int)rows);
  }

  public void apply_geometry()
  {
    /* The terminal needs to be realized first, so that when parsing the
     * geometry, the right geometry hints are already in place.
     */
    terminal.realize();

    if (App.Options.geometry != null) {
      if (parse_geometry(App.Options.geometry)) {
        /* After parse_geometry(), we can get the default size in
         * width/height increments, i.e. in grid size.
         */
        int columns, rows;
        get_default_size(out columns, out rows);
        terminal.set_size(columns, rows);
        resize_to_geometry(columns, rows);
      } else
        printerr("Failed to parse geometry spec \"%s\"\n", App.Options.geometry);
    } else {
      /* In GTK+ 3.0, the default size of a window comes from its minimum
       * size not its natural size, so we need to set the right default size
       * explicitly */
      set_default_geometry((int)terminal.get_column_count(),
                           (int)terminal.get_row_count());
    }
  }

  private void launch_command(string command) throws Error
  {
    string[] argv;

    Shell.parse_argv(command, out argv);
    terminal.spawn_sync(App.Options.get_pty_flags(),
                        App.Options.working_directory,
                        argv,
                        App.Options.environment,
                        GLib.SpawnFlags.SEARCH_PATH,
                        null, /* child setup */
                        out child_pid,
                        null /* cancellable */);
    print("Fork succeeded, PID %d\n", child_pid);
  }

  private void launch_shell() throws Error
  {
    string? shell;

    shell = Vte.get_user_shell();
    if (shell == null || shell[0] == '\0')
      shell = Environment.get_variable("SHELL");
    if (shell == null || shell[0] == '\0')
      shell = "/bin/sh";

    launch_command(shell);
  }

  private void fork() throws Error
  {
    Vte.Pty pty;
    Posix.pid_t pid;

    pty = new Vte.Pty.sync(App.Options.get_pty_flags(), null);

    pid = Posix.fork();

    switch (pid) {
    case -1: /* error */
      printerr("Error forking: %m");
      break;
    case 0: /* child */ {
      pty.child_setup();

      for (int i = 0; ; i++) {
        switch (i % 3) {
        case 0:
        case 1:
          print("%d\n", i);
          break;
        case 2:
          printerr("%d\n", i);
          break;
        }
        Posix.sleep(1);
      }
    }
    default: /* parent */
      terminal.set_pty(pty);
      terminal.watch_child(pid);
      print("Child PID is %d (mine is %d).\n", (int)pid, (int)Posix.getpid());
      break;
    }
  }

  public void launch()
  {
    try {
      if (App.Options.command != null)
        launch_command(App.Options.command);
      else if (!App.Options.no_shell)
        launch_shell();
      else
        fork();
    } catch (Error e) {
      printerr("Error: %s\n", e.message);
    }
  }

  private void update_copy_sensitivity()
  {
    var action = lookup_action("copy") as GLib.SimpleAction;
    action.set_enabled(terminal.get_has_selection());
  }

  private void update_paste_sensitivity()
  {
    Gdk.Atom[] targets;
    bool can_paste;

    if (clipboard.wait_for_targets(out targets))
      can_paste = Gtk.targets_include_text(targets);
    else
      can_paste = false;

    var action = lookup_action("paste") as GLib.SimpleAction;
    action.set_enabled(can_paste);
  }

  private void update_geometry()
  {
    if (App.Options.no_geometry_hints)
      return;
    if (!terminal.get_realized())
      return;

    terminal.set_geometry_hints_for_window(this);
  }

  /* Callbacks */

  private void action_copy_cb()
  {
    terminal.copy_clipboard();
  }

  private void action_copy_match_cb(GLib.SimpleAction action, GLib.Variant? parameter)
  {
    size_t len;
    unowned string str = parameter.get_string(out len);
    clipboard.set_text(str, (int)len);
  }

  private void action_paste_cb()
  {
    terminal.paste_clipboard();
  }

  private void action_reset_cb(GLib.SimpleAction action, GLib.Variant? parameter)
  {
    bool clear;
    Gdk.ModifierType modifiers;

    if (parameter != null) {
      clear = parameter.get_boolean();
    } else if (Gtk.get_current_event_state(out modifiers))
      clear = (modifiers & Gdk.ModifierType.CONTROL_MASK) != 0;
    else
      clear = false;

    terminal.reset(true, clear);
  }

  private void action_find_cb()
  {
    find_button.set_active(true);
  }

  private void action_quit_cb()
  {
    destroy();
  }

  private bool button_press_event_cb(Gtk.Widget widget, Gdk.EventButton event)
  {
    if (event.button != 3)
      return false;
    if (App.Options.no_context_menu)
      return false;

    var menu = new GLib.Menu();
    menu.append("_Copy", "win.copy");

#if VALA_0_24
    var match = terminal.match_check_event(event, null);
    if (match != null)
      menu.append("Copy _Match", "win.copy-match::" + match);
#endif

    menu.append("_Paste", "win.paste");

    var popup = new Gtk.Menu.from_model(menu);
    popup.attach_to_widget(this, null);
    popup.popup(null, null, null, event.button, event.time);

    return false;
  }

  private void char_size_changed_cb(Vte.Terminal terminal, uint width, uint height)
  {
    update_geometry();
  }

  private void child_exited_cb(Vte.Terminal terminal, int status)
  {
    printerr("Child exited with status %x\n", status);

    if (App.Options.output_filename != null) {
      try {
        var file = GLib.File.new_for_commandline_arg(App.Options.output_filename);
        var stream = file.replace(null, false, GLib.FileCreateFlags.NONE, null);
        terminal.write_contents_sync(stream, Vte.WriteFlags.DEFAULT, null);
      } catch (Error e) {
        printerr("Failed to write output to \"%s\": %s\n",
                 App.Options.output_filename, e.message);
      }
    }

    if (App.Options.keep)
      return;

    destroy();
  }

  private void clipboard_owner_change_cb(Gtk.Clipboard clipboard, Gdk.Event event)
  {
    update_paste_sensitivity();
  }

  private void decrease_font_size_cb(Vte.Terminal terminal)
  {
    adjust_font_size(1.0 / 1.2);
  }

  public void deiconify_window_cb(Vte.Terminal terminal)
  {
    deiconify();
  }

  private void icon_title_changed_cb(Vte.Terminal terminal)
  {
    get_window().set_icon_name(terminal.get_icon_title());
  }

  private void iconify_window_cb(Vte.Terminal terminal)
  {
    iconify();
  }

  private void increase_font_size_cb(Vte.Terminal terminal)
  {
    adjust_font_size(1.2);
  }

  private void lower_window_cb(Vte.Terminal terminal)
  {
    if (!get_realized())
      return;

    get_window().lower();
  }

  private void maximize_window_cb(Vte.Terminal terminal)
  {
    maximize();
  }

  private void move_window_cb(Vte.Terminal terminal, uint x, uint y)
  {
    move((int)x, (int)y);
  }

  private void notify_cb(Object object, ParamSpec pspec)
  {
    if (pspec.owner_type != typeof(Vte.Terminal))
      return;

    var value = GLib.Value(pspec.value_type);
    object.get_property(pspec.name, ref value);
    var str = value.strdup_contents();
    print("NOTIFY property \"%s\" value %s\n", pspec.name, str);
  }

  private void raise_window_cb(Vte.Terminal terminal)
  {
    if (!get_realized())
      return;

    get_window().raise();
  }

  private void realize_cb(Gtk.Widget widget)
  {
    update_geometry();
  }

  private void refresh_window_cb(Vte.Terminal terminal)
  {
    queue_draw();
  }

  private void resize_window_cb(Vte.Terminal terminal, uint columns, uint rows)
  {
    if (columns < 2 || rows < 2)
      return;

    terminal.set_size((int)columns, (int)rows);
    resize_to_geometry((int)columns, (int)rows);
  }

  private void restore_window_cb(Vte.Terminal terminal)
  {
    unmaximize();
  }

  private void selection_changed_cb(Vte.Terminal terminal)
  {
    update_copy_sensitivity();
  }

  private void window_title_changed_cb(Vte.Terminal terminal)
  {
    set_title(terminal.get_window_title());
  }

} /* class Window */

class App : Gtk.Application
{
  private Window window;

  public App()
  {
    Object(application_id: "org.gnome.Vte.Test.App",
           flags: ApplicationFlags.NON_UNIQUE);
  }

  protected override void startup()
  {
    base.startup();

    window = new Window(this);
    window.launch();
  }

  protected override void activate()
  {
    window.apply_geometry();
    window.present();
  }

  public struct Options
  {
    public static bool audible = false;
    public static string? command = null;
    private static string? cjk_ambiguous_width_string = null;
    private static string? cursor_blink_mode_string = null;
    private static string? cursor_color_string = null;
    private static string? cursor_shape_string = null;
    public static string[]? dingus = null;
    public static bool debug = false;
    public static string? encoding = null;
    public static string[]? environment = null;
    public static string? font_string = null;
    public static string? geometry = null;
    private static string? hl_bg_color_string = null;
    private static string? hl_fg_color_string = null;
    public static string? icon_title = null;
    public static bool keep = false;
    public static bool no_argb_visual = false;
    public static bool no_builtin_dingus = false;
    public static bool no_context_menu = false;
    public static bool no_double_buffer = false;
    public static bool no_geometry_hints = false;
    public static bool no_rewrap = false;
    public static bool no_shell = false;
    public static bool object_notifications = false;
    public static string? output_filename = null;
    private static string? pty_flags_string = null;
    public static bool reverse = false;
    public static int scrollback_lines = 512;
    public static int transparency_percent = 0;
    public static bool version = false;
    public static string? word_char_exceptions = null;
    public static string? working_directory = null;

    private static int parse_enum(Type type, string str)
    {
      int value = 0;
      EnumClass enum_klass = (EnumClass)type.class_ref();
      unowned EnumValue? enum_value = enum_klass.get_value_by_nick(str);
      if (enum_value != null)
        value = enum_value.value;
      else
        printerr("Failed to parse enum value \"%s\" as type \"%s\"\n",
                 str, type.qname().to_string());
      return value;
    }

    private static uint parse_flags(Type type, string str)
    {
      uint value = 0;
      var flags_klass = (FlagsClass)type.class_ref();
      string[]? flags = str.split(",|", -1);

      if (flags == null)
        return value;

      for (int i = 0; i < flags.length; i++) {
        unowned FlagsValue? flags_value = flags_klass.get_value_by_nick(flags[i]);
        if (flags_value != null)
          value |= flags_value.value;
        else
          printerr("Failed to parse flags value \"%s\" as type \"%s\"\n",
                   str, type.qname().to_string());
      }
      return value;
    }

    public static int get_cjk_ambiguous_width()
    {
      if (cjk_ambiguous_width_string == null)
        return 1;
      if (cjk_ambiguous_width_string == "narrow")
        return 1;
      if (cjk_ambiguous_width_string == "wide")
        return 2;
      printerr("Failed to parse \"%s\" argument to --cjk-width. Allowed values are \"narrow\" or \"wide\".\n", cjk_ambiguous_width_string);
      return 1;
    }

    public static Gdk.RGBA get_color_bg()
    {
      var color = Gdk.RGBA();
      color.alpha = (double)(100 - transparency_percent.clamp(0, 100)) / 100.0;
      if (Options.reverse) {
        color.red = color.green = color.blue = 1.0;
      } else {
        color.red = color.green = color.blue = 0.0;
      }
      return color;
    }

    public static Gdk.RGBA get_color_fg()
    {
      var color = Gdk.RGBA();
      color.alpha = 1.0;
      if (Options.reverse) {
        color.red = color.green = color.blue = 0.0;
      } else {
        color.red = color.green = color.blue = 1.0;
      }
      return color;
    }

    private static Gdk.RGBA? get_color(string? str)
    {
      if (str == null)
        return null;
      var color = Gdk.RGBA();
      if (!color.parse(str)) {
        printerr("Failed to parse \"%s\" as color.\n", str);
        return null;
      }
      return color;
    }

    public static Gdk.RGBA? get_color_cursor()
    {
      return get_color(cursor_color_string);
    }

    public static Gdk.RGBA? get_color_hl_bg()
    {
      return get_color(hl_bg_color_string);
    }

    public static Gdk.RGBA? get_color_hl_fg()
    {
      return get_color(hl_fg_color_string);
    }

    public static Vte.CursorBlinkMode get_cursor_blink_mode()
    {
      Vte.CursorBlinkMode value;
      if (cursor_blink_mode_string != null)
        value = (Vte.CursorBlinkMode)parse_enum(typeof(Vte.CursorBlinkMode),
                                                cursor_blink_mode_string);
      else
        value = Vte.CursorBlinkMode.SYSTEM;
      return value;
    }

    public static Vte.CursorShape get_cursor_shape()
    {
      Vte.CursorShape value;
      if (cursor_shape_string != null)
        value = (Vte.CursorShape)parse_enum(typeof(Vte.CursorShape),
                                            cursor_shape_string);
      else
        value = Vte.CursorShape.BLOCK;
      return value;
    }

    public static Vte.PtyFlags get_pty_flags()
    {
      Vte.PtyFlags flags;
      if (pty_flags_string != null)
        flags = (Vte.PtyFlags)parse_flags(typeof(Vte.CursorShape),
                                          pty_flags_string);
      else
        flags = Vte.PtyFlags.DEFAULT;
      return flags;
    }

    public static const OptionEntry[] entries = {
      { "audible-bell", 'a', 0, OptionArg.NONE, ref audible,
        "Use audible terminal bell", null },
      { "command", 'c', 0, OptionArg.STRING, ref command,
        "Execute a command in the terminal", null },
      { "cjk-width", 0, 0, OptionArg.STRING, ref cjk_ambiguous_width_string,
        "Specify the cjk ambiguous width to use for UTF-8 encoding", "NARROW|WIDE" },
      { "cursor-blink", 0, 0, OptionArg.STRING, ref cursor_blink_mode_string,
        "Cursor blink mode (system|on|off)", "MODE" },
      { "cursor-color", 0, 0, OptionArg.STRING, ref cursor_color_string,
        "Enable a colored cursor", null },
      { "cursor-shape", 0, 0, OptionArg.STRING, ref cursor_shape_string,
        "Set cursor shape (block|underline|ibeam)", null },
      { "dingu", 'D', 0, OptionArg.STRING_ARRAY, ref dingus,
        "Add regex highlight", null },
      { "debug", 'd', 0,OptionArg.NONE, ref debug,
        "Enable various debugging checks", null },
      { "encoding", 0, 0, OptionArg.STRING, ref encoding,
        "Specify the terminal encoding to use", null },
      { "env", 0, 0, OptionArg.STRING_ARRAY, ref environment,
        "Add environment variable to the child\'s environment", "VAR=VALUE" },
      { "font", 'f', 0, OptionArg.STRING, ref font_string,
        "Specify a font to use", null },
      { "geometry", 'g', 0, OptionArg.STRING, ref geometry,
        "Set the size (in characters) and position", "GEOMETRY" },
      { "highlight-background-color", 0, 0, OptionArg.STRING, ref hl_bg_color_string,
        "Enable distinct highlight background color for selection", null },
      { "highlight-foreground-color", 0, 0, OptionArg.STRING, ref hl_fg_color_string,
        "Enable distinct highlight foreground color for selection", null },
      { "icon-title", 'i', 0, OptionArg.NONE, ref icon_title,
        "Enable the setting of the icon title", null },
      { "keep", 'k', 0, OptionArg.NONE, ref keep,
        "Live on after the command exits", null },
      { "no-argb-visual", 0, 0, OptionArg.NONE, ref no_argb_visual,
        "Don't use an ARGB visual", null },
      { "no-builtin-dingus", 0, 0, OptionArg.NONE, ref no_builtin_dingus,
        "Highlight URLs inside the terminal", null },
      { "no-context-menu", 0, 0, OptionArg.NONE, ref no_context_menu,
        "Disable context menu", null },
      { "no-double-buffer", '2', 0, OptionArg.NONE, ref no_double_buffer,
        "Disable double-buffering", null },
      { "no-geometry-hints", 'G', 0, OptionArg.NONE, ref no_geometry_hints,
        "Allow the terminal to be resized to any dimension, not constrained to fit to an integer multiple of characters", null },
      { "no-rewrap", 'R', 0, OptionArg.NONE, ref no_rewrap,
        "Disable rewrapping on resize", null },
      { "no-shell", 'S', 0, OptionArg.NONE, ref no_shell,
        "Disable spawning a shell inside the terminal", null },
      { "object-notifications", 'N', 0, OptionArg.NONE, ref object_notifications,
        "Print VteTerminal object notifications", null },
      { "output-file", 0, 0, OptionArg.FILENAME, ref output_filename,
        "Save terminal contents to file at exit", null },
      { "pty-flags", 0, 0, OptionArg.STRING, ref pty_flags_string,
        "PTY flags set from default|no-utmp|no-wtmp|no-lastlog|no-helper|no-fallback", null },
      { "reverse", 0, 0, OptionArg.NONE, ref reverse,
        "Reverse foreground/background colors", null },
      { "scrollback-lines", 'n', 0, OptionArg.INT, ref scrollback_lines,
        "Specify the number of scrollback-lines", null },
      { "transparent", 'T', 0, OptionArg.INT, ref transparency_percent,
        "Enable the use of a transparent background", "0..100" },
      { "version", 0, 0, OptionArg.NONE, ref version,
        "Show version", null },
      { "word-char-exceptions", 0, 0, OptionArg.STRING, ref word_char_exceptions,
        "Specify the word char exceptions", "CHARS" },
      { "working-directory", 'w', 0, OptionArg.FILENAME, ref working_directory,
        "Specify the initial working directory of the terminal", null },
      { null }
    };
  }

  public static int main(string[] argv)
  {
    if (Environment.get_variable("VTE_CJK_WIDTH") != null) {
      printerr("VTE_CJK_WIDTH is not supported anymore, use --cjk-width instead\n");
    }
    /* Not interested in silly debug spew, bug #749195 */
    if (Environment.get_variable("G_ENABLE_DIAGNOSTIC") == null) {
      Environment.set_variable("G_ENABLE_DIAGNOSTIC", "0", true);
    }
    Environment.set_prgname("vte-app");
    Environment.set_application_name("Terminal");

    try {
      var context = new OptionContext("— simple VTE test application");
      context.set_help_enabled(true);
      context.add_main_entries(Options.entries, null);
      context.add_group(Gtk.get_option_group(true));
      context.parse(ref argv);
    } catch (OptionError e) {
      printerr("Error parsing arguments: %s\n", e.message);
      return 1;
    }

    if (Options.version) {
      print("Simple VTE Test Application %s\n", Config.VERSION);
      return 0;
    }

    if (Options.debug)
      Gdk.Window.set_debug_updates(Options.debug);

    var app = new App();
    return app.run(null);
  }
} /* class App */

} /* namespace */