mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 05:25:41 -05:00
Compare commits
11 Commits
wip/plugin
...
v0.0.28
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53fb927e36 | ||
|
|
fb5aa0313e | ||
|
|
9b41eecbf1 | ||
|
|
ae461b1caf | ||
|
|
57e36d6710 | ||
|
|
a7c4f09c5b | ||
|
|
554ef16e49 | ||
|
|
082321f860 | ||
|
|
df4f7b8c9e | ||
|
|
3f1742f074 | ||
|
|
4560d5c2d5 |
@@ -1353,7 +1353,7 @@ Singleton {
|
|||||||
id: settingsFile
|
id: settingsFile
|
||||||
|
|
||||||
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
|
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
|
||||||
blockLoading: isGreeterMode
|
blockLoading: true
|
||||||
blockWrites: true
|
blockWrites: true
|
||||||
atomicWrites: true
|
atomicWrites: true
|
||||||
watchChanges: !isGreeterMode
|
watchChanges: !isGreeterMode
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ Item {
|
|||||||
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
||||||
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
text: "Automatically lock after"
|
text: "Automatically lock after"
|
||||||
options: timeoutOptions
|
options: timeoutOptions
|
||||||
|
|
||||||
@@ -116,7 +115,6 @@ Item {
|
|||||||
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
||||||
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
text: "Turn off monitors after"
|
text: "Turn off monitors after"
|
||||||
options: timeoutOptions
|
options: timeoutOptions
|
||||||
|
|
||||||
@@ -153,7 +151,6 @@ Item {
|
|||||||
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
||||||
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
text: "Suspend system after"
|
text: "Suspend system after"
|
||||||
options: timeoutOptions
|
options: timeoutOptions
|
||||||
|
|
||||||
@@ -190,7 +187,6 @@ Item {
|
|||||||
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
|
||||||
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
text: "Hibernate system after"
|
text: "Hibernate system after"
|
||||||
options: timeoutOptions
|
options: timeoutOptions
|
||||||
visible: SessionService.hibernateSupported
|
visible: SessionService.hibernateSupported
|
||||||
|
|||||||
@@ -271,18 +271,17 @@ DankPopout {
|
|||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
visible: searchField.text.length === 0
|
visible: searchField.text.length === 0
|
||||||
leftPadding: Theme.spacingS
|
leftPadding: Theme.spacingS
|
||||||
topPadding: Theme.spacingXS
|
|
||||||
|
|
||||||
Item {
|
Rectangle {
|
||||||
width: 200
|
width: 180
|
||||||
height: 36
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.rightMargin: Theme.spacingS
|
|
||||||
anchors.bottomMargin: Theme.spacingS
|
|
||||||
text: ""
|
text: ""
|
||||||
|
dropdownWidth: 180
|
||||||
currentValue: appLauncher.selectedCategory
|
currentValue: appLauncher.selectedCategory
|
||||||
options: appLauncher.categories
|
options: appLauncher.categories
|
||||||
optionIcons: appLauncher.categoryIcons
|
optionIcons: appLauncher.categoryIcons
|
||||||
@@ -293,7 +292,7 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: parent.width - 310
|
width: parent.width - 290
|
||||||
height: 1
|
height: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -397,12 +397,10 @@ Item {
|
|||||||
|
|
||||||
// Inject PluginService for plugin widgets
|
// Inject PluginService for plugin widgets
|
||||||
if (item.pluginService !== undefined) {
|
if (item.pluginService !== undefined) {
|
||||||
console.log("CenterSection: Injecting PluginService into plugin widget:", model.widgetId)
|
if (item.pluginId !== undefined) {
|
||||||
item.pluginService = PluginService
|
item.pluginId = model.widgetId
|
||||||
if (item.loadTimezones) {
|
|
||||||
console.log("CenterSection: Calling loadTimezones for widget:", model.widgetId)
|
|
||||||
item.loadTimezones()
|
|
||||||
}
|
}
|
||||||
|
item.pluginService = PluginService
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutTimer.restart()
|
layoutTimer.restart()
|
||||||
|
|||||||
@@ -99,17 +99,11 @@ Item {
|
|||||||
target: PluginService
|
target: PluginService
|
||||||
function onPluginLoaded(pluginId) {
|
function onPluginLoaded(pluginId) {
|
||||||
console.log("DankBar: Plugin loaded:", pluginId)
|
console.log("DankBar: Plugin loaded:", pluginId)
|
||||||
// Force componentMap to update by triggering property change
|
SettingsData.widgetDataChanged()
|
||||||
if (topBarContent) {
|
|
||||||
topBarContent.updateComponentMap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function onPluginUnloaded(pluginId) {
|
function onPluginUnloaded(pluginId) {
|
||||||
console.log("DankBar: Plugin unloaded:", pluginId)
|
console.log("DankBar: Plugin unloaded:", pluginId)
|
||||||
// Force componentMap to update by triggering property change
|
SettingsData.widgetDataChanged()
|
||||||
if (topBarContent) {
|
|
||||||
topBarContent.updateComponentMap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,9 +327,10 @@ Item {
|
|||||||
bottom: barWindow.isVertical ? parent.bottom : undefined
|
bottom: barWindow.isVertical ? parent.bottom : undefined
|
||||||
}
|
}
|
||||||
// Only enable mouse handling while hidden (for reveal-on-edge logic).
|
// Only enable mouse handling while hidden (for reveal-on-edge logic).
|
||||||
hoverEnabled: SettingsData.dankBarAutoHide && !topBarCore.reveal
|
readonly property bool inOverview: CompositorService.isNiri && NiriService.inOverview && SettingsData.dankBarOpenOnOverview
|
||||||
|
hoverEnabled: SettingsData.dankBarAutoHide && !topBarCore.reveal && !inOverview
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
enabled: SettingsData.dankBarAutoHide && !topBarCore.reveal
|
enabled: SettingsData.dankBarAutoHide && !topBarCore.reveal && !inOverview
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: topBarContainer
|
id: topBarContainer
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ DankPopout {
|
|||||||
height: 32
|
height: 32
|
||||||
radius: 16
|
radius: 16
|
||||||
color: closeBatteryArea.containsMouse ? Theme.errorHover : "transparent"
|
color: closeBatteryArea.containsMouse ? Theme.errorHover : "transparent"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.top: parent.top
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|||||||
@@ -79,18 +79,15 @@ Loader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (item.pluginService !== undefined) {
|
if (item.pluginService !== undefined) {
|
||||||
console.log("WidgetHost: Injecting PluginService into plugin widget:", widgetId)
|
if (item.pluginId !== undefined) {
|
||||||
item.pluginService = PluginService
|
item.pluginId = widgetId
|
||||||
if (item.loadTimezones) {
|
|
||||||
console.log("WidgetHost: Calling loadTimezones for widget:", widgetId)
|
|
||||||
item.loadTimezones()
|
|
||||||
}
|
}
|
||||||
|
item.pluginService = PluginService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWidgetComponent(widgetId, components) {
|
function getWidgetComponent(widgetId, components) {
|
||||||
// Build component map for built-in widgets
|
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
"launcherButton": components.launcherButtonComponent,
|
"launcherButton": components.launcherButtonComponent,
|
||||||
"workspaceSwitcher": components.workspaceSwitcherComponent,
|
"workspaceSwitcher": components.workspaceSwitcherComponent,
|
||||||
@@ -121,12 +118,10 @@ Loader {
|
|||||||
"systemUpdate": components.systemUpdateComponent
|
"systemUpdate": components.systemUpdateComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for built-in component first
|
|
||||||
if (componentMap[widgetId]) {
|
if (componentMap[widgetId]) {
|
||||||
return componentMap[widgetId]
|
return componentMap[widgetId]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for plugin component
|
|
||||||
let pluginMap = PluginService.getWidgetComponents()
|
let pluginMap = PluginService.getWidgetComponents()
|
||||||
return pluginMap[widgetId] || null
|
return pluginMap[widgetId] || null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ Item {
|
|||||||
|
|
||||||
property string screenName: ""
|
property string screenName: ""
|
||||||
property string randomFact: ""
|
property string randomFact: ""
|
||||||
|
property string hyprlandCurrentLayout: ""
|
||||||
|
property string hyprlandKeyboard: ""
|
||||||
|
property int hyprlandLayoutCount: 0
|
||||||
property bool isPrimaryScreen: {
|
property bool isPrimaryScreen: {
|
||||||
if (!Qt.application.screens || Qt.application.screens.length === 0)
|
if (!Qt.application.screens || Qt.application.screens.length === 0)
|
||||||
return true
|
return true
|
||||||
@@ -61,6 +64,11 @@ Item {
|
|||||||
sessionListProc.running = true
|
sessionListProc.running = true
|
||||||
applyLastSuccessfulUser()
|
applyLastSuccessfulUser()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CompositorService.isHyprland) {
|
||||||
|
updateHyprlandLayout()
|
||||||
|
hyprlandLayoutUpdateTimer.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyLastSuccessfulUser() {
|
function applyLastSuccessfulUser() {
|
||||||
@@ -75,6 +83,56 @@ Item {
|
|||||||
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
WeatherService.removeRef()
|
WeatherService.removeRef()
|
||||||
|
if (CompositorService.isHyprland) {
|
||||||
|
hyprlandLayoutUpdateTimer.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateHyprlandLayout() {
|
||||||
|
if (CompositorService.isHyprland) {
|
||||||
|
hyprlandLayoutProcess.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: hyprlandLayoutProcess
|
||||||
|
running: false
|
||||||
|
command: ["hyprctl", "-j", "devices"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(text)
|
||||||
|
const mainKeyboard = data.keyboards.find(kb => kb.main === true)
|
||||||
|
hyprlandKeyboard = mainKeyboard.name
|
||||||
|
if (mainKeyboard && mainKeyboard.active_keymap) {
|
||||||
|
const parts = mainKeyboard.active_keymap.split(" ")
|
||||||
|
if (parts.length > 0) {
|
||||||
|
hyprlandCurrentLayout = parts[0].substring(0, 2).toUpperCase()
|
||||||
|
} else {
|
||||||
|
hyprlandCurrentLayout = mainKeyboard.active_keymap.substring(0, 2).toUpperCase()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hyprlandCurrentLayout = ""
|
||||||
|
}
|
||||||
|
if (mainKeyboard && mainKeyboard.layout_names) {
|
||||||
|
hyprlandLayoutCount = mainKeyboard.layout_names.length
|
||||||
|
} else {
|
||||||
|
hyprlandLayoutCount = 0
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
hyprlandCurrentLayout = ""
|
||||||
|
hyprlandLayoutCount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: hyprlandLayoutUpdateTimer
|
||||||
|
interval: 1000
|
||||||
|
running: false
|
||||||
|
repeat: true
|
||||||
|
onTriggered: updateHyprlandLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ! This was for development and testing, just leaving so people can see how I did it.
|
// ! This was for development and testing, just leaving so people can see how I did it.
|
||||||
@@ -438,6 +496,8 @@ Item {
|
|||||||
StyledText {
|
StyledText {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 20
|
Layout.preferredHeight: 20
|
||||||
|
Layout.topMargin: -Theme.spacingS
|
||||||
|
Layout.bottomMargin: -Theme.spacingS
|
||||||
text: {
|
text: {
|
||||||
if (GreeterState.pamState === "error")
|
if (GreeterState.pamState === "error")
|
||||||
return "Authentication error - try again"
|
return "Authentication error - try again"
|
||||||
@@ -448,7 +508,6 @@ Item {
|
|||||||
color: Theme.error
|
color: Theme.error
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
visible: GreeterState.pamState !== ""
|
|
||||||
opacity: GreeterState.pamState !== "" ? 1 : 0
|
opacity: GreeterState.pamState !== "" ? 1 : 0
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
@@ -461,7 +520,7 @@ Item {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.topMargin: Theme.spacingS
|
Layout.topMargin: 0
|
||||||
Layout.preferredWidth: switchUserRow.width + Theme.spacingL * 2
|
Layout.preferredWidth: switchUserRow.width + Theme.spacingL * 2
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: 40
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
@@ -516,6 +575,91 @@ Item {
|
|||||||
anchors.margins: Theme.spacingXL
|
anchors.margins: Theme.spacingXL
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: keyboardLayoutRow.width
|
||||||
|
height: keyboardLayoutRow.height
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: {
|
||||||
|
if (CompositorService.isNiri) {
|
||||||
|
return NiriService.keyboardLayoutNames.length > 1
|
||||||
|
} else if (CompositorService.isHyprland) {
|
||||||
|
return hyprlandLayoutCount > 1
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: keyboardLayoutRow
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: Theme.iconSize
|
||||||
|
height: Theme.iconSize
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "keyboard"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: "white"
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: childrenRect.width
|
||||||
|
height: Theme.iconSize
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (CompositorService.isNiri) {
|
||||||
|
const layout = NiriService.getCurrentKeyboardLayoutName()
|
||||||
|
if (!layout) return ""
|
||||||
|
const parts = layout.split(" ")
|
||||||
|
if (parts.length > 0) {
|
||||||
|
return parts[0].substring(0, 2).toUpperCase()
|
||||||
|
}
|
||||||
|
return layout.substring(0, 2).toUpperCase()
|
||||||
|
} else if (CompositorService.isHyprland) {
|
||||||
|
return hyprlandCurrentLayout
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Light
|
||||||
|
color: "white"
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: keyboardLayoutArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (CompositorService.isNiri) {
|
||||||
|
NiriService.cycleKeyboardLayout()
|
||||||
|
} else if (CompositorService.isHyprland) {
|
||||||
|
Quickshell.execDetached([
|
||||||
|
"hyprctl",
|
||||||
|
"switchxkblayout",
|
||||||
|
hyprlandKeyboard,
|
||||||
|
"next"
|
||||||
|
])
|
||||||
|
updateHyprlandLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 1
|
||||||
|
height: 24
|
||||||
|
color: Qt.rgba(255, 255, 255, 0.2)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: MprisController.activePlayer
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
visible: MprisController.activePlayer
|
visible: MprisController.activePlayer
|
||||||
@@ -970,7 +1114,7 @@ Item {
|
|||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
GreeterState.currentSessionIndex = idx
|
GreeterState.currentSessionIndex = idx
|
||||||
GreeterState.selectedSession = GreeterState.sessionExecs[idx]
|
GreeterState.selectedSession = GreeterState.sessionExecs[idx]
|
||||||
GreetdMemory.setLastSessionId(GreeterState.sessionExecs[idx].split(" ")[0])
|
GreetdMemory.setLastSessionId(GreeterState.sessionPaths[idx])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -987,6 +1131,7 @@ Item {
|
|||||||
property string currentSessionName: GreeterState.sessionList[GreeterState.currentSessionIndex] || ""
|
property string currentSessionName: GreeterState.sessionList[GreeterState.currentSessionIndex] || ""
|
||||||
property int pendingParsers: 0
|
property int pendingParsers: 0
|
||||||
|
|
||||||
|
|
||||||
function finalizeSessionSelection() {
|
function finalizeSessionSelection() {
|
||||||
if (GreeterState.sessionList.length === 0) {
|
if (GreeterState.sessionList.length === 0) {
|
||||||
return
|
return
|
||||||
@@ -997,8 +1142,8 @@ Item {
|
|||||||
const savedSession = GreetdMemory.lastSessionId
|
const savedSession = GreetdMemory.lastSessionId
|
||||||
let foundSaved = false
|
let foundSaved = false
|
||||||
if (savedSession) {
|
if (savedSession) {
|
||||||
for (var i = 0; i < GreeterState.sessionExecs.length; i++) {
|
for (var i = 0; i < GreeterState.sessionPaths.length; i++) {
|
||||||
if (GreeterState.sessionExecs[i].toLowerCase().includes(savedSession.toLowerCase()) || GreeterState.sessionList[i].toLowerCase().includes(savedSession.toLowerCase())) {
|
if (GreeterState.sessionPaths[i] === savedSession) {
|
||||||
GreeterState.currentSessionIndex = i
|
GreeterState.currentSessionIndex = i
|
||||||
foundSaved = true
|
foundSaved = true
|
||||||
break
|
break
|
||||||
@@ -1059,10 +1204,13 @@ Item {
|
|||||||
if (!GreeterState.sessionList.includes(name)) {
|
if (!GreeterState.sessionList.includes(name)) {
|
||||||
let newList = GreeterState.sessionList.slice()
|
let newList = GreeterState.sessionList.slice()
|
||||||
let newExecs = GreeterState.sessionExecs.slice()
|
let newExecs = GreeterState.sessionExecs.slice()
|
||||||
|
let newPaths = GreeterState.sessionPaths.slice()
|
||||||
newList.push(name)
|
newList.push(name)
|
||||||
newExecs.push(exec)
|
newExecs.push(exec)
|
||||||
|
newPaths.push(desktopPath)
|
||||||
GreeterState.sessionList = newList
|
GreeterState.sessionList = newList
|
||||||
GreeterState.sessionExecs = newExecs
|
GreeterState.sessionExecs = newExecs
|
||||||
|
GreeterState.sessionPaths = newPaths
|
||||||
root.sessionCount = GreeterState.sessionList.length
|
root.sessionCount = GreeterState.sessionList.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1098,17 +1246,16 @@ Item {
|
|||||||
GreeterState.unlocking = true
|
GreeterState.unlocking = true
|
||||||
const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex]
|
const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex]
|
||||||
if (sessionCmd) {
|
if (sessionCmd) {
|
||||||
GreetdMemory.setLastSessionId(sessionCmd.split(" ")[0])
|
GreetdMemory.setLastSessionId(GreeterState.sessionPaths[GreeterState.currentSessionIndex])
|
||||||
GreetdMemory.setLastSuccessfulUser(GreeterState.username)
|
GreetdMemory.setLastSuccessfulUser(GreeterState.username)
|
||||||
Greetd.launch(sessionCmd.split(" "), [], true)
|
Greetd.launch(sessionCmd.split(" "), ["XDG_SESSION_TYPE=wayland"], true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAuthFailure(message) {
|
function onAuthFailure(message) {
|
||||||
GreeterState.pamState = "fail"
|
GreeterState.pamState = "fail"
|
||||||
GreeterState.reset()
|
GreeterState.passwordBuffer = ""
|
||||||
inputField.text = ""
|
inputField.text = ""
|
||||||
PortalService.profileImage = ""
|
|
||||||
placeholderDelay.restart()
|
placeholderDelay.restart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ Singleton {
|
|||||||
|
|
||||||
property var sessionList: []
|
property var sessionList: []
|
||||||
property var sessionExecs: []
|
property var sessionExecs: []
|
||||||
|
property var sessionPaths: []
|
||||||
property int currentSessionIndex: 0
|
property int currentSessionIndex: 0
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Manual installation:
|
|||||||
1. Install `greetd` (in most distro's standard repositories)
|
1. Install `greetd` (in most distro's standard repositories)
|
||||||
2. Copy `assets/dms-niri.kdl` or `assets/dms-hypr.conf` to `/etc/greetd`
|
2. Copy `assets/dms-niri.kdl` or `assets/dms-hypr.conf` to `/etc/greetd`
|
||||||
- niri if you want to run the greeter under niri, hypr if you want to run the greeter under Hyprland
|
- niri if you want to run the greeter under niri, hypr if you want to run the greeter under Hyprland
|
||||||
3. Copy `assets/greet-niri.sh` or `assets/greet-hyprland.sh` to `/etc/greetd/start-dms.sh`
|
3. Copy `assets/greet-niri.sh` or `assets/greet-hyprland.sh` to `/usr/local/bin/start-dms-greetd.sh`
|
||||||
4. Edit `/etc/greetd/dms-niri.kdl` or `/etc/greetd/dms-hypr.conf` and replace `_DMS_PATH_` with the absolute path to dms, e.g. `/home/joecool/.config/quickshell/dms`
|
4. Edit `/etc/greetd/dms-niri.kdl` or `/etc/greetd/dms-hypr.conf` and replace `_DMS_PATH_` with the absolute path to dms, e.g. `/home/joecool/.config/quickshell/dms`
|
||||||
5. Edit or create `/etc/greetd/config.toml`
|
5. Edit or create `/etc/greetd/config.toml`
|
||||||
```toml
|
```toml
|
||||||
@@ -38,7 +38,7 @@ vt = 1
|
|||||||
# in the `video` group.
|
# in the `video` group.
|
||||||
user = "greeter"
|
user = "greeter"
|
||||||
|
|
||||||
command = "/etc/greetd/start-dms.sh"%
|
command = "/usr/local/bin/start-dms-greetd.sh"
|
||||||
```
|
```
|
||||||
|
|
||||||
Enable the greeter with `sudo systemctl enable greetd`
|
Enable the greeter with `sudo systemctl enable greetd`
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
env = DMS_RUN_GREETER,1
|
env = DMS_RUN_GREETER,1
|
||||||
env = QT_QPA_PLATFORM,wayland
|
|
||||||
env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1
|
|
||||||
env = EGL_PLATFORM,gbm
|
|
||||||
|
|
||||||
exec = sh -c "qs -p _DMS_PATH_; hyprctl dispatch exit"
|
exec = sh -c "qs -p _DMS_PATH_; hyprctl dispatch exit"
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ hotkey-overlay {
|
|||||||
|
|
||||||
environment {
|
environment {
|
||||||
DMS_RUN_GREETER "1"
|
DMS_RUN_GREETER "1"
|
||||||
QT_QPA_PLATFORM "wayland"
|
|
||||||
QT_WAYLAND_DISABLE_WINDOWDECORATION "1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
spawn-at-startup "sh" "-c" "qs -p _DMS_PATH_; niri msg action quit --skip-confirmation"
|
spawn-at-startup "sh" "-c" "qs -p _DMS_PATH_; niri msg action quit --skip-confirmation"
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
EGL_PLATFORM=gbm Hyprland -c /etc/greetd/dms-hypr.conf
|
export XDG_SESSION_TYPE=wayland
|
||||||
|
export QT_QPA_PLATFORM=wayland
|
||||||
|
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
|
||||||
|
export EGL_PLATFORM=gbm
|
||||||
|
|
||||||
|
exec Hyprland -c /etc/greetd/dms-hypr.conf
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
EGL_PLATFORM=gbm niri -c /etc/greetd/dms-niri.kdl
|
export XDG_SESSION_TYPE=wayland
|
||||||
|
export QT_QPA_PLATFORM=wayland
|
||||||
|
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
|
||||||
|
export EGL_PLATFORM=gbm
|
||||||
|
|
||||||
|
exec niri -c /etc/greetd/dms-niri.kdl
|
||||||
@@ -20,6 +20,9 @@ Item {
|
|||||||
property bool unlocking: false
|
property bool unlocking: false
|
||||||
property string pamState: ""
|
property string pamState: ""
|
||||||
property string randomFact: ""
|
property string randomFact: ""
|
||||||
|
property string hyprlandCurrentLayout: ""
|
||||||
|
property string hyprlandKeyboard: ""
|
||||||
|
property int hyprlandLayoutCount: 0
|
||||||
|
|
||||||
signal unlockRequested
|
signal unlockRequested
|
||||||
|
|
||||||
@@ -55,6 +58,11 @@ Item {
|
|||||||
|
|
||||||
WeatherService.addRef()
|
WeatherService.addRef()
|
||||||
UserInfoService.refreshUserInfo()
|
UserInfoService.refreshUserInfo()
|
||||||
|
|
||||||
|
if (CompositorService.isHyprland) {
|
||||||
|
updateHyprlandLayout()
|
||||||
|
hyprlandLayoutUpdateTimer.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onDemoModeChanged: {
|
onDemoModeChanged: {
|
||||||
if (demoMode) {
|
if (demoMode) {
|
||||||
@@ -63,6 +71,56 @@ Item {
|
|||||||
}
|
}
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
WeatherService.removeRef()
|
WeatherService.removeRef()
|
||||||
|
if (CompositorService.isHyprland) {
|
||||||
|
hyprlandLayoutUpdateTimer.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateHyprlandLayout() {
|
||||||
|
if (CompositorService.isHyprland) {
|
||||||
|
hyprlandLayoutProcess.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: hyprlandLayoutProcess
|
||||||
|
running: false
|
||||||
|
command: ["hyprctl", "-j", "devices"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(text)
|
||||||
|
const mainKeyboard = data.keyboards.find(kb => kb.main === true)
|
||||||
|
hyprlandKeyboard = mainKeyboard.name
|
||||||
|
if (mainKeyboard && mainKeyboard.active_keymap) {
|
||||||
|
const parts = mainKeyboard.active_keymap.split(" ")
|
||||||
|
if (parts.length > 0) {
|
||||||
|
hyprlandCurrentLayout = parts[0].substring(0, 2).toUpperCase()
|
||||||
|
} else {
|
||||||
|
hyprlandCurrentLayout = mainKeyboard.active_keymap.substring(0, 2).toUpperCase()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hyprlandCurrentLayout = ""
|
||||||
|
}
|
||||||
|
if (mainKeyboard && mainKeyboard.layout_names) {
|
||||||
|
hyprlandLayoutCount = mainKeyboard.layout_names.length
|
||||||
|
} else {
|
||||||
|
hyprlandLayoutCount = 0
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
hyprlandCurrentLayout = ""
|
||||||
|
hyprlandLayoutCount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: hyprlandLayoutUpdateTimer
|
||||||
|
interval: 1000
|
||||||
|
running: false
|
||||||
|
repeat: true
|
||||||
|
onTriggered: updateHyprlandLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
@@ -520,7 +578,7 @@ Item {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: root.pamState ? 20 : 0
|
Layout.preferredHeight: 20
|
||||||
text: {
|
text: {
|
||||||
if (root.pamState === "error") {
|
if (root.pamState === "error") {
|
||||||
return "Authentication error - try again"
|
return "Authentication error - try again"
|
||||||
@@ -536,7 +594,6 @@ Item {
|
|||||||
color: Theme.error
|
color: Theme.error
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
visible: root.pamState !== ""
|
|
||||||
opacity: root.pamState !== "" ? 1 : 0
|
opacity: root.pamState !== "" ? 1 : 0
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
@@ -545,13 +602,6 @@ Item {
|
|||||||
easing.type: Theme.standardEasing
|
easing.type: Theme.standardEasing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on Layout.preferredHeight {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,6 +622,92 @@ Item {
|
|||||||
anchors.margins: Theme.spacingXL
|
anchors.margins: Theme.spacingXL
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: keyboardLayoutRow.width
|
||||||
|
height: keyboardLayoutRow.height
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: {
|
||||||
|
if (CompositorService.isNiri) {
|
||||||
|
return NiriService.keyboardLayoutNames.length > 1
|
||||||
|
} else if (CompositorService.isHyprland) {
|
||||||
|
return hyprlandLayoutCount > 1
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: keyboardLayoutRow
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: Theme.iconSize
|
||||||
|
height: Theme.iconSize
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "keyboard"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: "white"
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: childrenRect.width
|
||||||
|
height: Theme.iconSize
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (CompositorService.isNiri) {
|
||||||
|
const layout = NiriService.getCurrentKeyboardLayoutName()
|
||||||
|
if (!layout) return ""
|
||||||
|
const parts = layout.split(" ")
|
||||||
|
if (parts.length > 0) {
|
||||||
|
return parts[0].substring(0, 2).toUpperCase()
|
||||||
|
}
|
||||||
|
return layout.substring(0, 2).toUpperCase()
|
||||||
|
} else if (CompositorService.isHyprland) {
|
||||||
|
return hyprlandCurrentLayout
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Light
|
||||||
|
color: "white"
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: keyboardLayoutArea
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: !demoMode
|
||||||
|
hoverEnabled: enabled
|
||||||
|
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
onClicked: {
|
||||||
|
if (CompositorService.isNiri) {
|
||||||
|
NiriService.cycleKeyboardLayout()
|
||||||
|
} else if (CompositorService.isHyprland) {
|
||||||
|
Quickshell.execDetached([
|
||||||
|
"hyprctl",
|
||||||
|
"switchxkblayout",
|
||||||
|
hyprlandKeyboard,
|
||||||
|
"next"
|
||||||
|
])
|
||||||
|
updateHyprlandLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 1
|
||||||
|
height: 24
|
||||||
|
color: Qt.rgba(255, 255, 255, 0.2)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: MprisController.activePlayer
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
visible: MprisController.activePlayer
|
visible: MprisController.activePlayer
|
||||||
@@ -1036,6 +1172,8 @@ Item {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log("Authentication failed:", res)
|
console.log("Authentication failed:", res)
|
||||||
|
passwordField.text = ""
|
||||||
|
root.passwordBuffer = ""
|
||||||
if (res === PamResult.Error)
|
if (res === PamResult.Error)
|
||||||
root.pamState = "error"
|
root.pamState = "error"
|
||||||
else if (res === PamResult.MaxTries)
|
else if (res === PamResult.MaxTries)
|
||||||
|
|||||||
@@ -157,7 +157,6 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width
|
|
||||||
text: "Low Priority"
|
text: "Low Priority"
|
||||||
description: "Timeout for low priority notifications"
|
description: "Timeout for low priority notifications"
|
||||||
currentValue: getTimeoutText(SettingsData.notificationTimeoutLow)
|
currentValue: getTimeoutText(SettingsData.notificationTimeoutLow)
|
||||||
@@ -173,7 +172,6 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width
|
|
||||||
text: "Normal Priority"
|
text: "Normal Priority"
|
||||||
description: "Timeout for normal priority notifications"
|
description: "Timeout for normal priority notifications"
|
||||||
currentValue: getTimeoutText(SettingsData.notificationTimeoutNormal)
|
currentValue: getTimeoutText(SettingsData.notificationTimeoutNormal)
|
||||||
@@ -189,7 +187,6 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width
|
|
||||||
text: "Critical Priority"
|
text: "Critical Priority"
|
||||||
description: "Timeout for critical priority notifications"
|
description: "Timeout for critical priority notifications"
|
||||||
currentValue: getTimeoutText(SettingsData.notificationTimeoutCritical)
|
currentValue: getTimeoutText(SettingsData.notificationTimeoutCritical)
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ Column {
|
|||||||
required property string settingKey
|
required property string settingKey
|
||||||
required property string label
|
required property string label
|
||||||
property string description: ""
|
property string description: ""
|
||||||
property var items: []
|
property var defaultValue: []
|
||||||
|
property var items: defaultValue
|
||||||
property Component delegate: null
|
property Component delegate: null
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -17,7 +18,7 @@ Column {
|
|||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
const settings = findSettings()
|
const settings = findSettings()
|
||||||
if (settings) {
|
if (settings) {
|
||||||
items = settings.loadValue(settingKey, [])
|
items = settings.loadValue(settingKey, defaultValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ Column {
|
|||||||
required property string label
|
required property string label
|
||||||
property string description: ""
|
property string description: ""
|
||||||
property var fields: []
|
property var fields: []
|
||||||
property var items: []
|
property var defaultValue: []
|
||||||
|
property var items: defaultValue
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
@@ -17,7 +18,7 @@ Column {
|
|||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
const settings = findSettings()
|
const settings = findSettings()
|
||||||
if (settings) {
|
if (settings) {
|
||||||
items = settings.loadValue(settingKey, [])
|
items = settings.loadValue(settingKey, defaultValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ Item {
|
|||||||
property var parentScreen: null
|
property var parentScreen: null
|
||||||
property real widgetThickness: 30
|
property real widgetThickness: 30
|
||||||
property real barThickness: 48
|
property real barThickness: 48
|
||||||
|
property string pluginId: ""
|
||||||
|
property var pluginService: null
|
||||||
|
|
||||||
property Component horizontalBarPill: null
|
property Component horizontalBarPill: null
|
||||||
property Component verticalBarPill: null
|
property Component verticalBarPill: null
|
||||||
@@ -18,11 +20,42 @@ Item {
|
|||||||
property real popoutWidth: 400
|
property real popoutWidth: 400
|
||||||
property real popoutHeight: 400
|
property real popoutHeight: 400
|
||||||
|
|
||||||
|
property var pluginData: ({})
|
||||||
|
|
||||||
readonly property bool isVertical: axis?.isVertical ?? false
|
readonly property bool isVertical: axis?.isVertical ?? false
|
||||||
readonly property bool hasHorizontalPill: horizontalBarPill !== null
|
readonly property bool hasHorizontalPill: horizontalBarPill !== null
|
||||||
readonly property bool hasVerticalPill: verticalBarPill !== null
|
readonly property bool hasVerticalPill: verticalBarPill !== null
|
||||||
readonly property bool hasPopout: popoutContent !== null
|
readonly property bool hasPopout: popoutContent !== null
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
loadPluginData()
|
||||||
|
}
|
||||||
|
|
||||||
|
onPluginServiceChanged: {
|
||||||
|
loadPluginData()
|
||||||
|
}
|
||||||
|
|
||||||
|
onPluginIdChanged: {
|
||||||
|
loadPluginData()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: pluginService
|
||||||
|
function onPluginDataChanged(changedPluginId) {
|
||||||
|
if (changedPluginId === pluginId) {
|
||||||
|
loadPluginData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPluginData() {
|
||||||
|
if (!pluginService || !pluginId) {
|
||||||
|
pluginData = {}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pluginData = SettingsData.getPluginSettingsForPlugin(pluginId)
|
||||||
|
}
|
||||||
|
|
||||||
width: isVertical ? (hasVerticalPill ? verticalPill.width : 0) : (hasHorizontalPill ? horizontalPill.width : 0)
|
width: isVertical ? (hasVerticalPill ? verticalPill.width : 0) : (hasHorizontalPill ? horizontalPill.width : 0)
|
||||||
height: isVertical ? (hasVerticalPill ? verticalPill.height : 0) : (hasHorizontalPill ? horizontalPill.height : 0)
|
height: isVertical ? (hasVerticalPill ? verticalPill.height : 0) : (hasHorizontalPill ? horizontalPill.height : 0)
|
||||||
|
|
||||||
@@ -60,6 +93,12 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closePopout() {
|
||||||
|
if (pluginPopout) {
|
||||||
|
pluginPopout.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PluginPopout {
|
PluginPopout {
|
||||||
id: pluginPopout
|
id: pluginPopout
|
||||||
contentWidth: root.popoutWidth
|
contentWidth: root.popoutWidth
|
||||||
|
|||||||
@@ -62,53 +62,24 @@ DankPopout {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: popoutColumn
|
id: popoutColumn
|
||||||
width: parent.width - Theme.spacingL * 2
|
width: parent.width - Theme.spacingS * 2
|
||||||
anchors.left: parent.left
|
height: parent.height - Theme.spacingS * 2
|
||||||
anchors.top: parent.top
|
x: Theme.spacingS
|
||||||
anchors.margins: Theme.spacingL
|
y: Theme.spacingS
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
height: 32
|
|
||||||
visible: closeButton.visible
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width - 32
|
|
||||||
height: 32
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: closeButton
|
|
||||||
width: 32
|
|
||||||
height: 32
|
|
||||||
radius: 16
|
|
||||||
color: closeArea.containsMouse ? Theme.errorHover : "transparent"
|
|
||||||
visible: true
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "close"
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: closeArea.containsMouse ? Theme.error : Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: closeArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onPressed: {
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: popoutContent
|
id: popoutContent
|
||||||
width: parent.width
|
width: parent.width
|
||||||
sourceComponent: root.pluginContent
|
sourceComponent: root.pluginContent
|
||||||
|
|
||||||
|
onLoaded: {
|
||||||
|
if (item && "closePopout" in item) {
|
||||||
|
item.closePopout = function() {
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -9,12 +10,35 @@ Item {
|
|||||||
property var pluginService: null
|
property var pluginService: null
|
||||||
default property alias content: settingsColumn.children
|
default property alias content: settingsColumn.children
|
||||||
|
|
||||||
implicitHeight: settingsColumn.implicitHeight
|
signal settingChanged()
|
||||||
|
|
||||||
|
implicitHeight: hasPermission ? settingsColumn.implicitHeight : errorText.implicitHeight
|
||||||
height: implicitHeight
|
height: implicitHeight
|
||||||
|
|
||||||
|
readonly property bool hasPermission: pluginService && pluginService.hasPermission ? pluginService.hasPermission(pluginId, "settings_write") : true
|
||||||
|
|
||||||
|
onPluginServiceChanged: {
|
||||||
|
if (pluginService) {
|
||||||
|
for (let i = 0; i < settingsColumn.children.length; i++) {
|
||||||
|
const child = settingsColumn.children[i]
|
||||||
|
if (child.loadValue) {
|
||||||
|
child.loadValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function saveValue(key, value) {
|
function saveValue(key, value) {
|
||||||
if (pluginService && pluginService.savePluginData) {
|
if (!pluginService) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!hasPermission) {
|
||||||
|
console.warn("PluginSettings: Plugin", pluginId, "does not have settings_write permission")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (pluginService.savePluginData) {
|
||||||
pluginService.savePluginData(pluginId, key, value)
|
pluginService.savePluginData(pluginId, key, value)
|
||||||
|
settingChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,8 +49,21 @@ Item {
|
|||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: errorText
|
||||||
|
visible: pluginService && !root.hasPermission
|
||||||
|
anchors.fill: parent
|
||||||
|
text: "This plugin does not have 'settings_write' permission.\n\nAdd \"permissions\": [\"settings_read\", \"settings_write\"] to plugin.json"
|
||||||
|
color: Theme.error
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: settingsColumn
|
id: settingsColumn
|
||||||
|
visible: root.hasPermission
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
}
|
}
|
||||||
|
|||||||
76
Modules/Plugins/PopoutComponent.qml
Normal file
76
Modules/Plugins/PopoutComponent.qml
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string headerText: ""
|
||||||
|
property string detailsText: ""
|
||||||
|
property bool showCloseButton: false
|
||||||
|
property var closePopout: null
|
||||||
|
|
||||||
|
readonly property int headerHeight: popoutHeader.visible ? popoutHeader.height : 0
|
||||||
|
readonly property int detailsHeight: popoutDetails.visible ? popoutDetails.implicitHeight : 0
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: popoutHeader
|
||||||
|
width: parent.width
|
||||||
|
height: 40
|
||||||
|
visible: headerText.length > 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: root.headerText
|
||||||
|
font.pixelSize: Theme.fontSizeLarge + 4
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: closeButton
|
||||||
|
width: 32
|
||||||
|
height: 32
|
||||||
|
radius: 16
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: closeArea.containsMouse ? Theme.errorHover : "transparent"
|
||||||
|
visible: root.showCloseButton
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "close"
|
||||||
|
size: Theme.iconSize - 4
|
||||||
|
color: closeArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: closeArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onPressed: {
|
||||||
|
if (root.closePopout) {
|
||||||
|
root.closePopout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: popoutDetails
|
||||||
|
width: parent.width
|
||||||
|
leftPadding: Theme.spacingS
|
||||||
|
bottomPadding: Theme.spacingS
|
||||||
|
text: root.detailsText
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
visible: detailsText.length > 0
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,17 @@ Column {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
function loadValue() {
|
||||||
|
const settings = findSettings()
|
||||||
|
if (settings && settings.pluginService) {
|
||||||
|
value = settings.loadValue(settingKey, defaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
loadValue()
|
||||||
|
}
|
||||||
|
|
||||||
readonly property var optionLabels: {
|
readonly property var optionLabels: {
|
||||||
const labels = []
|
const labels = []
|
||||||
for (let i = 0; i < options.length; i++) {
|
for (let i = 0; i < options.length; i++) {
|
||||||
@@ -49,13 +60,6 @@ Column {
|
|||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
const settings = findSettings()
|
|
||||||
if (settings) {
|
|
||||||
value = settings.loadValue(settingKey, defaultValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onValueChanged: {
|
onValueChanged: {
|
||||||
const settings = findSettings()
|
const settings = findSettings()
|
||||||
if (settings) {
|
if (settings) {
|
||||||
@@ -74,35 +78,10 @@ Column {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width * 0.4
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: root.label
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: root.description
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
width: parent.width
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
visible: root.description !== ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width * 0.6 - Theme.spacingM
|
width: parent.width
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
text: root.label
|
||||||
|
description: root.description
|
||||||
currentValue: root.valueToLabel[root.value] || root.value
|
currentValue: root.valueToLabel[root.value] || root.value
|
||||||
options: root.optionLabels
|
options: root.optionLabels
|
||||||
onValueChanged: newValue => {
|
onValueChanged: newValue => {
|
||||||
@@ -110,4 +89,3 @@ Column {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
81
Modules/Plugins/SliderSetting.qml
Normal file
81
Modules/Plugins/SliderSetting.qml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property string settingKey
|
||||||
|
required property string label
|
||||||
|
property string description: ""
|
||||||
|
property int defaultValue: 0
|
||||||
|
property int value: defaultValue
|
||||||
|
property int minimum: 0
|
||||||
|
property int maximum: 100
|
||||||
|
property string leftIcon: ""
|
||||||
|
property string rightIcon: ""
|
||||||
|
property string unit: ""
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
function loadValue() {
|
||||||
|
const settings = findSettings()
|
||||||
|
if (settings && settings.pluginService) {
|
||||||
|
value = settings.loadValue(settingKey, defaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
loadValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
onValueChanged: {
|
||||||
|
const settings = findSettings()
|
||||||
|
if (settings) {
|
||||||
|
settings.saveValue(settingKey, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSettings() {
|
||||||
|
let item = parent
|
||||||
|
while (item) {
|
||||||
|
if (item.saveValue !== undefined && item.loadValue !== undefined) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
item = item.parent
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.label
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.description
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
visible: root.description !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
DankSlider {
|
||||||
|
width: parent.width
|
||||||
|
value: root.value
|
||||||
|
minimum: root.minimum
|
||||||
|
maximum: root.maximum
|
||||||
|
leftIcon: root.leftIcon
|
||||||
|
rightIcon: root.rightIcon
|
||||||
|
unit: root.unit
|
||||||
|
wheelEnabled: false
|
||||||
|
thumbOutlineColor: Theme.surfaceContainerHighest
|
||||||
|
onSliderValueChanged: newValue => {
|
||||||
|
root.value = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,15 @@ import qs.Widgets
|
|||||||
Item {
|
Item {
|
||||||
id: dankBarTab
|
id: dankBarTab
|
||||||
|
|
||||||
|
function getWidgetsForPopup() {
|
||||||
|
return baseWidgetDefinitions.filter(widget => {
|
||||||
|
if (widget.warning && widget.warning.includes("Plugin is disabled")) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
property var baseWidgetDefinitions: {
|
property var baseWidgetDefinitions: {
|
||||||
var coreWidgets = [{
|
var coreWidgets = [{
|
||||||
"id": "launcherButton",
|
"id": "launcherButton",
|
||||||
@@ -179,16 +188,18 @@ Item {
|
|||||||
"enabled": SystemUpdateService.distributionSupported
|
"enabled": SystemUpdateService.distributionSupported
|
||||||
}]
|
}]
|
||||||
|
|
||||||
// Add plugin widgets dynamically
|
// Add all available plugins (loaded and unloaded)
|
||||||
var loadedPlugins = PluginService.getLoadedPlugins()
|
var allPlugins = PluginService.getAvailablePlugins()
|
||||||
for (var i = 0; i < loadedPlugins.length; i++) {
|
for (var i = 0; i < allPlugins.length; i++) {
|
||||||
var plugin = loadedPlugins[i]
|
var plugin = allPlugins[i]
|
||||||
|
var isLoaded = PluginService.isPluginLoaded(plugin.id)
|
||||||
coreWidgets.push({
|
coreWidgets.push({
|
||||||
"id": plugin.id,
|
"id": plugin.id,
|
||||||
"text": plugin.name,
|
"text": plugin.name,
|
||||||
"description": plugin.description || "Plugin widget",
|
"description": plugin.description || "Plugin widget",
|
||||||
"icon": plugin.icon || "extension",
|
"icon": plugin.icon || "extension",
|
||||||
"enabled": true
|
"enabled": isLoaded,
|
||||||
|
"warning": !isLoaded ? "Plugin is disabled - enable in Plugins settings to use" : undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1146,7 +1157,7 @@ Item {
|
|||||||
}
|
}
|
||||||
onAddWidget: sectionId => {
|
onAddWidget: sectionId => {
|
||||||
widgetSelectionPopup.allWidgets
|
widgetSelectionPopup.allWidgets
|
||||||
= dankBarTab.baseWidgetDefinitions
|
= dankBarTab.getWidgetsForPopup()
|
||||||
widgetSelectionPopup.targetSection = sectionId
|
widgetSelectionPopup.targetSection = sectionId
|
||||||
widgetSelectionPopup.safeOpen()
|
widgetSelectionPopup.safeOpen()
|
||||||
}
|
}
|
||||||
@@ -1218,7 +1229,7 @@ Item {
|
|||||||
}
|
}
|
||||||
onAddWidget: sectionId => {
|
onAddWidget: sectionId => {
|
||||||
widgetSelectionPopup.allWidgets
|
widgetSelectionPopup.allWidgets
|
||||||
= dankBarTab.baseWidgetDefinitions
|
= dankBarTab.getWidgetsForPopup()
|
||||||
widgetSelectionPopup.targetSection = sectionId
|
widgetSelectionPopup.targetSection = sectionId
|
||||||
widgetSelectionPopup.safeOpen()
|
widgetSelectionPopup.safeOpen()
|
||||||
}
|
}
|
||||||
@@ -1290,7 +1301,7 @@ Item {
|
|||||||
}
|
}
|
||||||
onAddWidget: sectionId => {
|
onAddWidget: sectionId => {
|
||||||
widgetSelectionPopup.allWidgets
|
widgetSelectionPopup.allWidgets
|
||||||
= dankBarTab.baseWidgetDefinitions
|
= dankBarTab.getWidgetsForPopup()
|
||||||
widgetSelectionPopup.targetSection = sectionId
|
widgetSelectionPopup.targetSection = sectionId
|
||||||
widgetSelectionPopup.safeOpen()
|
widgetSelectionPopup.safeOpen()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -520,7 +520,6 @@ Item {
|
|||||||
DankDropdown {
|
DankDropdown {
|
||||||
id: monitorDropdown
|
id: monitorDropdown
|
||||||
|
|
||||||
width: parent.width - parent.leftPadding
|
|
||||||
text: "Monitor"
|
text: "Monitor"
|
||||||
description: "Select monitor to configure wallpaper"
|
description: "Select monitor to configure wallpaper"
|
||||||
currentValue: selectedMonitorName || "No monitors"
|
currentValue: selectedMonitorName || "No monitors"
|
||||||
@@ -678,7 +677,6 @@ Item {
|
|||||||
property var intervalOptions: ["1 minute", "5 minutes", "15 minutes", "30 minutes", "1 hour", "1.5 hours", "2 hours", "3 hours", "4 hours", "6 hours", "8 hours", "12 hours"]
|
property var intervalOptions: ["1 minute", "5 minutes", "15 minutes", "30 minutes", "1 hour", "1.5 hours", "2 hours", "3 hours", "4 hours", "6 hours", "8 hours", "12 hours"]
|
||||||
property var intervalValues: [60, 300, 900, 1800, 3600, 5400, 7200, 10800, 14400, 21600, 28800, 43200]
|
property var intervalValues: [60, 300, 900, 1800, 3600, 5400, 7200, 10800, 14400, 21600, 28800, 43200]
|
||||||
|
|
||||||
width: parent.width - parent.leftPadding
|
|
||||||
visible: {
|
visible: {
|
||||||
if (SessionData.perMonitorWallpaper) {
|
if (SessionData.perMonitorWallpaper) {
|
||||||
return SessionData.getMonitorCyclingSettings(selectedMonitorName).mode === "interval"
|
return SessionData.getMonitorCyclingSettings(selectedMonitorName).mode === "interval"
|
||||||
@@ -833,7 +831,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width
|
|
||||||
text: "Transition Effect"
|
text: "Transition Effect"
|
||||||
description: "Visual effect used when wallpaper changes"
|
description: "Visual effect used when wallpaper changes"
|
||||||
currentValue: {
|
currentValue: {
|
||||||
@@ -851,8 +848,6 @@ Item {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
visible: SessionData.wallpaperTransition === "random"
|
visible: SessionData.wallpaperTransition === "random"
|
||||||
leftPadding: Theme.spacingM
|
|
||||||
rightPadding: Theme.spacingM
|
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: "Include Transitions"
|
text: "Include Transitions"
|
||||||
@@ -866,12 +861,12 @@ Item {
|
|||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
width: parent.width - parent.leftPadding - parent.rightPadding
|
width: parent.width
|
||||||
}
|
}
|
||||||
|
|
||||||
DankButtonGroup {
|
DankButtonGroup {
|
||||||
id: transitionGroup
|
id: transitionGroup
|
||||||
width: parent.width - parent.leftPadding - parent.rightPadding
|
width: parent.width
|
||||||
selectionMode: "multi"
|
selectionMode: "multi"
|
||||||
model: SessionData.availableWallpaperTransitions.filter(t => t !== "none")
|
model: SessionData.availableWallpaperTransitions.filter(t => t !== "none")
|
||||||
initialSelection: SessionData.includedTransitions
|
initialSelection: SessionData.includedTransitions
|
||||||
@@ -959,7 +954,6 @@ Item {
|
|||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
id: personalizationMatugenPaletteDropdown
|
id: personalizationMatugenPaletteDropdown
|
||||||
width: parent.width
|
|
||||||
text: "Matugen Palette"
|
text: "Matugen Palette"
|
||||||
description: "Select the palette algorithm used for wallpaper-based colors"
|
description: "Select the palette algorithm used for wallpaper-based colors"
|
||||||
options: Theme.availableMatugenSchemes.map(function (option) { return option.label })
|
options: Theme.availableMatugenSchemes.map(function (option) { return option.label })
|
||||||
@@ -1075,7 +1069,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width
|
|
||||||
text: "Temperature"
|
text: "Temperature"
|
||||||
description: "Color temperature for night mode"
|
description: "Color temperature for night mode"
|
||||||
currentValue: SessionData.nightModeTemperature + "K"
|
currentValue: SessionData.nightModeTemperature + "K"
|
||||||
@@ -1424,7 +1417,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width
|
|
||||||
text: "Popup Position"
|
text: "Popup Position"
|
||||||
description: "Choose where notification popups appear on screen"
|
description: "Choose where notification popups appear on screen"
|
||||||
currentValue: {
|
currentValue: {
|
||||||
@@ -1506,7 +1498,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width
|
|
||||||
text: "Font Family"
|
text: "Font Family"
|
||||||
description: "Select system font family"
|
description: "Select system font family"
|
||||||
currentValue: {
|
currentValue: {
|
||||||
@@ -1528,7 +1519,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width
|
|
||||||
text: "Font Weight"
|
text: "Font Weight"
|
||||||
description: "Select font weight"
|
description: "Select font weight"
|
||||||
currentValue: {
|
currentValue: {
|
||||||
@@ -1595,7 +1585,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width
|
|
||||||
text: "Monospace Font"
|
text: "Monospace Font"
|
||||||
description: "Select monospace font for process list and technical displays"
|
description: "Select monospace font for process list and technical displays"
|
||||||
currentValue: {
|
currentValue: {
|
||||||
|
|||||||
@@ -10,14 +10,6 @@ Item {
|
|||||||
|
|
||||||
property string expandedPluginId: ""
|
property string expandedPluginId: ""
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
console.log("PluginsTab: Component completed")
|
|
||||||
console.log("PluginsTab: PluginService available:", typeof PluginService !== "undefined")
|
|
||||||
if (typeof PluginService !== "undefined") {
|
|
||||||
console.log("PluginsTab: Available plugins:", Object.keys(PluginService.availablePlugins).length)
|
|
||||||
console.log("PluginsTab: Plugin directory:", PluginService.pluginDirectory)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankFlickable {
|
DankFlickable {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -186,9 +178,6 @@ Item {
|
|||||||
property bool hasSettings: pluginData && pluginData.settings !== undefined && pluginData.settings !== ""
|
property bool hasSettings: pluginData && pluginData.settings !== undefined && pluginData.settings !== ""
|
||||||
property bool isExpanded: pluginsTab.expandedPluginId === pluginId
|
property bool isExpanded: pluginsTab.expandedPluginId === pluginId
|
||||||
|
|
||||||
onIsExpandedChanged: {
|
|
||||||
console.log("Plugin", pluginId, "isExpanded changed to:", isExpanded)
|
|
||||||
}
|
|
||||||
|
|
||||||
color: pluginMouseArea.containsMouse ? Theme.surfacePressed : (isExpanded ? Theme.surfaceContainerHighest : Theme.surfaceContainerHigh)
|
color: pluginMouseArea.containsMouse ? Theme.surfacePressed : (isExpanded ? Theme.surfaceContainerHighest : Theme.surfaceContainerHigh)
|
||||||
border.width: 0
|
border.width: 0
|
||||||
@@ -200,15 +189,8 @@ Item {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: pluginDelegate.hasSettings ? Qt.PointingHandCursor : Qt.ArrowCursor
|
cursorShape: pluginDelegate.hasSettings ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
console.log("Plugin clicked:", pluginDelegate.pluginId, "hasSettings:", pluginDelegate.hasSettings, "isLoaded:", PluginService.isPluginLoaded(pluginDelegate.pluginId))
|
|
||||||
if (pluginDelegate.hasSettings) {
|
if (pluginDelegate.hasSettings) {
|
||||||
if (pluginsTab.expandedPluginId === pluginDelegate.pluginId) {
|
pluginsTab.expandedPluginId = pluginsTab.expandedPluginId === pluginDelegate.pluginId ? "" : pluginDelegate.pluginId
|
||||||
console.log("Collapsing plugin:", pluginDelegate.pluginId)
|
|
||||||
pluginsTab.expandedPluginId = ""
|
|
||||||
} else {
|
|
||||||
console.log("Expanding plugin:", pluginDelegate.pluginId)
|
|
||||||
pluginsTab.expandedPluginId = pluginDelegate.pluginId
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,7 +216,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: parent.width - Theme.iconSize - Theme.spacingM - pluginToggle.width - Theme.spacingM
|
width: parent.width - Theme.iconSize - Theme.spacingM - toggleRow.width - Theme.spacingM
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
@@ -267,32 +249,74 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: toggleRow
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 28
|
||||||
|
height: 28
|
||||||
|
radius: 14
|
||||||
|
color: reloadArea.containsMouse ? Theme.surfaceContainerHighest : "transparent"
|
||||||
|
visible: PluginService.isPluginLoaded(pluginDelegate.pluginId)
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "refresh"
|
||||||
|
size: 16
|
||||||
|
color: reloadArea.containsMouse ? Theme.primary : Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: reloadArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
const currentPluginId = pluginDelegate.pluginId
|
||||||
|
const currentPluginName = pluginDelegate.pluginName
|
||||||
|
pluginsTab.isReloading = true
|
||||||
|
if (PluginService.reloadPlugin(currentPluginId)) {
|
||||||
|
ToastService.showInfo("Plugin reloaded: " + currentPluginName)
|
||||||
|
} else {
|
||||||
|
ToastService.showError("Failed to reload plugin: " + currentPluginName)
|
||||||
|
pluginsTab.isReloading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DankToggle {
|
DankToggle {
|
||||||
id: pluginToggle
|
id: pluginToggle
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
checked: PluginService.isPluginLoaded(pluginDelegate.pluginId)
|
checked: PluginService.isPluginLoaded(pluginDelegate.pluginId)
|
||||||
onToggled: (isChecked) => {
|
onToggled: isChecked => {
|
||||||
|
const currentPluginId = pluginDelegate.pluginId
|
||||||
|
const currentPluginName = pluginDelegate.pluginName
|
||||||
|
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
if (PluginService.enablePlugin(pluginDelegate.pluginId)) {
|
if (PluginService.enablePlugin(currentPluginId)) {
|
||||||
ToastService.showInfo("Plugin enabled: " + pluginDelegate.pluginName)
|
ToastService.showInfo("Plugin enabled: " + currentPluginName)
|
||||||
} else {
|
} else {
|
||||||
ToastService.showError("Failed to enable plugin: " + pluginDelegate.pluginName)
|
ToastService.showError("Failed to enable plugin: " + currentPluginName)
|
||||||
checked = false
|
checked = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (PluginService.disablePlugin(pluginDelegate.pluginId)) {
|
if (PluginService.disablePlugin(currentPluginId)) {
|
||||||
ToastService.showInfo("Plugin disabled: " + pluginDelegate.pluginName)
|
ToastService.showInfo("Plugin disabled: " + currentPluginName)
|
||||||
if (pluginsTab.expandedPluginId === pluginDelegate.pluginId) {
|
if (pluginDelegate.isExpanded) {
|
||||||
pluginsTab.expandedPluginId = ""
|
pluginsTab.expandedPluginId = ""
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ToastService.showError("Failed to disable plugin: " + pluginDelegate.pluginName)
|
ToastService.showError("Failed to disable plugin: " + currentPluginName)
|
||||||
checked = true
|
checked = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -355,15 +379,8 @@ Item {
|
|||||||
active: pluginDelegate.isExpanded && pluginDelegate.hasSettings && PluginService.isPluginLoaded(pluginDelegate.pluginId)
|
active: pluginDelegate.isExpanded && pluginDelegate.hasSettings && PluginService.isPluginLoaded(pluginDelegate.pluginId)
|
||||||
asynchronous: false
|
asynchronous: false
|
||||||
|
|
||||||
onActiveChanged: {
|
|
||||||
console.log("Settings loader active changed to:", active, "for plugin:", pluginDelegate.pluginId,
|
|
||||||
"isExpanded:", pluginDelegate.isExpanded, "hasSettings:", pluginDelegate.hasSettings,
|
|
||||||
"isLoaded:", PluginService.isPluginLoaded(pluginDelegate.pluginId))
|
|
||||||
}
|
|
||||||
|
|
||||||
source: {
|
source: {
|
||||||
if (active && pluginDelegate.pluginSettingsPath) {
|
if (active && pluginDelegate.pluginSettingsPath) {
|
||||||
console.log("Loading plugin settings from:", pluginDelegate.pluginSettingsPath)
|
|
||||||
var path = pluginDelegate.pluginSettingsPath
|
var path = pluginDelegate.pluginSettingsPath
|
||||||
if (!path.startsWith("file://")) {
|
if (!path.startsWith("file://")) {
|
||||||
path = "file://" + path
|
path = "file://" + path
|
||||||
@@ -373,37 +390,9 @@ Item {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
onStatusChanged: {
|
|
||||||
console.log("Settings loader status changed:", status, "for plugin:", pluginDelegate.pluginId)
|
|
||||||
if (status === Loader.Error) {
|
|
||||||
console.error("Failed to load plugin settings:", pluginDelegate.pluginSettingsPath)
|
|
||||||
} else if (status === Loader.Ready) {
|
|
||||||
console.log("Settings successfully loaded for plugin:", pluginDelegate.pluginId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoaded: {
|
onLoaded: {
|
||||||
if (item) {
|
if (item && typeof PluginService !== "undefined") {
|
||||||
console.log("Plugin settings loaded for:", pluginDelegate.pluginId)
|
|
||||||
|
|
||||||
if (typeof PluginService !== "undefined") {
|
|
||||||
console.log("Making PluginService available to plugin settings")
|
|
||||||
console.log("PluginService functions available:",
|
|
||||||
"savePluginData" in PluginService,
|
|
||||||
"loadPluginData" in PluginService)
|
|
||||||
item.pluginService = PluginService
|
item.pluginService = PluginService
|
||||||
console.log("PluginService assignment completed, item.pluginService:", item.pluginService !== null)
|
|
||||||
} else {
|
|
||||||
console.error("PluginService not available in PluginsTab context")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.loadTimezones) {
|
|
||||||
console.log("Calling loadTimezones for WorldClock plugin")
|
|
||||||
item.loadTimezones()
|
|
||||||
}
|
|
||||||
if (item.initializeSettings) {
|
|
||||||
item.initializeSettings()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -437,14 +426,19 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property bool isReloading: false
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: PluginService
|
target: PluginService
|
||||||
function onPluginLoaded() {
|
function onPluginLoaded() {
|
||||||
pluginRepeater.model = PluginService.getAvailablePlugins()
|
pluginRepeater.model = PluginService.getAvailablePlugins()
|
||||||
|
if (isReloading) {
|
||||||
|
isReloading = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function onPluginUnloaded() {
|
function onPluginUnloaded() {
|
||||||
pluginRepeater.model = PluginService.getAvailablePlugins()
|
pluginRepeater.model = PluginService.getAvailablePlugins()
|
||||||
if (pluginsTab.expandedPluginId !== "" && !PluginService.isPluginLoaded(pluginsTab.expandedPluginId)) {
|
if (!isReloading && pluginsTab.expandedPluginId !== "" && !PluginService.isPluginLoaded(pluginsTab.expandedPluginId)) {
|
||||||
pluginsTab.expandedPluginId = ""
|
pluginsTab.expandedPluginId = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -651,7 +651,6 @@ Item {
|
|||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
id: matugenPaletteDropdown
|
id: matugenPaletteDropdown
|
||||||
width: parent.width
|
|
||||||
text: "Matugen Palette"
|
text: "Matugen Palette"
|
||||||
description: "Select the palette algorithm used for wallpaper-based colors"
|
description: "Select the palette algorithm used for wallpaper-based colors"
|
||||||
options: Theme.availableMatugenSchemes.map(function (option) { return option.label })
|
options: Theme.availableMatugenSchemes.map(function (option) { return option.label })
|
||||||
@@ -993,7 +992,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width - Theme.iconSize - Theme.spacingXS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: "Icon Theme"
|
text: "Icon Theme"
|
||||||
description: "DankShell & System Icons\n(requires restart)"
|
description: "DankShell & System Icons\n(requires restart)"
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width
|
|
||||||
height: 50
|
height: 50
|
||||||
text: "Top Bar Format"
|
text: "Top Bar Format"
|
||||||
description: "Preview: " + (SettingsData.clockDateFormat ? new Date().toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) : new Date().toLocaleDateString(Qt.locale(), "ddd d"))
|
description: "Preview: " + (SettingsData.clockDateFormat ? new Date().toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) : new Date().toLocaleDateString(Qt.locale(), "ddd d"))
|
||||||
@@ -185,7 +184,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width
|
|
||||||
height: 50
|
height: 50
|
||||||
text: "Lock Screen Format"
|
text: "Lock Screen Format"
|
||||||
description: "Preview: " + (SettingsData.lockDateFormat ? new Date().toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat) : new Date().toLocaleDateString(Qt.locale(), Locale.LongFormat))
|
description: "Preview: " + (SettingsData.lockDateFormat ? new Date().toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat) : new Date().toLocaleDateString(Qt.locale(), Locale.LongFormat))
|
||||||
|
|||||||
@@ -142,13 +142,14 @@ Column {
|
|||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: 120
|
width: 60
|
||||||
height: 32
|
height: 32
|
||||||
visible: modelData.id === "gpuTemp"
|
visible: modelData.id === "gpuTemp"
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
id: gpuDropdown
|
id: gpuDropdown
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
popupWidth: -1
|
||||||
currentValue: {
|
currentValue: {
|
||||||
var selectedIndex = modelData.selectedGpuIndex
|
var selectedIndex = modelData.selectedGpuIndex
|
||||||
!== undefined ? modelData.selectedGpuIndex : 0
|
!== undefined ? modelData.selectedGpuIndex : 0
|
||||||
@@ -223,12 +224,7 @@ Column {
|
|||||||
Item {
|
Item {
|
||||||
width: 32
|
width: 32
|
||||||
height: 32
|
height: 32
|
||||||
visible: (modelData.warning !== undefined
|
visible: modelData.warning !== undefined && modelData.warning !== ""
|
||||||
&& modelData.warning !== "")
|
|
||||||
&& (modelData.id === "cpuUsage"
|
|
||||||
|| modelData.id === "memUsage"
|
|
||||||
|| modelData.id === "cpuTemp"
|
|
||||||
|| modelData.id === "gpuTemp")
|
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "warning"
|
name: "warning"
|
||||||
|
|||||||
97
PLUGINS/ExampleEmojiPlugin/EmojiSettings.qml
Normal file
97
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
PLUGINS/ExampleEmojiPlugin/EmojiWidget.qml
Normal file
149
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
PLUGINS/ExampleEmojiPlugin/README.md
Normal file
56
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!
|
||||||
14
PLUGINS/ExampleEmojiPlugin/plugin.json
Normal file
14
PLUGINS/ExampleEmojiPlugin/plugin.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"id": "exampleEmojiPlugin",
|
||||||
|
"name": "Emoji Cycler",
|
||||||
|
"description": "Display cycling emojis in your bar with a handy emoji picker popout",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "AvengeMedia",
|
||||||
|
"icon": "mood",
|
||||||
|
"component": "./EmojiWidget.qml",
|
||||||
|
"settings": "./EmojiSettings.qml",
|
||||||
|
"permissions": [
|
||||||
|
"settings_read",
|
||||||
|
"settings_write"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -57,18 +57,6 @@ The manifest file defines plugin metadata and configuration:
|
|||||||
"icon": "material_icon_name",
|
"icon": "material_icon_name",
|
||||||
"component": "./YourWidget.qml",
|
"component": "./YourWidget.qml",
|
||||||
"settings": "./YourSettings.qml",
|
"settings": "./YourSettings.qml",
|
||||||
"dependencies": {
|
|
||||||
"libraryName": {
|
|
||||||
"url": "https://cdn.example.com/library.js",
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"settings_schema": {
|
|
||||||
"settingKey": {
|
|
||||||
"type": "string|number|boolean|array|object",
|
|
||||||
"default": "defaultValue"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"settings_read",
|
"settings_read",
|
||||||
"settings_write"
|
"settings_write"
|
||||||
@@ -82,14 +70,20 @@ The manifest file defines plugin metadata and configuration:
|
|||||||
- `component`: Relative path to widget QML file
|
- `component`: Relative path to widget QML file
|
||||||
|
|
||||||
**Optional Fields:**
|
**Optional Fields:**
|
||||||
- `description`: Short description of plugin functionality
|
- `description`: Short description of plugin functionality (displayed in UI)
|
||||||
- `version`: Semantic version string
|
- `version`: Semantic version string (displayed in UI)
|
||||||
- `author`: Plugin creator name
|
- `author`: Plugin creator name (displayed in UI)
|
||||||
- `icon`: Material Design icon name
|
- `icon`: Material Design icon name (displayed in UI)
|
||||||
- `settings`: Path to settings component
|
- `settings`: Path to settings component (enables settings UI)
|
||||||
- `dependencies`: External JS libraries
|
- `permissions`: Required capabilities (enforced by PluginSettings component)
|
||||||
- `settings_schema`: Configuration schema
|
|
||||||
- `permissions`: Required capabilities
|
**Permissions:**
|
||||||
|
|
||||||
|
The plugin system enforces permissions when settings are accessed:
|
||||||
|
- `settings_read`: Required to read plugin settings (currently not enforced)
|
||||||
|
- `settings_write`: **Required** to use PluginSettings component and save settings
|
||||||
|
|
||||||
|
If your plugin includes a settings component but doesn't declare `settings_write` permission, users will see an error message instead of the settings UI.
|
||||||
|
|
||||||
### Widget Component
|
### Widget Component
|
||||||
|
|
||||||
@@ -127,10 +121,15 @@ PluginComponent {
|
|||||||
|
|
||||||
// Define popout content (optional)
|
// Define popout content (optional)
|
||||||
popoutContent: Component {
|
popoutContent: Component {
|
||||||
|
PopoutComponent {
|
||||||
|
headerText: "My Plugin"
|
||||||
|
detailsText: "Optional description text goes here"
|
||||||
|
showCloseButton: true
|
||||||
|
|
||||||
|
// Your popout content goes here
|
||||||
Column {
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
padding: Theme.spacingM
|
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: "Popout Content"
|
text: "Popout Content"
|
||||||
@@ -139,6 +138,7 @@ PluginComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Popout dimensions (required if popoutContent is set)
|
// Popout dimensions (required if popoutContent is set)
|
||||||
popoutWidth: 400
|
popoutWidth: 400
|
||||||
@@ -166,6 +166,42 @@ The PluginComponent automatically handles:
|
|||||||
- Proper positioning and anchoring
|
- Proper positioning and anchoring
|
||||||
- Theme integration
|
- Theme integration
|
||||||
|
|
||||||
|
### PopoutComponent
|
||||||
|
|
||||||
|
PopoutComponent provides a consistent header/content layout for plugin popouts:
|
||||||
|
|
||||||
|
```qml
|
||||||
|
import qs.Modules.Plugins
|
||||||
|
|
||||||
|
PopoutComponent {
|
||||||
|
headerText: "Header Title" // Main header text (bold, large)
|
||||||
|
detailsText: "Description text" // Optional description (smaller, gray)
|
||||||
|
showCloseButton: true // Show X button in top-right
|
||||||
|
|
||||||
|
// Access to exposed properties for dynamic sizing
|
||||||
|
readonly property int headerHeight // Height of header area
|
||||||
|
readonly property int detailsHeight // Height of description area
|
||||||
|
|
||||||
|
// Your content here - use parent.width for full width
|
||||||
|
// Calculate available height: root.popoutHeight - headerHeight - detailsHeight - spacing
|
||||||
|
DankGridView {
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**PopoutComponent Properties:**
|
||||||
|
- `headerText`: Main header text (optional, hidden if empty)
|
||||||
|
- `detailsText`: Description text below header (optional, hidden if empty)
|
||||||
|
- `showCloseButton`: Show close button in header (default: false)
|
||||||
|
- `closePopout`: Function to close popout (auto-injected by PluginPopout)
|
||||||
|
- `headerHeight`: Readonly height of header (0 if not visible)
|
||||||
|
- `detailsHeight`: Readonly height of description (0 if not visible)
|
||||||
|
|
||||||
|
The component automatically handles spacing and layout. Content children are rendered below the description with proper padding.
|
||||||
|
|
||||||
### Settings Component
|
### Settings Component
|
||||||
|
|
||||||
Optional settings UI loaded inline in the PluginsTab accordion interface. Use the simplified settings API with auto-storage components:
|
Optional settings UI loaded inline in the PluginsTab accordion interface. Use the simplified settings API with auto-storage components:
|
||||||
@@ -261,6 +297,27 @@ PluginSettings {
|
|||||||
|
|
||||||
All settings automatically save on change and load on component creation. No manual `pluginService.savePluginData()` calls needed!
|
All settings automatically save on change and load on component creation. No manual `pluginService.savePluginData()` calls needed!
|
||||||
|
|
||||||
|
**How Default Values Work:**
|
||||||
|
|
||||||
|
Each setting component has a `defaultValue` property that is used when no saved value exists. Define sensible defaults in your settings UI:
|
||||||
|
|
||||||
|
```qml
|
||||||
|
StringSetting {
|
||||||
|
settingKey: "apiKey"
|
||||||
|
defaultValue: "" // Empty string if no key saved
|
||||||
|
}
|
||||||
|
|
||||||
|
ToggleSetting {
|
||||||
|
settingKey: "enabled"
|
||||||
|
defaultValue: true // Enabled by default
|
||||||
|
}
|
||||||
|
|
||||||
|
ListSettingWithInput {
|
||||||
|
settingKey: "locations"
|
||||||
|
defaultValue: [] // Empty array if no locations saved
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
1. **PluginSettings** - Root wrapper for all plugin settings
|
1. **PluginSettings** - Root wrapper for all plugin settings
|
||||||
- `pluginId`: Your plugin ID (required)
|
- `pluginId`: Your plugin ID (required)
|
||||||
- Auto-handles storage and provides saveValue/loadValue to children
|
- Auto-handles storage and provides saveValue/loadValue to children
|
||||||
@@ -271,14 +328,14 @@ All settings automatically save on change and load on component creation. No man
|
|||||||
- `label`: Display label (required)
|
- `label`: Display label (required)
|
||||||
- `description`: Help text (optional)
|
- `description`: Help text (optional)
|
||||||
- `placeholder`: Input placeholder (optional)
|
- `placeholder`: Input placeholder (optional)
|
||||||
- `defaultValue`: Default value (optional)
|
- `defaultValue`: Default value (optional, default: `""`)
|
||||||
- Layout: Vertical stack (label, description, input field)
|
- Layout: Vertical stack (label, description, input field)
|
||||||
|
|
||||||
3. **ToggleSetting** - Boolean toggle switch
|
3. **ToggleSetting** - Boolean toggle switch
|
||||||
- `settingKey`: Storage key (required)
|
- `settingKey`: Storage key (required)
|
||||||
- `label`: Display label (required)
|
- `label`: Display label (required)
|
||||||
- `description`: Help text (optional)
|
- `description`: Help text (optional)
|
||||||
- `defaultValue`: Default boolean (optional)
|
- `defaultValue`: Default boolean (optional, default: `false`)
|
||||||
- Layout: Horizontal (label/description left, toggle right)
|
- Layout: Horizontal (label/description left, toggle right)
|
||||||
|
|
||||||
4. **SelectionSetting** - Dropdown menu
|
4. **SelectionSetting** - Dropdown menu
|
||||||
@@ -286,7 +343,7 @@ All settings automatically save on change and load on component creation. No man
|
|||||||
- `label`: Display label (required)
|
- `label`: Display label (required)
|
||||||
- `description`: Help text (optional)
|
- `description`: Help text (optional)
|
||||||
- `options`: Array of `{label, value}` objects or simple strings (required)
|
- `options`: Array of `{label, value}` objects or simple strings (required)
|
||||||
- `defaultValue`: Default value (optional)
|
- `defaultValue`: Default value (optional, default: `""`)
|
||||||
- Layout: Horizontal (label/description left, dropdown right)
|
- Layout: Horizontal (label/description left, dropdown right)
|
||||||
- Stores the `value` field, displays the `label` field
|
- Stores the `value` field, displays the `label` field
|
||||||
|
|
||||||
@@ -294,6 +351,7 @@ All settings automatically save on change and load on component creation. No man
|
|||||||
- `settingKey`: Storage key (required)
|
- `settingKey`: Storage key (required)
|
||||||
- `label`: Display label (required)
|
- `label`: Display label (required)
|
||||||
- `description`: Help text (optional)
|
- `description`: Help text (optional)
|
||||||
|
- `defaultValue`: Default array (optional, default: `[]`)
|
||||||
- `delegate`: Custom item delegate Component (optional)
|
- `delegate`: Custom item delegate Component (optional)
|
||||||
- `addItem(item)`: Add item to list
|
- `addItem(item)`: Add item to list
|
||||||
- `removeItem(index)`: Remove item from list
|
- `removeItem(index)`: Remove item from list
|
||||||
@@ -303,6 +361,7 @@ All settings automatically save on change and load on component creation. No man
|
|||||||
- `settingKey`: Storage key (required)
|
- `settingKey`: Storage key (required)
|
||||||
- `label`: Display label (required)
|
- `label`: Display label (required)
|
||||||
- `description`: Help text (optional)
|
- `description`: Help text (optional)
|
||||||
|
- `defaultValue`: Default array (optional, default: `[]`)
|
||||||
- `fields`: Array of field definitions (required)
|
- `fields`: Array of field definitions (required)
|
||||||
- `id`: Field ID in saved object (required)
|
- `id`: Field ID in saved object (required)
|
||||||
- `label`: Column header text (required)
|
- `label`: Column header text (required)
|
||||||
@@ -329,7 +388,6 @@ import qs.Modules.Plugins
|
|||||||
PluginSettings {
|
PluginSettings {
|
||||||
pluginId: "myPlugin"
|
pluginId: "myPlugin"
|
||||||
|
|
||||||
// Section header (optional)
|
|
||||||
StyledText {
|
StyledText {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: "General Settings"
|
text: "General Settings"
|
||||||
@@ -338,7 +396,6 @@ PluginSettings {
|
|||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text input
|
|
||||||
StringSetting {
|
StringSetting {
|
||||||
settingKey: "apiKey"
|
settingKey: "apiKey"
|
||||||
label: "API Key"
|
label: "API Key"
|
||||||
@@ -347,7 +404,6 @@ PluginSettings {
|
|||||||
defaultValue: ""
|
defaultValue: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle switches
|
|
||||||
ToggleSetting {
|
ToggleSetting {
|
||||||
settingKey: "enabled"
|
settingKey: "enabled"
|
||||||
label: "Enable Feature"
|
label: "Enable Feature"
|
||||||
@@ -355,7 +411,6 @@ PluginSettings {
|
|||||||
defaultValue: true
|
defaultValue: true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dropdown selection
|
|
||||||
SelectionSetting {
|
SelectionSetting {
|
||||||
settingKey: "theme"
|
settingKey: "theme"
|
||||||
label: "Theme"
|
label: "Theme"
|
||||||
@@ -368,11 +423,11 @@ PluginSettings {
|
|||||||
defaultValue: "dark"
|
defaultValue: "dark"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Structured list with multi-field input
|
|
||||||
ListSettingWithInput {
|
ListSettingWithInput {
|
||||||
settingKey: "locations"
|
settingKey: "locations"
|
||||||
label: "Locations"
|
label: "Locations"
|
||||||
description: "Track multiple locations"
|
description: "Track multiple locations"
|
||||||
|
defaultValue: []
|
||||||
fields: [
|
fields: [
|
||||||
{id: "name", label: "Name", placeholder: "Home", width: 150, required: true},
|
{id: "name", label: "Name", placeholder: "Home", width: 150, required: true},
|
||||||
{id: "timezone", label: "Timezone", placeholder: "America/New_York", width: 200, required: true}
|
{id: "timezone", label: "Timezone", placeholder: "America/New_York", width: 200, required: true}
|
||||||
@@ -636,12 +691,12 @@ Look for lines prefixed with:
|
|||||||
Plugins run with full QML runtime access. Only install plugins from trusted sources.
|
Plugins run with full QML runtime access. Only install plugins from trusted sources.
|
||||||
|
|
||||||
**Permissions System:**
|
**Permissions System:**
|
||||||
- `settings_read`: Read plugin configuration
|
- `settings_read`: Read plugin configuration (not currently enforced)
|
||||||
- `settings_write`: Write plugin configuration
|
- `settings_write`: **Required** to use PluginSettings - write plugin configuration (enforced)
|
||||||
- `process`: Execute system commands
|
- `process`: Execute system commands (not currently enforced)
|
||||||
- `network`: Network access
|
- `network`: Network access (not currently enforced)
|
||||||
|
|
||||||
Future versions may enforce permission restrictions.
|
Currently, only `settings_write` is enforced by the PluginSettings component.
|
||||||
|
|
||||||
## API Stability
|
## API Stability
|
||||||
|
|
||||||
25
README.md
25
README.md
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
A modern Wayland desktop shell built with [Quickshell](https://quickshell.org/) and designed for the [niri](https://github.com/YaLTeR/niri) and [Hyprland](https://hyprland.org/) compositors. Features Material 3 design principles with a heavy focus on functionality and customizability.
|
A modern Wayland desktop shell built with [Quickshell](https://quickshell.org/) and optimized for the [niri](https://github.com/YaLTeR/niri) and [Hyprland](https://hyprland.org/) compositors.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
@@ -122,6 +122,8 @@ curl -fsSL https://install.danklinux.com | sh
|
|||||||
- Configure bluetooth, wifi, and audio input+output devices.
|
- Configure bluetooth, wifi, and audio input+output devices.
|
||||||
- A lock screen
|
- A lock screen
|
||||||
- Idle monitoring - configure auto lock, screen off, suspend, and hibernate with different knobs for battery + AC power.
|
- Idle monitoring - configure auto lock, screen off, suspend, and hibernate with different knobs for battery + AC power.
|
||||||
|
- A greeter
|
||||||
|
- A comprehensive plugin system for endless customization possibilities.
|
||||||
|
|
||||||
**TL;DR** *dms replaces your waybar, swaylock, swayidle, hypridle, hyprlock, fuzzels, walker, mako, and basically everything you use to stitch a desktop together*
|
**TL;DR** *dms replaces your waybar, swaylock, swayidle, hypridle, hyprlock, fuzzels, walker, mako, and basically everything you use to stitch a desktop together*
|
||||||
|
|
||||||
@@ -313,7 +315,7 @@ sudo sh -c "curl -L https://github.com/AvengeMedia/dgop/releases/latest/download
|
|||||||
|
|
||||||
A lot of options are subject to personal preference, but the below sets a good starting point for most features.
|
A lot of options are subject to personal preference, but the below sets a good starting point for most features.
|
||||||
|
|
||||||
### Niri Integration
|
### niri Integration
|
||||||
|
|
||||||
Add to your niri config
|
Add to your niri config
|
||||||
|
|
||||||
@@ -393,6 +395,17 @@ binds {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### niri theming
|
||||||
|
|
||||||
|
If using a niri build newer than [3933903](https://github.com/YaLTeR/niri/commit/39339032cee3453faa54c361a38db6d83756f750), you can synchronize colors and gaps with the shell settings by adding the following to your niri config.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# For colors
|
||||||
|
echo -e 'include "dms/colors.kdl"' >> ~/.config/niri/config.kdl
|
||||||
|
# For gaps, border widths, certain window rules
|
||||||
|
echo -e 'include "dms/layout.kdl"' >> ~/.config/niri/config.kdl
|
||||||
|
```
|
||||||
|
|
||||||
### Hyprland Integration
|
### Hyprland Integration
|
||||||
|
|
||||||
Add to your Hyprland config (`~/.config/hypr/hyprland.conf`):
|
Add to your Hyprland config (`~/.config/hypr/hyprland.conf`):
|
||||||
@@ -629,6 +642,14 @@ echo "app-notifications = no-clipboard-copy,no-config-reload" >> ~/.config/ghost
|
|||||||
echo "include dank-theme.conf" >> ~/.config/kitty/kitty.conf
|
echo "include dank-theme.conf" >> ~/.config/kitty/kitty.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
dms features a plugin system - meaning you can create your own widgets and load other user widgets.
|
||||||
|
|
||||||
|
More comprehensive details available in the [PLUGINS](PLUGINS/README.md) - and example [Emoji Plugin](PLUGINS/ExampleEmojiPlugin) is available for reference.
|
||||||
|
|
||||||
|
The example plugin can be installed by `cp -R ./PLUGINS/ExampleEmojiPlugin ~/.config/DankMaterialShell/plugins` - then it will appear in dms settings.
|
||||||
|
|
||||||
### Calendar Setup
|
### Calendar Setup
|
||||||
|
|
||||||
Sync your caldev compatible calendar (Google, Office365, etc.) for dashboard integration:
|
Sync your caldev compatible calendar (Google, Office365, etc.) for dashboard integration:
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ Singleton {
|
|||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
detectCompositor()
|
detectCompositor()
|
||||||
|
NiriService.generateNiriLayoutConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterCurrentWorkspace(toplevels, screen) {
|
function filterCurrentWorkspace(toplevels, screen) {
|
||||||
@@ -192,6 +193,7 @@ Singleton {
|
|||||||
root.isHyprland = false
|
root.isHyprland = false
|
||||||
root.compositor = "niri"
|
root.compositor = "niri"
|
||||||
console.log("CompositorService: Detected Niri with socket:", root.niriSocket)
|
console.log("CompositorService: Detected Niri with socket:", root.niriSocket)
|
||||||
|
NiriService.generateNiriBinds()
|
||||||
} else {
|
} else {
|
||||||
root.isHyprland = false
|
root.isHyprland = false
|
||||||
root.isNiri = true
|
root.isNiri = true
|
||||||
|
|||||||
@@ -708,7 +708,6 @@ Singleton {
|
|||||||
|
|
||||||
onExited: function (exitCode) {
|
onExited: function (exitCode) {
|
||||||
geoclueAvailable = (exitCode === 0)
|
geoclueAvailable = (exitCode === 0)
|
||||||
console.log("DisplayService: geoclue available:", geoclueAvailable)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ pragma Singleton
|
|||||||
|
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtCore
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -31,6 +33,7 @@ Singleton {
|
|||||||
property bool suppressConfigToast: true
|
property bool suppressConfigToast: true
|
||||||
property bool suppressNextConfigToast: false
|
property bool suppressNextConfigToast: false
|
||||||
property bool matugenSuppression: false
|
property bool matugenSuppression: false
|
||||||
|
property bool configGenerationPending: false
|
||||||
|
|
||||||
readonly property string socketPath: Quickshell.env("NIRI_SOCKET")
|
readonly property string socketPath: Quickshell.env("NIRI_SOCKET")
|
||||||
|
|
||||||
@@ -412,7 +415,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function doScreenTransition() {
|
function doScreenTransition() {
|
||||||
send({
|
return send({
|
||||||
"Action": {
|
"Action": {
|
||||||
"DoScreenTransition": {
|
"DoScreenTransition": {
|
||||||
"delay_ms": 0,
|
"delay_ms": 0,
|
||||||
@@ -652,6 +655,7 @@ Singleton {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: suppressToastTimer
|
id: suppressToastTimer
|
||||||
interval: 3000
|
interval: 3000
|
||||||
@@ -663,4 +667,101 @@ Singleton {
|
|||||||
interval: 2000
|
interval: 2000
|
||||||
onTriggered: root.matugenSuppression = false
|
onTriggered: root.matugenSuppression = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: configGenerationDebounce
|
||||||
|
interval: 100
|
||||||
|
onTriggered: root.doGenerateNiriLayoutConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateNiriLayoutConfig() {
|
||||||
|
const niriSocket = Quickshell.env("NIRI_SOCKET")
|
||||||
|
if (!niriSocket || niriSocket.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configGenerationPending) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configGenerationPending = true
|
||||||
|
configGenerationDebounce.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
function doGenerateNiriLayoutConfig() {
|
||||||
|
console.log("NiriService: Generating layout config...")
|
||||||
|
|
||||||
|
const cornerRadius = typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12
|
||||||
|
const gaps = typeof SettingsData !== "undefined" ? Math.max(4, SettingsData.dankBarSpacing) : 4
|
||||||
|
|
||||||
|
const configContent = `layout {
|
||||||
|
gaps ${gaps}
|
||||||
|
|
||||||
|
border {
|
||||||
|
width 2
|
||||||
|
}
|
||||||
|
|
||||||
|
focus-ring {
|
||||||
|
width 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window-rule {
|
||||||
|
geometry-corner-radius ${cornerRadius}
|
||||||
|
clip-to-geometry true
|
||||||
|
tiled-state true
|
||||||
|
draw-border-with-background false
|
||||||
|
}`
|
||||||
|
|
||||||
|
const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
|
||||||
|
const niriDmsDir = configDir + "/niri/dms"
|
||||||
|
const configPath = niriDmsDir + "/layout.kdl"
|
||||||
|
|
||||||
|
writeConfigProcess.configContent = configContent
|
||||||
|
writeConfigProcess.configPath = configPath
|
||||||
|
writeConfigProcess.command = ["sh", "-c", `mkdir -p "${niriDmsDir}" && cat > "${configPath}" << 'EOF'\n${configContent}\nEOF`]
|
||||||
|
writeConfigProcess.running = true
|
||||||
|
configGenerationPending = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateNiriBinds() {
|
||||||
|
console.log("NiriService: Generating binds config...")
|
||||||
|
|
||||||
|
const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
|
||||||
|
const niriDmsDir = configDir + "/niri/dms"
|
||||||
|
const bindsPath = niriDmsDir + "/binds.kdl"
|
||||||
|
const sourceBindsPath = Paths.strip(Qt.resolvedUrl("niri-binds.kdl"))
|
||||||
|
|
||||||
|
writeBindsProcess.bindsPath = bindsPath
|
||||||
|
writeBindsProcess.command = ["sh", "-c", `mkdir -p "${niriDmsDir}" && cp "${sourceBindsPath}" "${bindsPath}"`]
|
||||||
|
writeBindsProcess.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: writeConfigProcess
|
||||||
|
property string configContent: ""
|
||||||
|
property string configPath: ""
|
||||||
|
|
||||||
|
onExited: exitCode => {
|
||||||
|
if (exitCode === 0) {
|
||||||
|
console.log("NiriService: Generated layout config at", configPath)
|
||||||
|
} else {
|
||||||
|
console.warn("NiriService: Failed to write layout config, exit code:", exitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: writeBindsProcess
|
||||||
|
property string bindsPath: ""
|
||||||
|
|
||||||
|
onExited: exitCode => {
|
||||||
|
if (exitCode === 0) {
|
||||||
|
console.log("NiriService: Generated binds config at", bindsPath)
|
||||||
|
} else {
|
||||||
|
console.warn("NiriService: Failed to write binds config, exit code:", exitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,7 @@ Singleton {
|
|||||||
signal pluginLoaded(string pluginId)
|
signal pluginLoaded(string pluginId)
|
||||||
signal pluginUnloaded(string pluginId)
|
signal pluginUnloaded(string pluginId)
|
||||||
signal pluginLoadFailed(string pluginId, string error)
|
signal pluginLoadFailed(string pluginId, string error)
|
||||||
|
signal pluginDataChanged(string pluginId)
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
Qt.callLater(initializePlugins)
|
Qt.callLater(initializePlugins)
|
||||||
@@ -53,20 +54,14 @@ Singleton {
|
|||||||
var dir = directories[i].trim()
|
var dir = directories[i].trim()
|
||||||
if (dir) {
|
if (dir) {
|
||||||
var manifestPath = currentDir + "/" + dir + "/plugin.json"
|
var manifestPath = currentDir + "/" + dir + "/plugin.json"
|
||||||
console.log("PluginService: Found plugin directory:", dir, "checking manifest at:", manifestPath)
|
|
||||||
loadPluginManifest(manifestPath)
|
loadPluginManifest(manifestPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
console.log("PluginService: No directories found in:", currentDir)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onExited: function(exitCode) {
|
onExited: function(exitCode) {
|
||||||
if (exitCode !== 0) {
|
|
||||||
console.log("PluginService: Directory scan failed for:", pluginDirectories[currentScanIndex], "exit code:", exitCode)
|
|
||||||
}
|
|
||||||
currentScanIndex++
|
currentScanIndex++
|
||||||
if (currentScanIndex < pluginDirectories.length) {
|
if (currentScanIndex < pluginDirectories.length) {
|
||||||
scanNextDirectory()
|
scanNextDirectory()
|
||||||
@@ -83,7 +78,6 @@ Singleton {
|
|||||||
|
|
||||||
function scanNextDirectory() {
|
function scanNextDirectory() {
|
||||||
var dir = pluginDirectories[currentScanIndex]
|
var dir = pluginDirectories[currentScanIndex]
|
||||||
console.log("PluginService: Scanning directory:", dir)
|
|
||||||
lsProcess.command = ["find", "-L", dir, "-maxdepth", "1", "-type", "d", "-not", "-path", dir, "-exec", "basename", "{}", ";"]
|
lsProcess.command = ["find", "-L", dir, "-maxdepth", "1", "-type", "d", "-not", "-path", dir, "-exec", "basename", "{}", ";"]
|
||||||
lsProcess.running = true
|
lsProcess.running = true
|
||||||
}
|
}
|
||||||
@@ -91,9 +85,6 @@ Singleton {
|
|||||||
property var manifestReaders: ({})
|
property var manifestReaders: ({})
|
||||||
|
|
||||||
function loadPluginManifest(manifestPath) {
|
function loadPluginManifest(manifestPath) {
|
||||||
console.log("PluginService: Loading manifest:", manifestPath)
|
|
||||||
|
|
||||||
// Create a unique key for this manifest reader
|
|
||||||
var readerId = "reader_" + Date.now() + "_" + Math.random()
|
var readerId = "reader_" + Date.now() + "_" + Math.random()
|
||||||
|
|
||||||
var catProcess = Qt.createComponent("data:text/plain,import Quickshell.Io; Process { stdout: StdioCollector { } }")
|
var catProcess = Qt.createComponent("data:text/plain,import Quickshell.Io; Process { stdout: StdioCollector { } }")
|
||||||
@@ -102,9 +93,7 @@ Singleton {
|
|||||||
process.command = ["cat", manifestPath]
|
process.command = ["cat", manifestPath]
|
||||||
process.stdout.streamFinished.connect(function() {
|
process.stdout.streamFinished.connect(function() {
|
||||||
try {
|
try {
|
||||||
console.log("PluginService: DEBUGGING parsing manifest, text length:", process.stdout.text.length)
|
|
||||||
var manifest = JSON.parse(process.stdout.text.trim())
|
var manifest = JSON.parse(process.stdout.text.trim())
|
||||||
console.log("PluginService: Successfully parsed manifest for plugin:", manifest.id)
|
|
||||||
processManifest(manifest, manifestPath)
|
processManifest(manifest, manifestPath)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("PluginService: Failed to parse manifest", manifestPath, ":", e.message)
|
console.error("PluginService: Failed to parse manifest", manifestPath, ":", e.message)
|
||||||
@@ -137,7 +126,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function registerPlugin(manifest, manifestPath) {
|
function registerPlugin(manifest, manifestPath) {
|
||||||
console.log("PluginService: registerPlugin called with", manifest.id)
|
|
||||||
if (!manifest.id || !manifest.name || !manifest.component) {
|
if (!manifest.id || !manifest.name || !manifest.component) {
|
||||||
console.error("PluginService: Invalid manifest, missing required fields:", manifestPath)
|
console.error("PluginService: Invalid manifest, missing required fields:", manifestPath)
|
||||||
return
|
return
|
||||||
@@ -167,12 +155,18 @@ Singleton {
|
|||||||
pluginInfo.loaded = false
|
pluginInfo.loaded = false
|
||||||
|
|
||||||
availablePlugins[manifest.id] = pluginInfo
|
availablePlugins[manifest.id] = pluginInfo
|
||||||
console.log("PluginService: Registered plugin:", manifest.id, "-", manifest.name)
|
}
|
||||||
console.log("PluginService: Component path:", pluginInfo.componentPath)
|
|
||||||
|
function hasPermission(pluginId, permission) {
|
||||||
|
var plugin = availablePlugins[pluginId]
|
||||||
|
if (!plugin) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var permissions = plugin.permissions || []
|
||||||
|
return permissions.indexOf(permission) !== -1
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPlugin(pluginId) {
|
function loadPlugin(pluginId) {
|
||||||
console.log("PluginService: loadPlugin called for", pluginId)
|
|
||||||
var plugin = availablePlugins[pluginId]
|
var plugin = availablePlugins[pluginId]
|
||||||
if (!plugin) {
|
if (!plugin) {
|
||||||
console.error("PluginService: Plugin not found:", pluginId)
|
console.error("PluginService: Plugin not found:", pluginId)
|
||||||
@@ -181,27 +175,45 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (plugin.loaded) {
|
if (plugin.loaded) {
|
||||||
console.log("PluginService: Plugin already loaded:", pluginId)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (pluginWidgetComponents[pluginId]) {
|
||||||
// Create the widget component
|
var oldComponent = pluginWidgetComponents[pluginId]
|
||||||
var componentUrl = "file://" + plugin.componentPath
|
if (oldComponent) {
|
||||||
console.log("PluginService: Loading component from:", componentUrl)
|
oldComponent.destroy()
|
||||||
|
}
|
||||||
|
delete pluginWidgetComponents[pluginId]
|
||||||
|
}
|
||||||
|
|
||||||
var component = Qt.createComponent(componentUrl)
|
try {
|
||||||
|
var componentUrl = "file://" + plugin.componentPath
|
||||||
|
var component = Qt.createComponent(componentUrl, Component.PreferSynchronous)
|
||||||
|
|
||||||
|
if (component.status === Component.Loading) {
|
||||||
|
component.statusChanged.connect(function() {
|
||||||
if (component.status === Component.Error) {
|
if (component.status === Component.Error) {
|
||||||
console.error("PluginService: Failed to create component for plugin:", pluginId, "Error:", component.errorString())
|
console.error("PluginService: Failed to create component for plugin:", pluginId, "Error:", component.errorString())
|
||||||
pluginLoadFailed(pluginId, component.errorString())
|
pluginLoadFailed(pluginId, component.errorString())
|
||||||
|
component.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.status === Component.Error) {
|
||||||
|
console.error("PluginService: Failed to create component for plugin:", pluginId, "Error:", component.errorString())
|
||||||
|
pluginLoadFailed(pluginId, component.errorString())
|
||||||
|
component.destroy()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginWidgetComponents[pluginId] = component
|
var newComponents = Object.assign({}, pluginWidgetComponents)
|
||||||
|
newComponents[pluginId] = component
|
||||||
|
pluginWidgetComponents = newComponents
|
||||||
|
|
||||||
plugin.loaded = true
|
plugin.loaded = true
|
||||||
loadedPlugins[pluginId] = plugin
|
loadedPlugins[pluginId] = plugin
|
||||||
|
|
||||||
console.log("PluginService: Successfully loaded plugin:", pluginId)
|
|
||||||
pluginLoaded(pluginId)
|
pluginLoaded(pluginId)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -220,14 +232,19 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Remove from component map
|
if (pluginWidgetComponents[pluginId]) {
|
||||||
delete pluginWidgetComponents[pluginId]
|
var component = pluginWidgetComponents[pluginId]
|
||||||
|
if (component) {
|
||||||
|
component.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var newComponents = Object.assign({}, pluginWidgetComponents)
|
||||||
|
delete newComponents[pluginId]
|
||||||
|
pluginWidgetComponents = newComponents
|
||||||
|
|
||||||
// Mark as unloaded
|
|
||||||
plugin.loaded = false
|
plugin.loaded = false
|
||||||
delete loadedPlugins[pluginId]
|
delete loadedPlugins[pluginId]
|
||||||
|
|
||||||
console.log("PluginService: Successfully unloaded plugin:", pluginId)
|
|
||||||
pluginUnloaded(pluginId)
|
pluginUnloaded(pluginId)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -262,13 +279,11 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function enablePlugin(pluginId) {
|
function enablePlugin(pluginId) {
|
||||||
console.log("PluginService: Enabling plugin:", pluginId)
|
|
||||||
SettingsData.setPluginSetting(pluginId, "enabled", true)
|
SettingsData.setPluginSetting(pluginId, "enabled", true)
|
||||||
return loadPlugin(pluginId)
|
return loadPlugin(pluginId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function disablePlugin(pluginId) {
|
function disablePlugin(pluginId) {
|
||||||
console.log("PluginService: Disabling plugin:", pluginId)
|
|
||||||
SettingsData.setPluginSetting(pluginId, "enabled", false)
|
SettingsData.setPluginSetting(pluginId, "enabled", false)
|
||||||
return unloadPlugin(pluginId)
|
return unloadPlugin(pluginId)
|
||||||
}
|
}
|
||||||
@@ -281,29 +296,22 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function savePluginData(pluginId, key, value) {
|
function savePluginData(pluginId, key, value) {
|
||||||
console.log("PluginService: Saving plugin data:", pluginId, key, JSON.stringify(value))
|
|
||||||
SettingsData.setPluginSetting(pluginId, key, value)
|
SettingsData.setPluginSetting(pluginId, key, value)
|
||||||
console.log("PluginService: Data saved successfully")
|
pluginDataChanged(pluginId)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPluginData(pluginId, key, defaultValue) {
|
function loadPluginData(pluginId, key, defaultValue) {
|
||||||
console.log("PluginService: Loading plugin data:", pluginId, key)
|
return SettingsData.getPluginSetting(pluginId, key, defaultValue)
|
||||||
var value = SettingsData.getPluginSetting(pluginId, key, defaultValue)
|
|
||||||
console.log("PluginService: Loaded key:", key, "value:", JSON.stringify(value))
|
|
||||||
return value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPluginDirectory() {
|
function createPluginDirectory() {
|
||||||
console.log("PluginService: Creating plugin directory:", pluginDirectory)
|
|
||||||
var mkdirProcess = Qt.createComponent("data:text/plain,import Quickshell.Io; Process { }")
|
var mkdirProcess = Qt.createComponent("data:text/plain,import Quickshell.Io; Process { }")
|
||||||
if (mkdirProcess.status === Component.Ready) {
|
if (mkdirProcess.status === Component.Ready) {
|
||||||
var process = mkdirProcess.createObject(root)
|
var process = mkdirProcess.createObject(root)
|
||||||
process.command = ["mkdir", "-p", pluginDirectory]
|
process.command = ["mkdir", "-p", pluginDirectory]
|
||||||
process.exited.connect(function(exitCode) {
|
process.exited.connect(function(exitCode) {
|
||||||
if (exitCode === 0) {
|
if (exitCode !== 0) {
|
||||||
console.log("PluginService: Successfully created plugin directory")
|
|
||||||
} else {
|
|
||||||
console.error("PluginService: Failed to create plugin directory, exit code:", exitCode)
|
console.error("PluginService: Failed to create plugin directory, exit code:", exitCode)
|
||||||
}
|
}
|
||||||
process.destroy()
|
process.destroy()
|
||||||
|
|||||||
55
Services/niri-binds.kdl
Normal file
55
Services/niri-binds.kdl
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
binds {
|
||||||
|
Mod+Space hotkey-overlay-title="Application Launcher" {
|
||||||
|
spawn "dms" "ipc" "call" "spotlight" "toggle";
|
||||||
|
}
|
||||||
|
|
||||||
|
Mod+V hotkey-overlay-title="Clipboard Manager" {
|
||||||
|
spawn "dms" "ipc" "call" "clipboard" "toggle";
|
||||||
|
}
|
||||||
|
|
||||||
|
Mod+M hotkey-overlay-title="Task Manager" {
|
||||||
|
spawn "dms" "ipc" "call" "processlist" "toggle";
|
||||||
|
}
|
||||||
|
|
||||||
|
Mod+Comma hotkey-overlay-title="Settings" {
|
||||||
|
spawn "dms" "ipc" "call" "settings" "toggle";
|
||||||
|
}
|
||||||
|
|
||||||
|
Mod+N hotkey-overlay-title="Notification Center" {
|
||||||
|
spawn "dms" "ipc" "call" "notifications" "toggle";
|
||||||
|
}
|
||||||
|
|
||||||
|
Mod+Shift+N hotkey-overlay-title="Notepad" {
|
||||||
|
spawn "dms" "ipc" "call" "notepad" "toggle";
|
||||||
|
}
|
||||||
|
|
||||||
|
Mod+Alt+L hotkey-overlay-title="Lock Screen" {
|
||||||
|
spawn "dms" "ipc" "call" "lock" "lock";
|
||||||
|
}
|
||||||
|
|
||||||
|
Ctrl+Alt+Delete hotkey-overlay-title="Task Manager" {
|
||||||
|
spawn "dms" "ipc" "call" "processlist" "toggle";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
XF86AudioRaiseVolume allow-when-locked=true {
|
||||||
|
spawn "dms" "ipc" "call" "audio" "increment" "3";
|
||||||
|
}
|
||||||
|
XF86AudioLowerVolume allow-when-locked=true {
|
||||||
|
spawn "dms" "ipc" "call" "audio" "decrement" "3";
|
||||||
|
}
|
||||||
|
XF86AudioMute allow-when-locked=true {
|
||||||
|
spawn "dms" "ipc" "call" "audio" "mute";
|
||||||
|
}
|
||||||
|
XF86AudioMicMute allow-when-locked=true {
|
||||||
|
spawn "dms" "ipc" "call" "audio" "micmute";
|
||||||
|
}
|
||||||
|
|
||||||
|
// BL
|
||||||
|
XF86MonBrightnessUp allow-when-locked=true {
|
||||||
|
spawn "dms" "ipc" "call" "brightness" "increment" "5" "";
|
||||||
|
}
|
||||||
|
XF86MonBrightnessDown allow-when-locked=true {
|
||||||
|
spawn "dms" "ipc" "call" "brightness" "decrement" "5" "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import "../Common/fzf.js" as Fzf
|
import "../Common/fzf.js" as Fzf
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Effects
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string text: ""
|
property string text: ""
|
||||||
@@ -12,50 +13,33 @@ Rectangle {
|
|||||||
property string currentValue: ""
|
property string currentValue: ""
|
||||||
property var options: []
|
property var options: []
|
||||||
property var optionIcons: []
|
property var optionIcons: []
|
||||||
property bool forceRecreate: false
|
|
||||||
property bool enableFuzzySearch: false
|
property bool enableFuzzySearch: false
|
||||||
property int popupWidthOffset: 0
|
property int popupWidthOffset: 0
|
||||||
property int maxPopupHeight: 400
|
property int maxPopupHeight: 400
|
||||||
property bool openUpwards: false
|
property bool openUpwards: false
|
||||||
property int popupWidth: 0
|
property int popupWidth: 0
|
||||||
property bool alignPopupRight: false
|
property bool alignPopupRight: false
|
||||||
|
property int dropdownWidth: 200
|
||||||
|
|
||||||
signal valueChanged(string value)
|
signal valueChanged(string value)
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 60
|
implicitHeight: Math.max(60, labelColumn.implicitHeight + Theme.spacingM)
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: "transparent"
|
|
||||||
Component.onCompleted: forceRecreateTimer.start()
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
const popup = popupLoader.item
|
const popup = dropdownMenu
|
||||||
if (popup && popup.visible) {
|
if (popup && popup.visible) {
|
||||||
popup.close()
|
popup.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onVisibleChanged: {
|
|
||||||
const popup = popupLoader.item
|
|
||||||
if (!visible && popup && popup.visible) {
|
|
||||||
popup.close()
|
|
||||||
} else if (visible) {
|
|
||||||
forceRecreateTimer.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: forceRecreateTimer
|
|
||||||
|
|
||||||
interval: 50
|
|
||||||
repeat: false
|
|
||||||
onTriggered: root.forceRecreate = !root.forceRecreate
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
id: labelColumn
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: dropdown.left
|
anchors.right: dropdown.left
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.leftMargin: Theme.spacingM
|
anchors.rightMargin: Theme.spacingL
|
||||||
anchors.rightMargin: Theme.spacingM
|
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
@@ -78,15 +62,14 @@ Rectangle {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: dropdown
|
id: dropdown
|
||||||
|
|
||||||
width: root.width <= 60 ? root.width : 180
|
width: root.popupWidth === -1 ? undefined : (root.popupWidth > 0 ? root.popupWidth : root.dropdownWidth)
|
||||||
height: 36
|
height: 40
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: Theme.spacingM
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: dropdownArea.containsMouse ? Theme.primaryHover : Theme.contentBackground()
|
color: dropdownArea.containsMouse || dropdownMenu.visible ? Theme.surfaceContainerHigh : Theme.surfaceContainer
|
||||||
border.color: Theme.surfaceVariantAlpha
|
border.color: dropdownMenu.visible ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
border.width: 1
|
border.width: dropdownMenu.visible ? 2 : 1
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: dropdownArea
|
id: dropdownArea
|
||||||
@@ -95,42 +78,39 @@ Rectangle {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const popup = popupLoader.item
|
if (dropdownMenu.visible) {
|
||||||
if (!popup) {
|
dropdownMenu.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (popup.visible) {
|
dropdownMenu.searchQuery = ""
|
||||||
popup.close()
|
dropdownMenu.updateFilteredOptions()
|
||||||
return
|
|
||||||
}
|
dropdownMenu.open()
|
||||||
|
|
||||||
if (root.openUpwards || root.alignPopupRight) {
|
|
||||||
popup.open()
|
|
||||||
Qt.callLater(() => {
|
|
||||||
if (root.openUpwards) {
|
|
||||||
const pos = dropdown.mapToItem(Overlay.overlay, 0, 0)
|
const pos = dropdown.mapToItem(Overlay.overlay, 0, 0)
|
||||||
|
const popupWidth = dropdownMenu.width
|
||||||
|
const popupHeight = dropdownMenu.height
|
||||||
|
const overlayHeight = Overlay.overlay.height
|
||||||
|
|
||||||
|
if (root.openUpwards || pos.y + dropdown.height + popupHeight + 4 > overlayHeight) {
|
||||||
if (root.alignPopupRight) {
|
if (root.alignPopupRight) {
|
||||||
popup.x = pos.x + dropdown.width - popup.width
|
dropdownMenu.x = pos.x + dropdown.width - popupWidth
|
||||||
} else {
|
} else {
|
||||||
popup.x = pos.x - (root.popupWidthOffset / 2)
|
dropdownMenu.x = pos.x - (root.popupWidthOffset / 2)
|
||||||
}
|
}
|
||||||
popup.y = pos.y - popup.height - 4
|
dropdownMenu.y = pos.y - popupHeight - 4
|
||||||
} else {
|
} else {
|
||||||
const pos = dropdown.mapToItem(Overlay.overlay, 0, dropdown.height + 4)
|
|
||||||
if (root.alignPopupRight) {
|
if (root.alignPopupRight) {
|
||||||
popup.x = pos.x + dropdown.width - popup.width
|
dropdownMenu.x = pos.x + dropdown.width - popupWidth
|
||||||
} else {
|
} else {
|
||||||
popup.x = pos.x - (root.popupWidthOffset / 2)
|
dropdownMenu.x = pos.x - (root.popupWidthOffset / 2)
|
||||||
}
|
}
|
||||||
popup.y = pos.y
|
dropdownMenu.y = pos.y + dropdown.height + 4
|
||||||
}
|
}
|
||||||
})
|
|
||||||
} else {
|
if (root.enableFuzzySearch && searchField.visible) {
|
||||||
const pos = dropdown.mapToItem(Overlay.overlay, 0, dropdown.height + 4)
|
searchField.forceActiveFocus()
|
||||||
popup.x = pos.x - (root.popupWidthOffset / 2)
|
|
||||||
popup.y = pos.y
|
|
||||||
popup.open()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,8 +119,10 @@ Rectangle {
|
|||||||
id: contentRow
|
id: contentRow
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
anchors.right: expandIcon.left
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.leftMargin: Theme.spacingM
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.rightMargin: Theme.spacingS
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
@@ -149,9 +131,9 @@ Rectangle {
|
|||||||
return currentIndex >= 0 && root.optionIcons.length > currentIndex ? root.optionIcons[currentIndex] : ""
|
return currentIndex >= 0 && root.optionIcons.length > currentIndex ? root.optionIcons[currentIndex] : ""
|
||||||
}
|
}
|
||||||
size: 18
|
size: 18
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: name !== "" && root.width > 60
|
visible: name !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
@@ -159,36 +141,30 @@ Rectangle {
|
|||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: root.width <= 60 ? dropdown.width - expandIcon.width - Theme.spacingS * 2 : dropdown.width - contentRow.x - expandIcon.width - Theme.spacingM - Theme.spacingS
|
width: contentRow.width - (contentRow.children[0].visible ? contentRow.children[0].width + contentRow.spacing : 0)
|
||||||
elide: root.width <= 60 ? Text.ElideNone : Text.ElideRight
|
elide: Text.ElideRight
|
||||||
horizontalAlignment: root.width <= 60 ? Text.AlignHCenter : Text.AlignLeft
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
id: expandIcon
|
id: expandIcon
|
||||||
|
|
||||||
name: "expand_more"
|
name: dropdownMenu.visible ? "expand_less" : "expand_more"
|
||||||
size: 20
|
size: 20
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceText
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.rightMargin: Theme.spacingS
|
anchors.rightMargin: Theme.spacingS
|
||||||
|
|
||||||
|
Behavior on rotation {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: popupLoader
|
|
||||||
|
|
||||||
property bool recreateFlag: root.forceRecreate
|
|
||||||
|
|
||||||
active: true
|
|
||||||
onRecreateFlagChanged: {
|
|
||||||
active = false
|
|
||||||
active = true
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceComponent: Component {
|
|
||||||
Popup {
|
Popup {
|
||||||
id: dropdownMenu
|
id: dropdownMenu
|
||||||
|
|
||||||
@@ -239,18 +215,11 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parent: Overlay.overlay
|
parent: Overlay.overlay
|
||||||
width: root.popupWidth > 0 ? root.popupWidth : (dropdown.width + root.popupWidthOffset)
|
width: root.popupWidth === -1 ? undefined : (root.popupWidth > 0 ? root.popupWidth : (dropdown.width + root.popupWidthOffset))
|
||||||
height: Math.min(root.maxPopupHeight, (root.enableFuzzySearch ? 54 : 0) + Math.min(filteredOptions.length, 10) * 36 + 16)
|
height: Math.min(root.maxPopupHeight, (root.enableFuzzySearch ? 54 : 0) + Math.min(filteredOptions.length, 10) * 36 + 16)
|
||||||
padding: 0
|
padding: 0
|
||||||
modal: true
|
modal: true
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||||
onOpened: {
|
|
||||||
searchQuery = ""
|
|
||||||
updateFilteredOptions()
|
|
||||||
if (root.enableFuzzySearch && searchField.visible) {
|
|
||||||
searchField.forceActiveFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
@@ -258,10 +227,18 @@ Rectangle {
|
|||||||
|
|
||||||
contentItem: Rectangle {
|
contentItem: Rectangle {
|
||||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1)
|
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1)
|
||||||
border.color: Theme.primarySelected
|
border.color: Theme.primary
|
||||||
border.width: 1
|
border.width: 2
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
shadowEnabled: true
|
||||||
|
shadowBlur: 0.4
|
||||||
|
shadowColor: Theme.shadowStrong
|
||||||
|
shadowVerticalOffset: 4
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingS
|
anchors.margins: Theme.spacingS
|
||||||
@@ -273,7 +250,7 @@ Rectangle {
|
|||||||
height: 42
|
height: 42
|
||||||
visible: root.enableFuzzySearch
|
visible: root.enableFuzzySearch
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceVariantAlpha
|
color: Theme.surfaceContainerHigh
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: searchField
|
id: searchField
|
||||||
@@ -281,29 +258,29 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 1
|
anchors.margins: 1
|
||||||
placeholderText: "Search..."
|
placeholderText: "Search..."
|
||||||
text: searchQuery
|
text: dropdownMenu.searchQuery
|
||||||
topPadding: Theme.spacingS
|
topPadding: Theme.spacingS
|
||||||
bottomPadding: Theme.spacingS
|
bottomPadding: Theme.spacingS
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
searchQuery = text
|
dropdownMenu.searchQuery = text
|
||||||
updateFilteredOptions()
|
dropdownMenu.updateFilteredOptions()
|
||||||
}
|
}
|
||||||
Keys.onDownPressed: selectNext()
|
Keys.onDownPressed: dropdownMenu.selectNext()
|
||||||
Keys.onUpPressed: selectPrevious()
|
Keys.onUpPressed: dropdownMenu.selectPrevious()
|
||||||
Keys.onReturnPressed: selectCurrent()
|
Keys.onReturnPressed: dropdownMenu.selectCurrent()
|
||||||
Keys.onEnterPressed: selectCurrent()
|
Keys.onEnterPressed: dropdownMenu.selectCurrent()
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
|
if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
|
||||||
selectNext()
|
dropdownMenu.selectNext()
|
||||||
event.accepted = true
|
event.accepted = true
|
||||||
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
|
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
|
||||||
selectPrevious()
|
dropdownMenu.selectPrevious()
|
||||||
event.accepted = true
|
event.accepted = true
|
||||||
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
|
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
|
||||||
selectNext()
|
dropdownMenu.selectNext()
|
||||||
event.accepted = true
|
event.accepted = true
|
||||||
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
|
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
|
||||||
selectPrevious()
|
dropdownMenu.selectPrevious()
|
||||||
event.accepted = true
|
event.accepted = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,12 +296,10 @@ Rectangle {
|
|||||||
DankListView {
|
DankListView {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
property var popupRef: dropdownMenu
|
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height - (root.enableFuzzySearch ? searchContainer.height + Theme.spacingXS : 0)
|
height: parent.height - (root.enableFuzzySearch ? searchContainer.height + Theme.spacingXS : 0)
|
||||||
clip: true
|
clip: true
|
||||||
model: filteredOptions
|
model: dropdownMenu.filteredOptions
|
||||||
spacing: 2
|
spacing: 2
|
||||||
|
|
||||||
interactive: true
|
interactive: true
|
||||||
@@ -336,7 +311,7 @@ Rectangle {
|
|||||||
flickableDirection: Flickable.VerticalFlick
|
flickableDirection: Flickable.VerticalFlick
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
property bool isSelected: selectedIndex === index
|
property bool isSelected: dropdownMenu.selectedIndex === index
|
||||||
property bool isCurrentValue: root.currentValue === modelData
|
property bool isCurrentValue: root.currentValue === modelData
|
||||||
property int optionIndex: root.options.indexOf(modelData)
|
property int optionIndex: root.options.indexOf(modelData)
|
||||||
|
|
||||||
@@ -354,7 +329,7 @@ Rectangle {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
name: optionIndex >= 0 && root.optionIcons.length > optionIndex ? root.optionIcons[optionIndex] : ""
|
name: optionIndex >= 0 && root.optionIcons.length > optionIndex ? root.optionIcons[optionIndex] : ""
|
||||||
size: 18
|
size: 18
|
||||||
color: isCurrentValue ? Theme.primary : Theme.surfaceVariantText
|
color: isCurrentValue ? Theme.primary : Theme.surfaceText
|
||||||
visible: name !== ""
|
visible: name !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,9 +354,7 @@ Rectangle {
|
|||||||
onClicked: {
|
onClicked: {
|
||||||
root.currentValue = modelData
|
root.currentValue = modelData
|
||||||
root.valueChanged(modelData)
|
root.valueChanged(modelData)
|
||||||
listView.popupRef.close()
|
dropdownMenu.close()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ Item {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
name: slider.leftIcon
|
name: slider.leftIcon
|
||||||
size: Theme.iconSize
|
size: Theme.iconSize
|
||||||
color: slider.enabled ? Theme.onSurface : Theme.onSurface_38
|
color: slider.enabled ? Theme.surfaceText : Theme.onSurface_38
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: slider.leftIcon.length > 0
|
visible: slider.leftIcon.length > 0
|
||||||
}
|
}
|
||||||
@@ -265,7 +265,7 @@ Item {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
name: slider.rightIcon
|
name: slider.rightIcon
|
||||||
size: Theme.iconSize
|
size: Theme.iconSize
|
||||||
color: slider.enabled ? Theme.onSurface : Theme.onSurface_38
|
color: slider.enabled ? Theme.surfaceText : Theme.onSurface_38
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: slider.rightIcon.length > 0
|
visible: slider.rightIcon.length > 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
[templates.niri]
|
[templates.niri]
|
||||||
input_path = './matugen/templates/niri-colors.kdl'
|
input_path = './matugen/templates/niri-colors.kdl'
|
||||||
output_path = '~/.config/niri/dankshell-colors.kdl'
|
output_path = '~/.config/niri/dms/colors.kdl'
|
||||||
Reference in New Issue
Block a user