diff --git a/assets/translations/en.json b/assets/translations/en.json index 9fbbf01cc..8d9a4c2fe 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -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", diff --git a/src/shell/settings/settings_content.cpp b/src/shell/settings/settings_content.cpp index 323d15486..e360a7543 100644 --- a/src/shell/settings/settings_content.cpp +++ b/src/shell/settings/settings_content.cpp @@ -648,7 +648,8 @@ namespace settings { return wrap; }; - const auto makeText = [&](const std::string& value, const std::string& placeholder, std::vector path) { + const auto makeText = [&](const std::string& value, const std::string& placeholder, std::vector path, + float width = 0.0f) { auto input = std::make_unique(); 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) { - 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) { return makeOptionalNumber(control, entry.path); } else if constexpr (std::is_same_v) { @@ -1250,8 +1252,10 @@ namespace settings { bool integerValue) -> std::unique_ptr { return makeSlider(value, minValue, maxValue, step, std::move(path), integerValue); }, - .makeText = [&](const std::string& value, const std::string& placeholder, std::vector path) - -> std::unique_ptr { return makeText(value, placeholder, std::move(path)); }, + .makeText = [&](const std::string& value, const std::string& placeholder, + std::vector path) -> std::unique_ptr { + return makeText(value, placeholder, std::move(path)); + }, // width not used in search .makeColorRolePicker = [&](const ColorRolePickerSetting& setting, std::vector path) -> std::unique_ptr { return makeColorRolePicker(setting, std::move(path)); }, .makeListBlock = [&](Flex& section, const SettingEntry& entry, diff --git a/src/shell/settings/settings_registry.cpp b/src/shell/settings/settings_registry.cpp index 6615abd28..1048b4cd9 100644 --- a/src/shell/settings/settings_registry.cpp +++ b/src/shell/settings/settings_registry.cpp @@ -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(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(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"), diff --git a/src/shell/settings/settings_registry.h b/src/shell/settings/settings_registry.h index dbd0a88f7..d28cbec7e 100644 --- a/src/shell/settings/settings_registry.h +++ b/src/shell/settings/settings_registry.h @@ -59,6 +59,7 @@ namespace settings { struct TextSetting { std::string value; std::string placeholder; + float width = 0.0f; // 0 = use default }; struct OptionalNumberSetting {