From 9703e09fb5cd151217ad17dbafd25338585b7ed7 Mon Sep 17 00:00:00 2001 From: Wolfgang Müller Date: Sat, 20 Jul 2019 21:58:18 +0200 Subject: Initial public release --- .gitignore | 3 + LICENSE | 13 ++ Makefile | 22 ++++ TODO | 2 + config.vala | 108 ++++++++++++++++ meson.build | 17 +++ resources.xml | 7 + terminal.css | 8 ++ terminal.ui | 233 +++++++++++++++++++++++++++++++++ terminal.vala | 387 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ weltschmerz.1 | 203 +++++++++++++++++++++++++++++ weltschmerz.vala | 36 ++++++ 12 files changed, 1039 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 TODO create mode 100644 config.vala create mode 100644 meson.build create mode 100644 resources.xml create mode 100644 terminal.css create mode 100644 terminal.ui create mode 100644 terminal.vala create mode 100644 weltschmerz.1 create mode 100644 weltschmerz.vala diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4aad62a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/* +*.c +weltschmerz diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ef05c3f --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright Wolfgang Müller + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..39463aa --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +PREFIX ?= /usr/local +EXEC_PREFIX ?= ${PREFIX} +BINDIR ?= ${EXEC_PREFIX}/bin +DATAROOTDIR ?= ${PREFIX}/share +MANDIR ?= ${DATAROOTDIR}/man +VALAC ?= valac + +weltschmerz: weltschmerz.vala terminal.vala config.vala resources.c + ${VALAC} --pkg posix --pkg gtk+-3.0 --pkg vte-2.91 --gresources resources.xml \ + weltschmerz.vala terminal.vala config.vala resources.c + +resources.c: resources.xml terminal.ui terminal.css + glib-compile-resources $< --target=$@ --generate-source + +install: weltschmerz weltschmerz.1 + install -D -m 755 -t '${DESTDIR}${BINDIR}' weltschmerz + install -D -m 644 -t '${DESTDIR}${MANDIR}/man1' weltschmerz.1 + +clean: + rm -f weltschmerz resources.c + +.PHONY: install clean diff --git a/TODO b/TODO new file mode 100644 index 0000000..26cbc45 --- /dev/null +++ b/TODO @@ -0,0 +1,2 @@ +- Upstream recommends spawn_async() instead of spawn_sync(), but the bindings + for it are missing, see https://bugzilla.gnome.org/show_bug.cgi?id=784232 diff --git a/config.vala b/config.vala new file mode 100644 index 0000000..c36d533 --- /dev/null +++ b/config.vala @@ -0,0 +1,108 @@ +class Config { + + KeyFile? keyfile = new KeyFile(); + string[] warnings = {}; + + public Config() { + var path = Path.build_filename(Environment.get_user_config_dir(), PROGRAM_NAME, "config"); + try { + keyfile.load_from_file(path, NONE); + } catch (Error e) { + // We want to ignore a legitimately missing file, since we fall back to defaults. + if (!(e is FileError.NOENT)) { + append_warning(path + ": " + e.message); + keyfile = null; + } + } + } + + public void append_warning(string message) { + warning(message); + + warnings += "• " + Markup.escape_text(message); + } + + void check_error(KeyFileError e) { + if (!(e is KeyFileError.KEY_NOT_FOUND || e is KeyFileError.GROUP_NOT_FOUND)) { + append_warning(e.message); + } + } + + public string[] done() { + if (keyfile == null) { + return warnings; + } + + string[] keys = {}; + try { + foreach(var group in keyfile.get_groups()) { + foreach(var key in keyfile.get_keys(group)) { + keys += string.join(".", group, key); + } + } + } catch (KeyFileError e) { + // purposefully ignored + } + + if (keys.length > 0) { + string k = keys.length > 1 ? "keys" : "key"; + append_warning("Unknown %s in config: %s".printf(k, string.joinv(", ", keys))); + } + + return warnings; + } + + public string? value(string group, string key, string? fallback) { + if (keyfile == null) { return fallback; } + + try { + string value = keyfile.get_value(group, key); + keyfile.remove_key(group, key); + return value; + } catch (KeyFileError e) { + check_error(e); + return fallback; + } + } + + public int? integer(string group, string key, int? fallback) { + if (keyfile == null) { return fallback; } + + try { + int integer = keyfile.get_integer(group, key); + keyfile.remove_key(group, key); + return integer; + } catch (KeyFileError e) { + check_error(e); + return fallback; + } + } + + public bool? boolean(string group, string key, bool? fallback) { + if (keyfile == null) { return fallback; } + + try { + bool boolean = keyfile.get_boolean(group, key); + keyfile.remove_key(group, key); + return boolean; + } catch (KeyFileError e) { + check_error(e); + return fallback; + } + } + + public Gdk.RGBA? colour(string key, string? fallback) { + string value = value("colours", key, fallback); + if (value == null) { + return null; + } + + var rgba = Gdk.RGBA(); + if (!rgba.parse(value)) { + append_warning("invalid colour: " + value); + return null; + } + + return rgba; + } +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..a36157b --- /dev/null +++ b/meson.build @@ -0,0 +1,17 @@ +project('weltschmerz', 'vala', 'c') + +gnome = import('gnome') +valac = meson.get_compiler('vala') + +dependencies = [ + valac.find_library('posix'), + dependency('gtk+-3.0'), + dependency('vte-2.91'), +] + +sources = files('weltschmerz.vala', 'terminal.vala', 'config.vala') +sources += gnome.compile_resources('resources', 'resources.xml') + +executable('weltschmerz', sources, dependencies: dependencies, install: true) + +install_man('weltschmerz.1') diff --git a/resources.xml b/resources.xml new file mode 100644 index 0000000..05f7109 --- /dev/null +++ b/resources.xml @@ -0,0 +1,7 @@ + + + + terminal.ui + terminal.css + + diff --git a/terminal.css b/terminal.css new file mode 100644 index 0000000..51ead0d --- /dev/null +++ b/terminal.css @@ -0,0 +1,8 @@ +vte-terminal { + /* This must be kept in sync with the geometry hints in terminal.vala */ + padding: 2px; +} + +scrolledwindow undershoot { + background-image: none; +} diff --git a/terminal.ui b/terminal.ui new file mode 100644 index 0000000..7885ecc --- /dev/null +++ b/terminal.ui @@ -0,0 +1,233 @@ + + + + + + + True + False + edit-copy + + + True + False + + + _Copy URL + True + False + Copy the URL to the clipboard + True + copy_url_image + False + True + + + + + + True + False + go-down-symbolic + + + True + False + go-up-symbolic + + + + True + False + + + gtk-copy + True + False + True + True + + + + + + + gtk-paste + True + False + True + True + + + + + + diff --git a/terminal.vala b/terminal.vala new file mode 100644 index 0000000..d0f9480 --- /dev/null +++ b/terminal.vala @@ -0,0 +1,387 @@ +[GtkTemplate (ui = "/weltschmerz/ui/terminal.ui")] +class Terminal : Gtk.Overlay { + const string URL_REGEX = """(?>https?|ftp):\/\/[^\s\$.?#].(?>[^\s()"]*|\([^\s]*\)|"[^\s"]*")"""; + const uint PCRE2_CASELESS = 0x00000008u; + const uint PCRE2_MULTILINE = 0x00000400u; + const uint PCRE2_NO_UTF_CHECK = 0x00080000u; + const uint PCRE2_UTF = 0x40000000u; + const uint PCRE2_JIT_COMPLETE = 0x00000001u; + const uint PCRE2_JIT_PARTIAL_SOFT = 0x00000002u; + const uint PCRE2_ERROR_JIT_BADOPTION = -45; + + struct PaletteEntry { + string name; + string normal; + string bright; + } + + const PaletteEntry[] DEFAULT_PALETTE = { + { "black", "black", "grey50" }, + { "red", "red3", "red" }, + { "green", "green3", "green" }, + { "yellow", "yellow3", "yellow" }, + { "blue", "blue2", "#5c5cff" }, + { "magenta", "magenta3", "magenta" }, + { "cyan", "cyan3", "cyan" }, + { "white", "grey90", "white" }, + }; + + public Gtk.Window window { get; construct set; } + [GtkChild] Gtk.Button search_button_down; + [GtkChild] Gtk.Button search_button_up; + [GtkChild] Gtk.InfoBar infobar; + [GtkChild] Gtk.Label infobar_label; + [GtkChild] Gtk.Menu standard_context_menu; + [GtkChild] Gtk.Menu url_context_menu; + [GtkChild] Gtk.MenuItem copy_item; + [GtkChild] Gtk.Revealer search_revealer; + [GtkChild] Gtk.ScrolledWindow scrolled_window; + [GtkChild] Gtk.SearchEntry search_entry; + [GtkChild] Vte.Terminal vte; + + bool has_search; + string url_match; + uint? infobar_timeout_id; + + public Terminal(string[] args, Gtk.Container parent, Gtk.Window window) { + Object(parent: parent, window: window); + + load_config(false); + search_update_sensitivity(); + + vte.search_set_wrap_around(true); + + url_context_menu.attach_to_widget(vte, null); + standard_context_menu.attach_to_widget(vte, null); + + try { + var regex = new Vte.Regex.for_match(URL_REGEX, URL_REGEX.length, PCRE2_CASELESS | PCRE2_MULTILINE); + vte.match_add_regex(regex, 0); + vte.match_set_cursor_name(0, "pointer"); + + var argv = args[1:args.length]; + if (argv.length == 0) { + argv = { Environment.get_variable("SHELL") }; + } + + vte.spawn_sync(DEFAULT, null, argv, null, SEARCH_PATH, null, null, null); + } catch (Error e) { + error(e.message); + } + } + + public void load_config(bool reload) { + var conf = new Config(); + + Gtk.PolicyType policy = conf.boolean("misc", "scrollbar", true) ? Gtk.PolicyType.AUTOMATIC : Gtk.PolicyType.NEVER; + scrolled_window.set_policy(policy, policy); + + vte.set_font(Pango.FontDescription.from_string(conf.value("misc", "font", "Monospace 12"))); + var geometry = Gdk.Geometry() { + // This must be kept in sync with the padding size in terminal.css + base_width = 2 * 2, + base_height = 2 * 2, + width_inc = (int)vte.get_char_width(), + height_inc = (int)vte.get_char_height() + }; + window.set_geometry_hints(null, geometry, BASE_SIZE | RESIZE_INC); + + vte.set_mouse_autohide(conf.boolean("misc", "autohide-mouse", false)); + vte.set_scrollback_lines(conf.integer("misc", "scrollback", 10000)); + + var cursor = conf.value("misc", "cursor-shape", "block"); + switch (cursor) { + case "block": + vte.set_cursor_shape(BLOCK); + break; + case "beam": + vte.set_cursor_shape(IBEAM); + break; + case "underline": + vte.set_cursor_shape(UNDERLINE); + break; + default: + conf.append_warning("invalid cursor '%s'".printf(cursor)); + vte.set_cursor_shape(BLOCK); + break; + } + + var foreground = conf.colour("foreground", null); + var background = conf.colour("background", null); + + var palette = new Gdk.RGBA[16]; + for (int i = 0; i < DEFAULT_PALETTE.length; i++) { + var entry = DEFAULT_PALETTE[i]; + palette[i] = conf.colour("normal." + entry.name, entry.normal); + palette[i + 8] = conf.colour("bright." + entry.name, entry.bright); + } + + vte.set_colors(foreground, background, palette); + + vte.set_color_bold(conf.colour("bold", null)); + + vte.set_color_cursor_foreground(conf.colour("cursor.foreground", null)); + vte.set_color_cursor(conf.colour("cursor.background", null)); + + vte.set_color_highlight_foreground(conf.colour("selection.foreground", null)); + vte.set_color_highlight(conf.colour("selection.background", null)); + + var warnings = conf.done(); + if (warnings.length > 0) { + string header = "Configuration loaded with warnings:\n"; + infobar_show(header + string.joinv("\n", warnings), Gtk.MessageType.WARNING); + } else if (reload) { + infobar_show("Configuration loaded successfully.", Gtk.MessageType.INFO, 3); + } + } + + bool match_button(Gdk.EventButton event, Gdk.ModifierType modifiers, uint button) { + return (event.state & modifiers) == modifiers && event.button == button; + } + + bool match_key(Gdk.EventKey event, Gdk.ModifierType modifiers, uint key) { + return (event.state & modifiers) == modifiers && event.keyval == key; + } + + void infobar_show(string message, Gtk.MessageType level, uint? timeout = null) { + infobar.set_message_type(level); + infobar.set_show_close_button(timeout == null); + infobar_label.set_markup(message); + + if (infobar_timeout_id != null) { + Source.remove(infobar_timeout_id); + } + + if (timeout != null) { + infobar_timeout_id = Timeout.add_seconds(timeout, () => { + infobar.set_revealed(false); + infobar_timeout_id = null; + return Source.REMOVE; + }); + } else { + infobar_timeout_id = null; + } + + infobar.set_revealed(true); + } + + [GtkCallback] + void infobar_respond(int id) { + infobar.set_revealed(false); + window.set_focus(vte); + } + + [GtkCallback] + void infobar_close() { + infobar.set_revealed(false); + window.set_focus(vte); + } + + [GtkCallback] + void search_changed() { + vte.unselect_all(); + search_entry_reset(); + + var pattern = search_entry.get_text(); + Vte.Regex regex = null; + + search_entry_reset(); + + if (pattern.length > 0) { + try { + regex = new Vte.Regex.for_search(pattern, pattern.length, PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MULTILINE); + try { + regex.jit(PCRE2_JIT_COMPLETE | PCRE2_JIT_PARTIAL_SOFT); + } catch (Error e) { + if (e.code != PCRE2_ERROR_JIT_BADOPTION) { // JIT not supported + search_entry_indicate("dialog-error-symbolic", e.message); + } + } + } catch (Error e) { + regex = null; + search_entry_indicate("dialog-error-symbolic", e.message); + } + } + + has_search = regex != null; + vte.search_set_regex(regex, 0); + search_update_sensitivity(); + search_perform(false); + } + + void search_perform(bool backwards) { + if (!has_search) { + return; + } + + bool found; + if (backwards) { + found = vte.search_find_previous(); + } else { + found = vte.search_find_next(); + } + + if (!found) { + // work around a possible bug in VTE + vte.unselect_all(); + if (backwards) { + found = vte.search_find_previous(); + } else { + found = vte.search_find_next(); + } + + if (!found) { + search_entry_indicate("action-unavailable-symbolic", "No results"); + } + } + } + + [GtkCallback] + void search_up() { + search_perform(true); + } + + [GtkCallback] + void search_down() { + search_perform(false); + } + + [GtkCallback] + void search_stop() { + search_entry_reset(); + + window.set_focus(vte); + search_revealer.set_reveal_child(false); + } + + void search_entry_reset() { + search_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "edit-clear-symbolic"); + search_entry.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, "Clear search"); + search_entry.get_style_context().remove_class(Gtk.STYLE_CLASS_ERROR); + } + + void search_entry_indicate(string icon_name, string tooltip) { + search_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon_name); + search_entry.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, tooltip); + search_entry.get_style_context().add_class(Gtk.STYLE_CLASS_ERROR); + } + + [GtkCallback] + bool search_entry_key_press(Gdk.EventKey event) { + // Return is captured by Gtk.Entry's "activate" signal, Shift + Return is not + if (match_key(event, SHIFT_MASK, Gdk.Key.Return)) { + search_perform(true); + return true; + } + return false; + } + + void search_update_sensitivity() { + search_button_up.set_sensitive(has_search); + search_button_down.set_sensitive(has_search); + } + + void url_match_open() { + try { + AppInfo.launch_default_for_uri(url_match, get_display().get_app_launch_context()); + } catch (Error e) { + warning(e.message); + infobar_show(e.message, Gtk.MessageType.ERROR); + } + } + + [GtkCallback] + void url_match_copy() { + if (url_match == null) { + return; + } + + Gtk.Clipboard clipboard = Gtk.Clipboard.get_default(window.get_display()); + clipboard.set_text(url_match, -1); + } + + [GtkCallback] + void vte_copy() { + if (vte.get_has_selection()) { + vte.copy_clipboard_format(TEXT); + } + } + + [GtkCallback] + void vte_paste() { + vte.paste_clipboard(); + } + + [GtkCallback] + bool vte_button_press(Gdk.EventButton event) { + if (match_button(event, 0, Gdk.BUTTON_SECONDARY)) { + url_match = vte.match_check_event(event, null); + + if (url_match != null) { + url_context_menu.popup_at_pointer(event); + } else { + copy_item.set_sensitive(vte.get_has_selection()); + standard_context_menu.popup_at_pointer(event); + } + return true; + } + + return false; + } + + [GtkCallback] + bool vte_button_release(Gdk.EventButton event) { + if (match_button(event, 0, Gdk.BUTTON_PRIMARY)) { + url_match = vte.match_check_event(event, null); + if (url_match != null && !vte.get_has_selection()) { + url_match_open(); + } + } + + return false; + } + + [GtkCallback] + bool vte_key_press(Gdk.EventKey event) { + if (match_key(event, CONTROL_MASK | SHIFT_MASK, Gdk.Key.C)) { + vte_copy(); + return true; + } + + if (match_key(event, CONTROL_MASK | SHIFT_MASK, Gdk.Key.V)) { + vte.paste_clipboard(); + return true; + } + + if (match_key(event, CONTROL_MASK | SHIFT_MASK, Gdk.Key.F)) { + if (!search_revealer.get_reveal_child()) { + search_revealer.set_reveal_child(true); + } + window.set_focus(search_entry); + return true; + } + + if (match_key(event, CONTROL_MASK | SHIFT_MASK, Gdk.Key.R)) { + load_config(true); + return true; + } + + return false; + } + + [GtkCallback] + void window_set_title() { + string title = null; + if (vte.window_title.length > 0) { + title = "%s - %s".printf(vte.window_title, PROGRAM_NAME); + } + + window.set_title(title ?? PROGRAM_NAME); + } + + [GtkCallback] + void window_toggle_urgency() { + window.set_urgency_hint(false); + window.set_urgency_hint(true); + } +} diff --git a/weltschmerz.1 b/weltschmerz.1 new file mode 100644 index 0000000..c66bbad --- /dev/null +++ b/weltschmerz.1 @@ -0,0 +1,203 @@ +.Dd January 17, 2019 +.Dt WELTSCHMERZ 1 +.Os +.Sh NAME +.Nm weltschmerz +.Nd a small VTE-based terminal emulator +.Sh SYNOPSIS +.Nm +.Oo +.Ar command +.Op Ar argument... +.Oc +.Sh DESCRIPTION +.Nm +is a terminal emulator using the VTE widget. +It supports clickable URLs, contains basic search functionality, and can +reload its configuration whilst running. +.Pp +.Nm +executes the given command, or the program specified in the +.Em SHELL +environment variable if no command was given. +.Pp +The clipboard can be copied to and pasted from with +.Sy CTRL + Shift + C +and +.Sy CTRL + Shift + V , +respectively. +.Sh SEARCH OVERLAY +The search overlay can be opened by pressing +.Sy CTRL + Shift + F . +The search will be updated automatically as the user types in the search bar. +.Pp +The key bindings for the overlay are as follows: +.Bl -tag -width Ds +.It Sy Enter , CTRL + G +Go to next search result. +.It Sy Shift + Enter , CTRL + Shift + G +Go to previous search result. +.It Sy Escape +Close search overlay. +.El +.Sh CONFIGURATION +Configuration is done using a configuration file. +.Nm +will reread that file when receiving the hangup signal (SIGHUP), or +when the user presses +.Sy CTRL + Shift + R . +.Pp +.Nm +uses GLib's key-value file parser. +The file format consists of key-value pairs collected in groups: +.Bd -literal -offset indent +[misc] +font = Iosevka Light 16 + +[colours] +foreground = #000000 +background = #ffffff +.Ed +.Pp +Refer to the GLib documentation for detailed information on this format. +.Pp +The options for the +.Em misc +group are as follows: +.Bl -tag -width Ds +.It Sy autohide-mouse +When set to +.Sy true , +the mouse cursor will be hidden once the user presses a key, and shown +once the user moves the mouse. +When set to +.Sy false , +the mouse cursor will always be shown. +The default is +.Sy false . +.It Sy cursor-shape +Specifies the shape of the terminal cursor. +Possible values are +.Sy beam , +.Sy block , +and +.Sy underline . +The default is +.Sy block . +.It Sy font +Specifies the font used to draw text, in the form of a Pango font +description. +Consists of one or more font families, zero or more style options, and +the size in points (or in pixels if given a suffix of +.Dq px ) : +.Bd -literal -offset indent +Monospace 12 +Iosevka, DejaVu Sans Mono bold italic 16 +Gohu GohuFont 11px +.Ed +.Pp +Refer to the Pango documentation for detailed information. +.Pp +The default is +.Sy Monospace 12 . +.It Sy scrollback +Specifies how many lines of scrollback to keep. +A value of -1 means infinite scrollback. +The default is +.Sy 10000 . +.It Sy scrollbar +When set to +.Sy true , +.Nm +will draw a scrollbar at the right side of the terminal window. +When set to +.Sy false , +no scrollbar is drawn. +The default is +.Sy true . +.El +.Pp +The +.Em colours +group contains the palette and colour overrides. +All keys in this group take a colour representation as their value: +.Bd -filled -offset indent +.TS +tab(/); +l |l +lb |l. +Representation/Example value += +Hexadecimal/#00ffff +RGB/rgb(0, 255, 255) +RGBA/rgba(0, 255, 255, 1) +X11 colour/cyan +.TE +.Ed +.Pp +The palette defines the 16 base colours available to the terminal. +Keys for the palette are in the form of: +.Bd -literal -offset indent +. +.Ed +.Pp +The colour type is either +.Dq normal +or +.Dq bright , +and the possible colour names along with their default representations +are as follows: +.Bd -filled -offset indent +.TS +tab(/); +l |l |l +lb |l |l. +Colour name/Default (normal)/Default (bright) += +black/black/grey50 +red/red3/red +green/green3/green +yellow/yellow3/yellow +blue/blue2/#5c5cff +magenta/magenta3/magenta +cyan/cyan3/cyan +white/grey90/white +.TE +.Ed +.Pp +The colour overrides specify which colour to use for certain parts of +the terminal. +The following table contains all possible keys for the colour overrides, +along with the default behaviour if the override is not set: +.Bd -filled -offset indent +.TS +tab(/); +l |l +lb |il. +Key/Default += +foreground/normal.white +background/normal.black +cursor.foreground/reverse video +cursor.background/reverse video +selection.foreground/reverse video +selection.background/reverse video +bold/inherit colour +.TE +.Ed +.Sh FILES +.Bl -tag -width Ds +.It Em $XDG_CONFIG_HOME/weltschmerz/config +The configuration file for +.Nm . +.El +.Pp +.Nm +adheres to the XDG Base Directory Specification. +If $XDG_CONFIG_HOME is unset or empty, it will default to +.Em ~/.config +.Sh AUTHORS +.An -nosplit +.Nm +was written by +.An Wolfgang Müller Aq Mt vehk@vehk.de diff --git a/weltschmerz.vala b/weltschmerz.vala new file mode 100644 index 0000000..dac2291 --- /dev/null +++ b/weltschmerz.vala @@ -0,0 +1,36 @@ +const string PROGRAM_NAME = "weltschmerz"; + +void warning(string message) { + stderr.printf("%s: %s\n", PROGRAM_NAME, message); +} + +static int main(string[] args) { + unowned string[]? nullargs = null; + Gtk.init(ref nullargs); + + var window = new Gtk.Window(); + + window.destroy.connect(Gtk.main_quit); + window.set_icon_name("utilities-terminal"); + + var visual = window.screen.get_rgba_visual(); + if (visual != null) { + window.set_visual(visual); + } + + var css_provider = new Gtk.CssProvider(); + css_provider.load_from_resource("/weltschmerz/css/terminal.css"); + Gtk.StyleContext.add_provider_for_screen(window.screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER); + + var terminal = new Terminal(args, window, window); + + Unix.signal_add(Posix.Signal.USR1, () => { + terminal.load_config(true); + return Source.CONTINUE; + }); + + window.show_all(); + + Gtk.main(); + return 0; +} -- cgit v1.2.3-2-gb3c3