feat: scrollble wallpaper
This commit is contained in:
@@ -43,9 +43,16 @@ layout {
|
||||
left 2
|
||||
}
|
||||
|
||||
background-color "#1e1e2e"
|
||||
background-color "transparent"
|
||||
}
|
||||
|
||||
overview {
|
||||
backdrop-color "#1e1e2e"
|
||||
|
||||
workspace-shadow {
|
||||
off
|
||||
}
|
||||
}
|
||||
|
||||
// Disable the "Important Hotkeys" pop-up at startup.
|
||||
hotkey-overlay {
|
||||
@@ -57,6 +64,11 @@ prefer-no-csd
|
||||
animations {
|
||||
// off
|
||||
// slowdown 3.0
|
||||
|
||||
workspace-switch {
|
||||
duration-ms 200
|
||||
curve "ease-out-cubic"
|
||||
}
|
||||
}
|
||||
|
||||
layer-rule {
|
||||
|
||||
@@ -92,10 +92,11 @@ Singleton {
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ShellState
|
||||
onColorStateChanged: {
|
||||
function onColorStateChanged() {
|
||||
reloadTimer.restart();
|
||||
}
|
||||
|
||||
target: ShellState
|
||||
}
|
||||
|
||||
Timer {
|
||||
|
||||
@@ -3,6 +3,7 @@ import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Constants
|
||||
import qs.Modules.Background
|
||||
import qs.Services
|
||||
|
||||
Variants {
|
||||
@@ -17,124 +18,9 @@ Variants {
|
||||
readonly property real blurPercentage: BackgroundService.blurPercentage
|
||||
readonly property real blurRadius: BackgroundService.blurRadius
|
||||
|
||||
PanelWindow {
|
||||
id: bgWindow
|
||||
|
||||
readonly property bool doBlur: BarService.focusMode && !BackgroundService.inPreviewMode
|
||||
readonly property string imagePath: BackgroundService.displayPath
|
||||
|
||||
screen: modelData
|
||||
WlrLayershell.namespace: "quickshell-background"
|
||||
WlrLayershell.layer: WlrLayer.Background
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Colors.mSurface
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Item {
|
||||
id: bgManager
|
||||
|
||||
property string activeSource: bgWindow.imagePath
|
||||
property bool showFirst: true
|
||||
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
onActiveSourceChanged: {
|
||||
showFirst = !showFirst;
|
||||
if (showFirst)
|
||||
bgImg1.source = activeSource;
|
||||
else
|
||||
bgImg2.source = activeSource;
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (showFirst)
|
||||
bgImg1.source = activeSource;
|
||||
else
|
||||
bgImg2.source = activeSource;
|
||||
}
|
||||
|
||||
Image {
|
||||
id: bgImg1
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: (bgManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Image {
|
||||
id: bgImg2
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: (!bgManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
source: bgManager
|
||||
anchors.fill: bgManager
|
||||
colorizationColor: root.tintColor
|
||||
colorization: bgWindow.doBlur ? root.tintOpacity : 0
|
||||
blurEnabled: true
|
||||
blur: bgWindow.doBlur ? root.blurPercentage : 0
|
||||
blurMax: root.blurRadius
|
||||
|
||||
Behavior on blur {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on colorization {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: bdWindow
|
||||
|
||||
property bool doBlur: true
|
||||
property string imagePath: BackgroundService.displayPath
|
||||
|
||||
screen: modelData
|
||||
WlrLayershell.namespace: "quickshell-backdrop"
|
||||
WlrLayershell.layer: WlrLayer.Background
|
||||
@@ -147,96 +33,10 @@ Variants {
|
||||
right: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Colors.mSurface
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Item {
|
||||
id: backdropManager
|
||||
|
||||
property string activeSource: bdWindow.imagePath
|
||||
property bool showFirst: true
|
||||
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
onActiveSourceChanged: {
|
||||
showFirst = !showFirst;
|
||||
if (showFirst)
|
||||
bdImg1.source = activeSource;
|
||||
else
|
||||
bdImg2.source = activeSource;
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (showFirst)
|
||||
bdImg1.source = activeSource;
|
||||
else
|
||||
bdImg2.source = activeSource;
|
||||
}
|
||||
|
||||
Image {
|
||||
id: bdImg1
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: (backdropManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Image {
|
||||
id: bdImg2
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: (!backdropManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
source: backdropManager
|
||||
anchors.fill: backdropManager
|
||||
colorizationColor: root.tintColor
|
||||
colorization: bdWindow.doBlur ? root.tintOpacity : 0
|
||||
blurEnabled: true
|
||||
blur: bdWindow.doBlur ? root.blurPercentage : 0
|
||||
blurMax: root.blurRadius
|
||||
|
||||
Behavior on blur {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on colorization {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
ScrollBackground {
|
||||
id: scrollBg
|
||||
|
||||
screen: modelData
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import QtQuick
|
||||
import qs.Constants
|
||||
import qs.Utils
|
||||
|
||||
Image {
|
||||
id: root
|
||||
|
||||
required property real viewportHeight
|
||||
required property real scrollProgress
|
||||
required property bool isOverview
|
||||
required property real centerCropRatio
|
||||
property bool isActive: true
|
||||
readonly property real clampedCenterCropRatio: Math.max(0.01, Math.min(1, centerCropRatio))
|
||||
property real imageScale: isOverview ? 1 : 1 / clampedCenterCropRatio
|
||||
readonly property real imageSourceWidth: sourceSize.width > 0 ? sourceSize.width : implicitWidth
|
||||
readonly property real imageSourceHeight: sourceSize.height > 0 ? sourceSize.height : implicitHeight
|
||||
readonly property real imageScaledHeight: {
|
||||
if (imageSourceWidth <= 0 || imageSourceHeight <= 0 || width <= 0)
|
||||
return 0;
|
||||
|
||||
return Math.max(viewportHeight, width * imageSourceHeight / imageSourceWidth);
|
||||
}
|
||||
readonly property real effectiveScaledHeight: imageScaledHeight * imageScale
|
||||
readonly property real initialYOffset: (imageScale - 1) * imageScaledHeight / 2
|
||||
readonly property real maxScrollOffset: Math.max(0, effectiveScaledHeight - viewportHeight)
|
||||
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
width: parent.width
|
||||
height: imageScaledHeight
|
||||
transformOrigin: Item.Center
|
||||
scale: imageScale
|
||||
y: initialYOffset - maxScrollOffset * scrollProgress
|
||||
|
||||
Connections {
|
||||
function onStatusChanged() {
|
||||
if (status === Image.Ready && isActive)
|
||||
opacity = 1;
|
||||
else
|
||||
opacity = 0;
|
||||
}
|
||||
|
||||
function onIsActiveChanged() {
|
||||
deleteTimer.stop();
|
||||
if (isActive && status === Image.Ready) {
|
||||
opacity = 1;
|
||||
} else {
|
||||
opacity = 0;
|
||||
if (!isActive)
|
||||
deleteTimer.start();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
target: root
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: deleteTimer
|
||||
|
||||
function onTriggered() {
|
||||
root.source = "";
|
||||
}
|
||||
|
||||
interval: Style.animationNormal + 100
|
||||
running: false
|
||||
repeat: false
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import qs.Constants
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property ShellScreen screen
|
||||
readonly property bool doBlur: BarService.focusMode && !BackgroundService.inPreviewMode
|
||||
readonly property bool isOverview: Niri.overviewActive
|
||||
readonly property real centerCropRatio: 0.9
|
||||
readonly property color tintColor: BackgroundService.tintColor
|
||||
readonly property real tintOpacity: BackgroundService.tintOpacity
|
||||
readonly property real blurPercentage: BackgroundService.blurPercentage
|
||||
readonly property real blurRadius: BackgroundService.blurRadius
|
||||
property string imagePath: ""
|
||||
property bool showFirst: true
|
||||
property int outputHeight: 0
|
||||
property int workspaceCount: 0
|
||||
property int focusedIndex: -1
|
||||
readonly property real viewportHeight: outputHeight > 0 ? outputHeight : height
|
||||
readonly property real scrollProgress: {
|
||||
if (workspaceCount <= 1 || focusedIndex < 0)
|
||||
return 0;
|
||||
|
||||
return Math.max(0, Math.min(1, (focusedIndex - 1) / (workspaceCount - 1)));
|
||||
}
|
||||
|
||||
function updateOutput() {
|
||||
const outputInfo = Niri.outputCache[screen.name];
|
||||
if (!outputInfo)
|
||||
return ;
|
||||
|
||||
outputHeight = outputInfo.height;
|
||||
}
|
||||
|
||||
function updateWorkspaces() {
|
||||
let count = 0;
|
||||
for (let i = 0; i < Niri.workspaces.count; i++) {
|
||||
const ws = Niri.workspaces.get(i);
|
||||
if (ws.output.toLowerCase() === screen.name.toLowerCase())
|
||||
count++;
|
||||
|
||||
}
|
||||
workspaceCount = count;
|
||||
updateFocusedIndex();
|
||||
}
|
||||
|
||||
function updateFocusedIndex() {
|
||||
const focusedWorkspaceId = Niri.focusedWorkspaceId;
|
||||
if (focusedWorkspaceId === -1)
|
||||
return ;
|
||||
|
||||
const ws = Niri.workspaceCache[focusedWorkspaceId];
|
||||
if (!ws)
|
||||
return ;
|
||||
|
||||
if (ws.output.toLowerCase() !== screen.name.toLowerCase())
|
||||
return ;
|
||||
|
||||
focusedIndex = ws.idx;
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
Component.onCompleted: {
|
||||
updateOutput();
|
||||
updateWorkspaces();
|
||||
}
|
||||
|
||||
Connections {
|
||||
function updateDisplay() {
|
||||
showFirst = !showFirst;
|
||||
if (showFirst)
|
||||
bgImg1.source = imagePath;
|
||||
else
|
||||
bgImg2.source = imagePath;
|
||||
}
|
||||
|
||||
function onDisplayPathChanged() {
|
||||
imagePath = BackgroundService.displayPath;
|
||||
Qt.callLater(updateDisplay);
|
||||
}
|
||||
|
||||
target: BackgroundService
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onOutputsChanged() {
|
||||
updateOutput();
|
||||
}
|
||||
|
||||
function onWorkspaceChanged() {
|
||||
updateWorkspaces();
|
||||
}
|
||||
|
||||
function onFocusedWorkspaceIdChanged() {
|
||||
updateFocusedIndex();
|
||||
}
|
||||
|
||||
target: Niri
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bgContainer
|
||||
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
color: Colors.mSurface
|
||||
|
||||
BackgroundImage {
|
||||
id: bgImg1
|
||||
|
||||
viewportHeight: root.viewportHeight
|
||||
scrollProgress: root.scrollProgress
|
||||
isOverview: root.isOverview
|
||||
centerCropRatio: root.centerCropRatio
|
||||
isActive: root.showFirst
|
||||
}
|
||||
|
||||
BackgroundImage {
|
||||
id: bgImg2
|
||||
|
||||
viewportHeight: root.viewportHeight
|
||||
scrollProgress: root.scrollProgress
|
||||
isOverview: root.isOverview
|
||||
centerCropRatio: root.centerCropRatio
|
||||
isActive: !root.showFirst
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
source: bgContainer
|
||||
anchors.fill: bgContainer
|
||||
colorizationColor: tintColor
|
||||
colorization: doBlur ? tintOpacity : 0
|
||||
blurEnabled: true
|
||||
blur: doBlur ? blurPercentage : 0
|
||||
blurMax: blurRadius
|
||||
|
||||
Behavior on blur {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on colorization {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -55,6 +55,20 @@ Item {
|
||||
|
||||
}
|
||||
|
||||
function syncWorkspaceFocus() {
|
||||
for (let i = 0; i < localWorkspaces.count; i++) {
|
||||
const ws = localWorkspaces.get(i);
|
||||
const wsData = Niri.workspaceCache[ws.id];
|
||||
if (!wsData) {
|
||||
localWorkspaces.setProperty(i, "isFocused", false);
|
||||
localWorkspaces.setProperty(i, "isActive", false);
|
||||
} else {
|
||||
localWorkspaces.setProperty(i, "isFocused", wsData.isFocused);
|
||||
localWorkspaces.setProperty(i, "isActive", wsData.isActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
implicitWidth: pillRow.implicitWidth + horizontalPadding * 2
|
||||
Component.onCompleted: syncWorkspaces()
|
||||
|
||||
@@ -63,6 +77,10 @@ Item {
|
||||
syncWorkspaces();
|
||||
}
|
||||
|
||||
function onFocusedWorkspaceIdChanged() {
|
||||
syncWorkspaceFocus();
|
||||
}
|
||||
|
||||
target: Niri
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Singleton {
|
||||
id: root
|
||||
|
||||
readonly property string backgroundWidth: "2560"
|
||||
readonly property string backgroundHeight: "1440"
|
||||
readonly property string backgroundHeight: "1600"
|
||||
property string cachedPath: ""
|
||||
property string previewPath: ""
|
||||
property string displayPath: ""
|
||||
|
||||
@@ -77,7 +77,7 @@ Singleton {
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// Public API: Get Large Image (scaled to specified dimensions)
|
||||
// Public API: Get Large Image (scaled by max width, preserve aspect ratio)
|
||||
// -------------------------------------------------
|
||||
function getLarge(sourcePath, width, height, callback) {
|
||||
if (!sourcePath || sourcePath === "") {
|
||||
@@ -89,28 +89,29 @@ Singleton {
|
||||
callback(sourcePath, false);
|
||||
return ;
|
||||
}
|
||||
// Fast dimension check - skip processing if image fits screen AND format is Qt-native
|
||||
if (width <= 0) {
|
||||
callback(sourcePath, false);
|
||||
return ;
|
||||
}
|
||||
const shouldCropToAspect = height > 0;
|
||||
getImageDimensions(sourcePath, function(imgWidth, imgHeight) {
|
||||
// const fitsScreen = imgWidth > 0 && imgHeight > 0 && imgWidth <= width && imgHeight <= height;
|
||||
// if (fitsScreen) {
|
||||
// // Only skip if format is natively supported by Qt
|
||||
// if (!needsConversion(sourcePath)) {
|
||||
// Logger.d("ImageCache", `Image ${imgWidth}x${imgHeight} fits screen ${width}x${height}, using original`);
|
||||
// callback(sourcePath, false);
|
||||
// return ;
|
||||
// }
|
||||
// Logger.d("ImageCache", `Image needs conversion despite fitting screen`);
|
||||
// }
|
||||
// Use actual image dimensions if it fits (convert without upscaling), otherwise use screen dimensions
|
||||
// const targetWidth = fitsScreen ? imgWidth : width;
|
||||
// const targetHeight = fitsScreen ? imgHeight : height;
|
||||
const targetWidth = width;
|
||||
const targetHeight = height;
|
||||
const sourceWidth = imgWidth > 0 ? imgWidth : width;
|
||||
const targetWidth = Math.min(sourceWidth, width);
|
||||
let cropWidth = 0;
|
||||
let cropHeight = 0;
|
||||
if (shouldCropToAspect && imgWidth > 0 && imgHeight > 0) {
|
||||
const sourceRatio = imgWidth / imgHeight;
|
||||
const targetRatio = width / height;
|
||||
if (sourceRatio > targetRatio) {
|
||||
cropWidth = Math.max(1, Math.floor(imgHeight * targetRatio));
|
||||
cropHeight = imgHeight;
|
||||
}
|
||||
}
|
||||
getMtime(sourcePath, function(mtime) {
|
||||
const cacheKey = generateLargeKey(sourcePath, width, height, mtime);
|
||||
const cacheKey = generateLargeKey(sourcePath, targetWidth, height, mtime);
|
||||
const cachedPath = wpLargeDir + cacheKey + ".png";
|
||||
processRequest(cacheKey, cachedPath, sourcePath, callback, function() {
|
||||
startLargeProcessing(sourcePath, cachedPath, targetWidth, targetHeight, cacheKey);
|
||||
startLargeProcessing(sourcePath, cachedPath, targetWidth, cropWidth, cropHeight, cacheKey);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -187,7 +188,7 @@ Singleton {
|
||||
// Cache Key Generation
|
||||
// -------------------------------------------------
|
||||
function generateLargeKey(sourcePath, width, height, mtime) {
|
||||
const keyString = sourcePath + "@" + width + "x" + height + "@" + (mtime || "unknown");
|
||||
const keyString = sourcePath + "@w" + width + "@h" + height + "@" + (mtime || "unknown");
|
||||
return Checksum.sha256(keyString);
|
||||
}
|
||||
|
||||
@@ -249,11 +250,13 @@ Singleton {
|
||||
// -------------------------------------------------
|
||||
// ImageMagick Processing: Large
|
||||
// -------------------------------------------------
|
||||
function startLargeProcessing(sourcePath, outputPath, width, height, cacheKey) {
|
||||
function startLargeProcessing(sourcePath, outputPath, width, cropWidth, cropHeight, cacheKey) {
|
||||
const srcEsc = sourcePath.replace(/'/g, "'\\''");
|
||||
const dstEsc = outputPath.replace(/'/g, "'\\''");
|
||||
// Use Lanczos filter for high-quality downscaling, subtle unsharp mask, and PNG for lossless output
|
||||
const command = `magick '${srcEsc}' -auto-orient -filter Lanczos -resize '${width}x${height}' -gravity center -unsharp 0x0.5 '${dstEsc}'`;
|
||||
// `${width}x>` means keep aspect ratio, cap by width, and never upscale.
|
||||
// Crop is prepared in QML for compatibility with ImageMagick versions lacking `-if`.
|
||||
const cropStep = (cropWidth > 0 && cropHeight > 0) ? ` -gravity center -crop '${cropWidth}x${cropHeight}+0+0' +repage` : "";
|
||||
const command = `magick '${srcEsc}' -auto-orient${cropStep} -filter Lanczos -resize '${width}x>' -unsharp 0x0.5 '${dstEsc}'`;
|
||||
runProcess(command, cacheKey, outputPath, sourcePath);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ Singleton {
|
||||
})
|
||||
property bool hasFocusedWindow: focusedWindowIndex >= 0
|
||||
property int focusedWindowIndex: -1
|
||||
property int focusedWorkspaceId: -1
|
||||
property string focusedWindowAppId: hasFocusedWindow ? windows[focusedWindowIndex].appId : ""
|
||||
property string focusedWindowTitle: hasFocusedWindow ? windows[focusedWindowIndex].title : ""
|
||||
property string focusedOutput: ""
|
||||
@@ -38,7 +39,7 @@ Singleton {
|
||||
signal workspaceChanged()
|
||||
signal activeWindowChanged()
|
||||
signal windowListChanged()
|
||||
signal displayScalesChanged()
|
||||
signal outputsChanged()
|
||||
|
||||
function initialize() {
|
||||
niriEventStream.connected = true;
|
||||
@@ -111,12 +112,14 @@ Singleton {
|
||||
scales[output.name] = outputData;
|
||||
}
|
||||
}
|
||||
outputsChanged();
|
||||
}
|
||||
|
||||
function _recollectWorkspaces(workspacesData) {
|
||||
const workspacesList = [];
|
||||
workspaceCache = {
|
||||
};
|
||||
focusedWorkspaceId = -1;
|
||||
for (const ws of workspacesData) {
|
||||
const wsData = {
|
||||
"id": ws.id,
|
||||
@@ -130,9 +133,10 @@ Singleton {
|
||||
};
|
||||
workspacesList.push(wsData);
|
||||
workspaceCache[ws.id] = wsData;
|
||||
if (wsData.isFocused)
|
||||
if (wsData.isFocused) {
|
||||
focusedOutput = wsData.output || "";
|
||||
|
||||
focusedWorkspaceId = wsData.id;
|
||||
}
|
||||
}
|
||||
workspacesList.sort((a, b) => {
|
||||
if (a.output !== b.output)
|
||||
@@ -234,14 +238,13 @@ Singleton {
|
||||
nextIndex = cachedIndex;
|
||||
|
||||
}
|
||||
|
||||
if (nextIndex < 0 && focusedWindowIndex >= 0 && focusedWindowIndex < windows.length && windows[focusedWindowIndex].isFocused)
|
||||
nextIndex = focusedWindowIndex;
|
||||
|
||||
if (nextIndex < 0)
|
||||
nextIndex = windows.findIndex((w) => {
|
||||
return w.isFocused;
|
||||
});
|
||||
return w.isFocused;
|
||||
});
|
||||
|
||||
const hasChanged = nextIndex !== focusedWindowIndex;
|
||||
focusedWindowIndex = nextIndex;
|
||||
@@ -291,7 +294,6 @@ Singleton {
|
||||
activeWindowChanged();
|
||||
}
|
||||
windowListChanged();
|
||||
workspaceUpdateTimer.restart();
|
||||
} catch (e) {
|
||||
Logger.e("NiriService", "Error handling WindowOpenedOrChanged:", e);
|
||||
}
|
||||
@@ -310,7 +312,6 @@ Singleton {
|
||||
activeWindowChanged();
|
||||
|
||||
windowListChanged();
|
||||
workspaceUpdateTimer.restart();
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.e("NiriService", "Error handling WindowClosed:", e);
|
||||
@@ -326,6 +327,44 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function _handleWorkspaceActivated(eventData) {
|
||||
try {
|
||||
const workspaceId = eventData.id;
|
||||
if (workspaceId === focusedWorkspaceId)
|
||||
return ;
|
||||
|
||||
// update workspaceCache
|
||||
const workspace = workspaceCache[workspaceId];
|
||||
if (!workspace) {
|
||||
workspaceUpdateTimer.restart();
|
||||
return ;
|
||||
}
|
||||
const focusedOutputName = workspace.output;
|
||||
focusedOutput = focusedOutputName || "";
|
||||
const oldWorkspace = workspaceCache[focusedWorkspaceId];
|
||||
if (oldWorkspace) {
|
||||
oldWorkspace.isFocused = false;
|
||||
oldWorkspace.isActive = oldWorkspace.output !== focusedOutputName;
|
||||
}
|
||||
workspace.isFocused = true;
|
||||
workspace.isActive = true;
|
||||
// update workspaces ListModel
|
||||
for (var i = 0; i < workspaces.count; i++) {
|
||||
const ws = workspaces.get(i);
|
||||
if (ws.id === workspaceId) {
|
||||
workspaces.setProperty(i, "isFocused", true);
|
||||
workspaces.setProperty(i, "isActive", true);
|
||||
} else if (ws.id === focusedWorkspaceId) {
|
||||
workspaces.setProperty(i, "isFocused", false);
|
||||
workspaces.setProperty(i, "isActive", ws.output !== focusedOutputName);
|
||||
}
|
||||
}
|
||||
focusedWorkspaceId = workspaceId;
|
||||
} catch (e) {
|
||||
Logger.e("NiriService", "Error handling WorkspaceActivated:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function _handleWindowFocusChanged(eventData) {
|
||||
try {
|
||||
const focusedId = eventData.id;
|
||||
@@ -361,7 +400,6 @@ Singleton {
|
||||
window.position = getWindowPosition(layout);
|
||||
|
||||
hasMatchedWindow = hasMatchedWindow || window !== null;
|
||||
|
||||
}
|
||||
if (!hasMatchedWindow)
|
||||
return ;
|
||||
@@ -527,7 +565,7 @@ Singleton {
|
||||
else if (event.WindowsChanged)
|
||||
_handleWindowsChanged(event.WindowsChanged);
|
||||
else if (event.WorkspaceActivated)
|
||||
workspaceUpdateTimer.restart();
|
||||
_handleWorkspaceActivated(event.WorkspaceActivated);
|
||||
else if (event.WindowFocusChanged)
|
||||
_handleWindowFocusChanged(event.WindowFocusChanged);
|
||||
else if (event.WindowLayoutsChanged)
|
||||
|
||||
Reference in New Issue
Block a user