scripting: remove notification scripting system

parent 79ce0af9
......@@ -20,8 +20,6 @@ theme might require extra tweaks to the default CSS style file*
* [Configuring](#configuring)
* [Toggle Buttons](#toggle-buttons)
* [Notification Inhibition](#notification-inhibition)
* [Scripting](#scripting)
* [Disable scripting](#disable-scripting)
* [Waybar Example](#waybar-example)
* [Debugging Environment Variables](#debugging-environment-variables)
......@@ -218,70 +216,6 @@ exec_before=ximper-shell-notification-center-client --inhibitor-add "xdg-desktop
exec_after=ximper-shell-notification-center-client --inhibitor-remove "xdg-desktop-portal-wlr"
```
## Scripting
Scripting rules and logic:
. <b>Only one</b> script can be fired per notification
. Each script requires `exec` and at least one of the other properties
. All listed properties must match the notification for the script to be ran
. If any of the properties doesn't match, the script will be skipped
. If a notification doesn't include one of the properties, that property will
be skipped
· If a script has `run-on` set to `action`, the script will only run when an
action is taken on the notification
More information can be found in the `ximper-shell-notification-center(5)` man page
Notification information can be printed into a terminal by running
`G_MESSAGES_DEBUG=all ximper-shell-notification-center` (when a notification appears).
Config properties:
```jsonc
{
"scripts": {
"example-script": {
"exec": "Your shell command or script here...",
"app-name": "Notification app-name Regex",
"summary": "Notification summary Regex",
"body": "Notification body Regex",
"urgency": "Low or Normal or Critical",
"category": "Notification category Regex"
}
}
// other non scripting properties...
}
```
`config.json` example:
```jsonc
{
"scripts": {
// This script will only run when Spotify sends a notification containing
// that exact summary and body
"example-script": {
"exec": "/path/to/myRickRollScript.sh",
"app-name": "Spotify",
"summary": "Never Gonna Give You Up",
"body": "Rick Astley - Whenever You Need Somebody"
}
}
// other non scripting properties...
}
```
### Disable scripting
To completely disable scripting, the project needs to be built like so:
```zsh
meson build -Dscripting=false
ninja -C build
meson install -C build
```
## Waybar Example
This example requires a [Nerd Fonts](https://www.nerdfonts.com/) font to get the icons looking right
......
......@@ -816,97 +816,3 @@ config file to be able to detect config errors
}
}
```
#START scripting
# Scripts
*script-fail-notify* ++
type: bool ++
default: true ++
description: Sends a notification if a script fails to run
*scripts* ++
type: object ++
script object properties: ++
*exec*++
type: string ++
optional: false ++
description: The script to run. Can also run regular shell commands.++
*app-name*++
type: string ++
optional: true ++
description: The app-name. Uses Regex.++
*desktop-entry*++
type: string ++
optional: true ++
description: The desktop-entry. 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.++
*sound-file*++
type: string ++
optional: true ++
description: Which sound file the notification requested. Uses Regex.++
*sound-name*++
type: string ++
optional: true ++
description: Which sound name the notification requested. Uses Regex.++
*run-on*++
type: string ++
optional: true ++
values: action, receive ++
default: receive ++
description: Whether to run this action when the notification is ++
received, or when an action is taken on it. ++
description: Which scripts to check and potentially run for every ++
notification. If the notification doesn't include one of the properties, ++
that property will be ignored. All properties (except for exec) use regex. ++
If all properties match the given notification, the script will be run. ++
Only the first matching script will be run. ++
example:
```
{
"scripts": {
"example-script": {
"exec": "Your shell command or script here...",
"app-name": "Notification app-name Regex",
"summary": "Notification summary Regex",
"body": "Notification body Regex",
"urgency": "Low or Normal or Critical",
"category": "Notification category Regex"
}
}
}
```
You can also use these environment variables in your script:
```
$XSNC_BODY="Notification body content"
$XSNC_DESKTOP_ENTRY="Desktop entry"
$XSNC_URGENCY="Notification urgency"
$XSNC_TIME="Notification time"
$XSNC_APP_NAME="Notification app name"
$XSNC_CATEGORY="Notification category"
$XSNC_REPLACES_ID="ID of notification to replace"
$XSNC_ID="Notification ID"
$XSNC_SUMMARY="Notification summary"
$XSNC_HINT_[NAME]="Value of the hint [NAME]"
$XSNC_SOUND_NAME="The name of the requested sound"
$XSNC_SOUND_FILE="The file path of the requested sound"
```
#END scripting
......@@ -126,7 +126,7 @@ if get_option('man-pages')
# Remove parts of man page if necessary
sed_command = []
foreach option : ['pulse-audio', 'scripting']
foreach option : ['pulse-audio']
if get_option(option) == false
# Removes all lines between the #START and #END lines (inclusive)
sed_command += ['-e', '/#START @0@/,/#END @0@/d'.format(option)]
......
option('systemd-service', type: 'boolean', value: true, description: 'Install systemd user service unit.')
option('scripting', type: 'boolean', value: true, description: 'Enable notification scripting.')
option('pulse-audio', type: 'boolean', value: true, description: 'Provide PulseAudio Widget.')
option('ddc', type: 'boolean', value: true, description: 'Provide DDC/CI monitor brightness control via libddcutil.')
option('man-pages', type: 'boolean', value: true, description: 'Install all man pages.')
......
......@@ -31,20 +31,6 @@
"hide-on-clear": false,
"hide-on-action": true,
"text-empty": "No Notifications",
"script-fail-notify": true,
"scripts": {
"example-script": {
"app-name": "example.app.id",
"exec": "echo 'Do something...'",
"urgency": "Normal"
},
"example-action-script": {
"app-name": "example.app.id",
"exec": "echo 'Do something actionable!'",
"urgency": "Normal",
"run-on": "action"
}
},
"notification-visibility": {
"example-name": {
"state": "muted",
......
......@@ -435,50 +435,6 @@ namespace XimperShellNotificationCenter {
}
}
public enum ScriptRunOnType {
ACTION,
RECEIVE;
}
#if WANT_SCRIPTING
public class Script : NotificationMatching {
public string ?exec { get; set; default = null; }
public ScriptRunOnType run_on { get; set; default = ScriptRunOnType.RECEIVE; }
public async bool run_script (NotifyParams param, out string msg) {
string[] spawn_env = {};
spawn_env += "XSNC_APP_NAME=%s".printf (param.app_name);
spawn_env += "XSNC_SUMMARY=%s".printf (param.summary);
spawn_env += "XSNC_BODY=%s".printf (param.body);
spawn_env += "XSNC_URGENCY=%s".printf (param.urgency.to_string ());
spawn_env += "XSNC_CATEGORY=%s".printf (param.category);
spawn_env += "XSNC_SOUND_NAME=%s".printf (param.sound_name);
spawn_env += "XSNC_SOUND_FILE=%s".printf (param.sound_file);
spawn_env += "XSNC_ID=%s".printf (param.applied_id.to_string ());
spawn_env += "XSNC_REPLACES_ID=%s".printf (param.replaces_id.to_string ());
spawn_env += "XSNC_TIME=%s".printf (param.time.to_string ());
spawn_env += "XSNC_DESKTOP_ENTRY=%s".printf (param.desktop_entry ?? "");
foreach (string hint in param.hints.get_keys ()) {
if (hint.contains ("image") || hint.contains ("icon")) {
continue;
}
spawn_env += "XSNC_HINT_%s=%s".printf (
hint.up ().replace ("-", "_"),
param.hints[hint].print (false));
}
return yield Functions.execute_command (exec, spawn_env, out msg);
}
public override bool matches_notification (NotifyParams param) {
if (exec == null) {
return false;
}
return base.matches_notification (param);
}
}
#endif
public class ConfigModel : Object, Json.Serializable {
private static ConfigModel ?_instance = null;
private static string _path = "";
......@@ -756,18 +712,6 @@ namespace XimperShellNotificationCenter {
default = new OrderedHashTable<NotificationVisibility> ();
}
#if WANT_SCRIPTING
/** Scripts */
public OrderedHashTable<Script> scripts {
get;
set;
default = new OrderedHashTable<Script> ();
}
/** Show notification if script fails */
public bool script_fail_notify { get; set; default = true; }
#endif
/** Whether to expand the notification center across both edges of the screen */
public bool fit_to_screen { get; set; default = true; }
......@@ -931,17 +875,6 @@ namespace XimperShellNotificationCenter {
out status);
value = result;
return status;
#if WANT_SCRIPTING
case "scripts":
bool status;
OrderedHashTable<Script> result =
extract_hashtable<Script> (
property_name,
property_node,
out status);
value = result;
return status;
#endif
case "widgets":
bool status;
GenericArray<string> result =
......@@ -1016,13 +949,6 @@ namespace XimperShellNotificationCenter {
var table = (OrderedHashTable<NotificationVisibility>) value;
node.set_object (serialize_hashtable<NotificationVisibility> (table));
break;
#if WANT_SCRIPTING
case "scripts":
node = new Json.Node (Json.NodeType.OBJECT);
var table = (OrderedHashTable<Script>) value;
node.set_object (serialize_hashtable<Script> (table));
break;
#endif
case "widgets":
node = new Json.Node (Json.NodeType.ARRAY);
var table = (GenericArray<string>) value;
......
......@@ -209,72 +209,6 @@
"description": "Text that appears when there are no notifications to show",
"default": "No Notifications"
},
"script-fail-notify": {
"type": "boolean",
"description": "Sends a notification if a script fails to run",
"default": true
},
"scripts": {
"type": "object",
"description": "Which scripts to check and potentially run for every notification. If the notification doesn't include one of the properties, that property will be ignored. All properties (except for exec) use regex. If all properties match the given notification, the script will be run. Only the first matching script will be run.",
"minProperties": 1,
"additionalProperties": false,
"patternProperties": {
"^.{1,}$": {
"type": "object",
"description": "Your script object.",
"required": ["exec"],
"minProperties": 2,
"additionalProperties": false,
"properties": {
"exec": {
"type": "string",
"description": "The script to run. Can also run regular shell commands."
},
"app-name": {
"type": "string",
"description": "The app-name. Uses Regex."
},
"desktop-entry": {
"type": "string",
"description": "The desktop-entry. 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."
},
"sound-file": {
"type": "string",
"description": "Which sound file the notification requested. Uses Regex."
},
"sound-name": {
"type": "string",
"description": "Which sound name the notification requested. Uses Regex."
},
"run-on": {
"type": "string",
"description": "Whether to run the script on an action being activated, or when the notification is received.",
"enum": ["action", "receive"],
"default": "receive"
}
}
}
}
},
"notification-action-filter": {
"type": "object",
"description": "Hides matching action(s) of matching notifications. If the notification doesn't include one of the properties, that property will be ignored. If all properties match the given notification, the matching actions will be hidden.",
......
......@@ -98,11 +98,6 @@ app_deps = [
protocol_dep,
]
# Checks if the user wants scripting enabled
if get_option('scripting')
add_project_arguments('-D', 'WANT_SCRIPTING', language: 'vala')
endif
# Checks if the user wants to compile with the PulseAudio dependency
if get_option('pulse-audio')
add_project_arguments('-D', 'HAVE_PULSE_AUDIO', language: 'vala')
......
......@@ -289,89 +289,11 @@ namespace XimperShellNotificationCenter {
ximper_shell_notification_center_daemon.emit_subscribe ();
}
#if WANT_SCRIPTING
if (param.ximper_shell_notification_center_no_script) {
debug ("Skipped scripts for this notification\n");
return id;
}
// Run the first script if notification meets requirements
OrderedHashTable<Script> scripts = ConfigModel.instance.scripts;
if (scripts.length == 0) {
return id;
}
this.run_scripts (param, ScriptRunOnType.RECEIVE);
#endif
debug ("\n");
return id;
}
/**
* Runs scripts that meet the requirements of the given `param`.
*/
internal void run_scripts (NotifyParams param, ScriptRunOnType run_on) {
#if WANT_SCRIPTING
if (param.ximper_shell_notification_center_no_script) {
debug ("Skipped action scripts for this notification\n");
return;
}
// Run the first script if notification meets requirements
OrderedHashTable<Script> scripts = ConfigModel.instance.scripts;
if (scripts.length == 0) {
return;
}
foreach (string key in scripts.get_keys ()) {
unowned Script script = scripts[key];
if (!script.matches_notification (param)) {
continue;
}
if (script.run_on != run_on) {
continue;
}
script.run_script.begin (param, (obj, res) => {
// Gets the end status
string error_msg;
if (script.run_script.end (res, out error_msg)) {
return;
}
if (!ConfigModel.instance.script_fail_notify) {
stderr.printf (
"Failed to run script: \"%s\" with exec: \"%s\"\n",
key, script.exec);
} else {
// Send notification with error message
try {
var _hints = new HashTable<string, Variant> (
str_hash,
str_equal);
// Disable scripts for this notification
_hints.insert ("XSNC_NO_SCRIPT", true);
_hints.insert ("urgency",
UrgencyLevels.CRITICAL.to_byte ());
string _summary = "Failed to run script: %s".printf (key);
string _body = "<b>Output:</b> " + error_msg;
this.new_notification ("XimperShellNotificationCenter",
0,
"dialog-error",
_summary,
_body,
{},
_hints,
-1);
} catch (Error e) {
stderr.printf ("NOTIFING SCRIPT-FAIL ERROR: %s\n",
e.message);
}
}
});
break;
}
#endif
}
/**
* Causes a notification to be forcefully closed and removed from the
* user's view. It can be used, for example, in the event that what
* the notification pertains to is no longer relevant, or to cancel a
......
......@@ -152,9 +152,6 @@ namespace XimperShellNotificationCenter {
public string ?inline_reply_placeholder { get; private set; }
// Custom hints
/** Disables scripting for notification */
public bool ximper_shell_notification_center_no_script { get; private set; }
/** Always show the notification, regardless of dnd/inhibit state */
public bool ximper_shell_notification_center_bypass_dnd { get; private set; }
......@@ -247,13 +244,6 @@ namespace XimperShellNotificationCenter {
foreach (var hint in hints.get_keys ()) {
Variant hint_value = hints[hint];
switch (hint) {
case "XSNC_NO_SCRIPT" :
if (hint_value.is_of_type (VariantType.INT32)) {
ximper_shell_notification_center_no_script = hint_value.get_int32 () == 1;
} else if (hint_value.is_of_type (VariantType.BOOLEAN)) {
ximper_shell_notification_center_no_script = hint_value.get_boolean ();
}
break;
case "XSNC_BYPASS_DND" :
if (hint_value.is_of_type (VariantType.INT32)) {
ximper_shell_notification_center_bypass_dnd = hint_value.get_int32 () == 1;
......
......@@ -459,7 +459,6 @@ namespace XimperShellNotificationCenter {
}
private void action_clicked (Action ?action) {
noti_daemon.run_scripts (param, ScriptRunOnType.ACTION);
if (action != null
&& action.identifier != null
&& action.identifier != "") {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment