mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 05:25:41 -05:00
switch hto monorepo structure
This commit is contained in:
32
quickshell/PLUGINS/ColorDemoPlugin/ColorDemoSettings.qml
Normal file
32
quickshell/PLUGINS/ColorDemoPlugin/ColorDemoSettings.qml
Normal file
@@ -0,0 +1,32 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Plugins
|
||||
import qs.Widgets
|
||||
|
||||
PluginSettings {
|
||||
id: root
|
||||
pluginId: "colorDemo"
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: "Color Demo Settings"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: "Choose a custom color to display in the bar widget"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
ColorSetting {
|
||||
settingKey: "customColor"
|
||||
label: "Custom Color"
|
||||
description: "Choose a custom color to display in the widget"
|
||||
defaultValue: Theme.primary
|
||||
}
|
||||
}
|
||||
57
quickshell/PLUGINS/ColorDemoPlugin/ColorDemoWidget.qml
Normal file
57
quickshell/PLUGINS/ColorDemoPlugin/ColorDemoWidget.qml
Normal file
@@ -0,0 +1,57 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginComponent {
|
||||
id: root
|
||||
|
||||
property color customColor: pluginData.customColor || Theme.primary
|
||||
|
||||
horizontalBarPill: Component {
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 4
|
||||
color: root.customColor
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.customColor.toString()
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
verticalBarPill: Component {
|
||||
Column {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Rectangle {
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 4
|
||||
color: root.customColor
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 1
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.customColor.toString()
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
quickshell/PLUGINS/ColorDemoPlugin/plugin.json
Normal file
12
quickshell/PLUGINS/ColorDemoPlugin/plugin.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"id": "colorDemo",
|
||||
"name": "Color Demo",
|
||||
"description": "Demonstrates color picker plugin setting",
|
||||
"version": "1.0.0",
|
||||
"author": "DMS",
|
||||
"icon": "palette",
|
||||
"type": "widget",
|
||||
"component": "./ColorDemoWidget.qml",
|
||||
"settings": "./ColorDemoSettings.qml",
|
||||
"permissions": ["settings_read", "settings_write"]
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginComponent {
|
||||
id: root
|
||||
|
||||
property bool isEnabled: pluginData.isEnabled || false
|
||||
property var options: pluginData.options || ["Option A", "Option B", "Option C"]
|
||||
|
||||
ccWidgetIcon: isEnabled ? "settings" : "settings"
|
||||
ccWidgetPrimaryText: "Detail Example"
|
||||
ccWidgetSecondaryText: {
|
||||
if (isEnabled) {
|
||||
const selected = pluginData.selectedOption || "Option A"
|
||||
return selected
|
||||
}
|
||||
return "Disabled"
|
||||
}
|
||||
ccWidgetIsActive: isEnabled
|
||||
|
||||
onCcWidgetToggled: {
|
||||
isEnabled = !isEnabled
|
||||
if (pluginService) {
|
||||
pluginService.savePluginData("controlCenterDetailExample", "isEnabled", isEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
ccDetailContent: Component {
|
||||
Rectangle {
|
||||
id: detailRoot
|
||||
implicitHeight: detailColumn.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.width: 0
|
||||
visible: true
|
||||
|
||||
property var options: ["Option A", "Option B", "Option C"]
|
||||
property string currentSelection: SettingsData.getPluginSetting("controlCenterDetailExample", "selectedOption", "Option A")
|
||||
|
||||
Column {
|
||||
id: detailColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: "Detail Example Settings"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Select an option below:"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: detailRoot.options
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: optionMouseArea.containsMouse ? Theme.surfaceContainerHighest : "transparent"
|
||||
border.color: detailRoot.currentSelection === modelData ? Theme.primary : "transparent"
|
||||
border.width: detailRoot.currentSelection === modelData ? 2 : 0
|
||||
|
||||
MouseArea {
|
||||
id: optionMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
z: 100
|
||||
onClicked: {
|
||||
SettingsData.setPluginSetting("controlCenterDetailExample", "selectedOption", modelData)
|
||||
detailRoot.currentSelection = modelData
|
||||
PluginService.pluginDataChanged("controlCenterDetailExample")
|
||||
ToastService.showInfo("Option Selected", modelData)
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
enabled: false
|
||||
|
||||
DankIcon {
|
||||
name: detailRoot.currentSelection === modelData ? "radio_button_checked" : "radio_button_unchecked"
|
||||
color: detailRoot.currentSelection === modelData ? Theme.primary : Theme.surfaceVariantText
|
||||
size: Theme.iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData
|
||||
color: detailRoot.currentSelection === modelData ? Theme.primary : Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
horizontalBarPill: Component {
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: root.isEnabled ? "settings" : "settings_off"
|
||||
color: root.isEnabled ? Theme.primary : Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.iconSize - 4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const selected = root.pluginData.selectedOption || "Option A"
|
||||
return selected.substring(0, 1)
|
||||
}
|
||||
color: root.isEnabled ? Theme.primary : Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
quickshell/PLUGINS/ControlCenterDetailExample/plugin.json
Normal file
12
quickshell/PLUGINS/ControlCenterDetailExample/plugin.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"id": "controlCenterDetailExample",
|
||||
"name": "CC Detail Example",
|
||||
"description": "Example plugin with Control Center detail dropdown",
|
||||
"version": "1.0.0",
|
||||
"author": "DankMaterialShell",
|
||||
"type": "widget",
|
||||
"capabilities": ["control-center"],
|
||||
"component": "./DetailExampleWidget.qml",
|
||||
"icon": "settings",
|
||||
"permissions": ["settings_read", "settings_write"]
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginComponent {
|
||||
id: root
|
||||
|
||||
property bool isEnabled: pluginData.isEnabled || false
|
||||
property int clickCount: pluginData.clickCount || 0
|
||||
|
||||
ccWidgetIcon: isEnabled ? "toggle_on" : "toggle_off"
|
||||
ccWidgetPrimaryText: "Example Toggle"
|
||||
ccWidgetSecondaryText: isEnabled ? `Active • ${clickCount} clicks` : "Inactive"
|
||||
ccWidgetIsActive: isEnabled
|
||||
|
||||
onCcWidgetToggled: {
|
||||
isEnabled = !isEnabled
|
||||
clickCount += 1
|
||||
if (pluginService) {
|
||||
pluginService.savePluginData("controlCenterExample", "isEnabled", isEnabled)
|
||||
pluginService.savePluginData("controlCenterExample", "clickCount", clickCount)
|
||||
}
|
||||
ToastService.showInfo("Example Toggle", isEnabled ? "Activated!" : "Deactivated!")
|
||||
}
|
||||
|
||||
horizontalBarPill: Component {
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: root.isEnabled ? "toggle_on" : "toggle_off"
|
||||
color: root.isEnabled ? Theme.primary : Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.iconSize - 4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: `${root.clickCount}`
|
||||
color: root.isEnabled ? Theme.primary : Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
verticalBarPill: Component {
|
||||
Column {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: root.isEnabled ? "toggle_on" : "toggle_off"
|
||||
color: root.isEnabled ? Theme.primary : Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.iconSize - 4
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: `${root.clickCount}`
|
||||
color: root.isEnabled ? Theme.primary : Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
quickshell/PLUGINS/ControlCenterExample/plugin.json
Normal file
12
quickshell/PLUGINS/ControlCenterExample/plugin.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"id": "controlCenterExample",
|
||||
"name": "CC Toggle Example",
|
||||
"description": "Example plugin with Control Center toggle widget",
|
||||
"version": "1.0.0",
|
||||
"author": "DankMaterialShell",
|
||||
"type": "widget",
|
||||
"capabilities": ["control-center"],
|
||||
"component": "./ControlCenterExampleWidget.qml",
|
||||
"icon": "toggle_on",
|
||||
"permissions": ["settings_read", "settings_write"]
|
||||
}
|
||||
97
quickshell/PLUGINS/ExampleEmojiPlugin/EmojiSettings.qml
Normal file
97
quickshell/PLUGINS/ExampleEmojiPlugin/EmojiSettings.qml
Normal file
@@ -0,0 +1,97 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginSettings {
|
||||
id: root
|
||||
pluginId: "exampleEmojiPlugin"
|
||||
|
||||
// Header section to explain what this plugin does
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: "Emoji Cycler Settings"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: "Configure which emojis appear in your bar, how quickly they cycle, and how many show at once."
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
// Dropdown to select which emoji set to use
|
||||
SelectionSetting {
|
||||
settingKey: "emojiSet"
|
||||
label: "Emoji Set"
|
||||
description: "Choose which collection of emojis to cycle through"
|
||||
options: [
|
||||
{label: "Happy & Sad", value: "happySad"},
|
||||
{label: "Hearts", value: "hearts"},
|
||||
{label: "Hand Gestures", value: "hands"},
|
||||
{label: "All Mixed", value: "mixed"}
|
||||
]
|
||||
defaultValue: "happySad"
|
||||
|
||||
// Update the actual emoji array when selection changes
|
||||
onValueChanged: {
|
||||
const sets = {
|
||||
"happySad": ["😊", "😢", "😂", "😭", "😍", "😡"],
|
||||
"hearts": ["❤️", "🧡", "💛", "💚", "💙", "💜", "🖤", "🤍"],
|
||||
"hands": ["👍", "👎", "👊", "✌️", "🤘", "👌", "✋", "🤚"],
|
||||
"mixed": ["😊", "❤️", "👍", "🎉", "🔥", "✨", "🌟", "💯"]
|
||||
}
|
||||
const newEmojis = sets[value] || sets["happySad"]
|
||||
root.saveValue("emojis", newEmojis)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Initialize the emojis array on first load
|
||||
const currentSet = value || defaultValue
|
||||
const sets = {
|
||||
"happySad": ["😊", "😢", "😂", "😭", "😍", "😡"],
|
||||
"hearts": ["❤️", "🧡", "💛", "💚", "💙", "💜", "🖤", "🤍"],
|
||||
"hands": ["👍", "👎", "👊", "✌️", "🤘", "👌", "✋", "🤚"],
|
||||
"mixed": ["😊", "❤️", "👍", "🎉", "🔥", "✨", "🌟", "💯"]
|
||||
}
|
||||
const emojis = sets[currentSet] || sets["happySad"]
|
||||
root.saveValue("emojis", emojis)
|
||||
}
|
||||
}
|
||||
|
||||
// Slider to control how fast emojis cycle (in milliseconds)
|
||||
SliderSetting {
|
||||
settingKey: "cycleInterval"
|
||||
label: "Cycle Speed"
|
||||
description: "How quickly emojis rotate (in seconds)"
|
||||
defaultValue: 3000
|
||||
minimum: 500
|
||||
maximum: 10000
|
||||
unit: "ms"
|
||||
leftIcon: "schedule"
|
||||
}
|
||||
|
||||
// Slider to control max emojis shown in the bar
|
||||
SliderSetting {
|
||||
settingKey: "maxBarEmojis"
|
||||
label: "Max Bar Emojis"
|
||||
description: "Maximum number of emojis to display in the bar at once"
|
||||
defaultValue: 3
|
||||
minimum: 1
|
||||
maximum: 8
|
||||
unit: ""
|
||||
rightIcon: "emoji_emotions"
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: "💡 Tip: Click the emoji widget in your bar to open the emoji picker and copy any emoji to your clipboard!"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
149
quickshell/PLUGINS/ExampleEmojiPlugin/EmojiWidget.qml
Normal file
149
quickshell/PLUGINS/ExampleEmojiPlugin/EmojiWidget.qml
Normal file
@@ -0,0 +1,149 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginComponent {
|
||||
id: root
|
||||
|
||||
property var enabledEmojis: pluginData.emojis || ["😊", "😢", "❤️"]
|
||||
property int cycleInterval: pluginData.cycleInterval || 3000
|
||||
property int maxBarEmojis: pluginData.maxBarEmojis || 3
|
||||
|
||||
property int currentIndex: 0
|
||||
property var displayedEmojis: []
|
||||
|
||||
Timer {
|
||||
interval: root.cycleInterval
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (root.enabledEmojis.length > 0) {
|
||||
root.currentIndex = (root.currentIndex + 1) % root.enabledEmojis.length
|
||||
root.updateDisplayedEmojis()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateDisplayedEmojis() {
|
||||
const maxToShow = Math.min(root.maxBarEmojis, root.enabledEmojis.length)
|
||||
let emojis = []
|
||||
for (let i = 0; i < maxToShow; i++) {
|
||||
const idx = (root.currentIndex + i) % root.enabledEmojis.length
|
||||
emojis.push(root.enabledEmojis[idx])
|
||||
}
|
||||
root.displayedEmojis = emojis
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updateDisplayedEmojis()
|
||||
}
|
||||
|
||||
onEnabledEmojisChanged: updateDisplayedEmojis()
|
||||
onMaxBarEmojisChanged: updateDisplayedEmojis()
|
||||
|
||||
horizontalBarPill: Component {
|
||||
Row {
|
||||
id: emojiRow
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: root.displayedEmojis
|
||||
StyledText {
|
||||
text: modelData
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
verticalBarPill: Component {
|
||||
Column {
|
||||
id: emojiColumn
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: root.displayedEmojis
|
||||
StyledText {
|
||||
text: modelData
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
popoutContent: Component {
|
||||
PopoutComponent {
|
||||
id: popoutColumn
|
||||
|
||||
headerText: "Emoji Picker"
|
||||
detailsText: "Click an emoji to copy it to clipboard"
|
||||
showCloseButton: true
|
||||
|
||||
property var allEmojis: [
|
||||
"😀", "😃", "😄", "😁", "😆", "😅", "🤣", "😂", "🙂", "🙃",
|
||||
"😉", "😊", "😇", "🥰", "😍", "🤩", "😘", "😗", "😚", "😙",
|
||||
"😋", "😛", "😜", "🤪", "😝", "🤑", "🤗", "🤭", "🤫", "🤔",
|
||||
"🤐", "🤨", "😐", "😑", "😶", "😏", "😒", "🙄", "😬", "🤥",
|
||||
"😌", "😔", "😪", "🤤", "😴", "😷", "🤒", "🤕", "🤢", "🤮",
|
||||
"🤧", "🥵", "🥶", "😶🌫️", "😵", "😵💫", "🤯", "🤠", "🥳", "😎",
|
||||
"🤓", "🧐", "😕", "😟", "🙁", "☹️", "😮", "😯", "😲", "😳",
|
||||
"🥺", "😦", "😧", "😨", "😰", "😥", "😢", "😭", "😱", "😖",
|
||||
"😣", "😞", "😓", "😩", "😫", "🥱", "😤", "😡", "😠", "🤬",
|
||||
"❤️", "🧡", "💛", "💚", "💙", "💜", "🖤", "🤍", "🤎", "💔",
|
||||
"❤️🔥", "❤️🩹", "💕", "💞", "💓", "💗", "💖", "💘", "💝", "💟",
|
||||
"👍", "👎", "👊", "✊", "🤛", "🤜", "🤞", "✌️", "🤟", "🤘",
|
||||
"👌", "🤌", "🤏", "👈", "👉", "👆", "👇", "☝️", "✋", "🤚"
|
||||
]
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
implicitHeight: root.popoutHeight - popoutColumn.headerHeight - popoutColumn.detailsHeight - Theme.spacingXL
|
||||
|
||||
DankGridView {
|
||||
id: emojiGrid
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: Math.floor(parent.width / 50) * 50
|
||||
height: parent.height
|
||||
clip: true
|
||||
cellWidth: 50
|
||||
cellHeight: 50
|
||||
model: popoutColumn.allEmojis
|
||||
|
||||
delegate: StyledRect {
|
||||
width: 45
|
||||
height: 45
|
||||
radius: Theme.cornerRadius
|
||||
color: emojiMouseArea.containsMouse ? Theme.surfaceContainerHighest : Theme.surfaceContainerHigh
|
||||
border.width: 0
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: modelData
|
||||
font.pixelSize: Theme.fontSizeXLarge
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: emojiMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["sh", "-c", "echo -n '" + modelData + "' | wl-copy"])
|
||||
ToastService.showInfo("Copied " + modelData + " to clipboard")
|
||||
popoutColumn.closePopout()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
popoutWidth: 400
|
||||
popoutHeight: 500
|
||||
}
|
||||
56
quickshell/PLUGINS/ExampleEmojiPlugin/README.md
Normal file
56
quickshell/PLUGINS/ExampleEmojiPlugin/README.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Emoji Cycler Plugin
|
||||
|
||||
An example dms plugin that displays cycling emojis in your bar with an emoji picker popout.
|
||||
|
||||
## Features
|
||||
|
||||
- **Cycling Emojis**: Automatically rotates through your selected emoji set in the bar
|
||||
- **Emoji Picker**: Click the widget to open a grid of 120+ emojis
|
||||
- **Copy to Clipboard**: Click any emoji in the picker to copy it to clipboard (uses `wl-copy`)
|
||||
- **Customizable**: Choose emoji sets, cycle speed, and max emojis shown
|
||||
|
||||
## Installation
|
||||
|
||||
1. Copy this directory to `~/.config/DankMaterialShell/plugins/ExampleEmojiPlugin`
|
||||
2. Open DMS Settings → Plugins
|
||||
3. Click "Scan for Plugins"
|
||||
4. Enable "Emoji Cycler"
|
||||
5. Add `exampleEmojiPlugin` to your DankBar widget list
|
||||
|
||||
## Settings
|
||||
|
||||
### Emoji Set
|
||||
Choose from different emoji collections:
|
||||
- **Happy & Sad**: Mix of emotional faces
|
||||
- **Hearts**: Various colored hearts
|
||||
- **Hand Gestures**: Thumbs up, peace signs, etc.
|
||||
- **All Mixed**: A bit of everything
|
||||
|
||||
### Cycle Speed
|
||||
Control how fast emojis rotate (500ms - 10000ms)
|
||||
|
||||
### Max Bar Emojis
|
||||
How many emojis to display at once (1-8)
|
||||
|
||||
## Usage
|
||||
|
||||
**In the bar**: Watch emojis cycle through automatically
|
||||
**Click the widget**: Opens emoji picker with 120+ emojis
|
||||
**Click any emoji**: Copies it to clipboard and shows toast
|
||||
|
||||
## Requirements
|
||||
|
||||
- `wl-copy` (for clipboard support on Wayland)
|
||||
|
||||
## Example Code Highlights
|
||||
|
||||
This plugin demonstrates:
|
||||
- Using `PluginComponent` for bar integration
|
||||
- `SelectionSetting`, `SliderSetting` for configuration
|
||||
- Timer-based animation
|
||||
- Popout content with grid layout
|
||||
- External command execution (`Quickshell.execDetached`)
|
||||
- Toast notifications (`ToastService.show`)
|
||||
- Dynamic settings loading/saving
|
||||
|
||||
Perfect template for creating your own DMS plugins!
|
||||
16
quickshell/PLUGINS/ExampleEmojiPlugin/plugin.json
Normal file
16
quickshell/PLUGINS/ExampleEmojiPlugin/plugin.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "exampleEmojiPlugin",
|
||||
"name": "Emoji Cycler",
|
||||
"description": "Display cycling emojis in your bar with a handy emoji picker popout",
|
||||
"version": "1.0.0",
|
||||
"author": "AvengeMedia",
|
||||
"type": "widget",
|
||||
"capabilities": ["emoji-search", "clipboard", "dankbar-widget"],
|
||||
"component": "./EmojiWidget.qml",
|
||||
"icon": "mood",
|
||||
"settings": "./EmojiSettings.qml",
|
||||
"permissions": [
|
||||
"settings_read",
|
||||
"settings_write"
|
||||
]
|
||||
}
|
||||
116
quickshell/PLUGINS/ExampleWithVariants/README.md
Normal file
116
quickshell/PLUGINS/ExampleWithVariants/README.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Example with Variants Plugin
|
||||
|
||||
This plugin demonstrates the dynamic variant system for DankMaterialShell plugins.
|
||||
|
||||
## What are Variants?
|
||||
|
||||
Variants allow a single plugin to create multiple widget instances with different configurations. Each variant appears as a separate widget option in the Bar Settings "Add Widget" menu.
|
||||
|
||||
## How It Works
|
||||
|
||||
### Plugin Architecture
|
||||
|
||||
1. **Single Component**: The plugin defines one widget component (`VariantWidget.qml`)
|
||||
2. **Variant Data**: Each variant stores its configuration in plugin settings
|
||||
3. **Dynamic Creation**: Variants are created through the plugin's settings UI
|
||||
4. **Widget ID Format**: Variants use the format `pluginId:variantId` (e.g., `exampleVariants:variant_1234567890`)
|
||||
|
||||
### Widget Properties
|
||||
|
||||
Each variant widget receives:
|
||||
- `pluginService`: Reference to PluginService
|
||||
- `pluginId`: The base plugin ID (e.g., "exampleVariants")
|
||||
- `variantId`: The specific variant ID (e.g., "variant_1234567890")
|
||||
- `variantData`: The variant's configuration object
|
||||
|
||||
### Variant Configuration
|
||||
|
||||
This example stores:
|
||||
```javascript
|
||||
{
|
||||
id: "variant_1234567890",
|
||||
name: "My Variant",
|
||||
text: "Display Text",
|
||||
icon: "star",
|
||||
color: "#FF5722"
|
||||
}
|
||||
```
|
||||
|
||||
## Creating Your Own Variant Plugin
|
||||
|
||||
### 1. Widget Component
|
||||
|
||||
Create a widget that accepts variant data:
|
||||
|
||||
```qml
|
||||
Rectangle {
|
||||
property var pluginService: null
|
||||
property string pluginId: ""
|
||||
property string variantId: ""
|
||||
property var variantData: null
|
||||
|
||||
// Use variantData for configuration
|
||||
property string displayText: variantData?.text || "Default"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Settings Component
|
||||
|
||||
Provide UI to manage variants:
|
||||
|
||||
```qml
|
||||
FocusScope {
|
||||
property var pluginService: null
|
||||
|
||||
// Create variant
|
||||
pluginService.createPluginVariant(pluginId, variantName, variantConfig)
|
||||
|
||||
// Remove variant
|
||||
pluginService.removePluginVariant(pluginId, variantId)
|
||||
|
||||
// Update variant
|
||||
pluginService.updatePluginVariant(pluginId, variantId, variantConfig)
|
||||
|
||||
// Get all variants
|
||||
var variants = pluginService.getPluginVariants(pluginId)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Plugin Manifest
|
||||
|
||||
Standard plugin manifest - no special configuration needed:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "yourPlugin",
|
||||
"name": "Your Plugin",
|
||||
"component": "./Widget.qml",
|
||||
"settings": "./Settings.qml"
|
||||
}
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Script Runner**: Multiple variants running different scripts
|
||||
- **System Monitors**: Different monitoring targets (CPU, GPU, Network)
|
||||
- **Quick Launchers**: Different apps or commands per variant
|
||||
- **Custom Indicators**: Different data sources or APIs
|
||||
- **Time Zones**: Multiple clocks for different time zones
|
||||
|
||||
## API Reference
|
||||
|
||||
### PluginService Functions
|
||||
|
||||
- `getPluginVariants(pluginId)`: Get all variants for a plugin
|
||||
- `getAllPluginVariants()`: Get all variants across all plugins
|
||||
- `createPluginVariant(pluginId, name, config)`: Create new variant
|
||||
- `removePluginVariant(pluginId, variantId)`: Delete variant
|
||||
- `updatePluginVariant(pluginId, variantId, config)`: Update variant
|
||||
- `getPluginVariantData(pluginId, variantId)`: Get specific variant data
|
||||
|
||||
### Widget Properties
|
||||
|
||||
- `pluginId`: Base plugin identifier
|
||||
- `variantId`: Variant identifier (null if no variant)
|
||||
- `variantData`: Variant configuration object
|
||||
- `pluginService`: PluginService reference
|
||||
303
quickshell/PLUGINS/ExampleWithVariants/VariantSettings.qml
Normal file
303
quickshell/PLUGINS/ExampleWithVariants/VariantSettings.qml
Normal file
@@ -0,0 +1,303 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginSettings {
|
||||
id: root
|
||||
pluginId: "exampleVariants"
|
||||
|
||||
onVariantsChanged: {
|
||||
variantsModel.clear()
|
||||
for (var i = 0; i < variants.length; i++) {
|
||||
variantsModel.append(variants[i])
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: "Variant Manager"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: "Create multiple widget variants with different text, icons, and colors"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: addVariantColumn.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
|
||||
Column {
|
||||
id: addVariantColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: "Add New Variant"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingM * 2) / 3
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Name"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: nameField
|
||||
width: parent.width
|
||||
placeholderText: "Variant Name"
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingM * 2) / 3
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Icon"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: iconField
|
||||
width: parent.width
|
||||
placeholderText: "star"
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingM * 2) / 3
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Text"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: textField
|
||||
width: parent.width
|
||||
placeholderText: "Display Text"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankButton {
|
||||
text: "Create Variant"
|
||||
iconName: "add"
|
||||
onClicked: {
|
||||
if (!nameField.text) {
|
||||
ToastService.showError("Please enter a variant name")
|
||||
return
|
||||
}
|
||||
|
||||
var variantConfig = {
|
||||
text: textField.text || nameField.text,
|
||||
icon: iconField.text || "widgets"
|
||||
}
|
||||
|
||||
createVariant(nameField.text, variantConfig)
|
||||
ToastService.showInfo("Variant created: " + nameField.text)
|
||||
|
||||
nameField.text = ""
|
||||
iconField.text = ""
|
||||
textField.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 Variants"
|
||||
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: ListModel {
|
||||
id: variantsModel
|
||||
}
|
||||
|
||||
delegate: StyledRect {
|
||||
required property var model
|
||||
width: ListView.view.width
|
||||
height: variantRow.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: variantMouseArea.containsMouse ? Theme.surfaceContainerHighest : Theme.surfaceContainer
|
||||
|
||||
Row {
|
||||
id: variantRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Item {
|
||||
width: Theme.iconSize
|
||||
height: Theme.iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: model.icon || "widgets"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
width: Theme.iconSize
|
||||
height: Theme.iconSize
|
||||
clip: true
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
width: parent.width - Theme.iconSize - deleteButton.width - Theme.spacingM * 4
|
||||
|
||||
StyledText {
|
||||
text: model.name || "Unnamed"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Text: " + (model.text || "") + " | Icon: " + (model.icon || "")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: deleteButton
|
||||
width: 32
|
||||
height: 32
|
||||
radius: 16
|
||||
color: deleteArea.containsMouse ? Theme.error : "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
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)
|
||||
ToastService.showInfo("Variant removed: " + model.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: variantMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: "No variants created yet"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
visible: variantsModel.count === 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: instructionsColumn.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surface
|
||||
|
||||
Column {
|
||||
id: instructionsColumn
|
||||
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: "How to Use Variants"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "1. Create variants with different names, icons, and text\n2. Go to Bar Settings and click 'Add Widget'\n3. Each variant will appear as a separate widget option\n4. Add variants to your bar just like any other widget"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
lineHeight: 1.4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
quickshell/PLUGINS/ExampleWithVariants/VariantWidget.qml
Normal file
57
quickshell/PLUGINS/ExampleWithVariants/VariantWidget.qml
Normal file
@@ -0,0 +1,57 @@
|
||||
import QtQuick
|
||||
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 displayText: variantData?.text || "Default Text"
|
||||
property string displayIcon: variantData?.icon || "widgets"
|
||||
|
||||
horizontalBarPill: Component {
|
||||
Row {
|
||||
spacing: 3
|
||||
|
||||
DankIcon {
|
||||
name: root.displayIcon
|
||||
size: Theme.iconSize - 8
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.displayText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
verticalBarPill: Component {
|
||||
Column {
|
||||
spacing: 1
|
||||
|
||||
DankIcon {
|
||||
name: root.displayIcon
|
||||
size: Theme.iconSize - 8
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.displayText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
quickshell/PLUGINS/ExampleWithVariants/plugin.json
Normal file
16
quickshell/PLUGINS/ExampleWithVariants/plugin.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "exampleVariants",
|
||||
"name": "Example with Variants",
|
||||
"description": "Demonstrates dynamic variant creation for plugins",
|
||||
"version": "1.0.0",
|
||||
"author": "DMS",
|
||||
"type": "widget",
|
||||
"capabilities": ["multiple-usecases", "variants"],
|
||||
"component": "./VariantWidget.qml",
|
||||
"icon": "widgets",
|
||||
"settings": "./VariantSettings.qml",
|
||||
"permissions": [
|
||||
"settings_read",
|
||||
"settings_write"
|
||||
]
|
||||
}
|
||||
140
quickshell/PLUGINS/LauncherExample/LauncherExampleLauncher.qml
Normal file
140
quickshell/PLUGINS/LauncherExample/LauncherExampleLauncher.qml
Normal file
@@ -0,0 +1,140 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Plugin properties
|
||||
property var pluginService: null
|
||||
property string trigger: "#"
|
||||
|
||||
// Plugin interface signals
|
||||
signal itemsChanged()
|
||||
|
||||
Component.onCompleted: {
|
||||
console.info("LauncherExample: Plugin loaded")
|
||||
|
||||
// Load custom trigger from settings
|
||||
if (pluginService) {
|
||||
trigger = pluginService.loadPluginData("launcherExample", "trigger", "#")
|
||||
}
|
||||
}
|
||||
|
||||
// Required function: Get items for launcher
|
||||
function getItems(query) {
|
||||
const baseItems = [
|
||||
{
|
||||
name: "Test Item 1",
|
||||
icon: "material:lightbulb",
|
||||
comment: "This is a test item that shows a toast notification",
|
||||
action: "toast:Test Item 1 executed!",
|
||||
categories: ["LauncherExample"]
|
||||
},
|
||||
{
|
||||
name: "Test Item 2",
|
||||
icon: "material:star",
|
||||
comment: "Another test item with different action",
|
||||
action: "toast:Test Item 2 clicked!",
|
||||
categories: ["LauncherExample"]
|
||||
},
|
||||
{
|
||||
name: "Test Item 3",
|
||||
icon: "material:favorite",
|
||||
comment: "Third test item for demonstration",
|
||||
action: "toast:Test Item 3 activated!",
|
||||
categories: ["LauncherExample"]
|
||||
},
|
||||
{
|
||||
name: "Unicode Icon Example",
|
||||
icon: "unicode:🚀",
|
||||
comment: "Demonstrates unicode/emoji icon support",
|
||||
action: "toast:Unicode icons work great!",
|
||||
categories: ["LauncherExample"]
|
||||
},
|
||||
{
|
||||
name: "Example Copy Action",
|
||||
icon: "material:content_copy",
|
||||
comment: "Demonstrates copying text to clipboard",
|
||||
action: "copy:This text was copied by the launcher plugin!",
|
||||
categories: ["LauncherExample"]
|
||||
},
|
||||
{
|
||||
name: "Example Script Action",
|
||||
icon: "material:terminal",
|
||||
comment: "Demonstrates running a simple command",
|
||||
action: "script:echo 'Hello from launcher plugin!'",
|
||||
categories: ["LauncherExample"]
|
||||
}
|
||||
]
|
||||
|
||||
if (!query || query.length === 0) {
|
||||
return baseItems
|
||||
}
|
||||
|
||||
// Filter items based on query
|
||||
const lowerQuery = query.toLowerCase()
|
||||
return baseItems.filter(item => {
|
||||
return item.name.toLowerCase().includes(lowerQuery) ||
|
||||
item.comment.toLowerCase().includes(lowerQuery)
|
||||
})
|
||||
}
|
||||
|
||||
// Required function: Execute item action
|
||||
function executeItem(item) {
|
||||
if (!item || !item.action) {
|
||||
console.warn("LauncherExample: Invalid item or action")
|
||||
return
|
||||
}
|
||||
|
||||
console.log("LauncherExample: Executing item:", item.name, "with action:", item.action)
|
||||
|
||||
const actionParts = item.action.split(":")
|
||||
const actionType = actionParts[0]
|
||||
const actionData = actionParts.slice(1).join(":")
|
||||
|
||||
switch (actionType) {
|
||||
case "toast":
|
||||
showToast(actionData)
|
||||
break
|
||||
case "copy":
|
||||
copyToClipboard(actionData)
|
||||
break
|
||||
case "script":
|
||||
runScript(actionData)
|
||||
break
|
||||
default:
|
||||
console.warn("LauncherExample: Unknown action type:", actionType)
|
||||
showToast("Unknown action: " + actionType)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions for different action types
|
||||
function showToast(message) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showInfo("LauncherExample", message)
|
||||
} else {
|
||||
console.log("LauncherExample Toast:", message)
|
||||
}
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
Quickshell.execDetached(["sh", "-c", "echo -n '" + text + "' | wl-copy"])
|
||||
showToast("Copied to clipboard: " + text)
|
||||
}
|
||||
|
||||
function runScript(command) {
|
||||
console.log("LauncherExample: Would run script:", command)
|
||||
showToast("Script executed: " + command)
|
||||
|
||||
// In a real plugin, you might create a Process component here
|
||||
// For demo purposes, we just show what would happen
|
||||
}
|
||||
|
||||
// Watch for trigger changes
|
||||
onTriggerChanged: {
|
||||
if (pluginService) {
|
||||
pluginService.savePluginData("launcherExample", "trigger", trigger)
|
||||
}
|
||||
}
|
||||
}
|
||||
244
quickshell/PLUGINS/LauncherExample/LauncherExampleSettings.qml
Normal file
244
quickshell/PLUGINS/LauncherExample/LauncherExampleSettings.qml
Normal file
@@ -0,0 +1,244 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Widgets
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
|
||||
property var pluginService: null
|
||||
|
||||
implicitHeight: settingsColumn.implicitHeight
|
||||
height: implicitHeight
|
||||
|
||||
Column {
|
||||
id: settingsColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 16
|
||||
|
||||
Text {
|
||||
text: "Launcher Example Plugin Settings"
|
||||
font.pixelSize: 18
|
||||
font.weight: Font.Bold
|
||||
color: "#FFFFFF"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "This plugin demonstrates the launcher plugin system with example items and actions."
|
||||
font.pixelSize: 14
|
||||
color: "#CCFFFFFF"
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width - 32
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - 32
|
||||
height: 1
|
||||
color: "#30FFFFFF"
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 12
|
||||
width: parent.width - 32
|
||||
|
||||
Text {
|
||||
text: "Trigger Configuration"
|
||||
font.pixelSize: 16
|
||||
font.weight: Font.Medium
|
||||
color: "#FFFFFF"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: noTriggerToggle.checked ? "Items will always show in the launcher (no trigger needed)." : "Set the trigger text to activate this plugin. Type the trigger in the launcher to filter to this plugin's items."
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 12
|
||||
|
||||
CheckBox {
|
||||
id: noTriggerToggle
|
||||
text: "No trigger (always show)"
|
||||
checked: loadSettings("noTrigger", false)
|
||||
|
||||
contentItem: Text {
|
||||
text: noTriggerToggle.text
|
||||
font.pixelSize: 14
|
||||
color: "#FFFFFF"
|
||||
leftPadding: noTriggerToggle.indicator.width + 8
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
indicator: Rectangle {
|
||||
implicitWidth: 20
|
||||
implicitHeight: 20
|
||||
radius: 4
|
||||
border.color: noTriggerToggle.checked ? "#4CAF50" : "#60FFFFFF"
|
||||
border.width: 2
|
||||
color: noTriggerToggle.checked ? "#4CAF50" : "transparent"
|
||||
|
||||
Rectangle {
|
||||
width: 12
|
||||
height: 12
|
||||
anchors.centerIn: parent
|
||||
radius: 2
|
||||
color: "#FFFFFF"
|
||||
visible: noTriggerToggle.checked
|
||||
}
|
||||
}
|
||||
|
||||
onCheckedChanged: {
|
||||
saveSettings("noTrigger", checked)
|
||||
if (checked) {
|
||||
saveSettings("trigger", "")
|
||||
} else {
|
||||
saveSettings("trigger", triggerField.text || "#")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 12
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
visible: !noTriggerToggle.checked
|
||||
|
||||
Text {
|
||||
text: "Trigger:"
|
||||
font.pixelSize: 14
|
||||
color: "#FFFFFF"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: triggerField
|
||||
width: 100
|
||||
height: 40
|
||||
text: loadSettings("trigger", "#")
|
||||
placeholderText: "#"
|
||||
backgroundColor: "#30FFFFFF"
|
||||
textColor: "#FFFFFF"
|
||||
|
||||
onTextEdited: {
|
||||
const newTrigger = text.trim()
|
||||
saveSettings("trigger", newTrigger || "#")
|
||||
saveSettings("noTrigger", newTrigger === "")
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Examples: #, !, @, !ex, etc."
|
||||
font.pixelSize: 12
|
||||
color: "#AAFFFFFF"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - 32
|
||||
height: 1
|
||||
color: "#30FFFFFF"
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 8
|
||||
width: parent.width - 32
|
||||
|
||||
Text {
|
||||
text: "Example Items:"
|
||||
font.pixelSize: 14
|
||||
font.weight: Font.Medium
|
||||
color: "#FFFFFF"
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 4
|
||||
leftPadding: 16
|
||||
|
||||
Text {
|
||||
text: "• Test Item 1, 2, 3 - Show toast notifications"
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "• Example Copy Action - Copy text to clipboard"
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "• Example Script Action - Demonstrate script execution"
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - 32
|
||||
height: 1
|
||||
color: "#30FFFFFF"
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 8
|
||||
width: parent.width - 32
|
||||
|
||||
Text {
|
||||
text: "Usage:"
|
||||
font.pixelSize: 14
|
||||
font.weight: Font.Medium
|
||||
color: "#FFFFFF"
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 4
|
||||
leftPadding: 16
|
||||
bottomPadding: 24
|
||||
|
||||
Text {
|
||||
text: "1. Open Launcher (Ctrl+Space or click launcher button)"
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: noTriggerToggle.checked ? "2. Items are always visible in the launcher" : "2. Type your trigger (default: #) to filter to this plugin"
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: noTriggerToggle.checked ? "3. Search works normally with plugin items included" : "3. Optionally add search terms: '# test' to find test items"
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "4. Select an item and press Enter to execute its action"
|
||||
font.pixelSize: 12
|
||||
color: "#CCFFFFFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings(key, value) {
|
||||
if (pluginService) {
|
||||
pluginService.savePluginData("launcherExample", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
function loadSettings(key, defaultValue) {
|
||||
if (pluginService) {
|
||||
return pluginService.loadPluginData("launcherExample", key, defaultValue)
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
240
quickshell/PLUGINS/LauncherExample/README.md
Normal file
240
quickshell/PLUGINS/LauncherExample/README.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# LauncherExample Plugin
|
||||
|
||||
A demonstration plugin that showcases the DMS launcher plugin system capabilities.
|
||||
|
||||
## Purpose
|
||||
|
||||
This plugin serves as a comprehensive example for developers creating launcher plugins for DMS. It demonstrates:
|
||||
|
||||
- **Plugin Structure**: Proper manifest, launcher, and settings components
|
||||
- **Trigger System**: Customizable trigger strings for plugin activation (including empty triggers)
|
||||
- **Item Management**: Providing searchable items to the launcher
|
||||
- **Action Execution**: Handling different types of actions (toast, copy, script)
|
||||
- **Settings Integration**: Configurable plugin settings with persistence
|
||||
|
||||
## Features
|
||||
|
||||
### Example Items
|
||||
- **Test Items 1-3**: Demonstrate toast notifications
|
||||
- **Copy Action**: Shows clipboard integration
|
||||
- **Script Action**: Demonstrates command execution
|
||||
|
||||
### Trigger System
|
||||
- **Default Trigger**: `#` (configurable in settings)
|
||||
- **Empty Trigger Option**: Items can always be visible without needing a trigger
|
||||
- **Usage**: Type `#` in launcher to filter to this plugin (when trigger is set)
|
||||
- **Search**: Type `# test` to search within plugin items
|
||||
|
||||
### Action Types
|
||||
- `toast:message` - Shows toast notification
|
||||
- `copy:text` - Copies text to clipboard
|
||||
- `script:command` - Executes shell command (demo only)
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
PLUGINS/LauncherExample/
|
||||
├── plugin.json # Plugin manifest
|
||||
├── LauncherExampleLauncher.qml # Main launcher component
|
||||
├── LauncherExampleSettings.qml # Settings interface
|
||||
└── README.md # This documentation
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Plugin Directory**: Copy to `~/.config/DankMaterialShell/plugins/LauncherExample`
|
||||
2. **Enable Plugin**: Settings → Plugins → Enable "LauncherExample"
|
||||
3. **Configure**: Set custom trigger in plugin settings if desired
|
||||
|
||||
## Usage
|
||||
|
||||
### With Trigger (Default)
|
||||
1. Open launcher (Ctrl+Space or launcher button)
|
||||
2. Type `#` to activate plugin trigger
|
||||
3. Browse available items or add search terms
|
||||
4. Press Enter to execute selected item
|
||||
|
||||
### Without Trigger (Empty Trigger Mode)
|
||||
1. Enable "No trigger (always show)" in plugin settings
|
||||
2. Open launcher - plugin items are always visible
|
||||
3. Search works normally with plugin items included
|
||||
4. Press Enter to execute selected item
|
||||
|
||||
### Search Examples
|
||||
- `#` - Show all plugin items (with trigger enabled)
|
||||
- `# test` - Show items matching "test"
|
||||
- `# copy` - Show items matching "copy"
|
||||
- `test` - Show all items matching "test" (with empty trigger enabled)
|
||||
|
||||
## Developer Guide
|
||||
|
||||
### Plugin Contract
|
||||
|
||||
**Launcher Component Requirements**:
|
||||
```qml
|
||||
// Required properties
|
||||
property var pluginService: null
|
||||
property string trigger: "#"
|
||||
|
||||
// Required signals
|
||||
signal itemsChanged()
|
||||
|
||||
// Required functions
|
||||
function getItems(query): array
|
||||
function executeItem(item): void
|
||||
```
|
||||
|
||||
**Item Structure**:
|
||||
```javascript
|
||||
{
|
||||
name: "Item Name", // Display name
|
||||
icon: "icon_name", // Icon (optional, see Icon Types below)
|
||||
comment: "Description", // Subtitle text
|
||||
action: "type:data", // Action to execute
|
||||
categories: ["PluginName"] // Category array
|
||||
}
|
||||
```
|
||||
|
||||
**Icon Types**:
|
||||
|
||||
The `icon` field supports four formats:
|
||||
|
||||
1. **Material Design Icons** - Use `material:` prefix:
|
||||
```javascript
|
||||
icon: "material:lightbulb" // Material Symbols Rounded font
|
||||
```
|
||||
Examples: `material:star`, `material:favorite`, `material:settings`
|
||||
|
||||
2. **Unicode/Emoji Icons** - Use `unicode:` prefix:
|
||||
```javascript
|
||||
icon: "unicode:🚀" // Unicode character or emoji
|
||||
```
|
||||
Display any Unicode character or emoji as the icon. Examples: `unicode:😀`, `unicode:⚡`, `unicode:🎨`
|
||||
|
||||
3. **Desktop Theme Icons** - Use icon name directly:
|
||||
```javascript
|
||||
icon: "firefox" // Uses system icon theme
|
||||
```
|
||||
Examples: `firefox`, `chrome`, `folder`, `text-editor`
|
||||
|
||||
4. **No Icon** - Omit the `icon` field entirely:
|
||||
```javascript
|
||||
{
|
||||
name: "😀 Grinning Face",
|
||||
// No icon field
|
||||
comment: "Copy emoji",
|
||||
action: "copy:😀",
|
||||
categories: ["MyPlugin"]
|
||||
}
|
||||
```
|
||||
When icon is omitted, the launcher hides the icon area and displays only text
|
||||
|
||||
**Action Format**: `type:data` where:
|
||||
- `type` - Action handler (toast, copy, script, etc.)
|
||||
- `data` - Action-specific data
|
||||
|
||||
### Settings Integration
|
||||
```qml
|
||||
// Save setting
|
||||
pluginService.savePluginData("pluginId", "key", value)
|
||||
|
||||
// Load setting
|
||||
pluginService.loadPluginData("pluginId", "key", defaultValue)
|
||||
```
|
||||
|
||||
### Trigger Configuration
|
||||
|
||||
The trigger can be configured in two ways:
|
||||
|
||||
1. **Empty Trigger** (No Trigger Mode):
|
||||
- Check "No trigger (always show)" in settings
|
||||
- Saves `trigger: ""` and `noTrigger: true`
|
||||
- Items always appear in launcher alongside regular apps
|
||||
|
||||
2. **Custom Trigger**:
|
||||
- Enter any string (e.g., `#`, `!`, `@`, `!ex`)
|
||||
- Uncheck "No trigger" checkbox
|
||||
- Items only appear when trigger is typed
|
||||
|
||||
### Manifest Structure
|
||||
```json
|
||||
{
|
||||
"id": "launcherExample",
|
||||
"name": "LauncherExample",
|
||||
"type": "launcher",
|
||||
"capabilities": ["launcher"],
|
||||
"component": "./LauncherExampleLauncher.qml",
|
||||
"settings": "./LauncherExampleSettings.qml",
|
||||
"permissions": ["settings_read", "settings_write"]
|
||||
}
|
||||
```
|
||||
|
||||
Note: The `trigger` field in the manifest is optional and serves as the default trigger value.
|
||||
|
||||
## Extending This Plugin
|
||||
|
||||
### Adding New Items
|
||||
```qml
|
||||
function getItems(query) {
|
||||
return [
|
||||
{
|
||||
name: "My Item",
|
||||
icon: "custom_icon",
|
||||
comment: "Does something cool",
|
||||
action: "custom:action_data",
|
||||
categories: ["LauncherExample"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Adding New Actions
|
||||
```qml
|
||||
function executeItem(item) {
|
||||
const actionParts = item.action.split(":")
|
||||
const actionType = actionParts[0]
|
||||
const actionData = actionParts.slice(1).join(":")
|
||||
|
||||
switch (actionType) {
|
||||
case "custom":
|
||||
handleCustomAction(actionData)
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Trigger Logic
|
||||
```qml
|
||||
Component.onCompleted: {
|
||||
if (pluginService) {
|
||||
trigger = pluginService.loadPluginData("launcherExample", "trigger", "#")
|
||||
}
|
||||
}
|
||||
|
||||
onTriggerChanged: {
|
||||
if (pluginService) {
|
||||
pluginService.savePluginData("launcherExample", "trigger", trigger)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Unique Triggers**: Choose triggers that don't conflict with other plugins
|
||||
2. **Clear Descriptions**: Write helpful item comments
|
||||
3. **Error Handling**: Gracefully handle action failures
|
||||
4. **Performance**: Return results quickly in getItems()
|
||||
5. **Cleanup**: Destroy temporary objects in executeItem()
|
||||
6. **Empty Trigger Support**: Consider if your plugin should support empty trigger mode
|
||||
|
||||
## Testing
|
||||
|
||||
Test the plugin by:
|
||||
1. Installing and enabling in DMS
|
||||
2. Testing with trigger enabled
|
||||
3. Testing with empty trigger (no trigger mode)
|
||||
4. Trying each action type
|
||||
5. Testing search functionality
|
||||
6. Verifying settings persistence
|
||||
|
||||
This plugin provides a solid foundation for building more sophisticated launcher plugins with custom functionality!
|
||||
17
quickshell/PLUGINS/LauncherExample/plugin.json
Normal file
17
quickshell/PLUGINS/LauncherExample/plugin.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "launcherExample",
|
||||
"name": "LauncherExample",
|
||||
"description": "Example launcher plugin demonstrating the launcher plugin system",
|
||||
"version": "1.0.0",
|
||||
"author": "DMS Team",
|
||||
"icon": "extension",
|
||||
"type": "launcher",
|
||||
"capabilities": ["clipboard", "command-execution"],
|
||||
"component": "./LauncherExampleLauncher.qml",
|
||||
"settings": "./LauncherExampleSettings.qml",
|
||||
"trigger": "#",
|
||||
"permissions": [
|
||||
"settings_read",
|
||||
"settings_write"
|
||||
]
|
||||
}
|
||||
285
quickshell/PLUGINS/POPOUT_SERVICE.md
Normal file
285
quickshell/PLUGINS/POPOUT_SERVICE.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# PopoutService for Plugins
|
||||
|
||||
## Overview
|
||||
|
||||
The `PopoutService` singleton provides plugins with access to all DankMaterialShell popouts and modals. It's automatically injected into plugin widgets and daemons, enabling them to control shell UI elements.
|
||||
|
||||
## Automatic Injection
|
||||
|
||||
The `popoutService` property is automatically injected into:
|
||||
- Widget plugins (loaded in DankBar)
|
||||
- Daemon plugins (background services)
|
||||
- Plugin settings components
|
||||
|
||||
**Required**: Declare the property in your plugin component:
|
||||
|
||||
```qml
|
||||
property var popoutService: null
|
||||
```
|
||||
|
||||
**Note**: Without this declaration, the service cannot be injected and you'll see errors like `Cannot assign to non-existent property "popoutService"`
|
||||
|
||||
## API Reference
|
||||
|
||||
### Popouts (DankPopout-based)
|
||||
|
||||
| Component | Open | Close | Toggle |
|
||||
|-----------|------|-------|--------|
|
||||
| Control Center | `openControlCenter()` | `closeControlCenter()` | `toggleControlCenter()` |
|
||||
| Notification Center | `openNotificationCenter()` | `closeNotificationCenter()` | `toggleNotificationCenter()` |
|
||||
| App Drawer | `openAppDrawer()` | `closeAppDrawer()` | `toggleAppDrawer()` |
|
||||
| Process List | `openProcessList()` | `closeProcessList()` | `toggleProcessList()` |
|
||||
| DankDash | `openDankDash(tab)` | `closeDankDash()` | `toggleDankDash(tab)` |
|
||||
| Battery | `openBattery()` | `closeBattery()` | `toggleBattery()` |
|
||||
| VPN | `openVpn()` | `closeVpn()` | `toggleVpn()` |
|
||||
| System Update | `openSystemUpdate()` | `closeSystemUpdate()` | `toggleSystemUpdate()` |
|
||||
|
||||
### Modals (DankModal-based)
|
||||
|
||||
| Modal | Show | Hide | Notes |
|
||||
|-------|------|------|-------|
|
||||
| Settings | `openSettings()` | `closeSettings()` | Full settings interface |
|
||||
| Clipboard History | `openClipboardHistory()` | `closeClipboardHistory()` | Cliphist integration |
|
||||
| Spotlight | `openSpotlight()` | `closeSpotlight()` | Command launcher |
|
||||
| Power Menu | `openPowerMenu()` | `closePowerMenu()` | Also has `togglePowerMenu()` |
|
||||
| Process List Modal | `showProcessListModal()` | `hideProcessListModal()` | Fullscreen version, has `toggleProcessListModal()` |
|
||||
| Color Picker | `showColorPicker()` | `hideColorPicker()` | Theme color selection |
|
||||
| Notification | `showNotificationModal()` | `hideNotificationModal()` | Notification details |
|
||||
| WiFi Password | `showWifiPasswordModal()` | `hideWifiPasswordModal()` | Network authentication |
|
||||
| Network Info | `showNetworkInfoModal()` | `hideNetworkInfoModal()` | Network details |
|
||||
|
||||
### Slideouts
|
||||
|
||||
| Component | Open | Close | Toggle |
|
||||
|-----------|------|-------|--------|
|
||||
| Notepad | `openNotepad()` | `closeNotepad()` | `toggleNotepad()` |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Simple Widget with Popout Control
|
||||
|
||||
```qml
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var popoutService: null
|
||||
|
||||
width: 100
|
||||
height: 30
|
||||
color: Theme.surfaceContainerHigh
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: popoutService?.toggleControlCenter()
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: "Settings"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Daemon with Event-Driven Popouts
|
||||
|
||||
```qml
|
||||
import QtQuick
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var popoutService: null
|
||||
|
||||
Connections {
|
||||
target: NotificationService
|
||||
|
||||
function onNotificationReceived(notification) {
|
||||
if (notification.urgency === "critical") {
|
||||
popoutService?.openNotificationCenter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: BatteryService
|
||||
|
||||
function onPercentageChanged() {
|
||||
if (BatteryService.percentage < 10 && !BatteryService.isCharging) {
|
||||
popoutService?.openBattery()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Widget with Multiple Popout Options
|
||||
|
||||
```qml
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
|
||||
Rectangle {
|
||||
property var popoutService: null
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
onClicked: (mouse) => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
contextMenu.popup()
|
||||
} else {
|
||||
popoutService?.toggleControlCenter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: contextMenu
|
||||
|
||||
MenuItem {
|
||||
text: "Settings"
|
||||
onClicked: popoutService?.openSettings()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Notifications"
|
||||
onClicked: popoutService?.toggleNotificationCenter()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Power Menu"
|
||||
onClicked: popoutService?.openPowerMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Service Architecture
|
||||
|
||||
`PopoutService` is a singleton that holds references to popout instances:
|
||||
|
||||
```qml
|
||||
// Services/PopoutService.qml
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property var controlCenterPopout: null
|
||||
property var notificationCenterPopout: null
|
||||
// ... other popout references
|
||||
|
||||
function toggleControlCenter() {
|
||||
controlCenterPopout?.toggle()
|
||||
}
|
||||
// ... other control functions
|
||||
}
|
||||
```
|
||||
|
||||
### Reference Assignment
|
||||
|
||||
References are assigned in `DMSShell.qml` when popouts are loaded:
|
||||
|
||||
```qml
|
||||
LazyLoader {
|
||||
ControlCenterPopout {
|
||||
id: controlCenterPopout
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.controlCenterPopout = controlCenterPopout
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Plugin Injection
|
||||
|
||||
The service is injected in three locations:
|
||||
|
||||
1. **DMSShell.qml** (daemon plugins):
|
||||
```qml
|
||||
Instantiator {
|
||||
delegate: Loader {
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
item.popoutService = PopoutService
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **WidgetHost.qml** (widget plugins):
|
||||
```qml
|
||||
onLoaded: {
|
||||
if (item.popoutService !== undefined) {
|
||||
item.popoutService = PopoutService
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **CenterSection.qml** (center widgets):
|
||||
```qml
|
||||
onLoaded: {
|
||||
if (item.popoutService !== undefined) {
|
||||
item.popoutService = PopoutService
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. **PluginsTab.qml** (settings):
|
||||
```qml
|
||||
onLoaded: {
|
||||
if (item && typeof PopoutService !== "undefined") {
|
||||
item.popoutService = PopoutService
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Optional Chaining**: Always use `?.` to handle null cases
|
||||
```qml
|
||||
popoutService?.toggleControlCenter()
|
||||
```
|
||||
|
||||
2. **Check Availability**: Some popouts may not be available
|
||||
```qml
|
||||
if (popoutService && popoutService.controlCenterPopout) {
|
||||
popoutService.toggleControlCenter()
|
||||
}
|
||||
```
|
||||
|
||||
3. **Lazy Loading**: First access may activate lazy loaders - this is normal
|
||||
|
||||
4. **Feature Detection**: Some popouts require specific features
|
||||
```qml
|
||||
if (BatteryService.batteryAvailable) {
|
||||
popoutService?.openBattery()
|
||||
}
|
||||
```
|
||||
|
||||
5. **User Intent**: Only trigger popouts based on user actions or critical events
|
||||
|
||||
## Example Plugin
|
||||
|
||||
See `PLUGINS/PopoutControlExample/` for a complete working example that demonstrates:
|
||||
- Widget creation with popout controls
|
||||
- Menu-based popout selection
|
||||
- Proper service usage
|
||||
- Error handling
|
||||
|
||||
## Limitations
|
||||
|
||||
- Popouts are shared across all plugins - avoid conflicts
|
||||
- Some popouts may be compositor or feature-dependent
|
||||
- Lazy-loaded popouts need activation before use
|
||||
- Multi-monitor considerations apply to positioned popouts
|
||||
@@ -0,0 +1,56 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginSettings {
|
||||
id: root
|
||||
pluginId: "popoutControlExample"
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: "Popout Control Settings"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: "Choose which popout/modal will open when clicking the widget"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
SelectionSetting {
|
||||
settingKey: "selectedPopout"
|
||||
label: "Popout to Open"
|
||||
description: "Select which popout or modal opens when you click the widget"
|
||||
options: [
|
||||
{label: "Control Center", value: "controlCenter"},
|
||||
{label: "Notification Center", value: "notificationCenter"},
|
||||
{label: "App Drawer", value: "appDrawer"},
|
||||
{label: "Process List", value: "processList"},
|
||||
{label: "DankDash", value: "dankDash"},
|
||||
{label: "Battery Info", value: "battery"},
|
||||
{label: "VPN", value: "vpn"},
|
||||
{label: "System Update", value: "systemUpdate"},
|
||||
{label: "Settings", value: "settings"},
|
||||
{label: "Clipboard History", value: "clipboardHistory"},
|
||||
{label: "Spotlight", value: "spotlight"},
|
||||
{label: "Power Menu", value: "powerMenu"},
|
||||
{label: "Color Picker", value: "colorPicker"},
|
||||
{label: "Notepad", value: "notepad"}
|
||||
]
|
||||
defaultValue: "controlCenter"
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: "💡 Tip: The widget displays the name of the selected popout and opens it when clicked!"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginComponent {
|
||||
id: root
|
||||
|
||||
property var popoutService: null
|
||||
|
||||
property string selectedPopout: pluginData.selectedPopout || "controlCenter"
|
||||
|
||||
property var popoutActions: ({
|
||||
"controlCenter": (x, y, w, s, scr) => popoutService?.toggleControlCenter(x, y, w, s, scr),
|
||||
"notificationCenter": (x, y, w, s, scr) => popoutService?.toggleNotificationCenter(x, y, w, s, scr),
|
||||
"appDrawer": (x, y, w, s, scr) => popoutService?.toggleAppDrawer(x, y, w, s, scr),
|
||||
"processList": (x, y, w, s, scr) => popoutService?.toggleProcessList(x, y, w, s, scr),
|
||||
"dankDash": (x, y, w, s, scr) => popoutService?.toggleDankDash(0, x, y, w, s, scr),
|
||||
"battery": (x, y, w, s, scr) => popoutService?.toggleBattery(x, y, w, s, scr),
|
||||
"vpn": (x, y, w, s, scr) => popoutService?.toggleVpn(x, y, w, s, scr),
|
||||
"systemUpdate": (x, y, w, s, scr) => popoutService?.toggleSystemUpdate(x, y, w, s, scr),
|
||||
"settings": () => popoutService?.openSettings(),
|
||||
"clipboardHistory": () => popoutService?.openClipboardHistory(),
|
||||
"spotlight": () => popoutService?.openSpotlight(),
|
||||
"powerMenu": () => popoutService?.togglePowerMenu(),
|
||||
"colorPicker": () => popoutService?.showColorPicker(),
|
||||
"notepad": () => popoutService?.toggleNotepad()
|
||||
})
|
||||
|
||||
property var popoutNames: ({
|
||||
"controlCenter": "Control Center",
|
||||
"notificationCenter": "Notification Center",
|
||||
"appDrawer": "App Drawer",
|
||||
"processList": "Process List",
|
||||
"dankDash": "DankDash",
|
||||
"battery": "Battery Info",
|
||||
"vpn": "VPN",
|
||||
"systemUpdate": "System Update",
|
||||
"settings": "Settings",
|
||||
"clipboardHistory": "Clipboard",
|
||||
"spotlight": "Spotlight",
|
||||
"powerMenu": "Power Menu",
|
||||
"colorPicker": "Color Picker",
|
||||
"notepad": "Notepad"
|
||||
})
|
||||
|
||||
pillClickAction: (x, y, width, section, screen) => {
|
||||
if (popoutActions[selectedPopout]) {
|
||||
popoutActions[selectedPopout](x, y, width, section, screen)
|
||||
}
|
||||
}
|
||||
|
||||
horizontalBarPill: Component {
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "widgets"
|
||||
color: Theme.primary
|
||||
font.pixelSize: Theme.iconSize - 6
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: popoutNames[selectedPopout] || "Popouts"
|
||||
color: Theme.primary
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
verticalBarPill: Component {
|
||||
Column {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "widgets"
|
||||
color: Theme.primary
|
||||
font.pixelSize: Theme.iconSize - 6
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: popoutNames[selectedPopout] || "Popouts"
|
||||
color: Theme.primary
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
207
quickshell/PLUGINS/PopoutControlExample/README.md
Normal file
207
quickshell/PLUGINS/PopoutControlExample/README.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# Popout Control Example Plugin
|
||||
|
||||
This example plugin demonstrates:
|
||||
- Using `PopoutService` to trigger positioned popouts and modals
|
||||
- Using `pillClickAction` with position parameters
|
||||
- Using `PluginSettings` with dropdown selection
|
||||
- Dynamic widget text based on settings
|
||||
|
||||
The `pillClickAction` receives position parameters `(x, y, width, section, screen)` which are passed to PopoutService functions to properly position popouts relative to the widget.
|
||||
|
||||
## PopoutService API
|
||||
|
||||
The `PopoutService` is automatically injected into plugin widgets and daemons as `popoutService`. It provides access to all shell popouts and modals.
|
||||
|
||||
### Available Popouts
|
||||
|
||||
#### Control Center
|
||||
```qml
|
||||
popoutService.openControlCenter()
|
||||
popoutService.closeControlCenter()
|
||||
popoutService.toggleControlCenter()
|
||||
```
|
||||
|
||||
#### Notification Center
|
||||
```qml
|
||||
popoutService.openNotificationCenter()
|
||||
popoutService.closeNotificationCenter()
|
||||
popoutService.toggleNotificationCenter()
|
||||
```
|
||||
|
||||
#### App Drawer
|
||||
```qml
|
||||
popoutService.openAppDrawer()
|
||||
popoutService.closeAppDrawer()
|
||||
popoutService.toggleAppDrawer()
|
||||
```
|
||||
|
||||
#### Process List (Popout)
|
||||
```qml
|
||||
popoutService.openProcessList()
|
||||
popoutService.closeProcessList()
|
||||
popoutService.toggleProcessList()
|
||||
```
|
||||
|
||||
#### DankDash
|
||||
```qml
|
||||
popoutService.openDankDash(tabIndex) // tabIndex: 0=Calendar, 1=Media, 2=Weather
|
||||
popoutService.closeDankDash()
|
||||
popoutService.toggleDankDash(tabIndex)
|
||||
```
|
||||
|
||||
#### Battery Popout
|
||||
```qml
|
||||
popoutService.openBattery()
|
||||
popoutService.closeBattery()
|
||||
popoutService.toggleBattery()
|
||||
```
|
||||
|
||||
#### VPN Popout
|
||||
```qml
|
||||
popoutService.openVpn()
|
||||
popoutService.closeVpn()
|
||||
popoutService.toggleVpn()
|
||||
```
|
||||
|
||||
#### System Update Popout
|
||||
```qml
|
||||
popoutService.openSystemUpdate()
|
||||
popoutService.closeSystemUpdate()
|
||||
popoutService.toggleSystemUpdate()
|
||||
```
|
||||
|
||||
### Available Modals
|
||||
|
||||
#### Settings Modal
|
||||
```qml
|
||||
popoutService.openSettings()
|
||||
popoutService.closeSettings()
|
||||
```
|
||||
|
||||
#### Clipboard History Modal
|
||||
```qml
|
||||
popoutService.openClipboardHistory()
|
||||
popoutService.closeClipboardHistory()
|
||||
```
|
||||
|
||||
#### Spotlight Modal
|
||||
```qml
|
||||
popoutService.openSpotlight()
|
||||
popoutService.closeSpotlight()
|
||||
```
|
||||
|
||||
#### Power Menu Modal
|
||||
```qml
|
||||
popoutService.openPowerMenu()
|
||||
popoutService.closePowerMenu()
|
||||
popoutService.togglePowerMenu()
|
||||
```
|
||||
|
||||
#### Process List Modal (fullscreen)
|
||||
```qml
|
||||
popoutService.showProcessListModal()
|
||||
popoutService.hideProcessListModal()
|
||||
popoutService.toggleProcessListModal()
|
||||
```
|
||||
|
||||
#### Color Picker Modal
|
||||
```qml
|
||||
popoutService.showColorPicker()
|
||||
popoutService.hideColorPicker()
|
||||
```
|
||||
|
||||
#### Notification Modal
|
||||
```qml
|
||||
popoutService.showNotificationModal()
|
||||
popoutService.hideNotificationModal()
|
||||
```
|
||||
|
||||
#### WiFi Password Modal
|
||||
```qml
|
||||
popoutService.showWifiPasswordModal()
|
||||
popoutService.hideWifiPasswordModal()
|
||||
```
|
||||
|
||||
#### Network Info Modal
|
||||
```qml
|
||||
popoutService.showNetworkInfoModal()
|
||||
popoutService.hideNetworkInfoModal()
|
||||
```
|
||||
|
||||
#### Notepad Slideout
|
||||
```qml
|
||||
popoutService.openNotepad()
|
||||
popoutService.closeNotepad()
|
||||
popoutService.toggleNotepad()
|
||||
```
|
||||
|
||||
## Usage in Plugins
|
||||
|
||||
### Widget Plugins
|
||||
|
||||
```qml
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var popoutService: null // REQUIRED: Must declare for injection
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
popoutService?.toggleControlCenter()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Daemon Plugins
|
||||
|
||||
```qml
|
||||
import QtQuick
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var popoutService: null // REQUIRED: Must declare for injection
|
||||
|
||||
Connections {
|
||||
target: NotificationService
|
||||
function onNotificationReceived() {
|
||||
popoutService?.openNotificationCenter()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Important**: The `popoutService` property **must** be declared in your plugin component. Without it, you'll get errors like:
|
||||
```
|
||||
Error: Cannot assign to non-existent property "popoutService"
|
||||
```
|
||||
|
||||
## Example Use Cases
|
||||
|
||||
1. **Custom Launcher**: Create a widget that opens the app drawer
|
||||
2. **Quick Settings**: Toggle control center from a custom button
|
||||
3. **Notification Manager**: Open notification center on new notifications
|
||||
4. **System Monitor**: Open process list on high CPU usage
|
||||
5. **Power Management**: Trigger power menu from a custom widget
|
||||
|
||||
## Installation
|
||||
|
||||
1. Copy the plugin directory to `~/.config/DankMaterialShell/plugins/`
|
||||
2. Open Settings → Plugins
|
||||
3. Click "Scan for Plugins"
|
||||
4. Enable "Popout Control Example"
|
||||
5. Add `popoutControlExample` to your DankBar widget list
|
||||
|
||||
## Notes
|
||||
|
||||
- The `popoutService` property is automatically injected - no manual setup required
|
||||
- Always use optional chaining (`?.`) when calling methods to handle null cases
|
||||
- Popouts are lazily loaded - first access may activate the loader
|
||||
- Some popouts require specific system features to be available
|
||||
13
quickshell/PLUGINS/PopoutControlExample/plugin.json
Normal file
13
quickshell/PLUGINS/PopoutControlExample/plugin.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "popoutControlExample",
|
||||
"name": "Popout Control Example",
|
||||
"description": "Example widget demonstrating PopoutService usage with pillClickAction",
|
||||
"version": "1.0.0",
|
||||
"author": "DankMaterialShell",
|
||||
"type": "widget",
|
||||
"capabilities": ["dankbar-widget"],
|
||||
"component": "./PopoutControlWidget.qml",
|
||||
"icon": "widgets",
|
||||
"settings": "./PopoutControlSettings.qml",
|
||||
"permissions": ["settings_read", "settings_write"]
|
||||
}
|
||||
1480
quickshell/PLUGINS/README.md
Normal file
1480
quickshell/PLUGINS/README.md
Normal file
File diff suppressed because it is too large
Load Diff
132
quickshell/PLUGINS/THEME_REFERENCE.md
Normal file
132
quickshell/PLUGINS/THEME_REFERENCE.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Theme Property Reference for Plugins
|
||||
|
||||
Quick reference for commonly used Theme properties in plugin development.
|
||||
|
||||
## Font Sizes
|
||||
|
||||
```qml
|
||||
Theme.fontSizeSmall // 12px (scaled)
|
||||
Theme.fontSizeMedium // 14px (scaled)
|
||||
Theme.fontSizeLarge // 16px (scaled)
|
||||
Theme.fontSizeXLarge // 20px (scaled)
|
||||
```
|
||||
|
||||
**Note**: These are scaled by `SettingsData.fontScale`
|
||||
|
||||
## Icon Sizes
|
||||
|
||||
```qml
|
||||
Theme.iconSizeSmall // 16px
|
||||
Theme.iconSize // 24px (default)
|
||||
Theme.iconSizeLarge // 32px
|
||||
```
|
||||
|
||||
## Spacing
|
||||
|
||||
```qml
|
||||
Theme.spacingXS // Extra small
|
||||
Theme.spacingS // Small
|
||||
Theme.spacingM // Medium
|
||||
Theme.spacingL // Large
|
||||
Theme.spacingXL // Extra large
|
||||
```
|
||||
|
||||
## Border Radius
|
||||
|
||||
```qml
|
||||
Theme.cornerRadius // Standard corner radius
|
||||
Theme.cornerRadiusSmall // Smaller radius
|
||||
Theme.cornerRadiusLarge // Larger radius
|
||||
```
|
||||
|
||||
## Colors
|
||||
|
||||
### Surface Colors
|
||||
```qml
|
||||
Theme.surface
|
||||
Theme.surfaceContainerLow
|
||||
Theme.surfaceContainer
|
||||
Theme.surfaceContainerHigh
|
||||
Theme.surfaceContainerHighest
|
||||
```
|
||||
|
||||
### Text Colors
|
||||
```qml
|
||||
Theme.onSurface // Primary text on surface
|
||||
Theme.onSurfaceVariant // Secondary text on surface
|
||||
Theme.outline // Border/divider color
|
||||
```
|
||||
|
||||
### Semantic Colors
|
||||
```qml
|
||||
Theme.primary
|
||||
Theme.onPrimary
|
||||
Theme.secondary
|
||||
Theme.onSecondary
|
||||
Theme.error
|
||||
Theme.warning
|
||||
Theme.success
|
||||
```
|
||||
|
||||
### Special Functions
|
||||
```qml
|
||||
Theme.popupBackground() // Popup background with opacity
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Icon with Text
|
||||
```qml
|
||||
DankIcon {
|
||||
name: "icon_name"
|
||||
color: Theme.onSurface
|
||||
font.pixelSize: Theme.iconSize
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Label"
|
||||
color: Theme.onSurface
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
}
|
||||
```
|
||||
|
||||
### Container with Border
|
||||
```qml
|
||||
Rectangle {
|
||||
color: Theme.surfaceContainerHigh
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
}
|
||||
```
|
||||
|
||||
### Hover Effect
|
||||
```qml
|
||||
MouseArea {
|
||||
hoverEnabled: true
|
||||
onEntered: parent.color = Qt.lighter(Theme.surfaceContainerHigh, 1.1)
|
||||
onExited: parent.color = Theme.surfaceContainerHigh
|
||||
}
|
||||
```
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
❌ **Wrong**:
|
||||
```qml
|
||||
font.pixelSize: Theme.fontSizeS // Property doesn't exist
|
||||
font.pixelSize: Theme.iconSizeS // Property doesn't exist
|
||||
```
|
||||
|
||||
✅ **Correct**:
|
||||
```qml
|
||||
font.pixelSize: Theme.fontSizeSmall // Use full name
|
||||
font.pixelSize: Theme.iconSizeSmall // Use full name
|
||||
```
|
||||
|
||||
## Checking Available Properties
|
||||
|
||||
To see all available Theme properties, check `Common/Theme.qml` or use:
|
||||
|
||||
```bash
|
||||
grep "property" Common/Theme.qml
|
||||
```
|
||||
37
quickshell/PLUGINS/WallpaperWatcherDaemon/README.md
Normal file
37
quickshell/PLUGINS/WallpaperWatcherDaemon/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Wallpaper Watcher Daemon
|
||||
|
||||
Run a script whenever your wallpaper changes.
|
||||
|
||||
## What it does
|
||||
|
||||
This daemon monitors wallpaper changes and executes a script you specify. The new wallpaper path gets passed as the first argument to your script.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Enable the plugin in Settings → Plugins
|
||||
2. Configure the script path in the plugin settings
|
||||
3. Make sure your script is executable (`chmod +x /path/to/script.sh`)
|
||||
|
||||
## Example script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
echo "New wallpaper: $1"
|
||||
# Do something with the wallpaper path
|
||||
```
|
||||
|
||||
Save this to a file, make it executable, and point the plugin to it.
|
||||
|
||||
## Use cases
|
||||
|
||||
- Generate color schemes from the new wallpaper
|
||||
- Update theme files based on wallpaper colors
|
||||
- Send notifications when wallpaper changes
|
||||
- Sync wallpaper info to other devices
|
||||
- Log wallpaper history
|
||||
|
||||
## Notes
|
||||
|
||||
- Script errors show up as toast notifications
|
||||
- Script output goes to console logs
|
||||
- The daemon runs invisibly in the background
|
||||
@@ -0,0 +1,66 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginComponent {
|
||||
id: root
|
||||
|
||||
property string scriptPath: pluginData.scriptPath || ""
|
||||
property var popoutService: null
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onWallpaperPathChanged() {
|
||||
if (scriptPath) {
|
||||
var scriptProcess = scriptProcessComponent.createObject(root, {
|
||||
wallpaperPath: SessionData.wallpaperPath
|
||||
})
|
||||
scriptProcess.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: scriptProcessComponent
|
||||
|
||||
Process {
|
||||
property string wallpaperPath: ""
|
||||
|
||||
command: [scriptPath, wallpaperPath]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.trim()) {
|
||||
console.log("WallpaperWatcherDaemon script output:", text.trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.trim()) {
|
||||
ToastService.showError("Wallpaper Change Script Error", text.trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
ToastService.showError("Wallpaper Change Script Error", "Script exited with code: " + exitCode)
|
||||
}
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
console.info("WallpaperWatcherDaemon: Started monitoring wallpaper changes")
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
console.info("WallpaperWatcherDaemon: Stopped monitoring wallpaper changes")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
PluginSettings {
|
||||
id: root
|
||||
pluginId: "wallpaperWatcherDaemon"
|
||||
|
||||
StyledText {
|
||||
text: "Wallpaper Change Hook"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StringSetting {
|
||||
settingKey: "scriptPath"
|
||||
label: "Script Path"
|
||||
description: "Path to a script that will be executed when the wallpaper changes. The new wallpaper path will be passed as the first argument."
|
||||
placeholder: "/path/to/your/script.sh"
|
||||
defaultValue: ""
|
||||
}
|
||||
}
|
||||
16
quickshell/PLUGINS/WallpaperWatcherDaemon/plugin.json
Normal file
16
quickshell/PLUGINS/WallpaperWatcherDaemon/plugin.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "wallpaperWatcherDaemon",
|
||||
"name": "Wallpaper Watcher Daemon",
|
||||
"description": "Background daemon that monitors wallpaper changes and logs them",
|
||||
"version": "1.0.0",
|
||||
"author": "DankMaterialShell",
|
||||
"type": "daemon",
|
||||
"capabilities": ["wallpaper", "monitoring"],
|
||||
"component": "./WallpaperWatcherDaemon.qml",
|
||||
"icon": "wallpaper",
|
||||
"settings": "./WallpaperWatcherSettings.qml",
|
||||
"permissions": [
|
||||
"settings_read",
|
||||
"settings_write"
|
||||
]
|
||||
}
|
||||
115
quickshell/PLUGINS/plugin-schema.json
Normal file
115
quickshell/PLUGINS/plugin-schema.json
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://danklinux.com/schemas/plugin.json",
|
||||
"title": "DankMaterialShell Plugin Manifest",
|
||||
"description": "Schema for DankMaterialShell plugin.json manifest files",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"version",
|
||||
"author",
|
||||
"type",
|
||||
"capabilities",
|
||||
"component"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Unique plugin identifier (camelCase, no spaces)",
|
||||
"pattern": "^[a-zA-Z][a-zA-Z0-9]*$"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Human-readable plugin name",
|
||||
"minLength": 1
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Short description of plugin functionality",
|
||||
"minLength": 1
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "Semantic version string (e.g., '1.0.0')",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?(\\+[a-zA-Z0-9.-]+)?$"
|
||||
},
|
||||
"author": {
|
||||
"type": "string",
|
||||
"description": "Plugin creator name or email",
|
||||
"minLength": 1
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Plugin type",
|
||||
"enum": ["widget", "daemon", "launcher"]
|
||||
},
|
||||
"capabilities": {
|
||||
"type": "array",
|
||||
"description": "Array of plugin capabilities",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"component": {
|
||||
"type": "string",
|
||||
"description": "Relative path to main QML component file",
|
||||
"pattern": "^\\./.*\\.qml$"
|
||||
},
|
||||
"trigger": {
|
||||
"type": "string",
|
||||
"description": "Trigger string for launcher activation (required for launcher type)"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "Material Design icon name"
|
||||
},
|
||||
"settings": {
|
||||
"type": "string",
|
||||
"description": "Path to settings component QML file",
|
||||
"pattern": "^\\./.*\\.qml$"
|
||||
},
|
||||
"requires_dms": {
|
||||
"type": "string",
|
||||
"description": "Minimum DMS version requirement (e.g., '>=0.1.18', '>0.1.0')",
|
||||
"pattern": "^(>=?|<=?|=|>|<)\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
"requires": {
|
||||
"type": "array",
|
||||
"description": "Array of required system tools/dependencies",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"type": "array",
|
||||
"description": "Required capabilities",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"settings_read",
|
||||
"settings_write",
|
||||
"process",
|
||||
"network"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "launcher"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": ["trigger"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"additionalProperties": true
|
||||
}
|
||||
Reference in New Issue
Block a user