1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

dankdash: add wallpaper selector + IPC targets

This commit is contained in:
bbedward
2025-10-21 23:15:10 -04:00
parent 967b7d05de
commit 1e72733e81
8 changed files with 812 additions and 19 deletions

View File

@@ -49,6 +49,30 @@ Item {
return false return false
} }
function triggerWallpaperBrowserOnFocusedScreen() {
let focusedScreenName = ""
if (CompositorService.isHyprland && Hyprland.focusedWorkspace && Hyprland.focusedWorkspace.monitor) {
focusedScreenName = Hyprland.focusedWorkspace.monitor.name
} else if (CompositorService.isNiri && NiriService.currentOutput) {
focusedScreenName = NiriService.currentOutput
}
if (!focusedScreenName && barVariants.instances.length > 0) {
const firstBar = barVariants.instances[0]
firstBar.triggerWallpaperBrowser()
return true
}
for (var i = 0; i < barVariants.instances.length; i++) {
const barInstance = barVariants.instances[i]
if (barInstance.modelData && barInstance.modelData.name === focusedScreenName) {
barInstance.triggerWallpaperBrowser()
return true
}
}
return false
}
Variants { Variants {
id: barVariants id: barVariants
model: SettingsData.getFilteredScreens("dankBar") model: SettingsData.getFilteredScreens("dankBar")
@@ -57,6 +81,7 @@ Item {
id: barWindow id: barWindow
property var controlCenterButtonRef: null property var controlCenterButtonRef: null
property var clockButtonRef: null
function triggerControlCenter() { function triggerControlCenter() {
controlCenterLoader.active = true controlCenterLoader.active = true
@@ -79,6 +104,27 @@ Item {
} }
} }
function triggerWallpaperBrowser() {
dankDashPopoutLoader.active = true
if (!dankDashPopoutLoader.item) {
return
}
if (clockButtonRef && dankDashPopoutLoader.item.setTriggerPosition) {
const globalPos = clockButtonRef.mapToGlobal(0, 0)
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, clockButtonRef.width)
const section = clockButtonRef.section || "center"
dankDashPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen)
} else {
dankDashPopoutLoader.item.triggerScreen = barWindow.screen
}
if (!dankDashPopoutLoader.item.dashVisible) {
dankDashPopoutLoader.item.currentTabIndex = 2
}
dankDashPopoutLoader.item.dashVisible = !dankDashPopoutLoader.item.dashVisible
}
readonly property var dBarLayer: { readonly property var dBarLayer: {
switch (Quickshell.env("DMS_DANKBAR_LAYER")) { switch (Quickshell.env("DMS_DANKBAR_LAYER")) {
case "bottom": case "bottom":
@@ -825,6 +871,17 @@ Item {
return dankDashPopoutLoader.item return dankDashPopoutLoader.item
} }
parentScreen: barWindow.screen parentScreen: barWindow.screen
Component.onCompleted: {
barWindow.clockButtonRef = this
}
Component.onDestruction: {
if (barWindow.clockButtonRef === this) {
barWindow.clockButtonRef = null
}
}
onClockClicked: { onClockClicked: {
dankDashPopoutLoader.active = true dankDashPopoutLoader.active = true
if (dankDashPopoutLoader.item) { if (dankDashPopoutLoader.item) {
@@ -874,7 +931,7 @@ Item {
dankDashPopoutLoader.active = true dankDashPopoutLoader.active = true
if (dankDashPopoutLoader.item) { if (dankDashPopoutLoader.item) {
dankDashPopoutLoader.item.dashVisible = !dankDashPopoutLoader.item.dashVisible dankDashPopoutLoader.item.dashVisible = !dankDashPopoutLoader.item.dashVisible
dankDashPopoutLoader.item.currentTabIndex = 2 dankDashPopoutLoader.item.currentTabIndex = 3
} }
} }
} }
@@ -1208,4 +1265,15 @@ Item {
} }
} }
} }
IpcHandler {
target: "dankdash"
function wallpaper(): string {
if (root.triggerWallpaperBrowserOnFocusedScreen()) {
return "SUCCESS: Toggled wallpaper browser"
}
return "ERROR: Failed to toggle wallpaper browser"
}
}
} }

View File

