Created Hook system (let's users run commands after specific actions)

NInputAction: create NTextInput with NButton
HooksService: add dark/light mode hook, add wallpaper change hook
HooksTab: create 1 NInputAction for each hook
Wallpaper: add hook functionallity
This commit is contained in:
Ly-sec 2025-09-04 17:54:58 +02:00
parent d53a404bf1
commit 37eefe3663
9 changed files with 274 additions and 3 deletions

View file

@ -335,6 +335,13 @@ Singleton {
property string manualSunrise: "06:30"
property string manualSunset: "18:30"
}
// hooks
property JsonObject hooks: JsonObject {
property bool enabled: false
property string wallpaperChange: ""
property string darkModeChange: ""
}
}
}
}

View file

@ -68,7 +68,9 @@ Variants {
// Make the overview darker
Rectangle {
anchors.fill: parent
color: Settings.data.colorSchemes.darkMode ? Qt.alpha(Color.mSurface, Style.opacityMedium) : Qt.alpha(Color.mOnSurface, Style.opacityMedium)
color: Settings.data.colorSchemes.darkMode ? Qt.alpha(Color.mSurface,
Style.opacityMedium) : Qt.alpha(Color.mOnSurface,
Style.opacityMedium)
}
}
}

View file

@ -31,6 +31,7 @@ NPanel {
About,
Audio,
Bar,
Hooks,
Launcher,
Brightness,
ColorScheme,
@ -111,6 +112,10 @@ NPanel {
id: aboutTab
Tabs.AboutTab {}
}
Component {
id: hooksTab
Tabs.HooksTab {}
}
// Order *DOES* matter
function updateTabsModel() {
@ -181,6 +186,11 @@ NPanel {
"label": "Screen Recorder",
"icon": "videocam",
"source": screenRecorderTab
}, {
"id": SettingsPanel.Tab.Hooks,
"label": "Hooks",
"icon": "link",
"source": hooksTab
}, {
"id": SettingsPanel.Tab.About,
"label": "About",

View file

@ -0,0 +1,104 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Services
import qs.Widgets
ScrollView {
id: root
property real scaling: 1.0
contentWidth: contentColumn.width
contentHeight: contentColumn.height
ColumnLayout {
id: contentColumn
spacing: Style.marginL * scaling
width: root.width
// Enable/Disable Toggle
NToggle {
label: "Enable Hooks"
description: "Enable or disable all hook commands."
checked: Settings.data.hooks.enabled
onToggled: checked => Settings.data.hooks.enabled = checked
}
ColumnLayout {
visible: Settings.data.hooks.enabled
spacing: Style.marginL * scaling
Layout.fillWidth: true
NDivider {
Layout.fillWidth: true
}
// Wallpaper Hook Section
NInputAction {
id: wallpaperHookInput
label: "Wallpaper Change Hook"
description: "Command to be executed when wallpaper changes."
placeholderText: "e.g., notify-send \"Wallpaper\" \"Changed\""
text: Settings.data.hooks.wallpaperChange
onEditingFinished: {
Settings.data.hooks.wallpaperChange = wallpaperHookInput.text
}
onActionClicked: {
if (wallpaperHookInput.text) {
HooksService.executeWallpaperHook("test")
}
}
Layout.fillWidth: true
}
NDivider {
Layout.fillWidth: true
}
// Dark Mode Hook Section
NInputAction {
id: darkModeHookInput
label: "Theme Toggle Hook"
description: "Command to be executed when theme toggles between dark and light mode."
placeholderText: "e.g., notify-send \"Theme\" \"Toggled\""
text: Settings.data.hooks.darkModeChange
onEditingFinished: {
Settings.data.hooks.darkModeChange = darkModeHookInput.text
}
onActionClicked: {
if (darkModeHookInput.text) {
HooksService.executeDarkModeHook(Settings.data.colorSchemes.darkMode)
}
}
Layout.fillWidth: true
}
NDivider {
Layout.fillWidth: true
}
// Info section
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
NText {
text: "Hook Command Information"
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: "• Commands are executed via shell (sh -c)\n• Commands run in background (detached)\n• Test buttons execute with current values"
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.Wrap
Layout.fillWidth: true
}
}
}
}
}

63
Services/HooksService.qml Normal file
View file

@ -0,0 +1,63 @@
pragma Singleton
import QtQuick
import Quickshell
import qs.Commons
import qs.Services
Singleton {
id: root
// Hook connections for automatic script execution
Connections {
target: Settings.data.colorSchemes
function onDarkModeChanged() {
executeDarkModeHook(Settings.data.colorSchemes.darkMode)
}
}
// Execute wallpaper change hook
function executeWallpaperHook(wallpaperPath) {
if (!Settings.data.hooks?.enabled) {
return
}
const script = Settings.data.hooks?.wallpaperChange
if (!script || script === "") {
return
}
try {
const command = script.replace(/\$1/g, wallpaperPath)
Quickshell.execDetached(["sh", "-c", command])
Logger.log("HooksService", `Executed wallpaper hook: ${command}`)
} catch (e) {
Logger.error("HooksService", `Failed to execute wallpaper hook: ${e}`)
}
}
// Execute dark mode change hook
function executeDarkModeHook(isDarkMode) {
if (!Settings.data.hooks?.enabled) {
return
}
const script = Settings.data.hooks?.darkModeChange
if (!script || script === "") {
return
}
try {
const command = script.replace(/\$1/g, isDarkMode ? "true" : "false")
Quickshell.execDetached(["sh", "-c", command])
Logger.log("HooksService", `Executed dark mode hook: ${command}`)
} catch (e) {
Logger.error("HooksService", `Failed to execute dark mode hook: ${e}`)
}
}
// Initialize the service
function init() {
Logger.log("HooksService", "Service initialized")
}
}

View file

@ -256,6 +256,11 @@ Singleton {
// Update cache directly
currentWallpapers[screenName] = path
// Execute wallpaper change hook
if (HooksService) {
HooksService.executeWallpaperHook(path)
}
// Update Settings - still need immutable update for Settings persistence
// The slice() ensures Settings detects the change and saves properly
var monitors = Settings.data.wallpaper.monitors || []

View file

@ -152,7 +152,7 @@ Rectangle {
target: ripple
property: "opacity"
from: 0
to: 0.2
to: 0.05
duration: 100
easing.type: Easing.OutCubic
}
@ -160,7 +160,7 @@ Rectangle {
NumberAnimation {
target: ripple
property: "opacity"
from: 0.2
from: 0.05
to: 0
duration: 300
easing.type: Easing.InCubic

77
Widgets/NInputAction.qml Normal file
View file

@ -0,0 +1,77 @@
import QtQuick
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
ColumnLayout {
id: root
// Public properties
property string label: ""
property string description: ""
property string placeholderText: ""
property string text: ""
property string actionButtonText: "Test"
property string actionButtonIcon: "play_arrow"
property bool actionButtonEnabled: text !== ""
// Signals
signal editingFinished
signal actionClicked
// Internal properties
property real scaling: 1.0
// Label
NText {
text: root.label
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true
}
// Description
NText {
text: root.description
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.Wrap
Layout.fillWidth: true
}
// Input and button row
RowLayout {
spacing: Style.marginM * scaling
Layout.fillWidth: true
NTextInput {
id: textInput
placeholderText: root.placeholderText
text: root.text
onEditingFinished: {
root.text = text
root.editingFinished()
}
Layout.fillWidth: true
}
Item {
Layout.fillWidth: true
}
NButton {
text: root.actionButtonText
icon: root.actionButtonIcon
backgroundColor: Color.mSecondary
textColor: Color.mOnSecondary
hoverColor: Color.mTertiary
pressColor: Color.mPrimary
enabled: root.actionButtonEnabled
Layout.fillWidth: false
onClicked: {
root.actionClicked()
}
}
}
}

View file

@ -110,6 +110,9 @@ ShellRoot {
// Initialize UpdateService
UpdateService.init()
// Initialize HooksService
HooksService.init()
// Kickoff NightLight service
NightLightService.apply()
}