Add .config/DankMaterialShell/firefox.css

Add .config/DankMaterialShell/plugin_settings.json
Add .config/DankMaterialShell/plugins/dankDesktopWeather.meta
Add .config/DankMaterialShell/plugins/dankHooks.meta
Add .config/DankMaterialShell/plugins/desktopCommand/LICENSE
Add .config/DankMaterialShell/plugins/desktopCommand/README.md
Add .config/DankMaterialShell/plugins/desktopCommand/Settings.qml
Add .config/DankMaterialShell/plugins/desktopCommand/Widget.qml
Add .config/DankMaterialShell/plugins/desktopCommand/assets/screenshot.jpg
Add .config/DankMaterialShell/plugins/desktopCommand/.git/HEAD
Add .config/DankMaterialShell/plugins/desktopCommand/.git/config
Add .config/DankMaterialShell/plugins/desktopCommand/.git/index
Add .config/DankMaterialShell/plugins/desktopCommand/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/desktopCommand/.git/objects/pack/pack-c2ca48eacecc3ab45931476641d058a89d755775.idx
Add .config/DankMaterialShell/plugins/desktopCommand/.git/objects/pack/pack-c2ca48eacecc3ab45931476641d058a89d755775.rev
Add .config/DankMaterialShell/plugins/desktopCommand/.git/objects/pack/pack-c2ca48eacecc3ab45931476641d058a89d755775.pack
Add .config/DankMaterialShell/plugins/desktopCommand/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/desktopCommand/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/desktopCommand/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/desktopCommand/.gitignore
Add .config/DankMaterialShell/plugins/desktopCommand/wrapCommand
Add .config/DankMaterialShell/plugins/desktopCommand/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankActions/DankActionsSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankActions/DankActionsWidget.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankActions/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankBatteryAlerts/DankBatteryAlerts.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankBatteryAlerts/DankBatteryAlertsSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankBatteryAlerts/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankDesktopWeather/DankDesktopWeather.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankDesktopWeather/DankDesktopWeatherSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankDesktopWeather/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/DankHooks.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/DankHooksSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/README.md
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankPomodoroTimer/DankPomodoroSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankPomodoroTimer/DankPomodoroWidget.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankPomodoroTimer/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/LICENSE
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/README.md
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/HEAD
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/config
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/index
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-3221a15c022ef4a7bb6bf2c47e40068b66b3588b.idx
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-3221a15c022ef4a7bb6bf2c47e40068b66b3588b.rev
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-9aca069a8b76b40fcc472eba1ed9b8219a87776b.idx
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-9aca069a8b76b40fcc472eba1ed9b8219a87776b.rev
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-e6f6cdfe9914bfb4a5717ef6036821794d59ab4b.idx
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-e6f6cdfe9914bfb4a5717ef6036821794d59ab4b.rev
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-3221a15c022ef4a7bb6bf2c47e40068b66b3588b.pack
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-9aca069a8b76b40fcc472eba1ed9b8219a87776b.pack
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-e6f6cdfe9914bfb4a5717ef6036821794d59ab4b.pack
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/refs/heads/master
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/refs/remotes/origin/master
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.gitignore
Add .config/DankMaterialShell/plugins/emojiLauncher/EmojiLauncher.qml
Add .config/DankMaterialShell/plugins/emojiLauncher/EmojiLauncherSettings.qml
Add .config/DankMaterialShell/plugins/emojiLauncher/LICENSE
Add .config/DankMaterialShell/plugins/emojiLauncher/README.md
Add .config/DankMaterialShell/plugins/emojiLauncher/catalog.js
Add .config/DankMaterialShell/plugins/emojiLauncher/data/emojis.txt
Add .config/DankMaterialShell/plugins/emojiLauncher/data/math.txt
Add .config/DankMaterialShell/plugins/emojiLauncher/data/nerdfont.txt
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/HEAD
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/config
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/index
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/pack/pack-e04a5b1ea381dc3a792b8bf08cf70e735b195c0d.idx
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/pack/pack-e04a5b1ea381dc3a792b8bf08cf70e735b195c0d.rev
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/pack/pack-e04a5b1ea381dc3a792b8bf08cf70e735b195c0d.pack
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/emojiLauncher/plugin.json
Add .config/DankMaterialShell/plugins/emojiLauncher/screenshot.png
Add .config/DankMaterialShell/plugins/emojiLauncher/scripts/generate_catalog.py
Add .config/DankMaterialShell/plugins/mediaPlayer/MediaPlayerSettings.qml
Add .config/DankMaterialShell/plugins/mediaPlayer/MediaPlayerTab.qml
Add .config/DankMaterialShell/plugins/mediaPlayer/README.md
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/HEAD
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/config
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/index
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/objects/pack/pack-0b9cb33f7da23f6ff361ee3aa5117928714bc3be.idx
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/objects/pack/pack-0b9cb33f7da23f6ff361ee3aa5117928714bc3be.rev
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/objects/pack/pack-0b9cb33f7da23f6ff361ee3aa5117928714bc3be.pack
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/mediaPlayer/plugin.json
Add .config/DankMaterialShell/plugins/mediaPlayer/screenshot_8.png
Add .config/DankMaterialShell/plugins/dankDesktopWeather
Add .config/DankMaterialShell/plugins/dankHooks
Add .config/DankMaterialShell/settings.json
This commit is contained in:
Lewis Wynne 2026-01-07 15:09:11 +00:00
parent 9d16d6e6b0
commit b18328bbad
96 changed files with 24119 additions and 0 deletions

View file

