/* Copyright (c) 2012, 2013, 2014 Thomas Weißschuh
*
* 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 3 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 .
*/
// modules: vte-2.90
using GLib;
using Gtk;
using Vte;
using Pango;
const string FONT = "17";
const double FONT_SCALE_STEP = 0.1;
const string[] COLORS = {
"#000000", "#c00000", "#00c000", "#c0c000",
"#0000c0", "#c000c0", "#00c0c0", "#c0c0c0",
"#3f3f3f", "#ff3f3f", "#3fff3f", "#ffff3f",
"#3f3fff", "#ff3fff", "#3fffff", "#ffffff"
};
const string FG_COLOR = "#c0c0c0";
const string BG_COLOR = "#000000";
static Pango.FontDescription font;
static Gdk.RGBA[] palette;
static Gdk.RGBA fg_color;
static Gdk.RGBA bg_color;
const uint PCRE2_CASELESS = 0x00000008u;
const uint PCRE2_MULTILINE = 0x00000400u;
public static Vte.Regex uri_regex;
// generated with get-uri-regex.c from terminal-regex.h from Gnome Terminal 3.38.1
const string regex_string = """(?(?<='))?(?(DEFINE)(?(?x: (?: [0-9] | [1-9][0-9] | 1[0-9]{2} | 2[0-4][0-9] | 25[0-5] ) (?! [0-9] ) )))(?(DEFINE)(?(?x: (?: (?&S4) \. ){3} (?&S4) )))(?(DEFINE)(?[[:xdigit:]]{1,4})(?:(?&S6))(?(?&S6):))(?(DEFINE)(?(?x: (?: (?x: :: ) | (?x: : (?&CS6){1,7} ) | (?x: (?! (?: [[:xdigit:]]*: ){8} ) (?&S6C){1,6} (?&CS6){1,6} ) | (?x: (?&S6C){1,7} : ) | (?x: (?&S6C){7} (?&S6) ) | (?: (?x: (?&S6C){6} ) | (?x: :: (?&S6C){0,5} ) | (?x: (?! (?: [[:xdigit:]]*: ){7} ) (?&S6C){1,4} (?&CS6){1,4} ) : | (?x: (?&S6C){1,5} : ) ) (?&IPV4) ) (?! [.:[:xdigit:]] ) )))(?(DEFINE)(?(?x: (?: [-[:alnum:]\Q_$.+!*,:;@&=?/~#|%'\E]* (?: \( (?&PATH_INNER) \) | \[ (?&PATH_INNER) \] ) )* [-[:alnum:]\Q_$.+!*,:;@&=?/~#|%'\E]* )))(?(DEFINE)(?(?x: (?: [-[:alnum:]\Q_$.+!*,:;@&=?/~#|%'\E]* (?: \( (?&PATH_INNER) \) | \[ (?&PATH_INNER) \] ) )* (?: [-[:alnum:]\Q_$.+!*,:;@&=?/~#|%'\E]* (?()[-[:alnum:]\Q_$+*:@&=/~#|%\E]|[-[:alnum:]\Q_$+*:@&=/~#|%'\E]) )? )))(?ix: news | telnet | nntp | https? | ftps? | sftp | webcal )://(?:[-+.[:alnum:]]+(?x: :[-[:alnum:]\Q,?;.:/!%$^*&~"#'\E]* )?@)?(?x: (?x: (?: (?x: [-[:alnum:]] | (?! [[:ascii:]] ) [[:graph:]] )+ \. )* (?x: [-[:alnum:]] | (?! [[:ascii:]] ) [[:graph:]] )* (?! [0-9] ) (?x: [-[:alnum:]] | (?! [[:ascii:]] ) [[:graph:]] )+ ) | (?&IPV4) | \[ (?&IPV6) \] )(?x: \:(?x: (?: [1-9][0-9]{0,3} | [1-5][0-9]{4} | 6[0-4][0-9]{3} | 65[0-4][0-9]{2} | 655[0-2][0-9] | 6553[0-5] ) (?! [0-9] ) ) )?(?x: /(?&PATH) )?""";
public static int main(string[] args)
{
try {
uri_regex = new Vte.Regex.for_match(regex_string, regex_string.length, PCRE2_CASELESS | PCRE2_MULTILINE);
uri_regex.jit(0);
} catch (GLib.Error e) {
GLib.assert_not_reached();
}
font = Pango.FontDescription.from_string(FONT);
for (int i = 0; i < COLORS.length; i++) {
Gdk.RGBA color = Gdk.RGBA();
color.parse(COLORS[i]);
palette += color;
}
fg_color.parse(FG_COLOR);
bg_color.parse(BG_COLOR);
return new Taterm().run();
}
class Taterm : Gtk.Application
{
string pwd = GLib.Environment.get_home_dir();
public Taterm()
{
Object(application_id: "de.t-8ch.taterm");
activate.connect(() => {
var new_win = new Window(pwd);
add_window(new_win);
new_win.focus_out_event.connect(() => {
pwd = new_win.pwd;
return Gdk.EVENT_PROPAGATE;
});
});
}
class Window : Gtk.Window
{
Vte.Terminal term;
GLib.Pid shell;
public string pwd;
string[] targs;
public signal void pwd_changed(string pwd);
public Window(string pwd)
{
decorated = false;
this.pwd = pwd;
term = new Terminal(this);
targs = { Vte.get_user_shell() };
try {
term.spawn_sync(Vte.PtyFlags.DEFAULT, pwd, targs, null, 0, null, out shell);
} catch (Error err) {
stderr.printf("%s\n", err.message);
}
focus_in_event.connect(() => {
urgency_hint = false;
return Gdk.EVENT_PROPAGATE;
});
term.child_exited.connect(() => {
destroy();
});
term.bell.connect(() => {
urgency_hint = true;
});
term.window_title_changed.connect(() => {
title = term.window_title;
var newpwd = Utils.cwd_of_pid(shell);
if (newpwd != this.pwd) {
this.pwd = newpwd;
pwd_changed(newpwd);
}
});
add(term);
show_all();
}
}
class Terminal : Vte.Terminal
{
string match_uri = null;
Gtk.Window window;
public Terminal(Gtk.Window window)
{
this.window = window;
cursor_blink_mode = Vte.CursorBlinkMode.OFF;
scrollback_lines = -1; /* infinity */
pointer_autohide = true;
set_font(font);
set_colors(fg_color, bg_color, palette);
key_press_event.connect(handle_key);
button_press_event.connect(handle_button);
match_add_regex(uri_regex, 0);
}
private bool handle_key(Gdk.EventKey event)
{
bool handled = false;
if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0) {
switch (event.keyval) {
case Gdk.Key.minus:
font_scale /= 1 + FONT_SCALE_STEP;
handled = true;
break;
case Gdk.Key.plus:
font_scale *= 1 + FONT_SCALE_STEP;
handled = true;
break;
case Gdk.Key.@0:
font_scale = 1;
handled = true;
break;
}
}
return handled ? Gdk.EVENT_STOP : Gdk.EVENT_PROPAGATE;
}
private bool handle_button(Gdk.EventButton event)
{
switch (event.button) {
case Gdk.BUTTON_PRIMARY:
check_regex(event);
return Gdk.EVENT_PROPAGATE;
case Gdk.BUTTON_MIDDLE:
return Gdk.EVENT_PROPAGATE;
}
return Gdk.EVENT_PROPAGATE;
}
private void check_regex(Gdk.EventButton event)
{
/*
this tag shouldn't be necessary but if we don't pass it to match_check()
the whole thing just segfaults
*/
int tag;
match_uri = match_check_event(event, out tag);
if (match_uri != null) {
try {
Gtk.show_uri_on_window(window, match_uri, event.time);
} catch (Error err) {
stderr.printf("%s\n", err.message);
} finally {
match_uri = null;
}
}
}
}
class Utils
{
public static string cwd_of_pid(GLib.Pid pid)
{
var cwdlink = @"/proc/$((int)pid)/cwd";
try {
return GLib.FileUtils.read_link(cwdlink);
} catch (FileError err) {
stderr.printf("%s\n", err.message);
}
return GLib.Environment.get_home_dir();
}
}
}