diff options
Diffstat (limited to 'lib/astal/io')
-rw-r--r-- | lib/astal/io/config.vala.in | 1 | ||||
-rw-r--r-- | lib/astal/io/daemon.vala | 88 | ||||
-rw-r--r-- | lib/astal/io/default.nix | 12 | ||||
-rw-r--r-- | lib/astal/io/file.vala | 10 | ||||
-rw-r--r-- | lib/astal/io/meson.build | 2 | ||||
-rw-r--r-- | lib/astal/io/process.vala | 89 | ||||
-rw-r--r-- | lib/astal/io/variable.vala | 2 |
7 files changed, 178 insertions, 26 deletions
diff --git a/lib/astal/io/config.vala.in b/lib/astal/io/config.vala.in index fe1e450..4aee790 100644 --- a/lib/astal/io/config.vala.in +++ b/lib/astal/io/config.vala.in @@ -1,3 +1,4 @@ +[CCode (gir_namespace = "AstalIO", gir_version = "@API_VERSION@")] namespace AstalIO { public const int MAJOR_VERSION = @MAJOR_VERSION@; public const int MINOR_VERSION = @MINOR_VERSION@; diff --git a/lib/astal/io/daemon.vala b/lib/astal/io/daemon.vala new file mode 100644 index 0000000..7f3173e --- /dev/null +++ b/lib/astal/io/daemon.vala @@ -0,0 +1,88 @@ +[DBus (name="io.Astal.Application")] +public class AstalIO.Daemon : GLib.Application, AstalIO.Application { + private SocketService service; + private DBusConnection conn; + private string _instance_name = "astal"; + private string socket_path { get; private set; } + + /** + * A unique instance name. + * + * This is the identifier used by the AstalIO package and the CLI. + */ + [DBus (visible=false)] + public string instance_name { + owned get { return _instance_name; } + construct set { + _instance_name = value != null ? value : "astal"; + application_id = @"io.Astal.$_instance_name"; + } + } + + /** + * Handler for an incoming request. + * + * @param msg Body of the message + * @param conn The connection which expects the response. + */ + [DBus (visible=false)] + public virtual void request(string msg, SocketConnection conn) { + AstalIO.write_sock.begin(conn, @"missing response implementation on $application_id"); + } + + /** + * Attempt to acquire the astal socket for this app identified by its [[email protected]:instance_name]. + * If the socket is in use by another app with the same name an [[email protected]_OCCUPIED] is thrown. + */ + [DBus (visible=false)] + public void acquire_socket() throws Error { + string path; + service = AstalIO.acquire_socket(this, out path); + socket_path = path; + + Bus.own_name( + BusType.SESSION, + application_id, + BusNameOwnerFlags.NONE, + (conn) => { + try { + this.conn = conn; + conn.register_object("/io/Astal/Application", this); + } catch (Error err) { + critical(err.message); + } + }, + () => {}, + () => {} + ); + } + + public void inspector() throws DBusError, IOError { + throw new DBusError.FAILED("Daemon does not implement inspector"); + } + + public void toggle_window(string window) throws DBusError, IOError { + throw new DBusError.FAILED("Daemon does not implement toggle_window"); + } + + /** + * Quit and stop the socket if it was acquired. + */ + public new void quit() throws DBusError, IOError { + if (service != null) { + service.stop(); + service.close(); + } + + base.quit(); + } + + construct { + hold(); + + shutdown.connect(() => { try { quit(); } catch(Error err) {} }); + Unix.signal_add(1, () => { try { quit(); } catch(Error err) {} }, Priority.HIGH); + Unix.signal_add(2, () => { try { quit(); } catch(Error err) {} }, Priority.HIGH); + Unix.signal_add(15, () => { try { quit(); } catch(Error err) {} }, Priority.HIGH); + } +} diff --git a/lib/astal/io/default.nix b/lib/astal/io/default.nix new file mode 100644 index 0000000..c33132a --- /dev/null +++ b/lib/astal/io/default.nix @@ -0,0 +1,12 @@ +{mkAstalPkg, ...}: +mkAstalPkg { + pname = "astal"; + src = ./.; + + libname = "io"; + gir-suffix = "IO"; + authors = "Aylur"; + description = "Astal Core library"; + repo-path = "astal/io"; + website-path = "io"; +} diff --git a/lib/astal/io/file.vala b/lib/astal/io/file.vala index 57b6dc0..e8e8a8b 100644 --- a/lib/astal/io/file.vala +++ b/lib/astal/io/file.vala @@ -26,6 +26,11 @@ public async string read_file_async(string path) throws Error { */ public void write_file(string path, string content) { try { + var dir = Path.get_dirname(path); + if (!FileUtils.test(dir, FileTest.IS_DIR)) { + File.new_for_path(dir).make_directory_with_parents(null); + } + FileUtils.set_contents(path, content); } catch (Error error) { critical(error.message); @@ -36,6 +41,11 @@ public void write_file(string path, string content) { * Write content to a file asynchronously. */ public async void write_file_async(string path, string content) throws Error { + var dir = Path.get_dirname(path); + if (!FileUtils.test(dir, FileTest.IS_DIR)) { + File.new_for_path(dir).make_directory_with_parents(null); + } + yield File.new_for_path(path).replace_contents_async( content.data, null, diff --git a/lib/astal/io/meson.build b/lib/astal/io/meson.build index 023dece..9a00904 100644 --- a/lib/astal/io/meson.build +++ b/lib/astal/io/meson.build @@ -22,6 +22,7 @@ config = configure_file( input: 'config.vala.in', output: 'config.vala', configuration: { + 'API_VERSION': api_version, 'VERSION': meson.project_version(), 'MAJOR_VERSION': version_split[0], 'MINOR_VERSION': version_split[1], @@ -38,6 +39,7 @@ deps = [ sources = [config] + files( 'application.vala', + 'daemon.vala', 'file.vala', 'process.vala', 'time.vala', diff --git a/lib/astal/io/process.vala b/lib/astal/io/process.vala index cfd05b9..95c67a3 100644 --- a/lib/astal/io/process.vala +++ b/lib/astal/io/process.vala @@ -2,20 +2,22 @@ * `Process` provides shortcuts for [[email protected]] with sane defaults. */ public class AstalIO.Process : Object { + public string[] argv { construct; get; } + private void read_stream(DataInputStream stream, bool err) { stream.read_line_utf8_async.begin(Priority.DEFAULT, null, (_, res) => { try { var output = stream.read_line_utf8_async.end(res); if (output != null) { - if (err) + if (err) { stdout(output.strip()); - else + } else { stderr(output.strip()); - + } read_stream(stream, err); } } catch (Error err) { - printerr("%s\n", err.message); + critical(err.message); } }); } @@ -24,20 +26,27 @@ public class AstalIO.Process : Object { private DataInputStream err_stream; private DataOutputStream in_stream; private Subprocess process; - public string[] argv { construct; get; } + /** + * When the underlying subprocess writes to its stdout. + * + * @param out Line written to stdout + */ + public signal void stdout(string out); /** - * When the underlying subprocess writes to its stdout - * this signal is emitted with that line. + * When the underlying subprocess writes to its stderr. + * + * @param err Line written to stderr */ - public signal void stdout (string out); + public signal void stderr(string err); /** - * When the underlying subprocess writes to its stderr - * this signal is emitted with that line. + * When the underlying subprocess exits or is terminated. + * + * @param code Exit code or signal number if terminated */ - public signal void stderr (string err); + public signal void exit(int code, bool terminated); /** * Force quit the subprocess. @@ -48,6 +57,8 @@ public class AstalIO.Process : Object { /** * Send a signal to the subprocess. + * + * @param signal_num Signal number to be sent */ public void signal(int signal_num) { process.send_signal(signal_num); @@ -55,6 +66,8 @@ public class AstalIO.Process : Object { /** * Write a line to the subprocess' stdin synchronously. + * + * @param in String to be written to stdin */ public void write(string in) throws Error { in_stream.put_string(in); @@ -62,6 +75,8 @@ public class AstalIO.Process : Object { /** * Write a line to the subprocess' stdin asynchronously. + * + * @param in String to be written to stdin */ public async void write_async(string in) { try { @@ -71,23 +86,47 @@ public class AstalIO.Process : Object { } } - /** - * Start a new subprocess with the given command. - * - * The first element of the vector is executed with the remaining elements as the argument list. - */ - public Process.subprocessv(string[] cmd) throws Error { + /** See [[email protected]] */ + public Process(string[] cmd) throws Error { Object(argv: cmd); - process = new Subprocess.newv(cmd, + process = new Subprocess.newv( + cmd, SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDERR_PIPE | SubprocessFlags.STDOUT_PIPE ); + out_stream = new DataInputStream(process.get_stdout_pipe()); err_stream = new DataInputStream(process.get_stderr_pipe()); in_stream = new DataOutputStream(process.get_stdin_pipe()); + read_stream(out_stream, true); read_stream(err_stream, false); + + process.wait_async.begin(null, (_, res) => { + try { + process.wait_async.end(res); + } catch (Error err) { + // ignore + } + + if (process.get_if_exited()) { + exit(process.get_exit_status(), false); + } + + if (process.get_if_signaled()) { + exit(process.get_term_sig(), true); + } + }); + } + + /** + * Start a new subprocess with the given command. + * + * The first element of the vector is executed with the remaining elements as the argument list. + */ + public static Process subprocessv(string[] cmd) throws Error { + return new Process(cmd); } /** @@ -97,7 +136,7 @@ public class AstalIO.Process : Object { public static Process subprocess(string cmd) throws Error { string[] argv; Shell.parse_argv(cmd, out argv); - return new Process.subprocessv(argv); + return Process.subprocessv(argv); } /** @@ -116,11 +155,11 @@ public class AstalIO.Process : Object { string err_str, out_str; process.communicate_utf8(null, null, out out_str, out err_str); var success = process.get_successful(); - process.dispose(); - if (success) + if (success) { return out_str.strip(); - else + } else { throw new IOError.FAILED(err_str.strip()); + } } /** @@ -151,11 +190,11 @@ public class AstalIO.Process : Object { string err_str, out_str; yield process.communicate_utf8_async(null, null, out out_str, out err_str); var success = process.get_successful(); - process.dispose(); - if (success) + if (success) { return out_str.strip(); - else + } else { throw new IOError.FAILED(err_str.strip()); + } } /** diff --git a/lib/astal/io/variable.vala b/lib/astal/io/variable.vala index 312a27a..e4105f8 100644 --- a/lib/astal/io/variable.vala +++ b/lib/astal/io/variable.vala @@ -172,7 +172,7 @@ public class AstalIO.Variable : VariableBase { return_if_fail(watch_proc == null); return_if_fail(watch_exec != null); - watch_proc = new Process.subprocessv(watch_exec); + watch_proc = Process.subprocessv(watch_exec); watch_proc.stdout.connect((str) => set_closure(str, watch_transform)); watch_proc.stderr.connect((str) => this.error(str)); } |