Add singbox log
All checks were successful
PostmarketOS Build / Prepare (push) Successful in 23s
PostmarketOS Build / Build for aarch64 (push) Successful in 48s
PostmarketOS Build / Build for x86_64 (push) Successful in 16s

Signed-off-by: Vasiliy Doylov <nekocwd@mainlining.org>
This commit is contained in:
Vasiliy Doylov 2025-06-22 19:53:10 +03:00
parent 80a3498ff4
commit bf7c271556
Signed by: NekoCWD
GPG key ID: B7BE22D44474A582
4 changed files with 157 additions and 41 deletions

View file

@ -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;
}

View file

@ -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 {};
}
};
};
};
}

View file

@ -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;
});
}

View file

@ -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 () {}