diff --git a/man/swaync.5.scd b/man/swaync.5.scd index 0188db1..505408b 100644 --- a/man/swaync.5.scd +++ b/man/swaync.5.scd @@ -87,6 +87,59 @@ config file to be able to detect config errors default: true ++ description: Hides the control center when clicking on notification action +*notification-visibility* ++ + type: object ++ + visibility object properties: ++ + *state*++ + type: string ++ + optional: false ++ + default: enabled ++ + values: ignored, muted, enabled ++ + description: The notification visibility state. ++ + *app-name*++ + type: string ++ + optional: true ++ + description: The app-name. Uses Regex.++ + *summary*++ + type: string ++ + optional: true ++ + description: The summary of the notification. Uses Regex.++ + *body*++ + type: string ++ + optional: true ++ + description: The body of the notification. Uses Regex.++ + *urgency*++ + type: string ++ + optional: true ++ + default: Normal ++ + values: Low, Normal, Critical ++ + description: The urgency of the notification.++ + *category*++ + type: string ++ + optional: true ++ + description: Which category the notification belongs to. Uses Regex.++ + description: Set the visibility of each incoming notification. ++ + If the notification doesn't include one of the properites, that ++ + property will be ignored. All properties (except for state) use ++ + regex. If all properties match the given notification, the ++ + notification will be follow the provided state. ++ + Only the first matching object will be used. ++ + example: +``` +{ + "notification-visibility": { + "example-name": { + "state": "The notification state", + "app-name": "Notification app-name Regex", + "summary": "Notification summary Regex", + "body": "Notification body Regex", + "urgency": "Low or Normal or Critical", + "category": "Notification category Regex" + } + } +} +``` + # IF BUILT WITH SCRIPTING *script-fail-notify* ++ diff --git a/src/config.json.in b/src/config.json.in index 1504777..5991faf 100644 --- a/src/config.json.in +++ b/src/config.json.in @@ -21,5 +21,12 @@ "exec": "echo 'Do something...'", "urgency": "Normal" } + }, + "notification-visibility": { + "example-name": { + "state": "muted", + "urgency": "Low", + "app-name": "Spotify" + } } } diff --git a/src/configModel/configModel.vala b/src/configModel/configModel.vala index de5f1ce..c730faa 100644 --- a/src/configModel/configModel.vala +++ b/src/configModel/configModel.vala @@ -54,60 +54,14 @@ namespace SwayNotificationCenter { } } -#if WANT_SCRIPTING - public class Script : Object { - public string ? exec { get; set; default = null; } - + public class NotificationMatching : Object, Json.Serializable { public string ? app_name { get; set; default = null; } public string ? summary { get; set; default = null; } public string ? body { get; set; default = null; } public string ? urgency { get; set; default = null; } public string ? category { get; set; default = null; } - public async bool run_script (NotifyParams param, out string msg) { - msg = ""; - try { - string[] spawn_env = Environ.get (); - // Export env variables - spawn_env += @"SWAYNC_APP_NAME=" + param.app_name; - spawn_env += @"SWAYNC_SUMMARY=" + param.summary; - spawn_env += @"SWAYNC_BODY=" + param.body; - spawn_env += @"SWAYNC_URGENCY=" + param.urgency.to_string (); - spawn_env += @"SWAYNC_CATEGORY=" + param.category; - spawn_env += @"SWAYNC_ID=" + param.applied_id.to_string (); - spawn_env += @"SWAYNC_REPLACES_ID=" + param.replaces_id.to_string (); - spawn_env += @"SWAYNC_TIME=" + param.time.to_string (); - spawn_env += @"SWAYNC_DESKTOP_ENTRY=" + param.desktop_entry ?? ""; - - Pid child_pid; - Process.spawn_async ( - "/", - exec.split (" "), - spawn_env, - SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD, - null, - out child_pid); - - // Close the child when the spawned process is idling - int end_status = 0; - ChildWatch.add (child_pid, (pid, status) => { - Process.close_pid (pid); - end_status = status; - run_script.callback (); - }); - // Waits until `run_script.callback()` is called above - yield; - return end_status == 0; - } catch (Error e) { - stderr.printf ("Run_Script Error: %s\n", e.message); - msg = e.message; - return false; - } - } - - public bool matches_notification (NotifyParams param) { - if (exec == null) return false; - + public virtual bool matches_notification (NotifyParams param) { if (app_name != null) { if (param.app_name == null) return false; bool result = Regex.match_simple ( @@ -159,6 +113,108 @@ namespace SwayNotificationCenter { if (category != null) fields += @"category: $category"; return string.joinv (", ", fields); } + + public override Json.Node serialize_property (string property_name, + Value value, + ParamSpec pspec) { + // Return enum nickname instead of enum int value + if (value.type ().is_a (Type.ENUM)) { + var node = new Json.Node (Json.NodeType.VALUE); + EnumClass enumc = (EnumClass) value.type ().class_ref (); + unowned EnumValue ? eval + = enumc.get_value (value.get_enum ()); + if (eval == null) { + node.set_value (value); + return node; + } + node.set_string (eval.value_nick); + return node; + } + return default_serialize_property (property_name, value, pspec); + } + } + + public enum NotificationStatusEnum { + ENABLED, + MUTED, + IGNORED; + + public string to_string () { + switch (this) { + default : + return "enabled"; + case MUTED: + return "muted"; + case IGNORED: + return "ignored"; + } + } + + public static NotificationStatusEnum from_value (string value) { + switch (value) { + default: + return ENABLED; + case "muted": + return MUTED; + case "ignored": + return IGNORED; + } + } + } + + public class NotificationVisibility : NotificationMatching { + public NotificationStatusEnum state { get; set; } + } + +#if WANT_SCRIPTING + public class Script : NotificationMatching { + public string ? exec { get; set; default = null; } + + public async bool run_script (NotifyParams param, out string msg) { + msg = ""; + try { + string[] spawn_env = Environ.get (); + // Export env variables + spawn_env += @"SWAYNC_APP_NAME=" + param.app_name; + spawn_env += @"SWAYNC_SUMMARY=" + param.summary; + spawn_env += @"SWAYNC_BODY=" + param.body; + spawn_env += @"SWAYNC_URGENCY=" + param.urgency.to_string (); + spawn_env += @"SWAYNC_CATEGORY=" + param.category; + spawn_env += @"SWAYNC_ID=" + param.applied_id.to_string (); + spawn_env += @"SWAYNC_REPLACES_ID=" + param.replaces_id.to_string (); + spawn_env += @"SWAYNC_TIME=" + param.time.to_string (); + spawn_env += @"SWAYNC_DESKTOP_ENTRY=" + param.desktop_entry ?? ""; + + Pid child_pid; + Process.spawn_async ( + "/", + exec.split (" "), + spawn_env, + SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD, + null, + out child_pid); + + // Close the child when the spawned process is idling + int end_status = 0; + ChildWatch.add (child_pid, (pid, status) => { + Process.close_pid (pid); + end_status = status; + run_script.callback (); + }); + // Waits until `run_script.callback()` is called above + yield; + return end_status == 0; + } catch (Error e) { + stderr.printf ("Run_Script Error: %s\n", e.message); + msg = e.message; + return false; + } + } + + public override bool matches_notification (NotifyParams param) { + if (exec == null) return false; + return base.matches_notification (param); + } } #endif @@ -339,6 +395,13 @@ namespace SwayNotificationCenter { default = new HashTable(str_hash, str_equal); } + /** Notification Status */ + public HashTable notification_visibility { + get; + set; + default = new HashTable(str_hash, str_equal); + } + #if WANT_SCRIPTING /** Scripts */ public HashTable scripts { @@ -371,6 +434,15 @@ namespace SwayNotificationCenter { out status); value = result; return status; + case "notification-visibility": + bool status; + HashTable result = + extract_hashtable( + property_name, + property_node, + out status); + value = result; + return status; #if WANT_SCRIPTING case "scripts": bool status; @@ -412,6 +484,11 @@ namespace SwayNotificationCenter { var table = (HashTable) value.get_boxed (); node.set_object (serialize_hashtable(table)); break; + case "notification-visibility": + node = new Json.Node (Json.NodeType.OBJECT); + var table = (HashTable) value.get_boxed (); + node.set_object (serialize_hashtable(table)); + break; #if WANT_SCRIPTING case "scripts": node = new Json.Node (Json.NodeType.OBJECT); @@ -485,10 +562,36 @@ namespace SwayNotificationCenter { if (object == null) break; // Creates a new GLib.Object with all of the properties of T - Object obj = Object.new (typeof (T)); + Type type = typeof (T); + ObjectClass ocl = (ObjectClass) type.class_ref (); + Object obj = Object.new (type); foreach (var name in object.get_members ()) { Value value = object.get_member (name).get_value (); - obj.set_property (name, value); + + unowned ParamSpec value_spec = null; + foreach (var spec in ocl.list_properties ()) { + if (spec.name == name) { + value_spec = spec; + break; + } + } + if (value_spec == null) continue; + + unowned Type spec_type = value_spec.value_type; + unowned Type val_type = value.type (); + if (spec_type.is_a (val_type)) { + // Both are the same type + obj.set_property (name, value); + } else if (spec_type.is_a (Type.ENUM) + && val_type.is_a (Type.STRING)) { + // Set enum from string + EnumClass enumc = (EnumClass) spec_type.class_ref (); + EnumValue ? eval + = enumc.get_value_by_nick (value.get_string ()); + if (eval != null) { + obj.set_property (name, eval.value); + } + } } tmp_table.insert (key, (T) obj); @@ -511,7 +614,7 @@ namespace SwayNotificationCenter { Type generic_type = Functions.get_base_type (typeof (T)); switch (generic_type) { - case Type.STRING : + case Type.STRING: string ? casted = (string) item; if (casted != null) { json_object.set_string_member (key, casted); diff --git a/src/configSchema.json b/src/configSchema.json index df35d65..e596ad5 100644 --- a/src/configSchema.json +++ b/src/configSchema.json @@ -132,6 +132,51 @@ } } } + }, + "notification-visibility": { + "type": "object", + "description": "Set the visibility of each incoming notification. If the notification doesn't include one of the properites, that property will be ignored. All properties (except for state) use regex. If all properties match the given notification, the notification will be follow the provided state. Only the first matching object will be used.", + "minProperties": 1, + "additionalProperties": false, + "patternProperties": { + "^.{1,}$": { + "type": "object", + "description": "Your script object.", + "required": ["state"], + "minProperties": 2, + "additionalProperties": false, + "properties": { + "state": { + "type": "string", + "description": "The notification visibility state.", + "default": "enabled", + "enum": ["ignored", "muted", "enabled"] + }, + "app-name": { + "type": "string", + "description": "The app-name. Uses Regex." + }, + "summary": { + "type": "string", + "description": "The summary of the notification. Uses Regex." + }, + "body": { + "type": "string", + "description": "The body of the notification. Uses Regex." + }, + "urgency": { + "type": "string", + "description": "The urgency of the notification.", + "default": "Normal", + "enum": ["Low", "Normal", "Critical"] + }, + "category": { + "type": "string", + "description": "Which category the notification belongs to. Uses Regex." + } + } + } + } } } } diff --git a/src/notiDaemon/notiDaemon.vala b/src/notiDaemon/notiDaemon.vala index 5d7c382..59a78ec 100644 --- a/src/notiDaemon/notiDaemon.vala +++ b/src/notiDaemon/notiDaemon.vala @@ -141,6 +141,16 @@ namespace SwayNotificationCenter { hints, expire_timeout); + // The notification visibility state + NotificationStatusEnum state = NotificationStatusEnum.ENABLED; + var visibilities = ConfigModel.instance.notification_visibility; + foreach (string key in visibilities.get_keys ()) { + unowned NotificationVisibility vis = visibilities[key]; + if (!vis.matches_notification (param)) continue; + state = vis.state; + break; + } + debug ("Notification: %s\n", param.to_string ()); // Replace notification logic @@ -163,13 +173,18 @@ namespace SwayNotificationCenter { synchronous_ids.set (param.synchronous, id); } - if (!ccDaemon.controlCenter.get_visibility ()) { + // Only show popup notification if it is ENABLED + if (state == NotificationStatusEnum.ENABLED + && !ccDaemon.controlCenter.get_visibility ()) { if (param.urgency == UrgencyLevels.CRITICAL || (!dnd && param.urgency != UrgencyLevels.CRITICAL)) { notiWindow.add_notification (param, this); } } - ccDaemon.controlCenter.add_notification (param, this); + // Only add notification to CC if it isn't IGNORED + if (state != NotificationStatusEnum.IGNORED) { + ccDaemon.controlCenter.add_notification (param, this); + } #if WANT_SCRIPTING if (param.swaync_no_script) {