Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ec7e5e9fcb | |||
| 22f22b570a |
@@ -15,6 +15,8 @@
|
|||||||
|
|
||||||
<summary>Niri & Quickshell</summary>
|
<summary>Niri & Quickshell</summary>
|
||||||
|
|
||||||
|
https://github.com/user-attachments/assets/af29bcac-7207-4f23-88bb-d8c5d447776a
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
<img src="https://github.com/Uyanide/backgrounds/blob/master/screenshots/desktop-alt.webp?raw=true"/>
|
<img src="https://github.com/Uyanide/backgrounds/blob/master/screenshots/desktop-alt.webp?raw=true"/>
|
||||||
</figure>
|
</figure>
|
||||||
|
|||||||
@@ -43,9 +43,16 @@ layout {
|
|||||||
left 2
|
left 2
|
||||||
}
|
}
|
||||||
|
|
||||||
background-color "#1e1e2e"
|
background-color "transparent"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
overview {
|
||||||
|
backdrop-color "#1e1e2e"
|
||||||
|
|
||||||
|
workspace-shadow {
|
||||||
|
off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Disable the "Important Hotkeys" pop-up at startup.
|
// Disable the "Important Hotkeys" pop-up at startup.
|
||||||
hotkey-overlay {
|
hotkey-overlay {
|
||||||
@@ -57,6 +64,11 @@ prefer-no-csd
|
|||||||
animations {
|
animations {
|
||||||
// off
|
// off
|
||||||
// slowdown 3.0
|
// slowdown 3.0
|
||||||
|
|
||||||
|
workspace-switch {
|
||||||
|
duration-ms 200
|
||||||
|
curve "ease-out-cubic"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layer-rule {
|
layer-rule {
|
||||||
|
|||||||
@@ -92,10 +92,11 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: ShellState
|
function onColorStateChanged() {
|
||||||
onColorStateChanged: {
|
|
||||||
reloadTimer.restart();
|
reloadTimer.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
target: ShellState
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import QtQuick.Effects
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
|
import qs.Modules.Background
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
Variants {
|
Variants {
|
||||||
@@ -17,124 +18,9 @@ Variants {
|
|||||||
readonly property real blurPercentage: BackgroundService.blurPercentage
|
readonly property real blurPercentage: BackgroundService.blurPercentage
|
||||||
readonly property real blurRadius: BackgroundService.blurRadius
|
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 {
|
PanelWindow {
|
||||||
id: bdWindow
|
id: bdWindow
|
||||||
|
|
||||||
property bool doBlur: true
|
|
||||||
property string imagePath: BackgroundService.displayPath
|
|
||||||
|
|
||||||
screen: modelData
|
screen: modelData
|
||||||
WlrLayershell.namespace: "quickshell-backdrop"
|
WlrLayershell.namespace: "quickshell-backdrop"
|
||||||
WlrLayershell.layer: WlrLayer.Background
|
WlrLayershell.layer: WlrLayer.Background
|
||||||
@@ -147,96 +33,10 @@ Variants {
|
|||||||
right: true
|
right: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
ScrollBackground {
|
||||||
anchors.fill: parent
|
id: scrollBg
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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
|
implicitWidth: pillRow.implicitWidth + horizontalPadding * 2
|
||||||
Component.onCompleted: syncWorkspaces()
|
Component.onCompleted: syncWorkspaces()
|
||||||
|
|
||||||
@@ -63,6 +77,10 @@ Item {
|
|||||||
syncWorkspaces();
|
syncWorkspaces();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onFocusedWorkspaceIdChanged() {
|
||||||
|
syncWorkspaceFocus();
|
||||||
|
}
|
||||||
|
|
||||||
target: Niri
|
target: Niri
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property string backgroundWidth: "2560"
|
readonly property string backgroundWidth: "2560"
|
||||||
readonly property string backgroundHeight: "1440"
|
readonly property string backgroundHeight: "1600"
|
||||||
property string cachedPath: ""
|
property string cachedPath: ""
|
||||||
property string previewPath: ""
|
property string previewPath: ""
|
||||||
property string displayPath: ""
|
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) {
|
function getLarge(sourcePath, width, height, callback) {
|
||||||
if (!sourcePath || sourcePath === "") {
|
if (!sourcePath || sourcePath === "") {
|
||||||
@@ -89,28 +89,29 @@ Singleton {
|
|||||||
callback(sourcePath, false);
|
callback(sourcePath, false);
|
||||||
return ;
|
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) {
|
getImageDimensions(sourcePath, function(imgWidth, imgHeight) {
|
||||||
// const fitsScreen = imgWidth > 0 && imgHeight > 0 && imgWidth <= width && imgHeight <= height;
|
const sourceWidth = imgWidth > 0 ? imgWidth : width;
|
||||||
// if (fitsScreen) {
|
const targetWidth = Math.min(sourceWidth, width);
|
||||||
// // Only skip if format is natively supported by Qt
|
let cropWidth = 0;
|
||||||
// if (!needsConversion(sourcePath)) {
|
let cropHeight = 0;
|
||||||
// Logger.d("ImageCache", `Image ${imgWidth}x${imgHeight} fits screen ${width}x${height}, using original`);
|
if (shouldCropToAspect && imgWidth > 0 && imgHeight > 0) {
|
||||||
// callback(sourcePath, false);
|
const sourceRatio = imgWidth / imgHeight;
|
||||||
// return ;
|
const targetRatio = width / height;
|
||||||
// }
|
if (sourceRatio > targetRatio) {
|
||||||
// Logger.d("ImageCache", `Image needs conversion despite fitting screen`);
|
cropWidth = Math.max(1, Math.floor(imgHeight * targetRatio));
|
||||||
// }
|
cropHeight = imgHeight;
|
||||||
// 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;
|
|
||||||
getMtime(sourcePath, function(mtime) {
|
getMtime(sourcePath, function(mtime) {
|
||||||
const cacheKey = generateLargeKey(sourcePath, width, height, mtime);
|
const cacheKey = generateLargeKey(sourcePath, targetWidth, height, mtime);
|
||||||
const cachedPath = wpLargeDir + cacheKey + ".png";
|
const cachedPath = wpLargeDir + cacheKey + ".png";
|
||||||
processRequest(cacheKey, cachedPath, sourcePath, callback, function() {
|
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
|
// Cache Key Generation
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
function generateLargeKey(sourcePath, width, height, mtime) {
|
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);
|
return Checksum.sha256(keyString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,11 +250,13 @@ Singleton {
|
|||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
// ImageMagick Processing: Large
|
// ImageMagick Processing: Large
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
function startLargeProcessing(sourcePath, outputPath, width, height, cacheKey) {
|
function startLargeProcessing(sourcePath, outputPath, width, cropWidth, cropHeight, cacheKey) {
|
||||||
const srcEsc = sourcePath.replace(/'/g, "'\\''");
|
const srcEsc = sourcePath.replace(/'/g, "'\\''");
|
||||||
const dstEsc = outputPath.replace(/'/g, "'\\''");
|
const dstEsc = outputPath.replace(/'/g, "'\\''");
|
||||||
// Use Lanczos filter for high-quality downscaling, subtle unsharp mask, and PNG for lossless output
|
// `${width}x>` means keep aspect ratio, cap by width, and never upscale.
|
||||||
const command = `magick '${srcEsc}' -auto-orient -filter Lanczos -resize '${width}x${height}' -gravity center -unsharp 0x0.5 '${dstEsc}'`;
|
// 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);
|
runProcess(command, cacheKey, outputPath, sourcePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Singleton {
|
|||||||
})
|
})
|
||||||
property bool hasFocusedWindow: focusedWindowIndex >= 0
|
property bool hasFocusedWindow: focusedWindowIndex >= 0
|
||||||
property int focusedWindowIndex: -1
|
property int focusedWindowIndex: -1
|
||||||
|
property int focusedWorkspaceId: -1
|
||||||
property string focusedWindowAppId: hasFocusedWindow ? windows[focusedWindowIndex].appId : ""
|
property string focusedWindowAppId: hasFocusedWindow ? windows[focusedWindowIndex].appId : ""
|
||||||
property string focusedWindowTitle: hasFocusedWindow ? windows[focusedWindowIndex].title : ""
|
property string focusedWindowTitle: hasFocusedWindow ? windows[focusedWindowIndex].title : ""
|
||||||
property string focusedOutput: ""
|
property string focusedOutput: ""
|
||||||
@@ -38,7 +39,7 @@ Singleton {
|
|||||||
signal workspaceChanged()
|
signal workspaceChanged()
|
||||||
signal activeWindowChanged()
|
signal activeWindowChanged()
|
||||||
signal windowListChanged()
|
signal windowListChanged()
|
||||||
signal displayScalesChanged()
|
signal outputsChanged()
|
||||||
|
|
||||||
function initialize() {
|
function initialize() {
|
||||||
niriEventStream.connected = true;
|
niriEventStream.connected = true;
|
||||||
@@ -111,12 +112,14 @@ Singleton {
|
|||||||
scales[output.name] = outputData;
|
scales[output.name] = outputData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
outputsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
function _recollectWorkspaces(workspacesData) {
|
function _recollectWorkspaces(workspacesData) {
|
||||||
const workspacesList = [];
|
const workspacesList = [];
|
||||||
workspaceCache = {
|
workspaceCache = {
|
||||||
};
|
};
|
||||||
|
focusedWorkspaceId = -1;
|
||||||
for (const ws of workspacesData) {
|
for (const ws of workspacesData) {
|
||||||
const wsData = {
|
const wsData = {
|
||||||
"id": ws.id,
|
"id": ws.id,
|
||||||
@@ -130,9 +133,10 @@ Singleton {
|
|||||||
};
|
};
|
||||||
workspacesList.push(wsData);
|
workspacesList.push(wsData);
|
||||||
workspaceCache[ws.id] = wsData;
|
workspaceCache[ws.id] = wsData;
|
||||||
if (wsData.isFocused)
|
if (wsData.isFocused) {
|
||||||
focusedOutput = wsData.output || "";
|
focusedOutput = wsData.output || "";
|
||||||
|
focusedWorkspaceId = wsData.id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
workspacesList.sort((a, b) => {
|
workspacesList.sort((a, b) => {
|
||||||
if (a.output !== b.output)
|
if (a.output !== b.output)
|
||||||
@@ -234,14 +238,13 @@ Singleton {
|
|||||||
nextIndex = cachedIndex;
|
nextIndex = cachedIndex;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextIndex < 0 && focusedWindowIndex >= 0 && focusedWindowIndex < windows.length && windows[focusedWindowIndex].isFocused)
|
if (nextIndex < 0 && focusedWindowIndex >= 0 && focusedWindowIndex < windows.length && windows[focusedWindowIndex].isFocused)
|
||||||
nextIndex = focusedWindowIndex;
|
nextIndex = focusedWindowIndex;
|
||||||
|
|
||||||
if (nextIndex < 0)
|
if (nextIndex < 0)
|
||||||
nextIndex = windows.findIndex((w) => {
|
nextIndex = windows.findIndex((w) => {
|
||||||
return w.isFocused;
|
return w.isFocused;
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasChanged = nextIndex !== focusedWindowIndex;
|
const hasChanged = nextIndex !== focusedWindowIndex;
|
||||||
focusedWindowIndex = nextIndex;
|
focusedWindowIndex = nextIndex;
|
||||||
@@ -291,7 +294,6 @@ Singleton {
|
|||||||
activeWindowChanged();
|
activeWindowChanged();
|
||||||
}
|
}
|
||||||
windowListChanged();
|
windowListChanged();
|
||||||
workspaceUpdateTimer.restart();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger.e("NiriService", "Error handling WindowOpenedOrChanged:", e);
|
Logger.e("NiriService", "Error handling WindowOpenedOrChanged:", e);
|
||||||
}
|
}
|
||||||
@@ -310,7 +312,6 @@ Singleton {
|
|||||||
activeWindowChanged();
|
activeWindowChanged();
|
||||||
|
|
||||||
windowListChanged();
|
windowListChanged();
|
||||||
workspaceUpdateTimer.restart();
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger.e("NiriService", "Error handling WindowClosed:", 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) {
|
function _handleWindowFocusChanged(eventData) {
|
||||||
try {
|
try {
|
||||||
const focusedId = eventData.id;
|
const focusedId = eventData.id;
|
||||||
@@ -361,7 +400,6 @@ Singleton {
|
|||||||
window.position = getWindowPosition(layout);
|
window.position = getWindowPosition(layout);
|
||||||
|
|
||||||
hasMatchedWindow = hasMatchedWindow || window !== null;
|
hasMatchedWindow = hasMatchedWindow || window !== null;
|
||||||
|
|
||||||
}
|
}
|
||||||
if (!hasMatchedWindow)
|
if (!hasMatchedWindow)
|
||||||
return ;
|
return ;
|
||||||
@@ -527,7 +565,7 @@ Singleton {
|
|||||||
else if (event.WindowsChanged)
|
else if (event.WindowsChanged)
|
||||||
_handleWindowsChanged(event.WindowsChanged);
|
_handleWindowsChanged(event.WindowsChanged);
|
||||||
else if (event.WorkspaceActivated)
|
else if (event.WorkspaceActivated)
|
||||||
workspaceUpdateTimer.restart();
|
_handleWorkspaceActivated(event.WorkspaceActivated);
|
||||||
else if (event.WindowFocusChanged)
|
else if (event.WindowFocusChanged)
|
||||||
_handleWindowFocusChanged(event.WindowFocusChanged);
|
_handleWindowFocusChanged(event.WindowFocusChanged);
|
||||||
else if (event.WindowLayoutsChanged)
|
else if (event.WindowLayoutsChanged)
|
||||||
|
|||||||
Reference in New Issue
Block a user