Notification visibility (#106)

* Added config option

* Show notifications depending on state

* Updated json.in and jsonSchema

* Updated swaync(5) man file
This commit is contained in:
Erik Reider
2022-04-03 17:58:09 +02:00
committed by GitHub
parent a9edf6d360
commit 83a8a62ea9
5 changed files with 276 additions and 53 deletions

View File

@@ -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* ++

View File

@@ -21,5 +21,12 @@
"exec": "echo 'Do something...'",
"urgency": "Normal"
}
},
"notification-visibility": {
"example-name": {
"state": "muted",
"urgency": "Low",
"app-name": "Spotify"
}
}
}

View File

@@ -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<string, Category>(str_hash, str_equal);
}
/** Notification Status */
public HashTable<string, NotificationVisibility> notification_visibility {
get;
set;
default = new HashTable<string, NotificationVisibility>(str_hash, str_equal);
}
#if WANT_SCRIPTING
/** Scripts */
public HashTable<string, Script> scripts {
@@ -371,6 +434,15 @@ namespace SwayNotificationCenter {
out status);
value = result;
return status;
case "notification-visibility":
bool status;
HashTable<string, NotificationVisibility> result =
extract_hashtable<NotificationVisibility>(
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<string, Category>) value.get_boxed ();
node.set_object (serialize_hashtable<Category>(table));
break;
case "notification-visibility":
node = new Json.Node (Json.NodeType.OBJECT);
var table = (HashTable<string, NotificationVisibility>) value.get_boxed ();
node.set_object (serialize_hashtable<NotificationVisibility>(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);

View File

@@ -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."
}
}
}
}
}
}
}

View File

@@ -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) {