@@ -16,6 +16,8 @@ DankPopout {
property var triggerScreen: null property var triggerScreen: null
property int currentTabIndex: 0 property int currentTabIndex: 0
keyboardFocusMode: WlrKeyboardFocus.Exclusive
function setTriggerPosition(x, y, width, section, screen) { function setTriggerPosition(x, y, width, section, screen) {
triggerSection = section triggerSection = section
triggerScreen = screen triggerScreen = screen
@@ -43,15 +45,49 @@ DankPopout {
shouldBeVisible: dashVisible shouldBeVisible: dashVisible
visible: shouldBeVisible visible: shouldBeVisible
property bool __focusArmed: false
property bool __contentReady: false
function __tryFocusOnce() {
if (!__focusArmed) return
const win = root.window
if (!win || !win.visible) return
if (!contentLoader.item) return
if (win.requestActivate) win.requestActivate()
contentLoader.item.forceActiveFocus(Qt.TabFocusReason)
if (contentLoader.item.activeFocus)
__focusArmed = false
}
onDashVisibleChanged: { onDashVisibleChanged: {
if (dashVisible) { if (dashVisible) {
__focusArmed = true
__contentReady = !!contentLoader.item
open() open()
__tryFocusOnce()
} else { } else {
__focusArmed = false
__contentReady = false
close() close()
} }
} }
Connections {
target: contentLoader
function onLoaded() {
__contentReady = true
if (__focusArmed) __tryFocusOnce()
}
}
Connections {
target: root.window ? root.window : null
enabled: !!root.window
function onVisibleChanged() { if (__focusArmed) __tryFocusOnce() }
}
onBackgroundClicked: { onBackgroundClicked: {
dashVisible = false dashVisible = false
} }
@@ -67,18 +103,12 @@ DankPopout {
Component.onCompleted: { Component.onCompleted: {
if (root.shouldBeVisible) { if (root.shouldBeVisible) {
forceActiveFocus() mainContainer.forceActiveFocus()
}
}
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape) {
root.dashVisible = false
event.accepted = true
} }
} }
Connections { Connections {
target: root
function onShouldBeVisibleChanged() { function onShouldBeVisibleChanged() {
if (root.shouldBeVisible) { if (root.shouldBeVisible) {
Qt.callLater(function() { Qt.callLater(function() {
@@ -86,7 +116,52 @@ DankPopout {
}) })
} }
} }
target: root }
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape) {
root.dashVisible = false
event.accepted = true
return
}
if (event.key === Qt.Key_Tab && !(event.modifiers & Qt.ShiftModifier)) {
let nextIndex = root.currentTabIndex + 1
while (nextIndex < tabBar.model.length && tabBar.model[nextIndex] && tabBar.model[nextIndex].isAction) {
nextIndex++
}
if (nextIndex >= tabBar.model.length) {
nextIndex = 0
}
root.currentTabIndex = nextIndex
event.accepted = true
return
}
if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) {
let prevIndex = root.currentTabIndex - 1
while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) {
prevIndex--
}
if (prevIndex < 0) {
prevIndex = tabBar.model.length - 1
while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) {
prevIndex--
}
}
if (prevIndex >= 0) {
root.currentTabIndex = prevIndex
}
event.accepted = true
return
}
if (root.currentTabIndex === 2 && wallpaperTab.handleKeyEvent) {
if (wallpaperTab.handleKeyEvent(event)) {
event.accepted = true
return
}
}
} }
Rectangle { Rectangle {
@@ -128,11 +203,23 @@ DankPopout {
currentIndex: root.currentTabIndex currentIndex: root.currentTabIndex
spacing: Theme.spacingS spacing: Theme.spacingS
equalWidthTabs: true equalWidthTabs: true
enableArrowNavigation: false
focus: false
activeFocusOnTab: false
nextFocusTarget: {
const item = pages.currentItem
if (!item)
return null
if (item.focusTarget)
return item.focusTarget
return item
}
model: { model: {
let tabs = [ let tabs = [
{ icon: "dashboard", text: I18n.tr("Overview") }, { icon: "dashboard", text: I18n.tr("Overview") },
{ icon: "music_note", text: I18n.tr("Media") } { icon: "music_note", text: I18n.tr("Media") },
{ icon: "wallpaper", text: I18n.tr("Wallpapers") }
] ]
if (SettingsData.weatherEnabled) { if (SettingsData.weatherEnabled) {
@@ -148,7 +235,7 @@ DankPopout {
} }
onActionTriggered: function(index) { onActionTriggered: function(index) {
let settingsIndex = SettingsData.weatherEnabled ? 3 : 2 let settingsIndex = SettingsData.weatherEnabled ? 4 : 3
if (index === settingsIndex) { if (index === settingsIndex) {
dashVisible = false dashVisible = false
settingsModal.show() settingsModal.show()
@@ -168,7 +255,8 @@ DankPopout {
implicitHeight: { implicitHeight: {
if (currentIndex === 0) return overviewTab.implicitHeight if (currentIndex === 0) return overviewTab.implicitHeight
if (currentIndex === 1) return mediaTab.implicitHeight if (currentIndex === 1) return mediaTab.implicitHeight
if (SettingsData.weatherEnabled && currentIndex === 2) return weatherTab.implicitHeight if (currentIndex === 2) return wallpaperTab.implicitHeight
if (SettingsData.weatherEnabled && currentIndex === 3) return weatherTab.implicitHeight
return overviewTab.implicitHeight return overviewTab.implicitHeight
} }
currentIndex: root.currentTabIndex currentIndex: root.currentTabIndex
@@ -178,8 +266,8 @@ DankPopout {
onSwitchToWeatherTab: { onSwitchToWeatherTab: {
if (SettingsData.weatherEnabled) { if (SettingsData.weatherEnabled) {
tabBar.currentIndex = 2 tabBar.currentIndex = 3
tabBar.tabClicked(2) tabBar.tabClicked(3)
} }
} }
@@ -193,9 +281,16 @@ DankPopout {
id: mediaTab id: mediaTab
} }
WallpaperTab {
id: wallpaperTab
active: root.currentTabIndex === 2
tabBarItem: tabBar
keyForwardTarget: mainContainer
}
WeatherTab { WeatherTab {
id: weatherTab id: weatherTab
visible: SettingsData.weatherEnabled && root.currentTabIndex === 2 visible: SettingsData.weatherEnabled && root.currentTabIndex === 3
} }
} }
} }

View File

@@ -0,0 +1,523 @@
import Qt.labs.folderlistmodel
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modals.FileBrowser
import qs.Services
import qs.Widgets
Item {
id: root
implicitWidth: 700
implicitHeight: 410
property var wallpaperList: []
property string wallpaperDir: ""
property int currentPage: 0
property int itemsPerPage: 16
property int totalPages: Math.max(1, Math.ceil(wallpaperList.length / itemsPerPage))
property bool active: false
property Item focusTarget: wallpaperGrid
property Item tabBarItem: null
property int gridIndex: 0
property Item keyForwardTarget: null
property int lastPage: 0
property bool enableAnimation: false
signal requestTabChange(int newIndex)
onCurrentPageChanged: {
if (currentPage !== lastPage) {
enableAnimation = false
lastPage = currentPage
}
}
onVisibleChanged: {
if (visible && active) {
setInitialSelection()
}
}
Component.onCompleted: {
loadWallpapers()
if (visible && active) {
setInitialSelection()
}
}
onActiveChanged: {
if (active && visible) {
setInitialSelection()
}
}
function handleKeyEvent(event) {
const columns = 4
const rows = 4
const currentRow = Math.floor(gridIndex / columns)
const currentCol = gridIndex % columns
const visibleCount = wallpaperGrid.model.length
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
if (gridIndex >= 0) {
const item = wallpaperGrid.currentItem
if (item && item.wallpaperPath) {
SessionData.setWallpaper(item.wallpaperPath)
}
}
return true
}
if (event.key === Qt.Key_Right) {
if (gridIndex + 1 < visibleCount) {
// Move right within current page
gridIndex++
} else if (gridIndex === visibleCount - 1 && currentPage < totalPages - 1) {
// At last item in page, go to next page
gridIndex = 0
currentPage++
}
return true
}
if (event.key === Qt.Key_Left) {
if (gridIndex > 0) {
// Move left within current page
gridIndex--
} else if (gridIndex === 0 && currentPage > 0) {
// At first item in page, go to previous page (last item)
currentPage--
gridIndex = Math.min(itemsPerPage - 1, wallpaperList.length - currentPage * itemsPerPage - 1)
}
return true
}
if (event.key === Qt.Key_Down) {
if (gridIndex + columns < visibleCount) {
// Move down within current page
gridIndex += columns
} else if (gridIndex >= visibleCount - columns && currentPage < totalPages - 1) {
// In last row, go to next page
gridIndex = currentCol
currentPage++
}
return true
}
if (event.key === Qt.Key_Up) {
if (gridIndex >= columns) {
// Move up within current page
gridIndex -= columns
} else if (gridIndex < columns && currentPage > 0) {
// In first row, go to previous page (last row)
currentPage--
const prevPageCount = Math.min(itemsPerPage, wallpaperList.length - currentPage * itemsPerPage)
const prevPageRows = Math.ceil(prevPageCount / columns)
gridIndex = (prevPageRows - 1) * columns + currentCol
gridIndex = Math.min(gridIndex, prevPageCount - 1)
}
return true
}
if (event.key === Qt.Key_PageUp && currentPage > 0) {
gridIndex = 0
currentPage--
return true
}
if (event.key === Qt.Key_PageDown && currentPage < totalPages - 1) {
gridIndex = 0
currentPage++
return true
}
if (event.key === Qt.Key_Home && event.modifiers & Qt.ControlModifier) {
gridIndex = 0
currentPage = 0
return true
}
if (event.key === Qt.Key_End && event.modifiers & Qt.ControlModifier) {
gridIndex = 0
currentPage = totalPages - 1
return true
}
return false
}
function setInitialSelection() {
if (!SessionData.wallpaperPath) {
gridIndex = 0
return
}
const startIndex = currentPage * itemsPerPage
const endIndex = Math.min(startIndex + itemsPerPage, wallpaperList.length)
const pageWallpapers = wallpaperList.slice(startIndex, endIndex)
for (let i = 0; i < pageWallpapers.length; i++) {
if (pageWallpapers[i] === SessionData.wallpaperPath) {
gridIndex = i
return
}
}
gridIndex = 0
}
onWallpaperListChanged: {
if (visible && active) {
setInitialSelection()
}
}
function loadWallpapers() {
const currentWallpaper = SessionData.wallpaperPath
// Try current wallpaper path / fallback to wallpaperLastPath
if (!currentWallpaper || currentWallpaper.startsWith("#") || currentWallpaper.startsWith("we:")) {
if (CacheData.wallpaperLastPath && CacheData.wallpaperLastPath !== "") {
wallpaperDir = CacheData.wallpaperLastPath
} else {
wallpaperDir = ""
wallpaperList = []
}
return
}
wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'))
}
function updateWallpaperList() {
if (!wallpaperFolderModel || wallpaperFolderModel.count === 0) {
wallpaperList = []
currentPage = 0
gridIndex = 0
return
}
// Build list from FolderListModel
const files = []
for (let i = 0; i < wallpaperFolderModel.count; i++) {
const filePath = wallpaperFolderModel.get(i, "filePath")
if (filePath) {
// Remove file:// prefix if present
const cleanPath = filePath.toString().replace(/^file:\/\//, '')
files.push(cleanPath)
}
}
wallpaperList = files
const currentPath = SessionData.wallpaperPath
const selectedIndex = currentPath ? wallpaperList.indexOf(currentPath) : -1
if (selectedIndex >= 0) {
currentPage = Math.floor(selectedIndex / itemsPerPage)
gridIndex = selectedIndex % itemsPerPage
} else {
const maxPage = Math.max(0, Math.ceil(files.length / itemsPerPage) - 1)
currentPage = Math.min(Math.max(0, currentPage), maxPage)
gridIndex = 0
}
}
Connections {
target: SessionData
function onWallpaperPathChanged() {
loadWallpapers()
}
}
FolderListModel {
id: wallpaperFolderModel
showDirsFirst: false
showDotAndDotDot: false
showHidden: false
nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
showFiles: true
showDirs: false
sortField: FolderListModel.Name
folder: wallpaperDir ? "file://" + wallpaperDir : ""
onStatusChanged: {
if (status === FolderListModel.Ready) {
updateWallpaperList()
}
}
onCountChanged: {
if (status === FolderListModel.Ready) {
updateWallpaperList()
}
}
}
Loader {
id: wallpaperBrowserLoader
active: false
asynchronous: true
sourceComponent: FileBrowserModal {
Component.onCompleted: {
open()
}
browserTitle: "Select Wallpaper Directory"
browserIcon: "folder_open"
browserType: "wallpaper"
showHiddenFiles: false
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
allowStacking: true
onFileSelected: (path) => {
// Set the selected wallpaper
const cleanPath = path.replace(/^file:\/\//, '')
SessionData.setWallpaper(cleanPath)
// Extract directory from the selected file and load all wallpapers
const dirPath = cleanPath.substring(0, cleanPath.lastIndexOf('/'))
if (dirPath) {
wallpaperDir = dirPath
CacheData.wallpaperLastPath = dirPath
CacheData.saveCache()
}
close()
}
onDialogClosed: {
Qt.callLater(() => wallpaperBrowserLoader.active = false)
}
}
}
Column {
anchors.fill: parent
spacing: 0
Item {
width: parent.width
height: parent.height - 50
GridView {
id: wallpaperGrid
anchors.centerIn: parent
width: parent.width - Theme.spacingS
height: parent.height - Theme.spacingS
cellWidth: width / 4
cellHeight: height / 4
clip: true
enabled: root.active
interactive: root.active
boundsBehavior: Flickable.StopAtBounds
keyNavigationEnabled: false
activeFocusOnTab: false
highlightFollowsCurrentItem: true
highlightMoveDuration: enableAnimation ? Theme.shortDuration : 0
focus: false
highlight: Item {
z: 1000
Rectangle {
anchors.fill: parent
anchors.margins: Theme.spacingXS
color: "transparent"
border.width: 3
border.color: Theme.primary
radius: Theme.cornerRadius
}
}
model: {
const startIndex = currentPage * itemsPerPage
const endIndex = Math.min(startIndex + itemsPerPage, wallpaperList.length)
return wallpaperList.slice(startIndex, endIndex)
}
onModelChanged: {
const clampedIndex = model.length > 0 ? Math.min(Math.max(0, gridIndex), model.length - 1) : 0
if (gridIndex !== clampedIndex) {
gridIndex = clampedIndex
}
}
onCountChanged: {
if (count > 0) {
const clampedIndex = Math.min(gridIndex, count - 1)
currentIndex = clampedIndex
positionViewAtIndex(clampedIndex, GridView.Contain)
}
enableAnimation = true
}
Connections {
target: root
function onGridIndexChanged() {
if (enableAnimation && wallpaperGrid.count > 0) {
wallpaperGrid.currentIndex = gridIndex
}
}
}
delegate: Item {
width: wallpaperGrid.cellWidth
height: wallpaperGrid.cellHeight
property string wallpaperPath: modelData || ""
property bool isSelected: SessionData.wallpaperPath === modelData
Rectangle {
id: wallpaperCard
anchors.fill: parent
anchors.margins: Theme.spacingXS
color: Theme.surfaceContainerHighest
radius: Theme.cornerRadius
clip: true
Rectangle {
anchors.fill: parent
color: isSelected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15) : "transparent"
radius: parent.radius
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
Image {
id: thumbnailImage
anchors.fill: parent
source: modelData ? `file://${modelData}` : ""
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: true
smooth: true
layer.enabled: true
layer.effect: MultiEffect {
maskEnabled: true
maskThresholdMin: 0.5
maskSpreadAtMin: 1.0
maskSource: ShaderEffectSource {
sourceItem: Rectangle {
width: thumbnailImage.width
height: thumbnailImage.height
radius: Theme.cornerRadius
}
}
}
}
BusyIndicator {
anchors.centerIn: parent
running: thumbnailImage.status === Image.Loading
visible: running
}
StateLayer {
anchors.fill: parent
cornerRadius: parent.radius
stateColor: Theme.primary
}
MouseArea {
id: wallpaperMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
gridIndex = index
if (modelData) {
SessionData.setWallpaper(modelData)
}
// Don't steal focus - let mainContainer keep it for keyboard nav
}
}
}
}
}
StyledText {
anchors.centerIn: parent
visible: wallpaperList.length === 0
text: "No wallpapers found\n\nClick the folder icon below to browse"
font.pixelSize: 14
color: Theme.outline
horizontalAlignment: Text.AlignHCenter
}
}
Row {
width: parent.width
height: 50
spacing: Theme.spacingS
Item {
width: (parent.width - controlsRow.width - browseButton.width - Theme.spacingS) / 2
height: parent.height
}
Row {
id: controlsRow
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "skip_previous"
iconSize: 20
buttonSize: 32
enabled: currentPage > 0
opacity: enabled ? 1.0 : 0.3
onClicked: {
if (currentPage > 0) {
currentPage--
}
}
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: wallpaperList.length > 0 ? `${wallpaperList.length} wallpapers ${currentPage + 1} / ${totalPages}` : "No wallpapers"
font.pixelSize: 14
color: Theme.surfaceText
opacity: 0.7
}
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "skip_next"
iconSize: 20
buttonSize: 32
enabled: currentPage < totalPages - 1
opacity: enabled ? 1.0 : 0.3
onClicked: {
if (currentPage < totalPages - 1) {
currentPage++
}
}
}
}
DankActionButton {
id: browseButton
anchors.verticalCenter: parent.verticalCenter
iconName: "folder_open"
iconSize: 20
buttonSize: 32
opacity: 0.7
onClicked: wallpaperBrowserLoader.active = true
}
}
}
}

View File

@@ -411,6 +411,9 @@ binds {
Mod+C hotkey-overlay-title="Control Center" { Mod+C hotkey-overlay-title="Control Center" {
spawn "dms" "ipc" "call" "control-center" "toggle"; spawn "dms" "ipc" "call" "control-center" "toggle";
} }
Mod+Y hotkey-overlay-title="Browse Wallpapers" {
spawn "dms" "ipc" "call" "dankdash" "wallpaper";
}
XF86AudioRaiseVolume allow-when-locked=true { XF86AudioRaiseVolume allow-when-locked=true {
spawn "dms" "ipc" "call" "audio" "increment" "3"; spawn "dms" "ipc" "call" "audio" "increment" "3";
} }
@@ -478,6 +481,7 @@ bind = SUPER, comma, exec, dms ipc call settings toggle
bind = SUPER, P, exec, dms ipc call notepad toggle bind = SUPER, P, exec, dms ipc call notepad toggle
bind = SUPERALT, L, exec, dms ipc call lock lock bind = SUPERALT, L, exec, dms ipc call lock lock
bind = SUPER, X, exec, dms ipc call powermenu toggle bind = SUPER, X, exec, dms ipc call powermenu toggle
bind = SUPER, Y, exec, dms ipc call dankdash wallpaper
bind = SUPER, C, exec, dms ipc call control-center toggle bind = SUPER, C, exec, dms ipc call control-center toggle
bind = SUPER, TAB, exec, dms ipc call hypr toggleOverview bind = SUPER, TAB, exec, dms ipc call hypr toggleOverview

View File

@@ -19,6 +19,10 @@ binds {
spawn "dms" "ipc" "call" "notifications" "toggle"; spawn "dms" "ipc" "call" "notifications" "toggle";
} }
Mod+Y hotkey-overlay-title="Browse Wallpapers" {
spawn "dms" "ipc" "call" "dankdash" "wallpaper";
}
Mod+Shift+N hotkey-overlay-title="Notepad" { Mod+Shift+N hotkey-overlay-title="Notepad" {
spawn "dms" "ipc" "call" "notepad" "toggle"; spawn "dms" "ipc" "call" "notepad" "toggle";
} }

View File

@@ -25,6 +25,7 @@ PanelWindow {
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
property bool shouldBeVisible: false property bool shouldBeVisible: false
property int keyboardFocusMode: WlrKeyboardFocus.OnDemand
signal opened signal opened
signal popoutClosed signal popoutClosed
@@ -63,7 +64,7 @@ PanelWindow {
color: "transparent" color: "transparent"
WlrLayershell.layer: WlrLayershell.Top WlrLayershell.layer: WlrLayershell.Top
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: shouldBeVisible ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None WlrLayershell.keyboardFocus: shouldBeVisible ? keyboardFocusMode : WlrKeyboardFocus.None
anchors { anchors {
top: true top: true

View File

@@ -2,7 +2,7 @@ import QtQuick
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
Item { FocusScope {
id: tabBar id: tabBar
property alias model: tabRepeater.model property alias model: tabRepeater.model
@@ -11,12 +11,101 @@ Item {
property int tabHeight: 56 property int tabHeight: 56
property bool showIcons: true property bool showIcons: true
property bool equalWidthTabs: true property bool equalWidthTabs: true
property bool enableArrowNavigation: true
property Item nextFocusTarget: null
property Item previousFocusTarget: null
signal tabClicked(int index) signal tabClicked(int index)
signal actionTriggered(int index) signal actionTriggered(int index)
focus: false
activeFocusOnTab: true
height: tabHeight height: tabHeight
KeyNavigation.tab: nextFocusTarget
KeyNavigation.down: nextFocusTarget
KeyNavigation.backtab: previousFocusTarget
KeyNavigation.up: previousFocusTarget
Keys.onPressed: (event) => {
if (!tabBar.activeFocus || tabRepeater.count === 0)
return
function findSelectableIndex(startIndex, step) {
let idx = startIndex
for (let i = 0; i < tabRepeater.count; i++) {
idx = (idx + step + tabRepeater.count) % tabRepeater.count
const item = tabRepeater.itemAt(idx)
if (item && !item.isAction)
return idx
}
return -1
}
const goToIndex = (nextIndex) => {
if (nextIndex >= 0 && nextIndex !== tabBar.currentIndex) {
tabBar.currentIndex = nextIndex
tabBar.tabClicked(nextIndex)
}
}
const resolveTarget = (item) => {
if (!item)
return null
if (item.focusTarget)
return resolveTarget(item.focusTarget)
return item
}
const focusItem = (item) => {
const target = resolveTarget(item)
if (!target)
return false
if (target.requestFocus) {
Qt.callLater(() => target.requestFocus())
return true
}
if (target.forceActiveFocus) {
Qt.callLater(() => target.forceActiveFocus())
return true
}
return false
}
if (event.key === Qt.Key_Right && tabBar.enableArrowNavigation) {
const baseIndex = (tabBar.currentIndex >= 0 && tabBar.currentIndex < tabRepeater.count) ? tabBar.currentIndex : -1
const nextIndex = findSelectableIndex(baseIndex, 1)
if (nextIndex >= 0) {
goToIndex(nextIndex)
event.accepted = true
}
} else if (event.key === Qt.Key_Left && tabBar.enableArrowNavigation) {
const baseIndex = (tabBar.currentIndex >= 0 && tabBar.currentIndex < tabRepeater.count) ? tabBar.currentIndex : 0
const nextIndex = findSelectableIndex(baseIndex, -1)
if (nextIndex >= 0) {
goToIndex(nextIndex)
event.accepted = true
}
} else if (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier)) {
if (focusItem(tabBar.previousFocusTarget)) {
event.accepted = true
}
} else if (event.key === Qt.Key_Tab || event.key === Qt.Key_Down) {
if (focusItem(tabBar.nextFocusTarget)) {
event.accepted = true
}
} else if (event.key === Qt.Key_Up) {
if (focusItem(tabBar.previousFocusTarget)) {
event.accepted = true
}
}
}
Row { Row {
id: tabRow id: tabRow
anchors.fill: parent anchors.fill: parent
@@ -77,7 +166,6 @@ Item {
if (tabItem.isAction) { if (tabItem.isAction) {
tabBar.actionTriggered(index) tabBar.actionTriggered(index)
} else { } else {
tabBar.currentIndex = index
tabBar.tabClicked(index) tabBar.tabClicked(index)
} }
} }

View File

@@ -501,6 +501,13 @@ Dashboard popup control with tab selection for overview, media, and weather info
- Parameters: `tab` - Optional tab to open when showing: "" (default), "overview", "media", or "weather" - Parameters: `tab` - Optional tab to open when showing: "" (default), "overview", "media", or "weather"
- Returns: Success/failure message - Returns: Success/failure message
### Target: `dankdash`
DankDash wallpaper browser control.
**Functions:**
- `wallpaper` - Toggle DankDash popup on focused screen with wallpaper tab selected
- Returns: Success/failure message
### Target: `file` ### Target: `file`
File browser controls for selecting wallpapers and profile images. File browser controls for selecting wallpapers and profile images.
@@ -581,6 +588,9 @@ dms ipc call dash open overview
dms ipc call dash toggle media dms ipc call dash toggle media
dms ipc call dash open weather dms ipc call dash open weather
# Open wallpaper browser
dms ipc call dankdash wallpaper
# Open file browsers # Open file browsers
dms ipc call file browse wallpaper dms ipc call file browse wallpaper
dms ipc call file browse profile dms ipc call file browse profile