mirror of https://github.com/oupson/FooTerm.git
Initial support for ssh
This commit is contained in:
parent
0b8689ef6c
commit
dba7b10698
|
@ -0,0 +1,3 @@
|
|||
[submodule "bindings/vapi"]
|
||||
path = bindings/vapi
|
||||
url = https://gitlab.gnome.org/GNOME/vala-extra-vapis.git
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 9ce7531a398c3ba629c89aad6ba43fb73c6060b6
|
|
@ -44,6 +44,26 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name" : "libssh2",
|
||||
"buildsystem" : "cmake-ninja",
|
||||
"config-opts" : [
|
||||
"-DCMAKE_BUILD_TYPE=RelWithDebInfo",
|
||||
"-DCMAKE_INSTALL_LIBDIR:PATH=/app/lib",
|
||||
"-DBUILD_SHARED_LIBS:BOOL=ON"
|
||||
],
|
||||
"cleanup" : [
|
||||
"/share/doc"
|
||||
],
|
||||
"sources" : [
|
||||
{
|
||||
"type" : "git",
|
||||
"url" : "https://github.com/libssh2/libssh2.git",
|
||||
"tag" : "libssh2-1.10.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"name" : "footerm",
|
||||
"builddir" : true,
|
||||
|
|
10
meson.build
10
meson.build
|
@ -7,7 +7,17 @@ project('footerm', ['c', 'vala'],
|
|||
i18n = import('i18n')
|
||||
gnome = import('gnome')
|
||||
|
||||
metadata_dir = meson.project_source_root() / 'bindings'/ 'metadata'
|
||||
vapi_dir = meson.project_source_root() / 'bindings' / 'vapi'
|
||||
|
||||
add_project_arguments([
|
||||
# Make sure Meson can find custom VAPIs
|
||||
'--vapidir', vapi_dir,
|
||||
'--metadatadir', metadata_dir,
|
||||
],
|
||||
language: 'vala'
|
||||
)
|
||||
add_project_arguments('-D_GNU_SOURCE', language: 'c')
|
||||
|
||||
subdir('data')
|
||||
subdir('src')
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
<gresources>
|
||||
<gresource prefix="/fr/oupson/FooTerm">
|
||||
<file preprocess="xml-stripblanks">window.ui</file>
|
||||
<file preprocess="xml-stripblanks">newpane.ui</file>
|
||||
<file preprocess="xml-stripblanks">terminalpane.ui</file>
|
||||
<file preprocess="xml-stripblanks">newserver.ui</file>
|
||||
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
|
|
|
@ -19,6 +19,15 @@
|
|||
*/
|
||||
|
||||
int main (string[] args) {
|
||||
var rc = SSH2.init (0);
|
||||
if (rc != SSH2.Error.NONE) {
|
||||
stdout.printf ("libssh2 initialization failed (%d)\n", rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
var app = new Footerm.Application ();
|
||||
return app.run (args);
|
||||
var res = app.run(args);
|
||||
|
||||
SSH2.exit ();
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -2,14 +2,21 @@ footerm_sources = [
|
|||
'main.vala',
|
||||
'application.vala',
|
||||
'window.vala',
|
||||
'newpane.vala',
|
||||
'newserver.vala',
|
||||
'terminalpane.vala',
|
||||
]
|
||||
|
||||
footerm_deps = [
|
||||
dependency('gtk4'),
|
||||
dependency('libadwaita-1'),
|
||||
dependency('vte-2.91-gtk4', version: '>= 0.70.0'),
|
||||
dependency('json-glib-1.0', version: '>= 1.2.0'),
|
||||
dependency('libssh2'),
|
||||
]
|
||||
|
||||
subdir('model')
|
||||
|
||||
footerm_sources += gnome.compile_resources('footerm-resources',
|
||||
'footerm.gresource.xml',
|
||||
c_name: 'footerm'
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/* Server.vala
|
||||
*
|
||||
* Copyright 2023 oupson
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace FooTerm.Model {
|
||||
public class Server {
|
||||
public string hostname;
|
||||
public ushort port;
|
||||
public string username;
|
||||
public string password;
|
||||
|
||||
public Server(string hostname, ushort port, string username, string password) {
|
||||
this.hostname = hostname;
|
||||
this.port = port;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
footerm_sources += files(
|
||||
'Server.vala'
|
||||
)
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="Adw" version="1.0"/>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="FooTermNewPane" parent="GtkBox">
|
||||
<child>
|
||||
<object class="GtkStack" id="newpane_stack">
|
||||
<child>
|
||||
<object class="AdwClamp">
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="server_list">
|
||||
<property name="margin-bottom">32</property>
|
||||
<property name="margin-top">32</property>
|
||||
<property name="margin-start">32</property>
|
||||
<property name="margin-end">32</property>
|
||||
<property name="title" translatable="yes">Servers</property>
|
||||
<child type="header-suffix">
|
||||
<object class="GtkButton" id="newpane_add_button">
|
||||
<property name="valign">center</property>
|
||||
<property name="child">
|
||||
<object class="AdwButtonContent">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
<property name="label" translatable="yes">Add new</property>
|
||||
</object>
|
||||
</property>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwClamp">
|
||||
<child>
|
||||
<object class="FooTermNewServer" id="new_server"></object>
|
||||
</child>
|
||||
</object>
|
||||
</child>"
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -0,0 +1,71 @@
|
|||
/* newpane.vala
|
||||
*
|
||||
* Copyright 2023 oupson
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace FooTerm {
|
||||
[GtkTemplate (ui = "/fr/oupson/FooTerm/newpane.ui")]
|
||||
public class NewPane : Gtk.Box {
|
||||
[GtkChild]
|
||||
private unowned Adw.PreferencesGroup server_list;
|
||||
|
||||
[GtkChild]
|
||||
private unowned FooTerm.NewServer new_server;
|
||||
|
||||
[GtkChild]
|
||||
private unowned Gtk.Stack newpane_stack;
|
||||
|
||||
[GtkChild]
|
||||
private unowned Gtk.Button newpane_add_button;
|
||||
|
||||
public signal void on_server_selected(FooTerm.Model.Server server);
|
||||
|
||||
construct {
|
||||
this.new_server.on_new_server.connect((s) => {
|
||||
this.newpane_stack.set_visible_child(server_list.get_parent());
|
||||
var action_row = new Adw.ActionRow();
|
||||
action_row.set_title(s.hostname);
|
||||
action_row.set_activatable(true);
|
||||
action_row.activated.connect(() => {
|
||||
this.on_server_selected(s);
|
||||
});
|
||||
server_list.add(action_row);
|
||||
});
|
||||
this.newpane_add_button.clicked.connect (() => {
|
||||
this.newpane_stack.set_visible_child(new_server.get_parent());
|
||||
});
|
||||
|
||||
try {
|
||||
var config_dir = GLib.File.new_for_path(GLib.Environment.get_user_config_dir());
|
||||
var server_files = config_dir.get_child("servers.json");
|
||||
|
||||
if (server_files.query_exists()) {
|
||||
var parser = new Json.Parser();
|
||||
|
||||
parser.load_from_file(server_files.get_path());
|
||||
|
||||
// TODO
|
||||
} else {
|
||||
GLib.debug("Server file does not exist");
|
||||
}
|
||||
} catch (Error e) {
|
||||
GLib.warning("Failed to read server list : %s", e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<template class="FooTermNewServer" parent="GtkBox">
|
||||
<property name="margin-bottom">32</property>
|
||||
<property name="margin-top">32</property>
|
||||
<property name="margin-start">32</property>
|
||||
<property name="margin-end">32</property>
|
||||
<property name="spacing">12</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="hostname_entry">
|
||||
<property name="title" translatable="yes">Hostname</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="port_entry">
|
||||
<property name="title" translatable="yes">Port</property>
|
||||
<property name="input-purpose">number</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="username_entry">
|
||||
<property name="title" translatable="yes">Username</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPasswordEntryRow" id="password_entry">
|
||||
<property name="title" translatable="yes">Password</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="add_server_button">
|
||||
<property name="label" translatable="yes">Add</property>
|
||||
<property name="margin-top">12</property>
|
||||
<property name="halign">end</property>
|
||||
<style>
|
||||
<class name="suggested-action" />
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -0,0 +1,58 @@
|
|||
/* newserver.vala
|
||||
*
|
||||
* Copyright 2023 oupson
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace FooTerm {
|
||||
[GtkTemplate (ui = "/fr/oupson/FooTerm/newserver.ui")]
|
||||
public class NewServer : Gtk.Box {
|
||||
public signal void on_new_server(FooTerm.Model.Server server);
|
||||
|
||||
[GtkChild]
|
||||
private unowned Adw.EntryRow hostname_entry;
|
||||
|
||||
[GtkChild]
|
||||
private unowned Adw.EntryRow port_entry;
|
||||
|
||||
[GtkChild]
|
||||
private unowned Adw.EntryRow username_entry;
|
||||
|
||||
[GtkChild]
|
||||
private unowned Adw.PasswordEntryRow password_entry;
|
||||
|
||||
[GtkChild]
|
||||
private unowned Gtk.Button add_server_button;
|
||||
|
||||
construct {
|
||||
add_server_button.clicked.connect (this.on_add_button_clicked);
|
||||
}
|
||||
|
||||
private void on_add_button_clicked() {
|
||||
var hostname = this.hostname_entry.get_text();
|
||||
var port = int.parse(this.port_entry.get_text());
|
||||
var username = this.username_entry.get_text();
|
||||
var password = this.password_entry.get_text();
|
||||
|
||||
if (port > 65545 || port < 0) {
|
||||
// Port is invalid
|
||||
}
|
||||
|
||||
this.on_new_server(new FooTerm.Model.Server(hostname, (ushort)port, username, password));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="vte-2.91-gtk4" version="0.70.0"/>
|
||||
<template class="FooTermTerminalPane" parent="GtkBox">
|
||||
<child>
|
||||
<object class="VteTerminal" id="terminal">
|
||||
<property name="hexpand">true</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -0,0 +1,190 @@
|
|||
/* terminalpane.vala
|
||||
*
|
||||
* Copyright 2023 oupson
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace FooTerm {
|
||||
[GtkTemplate (ui = "/fr/oupson/FooTerm/terminalpane.ui")]
|
||||
public class TerminalPane : Gtk.Box {
|
||||
[GtkChild]
|
||||
private unowned Vte.Terminal terminal;
|
||||
|
||||
private SSH2.Session<bool>? session;
|
||||
private SSH2.Channel? channel;
|
||||
private Socket socket;
|
||||
private int slave_pty;
|
||||
|
||||
private FooTerm.Model.Server server;
|
||||
|
||||
public TerminalPane(FooTerm.Model.Server server) {
|
||||
this.server = server;
|
||||
this.terminal.set_enable_sixel (true);
|
||||
this.connect_to_server();
|
||||
this.terminal.char_size_changed.connect(() => {
|
||||
int rows = 0;
|
||||
int columns = 0;
|
||||
this.terminal.get_pty().get_size(out rows, out columns);
|
||||
this.channel.request_pty_size(columns, rows);
|
||||
});
|
||||
}
|
||||
|
||||
~TerminalPane() {
|
||||
// TODO DESTRUCT
|
||||
// session.disconnect( "Normal Shutdown, Thank you for playing");
|
||||
// session = null;
|
||||
// Posix.close(sock);
|
||||
// stdout.printf("all done!\n");)
|
||||
}
|
||||
|
||||
private void connect_to_server() throws GLib.IOError, GLib.Error {
|
||||
this.socket = new Socket (SocketFamily.IPV4, SocketType.STREAM, SocketProtocol.TCP);
|
||||
var addrs = new NetworkAddress(this.server.hostname, this.server.port);
|
||||
socket.connect(addrs.enumerate().next(), null);
|
||||
|
||||
var sock = socket.get_fd(); // TODO
|
||||
|
||||
this.session = SSH2.Session.create<bool>();
|
||||
if (session.handshake(sock) != SSH2.Error.NONE) {
|
||||
stderr.printf("Failure establishing SSH session\n");
|
||||
return;
|
||||
}
|
||||
|
||||
var fingerprint = session.get_host_key_hash(SSH2.HashType.SHA1);
|
||||
stdout.printf("Fingerprint: ");
|
||||
for(var i = 0; i < 20; i++) {
|
||||
stdout.printf("%02X ", fingerprint[i]);
|
||||
}
|
||||
stdout.printf("\n");
|
||||
|
||||
if (session.auth_password(this.server.username, this.server.password) != SSH2.Error.NONE) {
|
||||
stdout.printf("\tAuthentication by password failed!\n");
|
||||
session.disconnect( "Normal Shutdown, Thank you for playing");
|
||||
session = null;
|
||||
Posix.close(sock);
|
||||
return;
|
||||
} else {
|
||||
stdout.printf("\tAuthentication by password succeeded.\n");
|
||||
}
|
||||
|
||||
this.channel = null;
|
||||
if (session.authenticated && (channel = session.open_channel()) == null) {
|
||||
stderr.printf("Unable to open a session\n");
|
||||
} else {
|
||||
if (channel.request_pty("xterm-256color".data) != SSH2.Error.NONE) {
|
||||
stderr.printf("Failed requesting pty\n");
|
||||
session.disconnect( "Normal Shutdown, Thank you for playing");
|
||||
session = null;
|
||||
Posix.close(sock);
|
||||
}
|
||||
|
||||
channel.set_env ("TERM", "xterm-256color");
|
||||
|
||||
if (channel.start_shell() != SSH2.Error.NONE) {
|
||||
stderr.printf("Unable to request shell on allocated pty\n");
|
||||
session.disconnect( "Normal Shutdown, Thank you for playing");
|
||||
session = null;
|
||||
Posix.close(sock);
|
||||
}
|
||||
|
||||
var master_pty = Posix.posix_openpt(Posix.O_RDWR);
|
||||
if (master_pty == -1) {
|
||||
throw GLib.IOError.from_errno (Posix.errno);
|
||||
}
|
||||
|
||||
var settings = Posix.termios();
|
||||
Posix.cfmakeraw (ref settings);
|
||||
|
||||
if (Posix.tcsetattr (master_pty, Posix.TCSANOW, settings) == -1) {
|
||||
throw GLib.IOError.from_errno (Posix.errno);
|
||||
}
|
||||
|
||||
if (Posix.grantpt(master_pty) == -1) {
|
||||
throw GLib.IOError.from_errno (Posix.errno);
|
||||
}
|
||||
|
||||
if (Posix.unlockpt(master_pty) == -1) {
|
||||
throw GLib.IOError.from_errno (Posix.errno);
|
||||
}
|
||||
|
||||
var pts_name = Posix.ptsname(master_pty);
|
||||
if (pts_name == null) {
|
||||
throw GLib.IOError.from_errno (Posix.errno);
|
||||
}
|
||||
|
||||
this.slave_pty = Posix.open(pts_name, Posix.O_RDWR);
|
||||
if (this.slave_pty < 0) {
|
||||
throw GLib.IOError.from_errno (Posix.errno);
|
||||
}
|
||||
|
||||
var vte_pty = new Vte.Pty.foreign_sync(master_pty, null);
|
||||
this.terminal.set_pty(vte_pty);
|
||||
|
||||
session.blocking = false;
|
||||
|
||||
var sock_channel = new GLib.IOChannel.unix_new(sock);
|
||||
sock_channel.set_encoding (null);
|
||||
sock_channel.set_buffered (false);
|
||||
sock_channel.set_close_on_unref (false);
|
||||
|
||||
var slave_channel = new GLib.IOChannel.unix_new(slave_pty);
|
||||
slave_channel.set_encoding (null);
|
||||
slave_channel.set_buffered (false);
|
||||
|
||||
sock_channel.add_watch (GLib.IOCondition.IN, (source, condition) => {
|
||||
if (condition == IOCondition.HUP) {
|
||||
print ("The connection has been broken.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
var buffer = new uint8[1024];
|
||||
var size = this.channel.read(buffer);
|
||||
|
||||
if (Posix.write(this.slave_pty, buffer, size) < 0) {
|
||||
throw GLib.IOError.from_errno(Posix.errno);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch(Error e) {
|
||||
GLib.warning("Failed to read from ssh : %s", e.message);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
slave_channel.add_watch (GLib.IOCondition.IN, (source, condition) => {
|
||||
if (condition == IOCondition.HUP) {
|
||||
print ("The connection has been broken.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
var buffer = new char[1024];
|
||||
size_t size = 0;
|
||||
source.read_chars(buffer, out size);
|
||||
|
||||
this.channel.write ((uint8[])buffer[0:size]);
|
||||
return true;
|
||||
} catch (Error e) {
|
||||
GLib.warning("Failed to read from terminal : %s", e.message);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<requires lib="Adw" version="1.0"/>
|
||||
<requires lib="vte-2.91-gtk4" version="0.70.0"/>
|
||||
<template class="FootermWindow" parent="AdwApplicationWindow">
|
||||
<property name="default-width">600</property>
|
||||
<property name="default-height">300</property>
|
||||
|
@ -20,8 +19,8 @@
|
|||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="VteTerminal" id="terminal">
|
||||
<property name="hexpand">true</property>
|
||||
<object class="GtkBox" id="pane_content">
|
||||
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
|
|
@ -22,22 +22,21 @@ namespace Footerm {
|
|||
[GtkTemplate (ui = "/fr/oupson/FooTerm/window.ui")]
|
||||
public class Window : Adw.ApplicationWindow {
|
||||
[GtkChild]
|
||||
private unowned Vte.Terminal terminal;
|
||||
private unowned Gtk.Box pane_content;
|
||||
|
||||
public Window (Gtk.Application app) {
|
||||
Object (application: app);
|
||||
}
|
||||
|
||||
construct {
|
||||
this.terminal.set_enable_sixel (true);
|
||||
this.terminal.spawn_async (Vte.PtyFlags.NO_CTTY, null, {"flatpak-spawn", "--host", "bash"}, null, 0, null, -1, null, this.on_spawn_finished);
|
||||
}
|
||||
|
||||
|
||||
private void on_spawn_finished (Vte.Terminal t, Pid _pid, GLib.Error? error) {
|
||||
if (error != null) {
|
||||
warning ("%s", error.message);
|
||||
}
|
||||
var new_pane = new FooTerm.NewPane();
|
||||
ulong handler_id;
|
||||
handler_id = new_pane.on_server_selected.connect((s) => {
|
||||
new_pane.disconnect (handler_id);
|
||||
pane_content.remove(new_pane);
|
||||
pane_content.append(new FooTerm.TerminalPane(s));
|
||||
});
|
||||
pane_content.append(new_pane);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue