mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Compositor: proper monitor scaling detection and display in settings + fixes blurry wallpapers on compositor scaled monitors.
This commit is contained in:
@@ -1394,7 +1394,7 @@
|
||||
"system": {
|
||||
"uptime": "Uptime: {uptime}",
|
||||
"welcome-back": "Welcome back,",
|
||||
"monitor-description": "{model} ({width}x{height})",
|
||||
"monitor-description": "{model} ({width}x{height} @ {scale}x)",
|
||||
"scaling-percentage": "{percentage}%",
|
||||
"location-display": "{name} ({coordinates})",
|
||||
"signal-strength": "{signal}%",
|
||||
|
||||
@@ -44,19 +44,6 @@ Variants {
|
||||
property real fillMode: WallpaperService.getFillModeUniform()
|
||||
property vector4d fillColor: Qt.vector4d(Settings.data.wallpaper.fillColor.r, Settings.data.wallpaper.fillColor.g, Settings.data.wallpaper.fillColor.b, 1.0)
|
||||
|
||||
property int monitoredWidth: modelData.width
|
||||
property int monitoredHeight: modelData.height
|
||||
|
||||
onMonitoredWidthChanged: {
|
||||
Logger.log("Background", "Screen width changed to:", monitoredWidth, "for", modelData.name)
|
||||
recalculateImageSizes()
|
||||
}
|
||||
|
||||
onMonitoredHeightChanged: {
|
||||
Logger.log("Background", "Screen height changed to:", monitoredHeight, "for", modelData.name)
|
||||
recalculateImageSizes()
|
||||
}
|
||||
|
||||
Component.onCompleted: setWallpaperInitial()
|
||||
|
||||
Component.onDestruction: {
|
||||
@@ -87,6 +74,13 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CompositorService
|
||||
function onDisplayScalesChanged() {
|
||||
setWallpaperInitial()
|
||||
}
|
||||
}
|
||||
|
||||
color: Color.transparent
|
||||
screen: modelData
|
||||
WlrLayershell.layer: WlrLayer.Background
|
||||
@@ -122,38 +116,21 @@ Variants {
|
||||
cache: false
|
||||
asynchronous: true
|
||||
sourceSize: undefined
|
||||
|
||||
onStatusChanged: {
|
||||
if (status === Image.Error) {
|
||||
Logger.warn("Current wallpaper failed to load:", source)
|
||||
} else if (status === Image.Ready && !dimensionsCalculated) {
|
||||
dimensionsCalculated = true
|
||||
calculateSourceSize()
|
||||
const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight)
|
||||
if (optimalSize !== false) {
|
||||
sourceSize = optimalSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onSourceChanged: {
|
||||
dimensionsCalculated = false
|
||||
sourceSize = undefined
|
||||
}
|
||||
|
||||
function calculateSourceSize() {
|
||||
if (implicitWidth === modelData.width || implicitHeight === modelData.height) {
|
||||
// Do not resize if one of the dimensions fits perfectly on the screen
|
||||
return
|
||||
}
|
||||
|
||||
if (implicitWidth > 0 && implicitHeight > 0) {
|
||||
const imageAspectRatio = implicitWidth / implicitHeight
|
||||
if (modelData.width >= modelData.height) {
|
||||
const w = Math.min(modelData.width, implicitWidth)
|
||||
sourceSize = Qt.size(w, w / imageAspectRatio)
|
||||
} else {
|
||||
const h = Math.min(modelData.height, implicitHeight)
|
||||
sourceSize = Qt.size(h * imageAspectRatio, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
@@ -168,38 +145,21 @@ Variants {
|
||||
cache: false
|
||||
asynchronous: true
|
||||
sourceSize: undefined
|
||||
|
||||
onStatusChanged: {
|
||||
if (status === Image.Error) {
|
||||
Logger.warn("Next wallpaper failed to load:", source)
|
||||
} else if (status === Image.Ready && !dimensionsCalculated) {
|
||||
dimensionsCalculated = true
|
||||
calculateSourceSize()
|
||||
const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight)
|
||||
if (optimalSize !== false) {
|
||||
sourceSize = optimalSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onSourceChanged: {
|
||||
dimensionsCalculated = false
|
||||
sourceSize = undefined
|
||||
}
|
||||
|
||||
function calculateSourceSize() {
|
||||
if (implicitWidth === modelData.width || implicitHeight === modelData.height) {
|
||||
// Do not resize if one of the dimensions fits perfectly on the screen
|
||||
return
|
||||
}
|
||||
|
||||
if (implicitWidth > 0 && implicitHeight > 0) {
|
||||
const imageAspectRatio = implicitWidth / implicitHeight
|
||||
if (modelData.width >= modelData.height) {
|
||||
const w = Math.min(modelData.width, implicitWidth)
|
||||
sourceSize = Qt.size(w, w / imageAspectRatio)
|
||||
} else {
|
||||
const h = Math.min(modelData.height, implicitHeight)
|
||||
sourceSize = Qt.size(h * imageAspectRatio, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic shader loader - only loads the active transition shader
|
||||
@@ -356,6 +316,31 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
function calculateOptimalWallpaperSize(wpWidth, wpHeight) {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name)
|
||||
const screenWidth = modelData.width * compositorScale
|
||||
const screenHeight = modelData.height * compositorScale
|
||||
if (wpWidth <= screenWidth || wpHeight <= screenHeight || wpWidth <= 0 || wpHeight <= 0) {
|
||||
// Do not resize if wallpaper is smaller than one of the screen dimension
|
||||
return
|
||||
}
|
||||
|
||||
const imageAspectRatio = wpWidth / wpHeight
|
||||
var dim = Qt.size(0, 0)
|
||||
if (screenWidth >= screenHeight) {
|
||||
const w = Math.min(screenWidth, wpWidth)
|
||||
dim = Qt.size(w, w / imageAspectRatio)
|
||||
} else {
|
||||
const h = Math.min(screenHeight, wpHeight)
|
||||
dim = Qt.size(h * imageAspectRatio, h)
|
||||
}
|
||||
|
||||
Logger.log("Background", `Wallpaper resized on ${modelData.name} ${screenWidth}x${screenHeight} @ ${compositorScale}x`, "src:", wpWidth, wpHeight, "dst:", dim.width, dim.height)
|
||||
return dim
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
function recalculateImageSizes() {
|
||||
if (currentWallpaper.status === Image.Ready) {
|
||||
currentWallpaper.calculateSourceSize()
|
||||
@@ -365,6 +350,7 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
function setWallpaperInitial() {
|
||||
// On startup, defer assigning wallpaper until the service cache is ready, retries every tick
|
||||
if (!WallpaperService || !WallpaperService.isInitialized) {
|
||||
@@ -375,6 +361,7 @@ Variants {
|
||||
setWallpaperImmediate(WallpaperService.getWallpaper(modelData.name))
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
function setWallpaperImmediate(source) {
|
||||
transitionAnimation.stop()
|
||||
transitionProgress = 0.0
|
||||
@@ -388,6 +375,7 @@ Variants {
|
||||
})
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
function setWallpaperWithTransition(source) {
|
||||
if (source === currentWallpaper.source) {
|
||||
return
|
||||
@@ -421,6 +409,7 @@ Variants {
|
||||
transitionAnimation.start()
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Main method that actually trigger the wallpaper change
|
||||
function changeWallpaper() {
|
||||
// Get the transitionType from the settings
|
||||
|
||||
@@ -90,11 +90,15 @@ ColumnLayout {
|
||||
|
||||
NLabel {
|
||||
label: modelData.name || "Unknown"
|
||||
description: I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width,
|
||||
"height": modelData.height
|
||||
})
|
||||
description: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name)
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Scale
|
||||
|
||||
@@ -2,6 +2,7 @@ pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
|
||||
@@ -18,6 +19,10 @@ Singleton {
|
||||
property ListModel windows: ListModel {}
|
||||
property int focusedWindowIndex: -1
|
||||
|
||||
// Display scale data
|
||||
property var displayScales: ({})
|
||||
property bool displayScalesLoaded: false
|
||||
|
||||
// Generic events
|
||||
signal workspaceChanged
|
||||
signal activeWindowChanged
|
||||
@@ -26,7 +31,18 @@ Singleton {
|
||||
// Backend service loader
|
||||
property var backend: null
|
||||
|
||||
// Cache file path
|
||||
property string displayCachePath: ""
|
||||
|
||||
Component.onCompleted: {
|
||||
// Setup cache path (needs Settings to be available)
|
||||
Qt.callLater(() => {
|
||||
if (typeof Settings !== 'undefined' && Settings.cacheDir) {
|
||||
displayCachePath = Settings.cacheDir + "display.json"
|
||||
displayCacheFileView.path = displayCachePath
|
||||
}
|
||||
})
|
||||
|
||||
detectCompositor()
|
||||
}
|
||||
|
||||
@@ -69,6 +85,31 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
// Cache FileView for display scales
|
||||
FileView {
|
||||
id: displayCacheFileView
|
||||
printErrors: false
|
||||
watchChanges: false
|
||||
|
||||
adapter: JsonAdapter {
|
||||
id: displayCacheAdapter
|
||||
property var displays: ({})
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
// Load cached display scales
|
||||
displayScales = displayCacheAdapter.displays || {}
|
||||
displayScalesLoaded = true
|
||||
// Logger.log("CompositorService", "Loaded display scales from cache:", JSON.stringify(displayScales))
|
||||
}
|
||||
|
||||
onLoadFailed: {
|
||||
// Cache doesn't exist yet, will be created on first update
|
||||
displayScalesLoaded = true
|
||||
// Logger.log("CompositorService", "No display cache found, will create on first update")
|
||||
}
|
||||
}
|
||||
|
||||
// Hyprland backend component
|
||||
Component {
|
||||
id: hyprlandComponent
|
||||
@@ -151,6 +192,50 @@ Singleton {
|
||||
windowListChanged()
|
||||
}
|
||||
|
||||
// Update display scales from backend
|
||||
function updateDisplayScales() {
|
||||
if (!backend || !backend.queryDisplayScales) {
|
||||
Logger.warn("CompositorService", "Backend does not support display scale queries")
|
||||
return
|
||||
}
|
||||
|
||||
backend.queryDisplayScales()
|
||||
}
|
||||
|
||||
// Called by backend when display scales are ready
|
||||
function onDisplayScalesUpdated(scales) {
|
||||
displayScales = scales
|
||||
saveDisplayScalesToCache()
|
||||
displayScalesChanged()
|
||||
Logger.log("CompositorService", "Display scales updated")
|
||||
}
|
||||
|
||||
// Save display scales to cache
|
||||
function saveDisplayScalesToCache() {
|
||||
if (!displayCachePath) {
|
||||
return
|
||||
}
|
||||
|
||||
displayCacheAdapter.displays = displayScales
|
||||
displayCacheFileView.writeAdapter()
|
||||
}
|
||||
|
||||
// Public function to get scale for a specific display
|
||||
function getDisplayScale(displayName) {
|
||||
if (!displayName || !displayScales[displayName]) {
|
||||
return 1.0
|
||||
}
|
||||
return displayScales[displayName].scale || 1.0
|
||||
}
|
||||
|
||||
// Public function to get all display info for a specific display
|
||||
function getDisplayInfo(displayName) {
|
||||
if (!displayName || !displayScales[displayName]) {
|
||||
return null
|
||||
}
|
||||
return displayScales[displayName]
|
||||
}
|
||||
|
||||
// Get focused window
|
||||
function getFocusedWindow() {
|
||||
if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.count) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
|
||||
Item {
|
||||
@@ -15,6 +16,7 @@ Item {
|
||||
signal workspaceChanged
|
||||
signal activeWindowChanged
|
||||
signal windowListChanged
|
||||
signal displayScalesChanged
|
||||
|
||||
// Hyprland-specific properties
|
||||
property bool initialized: false
|
||||
@@ -40,6 +42,7 @@ Item {
|
||||
Qt.callLater(() => {
|
||||
safeUpdateWorkspaces()
|
||||
safeUpdateWindows()
|
||||
queryDisplayScales()
|
||||
})
|
||||
initialized = true
|
||||
Logger.log("HyprlandService", "Initialized successfully")
|
||||
@@ -48,6 +51,67 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Query display scales
|
||||
function queryDisplayScales() {
|
||||
hyprlandMonitorsProcess.running = true
|
||||
}
|
||||
|
||||
// Hyprland monitors process for display scale detection
|
||||
// Hyprland monitors process for display scale detection
|
||||
Process {
|
||||
id: hyprlandMonitorsProcess
|
||||
running: false
|
||||
command: ["hyprctl", "monitors", "-j"]
|
||||
|
||||
property string accumulatedOutput: ""
|
||||
|
||||
stdout: SplitParser {
|
||||
onRead: function (line) {
|
||||
// Accumulate lines instead of parsing each one
|
||||
hyprlandMonitorsProcess.accumulatedOutput += line
|
||||
}
|
||||
}
|
||||
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode !== 0 || !accumulatedOutput) {
|
||||
Logger.error("HyprlandService", "Failed to query monitors, exit code:", exitCode)
|
||||
accumulatedOutput = ""
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const monitorsData = JSON.parse(accumulatedOutput)
|
||||
const scales = {}
|
||||
|
||||
for (const monitor of monitorsData) {
|
||||
if (monitor.name) {
|
||||
scales[monitor.name] = {
|
||||
"name": monitor.name,
|
||||
"scale": monitor.scale || 1.0,
|
||||
"width": monitor.width || 0,
|
||||
"height": monitor.height || 0,
|
||||
"refresh_rate": monitor.refreshRate || 0,
|
||||
"x": monitor.x || 0,
|
||||
"y": monitor.y || 0,
|
||||
"active_workspace": monitor.activeWorkspace ? monitor.activeWorkspace.id : -1,
|
||||
"vrr": monitor.vrr || false,
|
||||
"focused": monitor.focused || false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify CompositorService (it will emit displayScalesChanged)
|
||||
if (CompositorService && CompositorService.onDisplayScalesUpdated) {
|
||||
CompositorService.onDisplayScalesUpdated(scales)
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error("HyprlandService", "Failed to parse monitors:", e)
|
||||
} finally {
|
||||
// Clear accumulated output for next query
|
||||
accumulatedOutput = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
// Safe update wrapper
|
||||
function safeUpdate() {
|
||||
safeUpdateWindows()
|
||||
@@ -188,7 +252,7 @@ Item {
|
||||
"id": windowId,
|
||||
"title": title,
|
||||
"appId": appId,
|
||||
"workspaceId": wsId,
|
||||
"workspaceId": wsId || -1,
|
||||
"isFocused": focused,
|
||||
"output": output
|
||||
}
|
||||
@@ -268,6 +332,11 @@ Item {
|
||||
safeUpdateWorkspaces()
|
||||
workspaceChanged()
|
||||
updateTimer.restart()
|
||||
|
||||
const monitorsEvents = ["configreloaded", "monitoradded", "monitorremoved", "monitoraddedv2", "monitorremovedv2"]
|
||||
if (monitorsEvents.includes(event.name)) {
|
||||
Qt.callLater(queryDisplayScales)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,12 +20,14 @@ Item {
|
||||
signal workspaceChanged
|
||||
signal activeWindowChanged
|
||||
signal windowListChanged
|
||||
signal displayScalesChanged
|
||||
|
||||
// Initialization
|
||||
function initialize() {
|
||||
niriEventStream.running = true
|
||||
updateWorkspaces()
|
||||
updateWindows()
|
||||
queryDisplayScales()
|
||||
Logger.log("NiriService", "Initialized successfully")
|
||||
}
|
||||
|
||||
@@ -39,6 +41,60 @@ Item {
|
||||
niriWindowsProcess.running = true
|
||||
}
|
||||
|
||||
// Query display scales
|
||||
function queryDisplayScales() {
|
||||
niriOutputsProcess.running = true
|
||||
}
|
||||
|
||||
// Niri outputs process for display scale detection
|
||||
Process {
|
||||
id: niriOutputsProcess
|
||||
running: false
|
||||
command: ["niri", "msg", "--json", "outputs"]
|
||||
|
||||
stdout: SplitParser {
|
||||
onRead: function (line) {
|
||||
try {
|
||||
const outputsData = JSON.parse(line)
|
||||
const scales = {}
|
||||
|
||||
// Niri returns an object with display names as keys
|
||||
for (const outputName in outputsData) {
|
||||
const output = outputsData[outputName]
|
||||
if (output && output.name) {
|
||||
const logical = output.logical || {}
|
||||
const currentModeIdx = output.current_mode || 0
|
||||
const modes = output.modes || []
|
||||
const currentMode = modes[currentModeIdx] || {}
|
||||
|
||||
scales[output.name] = {
|
||||
"name": output.name,
|
||||
"scale": logical.scale || 1.0,
|
||||
"width": logical.width || 0,
|
||||
"height": logical.height || 0,
|
||||
"x": logical.x || 0,
|
||||
"y": logical.y || 0,
|
||||
"physical_width": (output.physical_size && output.physical_size[0]) || 0,
|
||||
"physical_height": (output.physical_size && output.physical_size[1]) || 0,
|
||||
"refresh_rate": currentMode.refresh_rate || 0,
|
||||
"vrr_supported": output.vrr_supported || false,
|
||||
"vrr_enabled": output.vrr_enabled || false,
|
||||
"transform": logical.transform || "Normal"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify CompositorService (it will emit displayScalesChanged)
|
||||
if (CompositorService && CompositorService.onDisplayScalesUpdated) {
|
||||
CompositorService.onDisplayScalesUpdated(scales)
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error("NiriService", "Failed to parse outputs:", e, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Niri workspace process
|
||||
Process {
|
||||
id: niriWorkspaceProcess
|
||||
@@ -86,7 +142,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Niri windows process (for initial load)
|
||||
// Niri windows process
|
||||
Process {
|
||||
id: niriWindowsProcess
|
||||
running: false
|
||||
@@ -131,6 +187,10 @@ Item {
|
||||
handleWindowLayoutsChanged(event.WindowLayoutsChanged)
|
||||
} else if (event.OverviewOpenedOrClosed) {
|
||||
handleOverviewOpenedOrClosed(event.OverviewOpenedOrClosed)
|
||||
} else if (event.OutputsChanged) {
|
||||
queryDisplayScales()
|
||||
} else if (event.ConfigLoaded) {
|
||||
queryDisplayScales()
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error("NiriService", "Error parsing event stream:", e, data)
|
||||
|
||||
@@ -2,6 +2,7 @@ import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.I3
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
|
||||
Item {
|
||||
@@ -16,6 +17,7 @@ Item {
|
||||
signal workspaceChanged
|
||||
signal activeWindowChanged
|
||||
signal windowListChanged
|
||||
signal displayScalesChanged
|
||||
|
||||
// I3-specific properties
|
||||
property bool initialized: false
|
||||
@@ -38,6 +40,7 @@ Item {
|
||||
Qt.callLater(() => {
|
||||
safeUpdateWorkspaces()
|
||||
safeUpdateWindows()
|
||||
queryDisplayScales()
|
||||
})
|
||||
initialized = true
|
||||
Logger.log("SwayService", "Initialized successfully")
|
||||
@@ -46,6 +49,66 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// 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.error("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.error("SwayService", "Failed to parse outputs:", e)
|
||||
} finally {
|
||||
// Clear accumulated output for next query
|
||||
accumulatedOutput = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Safe update wrapper
|
||||
function safeUpdate() {
|
||||
safeUpdateWindows()
|
||||
@@ -197,6 +260,10 @@ Item {
|
||||
safeUpdateWorkspaces()
|
||||
workspaceChanged()
|
||||
updateTimer.restart()
|
||||
|
||||
if (event.type === "output") {
|
||||
Qt.callLater(queryDisplayScales)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user