@ -0,0 +1,644 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
PluginSettings {
id: root
pluginId: "dankActions"
property string editingVariantId: ""
function loadVariantForEditing(variantData) {
editingVariantId = variantData.id || ""
nameField.text = variantData.name || ""
iconField.text = variantData.icon || ""
displayTextField.text = variantData.displayText || ""
displayCommandField.text = variantData.displayCommand || ""
clickCommandField.text = variantData.clickCommand || ""
middleClickCommandField.text = variantData.middleClickCommand || ""
rightClickCommandField.text = variantData.rightClickCommand || ""
updateIntervalField.text = (variantData.updateInterval || 0).toString()
showIconToggle.checked = variantData.showIcon !== undefined ? variantData.showIcon : true
showTextToggle.checked = variantData.showText !== undefined ? variantData.showText : true
}
function clearForm() {
editingVariantId = ""
nameField.text = ""
iconField.text = ""
displayTextField.text = ""
displayCommandField.text = ""
clickCommandField.text = ""
middleClickCommandField.text = ""
rightClickCommandField.text = ""
updateIntervalField.text = "0"
showIconToggle.checked = true
showTextToggle.checked = true
}
StyledText {
width: parent.width
text: "Custom Action Manager"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
}
StyledText {
width: parent.width
text: "Create custom widgets that execute commands and display dynamic output"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
StyledRect {
width: parent.width
height: addActionColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
id: addActionColumn
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
StyledText {
text: root.editingVariantId ? "Edit Action" : "Create New Action"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
DankButton {
text: "Cancel"
iconName: "close"
visible: root.editingVariantId !== ""
onClicked: root.clearForm()
}
}
Row {
width: parent.width
spacing: Theme.spacingM
Column {
width: (parent.width - Theme.spacingM) / 2
spacing: Theme.spacingXS
StyledText {
text: "Variant Name *"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: nameField
width: parent.width
placeholderText: "e.g., Power Profile"
keyNavigationTab: iconField
onFocusStateChanged: hasFocus => {
if (hasFocus) root.ensureItemVisible(nameField)
}
}
}
Column {
width: (parent.width - Theme.spacingM) / 2
spacing: Theme.spacingXS
StyledText {
text: "Icon Name"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: iconField
width: parent.width
placeholderText: "e.g., power_settings_new"
keyNavigationBacktab: nameField
keyNavigationTab: displayTextField
onFocusStateChanged: hasFocus => {
if (hasFocus) root.ensureItemVisible(iconField)
}
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
StyledText {
text: "Static Display Text (optional)"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: displayTextField
width: parent.width
placeholderText: "Text to show (or leave empty if using command output)"
keyNavigationBacktab: iconField
keyNavigationTab: displayCommandField
onFocusStateChanged: hasFocus => {
if (hasFocus) root.ensureItemVisible(displayTextField)
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
StyledText {
text: "Display Command (optional)"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: displayCommandField
width: parent.width
placeholderText: 'e.g., echo "Hello World" or powerprofilesctl get'
keyNavigationBacktab: displayTextField
keyNavigationTab: clickCommandField
onFocusStateChanged: hasFocus => {
if (hasFocus) root.ensureItemVisible(displayCommandField)
}
}
StyledText {
text: "Command output will replace static text. Runs on widget load."
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
StyledText {
text: "Left Click Command (optional)"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: clickCommandField
width: parent.width
placeholderText: "e.g., notify-send 'Clicked!' or cycle-power-profile.sh"
keyNavigationBacktab: displayCommandField
keyNavigationTab: middleClickCommandField
onFocusStateChanged: hasFocus => {
if (hasFocus) root.ensureItemVisible(clickCommandField)
}
}
StyledText {
text: "Command to run on left click. After completion, display command refreshes."
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
StyledText {
text: "Middle Click Command (optional)"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: middleClickCommandField
width: parent.width
placeholderText: "e.g., notify-send 'Middle clicked!'"
keyNavigationBacktab: clickCommandField
keyNavigationTab: rightClickCommandField
onFocusStateChanged: hasFocus => {
if (hasFocus) root.ensureItemVisible(middleClickCommandField)
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
StyledText {
text: "Right Click Command (optional)"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: rightClickCommandField
width: parent.width
placeholderText: "e.g., notify-send 'Right clicked!'"
keyNavigationBacktab: middleClickCommandField
keyNavigationTab: updateIntervalField
onFocusStateChanged: hasFocus => {
if (hasFocus) root.ensureItemVisible(rightClickCommandField)
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
StyledText {
text: "Update Interval (seconds, 0 = disabled)"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: updateIntervalField
width: parent.width
placeholderText: "0"
text: "0"
keyNavigationBacktab: rightClickCommandField
onFocusStateChanged: hasFocus => {
if (hasFocus) root.ensureItemVisible(updateIntervalField)
}
}
StyledText {
text: "Automatically re-run display command every N seconds. Set to 0 to disable."
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
Row {
width: parent.width
spacing: Theme.spacingL
Column {
spacing: Theme.spacingXS
StyledText {
text: "Show Icon"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankToggle {
id: showIconToggle
checked: true
onToggled: isChecked => {
checked = isChecked
}
}
}
Column {
spacing: Theme.spacingXS
StyledText {
text: "Show Text"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankToggle {
id: showTextToggle
checked: true
onToggled: isChecked => {
checked = isChecked
}
}
}
}
DankButton {
text: root.editingVariantId ? "Update Action" : "Create Action"
iconName: root.editingVariantId ? "check" : "add"
onClicked: {
if (!nameField.text) {
ToastService.showError("Please enter a variant name")
return
}
var interval = parseInt(updateIntervalField.text) || 0
if (interval < 0) {
ToastService.showError("Update interval must be 0 or greater")
return
}
var variantConfig = {
icon: iconField.text || "terminal",
displayText: displayTextField.text || "",
displayCommand: displayCommandField.text || "",
clickCommand: clickCommandField.text || "",
middleClickCommand: middleClickCommandField.text || "",
rightClickCommand: rightClickCommandField.text || "",
updateInterval: interval,
showIcon: showIconToggle.checked,
showText: showTextToggle.checked
}
if (root.editingVariantId) {
variantConfig.name = nameField.text
updateVariant(root.editingVariantId, variantConfig)
} else {
createVariant(nameField.text, variantConfig)
}
root.clearForm()
}
}
}
}
StyledRect {
width: parent.width
height: Math.max(200, variantsColumn.implicitHeight + Theme.spacingL * 2)
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
id: variantsColumn
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
StyledText {
text: "Existing Actions"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
ListView {
width: parent.width
height: Math.max(100, contentHeight)
clip: true
spacing: Theme.spacingXS
model: root.variantsModel
delegate: StyledRect {
required property var model
width: ListView.view.width
height: variantColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: variantMouseArea.containsMouse ? Theme.surfaceContainerHighest : Theme.surfaceContainer
Column {
id: variantColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingXS
Row {
width: parent.width
spacing: Theme.spacingM
Item {
width: Theme.iconSize
height: Theme.iconSize
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: model.icon || "terminal"
size: Theme.iconSize
color: Theme.surfaceText
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
width: parent.width - Theme.iconSize - (editButton.width + deleteButton.width + Theme.spacingXS) - Theme.spacingM * 3
StyledText {
text: model.name || "Unnamed"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
width: parent.width
elide: Text.ElideRight
}
StyledText {
text: {
var parts = []
if (model.displayText) parts.push("Text: " + model.displayText)
if (model.displayCommand) parts.push("Cmd: " + model.displayCommand)
if (model.updateInterval && model.updateInterval > 0) parts.push("Update: " + model.updateInterval + "s")
return parts.join(" | ") || "No display config"
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
elide: Text.ElideRight
}
StyledText {
text: {
var actions = []
if (model.clickCommand) actions.push("L: " + model.clickCommand)
if (model.middleClickCommand) actions.push("M: " + model.middleClickCommand)
if (model.rightClickCommand) actions.push("R: " + model.rightClickCommand)
return actions.join(" | ") || "No click actions"
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
elide: Text.ElideRight
}
}
Row {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
Rectangle {
id: editButton
width: 32
height: 32
radius: 16
color: editArea.containsMouse ? Theme.primary : "transparent"
DankIcon {
anchors.centerIn: parent
name: "edit"
size: 16
color: editArea.containsMouse ? Theme.onPrimary : Theme.surfaceVariantText
}
MouseArea {
id: editArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.loadVariantForEditing(model)
}
}
}
Rectangle {
id: deleteButton
width: 32
height: 32
radius: 16
color: deleteArea.containsMouse ? Theme.error : "transparent"
DankIcon {
anchors.centerIn: parent
name: "delete"
size: 16
color: deleteArea.containsMouse ? Theme.onError : Theme.surfaceVariantText
}
MouseArea {
id: deleteArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
removeVariant(model.id)
}
}
}
}
}
}
MouseArea {
id: variantMouseArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
}
}
StyledText {
anchors.centerIn: parent
text: "No actions created yet"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
visible: variantsModel.count === 0
}
}
}
}
StyledRect {
width: parent.width
height: examplesColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surface
Column {
id: examplesColumn
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
spacing: Theme.spacingM
DankIcon {
name: "lightbulb"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Usage Examples"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: "• Power Profile Cycler"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: "Display: powerprofilesctl get\nClick: powerprofilesctl set $(powerprofilesctl list | grep -v \"$(powerprofilesctl get)\" | head -1)"
font.pixelSize: Theme.fontSizeSmall
font.family: "monospace"
color: Theme.surfaceVariantText
wrapMode: Text.Wrap
width: parent.width
leftPadding: Theme.spacingM
}
StyledText {
text: "• System Uptime"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: "Display: uptime -p | sed 's/up //'\nClick: notify-send \"Uptime\" \"$(uptime -p)\""
font.pixelSize: Theme.fontSizeSmall
font.family: "monospace"
color: Theme.surfaceVariantText
wrapMode: Text.Wrap
width: parent.width
leftPadding: Theme.spacingM
}
StyledText {
text: "• Custom Greeting"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: "Display: echo \"Hello $(whoami)!\"\nClick: notify-send \"Hi!\" \"Welcome back!\""
font.pixelSize: Theme.fontSizeSmall
font.family: "monospace"
color: Theme.surfaceVariantText
wrapMode: Text.Wrap
width: parent.width
leftPadding: Theme.spacingM
}
}
StyledText {
text: "After creating actions, add them to your bar from Bar Settings → Add Widget"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
}
}

View file

@ -0,0 +1,263 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
PluginComponent {
id: root
property string variantId: ""
property var variantData: null
property string displayIcon: "terminal"
property string displayText: ""
property string displayCommand: ""
property string clickCommand: ""
property string middleClickCommand: ""
property string rightClickCommand: ""
property int updateInterval: 0
property bool showIcon: true
property bool showText: true
property string currentOutput: ""
property bool isLoading: false
onVariantDataChanged: {
updatePropertiesFromVariantData()
}
Connections {
target: PluginService
function onPluginDataChanged(changedPluginId) {
if (changedPluginId === "dankActions" && variantId) {
const newData = PluginService.getPluginVariantData("dankActions", variantId)
if (newData) {
variantData = newData
}
}
}
}
function updatePropertiesFromVariantData() {
if (!variantData) {
displayIcon = "terminal"
displayText = ""
displayCommand = ""
clickCommand = ""
middleClickCommand = ""
rightClickCommand = ""
updateInterval = 0
showIcon = true
showText = true
currentOutput = ""
return
}
displayIcon = variantData.icon || "terminal"
displayText = variantData.displayText || ""
displayCommand = variantData.displayCommand || ""
clickCommand = variantData.clickCommand || ""
middleClickCommand = variantData.middleClickCommand || ""
rightClickCommand = variantData.rightClickCommand || ""
updateInterval = variantData.updateInterval || 0
showIcon = variantData.showIcon !== undefined ? variantData.showIcon : true
showText = variantData.showText !== undefined ? variantData.showText : true
if (displayCommand) {
Qt.callLater(refreshOutput)
} else {
currentOutput = displayText
}
if (updateInterval > 0) {
updateTimer.restart()
}
}
onDisplayCommandChanged: {
if (displayCommand) {
Qt.callLater(refreshOutput)
} else {
currentOutput = displayText
}
}
onDisplayTextChanged: {
if (!displayCommand) {
currentOutput = displayText
}
}
onUpdateIntervalChanged: {
if (updateInterval > 0) {
updateTimer.restart()
} else {
updateTimer.stop()
}
}
Component.onCompleted: {
if (displayCommand) {
Qt.callLater(refreshOutput)
} else {
currentOutput = displayText
}
if (updateInterval > 0) {
updateTimer.start()
}
}
Timer {
id: updateTimer
interval: root.updateInterval * 1000
repeat: true
running: false
onTriggered: {
if (root.displayCommand) {
root.refreshOutput()
}
}
}
function refreshOutput() {
if (!displayCommand) {
currentOutput = displayText
return
}
isLoading = true
displayProcess.running = true
}
function executeCommand(command) {
if (!command) return
isLoading = true
actionProcess.command = ["sh", "-c", command]
actionProcess.running = true
}
Process {
id: displayProcess
command: ["sh", "-c", root.displayCommand]
running: false
stdout: SplitParser {
onRead: data => {
root.currentOutput = data.trim()
}
}
onExited: (exitCode, exitStatus) => {
root.isLoading = false
if (exitCode !== 0) {
console.warn("CustomActions: Display command failed with code", exitCode)
}
}
}
Process {
id: actionProcess
command: ["sh", "-c", ""]
running: false
onExited: (exitCode, exitStatus) => {
root.isLoading = false
if (exitCode === 0) {
if (root.displayCommand) {
root.refreshOutput()
}
} else {
console.warn("CustomActions: Action command failed with code", exitCode)
}
}
}
pillClickAction: () => {
if (root.clickCommand) {
root.executeCommand(root.clickCommand)
}
}
horizontalBarPill: Component {
MouseArea {
implicitWidth: contentRow.implicitWidth
implicitHeight: contentRow.implicitHeight
acceptedButtons: Qt.MiddleButton | Qt.RightButton
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
if (mouse.button === Qt.MiddleButton && root.middleClickCommand) {
root.executeCommand(root.middleClickCommand)
} else if (mouse.button === Qt.RightButton && root.rightClickCommand) {
root.executeCommand(root.rightClickCommand)
}
}
Row {
id: contentRow
spacing: Theme.spacingXS
DankIcon {
name: root.displayIcon
size: Theme.iconSize - 6
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: root.showIcon
}
StyledText {
text: root.currentOutput || ""
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: root.showText && root.currentOutput
}
}
}
}
verticalBarPill: Component {
MouseArea {
implicitWidth: contentColumn.implicitWidth
implicitHeight: contentColumn.implicitHeight
acceptedButtons: Qt.MiddleButton | Qt.RightButton
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
if (mouse.button === Qt.MiddleButton && root.middleClickCommand) {
root.executeCommand(root.middleClickCommand)
} else if (mouse.button === Qt.RightButton && root.rightClickCommand) {
root.executeCommand(root.rightClickCommand)
}
}
Column {
id: contentColumn
spacing: Theme.spacingXS
DankIcon {
name: root.displayIcon
size: Theme.iconSize - 6
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showIcon
}
StyledText {
text: root.currentOutput || ""
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showText && root.currentOutput
}
}
}
}
}

View file

@ -0,0 +1,16 @@
{
"id": "dankActions",
"name": "Dank Actions",
"description": "Execute custom commands with dynamic output display and configurable icons",
"version": "1.0.4",
"license": "MIT",
"author": "Avenge Media",
"icon": "terminal",
"firstParty": true,
"component": "./DankActionsWidget.qml",
"settings": "./DankActionsSettings.qml",
"permissions": [
"settings_read",
"settings_write"
]
}

View file

@ -0,0 +1,110 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Services
import qs.Modules.Plugins
PluginComponent {
id: root
Item {}
property bool enableCriticalAlert: pluginData.enableCriticalAlert ?? true
property int criticalThreshold: pluginData.criticalThreshold ?? 10
property string criticalTitle: pluginData.criticalTitle || "Critical Battery Level"
property string criticalMessage: pluginData.criticalMessage || "Battery at ${level}% - Connect charger immediately!"
property bool enableWarningAlert: pluginData.enableWarningAlert ?? true
property int warningThreshold: pluginData.warningThreshold ?? 20
property string warningTitle: pluginData.warningTitle || "Low Battery"
property string warningMessage: pluginData.warningMessage || "Battery at ${level}% - Consider charging soon"
property bool criticalAlertSent: false
property bool warningAlertSent: false
Component.onCompleted: {
console.log("DankBatteryAlerts: Started monitoring battery level")
console.log("DankBatteryAlerts: Critical alerts:", enableCriticalAlert, "at", criticalThreshold + "%")
console.log("DankBatteryAlerts: Warning alerts:", enableWarningAlert, "at", warningThreshold + "%")
}
Connections {
target: BatteryService.batteryAvailable ? BatteryService : null
function onBatteryLevelChanged() {
const level = BatteryService.batteryLevel
const isCharging = BatteryService.isCharging
if (isCharging) {
criticalAlertSent = false
warningAlertSent = false
return
}
if (enableCriticalAlert && level <= criticalThreshold && !criticalAlertSent) {
sendNotification(
criticalTitle,
criticalMessage.replace("${level}", level),
"critical",
"battery_alert"
)
criticalAlertSent = true
} else if (enableWarningAlert && level <= warningThreshold && !warningAlertSent && !criticalAlertSent) {
sendNotification(
warningTitle,
warningMessage.replace("${level}", level),
"normal",
"battery_std"
)
warningAlertSent = true
}
if (level > warningThreshold) {
warningAlertSent = false
}
if (level > criticalThreshold) {
criticalAlertSent = false
}
}
}
function sendNotification(title, message, urgency, icon) {
const process = notifyProcessComponent.createObject(root, {
notifyTitle: title,
notifyMessage: message,
notifyUrgency: urgency,
notifyIcon: icon
})
process.running = true
}
Component {
id: notifyProcessComponent
Process {
property string notifyTitle: ""
property string notifyMessage: ""
property string notifyUrgency: "normal"
property string notifyIcon: "battery_alert"
command: [
"notify-send",
"-a", "DankMaterialShell",
"-i", notifyIcon,
"-u", notifyUrgency,
notifyTitle,
notifyMessage
]
onExited: (exitCode) => {
if (exitCode !== 0) {
console.error("DankBatteryAlerts: notify-send failed with code:", exitCode)
}
destroy()
}
}
}
Component.onDestruction: {
console.log("DankBatteryAlerts: Stopped monitoring battery level")
}
}

View file

@ -0,0 +1,139 @@
import QtQuick
import qs.Common
import qs.Widgets
import qs.Modules.Plugins
PluginSettings {
id: root
pluginId: "dankBatteryAlerts"
StyledText {
text: "Battery Alerts"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
}
StyledText {
text: "Get notified when battery reaches critical or warning levels while on battery power"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
wrapMode: Text.WordWrap
}
StyledRect {
width: parent.width
height: 1
color: Theme.surfaceVariant
}
StyledText {
text: "Critical Alert"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.DemiBold
color: Theme.surfaceText
}
ToggleSetting {
settingKey: "enableCriticalAlert"
label: "Enable Critical Alert"
description: "Show urgent notification when battery reaches critical level"
defaultValue: true
}
SliderSetting {
settingKey: "criticalThreshold"
label: "Critical Threshold"
description: "Battery percentage to trigger critical alert"
defaultValue: 10
minimum: 1
maximum: 30
unit: "%"
rightIcon: "battery_alert"
}
StringSetting {
settingKey: "criticalTitle"
label: "Critical Title"
description: "Notification title for critical alerts"
placeholder: "Critical Battery Level"
defaultValue: "Critical Battery Level"
}
StringSetting {
settingKey: "criticalMessage"
label: "Critical Message"
description: "Use ${level} for battery percentage"
placeholder: "Battery at ${level}% - Connect charger immediately!"
defaultValue: "Battery at ${level}% - Connect charger immediately!"
}
StyledRect {
width: parent.width
height: 1
color: Theme.surfaceVariant
}
StyledText {
text: "Warning Alert"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.DemiBold
color: Theme.surfaceText
}
ToggleSetting {
settingKey: "enableWarningAlert"
label: "Enable Warning Alert"
description: "Show notification when battery reaches warning level"
defaultValue: true
}
SliderSetting {
settingKey: "warningThreshold"
label: "Warning Threshold"
description: "Battery percentage to trigger warning alert"
defaultValue: 20
minimum: 5
maximum: 50
unit: "%"
rightIcon: "battery_std"
}
StringSetting {
settingKey: "warningTitle"
label: "Warning Title"
description: "Notification title for warning alerts"
placeholder: "Low Battery"
defaultValue: "Low Battery"
}
StringSetting {
settingKey: "warningMessage"
label: "Warning Message"
description: "Use ${level} for battery percentage"
placeholder: "Battery at ${level}% - Consider charging soon"
defaultValue: "Battery at ${level}% - Consider charging soon"
}
StyledRect {
width: parent.width
height: 1
color: Theme.surfaceVariant
}
StyledText {
text: "Alert Behavior"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.DemiBold
color: Theme.surfaceText
}
StyledText {
text: "• Critical alerts use urgent priority and persist longer\n• Alerts reset when battery is charging or rises above threshold\n• Only one alert per threshold per battery discharge cycle"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
wrapMode: Text.WordWrap
}
}

View file

@ -0,0 +1,13 @@
{
"id": "dankBatteryAlerts",
"name": "Dank Battery Alerts",
"description": "Receive notifications when battery level reaches critical or warning thresholds",
"version": "1.1.1",
"license": "MIT",
"author": "Avenge Media",
"icon": "battery_alert",
"type": "daemon",
"component": "./DankBatteryAlerts.qml",
"settings": "./DankBatteryAlertsSettings.qml",
"permissions": ["settings_read", "settings_write"]
}

View file

@ -0,0 +1,767 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Effects
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
DesktopPluginComponent {
id: root
minWidth: {
switch (viewMode) {
case "compact":
return 80;
case "standard":
return 140;
case "detailed":
return 200;
case "forecast":
return 280;
default:
return 160;
}
}
minHeight: {
switch (viewMode) {
case "compact":
return 80;
case "standard":
return 100;
case "detailed":
return 200;
case "forecast":
return 320;
default:
return 140;
}
}
property string viewMode: pluginData.viewMode ?? "standard"
property real backgroundOpacity: (pluginData.backgroundOpacity ?? 80) / 100
property string colorMode: pluginData.colorMode ?? "primary"
property color customColor: pluginData.customColor ?? "#ffffff"
property bool showLocation: pluginData.showLocation ?? true
property bool showCondition: pluginData.showCondition ?? true
property bool showFeelsLike: pluginData.showFeelsLike ?? true
property bool showHumidity: pluginData.showHumidity ?? true
property bool showWind: pluginData.showWind ?? true
property bool showPressure: pluginData.showPressure ?? false
property bool showPrecipitation: pluginData.showPrecipitation ?? true
property bool showSunTimes: pluginData.showSunTimes ?? true
property bool showForecast: pluginData.showForecast ?? true
property int forecastDays: pluginData.forecastDays ?? 5
property bool showHourlyForecast: pluginData.showHourlyForecast ?? false
property int hourlyCount: pluginData.hourlyCount ?? 6
readonly property color accentColor: {
switch (colorMode) {
case "secondary":
return Theme.secondary;
case "custom":
return customColor;
default:
return Theme.primary;
}
}
readonly property color bgColor: Theme.withAlpha(Theme.surface, backgroundOpacity)
readonly property color tileBg: Theme.withAlpha(Theme.surfaceContainerHigh, backgroundOpacity)
readonly property color textColor: Theme.surfaceText
readonly property color dimColor: Theme.surfaceVariantText
readonly property bool available: WeatherService.weather.available
readonly property var weather: WeatherService.weather
readonly property real scaleFactor: Math.min(width, height) / 200
readonly property int scaledMargin: {
switch (viewMode) {
case "compact":
return 0;
case "standard":
return 2;
default:
return Math.round(Math.max(4, Theme.spacingS * scaleFactor));
}
}
readonly property int scaledSpacing: Math.round(Math.max(1, Theme.spacingXS * scaleFactor))
Ref {
service: WeatherService
}
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: root.bgColor
border.width: 0
ColumnLayout {
anchors.fill: parent
anchors.margins: root.scaledMargin
spacing: root.scaledSpacing
Loader {
id: headerLoader
Layout.fillWidth: true
Layout.fillHeight: {
switch (root.viewMode) {
case "compact":
case "standard":
return true;
case "detailed":
return !root.showForecast;
case "forecast":
return false;
default:
return true;
}
}
Layout.preferredHeight: {
if (root.viewMode === "forecast")
return 50;
if (root.viewMode === "detailed" && root.showForecast)
return 140;
return -1;
}
sourceComponent: {
switch (root.viewMode) {
case "compact":
return compactView;
case "standard":
return standardView;
case "detailed":
return detailedView;
case "forecast":
return forecastHeaderView;
default:
return standardView;
}
}
}
Loader {
Layout.fillWidth: true
Layout.fillHeight: true
visible: root.viewMode === "forecast" || (root.viewMode === "detailed" && root.showForecast)
active: visible
sourceComponent: forecastSection
}
}
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
visible: !root.available
DankIcon {
name: "cloud_off"
size: Theme.iconSize * 1.5
color: root.dimColor
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("No Weather Data")
font.pixelSize: Theme.fontSizeSmall
color: root.dimColor
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Component {
id: compactView
Item {
id: compactRoot
visible: root.available
readonly property int baseSize: Math.min(width, height)
readonly property int iconSize: Math.round(baseSize * 0.55)
readonly property int tempFontSize: Math.round(baseSize * 0.22)
Column {
anchors.centerIn: parent
spacing: 0
DankIcon {
name: WeatherService.getWeatherIcon(root.weather.wCode)
size: compactRoot.iconSize
color: root.accentColor
anchors.horizontalCenter: parent.horizontalCenter
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
shadowHorizontalOffset: 0
shadowVerticalOffset: 2
shadowBlur: 0.6
shadowColor: Theme.shadowMedium
shadowOpacity: 0.2
}
}
StyledText {
text: WeatherService.formatTemp(root.weather.temp, true, false)
font.pixelSize: compactRoot.tempFontSize
font.weight: Font.Light
color: root.textColor
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
}
Component {
id: standardView
Item {
id: standardRoot
visible: root.available
readonly property int baseSize: Math.min(width, height)
readonly property int iconSize: Math.round(baseSize * 0.55)
readonly property int tempFontSize: Math.round(baseSize * 0.28)
readonly property int labelFontSize: Math.max(9, Math.round(baseSize * 0.12))
RowLayout {
anchors.centerIn: parent
width: parent.width
spacing: Math.round(baseSize * 0.04)
DankIcon {
name: WeatherService.getWeatherIcon(root.weather.wCode)
size: standardRoot.iconSize
color: root.accentColor
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
shadowHorizontalOffset: 0
shadowVerticalOffset: 3
shadowBlur: 0.7
shadowColor: Theme.shadowMedium
shadowOpacity: 0.2
}
}
Column {
Layout.fillWidth: true
spacing: 0
StyledText {
text: WeatherService.formatTemp(root.weather.temp, true, false)
font.pixelSize: standardRoot.tempFontSize
font.weight: Font.Light
color: root.textColor
}
StyledText {
visible: root.showCondition
text: WeatherService.getWeatherCondition(root.weather.wCode)
font.pixelSize: standardRoot.labelFontSize
color: root.dimColor
elide: Text.ElideRight
width: parent.width
}
StyledText {
visible: root.showLocation && root.weather.city
text: root.weather.city
font.pixelSize: standardRoot.labelFontSize
color: root.dimColor
elide: Text.ElideRight
width: parent.width
}
}
}
}
}
Component {
id: detailedView
Item {
id: detailedRoot
visible: root.available
readonly property int baseSize: Math.min(width, height)
readonly property int iconSize: Math.round(Math.max(28, Math.min(56, baseSize * 0.28)))
readonly property int tempFontSize: Math.round(Math.max(16, Math.min(32, baseSize * 0.16)))
readonly property int labelFontSize: Math.round(Math.max(10, Math.min(14, baseSize * 0.07)))
readonly property int smallIconSize: Math.round(Math.max(12, Math.min(16, baseSize * 0.07)))
readonly property int itemSpacing: Math.round(Math.max(2, Math.min(8, baseSize * 0.04)))
ColumnLayout {
anchors.fill: parent
spacing: detailedRoot.itemSpacing
RowLayout {
Layout.fillWidth: true
spacing: detailedRoot.itemSpacing * 2
DankIcon {
name: WeatherService.getWeatherIcon(root.weather.wCode)
size: detailedRoot.iconSize
color: root.accentColor
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
shadowHorizontalOffset: 0
shadowVerticalOffset: 3
shadowBlur: 0.7
shadowColor: Theme.shadowMedium
shadowOpacity: 0.2
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
StyledText {
text: WeatherService.formatTemp(root.weather.temp, true, false)
font.pixelSize: detailedRoot.tempFontSize
font.weight: Font.Light
color: root.textColor
}
StyledText {
visible: root.showCondition
text: WeatherService.getWeatherCondition(root.weather.wCode)
font.pixelSize: detailedRoot.labelFontSize
color: root.dimColor
}
StyledText {
visible: root.showLocation && root.weather.city
text: root.weather.city
font.pixelSize: detailedRoot.labelFontSize
color: root.dimColor
elide: Text.ElideRight
Layout.fillWidth: true
}
}
Item {
Layout.fillWidth: true
}
ColumnLayout {
visible: root.showSunTimes
spacing: 1
Layout.alignment: Qt.AlignRight
RowLayout {
spacing: 2
DankIcon {
name: "wb_twilight"
size: detailedRoot.smallIconSize
color: root.dimColor
}
StyledText {
text: root.weather.sunrise || "--"
font.pixelSize: detailedRoot.labelFontSize
color: root.dimColor
}
}
RowLayout {
spacing: 2
DankIcon {
name: "bedtime"
size: detailedRoot.smallIconSize
color: root.dimColor
}
StyledText {
text: root.weather.sunset || "--"
font.pixelSize: detailedRoot.labelFontSize
color: root.dimColor
}
}
}
}
Rectangle {
Layout.fillWidth: true
height: 1
color: Theme.withAlpha(Theme.outline, 0.15)
}
Flow {
Layout.fillWidth: true
spacing: detailedRoot.itemSpacing
WeatherMetric {
visible: root.showFeelsLike
icon: "device_thermostat"
label: I18n.tr("Feels")
value: WeatherService.formatTemp(root.weather.feelsLike, true, true)
accentColor: root.accentColor
textColor: root.textColor
dimColor: root.dimColor
iconSize: detailedRoot.smallIconSize
fontSize: detailedRoot.labelFontSize
}
WeatherMetric {
visible: root.showHumidity
icon: "humidity_percentage"
label: I18n.tr("Humidity")
value: WeatherService.formatPercent(root.weather.humidity)
accentColor: root.accentColor
textColor: root.textColor
dimColor: root.dimColor
iconSize: detailedRoot.smallIconSize
fontSize: detailedRoot.labelFontSize
}
WeatherMetric {
visible: root.showWind
icon: "air"
label: I18n.tr("Wind")
value: root.weather.wind || "--"
accentColor: root.accentColor
textColor: root.textColor
dimColor: root.dimColor
iconSize: detailedRoot.smallIconSize
fontSize: detailedRoot.labelFontSize
}
WeatherMetric {
visible: root.showPrecipitation
icon: "rainy"
label: I18n.tr("Precip")
value: WeatherService.formatPercent(root.weather.precipitationProbability)
accentColor: root.accentColor
textColor: root.textColor
dimColor: root.dimColor
iconSize: detailedRoot.smallIconSize
fontSize: detailedRoot.labelFontSize
}
WeatherMetric {
visible: root.showPressure
icon: "speed"
label: I18n.tr("Pressure")
value: WeatherService.formatPressure(root.weather.pressure)
accentColor: root.accentColor
textColor: root.textColor
dimColor: root.dimColor
iconSize: detailedRoot.smallIconSize
fontSize: detailedRoot.labelFontSize
}
}
Item {
Layout.fillHeight: true
visible: !root.showForecast
}
}
}
}
Component {
id: forecastHeaderView
Item {
id: forecastHeaderRoot
visible: root.available
readonly property int baseSize: Math.min(width, height)
readonly property int iconSize: Math.round(Math.max(20, baseSize * 0.7))
readonly property int tempFontSize: Math.round(Math.max(12, baseSize * 0.4))
readonly property int labelFontSize: Math.round(Math.max(9, baseSize * 0.22))
RowLayout {
anchors.fill: parent
spacing: Math.round(baseSize * 0.15)
DankIcon {
name: WeatherService.getWeatherIcon(root.weather.wCode)
size: forecastHeaderRoot.iconSize
color: root.accentColor
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
shadowHorizontalOffset: 0
shadowVerticalOffset: 2
shadowBlur: 0.6
shadowColor: Theme.shadowMedium
shadowOpacity: 0.2
}
}
ColumnLayout {
spacing: 0
StyledText {
text: WeatherService.formatTemp(root.weather.temp, true, false)
font.pixelSize: forecastHeaderRoot.tempFontSize
font.weight: Font.Light
color: root.textColor
}
StyledText {
visible: root.showLocation && root.weather.city
text: root.weather.city
font.pixelSize: forecastHeaderRoot.labelFontSize
color: root.dimColor
}
}
Item {
Layout.fillWidth: true
}
GridLayout {
columns: 2
rowSpacing: 1
columnSpacing: Math.round(forecastHeaderRoot.baseSize * 0.1)
visible: root.width > 300
WeatherMetric {
visible: root.showHumidity
icon: "humidity_percentage"
value: WeatherService.formatPercent(root.weather.humidity)
accentColor: root.accentColor
textColor: root.textColor
dimColor: root.dimColor
compact: true
iconSize: forecastHeaderRoot.labelFontSize
fontSize: forecastHeaderRoot.labelFontSize
}
WeatherMetric {
visible: root.showWind
icon: "air"
value: root.weather.wind || "--"
accentColor: root.accentColor
textColor: root.textColor
dimColor: root.dimColor
compact: true
iconSize: forecastHeaderRoot.labelFontSize
fontSize: forecastHeaderRoot.labelFontSize
}
}
}
}
}
Component {
id: forecastSection
ColumnLayout {
id: forecastRoot
spacing: root.scaledSpacing
visible: root.available && root.showForecast
readonly property int itemFontSize: Math.round(Math.max(10, Math.min(14, root.height * 0.035)))
readonly property int itemIconSize: Math.round(Math.max(12, Math.min(18, root.height * 0.04)))
Rectangle {
Layout.fillWidth: true
height: 1
color: Theme.withAlpha(Theme.outline, 0.15)
visible: root.viewMode === "forecast"
}
DankListView {
id: hourlyList
Layout.fillWidth: true
Layout.preferredHeight: root.showHourlyForecast ? Math.round(Math.max(50, Math.min(80, root.height * 0.18))) : 0
visible: root.showHourlyForecast && root.weather.hourlyForecast?.length > 0
orientation: ListView.Horizontal
flickableDirection: Flickable.HorizontalFlick
spacing: root.scaledSpacing
clip: true
model: Math.min(root.hourlyCount, root.weather.hourlyForecast?.length ?? 0)
delegate: Rectangle {
required property int index
width: Math.round(Math.max(36, hourlyList.height * 0.8))
height: hourlyList.height
radius: Theme.cornerRadius - 2
color: root.tileBg
property var forecast: root.weather.hourlyForecast?.[index] ?? {}
ColumnLayout {
anchors.fill: parent
anchors.margins: 2
spacing: 0
Item {
Layout.fillHeight: true
}
StyledText {
text: forecast.time || "--"
font.pixelSize: forecastRoot.itemFontSize
color: root.dimColor
Layout.alignment: Qt.AlignHCenter
}
DankIcon {
name: WeatherService.getWeatherIcon(forecast.wCode, forecast.isDay)
size: forecastRoot.itemIconSize
color: root.accentColor
Layout.alignment: Qt.AlignHCenter
}
StyledText {
text: WeatherService.formatTemp(forecast.temp, false)
font.pixelSize: forecastRoot.itemFontSize
font.weight: Font.Medium
color: root.textColor
Layout.alignment: Qt.AlignHCenter
}
Item {
Layout.fillHeight: true
}
}
}
}
Rectangle {
Layout.fillWidth: true
height: 1
color: Theme.withAlpha(Theme.outline, 0.1)
visible: root.showHourlyForecast && root.weather.hourlyForecast?.length > 0
}
DankListView {
id: dailyList
Layout.fillWidth: true
Layout.fillHeight: true
spacing: root.scaledSpacing
clip: true
readonly property int itemCount: Math.min(root.forecastDays, root.weather.forecast?.length ?? 0)
readonly property int dynamicItemHeight: itemCount > 0 ? Math.round((height - (itemCount - 1) * spacing) / itemCount) : 24
model: itemCount
delegate: Rectangle {
required property int index
width: dailyList.width
height: dailyList.dynamicItemHeight
radius: Theme.cornerRadius - 2
color: index === 0 ? Theme.withAlpha(root.accentColor, 0.1) : root.tileBg
property var forecast: root.weather.forecast?.[index] ?? {}
RowLayout {
anchors.fill: parent
anchors.leftMargin: root.scaledMargin
anchors.rightMargin: root.scaledMargin
spacing: root.scaledSpacing
StyledText {
text: forecast.day || "--"
font.pixelSize: forecastRoot.itemFontSize
font.weight: index === 0 ? Font.Medium : Font.Normal
color: root.textColor
Layout.preferredWidth: forecastRoot.itemFontSize * 5
}
DankIcon {
name: WeatherService.getWeatherIcon(forecast.wCode, true)
size: forecastRoot.itemIconSize + 2
color: root.accentColor
}
Item {
Layout.fillWidth: true
}
RowLayout {
spacing: 1
visible: forecast.precipitationProbability > 0
DankIcon {
name: "water_drop"
size: forecastRoot.itemIconSize - 2
color: Theme.primary
}
StyledText {
text: forecast.precipitationProbability + "%"
font.pixelSize: forecastRoot.itemFontSize - 1
color: Theme.primary
}
}
StyledText {
text: WeatherService.formatTemp(forecast.tempMax, false)
font.pixelSize: forecastRoot.itemFontSize
font.weight: Font.Medium
color: root.textColor
horizontalAlignment: Text.AlignRight
Layout.preferredWidth: forecastRoot.itemFontSize * 2.5
}
StyledText {
text: WeatherService.formatTemp(forecast.tempMin, false)
font.pixelSize: forecastRoot.itemFontSize
color: root.dimColor
horizontalAlignment: Text.AlignRight
Layout.preferredWidth: forecastRoot.itemFontSize * 2.5
}
}
}
}
}
}
component WeatherMetric: RowLayout {
property string icon: ""
property string label: ""
property string value: ""
property color accentColor: Theme.primary
property color textColor: Theme.surfaceText
property color dimColor: Theme.surfaceVariantText
property bool compact: false
property real iconSize: Theme.iconSizeSmall
property real fontSize: Theme.fontSizeSmall
spacing: 2
DankIcon {
name: parent.icon
size: compact ? parent.iconSize - 2 : parent.iconSize
color: parent.accentColor
}
ColumnLayout {
spacing: 0
visible: !compact
StyledText {
visible: parent.parent.label.length > 0
text: parent.parent.label
font.pixelSize: parent.parent.fontSize - 2
color: parent.parent.dimColor
}
StyledText {
text: parent.parent.value
font.pixelSize: parent.parent.fontSize
color: parent.parent.textColor
}
}
StyledText {
visible: compact
text: parent.value
font.pixelSize: parent.fontSize
color: parent.textColor
}
}
}

View file

@ -0,0 +1,147 @@
import QtQuick
import qs.Common
import qs.Modules.Plugins
PluginSettings {
id: root
pluginId: "dankDesktopWeather"
SelectionSetting {
settingKey: "viewMode"
label: I18n.tr("View Mode")
description: I18n.tr("Choose how the weather widget is displayed")
options: [
{
label: I18n.tr("Compact"),
value: "compact"
},
{
label: I18n.tr("Standard"),
value: "standard"
},
{
label: I18n.tr("Detailed"),
value: "detailed"
},
{
label: I18n.tr("Forecast"),
value: "forecast"
}
]
defaultValue: "standard"
}
SelectionSetting {
settingKey: "colorMode"
label: I18n.tr("Accent Color")
options: [
{
label: I18n.tr("Primary"),
value: "primary"
},
{
label: I18n.tr("Secondary"),
value: "secondary"
},
{
label: I18n.tr("Custom"),
value: "custom"
}
]
defaultValue: "primary"
}
ColorSetting {
settingKey: "customColor"
label: I18n.tr("Custom Color")
description: I18n.tr("Used when accent color is set to Custom")
defaultValue: "#4fc3f7"
}
SliderSetting {
settingKey: "backgroundOpacity"
label: I18n.tr("Background Opacity")
defaultValue: 80
minimum: 0
maximum: 100
unit: "%"
}
ToggleSetting {
settingKey: "showLocation"
label: I18n.tr("Show Location")
defaultValue: true
}
ToggleSetting {
settingKey: "showCondition"
label: I18n.tr("Show Weather Condition")
defaultValue: true
}
ToggleSetting {
settingKey: "showFeelsLike"
label: I18n.tr("Show Feels Like Temperature")
defaultValue: true
}
ToggleSetting {
settingKey: "showHumidity"
label: I18n.tr("Show Humidity")
defaultValue: true
}
ToggleSetting {
settingKey: "showWind"
label: I18n.tr("Show Wind Speed")
defaultValue: true
}
ToggleSetting {
settingKey: "showPressure"
label: I18n.tr("Show Pressure")
defaultValue: false
}
ToggleSetting {
settingKey: "showPrecipitation"
label: I18n.tr("Show Precipitation Probability")
defaultValue: true
}
ToggleSetting {
settingKey: "showSunTimes"
label: I18n.tr("Show Sunrise/Sunset")
defaultValue: true
}
ToggleSetting {
settingKey: "showForecast"
label: I18n.tr("Show Forecast")
description: I18n.tr("Available in Detailed and Forecast view modes")
defaultValue: true
}
SliderSetting {
settingKey: "forecastDays"
label: I18n.tr("Forecast Days")
defaultValue: 5
minimum: 1
maximum: 7
}
ToggleSetting {
settingKey: "showHourlyForecast"
label: I18n.tr("Show Hourly Forecast")
description: I18n.tr("Display hourly weather predictions")
defaultValue: false
}
SliderSetting {
settingKey: "hourlyCount"
label: I18n.tr("Hourly Forecast Count")
defaultValue: 6
minimum: 3
maximum: 12
}
}

View file

@ -0,0 +1,19 @@
{
"id": "dankDesktopWeather",
"name": "Dank Desktop Weather",
"description": "Feature-rich weather widget with current conditions, forecasts, and multiple view modes",
"version": "1.0.1",
"license": "MIT",
"author": "Avenge Media",
"icon": "partly_cloudy_day",
"firstParty": true,
"type": "desktop",
"capabilities": ["desktop-widget", "weather"],
"component": "./DankDesktopWeather.qml",
"settings": "./DankDesktopWeatherSettings.qml",
"requires_dms": ">=1.2.0",
"permissions": [
"settings_read",
"settings_write"
]
}

View file

@ -0,0 +1,277 @@
import QtQuick
import Quickshell.Io
import qs.Common
import qs.Services
import qs.Modules.Plugins
PluginComponent {
id: root
property bool preparingForSleep: false
property string hookWallpaperPath: pluginData.wallpaperPath || ""
property string hookLightMode: pluginData.lightMode || ""
property string hookTheme: pluginData.theme || ""
property string hookMatugenCompleted: pluginData.matugenCompleted || ""
property string hookBatteryLevel: pluginData.batteryLevel || ""
property string hookBatteryCharging: pluginData.batteryCharging || ""
property string hookBatteryPluggedIn: pluginData.batteryPluggedIn || ""
property string hookPowerRequestLock: pluginData.hookPowerRequestLock || ""
property string hookPowerMonitorOff: pluginData.hookPowerMonitorOff || ""
property string hookPowerMonitorOn: pluginData.hookPowerMonitorOn || ""
property string hookPowerSuspend: pluginData.hookPowerSuspend || ""
property string hookResumeFromSleep: pluginData.hookResumeFromSleep || ""
property string hookWifiConnected: pluginData.wifiConnected || ""
property string hookWifiSSID: pluginData.wifiSSID || ""
property string hookEthernetConnected: pluginData.ethernetConnected || ""
property string hookAudioVolume: pluginData.audioVolume || ""
property string hookAudioMute: pluginData.audioMute || ""
property string hookMicMute: pluginData.micMute || ""
property string hookBrightness: pluginData.brightness || ""
property string hookNightMode: pluginData.nightMode || ""
property string hookDoNotDisturb: pluginData.doNotDisturb || ""
property string hookMediaPlaying: pluginData.mediaPlaying || ""
property string hookIdleStateActive: pluginData.idleStateActive || ""
property string hookMonitorWallpaper: pluginData.monitorWallpaper || ""
Connections {
target: SessionData
function onWallpaperPathChanged() {
if (hookWallpaperPath) {
executeHook(hookWallpaperPath, "onWallpaperChanged", SessionData.wallpaperPath);
}
}
function onMonitorWallpapersChanged() {
if (hookMonitorWallpaper) {
const wallpapersJson = JSON.stringify(SessionData.monitorWallpapers);
executeHook(hookMonitorWallpaper, "onMonitorWallpapersChanged", wallpapersJson);
}
}
function onIsLightModeChanged() {
if (hookLightMode) {
executeHook(hookLightMode, "onLightModeChanged", SessionData.isLightMode ? "light" : "dark");
}
}
function onNightModeEnabledChanged() {
if (hookNightMode) {
executeHook(hookNightMode, "onNightModeChanged", SessionData.nightModeEnabled ? "enabled" : "disabled");
}
}
function onDoNotDisturbChanged() {
if (hookDoNotDisturb) {
executeHook(hookDoNotDisturb, "onDoNotDisturbChanged", SessionData.doNotDisturb ? "enabled" : "disabled");
}
}
}
Connections {
target: typeof Theme !== "undefined" ? Theme : null
function onCurrentThemeChanged() {
if (!hookTheme)
return;
executeHook(hookTheme, "onThemeChanged", Theme.currentTheme);
}
function onMatugenCompleted(mode, result) {
if (!hookMatugenCompleted)
return;
executeHook(hookMatugenCompleted, "onMatugenCompleted", mode + ":" + result);
}
}
Connections {
target: BatteryService.batteryAvailable ? BatteryService : null
function onBatteryLevelChanged() {
if (hookBatteryLevel) {
executeHook(hookBatteryLevel, "onBatteryLevelChanged", String(BatteryService.batteryLevel));
}
}
function onIsChargingChanged() {
if (hookBatteryCharging) {
executeHook(hookBatteryCharging, "onBatteryChargingChanged", BatteryService.isCharging ? "charging" : "not-charging");
}
}
function onIsPluggedInChanged() {
if (hookBatteryPluggedIn) {
executeHook(hookBatteryPluggedIn, "onBatteryPluggedInChanged", BatteryService.isPluggedIn ? "plugged-in" : "on-battery");
}
}
}
Connections {
target: IdleService
function onLockRequested() {
if (hookPowerRequestLock) {
executeHook(hookPowerRequestLock, "onLockRequested", "");
}
}
function onRequestMonitorOff() {
if (hookPowerMonitorOff) {
executeHook(hookPowerMonitorOff, "onRequestMonitorOff", "");
}
}
function onRequestMonitorOn() {
if (hookPowerMonitorOn) {
executeHook(hookPowerMonitorOn, "onRequestMonitorOn", "");
}
}
function onRequestSuspend() {
if (hookPowerSuspend) {
executeHook(hookPowerSuspend, "onRequestSuspend", "");
}
}
}
Connections {
target: DMSService
function onLoginctlStateUpdate(data) {
var lastState = root.preparingForSleep;
root.preparingForSleep = data.preparingForSleep;
if (lastState && !root.preparingForSleep) {
executeHook(hookResumeFromSleep, "onResumeFromSleep", "");
}
}
}
Connections {
target: NetworkService
function onWifiConnectedChanged() {
if (hookWifiConnected) {
executeHook(hookWifiConnected, "onWifiConnectedChanged", NetworkService.wifiConnected ? "connected" : "disconnected");
}
}
function onCurrentWifiSSIDChanged() {
if (hookWifiSSID) {
executeHook(hookWifiSSID, "onWifiSSIDChanged", NetworkService.currentWifiSSID || "none");
}
}
function onEthernetConnectedChanged() {
if (hookEthernetConnected) {
executeHook(hookEthernetConnected, "onEthernetConnectedChanged", NetworkService.ethernetConnected ? "connected" : "disconnected");
}
}
}
Connections {
target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null
function onVolumeChanged() {
if (hookAudioVolume && AudioService.sink && AudioService.sink.audio) {
executeHook(hookAudioVolume, "onAudioVolumeChanged", String(Math.round(AudioService.sink.audio.volume * 100)));
}
}
function onMutedChanged() {
if (hookAudioMute && AudioService.sink && AudioService.sink.audio) {
executeHook(hookAudioMute, "onAudioMuteChanged", AudioService.sink.audio.muted ? "muted" : "unmuted");
}
}
}
Connections {
target: AudioService.source && AudioService.source.audio ? AudioService.source.audio : null
function onMutedChanged() {
if (hookMicMute && AudioService.source && AudioService.source.audio) {
executeHook(hookMicMute, "onMicMuteChanged", AudioService.source.audio.muted ? "muted" : "unmuted");
}
}
}
Connections {
target: DisplayService
function onBrightnessLevelChanged() {
if (hookBrightness && DisplayService.brightnessAvailable) {
executeHook(hookBrightness, "onBrightnessChanged", String(DisplayService.brightnessLevel));
}
}
}
Connections {
target: MprisController.activePlayer
function onIsPlayingChanged() {
if (hookMediaPlaying && MprisController.activePlayer) {
executeHook(hookMediaPlaying, "onMediaPlayingChanged", MprisController.activePlayer.isPlaying ? "playing" : "paused");
}
}
}
function executeHook(scriptPath, hookName, hookValue) {
if (!scriptPath || scriptPath.trim() === "") {
return;
}
const process = hookProcessComponent.createObject(root, {
hookScript: scriptPath,
hookName: hookName,
hookValue: hookValue
});
if (!process) {
console.error("DankHooks: Failed to create process object");
return;
}
process.running = true;
}
Component {
id: hookProcessComponent
Process {
property string hookScript: ""
property string hookName: ""
property string hookValue: ""
command: ["sh", "-c", "$HOOK_SCRIPT \"$HOOK_NAME\" \"$HOOK_VALUE\""]
environment: {
"HOOK_SCRIPT": hookScript,
"HOOK_NAME": hookName,
"HOOK_VALUE": hookValue
}
stdout: StdioCollector {
onStreamFinished: {
if (text.trim()) {
console.log("DankHooks output:", text.trim());
}
}
}
stderr: StdioCollector {
onStreamFinished: {
if (text.trim()) {
ToastService.showError("Hook Script Error", text.trim());
}
}
}
onExited: exitCode => {
if (exitCode !== 0) {
ToastService.showError("Hook Script Error", `Script '${hookScript}' exited with code: ${exitCode}`);
}
destroy();
}
}
}
Component.onDestruction: {
console.log("DankHooks: Stopped monitoring system events");
}
}

View file

@ -0,0 +1,332 @@
import QtQuick
import qs.Common
import qs.Widgets
import qs.Modules.Plugins
PluginSettings {
id: root
pluginId: "dankHooks"
StyledText {
text: "System Event Hooks"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
}
StyledText {
text: "Execute custom scripts when system events occur. Scripts receive two arguments: hook name (e.g., 'onBatteryLevelChanged') and event value."
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
wrapMode: Text.WordWrap
}
StyledRect {
width: parent.width
height: 1
color: Theme.surfaceVariant
}
StyledText {
text: "Appearance & Theme"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.DemiBold
color: Theme.surfaceText
}
StringSetting {
settingKey: "wallpaperPath"
label: "Wallpaper Changed"
description: "Hook: onWallpaperChanged | Value: wallpaper file path"
placeholder: "/path/to/wallpaper-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "monitorWallpaper"
label: "Per-Monitor Wallpapers Changed"
description: "Hook: onMonitorWallpapersChanged | Value: JSON object with all monitors (e.g., '{\"eDP-1\":\"/path1.jpg\",\"DP-2\":\"/path2.jpg\"}')"
placeholder: "/path/to/monitor-wallpaper-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "lightMode"
label: "Light/Dark Mode Changed"
description: "Hook: onLightModeChanged | Value: 'light' or 'dark'"
placeholder: "/path/to/mode-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "theme"
label: "Theme Changed"
description: "Hook: onThemeChanged | Value: theme name (e.g., 'blue', 'red', 'dynamic')"
placeholder: "/path/to/theme-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "matugenCompleted"
label: "Matugen Generation Completed"
description: "Hook: onMatugenCompleted | Value: '<mode>:<result>' (e.g., 'dark:success', 'light:no-changes')"
placeholder: "/path/to/matugen-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "nightMode"
label: "Night Mode Changed"
description: "Hook: onNightModeChanged | Value: 'enabled' or 'disabled'"
placeholder: "/path/to/nightmode-hook.sh"
defaultValue: ""
}
StyledRect {
width: parent.width
height: 1
color: Theme.surfaceVariant
}
StyledText {
text: "Power & Battery"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.DemiBold
color: Theme.surfaceText
}
StringSetting {
settingKey: "batteryLevel"
label: "Battery Level Changed"
description: "Hook: onBatteryLevelChanged | Value: percentage (0-100)"
placeholder: "/path/to/battery-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "batteryCharging"
label: "Battery Charging State Changed"
description: "Hook: onBatteryChargingChanged | Value: 'charging' or 'not-charging'"
placeholder: "/path/to/charging-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "batteryPluggedIn"
label: "Power Adapter Changed"
description: "Hook: onBatteryPluggedInChanged | Value: 'plugged-in' or 'on-battery'"
placeholder: "/path/to/power-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "hookPowerRequestLock"
label: "Lock Screen Event Triggered"
description: "Hook: onLockRequested | Value: empty"
placeholder: "/path/to/sessionlock-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "hookPowerMonitorOff"
label: "Monitor Off Event Triggered"
description: "Hook: onRequestMonitorOff | Value: empty"
placeholder: "/path/to/monitoroff-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "hookPowerMonitorOn"
label: "Monitor On Event Triggered"
description: "Hook: onRequestMonitorOn | Value: empty"
placeholder: "/path/to/monitoron-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "hookPowerSuspend"
label: "Suspend Event Triggered"
description: "Hook: onRequestSuspend | Value: empty"
placeholder: "/path/to/suspend-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "hookResumeFromSleep"
label: "Resume From Sleep Event Triggered"
description: "Hook: onResumeFromSleep | Value: empty"
placeholder: "/path/to/resumeFromSleep-hook.sh"
defaultValue: ""
}
StyledRect {
width: parent.width
height: 1
color: Theme.surfaceVariant
}
StyledText {
text: "Network"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.DemiBold
color: Theme.surfaceText
}
StringSetting {
settingKey: "wifiConnected"
label: "WiFi Connection Changed"
description: "Hook: onWifiConnectedChanged | Value: 'connected' or 'disconnected'"
placeholder: "/path/to/wifi-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "wifiSSID"
label: "WiFi Network Changed"
description: "Hook: onWifiSSIDChanged | Value: SSID name or 'none'"
placeholder: "/path/to/ssid-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "ethernetConnected"
label: "Ethernet Connection Changed"
description: "Hook: onEthernetConnectedChanged | Value: 'connected' or 'disconnected'"
placeholder: "/path/to/ethernet-hook.sh"
defaultValue: ""
}
StyledRect {
width: parent.width
height: 1
color: Theme.surfaceVariant
}
StyledText {
text: "Audio"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.DemiBold
color: Theme.surfaceText
}
StringSetting {
settingKey: "audioVolume"
label: "Audio Volume Changed"
description: "Hook: onAudioVolumeChanged | Value: percentage (0-100)"
placeholder: "/path/to/volume-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "audioMute"
label: "Audio Mute Changed"
description: "Hook: onAudioMuteChanged | Value: 'muted' or 'unmuted'"
placeholder: "/path/to/mute-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "micMute"
label: "Microphone Mute Changed"
description: "Hook: onMicMuteChanged | Value: 'muted' or 'unmuted'"
placeholder: "/path/to/mic-hook.sh"
defaultValue: ""
}
StyledRect {
width: parent.width
height: 1
color: Theme.surfaceVariant
}
StyledText {
text: "Display & Media"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.DemiBold
color: Theme.surfaceText
}
StringSetting {
settingKey: "brightness"
label: "Brightness Changed"
description: "Hook: onBrightnessChanged | Value: percentage (0-100)"
placeholder: "/path/to/brightness-hook.sh"
defaultValue: ""
}
StringSetting {
settingKey: "mediaPlaying"
label: "Media Playback Changed"
description: "Hook: onMediaPlayingChanged | Value: 'playing' or 'paused'"
placeholder: "/path/to/media-hook.sh"
defaultValue: ""
}
StyledRect {
width: parent.width
height: 1
color: Theme.surfaceVariant
}
StyledText {
text: "System"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.DemiBold
color: Theme.surfaceText
}
StringSetting {
settingKey: "doNotDisturb"
label: "Do Not Disturb Changed"
description: "Hook: onDoNotDisturbChanged | Value: 'enabled' or 'disabled'"
placeholder: "/path/to/dnd-hook.sh"
defaultValue: ""
}
StyledRect {
width: parent.width
height: 1
color: Theme.surfaceVariant
}
StyledText {
text: "Hook Script Examples"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.DemiBold
color: Theme.surfaceText
}
StyledText {
text: "Example hook script:"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledRect {
width: parent.width
height: exampleCode.height + 16
color: Theme.surface
radius: Theme.cornerRadius
StyledText {
id: exampleCode
anchors.centerIn: parent
anchors.margins: 8
width: parent.width - 16
text: '#!/bin/bash\n# Save as ~/.config/scripts/hook.sh\n# Make executable: chmod +x ~/.config/scripts/hook.sh\n\nHOOK_NAME="$1" # e.g., "onWallpaperChanged"\nHOOK_VALUE="$2" # e.g., "/path/to/wallpaper.jpg"\n\necho "Hook: $HOOK_NAME, Value: $HOOK_VALUE"\nnotify-send "$HOOK_NAME" "$HOOK_VALUE"'
font.pixelSize: Theme.fontSizeSmall
font.family: "monospace"
color: Theme.surfaceText
wrapMode: Text.WordWrap
}
}
StyledText {
text: "All hooks pass two arguments: $1 = hook name (e.g., 'onBatteryLevelChanged'), $2 = event value. See descriptions above for each hook's values."
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
wrapMode: Text.WordWrap
}
}

View file

@ -0,0 +1,50 @@
# Dank Hooks Plugin
## Available Hooks
### Appearance & Theme
| Hook | Trigger | Hook Name | Value |
|------|---------|-----------|-------|
| **Wallpaper Changed** | When wallpaper changes | `onWallpaperChanged` | Wallpaper file path |
| **Light/Dark Mode Changed** | When switching between modes | `onLightModeChanged` | `light` or `dark` |
| **Theme Changed** | When color theme changes | `onThemeChanged` | Theme name (e.g., `blue`, `red`, `dynamic`) |
| **Matugen Completed** | When matugen finishes generating colors | `onMatugenCompleted` | `<mode>:<result>` (e.g., `dark:success`, `light:no-changes`, `dark:error`) |
| **Night Mode Changed** | When night mode toggles | `onNightModeChanged` | `enabled` or `disabled` |
### Power & Battery
| Hook | Trigger | Hook Name | Value |
|------|---------|-----------|-------|
| **Battery Level Changed** | When battery percentage changes | `onBatteryLevelChanged` | Battery percentage (0-100) |
| **Battery Charging State Changed** | When charging state changes | `onBatteryChargingChanged` | `charging` or `not-charging` |
| **Power Adapter Changed** | When power adapter connects/disconnects | `onBatteryPluggedInChanged` | `plugged-in` or `on-battery` |
### Network
| Hook | Trigger | Hook Name | Value |
|------|---------|-----------|-------|
| **WiFi Connection Changed** | When WiFi connects/disconnects | `onWifiConnectedChanged` | `connected` or `disconnected` |
| **WiFi Network Changed** | When connected WiFi network changes | `onWifiSSIDChanged` | SSID name or `none` |
| **Ethernet Connection Changed** | When Ethernet connects/disconnects | `onEthernetConnectedChanged` | `connected` or `disconnected` |
### Audio
| Hook | Trigger | Hook Name | Value |
|------|---------|-----------|-------|
| **Audio Volume Changed** | When speaker volume changes | `onAudioVolumeChanged` | Volume percentage (0-100) |
| **Audio Mute Changed** | When speakers mute/unmute | `onAudioMuteChanged` | `muted` or `unmuted` |
| **Microphone Mute Changed** | When microphone mutes/unmutes | `onMicMuteChanged` | `muted` or `unmuted` |
### Display & Media
| Hook | Trigger | Hook Name | Value |
|------|---------|-----------|-------|
| **Brightness Changed** | When screen brightness changes | `onBrightnessChanged` | Brightness percentage (0-100) |
| **Media Playback Changed** | When media starts/stops playing | `onMediaPlayingChanged` | `playing` or `paused` |
### System
| Hook | Trigger | Hook Name | Value |
|------|---------|-----------|-------|
| **Do Not Disturb Changed** | When DND mode toggles | `onDoNotDisturbChanged` | `enabled` or `disabled` |

View file

@ -0,0 +1,16 @@
{
"id": "dankHooks",
"name": "Dank Hooks",
"description": "Execute custom scripts on system events like wallpaper changes, theme updates, battery level changes, and more",
"version": "1.0.7",
"license": "MIT",
"author": "Avenge Media",
"icon": "webhook",
"type": "daemon",
"component": "./DankHooks.qml",
"settings": "./DankHooksSettings.qml",
"permissions": [
"settings_read",
"settings_write"
]
}

View file

@ -0,0 +1,156 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
PluginSettings {
id: root
pluginId: "dankPomodoroTimer"
StyledText {
width: parent.width
text: "Pomodoro Settings"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
}
StyledText {
width: parent.width
text: "Configure timer durations and behavior"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
StyledRect {
width: parent.width
height: durationsColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
id: durationsColumn
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
StyledText {
text: "Durations"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
StringSetting {
settingKey: "workDuration"
label: "Work Duration (minutes)"
description: "Length of each focus session"
placeholder: "25"
defaultValue: "25"
}
StringSetting {
settingKey: "shortBreakDuration"
label: "Short Break (minutes)"
description: "Break after each pomodoro"
placeholder: "5"
defaultValue: "5"
}
StringSetting {
settingKey: "longBreakDuration"
label: "Long Break (minutes)"
description: "Break after 4 pomodoros"
placeholder: "15"
defaultValue: "15"
}
}
}
StyledRect {
width: parent.width
height: behaviorColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
id: behaviorColumn
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
StyledText {
text: "Behavior"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
ToggleSetting {
settingKey: "autoStartBreaks"
label: "Auto-start Breaks"
description: "Automatically start break timers after work sessions"
defaultValue: false
}
ToggleSetting {
settingKey: "autoStartPomodoros"
label: "Auto-start Pomodoros"
description: "Automatically start work sessions after breaks"
defaultValue: false
}
ToggleSetting {
settingKey: "autoSetDND"
label: "Do Not Disturb Work"
description: "Automatically enable Do Not Disturb mode during work sessions"
defaultValue: false
}
}
}
StyledRect {
width: parent.width
height: infoColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surface
Column {
id: infoColumn
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
spacing: Theme.spacingM
DankIcon {
name: "info"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "About Pomodoro Technique"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
text: "The Pomodoro Technique uses 25-minute focused work sessions followed by short breaks. After 4 pomodoros, take a longer break to recharge.\n\n• Work: 25 minutes of focused work\n• Short Break: 5 minute rest\n• Long Break: 15 minutes after 4 pomodoros\n\nNotifications will alert you when each session completes."
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
lineHeight: 1.4
}
}
}
}

View file

@ -0,0 +1,497 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
PluginComponent {
id: root
property int workDuration: pluginData.workDuration || 25
property int shortBreakDuration: pluginData.shortBreakDuration || 5
property int longBreakDuration: pluginData.longBreakDuration || 15
property bool autoStartBreaks: pluginData.autoStartBreaks ?? false
property bool autoStartPomodoros: pluginData.autoStartPomodoros ?? false
property bool autoSetDND: pluginData.autoSetDND ?? false
onWorkDurationChanged: {
if (globalTimerState.value === "work" && globalTotalSeconds.value > 0) {
const newTotal = workDuration * 60
const elapsed = globalTotalSeconds.value - globalRemainingSeconds.value
globalTotalSeconds.set(newTotal)
globalRemainingSeconds.set(Math.max(1, newTotal - elapsed))
}
}
onShortBreakDurationChanged: {
if (globalTimerState.value === "shortBreak" && globalTotalSeconds.value > 0) {
const newTotal = shortBreakDuration * 60
const elapsed = globalTotalSeconds.value - globalRemainingSeconds.value
globalTotalSeconds.set(newTotal)
globalRemainingSeconds.set(Math.max(1, newTotal - elapsed))
}
}
onLongBreakDurationChanged: {
if (globalTimerState.value === "longBreak" && globalTotalSeconds.value > 0) {
const newTotal = longBreakDuration * 60
const elapsed = globalTotalSeconds.value - globalRemainingSeconds.value
globalTotalSeconds.set(newTotal)
globalRemainingSeconds.set(Math.max(1, newTotal - elapsed))
}
}
PluginGlobalVar {
id: globalRemainingSeconds
varName: "remainingSeconds"
defaultValue: 0
}
PluginGlobalVar {
id: globalTotalSeconds
varName: "totalSeconds"
defaultValue: 0
}
PluginGlobalVar {
id: globalIsRunning
varName: "isRunning"
defaultValue: false
}
PluginGlobalVar {
id: globalTimerState
varName: "timerState"
defaultValue: "work"
}
PluginGlobalVar {
id: globalCompletedPomodoros
varName: "completedPomodoros"
defaultValue: 0
}
PluginGlobalVar {
id: globalTimerOwnerId
varName: "timerOwnerId"
defaultValue: ""
}
property string instanceId: Math.random().toString(36).substring(2)
Timer {
id: pomodoroTimer
interval: 1000
repeat: true
running: globalIsRunning.value && globalTimerOwnerId.value === root.instanceId
onTriggered: {
if (globalRemainingSeconds.value > 0) {
globalRemainingSeconds.set(globalRemainingSeconds.value - 1)
} else {
root.timerComplete()
}
}
}
function timerComplete() {
globalIsRunning.set(false)
if (globalTimerState.value === "work") {
globalCompletedPomodoros.set(globalCompletedPomodoros.value + 1)
const isLongBreak = globalCompletedPomodoros.value % 4 === 0
Quickshell.execDetached(["sh", "-c", "notify-send 'Pomodoro Complete' 'Time for a " + (isLongBreak ? "long" : "short") + " break!' -u normal"])
if (root.autoSetDND) {
SessionData.setDoNotDisturb(false)
}
if (isLongBreak) {
root.startLongBreak(root.autoStartBreaks)
} else {
root.startShortBreak(root.autoStartBreaks)
}
} else {
Quickshell.execDetached(["sh", "-c", "notify-send 'Break Complete' 'Ready for another pomodoro?' -u normal"])
root.startWork(root.autoStartPomodoros)
}
}
function startWork(autoStart) {
globalTimerState.set("work")
globalTotalSeconds.set(root.workDuration * 60)
globalRemainingSeconds.set(globalTotalSeconds.value)
if (autoStart) {
globalTimerOwnerId.set(root.instanceId)
if (root.autoSetDND) {
SessionData.setDoNotDisturb(true)
}
}
globalIsRunning.set(autoStart ?? false)
}
function startShortBreak(autoStart) {
if(globalTimerState.value === "work" && root.autoSetDND) {
SessionData.setDoNotDisturb(false)
}
globalTimerState.set("shortBreak")
globalTotalSeconds.set(root.shortBreakDuration * 60)
globalRemainingSeconds.set(globalTotalSeconds.value)
if (autoStart) {
globalTimerOwnerId.set(root.instanceId)
}
globalIsRunning.set(autoStart ?? false)
}
function startLongBreak(autoStart) {
if(globalTimerState.value === "work" && root.autoSetDND) {
SessionData.setDoNotDisturb(false)
}
globalTimerState.set("longBreak")
globalTotalSeconds.set(root.longBreakDuration * 60)
globalRemainingSeconds.set(globalTotalSeconds.value)
if (autoStart) {
globalTimerOwnerId.set(root.instanceId)
}
globalIsRunning.set(autoStart ?? false)
}
function toggleTimer() {
if (!globalIsRunning.value) {
globalTimerOwnerId.set(root.instanceId)
}
globalIsRunning.set(!globalIsRunning.value)
if (root.autoSetDND && globalTimerState.value === "work") {
SessionData.setDoNotDisturb(globalIsRunning.value)
}
}
function resetTimer() {
globalIsRunning.set(false)
if (root.autoSetDND && globalTimerState.value === "work") {
SessionData.setDoNotDisturb(false)
}
globalRemainingSeconds.set(globalTotalSeconds.value)
}
function formatTime(seconds, isVertical = false) {
const mins = Math.floor(seconds / 60)
const secs = seconds % 60
return isVertical ? mins + "\n" + (secs < 10 ? "0" : "") + secs : mins + " " + (secs < 10 ? "0" : "") + secs
}
function getStateColor() {
if (globalTimerState.value === "work") return Theme.primary
if (globalTimerState.value === "shortBreak") return Theme.success
return Theme.warning
}
function getStateIcon() {
if (globalTimerState.value === "work") return "work"
return "coffee"
}
IpcHandler {
function resetTimer(): string {
root.resetTimer()
return "POMDORO_TIME_RESET_SUCCESS"
}
function toggleTimer(): string {
root.toggleTimer()
return globalIsRunning.value ? "Timer is running" : "Timer is paused"
}
function startWork(): string {
root.startWork(true)
return "POMODORO_WORK_STARTED"
}
function startShortBreak(): string {
root.startShortBreak(true)
return "POMODORO_SHORT_BREAK_STARTED"
}
function startLongBreak(): string {
root.startLongBreak(true)
return "POMODORO_LONG_BREAK_STARTED"
}
target: "pomodoroTimer"
}
Timer {
id: initTimer
interval: 100
repeat: false
running: true
onTriggered: {
if (globalRemainingSeconds.value === 0 && globalTotalSeconds.value === 0) {
root.startWork(false)
}
}
}
horizontalBarPill: Component {
Row {
spacing: Theme.spacingXS
DankIcon {
name: root.getStateIcon()
size: Theme.iconSize - 6
color: root.getStateColor()
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: root.formatTime(globalRemainingSeconds.value)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
}
}
verticalBarPill: Component {
Column {
spacing: Theme.spacingXS
DankIcon {
name: root.getStateIcon()
size: Theme.iconSize - 6
color: root.getStateColor()
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: root.formatTime(globalRemainingSeconds.value, true)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
popoutContent: Component {
PopoutComponent {
id: popout
headerText: "Pomodoro Timer"
detailsText: {
if (globalTimerState.value === "work") return "Focus session • " + globalCompletedPomodoros.value + " completed"
if (globalTimerState.value === "shortBreak") return "Short break"
return "Long break"
}
showCloseButton: true
Column {
id: popoutContentColumn
width: parent.width
spacing: Theme.spacingM
Item {
width: parent.width
height: 180
Rectangle {
width: 180
height: 180
radius: 90
anchors.centerIn: parent
color: "transparent"
border.width: 8
border.color: Qt.rgba(root.getStateColor().r, root.getStateColor().g, root.getStateColor().b, 0.2)
Canvas {
id: progressCanvas
width: parent.width - 16
height: parent.height - 16
anchors.centerIn: parent
onPaint: {
var ctx = getContext("2d")
ctx.clearRect(0, 0, width, height)
ctx.lineWidth = 8
ctx.strokeStyle = root.getStateColor()
ctx.beginPath()
const centerX = width / 2
const centerY = height / 2
const radius = (width - 8) / 2
const progress = globalRemainingSeconds.value / globalTotalSeconds.value
const startAngle = -Math.PI / 2
const endAngle = startAngle + (2 * Math.PI * progress)
ctx.arc(centerX, centerY, radius, startAngle, endAngle, false)
ctx.stroke()
}
Connections {
target: globalRemainingSeconds
function onValueChanged() {
progressCanvas.requestPaint()
}
}
}
Column {
anchors.centerIn: parent
spacing: Theme.spacingXS
StyledText {
text: root.formatTime(globalRemainingSeconds.value)
font.pixelSize: 36
font.weight: Font.Bold
color: root.getStateColor()
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
width: 120
}
StyledText {
text: {
if (globalTimerState.value === "work") return "Work"
if (globalTimerState.value === "shortBreak") return "Short Break"
return "Long Break"
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingM
Rectangle {
width: 64
height: 64
radius: 32
color: playArea.containsMouse ? Qt.rgba(root.getStateColor().r, root.getStateColor().g, root.getStateColor().b, 0.2) : "transparent"
DankIcon {
anchors.centerIn: parent
name: globalIsRunning.value ? "pause" : "play_arrow"
size: 32
color: root.getStateColor()
}
MouseArea {
id: playArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.toggleTimer()
}
}
Rectangle {
width: 64
height: 64
radius: 32
color: resetArea.containsMouse ? Theme.surfaceContainerHighest : "transparent"
DankIcon {
anchors.centerIn: parent
name: "refresh"
size: 24
color: Theme.surfaceText
}
MouseArea {
id: resetArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.resetTimer()
}
}
}
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: "Quick Actions"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
Row {
id: quickActionsRow
width: parent.width
spacing: Theme.spacingS
property real buttonWidth: (width - spacing * 2) / 3
DankButton {
text: "Work"
iconName: "work"
width: quickActionsRow.buttonWidth
onClicked: root.startWork(false)
}
DankButton {
text: "Short Break"
iconName: "coffee"
width: quickActionsRow.buttonWidth
onClicked: root.startShortBreak(false)
}
DankButton {
text: "Long Break"
iconName: "weekend"
width: quickActionsRow.buttonWidth
onClicked: root.startLongBreak(false)
}
}
}
StyledRect {
width: parent.width
height: statsColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
id: statsColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingXS
Row {
spacing: Theme.spacingM
DankIcon {
name: "check_circle"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: globalCompletedPomodoros.value + " pomodoros completed"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
text: "Next long break after " + (4 - (globalCompletedPomodoros.value % 4)) + " more"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
leftPadding: Theme.iconSize + Theme.spacingM
}
}
}
}
}
}
}

View file

@ -0,0 +1,16 @@
{
"id": "dankPomodoroTimer",
"name": "Dank Pomodoro Timer",
"description": "Productivity timer with 25-minute work sessions and breaks",
"version": "1.1.5",
"license": "MIT",
"author": "Avenge Media",
"icon": "timer",
"firstParty": true,
"component": "./DankPomodoroWidget.qml",
"settings": "./DankPomodoroSettings.qml",
"permissions": [
"settings_read",
"settings_write"
]
}

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Avenge Media LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,31 @@
# DankMaterialShell plugins
A collection of first-party, officially maintained plugins for [DankMaterialShell](https://github.com/AvengeMedia/DankMaterialShell)
## Plugins
### [Dank Actions](./DankActions)
Scriptable, custom actions that can be added to the DankBar.
Allows for creating multiple widgets that execute custom script actions on click, or periodically.
<img width="488" height="638" alt="image" src="https://github.com/user-attachments/assets/36b44c32-69b5-49c9-97d2-87f530e4b7fd" />
### [Dank Pomodoro Timer](./DankPomodoroTimer)
A timer that is intended to improve productivity using the [Pomodoro technique](https://en.wikipedia.org/wiki/Pomodoro_Technique).
<img width="442" height="545" alt="image" src="https://github.com/user-attachments/assets/b51b5f78-5215-403c-850f-c7e137097438" />
### [Dank Hooks](./DankHooks)
Trigger scripts based on various system events, such as `onWallpaperChanged`, `onVolumeChanged`, etc.
<img width="472" height="593" alt="image" src="https://github.com/user-attachments/assets/83e89b5b-0636-4b8e-ba29-1fa4d12169a0" />
### [Dank Battery Alerts](./DankBatteryAlerts)
Trigger notifications when battery reaches low charge levels.
<img width="497" height="710" alt="image" src="https://github.com/user-attachments/assets/4302d886-eb87-41d4-a9a4-1eeaadd787c6" />

View file

@ -0,0 +1 @@
ref: refs/heads/master

View file

@ -0,0 +1,9 @@
[core]
bare = false
filemode = true
[remote "origin"]
url = https://github.com/AvengeMedia/dms-plugins
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master

View file

@ -0,0 +1 @@
87870d8b70758f08aa8fd5401a0e4eec3941ed48

View file

@ -0,0 +1 @@
87870d8b70758f08aa8fd5401a0e4eec3941ed48

View file

@ -0,0 +1,54 @@
# C++ objects and libs
*.slo
*.lo
*.o
*.a
*.la
*.lai
*.so
*.so.*
*.dll
*.dylib
# Qt-es
object_script.*.Release
object_script.*.Debug
*_plugin_import.cpp
/.qmake.cache
/.qmake.stash
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
moc_*.cpp
moc_*.h
qrc_*.cpp
ui_*.h
*.qmlc
*.jsc
Makefile*
*build-*
*.qm
*.prl
# Qt unit tests
target_wrapper.*
# QtCreator
*.autosave
# QtCreator Qml
*.qmlproject.user
*.qmlproject.user.*
# QtCreator CMake
CMakeLists.txt.user*
# QtCreator 4.8< compilation database
compile_commands.json
# QtCreator local machine specific files for imported projects
*creator.user*
*_qmlcache.qrc