mirror of
https://github.com/zoriya/astal.git
synced 2026-06-02 18:20:48 +00:00
fixes, features
* cli dnd-toggle * save dnd and ignore_timeout state * block contstructor until dbus acqusition * fix proxy notify signals
This commit is contained in:
+29
-25
@@ -5,6 +5,7 @@ static bool list;
|
||||
static string invoke;
|
||||
static int close_n;
|
||||
static int get_n;
|
||||
static bool toggle_dnd;
|
||||
|
||||
const OptionEntry[] options = {
|
||||
{ "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null },
|
||||
@@ -14,6 +15,7 @@ const OptionEntry[] options = {
|
||||
{ "invoke", 'i', OptionFlags.NONE, OptionArg.STRING, ref invoke, null, null },
|
||||
{ "close", 'c', OptionFlags.NONE, OptionArg.INT, ref close_n, null, null },
|
||||
{ "get", 'g', OptionFlags.NONE, OptionArg.INT, ref get_n, null, null },
|
||||
{ "toggle-dnd", 't', OptionFlags.NONE, OptionArg.NONE, ref toggle_dnd, null, null },
|
||||
{ null },
|
||||
};
|
||||
|
||||
@@ -34,17 +36,17 @@ int main(string[] argv) {
|
||||
print("Usage:\n");
|
||||
print(" %s [flags]\n\n", argv[0]);
|
||||
print("Flags:\n");
|
||||
print(" -h, --help Print this help and exit\n");
|
||||
print(" -v, --version Print version number and exit\n");
|
||||
print(" -l, --list Print every notification and exit\n");
|
||||
print(" -d, --daemonize Watch for new notifications\n");
|
||||
print(" -i, --invoke Invoke a notification action\n");
|
||||
print(" -c, --close Close a notification by its id\n");
|
||||
print(" -g, --get Print a notification by its id\n");
|
||||
print(" -h, --help Print this help and exit\n");
|
||||
print(" -v, --version Print version number and exit\n");
|
||||
print(" -l, --list Print every notification and exit\n");
|
||||
print(" -d, --daemonize Watch for new notifications\n");
|
||||
print(" -i, --invoke Invoke a notification action\n");
|
||||
print(" -c, --close Close a notification by its id\n");
|
||||
print(" -g, --get Print a notification by its id\n");
|
||||
print(" -t, --toggle-dnd Toggle do not disturb\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var loop = new MainLoop();
|
||||
var notifd = new AstalNotifd.Notifd();
|
||||
|
||||
if (version) {
|
||||
@@ -53,16 +55,27 @@ int main(string[] argv) {
|
||||
}
|
||||
|
||||
if (list) {
|
||||
var cache = Environment.get_user_cache_dir() + "/astal/notifd/notifications.json";
|
||||
if (FileUtils.test(cache, FileTest.EXISTS)) {
|
||||
var state = Environment.get_user_state_dir() + "/astal/notifd/notifications.json";
|
||||
if (FileUtils.test(state, FileTest.EXISTS)) {
|
||||
try {
|
||||
uint8[] json;
|
||||
File.new_for_path(cache).load_contents(null, out json, null);
|
||||
stdout.printf("%s", (string)json);
|
||||
File.new_for_path(state).load_contents(null, out json, null);
|
||||
|
||||
var obj = Json.from_string((string)json);
|
||||
|
||||
var list = obj.get_object().get_member("notifications");
|
||||
stdout.printf("%s\n", Json.to_string(list, true));
|
||||
return 0;
|
||||
} catch (Error err) {
|
||||
stderr.printf("failed to load cache: %s", err.message);
|
||||
}
|
||||
}
|
||||
stdout.printf("[]\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (toggle_dnd) {
|
||||
notifd.dont_disturb = !notifd.dont_disturb;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -71,6 +84,7 @@ int main(string[] argv) {
|
||||
stdout.printf("%s\n", notifd.get_notification_json(id));
|
||||
stdout.flush();
|
||||
});
|
||||
new MainLoop().run();
|
||||
}
|
||||
|
||||
if (invoke != null) {
|
||||
@@ -83,29 +97,19 @@ int main(string[] argv) {
|
||||
var n_id = int.parse(split[0]);
|
||||
var a_id = split[1];
|
||||
|
||||
notifd.active.connect(() => {
|
||||
notifd.get_notification(n_id).invoke(a_id);
|
||||
loop.quit();
|
||||
});
|
||||
notifd.get_notification(n_id).invoke(a_id);
|
||||
}
|
||||
|
||||
if (close_n > 0) {
|
||||
notifd.active.connect(() => {
|
||||
notifd.get_notification(close_n).dismiss();
|
||||
loop.quit();
|
||||
});
|
||||
notifd.get_notification(close_n).dismiss();
|
||||
}
|
||||
|
||||
if (get_n > 0) {
|
||||
notifd.active.connect(() => {
|
||||
stdout.printf("%s", notifd.get_notification(get_n).to_json_string());
|
||||
loop.quit();
|
||||
});
|
||||
stdout.printf("%s", notifd.get_notification(get_n).to_json_string());
|
||||
}
|
||||
|
||||
if (!daemonize && invoke == null && close_n == 0 && get_n == 0)
|
||||
return 1;
|
||||
|
||||
loop.run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
+57
-29
@@ -1,30 +1,39 @@
|
||||
namespace AstalNotifd {
|
||||
public enum ClosedReason {
|
||||
EXPIRED = 1,
|
||||
DISMISSED_BY_USER = 2,
|
||||
CLOSED = 3,
|
||||
UNDEFINED = 4,
|
||||
}
|
||||
|
||||
[DBus (name = "org.freedesktop.Notifications")]
|
||||
internal class Daemon : Object {
|
||||
internal class AstalNotifd.Daemon : Object {
|
||||
public static string name = "notifd";
|
||||
public static string vendor = "astal";
|
||||
public static string version = "0.1";
|
||||
|
||||
private string cache_file;
|
||||
private string state_file;
|
||||
private string state_directory;
|
||||
private string cache_directory;
|
||||
|
||||
private uint n_id = 1;
|
||||
private HashTable<uint, Notification> notifs =
|
||||
new HashTable<uint, Notification>((i) => i, (a, b) => a == b);
|
||||
|
||||
public bool ignore_timeout { get; set; }
|
||||
public bool dont_disturb { get; set; }
|
||||
private bool _ignore_timeout;
|
||||
public bool ignore_timeout {
|
||||
get { return _ignore_timeout; }
|
||||
set {
|
||||
_ignore_timeout = value;
|
||||
write_state();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _dont_disturb;
|
||||
public bool dont_disturb {
|
||||
get { return _dont_disturb; }
|
||||
set {
|
||||
_dont_disturb = value;
|
||||
write_state();
|
||||
}
|
||||
}
|
||||
|
||||
public signal void notified(uint id, bool replaced);
|
||||
public signal void resolved(uint id, ClosedReason reason);
|
||||
public signal void action_invoked(uint id, string action);
|
||||
public signal void prop_changed(string prop);
|
||||
|
||||
// emitting an event from proxy doesn't seem to work
|
||||
public void emit_resolved(uint id, ClosedReason reason) { resolved(id, reason); }
|
||||
@@ -32,24 +41,31 @@ internal class Daemon : Object {
|
||||
|
||||
construct {
|
||||
cache_directory = Environment.get_user_cache_dir() + "/astal/notifd";
|
||||
cache_file = cache_directory + "/notifications.json";
|
||||
state_directory = Environment.get_user_state_dir() + "/astal/notifd";
|
||||
state_file = state_directory + "/notifications.json";
|
||||
|
||||
if (FileUtils.test(cache_file, FileTest.EXISTS)) {
|
||||
if (FileUtils.test(state_file, FileTest.EXISTS)) {
|
||||
try {
|
||||
uint8[] json;
|
||||
File.new_for_path(cache_file).load_contents(null, out json, null);
|
||||
var parser = new Json.Parser();
|
||||
parser.load_from_data((string)json);
|
||||
var list = parser.get_root().get_array();
|
||||
File.new_for_path(state_file).load_contents(null, out json, null);
|
||||
|
||||
var obj = Json.from_string((string)json);
|
||||
|
||||
var list = obj.get_object().get_array_member("notifications");
|
||||
for (var i = 0; i < list.get_length(); ++i) {
|
||||
add_notification(new Notification.from_json(list.get_object_element(i)));
|
||||
}
|
||||
n_id = list.get_length() + 1;
|
||||
|
||||
_dont_disturb = obj.get_object().get_boolean_member("dont_disturb");
|
||||
_ignore_timeout = obj.get_object().get_boolean_member("ignore_timeout");
|
||||
} catch (Error err) {
|
||||
warning("failed to load cache: %s", err.message);
|
||||
}
|
||||
}
|
||||
|
||||
notify.connect((prop) => prop_changed(prop.name));
|
||||
|
||||
notified.connect(() => {
|
||||
notify_property("notifications");
|
||||
});
|
||||
@@ -57,7 +73,7 @@ internal class Daemon : Object {
|
||||
resolved.connect((id, reason) => {
|
||||
notifs.get(id).resolved(reason);
|
||||
notifs.remove(id);
|
||||
cache();
|
||||
write_state();
|
||||
notify_property("notifications");
|
||||
notification_closed(id, reason);
|
||||
});
|
||||
@@ -123,7 +139,7 @@ internal class Daemon : Object {
|
||||
|
||||
notified(id, replaced);
|
||||
|
||||
cache();
|
||||
write_state();
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -135,21 +151,25 @@ internal class Daemon : Object {
|
||||
return replaced;
|
||||
}
|
||||
|
||||
private void cache() {
|
||||
private void write_state() {
|
||||
var list = new Json.Builder().begin_array();
|
||||
foreach (var n in notifications) {
|
||||
list.add_value(n.to_json());
|
||||
}
|
||||
list.end_array();
|
||||
var generator = new Json.Generator();
|
||||
generator.set_root(list.get_root());
|
||||
var json = generator.to_data(null);
|
||||
|
||||
var obj = new Json.Builder()
|
||||
.begin_object()
|
||||
.set_member_name("notifications").add_value(list.get_root())
|
||||
.set_member_name("ignore_timeout").add_boolean_value(ignore_timeout)
|
||||
.set_member_name("dont_disturb").add_boolean_value(dont_disturb)
|
||||
.end_object();
|
||||
|
||||
try {
|
||||
if (!FileUtils.test(cache_directory, FileTest.EXISTS))
|
||||
File.new_for_path(cache_directory).make_directory_with_parents(null);
|
||||
if (!FileUtils.test(state_directory, FileTest.EXISTS))
|
||||
File.new_for_path(state_directory).make_directory_with_parents(null);
|
||||
|
||||
FileUtils.set_contents_full(cache_file, json);
|
||||
FileUtils.set_contents_full(state_file, Json.to_string(obj.get_root(), false));
|
||||
} catch (Error err) {
|
||||
warning("failed to cache notifications: %s", err.message);
|
||||
}
|
||||
@@ -201,6 +221,9 @@ internal class Daemon : Object {
|
||||
var file_name = cache_directory + "/" + data.hash().to_string("%u.png");
|
||||
|
||||
try {
|
||||
if (!FileUtils.test(cache_directory, FileTest.EXISTS))
|
||||
File.new_for_path(cache_directory).make_directory_with_parents(null);
|
||||
|
||||
var output_stream = File.new_for_path(file_name)
|
||||
.replace(null, false, FileCreateFlags.NONE, null);
|
||||
|
||||
@@ -214,8 +237,7 @@ internal class Daemon : Object {
|
||||
return file_name;
|
||||
}
|
||||
|
||||
[DBus (visible = false)]
|
||||
public Daemon register(DBusConnection conn) {
|
||||
internal Daemon register(DBusConnection conn) {
|
||||
try {
|
||||
conn.register_object("/org/freedesktop/Notifications", this);
|
||||
} catch (Error err) {
|
||||
@@ -224,4 +246,10 @@ internal class Daemon : Object {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public enum AstalNotifd.ClosedReason {
|
||||
EXPIRED = 1,
|
||||
DISMISSED_BY_USER = 2,
|
||||
CLOSED = 3,
|
||||
UNDEFINED = 4,
|
||||
}
|
||||
|
||||
+29
-15
@@ -1,9 +1,10 @@
|
||||
namespace AstalNotifd {
|
||||
public Notifd get_default() {
|
||||
return Notifd.get_default();
|
||||
public Notifd get_default() {
|
||||
return Notifd.get_default();
|
||||
}
|
||||
}
|
||||
|
||||
public class Notifd : Object {
|
||||
public class AstalNotifd.Notifd : Object {
|
||||
private static Notifd _instance;
|
||||
public static Notifd get_default() {
|
||||
if (_instance == null)
|
||||
@@ -62,7 +63,13 @@ public class Notifd : Object {
|
||||
|
||||
construct {
|
||||
// hack to make it synchronous
|
||||
var loop = new MainLoop();
|
||||
MainLoop? loop = null;
|
||||
|
||||
if (!MainContext.default().is_owner()) {
|
||||
loop = new MainLoop();
|
||||
}
|
||||
|
||||
bool done = false;
|
||||
|
||||
Bus.own_name(
|
||||
BusType.SESSION,
|
||||
@@ -74,22 +81,23 @@ public class Notifd : Object {
|
||||
);
|
||||
|
||||
active.connect(() => {
|
||||
if (loop.is_running())
|
||||
done = true;
|
||||
if (loop != null && loop.is_running()) {
|
||||
loop.quit();
|
||||
}
|
||||
});
|
||||
|
||||
loop.run();
|
||||
if (loop != null) {
|
||||
loop.run();
|
||||
} else {
|
||||
while (!done) {
|
||||
MainContext.default().iteration(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void acquire_daemon(DBusConnection conn) {
|
||||
daemon = new Daemon().register(conn);
|
||||
daemon.notified.connect((id, replaced) => notified(id, replaced));
|
||||
daemon.resolved.connect((id, reason) => resolved(id, reason));
|
||||
daemon.notify.connect((prop) => {
|
||||
if (get_class().find_property(prop.name) != null) {
|
||||
notify_property(prop.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void on_daemon_acquired() {
|
||||
@@ -97,6 +105,13 @@ public class Notifd : Object {
|
||||
proxy.stop();
|
||||
proxy = null;
|
||||
}
|
||||
daemon.notified.connect((id, replaced) => notified(id, replaced));
|
||||
daemon.resolved.connect((id, reason) => resolved(id, reason));
|
||||
daemon.notify.connect((prop) => {
|
||||
if (get_class().find_property(prop.name) != null) {
|
||||
notify_property(prop.name);
|
||||
}
|
||||
});
|
||||
active(ActiveType.DAEMON);
|
||||
}
|
||||
|
||||
@@ -119,8 +134,7 @@ public class Notifd : Object {
|
||||
}
|
||||
}
|
||||
|
||||
public enum ActiveType {
|
||||
public enum AstalNotifd.ActiveType {
|
||||
DAEMON,
|
||||
PROXY,
|
||||
}
|
||||
}
|
||||
|
||||
+15
-18
@@ -1,21 +1,16 @@
|
||||
namespace AstalNotifd {
|
||||
public enum Urgency {
|
||||
public enum AstalNotifd.Urgency {
|
||||
LOW = 0,
|
||||
NORMAL = 1,
|
||||
CRITICAL = 2,
|
||||
}
|
||||
|
||||
public class Action {
|
||||
public Action(string id, string label) {
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
}
|
||||
public struct AstalNotifd.Action {
|
||||
public string id;
|
||||
public string label;
|
||||
}
|
||||
|
||||
public class Notification : Object {
|
||||
private List<Action> _actions;
|
||||
public class AstalNotifd.Notification : Object {
|
||||
private List<Action?> _actions;
|
||||
private HashTable<string, Variant> hints;
|
||||
|
||||
public int64 time { construct set; get; }
|
||||
@@ -25,7 +20,7 @@ public class Notification : Object {
|
||||
public string body { construct set; get; }
|
||||
public uint id { construct set; get; }
|
||||
public int expire_timeout { construct set; get; }
|
||||
public List<Action> actions { get { return _actions; } }
|
||||
public List<Action?> actions { get { return _actions; } }
|
||||
|
||||
public string image { get { return get_str_hint("image-path"); } }
|
||||
public bool action_icons { get { return get_bool_hint("action-icons"); } }
|
||||
@@ -61,9 +56,12 @@ public class Notification : Object {
|
||||
);
|
||||
|
||||
this.hints = hints;
|
||||
_actions = new List<Action>();
|
||||
_actions = new List<Action?>();
|
||||
for (var i = 0; i < actions.length; i += 2) {
|
||||
_actions.append(new Action(actions[i], actions[i + 1]));
|
||||
_actions.append(Action() {
|
||||
id = actions[i],
|
||||
label = actions[i + 1]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,13 +107,13 @@ public class Notification : Object {
|
||||
}
|
||||
break;
|
||||
case "actions":
|
||||
_actions = new List<Action>();
|
||||
_actions = new List<Action?>();
|
||||
for (var i = 0; i < node.get_array().get_length(); ++i) {
|
||||
var o = node.get_array().get_object_element(i);
|
||||
_actions.append(new Action(
|
||||
o.get_member("id").get_string(),
|
||||
o.get_member("label").get_string()
|
||||
));
|
||||
_actions.append(Action() {
|
||||
id = o.get_member("id").get_string(),
|
||||
label = o.get_member("label").get_string()
|
||||
});
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
@@ -160,4 +158,3 @@ public class Notification : Object {
|
||||
.get_root();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-11
@@ -1,6 +1,5 @@
|
||||
namespace AstalNotifd {
|
||||
[DBus (name = "org.freedesktop.Notifications")]
|
||||
internal interface IDaemon : Object {
|
||||
internal interface AstalNotifd.IDaemon : DBusProxy {
|
||||
public abstract bool ignore_timeout { get; set; }
|
||||
public abstract bool dont_disturb { get; set; }
|
||||
|
||||
@@ -9,12 +8,13 @@ internal interface IDaemon : Object {
|
||||
|
||||
public signal void notified(uint id, bool replaced);
|
||||
public signal void resolved(uint id, ClosedReason reason);
|
||||
public signal void prop_changed(string prop);
|
||||
|
||||
public abstract void emit_resolved(uint id, ClosedReason reason);
|
||||
public abstract void emit_action_invoked(uint id, string action);
|
||||
}
|
||||
|
||||
internal class DaemonProxy : Object {
|
||||
internal class AstalNotifd.DaemonProxy : Object {
|
||||
private HashTable<uint, Notification> notifs =
|
||||
new HashTable<uint, Notification>((i) => i, (a, b) => a == b);
|
||||
|
||||
@@ -43,8 +43,8 @@ internal class DaemonProxy : Object {
|
||||
public signal void notified(uint id, bool replaced);
|
||||
public signal void resolved(uint id, ClosedReason reason);
|
||||
|
||||
IDaemon proxy;
|
||||
List<ulong> ids = new List<ulong>();
|
||||
private IDaemon proxy;
|
||||
private List<ulong> ids = new List<ulong>();
|
||||
|
||||
public void stop() {
|
||||
if (ids.length() > 0) {
|
||||
@@ -72,8 +72,8 @@ internal class DaemonProxy : Object {
|
||||
var version = variant.get_child_value(2).get_string();
|
||||
|
||||
var running = name == Daemon.name
|
||||
&& vendor == Daemon.vendor
|
||||
&& version == Daemon.version;
|
||||
&& vendor == Daemon.vendor
|
||||
&& version == Daemon.version;
|
||||
|
||||
if (running) {
|
||||
setup_proxy();
|
||||
@@ -97,19 +97,21 @@ internal class DaemonProxy : Object {
|
||||
foreach (var id in proxy.notification_ids())
|
||||
add_notification(id);
|
||||
|
||||
ids.append(proxy.notify.connect((pspec) => {
|
||||
if (get_class().find_property(pspec.name) != null)
|
||||
notify_property(pspec.name);
|
||||
ids.append(proxy.prop_changed.connect((prop) => {
|
||||
if (prop == "ignore-timeout" || prop == "dont-disturb")
|
||||
notify_property(prop);
|
||||
}));
|
||||
|
||||
ids.append(proxy.notified.connect((id, replaced) => {
|
||||
add_notification(id);
|
||||
notified(id, replaced);
|
||||
notify_property("notifications");
|
||||
}));
|
||||
|
||||
ids.append(proxy.resolved.connect((id, reason) => {
|
||||
notifs.remove(id);
|
||||
resolved(id, reason);
|
||||
notify_property("notifications");
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -125,4 +127,3 @@ internal class DaemonProxy : Object {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user