summaryrefslogtreecommitdiff
path: root/lib/astal/io
diff options
context:
space:
mode:
Diffstat (limited to 'lib/astal/io')
-rw-r--r--lib/astal/io/config.vala.in1
-rw-r--r--lib/astal/io/daemon.vala88
-rw-r--r--lib/astal/io/default.nix12
-rw-r--r--lib/astal/io/file.vala10
-rw-r--r--lib/astal/io/meson.build2
-rw-r--r--lib/astal/io/process.vala89
-rw-r--r--lib/astal/io/variable.vala2
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));
}