mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-28 23:42:51 -05:00
event based workspace switches
This commit is contained in:
288
Services/NiriWorkspaceService.qml
Normal file
288
Services/NiriWorkspaceService.qml
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Workspace management
|
||||||
|
property list<var> allWorkspaces: []
|
||||||
|
property int focusedWorkspaceIndex: 0
|
||||||
|
property string focusedWorkspaceId: ""
|
||||||
|
property var currentOutputWorkspaces: []
|
||||||
|
property string currentOutput: ""
|
||||||
|
|
||||||
|
// Window management
|
||||||
|
property list<var> windows: []
|
||||||
|
property int focusedWindowIndex: -1
|
||||||
|
property string focusedWindowTitle: "(No active window)"
|
||||||
|
property string focusedWindowId: ""
|
||||||
|
|
||||||
|
// Overview state
|
||||||
|
property bool inOverview: false
|
||||||
|
|
||||||
|
// Feature availability
|
||||||
|
property bool niriAvailable: false
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
console.log("NiriWorkspaceService: Component.onCompleted - initializing service")
|
||||||
|
checkNiriAvailability()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if niri is available
|
||||||
|
Process {
|
||||||
|
id: niriCheck
|
||||||
|
command: ["which", "niri"]
|
||||||
|
|
||||||
|
onExited: (exitCode) => {
|
||||||
|
root.niriAvailable = exitCode === 0
|
||||||
|
if (root.niriAvailable) {
|
||||||
|
console.log("NiriWorkspaceService: niri found, starting event stream and loading initial data")
|
||||||
|
eventStreamProcess.running = true
|
||||||
|
loadInitialWorkspaceData()
|
||||||
|
} else {
|
||||||
|
console.log("NiriWorkspaceService: niri not found, workspace features disabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkNiriAvailability() {
|
||||||
|
niriCheck.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load initial workspace data
|
||||||
|
Process {
|
||||||
|
id: initialDataQuery
|
||||||
|
command: ["niri", "msg", "-j", "workspaces"]
|
||||||
|
running: false
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
if (text && text.trim()) {
|
||||||
|
try {
|
||||||
|
console.log("NiriWorkspaceService: Loaded initial workspace data")
|
||||||
|
const workspaces = JSON.parse(text.trim())
|
||||||
|
// Initial query returns array directly, event stream wraps it in WorkspacesChanged
|
||||||
|
handleWorkspacesChanged({ workspaces: workspaces })
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("NiriWorkspaceService: Failed to parse initial workspace data:", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadInitialWorkspaceData() {
|
||||||
|
console.log("NiriWorkspaceService: Loading initial workspace data...")
|
||||||
|
initialDataQuery.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event stream for real-time updates
|
||||||
|
Process {
|
||||||
|
id: eventStreamProcess
|
||||||
|
command: ["niri", "msg", "-j", "event-stream"]
|
||||||
|
running: false // Will be enabled after niri check
|
||||||
|
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: data => {
|
||||||
|
try {
|
||||||
|
const event = JSON.parse(data.trim())
|
||||||
|
handleNiriEvent(event)
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("NiriWorkspaceService: Failed to parse event:", data, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onExited: (exitCode) => {
|
||||||
|
if (exitCode !== 0 && root.niriAvailable) {
|
||||||
|
console.warn("NiriWorkspaceService: Event stream exited with code", exitCode, "restarting in 2 seconds")
|
||||||
|
restartTimer.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart timer for event stream
|
||||||
|
Timer {
|
||||||
|
id: restartTimer
|
||||||
|
interval: 2000
|
||||||
|
onTriggered: {
|
||||||
|
if (root.niriAvailable) {
|
||||||
|
eventStreamProcess.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNiriEvent(event) {
|
||||||
|
if (event.WorkspacesChanged) {
|
||||||
|
handleWorkspacesChanged(event.WorkspacesChanged)
|
||||||
|
} else if (event.WorkspaceActivated) {
|
||||||
|
handleWorkspaceActivated(event.WorkspaceActivated)
|
||||||
|
} else if (event.WindowsChanged) {
|
||||||
|
handleWindowsChanged(event.WindowsChanged)
|
||||||
|
} else if (event.WindowClosed) {
|
||||||
|
handleWindowClosed(event.WindowClosed)
|
||||||
|
} else if (event.WindowFocusChanged) {
|
||||||
|
handleWindowFocusChanged(event.WindowFocusChanged)
|
||||||
|
} else if (event.OverviewOpenedOrClosed) {
|
||||||
|
handleOverviewChanged(event.OverviewOpenedOrClosed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWorkspacesChanged(data) {
|
||||||
|
allWorkspaces = [...data.workspaces].sort((a, b) => a.idx - b.idx)
|
||||||
|
|
||||||
|
// Update focused workspace
|
||||||
|
focusedWorkspaceIndex = allWorkspaces.findIndex(w => w.is_focused)
|
||||||
|
if (focusedWorkspaceIndex >= 0) {
|
||||||
|
var focusedWs = allWorkspaces[focusedWorkspaceIndex]
|
||||||
|
focusedWorkspaceId = focusedWs.id
|
||||||
|
currentOutput = focusedWs.output || ""
|
||||||
|
} else {
|
||||||
|
focusedWorkspaceIndex = 0
|
||||||
|
focusedWorkspaceId = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCurrentOutputWorkspaces()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWorkspaceActivated(data) {
|
||||||
|
console.log("DEBUG: WorkspaceActivated event - ID:", data.id, "focused:", data.focused)
|
||||||
|
|
||||||
|
// Update focused workspace
|
||||||
|
focusedWorkspaceId = data.id
|
||||||
|
focusedWorkspaceIndex = allWorkspaces.findIndex(w => w.id === data.id)
|
||||||
|
|
||||||
|
if (focusedWorkspaceIndex >= 0) {
|
||||||
|
var activatedWs = allWorkspaces[focusedWorkspaceIndex]
|
||||||
|
console.log("DEBUG: Found workspace - idx:", activatedWs.idx + 1, "output:", activatedWs.output, "was_active:", activatedWs.is_active)
|
||||||
|
|
||||||
|
// Update workspace states properly
|
||||||
|
// First, deactivate all workspaces on this output
|
||||||
|
for (var i = 0; i < allWorkspaces.length; i++) {
|
||||||
|
if (allWorkspaces[i].output === activatedWs.output) {
|
||||||
|
allWorkspaces[i].is_active = false
|
||||||
|
allWorkspaces[i].is_focused = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then activate the new workspace
|
||||||
|
allWorkspaces[focusedWorkspaceIndex].is_active = true
|
||||||
|
allWorkspaces[focusedWorkspaceIndex].is_focused = data.focused || false
|
||||||
|
|
||||||
|
currentOutput = activatedWs.output || ""
|
||||||
|
console.log("DEBUG: Activated workspace", activatedWs.idx + 1, "on", currentOutput)
|
||||||
|
|
||||||
|
updateCurrentOutputWorkspaces()
|
||||||
|
} else {
|
||||||
|
focusedWorkspaceIndex = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWindowsChanged(data) {
|
||||||
|
windows = [...data.windows].sort((a, b) => a.id - b.id)
|
||||||
|
updateFocusedWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWindowClosed(data) {
|
||||||
|
windows = windows.filter(w => w.id !== data.id)
|
||||||
|
updateFocusedWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWindowFocusChanged(data) {
|
||||||
|
if (data.id) {
|
||||||
|
focusedWindowId = data.id
|
||||||
|
focusedWindowIndex = windows.findIndex(w => w.id === data.id)
|
||||||
|
} else {
|
||||||
|
focusedWindowId = ""
|
||||||
|
focusedWindowIndex = -1
|
||||||
|
}
|
||||||
|
updateFocusedWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOverviewChanged(data) {
|
||||||
|
inOverview = data.is_open
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCurrentOutputWorkspaces() {
|
||||||
|
if (!currentOutput) {
|
||||||
|
currentOutputWorkspaces = allWorkspaces
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter workspaces for current output
|
||||||
|
var outputWs = allWorkspaces.filter(w => w.output === currentOutput)
|
||||||
|
currentOutputWorkspaces = outputWs
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFocusedWindow() {
|
||||||
|
if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.length) {
|
||||||
|
var focusedWin = windows[focusedWindowIndex]
|
||||||
|
focusedWindowTitle = focusedWin.title || "(Unnamed window)"
|
||||||
|
} else {
|
||||||
|
focusedWindowTitle = "(No active window)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public API functions
|
||||||
|
function switchToWorkspace(workspaceId) {
|
||||||
|
if (!niriAvailable) return false
|
||||||
|
|
||||||
|
Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", workspaceId.toString()])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchToWorkspaceByIndex(index) {
|
||||||
|
if (!niriAvailable || index < 0 || index >= allWorkspaces.length) return false
|
||||||
|
|
||||||
|
var workspace = allWorkspaces[index]
|
||||||
|
return switchToWorkspace(workspace.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchToWorkspaceByNumber(number) {
|
||||||
|
if (!niriAvailable) return false
|
||||||
|
|
||||||
|
// Find workspace by number (1-based)
|
||||||
|
var workspace = allWorkspaces.find(w => (w.idx + 1) === number)
|
||||||
|
if (workspace) {
|
||||||
|
return switchToWorkspace(workspace.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not found, try to switch by number directly
|
||||||
|
Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", number.toString()])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWorkspaceByIndex(index) {
|
||||||
|
if (index >= 0 && index < allWorkspaces.length) {
|
||||||
|
return allWorkspaces[index]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentOutputWorkspaceNumbers() {
|
||||||
|
return currentOutputWorkspaces.map(w => w.idx + 1) // niri uses 0-based, UI shows 1-based
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentWorkspaceNumber() {
|
||||||
|
if (focusedWorkspaceIndex >= 0 && focusedWorkspaceIndex < allWorkspaces.length) {
|
||||||
|
return allWorkspaces[focusedWorkspaceIndex].idx + 1
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWorkspaceActive(workspaceNumber) {
|
||||||
|
return workspaceNumber === getCurrentWorkspaceNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
// For compatibility with existing components
|
||||||
|
function getCurrentWorkspace() {
|
||||||
|
return getCurrentWorkspaceNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWorkspaceList() {
|
||||||
|
return getCurrentOutputWorkspaceNumbers()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,3 +12,4 @@ singleton SystemMonitorService 1.0 SystemMonitorService.qml
|
|||||||
singleton AppSearchService 1.0 AppSearchService.qml
|
singleton AppSearchService 1.0 AppSearchService.qml
|
||||||
singleton PreferencesService 1.0 PreferencesService.qml
|
singleton PreferencesService 1.0 PreferencesService.qml
|
||||||
singleton LauncherService 1.0 LauncherService.qml
|
singleton LauncherService 1.0 LauncherService.qml
|
||||||
|
singleton NiriWorkspaceService 1.0 NiriWorkspaceService.qml
|
||||||
@@ -242,100 +242,79 @@ EOF`
|
|||||||
radius: Theme.cornerRadiusLarge
|
radius: Theme.cornerRadiusLarge
|
||||||
color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, 0.8)
|
color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, 0.8)
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: NiriWorkspaceService.niriAvailable
|
||||||
|
|
||||||
property int currentWorkspace: 1
|
|
||||||
property var workspaceList: []
|
|
||||||
|
|
||||||
Process {
|
// Use reactive properties from NiriWorkspaceService
|
||||||
id: workspaceQuery
|
property int currentWorkspace: getDisplayActiveWorkspace()
|
||||||
command: ["niri", "msg", "workspaces"]
|
property var workspaceList: getDisplayWorkspaces()
|
||||||
running: true
|
|
||||||
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
if (text && text.trim()) {
|
// Get workspaces for this display
|
||||||
workspaceSwitcher.parseWorkspaceOutput(text.trim())
|
function getDisplayWorkspaces() {
|
||||||
}
|
// Always return something for now, even if service isn't ready
|
||||||
|
if (!NiriWorkspaceService.niriAvailable || NiriWorkspaceService.allWorkspaces.length === 0) {
|
||||||
|
return [1, 2] // Fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!topBar.screenName) {
|
||||||
|
return NiriWorkspaceService.getCurrentOutputWorkspaceNumbers()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter workspaces for this specific display
|
||||||
|
var displayWorkspaces = []
|
||||||
|
for (var i = 0; i < NiriWorkspaceService.allWorkspaces.length; i++) {
|
||||||
|
var ws = NiriWorkspaceService.allWorkspaces[i]
|
||||||
|
if (ws.output === topBar.screenName) {
|
||||||
|
displayWorkspaces.push(ws.idx + 1) // Convert to 1-based
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return displayWorkspaces.length > 0 ? displayWorkspaces : [1, 2]
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseWorkspaceOutput(data) {
|
// Get active workspace for this display
|
||||||
const lines = data.split('\n')
|
function getDisplayActiveWorkspace() {
|
||||||
let currentOutputName = ""
|
// Always return something for now, even if service isn't ready
|
||||||
let focusedOutput = ""
|
if (!NiriWorkspaceService.niriAvailable || NiriWorkspaceService.allWorkspaces.length === 0) {
|
||||||
let focusedWorkspace = 1
|
return 1 // Fallback
|
||||||
let outputWorkspaces = {}
|
}
|
||||||
|
|
||||||
|
if (!topBar.screenName) {
|
||||||
|
return NiriWorkspaceService.getCurrentWorkspaceNumber()
|
||||||
|
}
|
||||||
|
|
||||||
for (const line of lines) {
|
// Find active workspace for this display (is_active, not is_focused)
|
||||||
if (line.startsWith('Output "')) {
|
for (var i = 0; i < NiriWorkspaceService.allWorkspaces.length; i++) {
|
||||||
const outputMatch = line.match(/Output "(.+)"/)
|
var ws = NiriWorkspaceService.allWorkspaces[i]
|
||||||
if (outputMatch) {
|
if (ws.output === topBar.screenName && ws.is_active) {
|
||||||
currentOutputName = outputMatch[1]
|
return ws.idx + 1 // Convert to 1-based
|
||||||
outputWorkspaces[currentOutputName] = []
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.trim() && line.match(/^\s*\*?\s*(\d+)$/)) {
|
|
||||||
const wsMatch = line.match(/^\s*(\*?)\s*(\d+)$/)
|
|
||||||
if (wsMatch) {
|
|
||||||
const isActive = wsMatch[1] === '*'
|
|
||||||
const wsNum = parseInt(wsMatch[2])
|
|
||||||
|
|
||||||
if (currentOutputName && outputWorkspaces[currentOutputName]) {
|
|
||||||
outputWorkspaces[currentOutputName].push(wsNum)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isActive) {
|
|
||||||
focusedOutput = currentOutputName
|
|
||||||
focusedWorkspace = wsNum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show workspaces for THIS screen only
|
return 1
|
||||||
if (topBar.screenName && outputWorkspaces[topBar.screenName]) {
|
|
||||||
workspaceList = outputWorkspaces[topBar.screenName]
|
|
||||||
|
|
||||||
// Always track the active workspace for this display
|
|
||||||
// Parse all lines to find which workspace is active on this display
|
|
||||||
let thisDisplayActiveWorkspace = 1
|
|
||||||
let inThisOutput = false
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.startsWith('Output "')) {
|
|
||||||
const outputMatch = line.match(/Output "(.+)"/)
|
|
||||||
inThisOutput = outputMatch && outputMatch[1] === topBar.screenName
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inThisOutput && line.trim() && line.match(/^\s*\*\s*(\d+)$/)) {
|
|
||||||
const wsMatch = line.match(/^\s*\*\s*(\d+)$/)
|
|
||||||
if (wsMatch) {
|
|
||||||
thisDisplayActiveWorkspace = parseInt(wsMatch[1])
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentWorkspace = thisDisplayActiveWorkspace
|
|
||||||
// console.log("Monitor", topBar.screenName, "active workspace:", thisDisplayActiveWorkspace)
|
|
||||||
} else {
|
|
||||||
// Fallback if screen name not found
|
|
||||||
workspaceList = [1, 2]
|
|
||||||
currentWorkspace = 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
// React to workspace changes
|
||||||
interval: 500
|
Connections {
|
||||||
running: true
|
target: NiriWorkspaceService
|
||||||
repeat: true
|
function onAllWorkspacesChanged() {
|
||||||
onTriggered: {
|
var oldCurrent = workspaceSwitcher.currentWorkspace
|
||||||
workspaceQuery.running = true
|
workspaceSwitcher.workspaceList = workspaceSwitcher.getDisplayWorkspaces()
|
||||||
|
workspaceSwitcher.currentWorkspace = workspaceSwitcher.getDisplayActiveWorkspace()
|
||||||
|
console.log("DEBUG: TopBar onAllWorkspacesChanged for", topBar.screenName, "- current workspace:", oldCurrent, "→", workspaceSwitcher.currentWorkspace)
|
||||||
|
}
|
||||||
|
function onFocusedWorkspaceIndexChanged() {
|
||||||
|
var oldCurrent = workspaceSwitcher.currentWorkspace
|
||||||
|
workspaceSwitcher.currentWorkspace = workspaceSwitcher.getDisplayActiveWorkspace()
|
||||||
|
console.log("DEBUG: TopBar onFocusedWorkspaceIndexChanged for", topBar.screenName, "- current workspace:", oldCurrent, "→", workspaceSwitcher.currentWorkspace)
|
||||||
|
}
|
||||||
|
function onNiriAvailableChanged() {
|
||||||
|
if (NiriWorkspaceService.niriAvailable) {
|
||||||
|
workspaceSwitcher.workspaceList = workspaceSwitcher.getDisplayWorkspaces()
|
||||||
|
workspaceSwitcher.currentWorkspace = workspaceSwitcher.getDisplayActiveWorkspace()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,6 +330,7 @@ EOF`
|
|||||||
property bool isActive: modelData === workspaceSwitcher.currentWorkspace
|
property bool isActive: modelData === workspaceSwitcher.currentWorkspace
|
||||||
property bool isHovered: mouseArea.containsMouse
|
property bool isHovered: mouseArea.containsMouse
|
||||||
|
|
||||||
|
|
||||||
width: isActive ? Theme.spacingXL + Theme.spacingS : Theme.spacingL
|
width: isActive ? Theme.spacingXL + Theme.spacingS : Theme.spacingL
|
||||||
height: Theme.spacingS
|
height: Theme.spacingS
|
||||||
radius: height / 2
|
radius: height / 2
|
||||||
@@ -379,44 +359,18 @@ EOF`
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
// Set target workspace and focus monitor first
|
// Use NiriWorkspaceService for workspace switching
|
||||||
console.log("Clicking workspace", modelData, "on monitor", topBar.screenName)
|
if (NiriWorkspaceService.niriAvailable) {
|
||||||
workspaceSwitcher.targetWorkspace = modelData
|
NiriWorkspaceService.switchToWorkspaceByNumber(modelData)
|
||||||
focusMonitorProcess.command = ["niri", "msg", "action", "focus-monitor", topBar.screenName]
|
} else {
|
||||||
focusMonitorProcess.running = true
|
// Fallback for when service isn't ready
|
||||||
|
Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", modelData.toString()])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
|
||||||
id: switchProcess
|
|
||||||
running: false
|
|
||||||
|
|
||||||
onExited: {
|
|
||||||
// Update current workspace and refresh query
|
|
||||||
workspaceSwitcher.currentWorkspace = workspaceSwitcher.targetWorkspace
|
|
||||||
Qt.callLater(() => {
|
|
||||||
workspaceQuery.running = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: focusMonitorProcess
|
|
||||||
running: false
|
|
||||||
|
|
||||||
onExited: {
|
|
||||||
// After focusing the monitor, switch to the workspace
|
|
||||||
Qt.callLater(() => {
|
|
||||||
switchProcess.command = ["niri", "msg", "action", "focus-workspace", workspaceSwitcher.targetWorkspace.toString()]
|
|
||||||
switchProcess.running = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property int targetWorkspace: 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,120 +2,53 @@ import QtQuick
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
|
import "../Common"
|
||||||
|
import "../Services"
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: workspaceSwitcher
|
id: workspaceSwitcher
|
||||||
|
|
||||||
property var theme
|
width: Math.max(120, workspaceRow.implicitWidth + Theme.spacingL * 2)
|
||||||
property var root
|
|
||||||
|
|
||||||
width: Math.max(120, workspaceRow.implicitWidth + theme.spacingL * 2)
|
|
||||||
height: 32
|
height: 32
|
||||||
radius: theme.cornerRadiusLarge
|
radius: Theme.cornerRadiusLarge
|
||||||
color: Qt.rgba(theme.secondary.r, theme.secondary.g, theme.secondary.b, 0.08)
|
color: Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: NiriWorkspaceService.niriAvailable
|
||||||
|
|
||||||
property int currentWorkspace: 1
|
// Use the reactive workspace service
|
||||||
property var workspaceList: []
|
property int currentWorkspace: NiriWorkspaceService.getCurrentWorkspaceNumber()
|
||||||
|
property var workspaceList: NiriWorkspaceService.getCurrentOutputWorkspaceNumbers()
|
||||||
Process {
|
|
||||||
id: workspaceQuery
|
|
||||||
command: ["niri", "msg", "workspaces"]
|
|
||||||
running: true
|
|
||||||
|
|
||||||
stdout: SplitParser {
|
|
||||||
splitMarker: "\n"
|
|
||||||
onRead: (data) => {
|
|
||||||
if (data.trim()) {
|
|
||||||
workspaceSwitcher.parseWorkspaceOutput(data.trim())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseWorkspaceOutput(data) {
|
|
||||||
const lines = data.split('\n')
|
|
||||||
let currentOutputName = ""
|
|
||||||
let focusedOutput = ""
|
|
||||||
let focusedWorkspace = 1
|
|
||||||
let outputWorkspaces = {}
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.startsWith('Output "')) {
|
|
||||||
const outputMatch = line.match(/Output "(.+)"/)
|
|
||||||
if (outputMatch) {
|
|
||||||
currentOutputName = outputMatch[1]
|
|
||||||
outputWorkspaces[currentOutputName] = []
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.trim() && line.match(/^\s*\*?\s*(\d+)$/)) {
|
|
||||||
const wsMatch = line.match(/^\s*(\*?)\s*(\d+)$/)
|
|
||||||
if (wsMatch) {
|
|
||||||
const isActive = wsMatch[1] === '*'
|
|
||||||
const wsNum = parseInt(wsMatch[2])
|
|
||||||
|
|
||||||
if (currentOutputName && outputWorkspaces[currentOutputName]) {
|
|
||||||
outputWorkspaces[currentOutputName].push(wsNum)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isActive) {
|
|
||||||
focusedOutput = currentOutputName
|
|
||||||
focusedWorkspace = wsNum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentWorkspace = focusedWorkspace
|
|
||||||
|
|
||||||
if (focusedOutput && outputWorkspaces[focusedOutput]) {
|
|
||||||
workspaceList = outputWorkspaces[focusedOutput]
|
|
||||||
} else {
|
|
||||||
workspaceList = [1, 2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
interval: 500
|
|
||||||
running: true
|
|
||||||
repeat: true
|
|
||||||
onTriggered: {
|
|
||||||
workspaceQuery.running = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: workspaceRow
|
id: workspaceRow
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: workspaceSwitcher.workspaceList
|
model: workspaceSwitcher.workspaceList
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
property bool isActive: modelData === workspaceSwitcher.currentWorkspace
|
property bool isActive: NiriWorkspaceService.isWorkspaceActive(modelData)
|
||||||
property bool isHovered: mouseArea.containsMouse
|
property bool isHovered: mouseArea.containsMouse
|
||||||
|
|
||||||
width: isActive ? theme.spacingXL + theme.spacingS : theme.spacingL
|
width: isActive ? Theme.spacingXL + Theme.spacingS : Theme.spacingL
|
||||||
height: theme.spacingS
|
height: Theme.spacingS
|
||||||
radius: height / 2
|
radius: height / 2
|
||||||
color: isActive ? theme.primary :
|
color: isActive ? Theme.primary :
|
||||||
isHovered ? Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.5) :
|
isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5) :
|
||||||
Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.3)
|
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
||||||
|
|
||||||
Behavior on width {
|
Behavior on width {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: theme.mediumDuration
|
duration: Theme.mediumDuration
|
||||||
easing.type: theme.emphasizedEasing
|
easing.type: Theme.emphasizedEasing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
duration: theme.mediumDuration
|
duration: Theme.mediumDuration
|
||||||
easing.type: theme.emphasizedEasing
|
easing.type: Theme.emphasizedEasing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,20 +59,13 @@ Rectangle {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
switchProcess.command = ["niri", "msg", "action", "focus-workspace", modelData.toString()]
|
// Use the service to switch workspaces
|
||||||
switchProcess.running = true
|
// modelData is workspace number (1-based)
|
||||||
workspaceSwitcher.currentWorkspace = modelData
|
NiriWorkspaceService.switchToWorkspaceByNumber(modelData)
|
||||||
Qt.callLater(() => {
|
|
||||||
workspaceQuery.running = true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
|
||||||
id: switchProcess
|
|
||||||
running: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user