diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/tmate/config.vala | 53 | ||||
-rw-r--r-- | src/tmate/meson.build | 5 | ||||
-rw-r--r-- | src/tmate/session.vala | 207 | ||||
-rw-r--r-- | src/tmate/sessiontype.vala | 28 | ||||
-rw-r--r-- | src/window.vala | 24 |
5 files changed, 315 insertions, 2 deletions
diff --git a/src/tmate/config.vala b/src/tmate/config.vala new file mode 100644 index 0000000..8957d02 --- /dev/null +++ b/src/tmate/config.vala @@ -0,0 +1,53 @@ +namespace Tmate +{ + [SingleInstance] + class Config : Object + { + private static HashTable<string, string> cfg; + construct { + cfg = new HashTable<string, string>(str_hash, str_equal); + cfg.insert("server-host", "tty.dev.jadupc.com"); + cfg.insert("server-port", "10022"); + cfg.insert("server-rsa-fingerprint", "SHA256:QdMBN/QsrS4QyGApxxjt3DZyiysgeRto5YGGjAHRO7g"); + cfg.insert("server-ed25519-fingerprint", "SHA256:w/TRuOK0w5qDXNBKdlYlANgZwq3Xg5LSZlBYIwEH8gU"); + } + + public void @delete(string? path) + { + if (path == null) return; + var fcfg = File.new_for_path(path); + try { + fcfg.delete(); + } catch (Error e) { + print(@"Error: $(e.message)\n"); + } + } + + public new string? @get() + { + bool failed = false; + string cfgpath = ".."; + + try { + FileIOStream scfg; + var fcfg = File.new_tmp(".tmate.conf.XXXXXXXXX", out scfg); + var cfgout = new DataOutputStream(scfg.output_stream); + cfgpath = fcfg.get_path(); + + cfg.foreach((key, val) => { + try { + if(! failed) + cfgout.put_string(@"set -g tmate-$(key) $(val)\n"); + } catch (Error m) { + print(@"Error: $(m.message)\n"); + failed = true; + } + }); + } catch (Error e) { + print(@"Error: $(e.message)\n"); + failed = true; + } + return failed ? null : cfgpath; + } + } +} diff --git a/src/tmate/meson.build b/src/tmate/meson.build new file mode 100644 index 0000000..e42ed65 --- /dev/null +++ b/src/tmate/meson.build @@ -0,0 +1,5 @@ +srcs += files( + 'config.vala', + 'sessiontype.vala', + 'session.vala', +) diff --git a/src/tmate/session.vala b/src/tmate/session.vala new file mode 100644 index 0000000..5571654 --- /dev/null +++ b/src/tmate/session.vala @@ -0,0 +1,207 @@ +namespace Tmate +{ + public interface SessionInterface : Object + { + public abstract string? @get(SessionType session); + public abstract bool start(string? config = null); + public abstract bool stop(); + } + + [SingleInstance] + public class Session: Object, SessionInterface + { + private string? _addr_ssh = null; + private string? _addr_http = null; + private string? _addr_ssh_ro = null; + private string? _addr_http_ro = null; + private IOChannel? stdout = null; + private Pid? pid = null; + + // Regex + private Regex r_connect = /^Connecting to .+\.\.\.$/; + private Regex r_socket = /^To connect.+ tmate -S (.+) attach$/; + private Regex r_http = /^web \w+[:] (.+)$/; + private Regex r_ssh = /^ssh \w+[:] .+ (.+)+$/; + private Regex r_http_ro = /^web \w+ read \w+[:] (.+)$/; + private Regex r_ssh_ro = /^ssh \w+ read \w+[:] .+ (.+)+$/; + private Regex r_joined = /^.+ joined \(([^()]+)\) -- (\d+) clients? .+$/; + private Regex r_left = /^.+ left \(([^()]+)\) -- (\d+) clients? .+$/; + + public SessionType flags { get; private set;} + public string? socket { get; private set;} + + construct { reset(); } + + public virtual signal void started (string socket) + { + info (_("Session: unix://%s"), socket); + } + public virtual signal void stopped () + { + info (_("Session terminated.")); + } + public override signal void address (SessionType session, string address) + { + info (_("%s address: %s"), session.to_string(), address); + switch(session) { + case SSH : + _addr_ssh = address; + break; + case HTTP: + _addr_http = address; + break; + case HTTP_READONLY: + _addr_http_ro = address; + break; + case SSH_READONLY : + _addr_ssh_ro = address; + break; + case DISCONNECTED : + _addr_http = _addr_ssh = _addr_http_ro = _addr_ssh_ro = null; + break; + default: + warn_if_reached(); + break; + } + } + public virtual signal void joined (string mate, int mates) + { + info(_("%s joined, connected: %d"), mate, mates); + } + public virtual signal void left (string mate, int mates) + { + info(_("%s left, connected: %d"), mate, mates); + } + + private void reset() + { + _addr_ssh = null; + _addr_http = null; + _addr_ssh_ro = null; + _addr_http_ro = null; + stdout = null; + pid = null; + socket = null; + flags = DISCONNECTED; + } + + private bool send(string[] args) + { + if(socket == null) + return false; + var command = new GenericArray<string>(); + command.data = { + "/usr/bin/tmate", + "-S", socket + }; + foreach(var arg in args) + command.add(arg); + + try { + return Process.spawn_sync(null, command.data, null, + STDERR_TO_DEV_NULL|STDOUT_TO_DEV_NULL, + null, null, null, null); + } catch (SpawnError e) { + return false; + } + + } + + public new string? @get(SessionType session) + { + switch(session) { + case SSH: + return _addr_ssh; + case HTTP: + return _addr_http; + case SSH_READONLY : + return _addr_ssh_ro; + case HTTP_READONLY: + return _addr_http_ro; + case DISCONNECTED : + warning("No running session."); + break; + default: + warning("Unknown Session Type %s", session.to_string()); + break; + } + + return null; + } + + public bool start(string? config = null) + { + if(! (pid == null)) { + started.emit(socket); + return true; + } + + int out_fd; + var args = new GenericArray<string>(); + args.data = {"/usr/bin/tmate", "-F"}; + + if(! (config == null) && FileUtils.test(config, IS_REGULAR)) { + args.add("-f"); + args.add(config); + } + try { + Process.spawn_async_with_pipes( + null, args.data, null, + STDERR_TO_DEV_NULL | DO_NOT_REAP_CHILD, + null, out pid, null, + out out_fd, null + ); + + stdout = new IOChannel.unix_new(out_fd); + stdout.add_watch(IOCondition.IN | IOCondition.HUP, (channel, condition) => { + if (condition == IOCondition.HUP) + return false; + + try { + string line; + MatchInfo info; + channel.read_line (out line, null, null); + if(r_socket.match(line, 0, out info)) + socket = info.fetch(1); + else if (r_connect.match(line) && socket != null) + started.emit(socket); + else if(r_http.match(line, 0, out info)) + address.emit(HTTP, info.fetch(1)); + else if(r_http_ro.match(line, 0, out info)) + address.emit(HTTP_READONLY, info.fetch(1)); + else if(r_ssh.match(line, 0, out info)) + address.emit(SSH, info.fetch(1)); + else if(r_ssh_ro.match(line, 0, out info)) + address.emit(SSH_READONLY, info.fetch(1)); + else + debug("Unprocessed line: \"%s\"", line); + } catch (IOChannelError e) { + print ("IOChannelError: %s\n", e.message); + return false; + } catch (ConvertError e) { + print ("ConvertError: %s\n", e.message); + return false; + } + return true; + }); + + ChildWatch.add(pid, (pid, status) => { + Process.close_pid(pid); + reset(); + stopped.emit(); + }); + + return true; + } catch (SpawnError e) { + warning("Error: %s", e.message); + reset(); + return false; + } + } + + public bool stop() + { + return false; + } + } +} diff --git a/src/tmate/sessiontype.vala b/src/tmate/sessiontype.vala new file mode 100644 index 0000000..4c44aea --- /dev/null +++ b/src/tmate/sessiontype.vala @@ -0,0 +1,28 @@ +namespace Tmate +{ + [Flags] + public enum SessionType { + DISCONNECTED, + SSH, HTTP, + SSH_READONLY, + HTTP_READONLY; + + public string to_string() { + switch(this) { + case DISCONNECTED : + return "Disconnected"; + case SSH : + return "SessionType.SSH"; + case HTTP : + return "SessionType.HTTP"; + case SSH_READONLY : + return "SessionType.SSH:ReadOnly"; + case HTTP_READONLY: + return "SessionType.HTTP.ReadOnly"; + default : + warn_if_reached(); + return "SessionType.Invalid"; + } + } + } +} diff --git a/src/window.vala b/src/window.vala index 8e38676..5cdfc65 100644 --- a/src/window.vala +++ b/src/window.vala @@ -32,20 +32,38 @@ namespace JadupcSupport [GtkChild] private unowned Gtk.Label infobarText; [GtkChild] private unowned Gtk.Image infobarIcon; + private Tmate.Config configFactory; + private Tmate.Session tmate; + private ulong? session_handler = null; + public Window (Gtk.Application app) { Object (application: app); utils.realize_all(this); // Realize all children to pacify Gtk. icon_name = "com.jadupc.support"; + configFactory = new Tmate.Config(); + tmate = new Tmate.Session(); infobar.response.connect(() => infobar.set_revealed(false)); + + tmate.address.connect((stype, addr) => { + if(Tmate.SessionType.SSH in stype) + on_session_connect(addr); + }); + sessionbutton.clicked.connect(() => { if (infobar.revealed) infobar.revealed = false; session.label = _("Connecting..."); view.visible_child_name = "inprogress"; - Timeout.add_once(3000, () => on_session_connect("test@tty.jadupc.com")); + + var config = configFactory.get(); + session_handler = tmate.started.connect(() => { + configFactory.delete(config); + tmate.disconnect(session_handler); + }); + tmate.start(config); }); terminal.child_exited.connect(status => { @@ -85,8 +103,10 @@ namespace JadupcSupport session.label = @"<a href=\"ssh://$url\">ssh://$url</a>"; view.visible_child_name = "terminal"; terminal.has_focus = true; + debug(url); + debug(tmate.socket); - terminal.spawn_async(Vte.PtyFlags.DEFAULT, "~", {"sh"}, {@"PATH=$(Config.SECURE_PATH)"}, + terminal.spawn_async(Vte.PtyFlags.DEFAULT, "~", {"tmate", "-S", tmate.socket, "attach"}, {@"PATH=$(Config.SECURE_PATH)"}, SpawnFlags.SEARCH_PATH_FROM_ENVP, null, -1, null, spawn_async_cb); } } |