From 22cd6fb733dbf54fe136144182047fe707e04ce3 Mon Sep 17 00:00:00 2001 From: Vasiliy Doylov Date: Sun, 22 Jun 2025 00:55:38 +0300 Subject: [PATCH] Initial functional Signed-off-by: Vasiliy Doylov --- .gitignore | 1 + README.md | 7 +- src/application.vala | 5 ++ src/base.json | 31 ++++++++ src/gtk/outbound-row.blp | 16 ++++ src/{window.vala => gtk/outbound-row.vala} | 18 +++-- src/gtk/style.css | 4 + src/{ => gtk}/window.blp | 15 ++-- src/gtk/window.vala | 42 +++++++++++ src/logic/meson.build | 1 + src/logic/outbounds/outbound.vala | 3 +- src/logic/singbox.vala | 88 ++++++++++++++++++++++ src/main.vala | 38 ++++++++-- src/meson.build | 6 +- src/singularity.gresource.xml | 7 +- 15 files changed, 256 insertions(+), 26 deletions(-) create mode 100644 src/base.json create mode 100644 src/gtk/outbound-row.blp rename src/{window.vala => gtk/outbound-row.vala} (67%) create mode 100644 src/gtk/style.css rename src/{ => gtk}/window.blp (78%) create mode 100644 src/gtk/window.vala create mode 100644 src/logic/singbox.vala diff --git a/.gitignore b/.gitignore index 96901c4..8b3e452 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /subprojects/blueprint-compiler +Sub* diff --git a/README.md b/README.md index aadba5e..4047535 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ -# singularity +# Singularity -A description of this project. +SingBox wrapper. + +## ENV variables: + - `SUB_FILE` = `.config/Singularity/subscription` - path to subscription file diff --git a/src/application.vala b/src/application.vala index dc5e5fe..232fe1c 100644 --- a/src/application.vala +++ b/src/application.vala @@ -41,6 +41,11 @@ public class Singularity.Application : Adw.Application { base.activate (); var win = this.active_window ?? new Singularity.Window (this); win.present (); + var styling = new Gtk.CssProvider (); + styling.load_from_resource ("/io/gitlab/nekocwd/singularity/gtk/style.css"); + Gtk.StyleContext.add_provider_for_display (Gdk.Display.get_default (), + styling, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); } private void on_about_action () { diff --git a/src/base.json b/src/base.json new file mode 100644 index 0000000..60b05a6 --- /dev/null +++ b/src/base.json @@ -0,0 +1,31 @@ +{ + "endpoints": [], + "experimental": { + "clash_api": { + "default_mode": "" + } + }, + "inbounds": [ + { + "domain_strategy": "", + "listen": "127.0.0.1", + "listen_port": 2080, + "tag": "mixed-in", + "type": "mixed" + } + ], + "log": { + "level": "info" + }, + "route": { + "final": "proxy", + "find_process": true, + "rule_set": [], + "rules": [ + { + "action": "sniff", + "inbound": ["mixed-in"] + } + ] + } +} diff --git a/src/gtk/outbound-row.blp b/src/gtk/outbound-row.blp new file mode 100644 index 0000000..fcd7a48 --- /dev/null +++ b/src/gtk/outbound-row.blp @@ -0,0 +1,16 @@ +using Gtk 4.0; +using Adw 1; + +template $SingularityUiOutboundRow: Gtk.Box { + styles [ + "outbound-row", + ] + + Label schema { + label: "vless"; + } + + Label descr { + label: "Placeholder name"; + } +} diff --git a/src/window.vala b/src/gtk/outbound-row.vala similarity index 67% rename from src/window.vala rename to src/gtk/outbound-row.vala index f305f91..c0c4421 100644 --- a/src/window.vala +++ b/src/gtk/outbound-row.vala @@ -1,4 +1,4 @@ -/* window.vala +/* outbound-row.vala * * Copyright 2025 Vasiliy Doylov (NekoCWD) * @@ -18,12 +18,16 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -[GtkTemplate (ui = "/io/gitlab/nekocwd/singularity/window.ui")] -public class Singularity.Window : Adw.ApplicationWindow { +[GtkTemplate(ui = "/io/gitlab/nekocwd/singularity/gtk/outbound-row.ui")] +class Singularity.Ui.OutboundRow : Gtk.Box { [GtkChild] - private unowned Gtk.Label label; - - public Window (Gtk.Application app) { - Object (application: app); + private unowned Gtk.Label schema; + [GtkChild] + private unowned Gtk.Label descr; + construct { + } + public void set_outbound(Outbound.Outbound outbound) { + schema.label = outbound.type_name; + descr.label = outbound.name; } } diff --git a/src/gtk/style.css b/src/gtk/style.css new file mode 100644 index 0000000..335097b --- /dev/null +++ b/src/gtk/style.css @@ -0,0 +1,4 @@ +.outbound-row { + border-radius: 5px; + padding: 10px; +} diff --git a/src/window.blp b/src/gtk/window.blp similarity index 78% rename from src/window.blp rename to src/gtk/window.blp index 51b89f3..f19aef0 100644 --- a/src/window.blp +++ b/src/gtk/window.blp @@ -18,12 +18,17 @@ template $SingularityWindow: Adw.ApplicationWindow { } } - content: Label label { - label: _("Hello, World!"); + content: Adw.Clamp { + ScrolledWindow { + ListView outbounds { + halign: fill; + valign: start; - styles [ - "title-1", - ] + styles [ + "card", + ] + } + } }; }; } diff --git a/src/gtk/window.vala b/src/gtk/window.vala new file mode 100644 index 0000000..bbab6ee --- /dev/null +++ b/src/gtk/window.vala @@ -0,0 +1,42 @@ +/* window.vala + * + * Copyright 2025 Vasiliy Doylov (NekoCWD) + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +[GtkTemplate (ui = "/io/gitlab/nekocwd/singularity/gtk/window.ui")] +public class Singularity.Window : Adw.ApplicationWindow { + [GtkChild] + private unowned Gtk.ListView outbounds; + + public Window (Gtk.Application app) { + Object (application: app); + var factory = new Gtk.SignalListItemFactory (); + factory.setup.connect ((obj) => { + var item = (Gtk.ListItem) obj; + item.set_child (new Ui.OutboundRow ()); + }); + 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); + }); + outbounds.set_model (SingBox.instance.outbound_selection); + outbounds.set_factory (factory); + } +} diff --git a/src/logic/meson.build b/src/logic/meson.build index c407a5b..c4e9eb3 100644 --- a/src/logic/meson.build +++ b/src/logic/meson.build @@ -15,4 +15,5 @@ singularity_sources += [ config_sources, 'logic/tls.vala', 'logic/error.vala', + 'logic/singbox.vala', ] diff --git a/src/logic/outbounds/outbound.vala b/src/logic/outbounds/outbound.vala index c640536..8d90119 100644 --- a/src/logic/outbounds/outbound.vala +++ b/src/logic/outbounds/outbound.vala @@ -22,6 +22,7 @@ class Singularity.Outbound.Outbound : Object, Json.Serializable { // We can't use name `type` in vala public string type_name { get; construct set; default = ""; } public string tag { get; set; default = "proxy"; } + public string name; // Not a property public static Outbound parse_uri (string profile) throws UriError, ParseError { Uri uri = null; @@ -130,7 +131,7 @@ class Singularity.Outbound.Outbound : Object, Json.Serializable { warning ("Unknown scheme %s", scheme); break; } - + outbound.name = name; return outbound; } } diff --git a/src/logic/singbox.vala b/src/logic/singbox.vala new file mode 100644 index 0000000..ceca154 --- /dev/null +++ b/src/logic/singbox.vala @@ -0,0 +1,88 @@ +/* singbox.vala + * + * Copyright 2025 Vasiliy Doylov (NekoCWD) + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +class Singularity.SingBox : Object { + public ListStore outbound_store = new ListStore (typeof (Outbound.Outbound)); + public Gtk.SingleSelection outbound_selection = null; + public Subprocess? singbox = null; + + + 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 (); + }); + } + + private void set_up () { + if (singbox != null) { + message ("killing singbox"); + singbox.force_exit (); + } + var conf_dir = File.new_build_filename (Environment.get_user_config_dir (), "Singularity"); + try { + conf_dir.make_directory (); + } catch (IOError.EXISTS err) { + // Skip this err + } catch (Error err) { + warning ("Error during creating config dir: %s", err.message); + } + var base_config = conf_dir.get_child ("BaseConfig.json"); + if (!base_config.query_exists ()) { + try { + message ("Saving base config"); + base_config.replace_contents (resources_lookup_data ("/io/gitlab/nekocwd/singularity/base.json", ResourceLookupFlags.NONE).get_data (), null, false, FileCreateFlags.NONE, null); + } catch (Error err) { + warning ("Error during saving base config: %s", err.message); + } + } + var outbound_config = conf_dir.get_child ("Outbound.json"); + try { + message ("Saving outbound config"); + var generator = new Json.Generator (); + var config = new SingConfig (); + config.outbounds.append ((Outbound.Outbound) outbound_selection.selected_item); + generator.root = Json.gobject_serialize (config); + generator.set_pretty (true); + outbound_config.replace_contents (generator.to_data (null).data, null, false, FileCreateFlags.NONE, null); + } 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); + 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"); + } + + private SingBox () {} + private static SingBox _instance = null; + public static SingBox instance { + get { + if (_instance == null) + _instance = new SingBox (); + return _instance; + } + } +} diff --git a/src/main.vala b/src/main.vala index 0829d24..3c0afac 100644 --- a/src/main.vala +++ b/src/main.vala @@ -18,11 +18,35 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -int main (string[] args) { - Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR); - Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8"); - Intl.textdomain (Config.GETTEXT_PACKAGE); - - var app = new Singularity.Application (); - return app.run (args); +namespace Singularity { + int main (string[] args) { + Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR); + Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8"); + Intl.textdomain (Config.GETTEXT_PACKAGE); + uint8[] content = null; + try { + var sub_path = Environment.get_variable ("SUB_FILE"); + File file = null; + if (sub_path != null) { + file = File.new_for_path (sub_path); + } else { + file = File.new_build_filename (Environment.get_user_config_dir (), "Singularity", "subscription"); + } + file.load_contents (null, out content, null); + } catch (Error err) { + error ("Failed to parse subscription %s", err.message); + } + var lines = ((string) content).split ("\n", -1); + foreach (var line in lines) { + if (line[0] == '#' && line != "") + continue; + try { + SingBox.instance.outbound_store.append (Outbound.Outbound.parse_uri (line)); + } catch (Error err) { + warning ("Error during subscription parse: %s", err.message); + } + } + var app = new Singularity.Application (); + return app.run (args); + } } diff --git a/src/meson.build b/src/meson.build index d7da0af..a169c74 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,7 +1,8 @@ singularity_sources = [ 'main.vala', 'application.vala', - 'window.vala', + 'gtk/window.vala', + 'gtk/outbound-row.vala', ] singularity_deps = [ @@ -16,7 +17,8 @@ blueprints = custom_target( 'blueprints', input: files( 'gtk/help-overlay.blp', - 'window.blp', + 'gtk/outbound-row.blp', + 'gtk/window.blp', ), output: '.', command: [ diff --git a/src/singularity.gresource.xml b/src/singularity.gresource.xml index d03cd2e..5552aff 100644 --- a/src/singularity.gresource.xml +++ b/src/singularity.gresource.xml @@ -1,7 +1,10 @@ - + - window.ui + gtk/window.ui gtk/help-overlay.ui + gtk/outbound-row.ui + base.json + gtk/style.css