feat(settings): add hooks to settings window

This commit is contained in:
Ly-sec
2026-05-07 13:02:17 +02:00
parent 25fc1eabff
commit db8d7cd497
4 changed files with 142 additions and 5 deletions
+68
View File
@@ -704,6 +704,7 @@
"appearance": "Appearance",
"desktop": "Desktop",
"dock": "Dock",
"hooks": "Hooks",
"panels": "Panels",
"notifications": "Notifications",
"backdrop": "Backdrop",
@@ -727,6 +728,7 @@
"focus-styling": "Focus Styling",
"general": "General",
"grouping": "Grouping",
"lifecycle": "Lifecycle",
"interface": "Interface",
"launcher": "Launcher",
"layout": "Layout",
@@ -739,6 +741,7 @@
"overview": "Overview",
"pinned-apps": "Pinned Apps",
"profile": "Profile",
"power": "Power",
"screen-corners": "Screen Corners",
"security": "Security",
"shape": "Shape",
@@ -1546,6 +1549,71 @@
"description": "Color temperature at night (Kelvin)"
}
},
"hooks": {
"events": {
"started": {
"label": "On Started",
"description": "Command to run after Noctalia starts"
},
"wallpaper_changed": {
"label": "On Wallpaper Changed",
"description": "Command to run when the wallpaper changes"
},
"colors_changed": {
"label": "On Colors Changed",
"description": "Command to run when the active color palette changes"
},
"session_locked": {
"label": "On Session Locked",
"description": "Command to run when the session is locked"
},
"session_unlocked": {
"label": "On Session Unlocked",
"description": "Command to run after the session is unlocked"
},
"logging_out": {
"label": "On Logout",
"description": "Command to run before logging out"
},
"rebooting": {
"label": "On Reboot",
"description": "Command to run before rebooting"
},
"shutting_down": {
"label": "On Shutdown",
"description": "Command to run before shutdown"
},
"wifi_enabled": {
"label": "On Wi-Fi Enabled",
"description": "Command to run when Wi-Fi is enabled"
},
"wifi_disabled": {
"label": "On Wi-Fi Disabled",
"description": "Command to run when Wi-Fi is disabled"
},
"bluetooth_enabled": {
"label": "On Bluetooth Enabled",
"description": "Command to run when Bluetooth is enabled"
},
"bluetooth_disabled": {
"label": "On Bluetooth Disabled",
"description": "Command to run when Bluetooth is disabled"
},
"battery_state_changed": {
"label": "On Battery State Changed",
"description": "Command to run when charging state changes"
},
"battery_under_threshold": {
"label": "On Battery Under Threshold",
"description": "Command to run when battery percent drops under the configured threshold"
}
},
"command-placeholder": "shell command or script path",
"battery-low-threshold": {
"label": "Battery Low Threshold",
"description": "Percent threshold for the low-battery hook (0 disables it)"
}
},
"notifications": {
"daemon": {
"label": "Notification Daemon",
+9 -5
View File
@@ -648,7 +648,8 @@ namespace settings {
return wrap;
};
const auto makeText = [&](const std::string& value, const std::string& placeholder, std::vector<std::string> path) {
const auto makeText = [&](const std::string& value, const std::string& placeholder, std::vector<std::string> path,
float width = 0.0f) {
auto input = std::make_unique<Input>();
input->setValue(value);
input->setPlaceholder(placeholder.empty() ? i18n::tr("settings.controls.list.add-entry-placeholder")
@@ -656,7 +657,8 @@ namespace settings {
input->setFontSize(Style::fontSizeBody * scale);
input->setControlHeight(Style::controlHeight * scale);
input->setHorizontalPadding(Style::spaceSm * scale);
input->setSize(190.0f * scale, Style::controlHeight * scale);
const float inputWidth = (width > 0.0f ? width : 190.0f) * scale;
input->setSize(inputWidth, Style::controlHeight * scale);
input->setOnSubmit([setOverride = ctx.setOverride, path](const std::string& v) { setOverride(path, v); });
return input;
};
@@ -1182,7 +1184,7 @@ namespace settings {
return makeSlider(control.value, control.minValue, control.maxValue, control.step, entry.path,
control.integerValue, control.linkedCommit);
} else if constexpr (std::is_same_v<T, TextSetting>) {
return makeText(control.value, control.placeholder, entry.path);
return makeText(control.value, control.placeholder, entry.path, control.width);
} else if constexpr (std::is_same_v<T, OptionalNumberSetting>) {
return makeOptionalNumber(control, entry.path);
} else if constexpr (std::is_same_v<T, ColorSetting>) {
@@ -1250,8 +1252,10 @@ namespace settings {
bool integerValue) -> std::unique_ptr<Node> {
return makeSlider(value, minValue, maxValue, step, std::move(path), integerValue);
},
.makeText = [&](const std::string& value, const std::string& placeholder, std::vector<std::string> path)
-> std::unique_ptr<Node> { return makeText(value, placeholder, std::move(path)); },
.makeText = [&](const std::string& value, const std::string& placeholder,
std::vector<std::string> path) -> std::unique_ptr<Node> {
return makeText(value, placeholder, std::move(path));
}, // width not used in search
.makeColorRolePicker = [&](const ColorRolePickerSetting& setting, std::vector<std::string> path)
-> std::unique_ptr<Node> { return makeColorRolePicker(setting, std::move(path)); },
.makeListBlock = [&](Flex& section, const SettingEntry& entry,
+64
View File
@@ -222,6 +222,8 @@ namespace settings {
return "layout-board";
if (section == "services")
return "stack-2";
if (section == "hooks")
return "link";
if (section == "notifications")
return "bell";
if (section == "bar")
@@ -750,6 +752,68 @@ namespace settings {
{"nightlight", "temperature_night"}, std::move(nightSlider), "wlsunset kelvin"));
}
// Hooks
auto hookGroup = [](HookKind kind) -> std::string {
switch (kind) {
case HookKind::Started:
case HookKind::SessionLocked:
case HookKind::SessionUnlocked:
case HookKind::LoggingOut:
case HookKind::Rebooting:
case HookKind::ShuttingDown:
return "lifecycle";
case HookKind::WallpaperChanged:
case HookKind::ColorsChanged:
return "theme";
case HookKind::WifiEnabled:
case HookKind::WifiDisabled:
case HookKind::BluetoothEnabled:
case HookKind::BluetoothDisabled:
return "network";
case HookKind::BatteryStateChanged:
case HookKind::BatteryUnderThreshold:
return "power";
case HookKind::Count:
break;
}
return "general";
};
auto hookTags = [](HookKind kind) -> std::string {
std::string tags = "hook command script exec event trigger";
if (kind == HookKind::BatteryUnderThreshold || kind == HookKind::BatteryStateChanged) {
tags += " battery power";
}
if (kind == HookKind::WallpaperChanged || kind == HookKind::ColorsChanged) {
tags += " wallpaper colors theme";
}
if (kind == HookKind::WifiEnabled || kind == HookKind::WifiDisabled || kind == HookKind::BluetoothEnabled ||
kind == HookKind::BluetoothDisabled) {
tags += " network wifi bluetooth";
}
if (kind == HookKind::SessionLocked || kind == HookKind::SessionUnlocked || kind == HookKind::LoggingOut ||
kind == HookKind::Rebooting || kind == HookKind::ShuttingDown || kind == HookKind::Started) {
tags += " session startup";
}
return tags;
};
for (const auto& kind : kHookKinds) {
const auto index = static_cast<std::size_t>(kind.value);
const std::string key(kind.key);
const std::string baseKey = "settings.schema.hooks.events." + key;
const std::string hookCmd = cfg.hooks.commands[index].empty() ? "" : cfg.hooks.commands[index][0];
entries.push_back(makeEntry(
"hooks", hookGroup(kind.value), tr(baseKey + ".label"), tr(baseKey + ".description"), {"hooks", key},
TextSetting{hookCmd, tr("settings.schema.hooks.command-placeholder"), 320.0f}, hookTags(kind.value)));
}
entries.push_back(makeEntry(
"hooks", "power", tr("settings.schema.hooks.battery-low-threshold.label"),
tr("settings.schema.hooks.battery-low-threshold.description"), {"hooks", "battery_low_percent_threshold"},
SliderSetting{static_cast<float>(cfg.hooks.batteryLowPercentThreshold), 0.0f, 100.0f, 1.0f, true},
"battery threshold hook trigger"));
// Notifications
entries.push_back(makeEntry("notifications", "general", tr("settings.schema.notifications.daemon.label"),
tr("settings.schema.notifications.daemon.description"),
+1
View File
@@ -59,6 +59,7 @@ namespace settings {
struct TextSetting {
std::string value;
std::string placeholder;
float width = 0.0f; // 0 = use default
};
struct OptionalNumberSetting {