mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-08-05 05:48:08 +00:00
302 lines
7.0 KiB
QML
302 lines
7.0 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.I3
|
|
import Quickshell.Wayland
|
|
import Quickshell.Io
|
|
import qs.Commons
|
|
|
|
Item {
|
|
id: root
|
|
|
|
// Properties that match the facade interface
|
|
property ListModel workspaces: ListModel {}
|
|
property var windows: []
|
|
property int focusedWindowIndex: -1
|
|
|
|
// Signals that match the facade interface
|
|
signal workspaceChanged
|
|
signal activeWindowChanged
|
|
signal windowListChanged
|
|
signal displayScalesChanged
|
|
|
|
// I3-specific properties
|
|
property bool initialized: false
|
|
|
|
// Debounce timer for updates
|
|
Timer {
|
|
id: updateTimer
|
|
interval: 50
|
|
repeat: false
|
|
onTriggered: safeUpdate()
|
|
}
|
|
|
|
// Initialization
|
|
function initialize() {
|
|
if (initialized)
|
|
return
|
|
|
|
try {
|
|
I3.refreshWorkspaces()
|
|
Qt.callLater(() => {
|
|
safeUpdateWorkspaces()
|
|
safeUpdateWindows()
|
|
queryDisplayScales()
|
|
})
|
|
initialized = true
|
|
Logger.i("SwayService", "Initialized successfully")
|
|
} catch (e) {
|
|
Logger.e("SwayService", "Failed to initialize:", e)
|
|
}
|
|
}
|
|
|
|
// Query display scales
|
|
function queryDisplayScales() {
|
|
swayOutputsProcess.running = true
|
|
}
|
|
|
|
// Sway outputs process for display scale detection
|
|
Process {
|
|
id: swayOutputsProcess
|
|
running: false
|
|
command: ["swaymsg", "-t", "get_outputs", "-r"]
|
|
|
|
property string accumulatedOutput: ""
|
|
|
|
stdout: SplitParser {
|
|
onRead: function (line) {
|
|
swayOutputsProcess.accumulatedOutput += line
|
|
}
|
|
}
|
|
|
|
onExited: function (exitCode) {
|
|
if (exitCode !== 0 || !accumulatedOutput) {
|
|
Logger.e("SwayService", "Failed to query outputs, exit code:", exitCode)
|
|
accumulatedOutput = ""
|
|
return
|
|
}
|
|
|
|
try {
|
|
const outputsData = JSON.parse(accumulatedOutput)
|
|
const scales = {}
|
|
|
|
for (const output of outputsData) {
|
|
if (output.name) {
|
|
scales[output.name] = {
|
|
"name": output.name,
|
|
"scale": output.scale || 1.0,
|
|
"width": output.current_mode ? output.current_mode.width : 0,
|
|
"height": output.current_mode ? output.current_mode.height : 0,
|
|
"refresh_rate": output.current_mode ? output.current_mode.refresh : 0,
|
|
"x": output.rect ? output.rect.x : 0,
|
|
"y": output.rect ? output.rect.y : 0,
|
|
"active": output.active || false,
|
|
"focused": output.focused || false,
|
|
"current_workspace": output.current_workspace || ""
|
|
}
|
|
}
|
|
}
|
|
|
|
// Notify CompositorService (it will emit displayScalesChanged)
|
|
if (CompositorService && CompositorService.onDisplayScalesUpdated) {
|
|
CompositorService.onDisplayScalesUpdated(scales)
|
|
}
|
|
} catch (e) {
|
|
Logger.e("SwayService", "Failed to parse outputs:", e)
|
|
} finally {
|
|
// Clear accumulated output for next query
|
|
accumulatedOutput = ""
|
|
}
|
|
}
|
|
}
|
|
|
|
// Safe update wrapper
|
|
function safeUpdate() {
|
|
safeUpdateWindows()
|
|
safeUpdateWorkspaces()
|
|
windowListChanged()
|
|
}
|
|
|
|
// Safe workspace update
|
|
function safeUpdateWorkspaces() {
|
|
try {
|
|
workspaces.clear()
|
|
|
|
if (!I3.workspaces || !I3.workspaces.values) {
|
|
return
|
|
}
|
|
|
|
const hlWorkspaces = I3.workspaces.values
|
|
|
|
for (var i = 0; i < hlWorkspaces.length; i++) {
|
|
const ws = hlWorkspaces[i]
|
|
if (!ws || ws.id < 1)
|
|
continue
|
|
|
|
const wsData = {
|
|
"id": i,
|
|
"idx": ws.num,
|
|
"name": ws.name || "",
|
|
"output": (ws.monitor && ws.monitor.name) ? ws.monitor.name : "",
|
|
"isActive": ws.active === true,
|
|
"isFocused": ws.focused === true,
|
|
"isUrgent": ws.urgent === true,
|
|
"isOccupied": true,
|
|
"handle": ws
|
|
}
|
|
|
|
workspaces.append(wsData)
|
|
}
|
|
} catch (e) {
|
|
Logger.e("SwayService", "Error updating workspaces:", e)
|
|
}
|
|
}
|
|
|
|
// Safe window update
|
|
function safeUpdateWindows() {
|
|
try {
|
|
const windowsList = []
|
|
|
|
if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values) {
|
|
windows = []
|
|
focusedWindowIndex = -1
|
|
return
|
|
}
|
|
|
|
const hlToplevels = ToplevelManager.toplevels.values
|
|
let newFocusedIndex = -1
|
|
|
|
for (var i = 0; i < hlToplevels.length; i++) {
|
|
const toplevel = hlToplevels[i]
|
|
if (!toplevel)
|
|
continue
|
|
|
|
const windowData = extractWindowData(toplevel)
|
|
if (windowData) {
|
|
windowsList.push(windowData)
|
|
|
|
if (windowData.isFocused) {
|
|
newFocusedIndex = windowsList.length - 1
|
|
}
|
|
}
|
|
}
|
|
|
|
windows = windowsList
|
|
|
|
if (newFocusedIndex !== focusedWindowIndex) {
|
|
focusedWindowIndex = newFocusedIndex
|
|
activeWindowChanged()
|
|
}
|
|
} catch (e) {
|
|
Logger.e("SwayService", "Error updating windows:", e)
|
|
}
|
|
}
|
|
|
|
// Extract window data safely from a toplevel
|
|
function extractWindowData(toplevel) {
|
|
if (!toplevel)
|
|
return null
|
|
|
|
try {
|
|
// Safely extract properties
|
|
const appId = getAppId(toplevel)
|
|
const title = safeGetProperty(toplevel, "title", "")
|
|
const focused = toplevel.activated === true
|
|
|
|
return {
|
|
"title": title,
|
|
"appId": appId,
|
|
"isFocused": focused,
|
|
"handle": toplevel
|
|
}
|
|
} catch (e) {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function getAppId(toplevel) {
|
|
if (!toplevel)
|
|
return ""
|
|
|
|
return toplevel.appId
|
|
}
|
|
|
|
// Safe property getter
|
|
function safeGetProperty(obj, prop, defaultValue) {
|
|
try {
|
|
const value = obj[prop]
|
|
if (value !== undefined && value !== null) {
|
|
return String(value)
|
|
}
|
|
} catch (e) {
|
|
|
|
// Property access failed
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
// Connections to I3
|
|
Connections {
|
|
target: I3.workspaces
|
|
enabled: initialized
|
|
function onValuesChanged() {
|
|
safeUpdateWorkspaces()
|
|
workspaceChanged()
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: ToplevelManager
|
|
enabled: initialized
|
|
function onActiveToplevelChanged() {
|
|
updateTimer.restart()
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: I3
|
|
enabled: initialized
|
|
function onRawEvent(event) {
|
|
safeUpdateWorkspaces()
|
|
workspaceChanged()
|
|
updateTimer.restart()
|
|
|
|
if (event.type === "output") {
|
|
Qt.callLater(queryDisplayScales)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Public functions
|
|
function switchToWorkspace(workspace) {
|
|
try {
|
|
workspace.handle.activate()
|
|
} catch (e) {
|
|
Logger.e("SwayService", "Failed to switch workspace:", e)
|
|
}
|
|
}
|
|
|
|
function focusWindow(window) {
|
|
try {
|
|
window.handle.activate()
|
|
} catch (e) {
|
|
Logger.e("SwayService", "Failed to switch window:", e)
|
|
}
|
|
}
|
|
|
|
function closeWindow(window) {
|
|
try {
|
|
window.handle.close()
|
|
} catch (e) {
|
|
Logger.e("SwayService", "Failed to close window:", e)
|
|
}
|
|
}
|
|
|
|
function logout() {
|
|
try {
|
|
Quickshell.execDetached(["swaymsg", "exit"])
|
|
} catch (e) {
|
|
Logger.e("SwayService", "Failed to logout:", e)
|
|
}
|
|
}
|
|
}
|