Add singbox log
Signed-off-by: Vasiliy Doylov <nekocwd@mainlining.org>
This commit is contained in:
parent
80a3498ff4
commit
bf7c271556
4 changed files with 157 additions and 41 deletions
|
@ -17,3 +17,22 @@
|
|||
padding: 5px;
|
||||
font-size: 13pt;
|
||||
}
|
||||
.singbox-status > sheet > stack > button,
|
||||
.singbox-status > sheet > stack > widget {
|
||||
background-color: alpha(var(--success-bg-color), 0.1);
|
||||
}
|
||||
.singbox-status.singbox-fail > sheet > stack > button,
|
||||
.singbox-status.singbox-fail > sheet > stack > widget {
|
||||
background-color: alpha(var(--error-bg-color), 0.1);
|
||||
}
|
||||
.singbox-status > sheet > stack > button {
|
||||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
.singbox-log {
|
||||
background: none;
|
||||
}
|
||||
.singbox-status > sheet > stack > widget > scrolledwindow {
|
||||
padding: 12px;
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,10 @@ template $SingularityWindow: Adw.ApplicationWindow {
|
|||
content: Adw.ToolbarView {
|
||||
[top]
|
||||
Adw.HeaderBar {
|
||||
styles [
|
||||
"flat",
|
||||
]
|
||||
|
||||
[end]
|
||||
MenuButton {
|
||||
primary: true;
|
||||
|
@ -18,21 +22,45 @@ template $SingularityWindow: Adw.ApplicationWindow {
|
|||
}
|
||||
}
|
||||
|
||||
content: Adw.Clamp {
|
||||
ScrolledWindow {
|
||||
hscrollbar-policy: never;
|
||||
content: Adw.BottomSheet sheet {
|
||||
styles [
|
||||
"singbox-status",
|
||||
]
|
||||
|
||||
ListView outbounds {
|
||||
margin-end: 12;
|
||||
margin-start: 12;
|
||||
halign: fill;
|
||||
valign: start;
|
||||
content: Adw.Clamp {
|
||||
ScrolledWindow {
|
||||
hscrollbar-policy: never;
|
||||
|
||||
styles [
|
||||
"outbound-list",
|
||||
]
|
||||
ListView outbounds {
|
||||
margin-end: 12;
|
||||
margin-start: 12;
|
||||
halign: fill;
|
||||
valign: start;
|
||||
|
||||
styles [
|
||||
"outbound-list",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bottom-bar: Label singbox_status {};
|
||||
|
||||
sheet: ScrolledWindow {
|
||||
propagate-natural-height: true;
|
||||
|
||||
TextView {
|
||||
styles [
|
||||
"singbox-log",
|
||||
]
|
||||
|
||||
editable: false;
|
||||
cursor-visible: false;
|
||||
monospace: true;
|
||||
|
||||
buffer: TextBuffer singbox_log {};
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,28 +18,50 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
[GtkTemplate (ui = "/io/gitlab/nekocwd/singularity/gtk/window.ui")]
|
||||
[GtkTemplate(ui = "/io/gitlab/nekocwd/singularity/gtk/window.ui")]
|
||||
public class Singularity.Window : Adw.ApplicationWindow {
|
||||
[GtkChild]
|
||||
private unowned Adw.BottomSheet sheet;
|
||||
[GtkChild]
|
||||
private unowned Gtk.ListView outbounds;
|
||||
[GtkChild]
|
||||
private unowned Gtk.TextBuffer singbox_log;
|
||||
[GtkChild]
|
||||
private unowned Gtk.Label singbox_status;
|
||||
|
||||
public Window (Gtk.Application app) {
|
||||
Object (application: app);
|
||||
var factory = new Gtk.SignalListItemFactory ();
|
||||
factory.setup.connect ((obj) => {
|
||||
private void on_singbox_status_change() {
|
||||
var status = SingBox.instance.singbox_status;
|
||||
singbox_status.label = "%s %s".printf(_("SingBox"), status ? _("running") : _("stopped"));
|
||||
if (status) {
|
||||
sheet.remove_css_class("singbox-fail");
|
||||
} else {
|
||||
sheet.add_css_class("singbox-fail");
|
||||
}
|
||||
}
|
||||
|
||||
public Window(Gtk.Application app) {
|
||||
Object(application: app);
|
||||
|
||||
SingBox.instance.notify["singbox-status"].connect(() => on_singbox_status_change());
|
||||
SingBox.instance.singbox_message.connect((message) => singbox_log.text += "\n" + message);
|
||||
on_singbox_status_change();
|
||||
|
||||
var factory = new Gtk.SignalListItemFactory();
|
||||
factory.setup.connect((obj) => {
|
||||
var item = (Gtk.ListItem) obj;
|
||||
item.set_child (new Ui.OutboundRow ());
|
||||
item.set_child(new Ui.OutboundRow());
|
||||
});
|
||||
factory.bind.connect ((obj) => {
|
||||
factory.bind.connect((obj) => {
|
||||
var item = (Gtk.ListItem) obj;
|
||||
var row = (Ui.OutboundRow) item.child;
|
||||
var data = (Outbound.Outbound) item.item;
|
||||
row.set_outbound (data);
|
||||
row.set_outbound(data);
|
||||
});
|
||||
outbounds.set_model (SingBox.instance.outbound_selection);
|
||||
outbounds.set_factory (factory);
|
||||
close_request.connect (() => {
|
||||
SingBox.instance.singbox.force_exit ();
|
||||
outbounds.set_model(SingBox.instance.outbound_selection);
|
||||
outbounds.set_factory(factory);
|
||||
|
||||
close_request.connect(() => {
|
||||
SingBox.instance.singbox.force_exit();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -19,24 +19,20 @@
|
|||
*/
|
||||
|
||||
class Singularity.SingBox : Object {
|
||||
public bool singbox_status { get; set; }
|
||||
public signal void singbox_message (string message);
|
||||
|
||||
public ListStore outbound_store = new ListStore (typeof (Outbound.Outbound));
|
||||
public Gtk.SingleSelection outbound_selection = null;
|
||||
public Subprocess? singbox = null;
|
||||
|
||||
bool can_use_singbox = true;
|
||||
|
||||
construct {
|
||||
outbound_selection = new Gtk.SingleSelection (outbound_store);
|
||||
outbound_selection.notify["selected"].connect (() => {
|
||||
message ("Selected %s", ((Outbound.Outbound) outbound_selection.selected_item).name);
|
||||
set_up ();
|
||||
});
|
||||
outbound_selection.notify["selected"].connect (() => set_up.begin ());
|
||||
singbox_message.connect ((msg) => message ("%s", msg));
|
||||
}
|
||||
|
||||
private void set_up () {
|
||||
if (singbox != null) {
|
||||
message ("killing singbox");
|
||||
singbox.force_exit ();
|
||||
}
|
||||
private void setup_config_dir () {
|
||||
var conf_dir = File.new_build_filename (Environment.get_user_config_dir (), "Singularity");
|
||||
try {
|
||||
conf_dir.make_directory ();
|
||||
|
@ -45,7 +41,10 @@ class Singularity.SingBox : Object {
|
|||
} catch (Error err) {
|
||||
warning ("Error during creating config dir: %s", err.message);
|
||||
}
|
||||
var base_config = conf_dir.get_child ("BaseConfig.json");
|
||||
}
|
||||
|
||||
private string get_base_config () {
|
||||
var base_config = File.new_build_filename (Environment.get_user_config_dir (), "Singularity", "BaseConfig.json");
|
||||
if (!base_config.query_exists ()) {
|
||||
try {
|
||||
message ("Saving base config");
|
||||
|
@ -54,7 +53,11 @@ class Singularity.SingBox : Object {
|
|||
warning ("Error during saving base config: %s", err.message);
|
||||
}
|
||||
}
|
||||
var outbound_config = conf_dir.get_child ("Outbound.json");
|
||||
return base_config.get_path ();
|
||||
}
|
||||
|
||||
private string get_outbound_config () {
|
||||
var outbound_config = File.new_build_filename (Environment.get_user_config_dir (), "Singularity", "Outbound.json");
|
||||
try {
|
||||
message ("Saving outbound config");
|
||||
var generator = new Json.Generator ();
|
||||
|
@ -66,14 +69,58 @@ class Singularity.SingBox : Object {
|
|||
} catch (Error err) {
|
||||
warning ("Error during saving outbound config: %s", err.message);
|
||||
}
|
||||
singbox = new Subprocess.newv ({ "sing-box", "--disable-color", "-c", base_config.get_path (), "-c", outbound_config.get_path (), "run" }, GLib.SubprocessFlags.STDERR_PIPE);
|
||||
return outbound_config.get_path ();
|
||||
}
|
||||
|
||||
private async void wait_for_singbox () {
|
||||
while (!can_use_singbox) {
|
||||
SourceFunc callback = wait_for_singbox.callback;
|
||||
Idle.add ((owned) callback);
|
||||
yield;
|
||||
}
|
||||
}
|
||||
|
||||
private async void set_up () {
|
||||
if (singbox != null) {
|
||||
singbox.force_exit ();
|
||||
yield wait_for_singbox ();
|
||||
}
|
||||
singbox_message ("[Singularity] Starting outbound: %s".printf (((Outbound.Outbound) outbound_selection.selected_item).name));
|
||||
setup_config_dir ();
|
||||
var base_cfg = get_base_config ();
|
||||
var outbound_cfg = get_outbound_config ();
|
||||
|
||||
try {
|
||||
singbox = new Subprocess.newv ({ "sing-box", "--disable-color", "-c", base_cfg, "-c", outbound_cfg, "run" }, SubprocessFlags.STDERR_PIPE);
|
||||
} catch (Error err) {
|
||||
singbox_message ("[Singularity] Failed to launch singbox: %s".printf (err.message));
|
||||
}
|
||||
meow_with_stream.begin (new DataInputStream (singbox.get_stderr_pipe ()));
|
||||
}
|
||||
|
||||
async void meow_with_stream (DataInputStream input) {
|
||||
for (string line = ""; line != null; line = yield input.read_line_async ())
|
||||
message ("SingBox: %s", line);
|
||||
message ("SingBox stream closed");
|
||||
singbox_status = true;
|
||||
can_use_singbox = false;
|
||||
string line = "";
|
||||
do {
|
||||
try {
|
||||
line = yield input.read_line_async ();
|
||||
} catch (Error err) {
|
||||
warning ("Error during read_line: %s", err.message);
|
||||
}
|
||||
if (line != null)
|
||||
singbox_message (line);
|
||||
} while (line != null);
|
||||
singbox_message ("[Singularity]: Singbox log closed");
|
||||
try {
|
||||
yield singbox.wait_async ();
|
||||
|
||||
singbox_status = false;
|
||||
} catch (Error err) {
|
||||
warning ("Failed to wait for singbox %s", err.message);
|
||||
}
|
||||
singbox_message ("[Singularity]: Singbox exited with code %d\n".printf (singbox.get_status ()));
|
||||
can_use_singbox = true;
|
||||
}
|
||||
|
||||
private SingBox () {}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue