Revert "Remove need for polkit, launch any ArchUpdater update through terminal"
This reverts commit 299add4a15.
This commit is contained in:
parent
299add4a15
commit
3496169c68
19 changed files with 807 additions and 1240 deletions
|
|
@ -17,6 +17,14 @@ Singleton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _getStackTrace() {
|
||||||
|
try {
|
||||||
|
throw new Error("Stack trace")
|
||||||
|
} catch (e) {
|
||||||
|
return e.stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function log(...args) {
|
function log(...args) {
|
||||||
var msg = _formatMessage(...args)
|
var msg = _formatMessage(...args)
|
||||||
console.log(msg)
|
console.log(msg)
|
||||||
|
|
@ -31,4 +39,20 @@ Singleton {
|
||||||
var msg = _formatMessage(...args)
|
var msg = _formatMessage(...args)
|
||||||
console.error(msg)
|
console.error(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function callStack() {
|
||||||
|
var stack = _getStackTrace()
|
||||||
|
Logger.log("Debug", "--------------------------")
|
||||||
|
Logger.log("Debug", "Current call stack")
|
||||||
|
// Split the stack into lines and log each one
|
||||||
|
var stackLines = stack.split('\n')
|
||||||
|
for (var i = 0; i < stackLines.length; i++) {
|
||||||
|
var line = stackLines[i].trim() // Remove leading/trailing whitespace
|
||||||
|
if (line.length > 0) {
|
||||||
|
// Only log non-empty lines
|
||||||
|
Logger.log("Debug", `- ${line}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Logger.log("Debug", "--------------------------")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,23 +89,16 @@ Singleton {
|
||||||
reload()
|
reload()
|
||||||
}
|
}
|
||||||
onLoaded: function () {
|
onLoaded: function () {
|
||||||
Qt.callLater(function () {
|
if (!isLoaded) {
|
||||||
// Some stuff like wallpaper setup and settings validation should just be executed once on startup
|
Logger.log("Settings", "----------------------------")
|
||||||
// And not on every reload
|
Logger.log("Settings", "Settings loaded successfully")
|
||||||
if (!isLoaded) {
|
isLoaded = true
|
||||||
Logger.log("Settings", "JSON completed loading")
|
|
||||||
if (adapter.wallpaper.current !== "") {
|
|
||||||
Logger.log("Settings", "Set current wallpaper", adapter.wallpaper.current)
|
|
||||||
WallpaperService.setCurrentWallpaper(adapter.wallpaper.current, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate monitor configurations, only once
|
Qt.callLater(function () {
|
||||||
// if none of the configured monitors exist, clear the lists
|
// Some stuff like settings validation should just be executed once on startup and not on every reload
|
||||||
validateMonitorConfigurations()
|
validateMonitorConfigurations()
|
||||||
|
})
|
||||||
isLoaded = true
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
onLoadFailed: function (error) {
|
onLoadFailed: function (error) {
|
||||||
if (error.toString().includes("No such file") || error === 2)
|
if (error.toString().includes("No such file") || error === 2)
|
||||||
|
|
@ -171,22 +164,13 @@ Singleton {
|
||||||
// wallpaper
|
// wallpaper
|
||||||
property JsonObject wallpaper: JsonObject {
|
property JsonObject wallpaper: JsonObject {
|
||||||
property string directory: "/usr/share/wallpapers"
|
property string directory: "/usr/share/wallpapers"
|
||||||
property string current: ""
|
property bool enableMultiMonitorDirectories: false
|
||||||
property bool isRandom: false
|
property bool setWallpaperOnAllMonitors: true
|
||||||
property int randomInterval: 300
|
property bool randomEnabled: false
|
||||||
property JsonObject swww
|
property int randomIntervalSec: 300 // 5 min
|
||||||
|
property int transitionDuration: 1500 // 1500 ms
|
||||||
onDirectoryChanged: WallpaperService.listWallpapers()
|
property string transitionType: "fade"
|
||||||
onIsRandomChanged: WallpaperService.toggleRandomWallpaper()
|
property list<var> monitors: []
|
||||||
onRandomIntervalChanged: WallpaperService.restartRandomWallpaperTimer()
|
|
||||||
|
|
||||||
swww: JsonObject {
|
|
||||||
property bool enabled: false
|
|
||||||
property string resizeMethod: "crop"
|
|
||||||
property int transitionFps: 60
|
|
||||||
property string transitionType: "random"
|
|
||||||
property real transitionDuration: 1.1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// applauncher
|
// applauncher
|
||||||
|
|
@ -234,19 +218,10 @@ Singleton {
|
||||||
property string fontDefault: "Roboto" // Default font for all text
|
property string fontDefault: "Roboto" // Default font for all text
|
||||||
property string fontFixed: "DejaVu Sans Mono" // Fixed width font for terminal
|
property string fontFixed: "DejaVu Sans Mono" // Fixed width font for terminal
|
||||||
property string fontBillboard: "Inter" // Large bold font for clocks and prominent displays
|
property string fontBillboard: "Inter" // Large bold font for clocks and prominent displays
|
||||||
|
property list<var> monitorsScaling: []
|
||||||
// Legacy compatibility
|
|
||||||
property string fontFamily: fontDefault // Keep for backward compatibility
|
|
||||||
|
|
||||||
// Idle inhibitor state
|
|
||||||
property bool idleInhibitorEnabled: false
|
property bool idleInhibitorEnabled: false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scaling (not stored inside JsonObject, or it crashes)
|
|
||||||
property var monitorsScaling: {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// brightness
|
// brightness
|
||||||
property JsonObject brightness: JsonObject {
|
property JsonObject brightness: JsonObject {
|
||||||
property int brightnessStep: 5
|
property int brightnessStep: 5
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,8 @@ NPanel {
|
||||||
|
|
||||||
// When the panel opens
|
// When the panel opens
|
||||||
onOpened: {
|
onOpened: {
|
||||||
console.log("ArchUpdaterPanel: Panel opened, refreshing package lists...")
|
ArchUpdaterService.doPoll()
|
||||||
// Always refresh when panel opens to ensure we have the latest data
|
ArchUpdaterService.doAurPoll()
|
||||||
ArchUpdaterService.forceRefresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
panelContent: Rectangle {
|
panelContent: Rectangle {
|
||||||
|
|
@ -48,19 +47,6 @@ NPanel {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset button (only show if update failed)
|
|
||||||
NIconButton {
|
|
||||||
visible: ArchUpdaterService.updateFailed
|
|
||||||
icon: "refresh"
|
|
||||||
tooltipText: "Reset update state"
|
|
||||||
sizeRatio: 0.8
|
|
||||||
colorBg: Color.mError
|
|
||||||
colorFg: Color.mOnError
|
|
||||||
onClicked: {
|
|
||||||
ArchUpdaterService.resetUpdateState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
NIconButton {
|
||||||
icon: "close"
|
icon: "close"
|
||||||
tooltipText: "Close"
|
tooltipText: "Close"
|
||||||
|
|
@ -73,10 +59,8 @@ NPanel {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update summary (only show when packages are available)
|
// Update summary
|
||||||
NText {
|
NText {
|
||||||
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed && !ArchUpdaterService.busy
|
|
||||||
&& !ArchUpdaterService.aurBusy && ArchUpdaterService.totalUpdates > 0
|
|
||||||
text: ArchUpdaterService.totalUpdates + " package" + (ArchUpdaterService.totalUpdates !== 1 ? "s" : "") + " can be updated"
|
text: ArchUpdaterService.totalUpdates + " package" + (ArchUpdaterService.totalUpdates !== 1 ? "s" : "") + " can be updated"
|
||||||
font.pointSize: Style.fontSizeL * scaling
|
font.pointSize: Style.fontSizeL * scaling
|
||||||
font.weight: Style.fontWeightMedium
|
font.weight: Style.fontWeightMedium
|
||||||
|
|
@ -84,184 +68,16 @@ NPanel {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Package selection info (only show when not updating and have packages)
|
// Package selection info
|
||||||
NText {
|
NText {
|
||||||
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed && !ArchUpdaterService.busy
|
|
||||||
&& !ArchUpdaterService.aurBusy && ArchUpdaterService.totalUpdates > 0
|
|
||||||
text: ArchUpdaterService.selectedPackagesCount + " of " + ArchUpdaterService.totalUpdates + " packages selected"
|
text: ArchUpdaterService.selectedPackagesCount + " of " + ArchUpdaterService.totalUpdates + " packages selected"
|
||||||
font.pointSize: Style.fontSizeS * scaling
|
font.pointSize: Style.fontSizeS * scaling
|
||||||
color: Color.mOnSurfaceVariant
|
color: Color.mOnSurfaceVariant
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update in progress state
|
// Unified list
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
visible: ArchUpdaterService.updateInProgress
|
|
||||||
spacing: Style.marginM * scaling
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
} // Spacer
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
text: "hourglass_empty"
|
|
||||||
font.pointSize: Style.fontSizeXXXL * scaling
|
|
||||||
color: Color.mPrimary
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Update in progress"
|
|
||||||
font.pointSize: Style.fontSizeL * scaling
|
|
||||||
color: Color.mOnSurface
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Please check your terminal window for update progress and prompts."
|
|
||||||
font.pointSize: Style.fontSizeNormal * scaling
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
Layout.maximumWidth: 280 * scaling
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
} // Spacer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update failed state
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
visible: ArchUpdaterService.updateFailed
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Style.marginM * scaling
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
text: "error_outline"
|
|
||||||
font.pointSize: Style.fontSizeXXXL * scaling
|
|
||||||
color: Color.mError
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Update failed"
|
|
||||||
font.pointSize: Style.fontSizeL * scaling
|
|
||||||
color: Color.mOnSurface
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Check your terminal for error details and try again."
|
|
||||||
font.pointSize: Style.fontSizeNormal * scaling
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
Layout.maximumWidth: 280 * scaling
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prominent refresh button
|
|
||||||
NIconButton {
|
|
||||||
icon: "refresh"
|
|
||||||
tooltipText: "Refresh and try again"
|
|
||||||
sizeRatio: 1.2
|
|
||||||
colorBg: Color.mPrimary
|
|
||||||
colorFg: Color.mOnPrimary
|
|
||||||
onClicked: {
|
|
||||||
ArchUpdaterService.resetUpdateState()
|
|
||||||
}
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.topMargin: Style.marginL * scaling
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No updates available state
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed && !ArchUpdaterService.busy
|
|
||||||
&& !ArchUpdaterService.aurBusy && ArchUpdaterService.totalUpdates === 0
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Style.marginM * scaling
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
text: "check_circle"
|
|
||||||
font.pointSize: Style.fontSizeXXXL * scaling
|
|
||||||
color: Color.mPrimary
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "System is up to date"
|
|
||||||
font.pointSize: Style.fontSizeL * scaling
|
|
||||||
color: Color.mOnSurface
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "All packages are current. Check back later for updates."
|
|
||||||
font.pointSize: Style.fontSizeNormal * scaling
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
Layout.maximumWidth: 280 * scaling
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checking for updates state
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
visible: (ArchUpdaterService.busy || ArchUpdaterService.aurBusy) && !ArchUpdaterService.updateInProgress
|
|
||||||
&& !ArchUpdaterService.updateFailed
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Style.marginM * scaling
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
text: "refresh"
|
|
||||||
font.pointSize: Style.fontSizeXXXL * scaling
|
|
||||||
color: Color.mPrimary
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Checking for updates"
|
|
||||||
font.pointSize: Style.fontSizeL * scaling
|
|
||||||
color: Color.mOnSurface
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Scanning package databases for available updates..."
|
|
||||||
font.pointSize: Style.fontSizeNormal * scaling
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
Layout.maximumWidth: 280 * scaling
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Package list (only show when not in any special state)
|
|
||||||
NBox {
|
NBox {
|
||||||
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed && !ArchUpdaterService.busy
|
|
||||||
&& !ArchUpdaterService.aurBusy && ArchUpdaterService.totalUpdates > 0
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
|
@ -348,49 +164,50 @@ NPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action buttons (only show when not updating)
|
// Action buttons
|
||||||
RowLayout {
|
RowLayout {
|
||||||
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Style.marginL * scaling
|
spacing: Style.marginL * scaling
|
||||||
|
|
||||||
NIconButton {
|
NIconButton {
|
||||||
icon: "refresh"
|
icon: "refresh"
|
||||||
tooltipText: "Refresh package lists"
|
tooltipText: "Check for updates"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
ArchUpdaterService.forceRefresh()
|
ArchUpdaterService.doPoll()
|
||||||
|
ArchUpdaterService.doAurPoll()
|
||||||
}
|
}
|
||||||
colorBg: Color.mSurfaceVariant
|
colorBg: Color.mSurfaceVariant
|
||||||
colorFg: Color.mOnSurface
|
colorFg: Color.mOnSurface
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
enabled: !ArchUpdaterService.busy && !ArchUpdaterService.aurBusy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NIconButton {
|
NIconButton {
|
||||||
icon: "system_update_alt"
|
icon: ArchUpdaterService.updateInProgress ? "hourglass_empty" : "system_update_alt"
|
||||||
tooltipText: "Update all packages"
|
tooltipText: ArchUpdaterService.updateInProgress ? "Update in progress..." : "Update all packages"
|
||||||
enabled: ArchUpdaterService.totalUpdates > 0
|
enabled: !ArchUpdaterService.updateInProgress
|
||||||
onClicked: {
|
onClicked: {
|
||||||
ArchUpdaterService.runUpdate()
|
ArchUpdaterService.runUpdate()
|
||||||
root.close()
|
root.close()
|
||||||
}
|
}
|
||||||
colorBg: ArchUpdaterService.totalUpdates > 0 ? Color.mPrimary : Color.mSurfaceVariant
|
colorBg: ArchUpdaterService.updateInProgress ? Color.mSurfaceVariant : Color.mPrimary
|
||||||
colorFg: ArchUpdaterService.totalUpdates > 0 ? Color.mOnPrimary : Color.mOnSurfaceVariant
|
colorFg: ArchUpdaterService.updateInProgress ? Color.mOnSurfaceVariant : Color.mOnPrimary
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
NIconButton {
|
NIconButton {
|
||||||
icon: "check_box"
|
icon: ArchUpdaterService.updateInProgress ? "hourglass_empty" : "check_box"
|
||||||
tooltipText: "Update selected packages"
|
tooltipText: ArchUpdaterService.updateInProgress ? "Update in progress..." : "Update selected packages"
|
||||||
enabled: ArchUpdaterService.selectedPackagesCount > 0
|
enabled: !ArchUpdaterService.updateInProgress && ArchUpdaterService.selectedPackagesCount > 0
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (ArchUpdaterService.selectedPackagesCount > 0) {
|
if (ArchUpdaterService.selectedPackagesCount > 0) {
|
||||||
ArchUpdaterService.runSelectiveUpdate()
|
ArchUpdaterService.runSelectiveUpdate()
|
||||||
root.close()
|
root.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
colorBg: ArchUpdaterService.selectedPackagesCount > 0 ? Color.mPrimary : Color.mSurfaceVariant
|
colorBg: ArchUpdaterService.updateInProgress ? Color.mSurfaceVariant : (ArchUpdaterService.selectedPackagesCount
|
||||||
colorFg: ArchUpdaterService.selectedPackagesCount > 0 ? Color.mOnPrimary : Color.mOnSurfaceVariant
|
> 0 ? Color.mPrimary : Color.mSurfaceVariant)
|
||||||
|
colorFg: ArchUpdaterService.updateInProgress ? Color.mOnSurfaceVariant : (ArchUpdaterService.selectedPackagesCount
|
||||||
|
> 0 ? Color.mOnPrimary : Color.mOnSurfaceVariant)
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,27 +4,67 @@ import Quickshell.Wayland
|
||||||
import qs.Commons
|
import qs.Commons
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
Loader {
|
Variants {
|
||||||
active: !Settings.data.wallpaper.swww.enabled
|
id: backgroundVariants
|
||||||
|
model: Quickshell.screens
|
||||||
|
|
||||||
sourceComponent: Variants {
|
delegate: Loader {
|
||||||
model: Quickshell.screens
|
|
||||||
|
|
||||||
delegate: PanelWindow {
|
required property ShellScreen modelData
|
||||||
required property ShellScreen modelData
|
|
||||||
property string wallpaperSource: WallpaperService.currentWallpaper !== ""
|
|
||||||
&& !Settings.data.wallpaper.swww.enabled ? WallpaperService.currentWallpaper : ""
|
|
||||||
|
|
||||||
visible: wallpaperSource !== "" && !Settings.data.wallpaper.swww.enabled
|
active: Settings.isLoaded && WallpaperService.getWallpaper(modelData.name)
|
||||||
|
|
||||||
// Force update when SWWW setting changes
|
sourceComponent: PanelWindow {
|
||||||
onVisibleChanged: {
|
id: root
|
||||||
if (visible) {
|
|
||||||
|
|
||||||
} else {
|
// Internal state management
|
||||||
|
property bool firstWallpaper: true
|
||||||
|
property bool transitioning: false
|
||||||
|
property real transitionProgress: 0.0
|
||||||
|
|
||||||
|
// Wipe direction: 0=left, 1=right, 2=up, 3=down
|
||||||
|
property real wipeDirection: 0
|
||||||
|
property real wipeSmoothness: 0.05
|
||||||
|
|
||||||
|
// External state management
|
||||||
|
property string servicedWallpaper: WallpaperService.getWallpaper(modelData.name)
|
||||||
|
onServicedWallpaperChanged: {
|
||||||
|
if (servicedWallpaper && servicedWallpaper !== currentWallpaper.source) {
|
||||||
|
|
||||||
|
// Set wallpaper immediately on startup
|
||||||
|
if (firstWallpaper) {
|
||||||
|
firstWallpaper = false
|
||||||
|
setWallpaperImmediate(servicedWallpaper)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (Settings.data.wallpaper.transitionType) {
|
||||||
|
case "none":
|
||||||
|
setWallpaperImmediate(servicedWallpaper)
|
||||||
|
break
|
||||||
|
case "wipe_left":
|
||||||
|
wipeDirection = 0
|
||||||
|
setWallpaperWithTransition(servicedWallpaper)
|
||||||
|
break
|
||||||
|
case "wipe_right":
|
||||||
|
wipeDirection = 1
|
||||||
|
setWallpaperWithTransition(servicedWallpaper)
|
||||||
|
break
|
||||||
|
case "wipe_up":
|
||||||
|
wipeDirection = 2
|
||||||
|
setWallpaperWithTransition(servicedWallpaper)
|
||||||
|
break
|
||||||
|
case "wipe_down":
|
||||||
|
wipeDirection = 3
|
||||||
|
setWallpaperWithTransition(servicedWallpaper)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
setWallpaperWithTransition(servicedWallpaper)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
color: Color.transparent
|
color: Color.transparent
|
||||||
screen: modelData
|
screen: modelData
|
||||||
WlrLayershell.layer: WlrLayer.Background
|
WlrLayershell.layer: WlrLayer.Background
|
||||||
|
|
@ -38,18 +78,106 @@ Loader {
|
||||||
left: true
|
left: true
|
||||||
}
|
}
|
||||||
|
|
||||||
margins {
|
|
||||||
top: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
|
id: currentWallpaper
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
source: wallpaperSource
|
source: ""
|
||||||
visible: wallpaperSource !== ""
|
|
||||||
cache: true
|
cache: true
|
||||||
smooth: true
|
smooth: true
|
||||||
mipmap: false
|
mipmap: false
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: nextWallpaper
|
||||||
|
anchors.fill: parent
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
source: ""
|
||||||
|
cache: true
|
||||||
|
smooth: true
|
||||||
|
mipmap: false
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fade transition shader
|
||||||
|
ShaderEffect {
|
||||||
|
id: fadeShader
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: Settings.data.wallpaper.transitionType === 'fade'
|
||||||
|
|
||||||
|
property variant source1: currentWallpaper
|
||||||
|
property variant source2: nextWallpaper
|
||||||
|
property real fade: transitionProgress
|
||||||
|
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_fade.frag.qsb")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wipe transition shader
|
||||||
|
ShaderEffect {
|
||||||
|
id: wipeShader
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: Settings.data.wallpaper.transitionType.startsWith('wipe_')
|
||||||
|
|
||||||
|
property variant source1: currentWallpaper
|
||||||
|
property variant source2: nextWallpaper
|
||||||
|
property real progress: transitionProgress
|
||||||
|
property real direction: wipeDirection
|
||||||
|
property real smoothness: wipeSmoothness
|
||||||
|
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_wipe.frag.qsb")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation for the transition progress
|
||||||
|
NumberAnimation {
|
||||||
|
id: transitionAnimation
|
||||||
|
target: root
|
||||||
|
property: "transitionProgress"
|
||||||
|
from: 0.0
|
||||||
|
to: 1.0
|
||||||
|
duration: Settings.data.wallpaper.transitionDuration ?? 1000
|
||||||
|
easing.type: {
|
||||||
|
const transitionType = Settings.data.wallpaper.transitionType ?? 'fade'
|
||||||
|
if (transitionType.startsWith('wipe_')) {
|
||||||
|
return Easing.InOutCubic
|
||||||
|
}
|
||||||
|
return Easing.InOutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
onFinished: {
|
||||||
|
// Swap images after transition completes
|
||||||
|
currentWallpaper.source = nextWallpaper.source
|
||||||
|
transitionProgress = 0.0
|
||||||
|
transitioning = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTransition() {
|
||||||
|
if (!transitioning && nextWallpaper.source != currentWallpaper.source) {
|
||||||
|
transitioning = true
|
||||||
|
transitionAnimation.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWallpaperImmediate(source) {
|
||||||
|
currentWallpaper.source = source
|
||||||
|
nextWallpaper.source = source
|
||||||
|
transitionProgress = 0.0
|
||||||
|
transitioning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWallpaperWithTransition(source) {
|
||||||
|
if (source != currentWallpaper.source) {
|
||||||
|
|
||||||
|
if (transitioning) {
|
||||||
|
// We are interrupting a transition
|
||||||
|
currentWallpaper.source = nextWallpaper.source
|
||||||
|
transitionAnimation.stop()
|
||||||
|
transitionProgress = 0
|
||||||
|
transitioning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
nextWallpaper.source = source
|
||||||
|
startTransition()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,24 +6,19 @@ import qs.Commons
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Loader {
|
Variants {
|
||||||
active: CompositorService.isNiri
|
model: Quickshell.screens
|
||||||
|
|
||||||
Component.onCompleted: {
|
delegate: Loader {
|
||||||
if (CompositorService.isNiri) {
|
required property ShellScreen modelData
|
||||||
Logger.log("Overview", "Loading Overview component for Niri")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceComponent: Variants {
|
active: Settings.isLoaded && CompositorService.isNiri
|
||||||
model: Quickshell.screens
|
|
||||||
|
|
||||||
delegate: PanelWindow {
|
sourceComponent: PanelWindow {
|
||||||
required property ShellScreen modelData
|
Component.onCompleted: {
|
||||||
property string wallpaperSource: WallpaperService.currentWallpaper !== ""
|
Logger.log("Overview", "Loading Overview component for Niri on", modelData.name)
|
||||||
&& !Settings.data.wallpaper.swww.enabled ? WallpaperService.currentWallpaper : ""
|
}
|
||||||
|
|
||||||
visible: wallpaperSource !== "" && !Settings.data.wallpaper.swww.enabled
|
|
||||||
color: Color.transparent
|
color: Color.transparent
|
||||||
screen: modelData
|
screen: modelData
|
||||||
WlrLayershell.layer: WlrLayer.Background
|
WlrLayershell.layer: WlrLayer.Background
|
||||||
|
|
@ -39,19 +34,15 @@ Loader {
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: bgImage
|
id: bgImage
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
source: wallpaperSource
|
source: WallpaperService.getWallpaper(modelData.name)
|
||||||
cache: true
|
cache: true
|
||||||
smooth: true
|
smooth: true
|
||||||
mipmap: false
|
mipmap: false
|
||||||
visible: wallpaperSource !== ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiEffect {
|
MultiEffect {
|
||||||
id: overviewBgBlur
|
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: bgImage
|
source: bgImage
|
||||||
blurEnabled: true
|
blurEnabled: true
|
||||||
|
|
|
||||||
|
|
@ -64,5 +64,6 @@ NIconButton {
|
||||||
|
|
||||||
PanelService.getPanel("archUpdaterPanel").toggle(screen, this)
|
PanelService.getPanel("archUpdaterPanel").toggle(screen, this)
|
||||||
ArchUpdaterService.doPoll()
|
ArchUpdaterService.doPoll()
|
||||||
|
ArchUpdaterService.doAurPoll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ Loader {
|
||||||
id: lockBgImage
|
id: lockBgImage
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
source: WallpaperService.currentWallpaper !== "" ? WallpaperService.currentWallpaper : ""
|
source: WallpaperService.getWallpaper(screen.name)
|
||||||
cache: true
|
cache: true
|
||||||
smooth: true
|
smooth: true
|
||||||
mipmap: false
|
mipmap: false
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
NText {
|
NText {
|
||||||
text: `${Math.round(ScalingService.scaleByName(modelData.name) * 100)}%`
|
text: `${Math.round(ScalingService.getMonitorScale(modelData.name) * 100)}%`
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
Layout.minimumWidth: 50 * scaling
|
Layout.minimumWidth: 50 * scaling
|
||||||
horizontalAlignment: Text.AlignRight
|
horizontalAlignment: Text.AlignRight
|
||||||
|
|
@ -204,12 +204,8 @@ ColumnLayout {
|
||||||
from: 0.7
|
from: 0.7
|
||||||
to: 1.8
|
to: 1.8
|
||||||
stepSize: 0.01
|
stepSize: 0.01
|
||||||
value: ScalingService.scaleByName(modelData.name)
|
value: ScalingService.getMonitorScale(modelData.name)
|
||||||
onPressedChanged: {
|
onPressedChanged: ScalingService.setMonitorScale(modelData.name, value)
|
||||||
var data = Settings.data.monitorsScaling || {}
|
|
||||||
data[modelData.name] = value
|
|
||||||
Settings.data.monitorsScaling = data
|
|
||||||
}
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.minimumWidth: 150 * scaling
|
Layout.minimumWidth: 150 * scaling
|
||||||
}
|
}
|
||||||
|
|
@ -217,11 +213,7 @@ ColumnLayout {
|
||||||
NIconButton {
|
NIconButton {
|
||||||
icon: "refresh"
|
icon: "refresh"
|
||||||
tooltipText: "Reset Scaling"
|
tooltipText: "Reset Scaling"
|
||||||
onClicked: {
|
onClicked: ScalingService.setMonitorScale(modelData.name, 1.0)
|
||||||
var data = Settings.data.monitorsScaling || {}
|
|
||||||
data[modelData.name] = 1.0
|
|
||||||
Settings.data.monitorsScaling = data
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,12 @@ ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 140 * scaling
|
Layout.preferredHeight: 140 * scaling
|
||||||
radius: Style.radiusM * scaling
|
radius: Style.radiusM * scaling
|
||||||
color: Color.mPrimary
|
color: Color.mSecondary
|
||||||
|
|
||||||
NImageRounded {
|
NImageRounded {
|
||||||
id: currentWallpaperImage
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Style.marginXS * scaling
|
anchors.margins: Style.marginXS * scaling
|
||||||
imagePath: WallpaperService.currentWallpaper
|
imagePath: WallpaperService.getWallpaper(screen.name)
|
||||||
fallbackIcon: "image"
|
fallbackIcon: "image"
|
||||||
imageRadius: Style.radiusM * scaling
|
imageRadius: Style.radiusM * scaling
|
||||||
}
|
}
|
||||||
|
|
@ -62,41 +61,44 @@ ColumnLayout {
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
NText {
|
|
||||||
text: Settings.data.wallpaper.swww.enabled ? "Wallpapers will change with " + Settings.data.wallpaper.swww.transitionType
|
|
||||||
+ " transition." : "Wallpapers will change instantly."
|
|
||||||
color: Color.mOnSurface
|
|
||||||
font.pointSize: Style.fontSizeXS * scaling
|
|
||||||
visible: Settings.data.wallpaper.swww.enabled
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NIconButton {
|
NIconButton {
|
||||||
icon: "refresh"
|
icon: "refresh"
|
||||||
tooltipText: "Refresh wallpaper list"
|
tooltipText: "Refresh wallpaper list"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
WallpaperService.listWallpapers()
|
WallpaperService.refreshWallpapersList()
|
||||||
}
|
}
|
||||||
Layout.alignment: Qt.AlignTop | Qt.AlignRight
|
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property list<string> wallpapersList: WallpaperService.getWallpapersList(screen.name)
|
||||||
|
|
||||||
|
NToggle {
|
||||||
|
label: "Assign selection to all monitors"
|
||||||
|
description: "Set selected wallpaper on all monitors at once."
|
||||||
|
checked: Settings.data.wallpaper.setWallpaperOnAllMonitors
|
||||||
|
onToggled: checked => Settings.data.wallpaper.setWallpaperOnAllMonitors = checked
|
||||||
|
visible: (wallpapersList.length > 0)
|
||||||
|
}
|
||||||
|
|
||||||
// Wallpaper grid container
|
// Wallpaper grid container
|
||||||
Item {
|
Item {
|
||||||
|
visible: !WallpaperService.scanning
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: {
|
Layout.preferredHeight: {
|
||||||
return Math.ceil(WallpaperService.wallpaperList.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight
|
return Math.ceil(wallpapersList.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
GridView {
|
GridView {
|
||||||
id: wallpaperGridView
|
id: wallpaperGridView
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
clip: true
|
clip: true
|
||||||
model: WallpaperService.wallpaperList
|
model: wallpapersList
|
||||||
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
flickableDirection: Flickable.AutoFlickDirection
|
flickableDirection: Flickable.VerticalFlick
|
||||||
interactive: false
|
interactive: false
|
||||||
|
|
||||||
property int columns: 5
|
property int columns: 5
|
||||||
|
|
@ -114,7 +116,7 @@ ColumnLayout {
|
||||||
id: wallpaperItem
|
id: wallpaperItem
|
||||||
|
|
||||||
property string wallpaperPath: modelData
|
property string wallpaperPath: modelData
|
||||||
property bool isSelected: wallpaperPath === WallpaperService.currentWallpaper
|
property bool isSelected: wallpaperPath === WallpaperService.getWallpaper(screen.name)
|
||||||
|
|
||||||
width: wallpaperGridView.itemSize
|
width: wallpaperGridView.itemSize
|
||||||
height: Math.floor(wallpaperGridView.itemSize * 0.67)
|
height: Math.floor(wallpaperGridView.itemSize * 0.67)
|
||||||
|
|
@ -179,46 +181,65 @@ ColumnLayout {
|
||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
onClicked: {
|
onClicked: {
|
||||||
WallpaperService.changeWallpaper(wallpaperPath)
|
if (Settings.data.wallpaper.setWallpaperOnAllMonitors) {
|
||||||
|
WallpaperService.changeWallpaper(undefined, wallpaperPath)
|
||||||
|
} else {
|
||||||
|
WallpaperService.changeWallpaper(screen.name, wallpaperPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Empty state
|
// Empty state
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
color: Color.mSurface
|
||||||
|
radius: Style.radiusM * scaling
|
||||||
|
border.color: Color.mOutline
|
||||||
|
border.width: Math.max(1, Style.borderS * scaling)
|
||||||
|
visible: wallpapersList.length === 0 || WallpaperService.scanning
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 130 * scaling
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: Color.mSurface
|
visible: WallpaperService.scanning
|
||||||
radius: Style.radiusM * scaling
|
NBusyIndicator {
|
||||||
border.color: Color.mOutline
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||||
border.width: Math.max(1, Style.borderS * scaling)
|
}
|
||||||
visible: WallpaperService.wallpaperList.length === 0 && !WallpaperService.scanning
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.centerIn: parent
|
anchors.fill: parent
|
||||||
spacing: Style.marginM * scaling
|
visible: wallpapersList.length === 0 && !WallpaperService.scanning
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
NIcon {
|
NIcon {
|
||||||
text: "folder_open"
|
text: "folder_open"
|
||||||
font.pointSize: Style.fontSizeL * scaling
|
font.pointSize: Style.fontSizeXL * scaling
|
||||||
color: Color.mOnSurface
|
color: Color.mOnSurface
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
NText {
|
NText {
|
||||||
text: "No wallpapers found"
|
text: "No wallpaper found."
|
||||||
color: Color.mOnSurface
|
color: Color.mOnSurface
|
||||||
font.weight: Style.fontWeightBold
|
font.weight: Style.fontWeightBold
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
NText {
|
NText {
|
||||||
text: "Make sure your wallpaper directory is configured and contains image files."
|
text: "Make sure your wallpaper directory is configured and contains image files."
|
||||||
color: Color.mOnSurface
|
color: Color.mOnSurfaceVariant
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
horizontalAlignment: Text.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.preferredWidth: Style.sliderWidth * 1.5 * scaling
|
}
|
||||||
}
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Commons
|
import qs.Commons
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
@ -9,36 +10,62 @@ import qs.Widgets
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
// Process to check if swww is installed
|
ColumnLayout {
|
||||||
Process {
|
spacing: Style.marginL * scaling
|
||||||
id: swwwCheck
|
Layout.fillWidth: true
|
||||||
command: ["which", "swww"]
|
NTextInput {
|
||||||
running: false
|
label: "Wallpaper Directory"
|
||||||
|
description: "Path to your common wallpaper directory."
|
||||||
|
text: Settings.data.wallpaper.directory
|
||||||
|
onEditingFinished: {
|
||||||
|
Settings.data.wallpaper.directory = text
|
||||||
|
}
|
||||||
|
Layout.maximumWidth: 420 * scaling
|
||||||
|
}
|
||||||
|
|
||||||
onExited: function (exitCode) {
|
// Monitor-specific directories
|
||||||
if (exitCode === 0) {
|
NToggle {
|
||||||
// SWWW exists, enable it
|
label: "Monitor-specific directories"
|
||||||
Settings.data.wallpaper.swww.enabled = true
|
description: "Enable multi-monitor wallpaper directory management."
|
||||||
WallpaperService.startSWWWDaemon()
|
checked: Settings.data.wallpaper.enableMultiMonitorDirectories
|
||||||
ToastService.showNotice("Swww", "Enabled")
|
onToggled: checked => Settings.data.wallpaper.enableMultiMonitorDirectories = checked
|
||||||
} else {
|
}
|
||||||
// SWWW not found
|
|
||||||
ToastService.showWarning("Swww", "Not installed")
|
NBox {
|
||||||
|
visible: Settings.data.wallpaper.enableMultiMonitorDirectories
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.minimumWidth: 550 * scaling
|
||||||
|
radius: Style.radiusM * scaling
|
||||||
|
color: Color.mSurfaceVariant
|
||||||
|
border.color: Color.mOutline
|
||||||
|
border.width: Math.max(1, Style.borderS * scaling)
|
||||||
|
implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: contentCol
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginXL * scaling
|
||||||
|
spacing: Style.marginM * scaling
|
||||||
|
Repeater {
|
||||||
|
model: Quickshell.screens || []
|
||||||
|
delegate: RowLayout {
|
||||||
|
NText {
|
||||||
|
text: (modelData.name || "Unknown")
|
||||||
|
color: Color.mSecondary
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
Layout.preferredWidth: 90 * scaling
|
||||||
|
}
|
||||||
|
NTextInput {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: WallpaperService.getMonitorDirectory(modelData.name)
|
||||||
|
onEditingFinished: WallpaperService.setMonitorDirectory(modelData.name, text)
|
||||||
|
Layout.maximumWidth: 420 * scaling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout: StdioCollector {}
|
|
||||||
stderr: StdioCollector {}
|
|
||||||
}
|
|
||||||
|
|
||||||
NTextInput {
|
|
||||||
label: "Wallpaper Directory"
|
|
||||||
description: "Path to your wallpaper directory."
|
|
||||||
text: Settings.data.wallpaper.directory
|
|
||||||
onEditingFinished: {
|
|
||||||
Settings.data.wallpaper.directory = text
|
|
||||||
}
|
|
||||||
Layout.maximumWidth: 420 * scaling
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NDivider {
|
NDivider {
|
||||||
|
|
@ -62,10 +89,42 @@ ColumnLayout {
|
||||||
NToggle {
|
NToggle {
|
||||||
label: "Random Wallpaper"
|
label: "Random Wallpaper"
|
||||||
description: "Automatically select random wallpapers from the folder."
|
description: "Automatically select random wallpapers from the folder."
|
||||||
checked: Settings.data.wallpaper.isRandom
|
checked: Settings.data.wallpaper.randomEnabled
|
||||||
onToggled: checked => {
|
onToggled: checked => Settings.data.wallpaper.randomEnabled = checked
|
||||||
Settings.data.wallpaper.isRandom = checked
|
}
|
||||||
}
|
|
||||||
|
// Transition Type
|
||||||
|
NComboBox {
|
||||||
|
label: "Transition Type"
|
||||||
|
description: "Animation type when switching between wallpapers."
|
||||||
|
model: WallpaperService.transitionsModel
|
||||||
|
currentKey: Settings.data.wallpaper.transitionType
|
||||||
|
onSelected: key => Settings.data.wallpaper.transitionType = key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transition Duration
|
||||||
|
ColumnLayout {
|
||||||
|
NLabel {
|
||||||
|
label: "Transition Duration"
|
||||||
|
description: "Duration of transition animations in seconds."
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: Style.marginL * scaling
|
||||||
|
NSlider {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
from: 100
|
||||||
|
to: 5000
|
||||||
|
stepSize: 100
|
||||||
|
value: Settings.data.wallpaper.transitionDuration
|
||||||
|
onMoved: Settings.data.wallpaper.transitionDuration = value
|
||||||
|
cutoutColor: Color.mSurface
|
||||||
|
}
|
||||||
|
NText {
|
||||||
|
text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(2) + "s"
|
||||||
|
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interval (slider + H:M inputs)
|
// Interval (slider + H:M inputs)
|
||||||
|
|
@ -79,25 +138,28 @@ ColumnLayout {
|
||||||
|
|
||||||
NText {
|
NText {
|
||||||
// Show friendly H:MM format from current settings
|
// Show friendly H:MM format from current settings
|
||||||
text: Time.formatVagueHumanReadableDuration(Settings.data.wallpaper.randomInterval)
|
text: Time.formatVagueHumanReadableDuration(Settings.data.wallpaper.randomIntervalSec)
|
||||||
Layout.alignment: Qt.AlignBottom | Qt.AlignRight
|
Layout.alignment: Qt.AlignBottom | Qt.AlignRight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preset chips
|
// Preset chips using Repeater
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: presetRow
|
id: presetRow
|
||||||
spacing: Style.marginS * scaling
|
spacing: Style.marginS * scaling
|
||||||
|
|
||||||
// Preset seconds list
|
// Factorized presets data
|
||||||
property var presets: [15 * 60, 30 * 60, 45 * 60, 60 * 60, 90 * 60, 120 * 60]
|
property var intervalPresets: [5 * 60, 10 * 60, 15 * 60, 30 * 60, 45 * 60, 60 * 60, 90 * 60, 120 * 60]
|
||||||
|
|
||||||
// Whether current interval equals one of the presets
|
// Whether current interval equals one of the presets
|
||||||
property bool isCurrentPreset: presets.indexOf(Settings.data.wallpaper.randomInterval) !== -1
|
property bool isCurrentPreset: {
|
||||||
|
return intervalPresets.some(seconds => seconds === Settings.data.wallpaper.randomIntervalSec)
|
||||||
|
}
|
||||||
// Allow user to force open the custom input; otherwise it's auto-open when not a preset
|
// Allow user to force open the custom input; otherwise it's auto-open when not a preset
|
||||||
property bool customForcedVisible: false
|
property bool customForcedVisible: false
|
||||||
|
|
||||||
function setIntervalSeconds(sec) {
|
function setIntervalSeconds(sec) {
|
||||||
Settings.data.wallpaper.randomInterval = sec
|
Settings.data.wallpaper.randomIntervalSec = sec
|
||||||
WallpaperService.restartRandomWallpaperTimer()
|
WallpaperService.restartRandomWallpaperTimer()
|
||||||
// Hide custom when selecting a preset
|
// Hide custom when selecting a preset
|
||||||
customForcedVisible = false
|
customForcedVisible = false
|
||||||
|
|
@ -105,168 +167,25 @@ ColumnLayout {
|
||||||
|
|
||||||
// Helper to color selected chip
|
// Helper to color selected chip
|
||||||
function isSelected(sec) {
|
function isSelected(sec) {
|
||||||
return Settings.data.wallpaper.randomInterval === sec
|
return Settings.data.wallpaper.randomIntervalSec === sec
|
||||||
}
|
}
|
||||||
|
|
||||||
// 15m
|
// Repeater for preset chips
|
||||||
Rectangle {
|
Repeater {
|
||||||
radius: height * 0.5
|
model: presetRow.intervalPresets
|
||||||
color: presetRow.isSelected(15 * 60) ? Color.mPrimary : Color.mSurfaceVariant
|
delegate: IntervalPresetChip {
|
||||||
implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling)
|
seconds: modelData
|
||||||
implicitWidth: label15.implicitWidth + Style.marginM * 1.5 * scaling
|
label: Time.formatVagueHumanReadableDuration(modelData)
|
||||||
border.width: 1
|
selected: presetRow.isSelected(modelData)
|
||||||
border.color: presetRow.isSelected(15 * 60) ? Color.transparent : Color.mOutline
|
onClicked: presetRow.setIntervalSeconds(modelData)
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: presetRow.setIntervalSeconds(15 * 60)
|
|
||||||
}
|
|
||||||
NText {
|
|
||||||
id: label15
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "15m"
|
|
||||||
font.pointSize: Style.fontSizeS * scaling
|
|
||||||
font.weight: Style.fontWeightMedium
|
|
||||||
color: presetRow.isSelected(15 * 60) ? Color.mOnPrimary : Color.mOnSurface
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 30m
|
|
||||||
Rectangle {
|
|
||||||
radius: height * 0.5
|
|
||||||
color: presetRow.isSelected(30 * 60) ? Color.mPrimary : Color.mSurfaceVariant
|
|
||||||
implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling)
|
|
||||||
implicitWidth: label30.implicitWidth + Style.marginM * 1.5 * scaling
|
|
||||||
border.width: 1
|
|
||||||
border.color: presetRow.isSelected(30 * 60) ? Color.transparent : Color.mOutline
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: presetRow.setIntervalSeconds(30 * 60)
|
|
||||||
}
|
|
||||||
NText {
|
|
||||||
id: label30
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "30m"
|
|
||||||
font.pointSize: Style.fontSizeS * scaling
|
|
||||||
font.weight: Style.fontWeightMedium
|
|
||||||
color: presetRow.isSelected(30 * 60) ? Color.mOnPrimary : Color.mOnSurface
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 45m
|
|
||||||
Rectangle {
|
|
||||||
radius: height * 0.5
|
|
||||||
color: presetRow.isSelected(45 * 60) ? Color.mPrimary : Color.mSurfaceVariant
|
|
||||||
implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling)
|
|
||||||
implicitWidth: label45.implicitWidth + Style.marginM * 1.5 * scaling
|
|
||||||
border.width: 1
|
|
||||||
border.color: presetRow.isSelected(45 * 60) ? Color.transparent : Color.mOutline
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: presetRow.setIntervalSeconds(45 * 60)
|
|
||||||
}
|
|
||||||
NText {
|
|
||||||
id: label45
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "45m"
|
|
||||||
font.pointSize: Style.fontSizeS * scaling
|
|
||||||
font.weight: Style.fontWeightMedium
|
|
||||||
color: presetRow.isSelected(45 * 60) ? Color.mOnPrimary : Color.mOnSurface
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1h
|
|
||||||
Rectangle {
|
|
||||||
radius: height * 0.5
|
|
||||||
color: presetRow.isSelected(60 * 60) ? Color.mPrimary : Color.mSurfaceVariant
|
|
||||||
implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling)
|
|
||||||
implicitWidth: label1h.implicitWidth + Style.marginM * 1.5 * scaling
|
|
||||||
border.width: 1
|
|
||||||
border.color: presetRow.isSelected(60 * 60) ? Color.transparent : Color.mOutline
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: presetRow.setIntervalSeconds(60 * 60)
|
|
||||||
}
|
|
||||||
NText {
|
|
||||||
id: label1h
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "1h"
|
|
||||||
font.pointSize: Style.fontSizeS * scaling
|
|
||||||
font.weight: Style.fontWeightMedium
|
|
||||||
color: presetRow.isSelected(60 * 60) ? Color.mOnPrimary : Color.mOnSurface
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1h 30m
|
|
||||||
Rectangle {
|
|
||||||
radius: height * 0.5
|
|
||||||
color: presetRow.isSelected(90 * 60) ? Color.mPrimary : Color.mSurfaceVariant
|
|
||||||
implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling)
|
|
||||||
implicitWidth: label90.implicitWidth + Style.marginM * 1.5 * scaling
|
|
||||||
border.width: 1
|
|
||||||
border.color: presetRow.isSelected(90 * 60) ? Color.transparent : Color.mOutline
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: presetRow.setIntervalSeconds(90 * 60)
|
|
||||||
}
|
|
||||||
NText {
|
|
||||||
id: label90
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "1h 30m"
|
|
||||||
font.pointSize: Style.fontSizeS * scaling
|
|
||||||
font.weight: Style.fontWeightMedium
|
|
||||||
color: presetRow.isSelected(90 * 60) ? Color.mOnPrimary : Color.mOnSurface
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2h
|
|
||||||
Rectangle {
|
|
||||||
radius: height * 0.5
|
|
||||||
color: presetRow.isSelected(120 * 60) ? Color.mPrimary : Color.mSurfaceVariant
|
|
||||||
implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling)
|
|
||||||
implicitWidth: label2h.implicitWidth + Style.marginM * 1.5 * scaling
|
|
||||||
border.width: 1
|
|
||||||
border.color: presetRow.isSelected(120 * 60) ? Color.transparent : Color.mOutline
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: presetRow.setIntervalSeconds(120 * 60)
|
|
||||||
}
|
|
||||||
NText {
|
|
||||||
id: label2h
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "2h"
|
|
||||||
font.pointSize: Style.fontSizeS * scaling
|
|
||||||
font.weight: Style.fontWeightMedium
|
|
||||||
color: presetRow.isSelected(120 * 60) ? Color.mOnPrimary : Color.mOnSurface
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom… opens inline input
|
// Custom… opens inline input
|
||||||
Rectangle {
|
IntervalPresetChip {
|
||||||
radius: height * 0.5
|
label: customRow.visible ? "Custom" : "Custom…"
|
||||||
color: customRow.visible ? Color.mPrimary : Color.mSurfaceVariant
|
selected: customRow.visible
|
||||||
implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling)
|
onClicked: presetRow.customForcedVisible = !presetRow.customForcedVisible
|
||||||
implicitWidth: labelCustom.implicitWidth + Style.marginM * 1.5 * scaling
|
|
||||||
border.width: 1
|
|
||||||
border.color: customRow.visible ? Color.transparent : Color.mOutline
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: presetRow.customForcedVisible = !presetRow.customForcedVisible
|
|
||||||
}
|
|
||||||
NText {
|
|
||||||
id: labelCustom
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: customRow.visible ? "Custom" : "Custom…"
|
|
||||||
font.pointSize: Style.fontSizeS * scaling
|
|
||||||
font.weight: Style.fontWeightMedium
|
|
||||||
color: customRow.visible ? Color.mOnPrimary : Color.mOnSurface
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,12 +201,11 @@ ColumnLayout {
|
||||||
description: "Enter time as HH:MM (e.g., 01:30)."
|
description: "Enter time as HH:MM (e.g., 01:30)."
|
||||||
inputMaxWidth: 100 * scaling
|
inputMaxWidth: 100 * scaling
|
||||||
text: {
|
text: {
|
||||||
const s = Settings.data.wallpaper.randomInterval
|
const s = Settings.data.wallpaper.randomIntervalSec
|
||||||
const h = Math.floor(s / 3600)
|
const h = Math.floor(s / 3600)
|
||||||
const m = Math.floor((s % 3600) / 60)
|
const m = Math.floor((s % 3600) / 60)
|
||||||
return h + ":" + (m < 10 ? ("0" + m) : m)
|
return h + ":" + (m < 10 ? ("0" + m) : m)
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditingFinished: {
|
onEditingFinished: {
|
||||||
const m = text.trim().match(/^(\d{1,2}):(\d{2})$/)
|
const m = text.trim().match(/^(\d{1,2}):(\d{2})$/)
|
||||||
if (m) {
|
if (m) {
|
||||||
|
|
@ -297,7 +215,7 @@ ColumnLayout {
|
||||||
return
|
return
|
||||||
h = Math.max(0, Math.min(24, h))
|
h = Math.max(0, Math.min(24, h))
|
||||||
min = Math.max(0, Math.min(59, min))
|
min = Math.max(0, Math.min(59, min))
|
||||||
Settings.data.wallpaper.randomInterval = (h * 3600) + (min * 60)
|
Settings.data.wallpaper.randomIntervalSec = (h * 3600) + (min * 60)
|
||||||
WallpaperService.restartRandomWallpaperTimer()
|
WallpaperService.restartRandomWallpaperTimer()
|
||||||
// Keep custom visible after manual entry
|
// Keep custom visible after manual entry
|
||||||
presetRow.customForcedVisible = true
|
presetRow.customForcedVisible = true
|
||||||
|
|
@ -308,193 +226,32 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NDivider {
|
// Reusable component for interval preset chips
|
||||||
Layout.fillWidth: true
|
component IntervalPresetChip: Rectangle {
|
||||||
Layout.topMargin: Style.marginXL * scaling
|
property int seconds: 0
|
||||||
Layout.bottomMargin: Style.marginXL * scaling
|
property string label: ""
|
||||||
}
|
property bool selected: false
|
||||||
|
signal clicked
|
||||||
|
|
||||||
// -------------------------------
|
radius: height * 0.5
|
||||||
// Swww
|
color: selected ? Color.mPrimary : Color.mSurfaceVariant
|
||||||
ColumnLayout {
|
implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling)
|
||||||
spacing: Style.marginL * scaling
|
implicitWidth: chipLabel.implicitWidth + Style.marginM * 1.5 * scaling
|
||||||
Layout.fillWidth: true
|
border.width: 1
|
||||||
|
border.color: selected ? Color.transparent : Color.mOutline
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: parent.clicked()
|
||||||
|
}
|
||||||
|
|
||||||
NText {
|
NText {
|
||||||
text: "Swww"
|
id: chipLabel
|
||||||
font.pointSize: Style.fontSizeXXL * scaling
|
anchors.centerIn: parent
|
||||||
font.weight: Style.fontWeightBold
|
text: parent.label
|
||||||
color: Color.mSecondary
|
font.pointSize: Style.fontSizeS * scaling
|
||||||
}
|
color: parent.selected ? Color.mOnPrimary : Color.mOnSurface
|
||||||
|
|
||||||
// Use SWWW
|
|
||||||
NToggle {
|
|
||||||
label: "Use Swww"
|
|
||||||
description: "Use Swww daemon for advanced wallpaper management."
|
|
||||||
checked: Settings.data.wallpaper.swww.enabled
|
|
||||||
onToggled: checked => {
|
|
||||||
if (checked) {
|
|
||||||
// Check if swww is installed
|
|
||||||
swwwCheck.running = true
|
|
||||||
} else {
|
|
||||||
Settings.data.wallpaper.swww.enabled = false
|
|
||||||
ToastService.showNotice("Swww", "Disabled")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SWWW Settings (only visible when useSWWW is enabled)
|
|
||||||
ColumnLayout {
|
|
||||||
spacing: Style.marginS * scaling
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: Style.marginS * scaling
|
|
||||||
visible: Settings.data.wallpaper.swww.enabled
|
|
||||||
|
|
||||||
// Resize Mode
|
|
||||||
NComboBox {
|
|
||||||
label: "Resize Mode"
|
|
||||||
description: "How Swww should resize wallpapers to fit the screen."
|
|
||||||
model: ListModel {
|
|
||||||
ListElement {
|
|
||||||
key: "no"
|
|
||||||
name: "No"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "crop"
|
|
||||||
name: "Crop"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "fit"
|
|
||||||
name: "Fit"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "stretch"
|
|
||||||
name: "Stretch"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentKey: Settings.data.wallpaper.swww.resizeMethod
|
|
||||||
onSelected: key => {
|
|
||||||
Settings.data.wallpaper.swww.resizeMethod = key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transition Type
|
|
||||||
NComboBox {
|
|
||||||
label: "Transition Type"
|
|
||||||
description: "Animation type when switching between wallpapers."
|
|
||||||
model: ListModel {
|
|
||||||
ListElement {
|
|
||||||
key: "none"
|
|
||||||
name: "None"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "simple"
|
|
||||||
name: "Simple"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "fade"
|
|
||||||
name: "Fade"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "left"
|
|
||||||
name: "Left"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "right"
|
|
||||||
name: "Right"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "top"
|
|
||||||
name: "Top"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "bottom"
|
|
||||||
name: "Bottom"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "wipe"
|
|
||||||
name: "Wipe"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "wave"
|
|
||||||
name: "Wave"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "grow"
|
|
||||||
name: "Grow"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "center"
|
|
||||||
name: "Center"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "any"
|
|
||||||
name: "Any"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "outer"
|
|
||||||
name: "Outer"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
key: "random"
|
|
||||||
name: "Random"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentKey: Settings.data.wallpaper.swww.transitionType
|
|
||||||
onSelected: key => {
|
|
||||||
Settings.data.wallpaper.swww.transitionType = key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transition FPS
|
|
||||||
ColumnLayout {
|
|
||||||
NLabel {
|
|
||||||
label: "Transition FPS"
|
|
||||||
description: "Frames per second for transition animations."
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
spacing: Style.marginL * scaling
|
|
||||||
NSlider {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
from: 30
|
|
||||||
to: 500
|
|
||||||
stepSize: 5
|
|
||||||
value: Settings.data.wallpaper.swww.transitionFps
|
|
||||||
onMoved: Settings.data.wallpaper.swww.transitionFps = Math.round(value)
|
|
||||||
cutoutColor: Color.mSurface
|
|
||||||
}
|
|
||||||
NText {
|
|
||||||
text: Settings.data.wallpaper.swww.transitionFps + " FPS"
|
|
||||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transition Duration
|
|
||||||
ColumnLayout {
|
|
||||||
NLabel {
|
|
||||||
label: "Transition Duration"
|
|
||||||
description: "Duration of transition animations in seconds."
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
spacing: Style.marginL * scaling
|
|
||||||
NSlider {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
from: 0.25
|
|
||||||
to: 10
|
|
||||||
stepSize: 0.05
|
|
||||||
value: Settings.data.wallpaper.swww.transitionDuration
|
|
||||||
onMoved: Settings.data.wallpaper.swww.transitionDuration = value
|
|
||||||
cutoutColor: Color.mSurface
|
|
||||||
}
|
|
||||||
NText {
|
|
||||||
text: Settings.data.wallpaper.swww.transitionDuration.toFixed(2) + "s"
|
|
||||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
30
README.md
30
README.md
|
|
@ -27,11 +27,11 @@ Features a modern modular architecture with a status bar, notification system, c
|
||||||
|
|
||||||
## Preview
|
## Preview
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -75,21 +75,14 @@ Features a modern modular architecture with a status bar, notification system, c
|
||||||
### Optional
|
### Optional
|
||||||
|
|
||||||
- `cliphist` - For clipboard history support
|
- `cliphist` - For clipboard history support
|
||||||
- `swww` - Wallpaper animations and effects
|
|
||||||
- `matugen` - Material You color scheme generation
|
- `matugen` - Material You color scheme generation
|
||||||
- `cava` - Audio visualizer component
|
- `cava` - Audio visualizer component
|
||||||
- `wlsunset` - To be able to use NightLight
|
- `wlsunset` - To be able to use NightLight
|
||||||
|
|
||||||
> There is one more optional dependency.
|
> There are 2 more optional dependencies.
|
||||||
|
> Any `polkit agent` to be able to use the ArchUpdater widget.
|
||||||
> And also any `xdg-desktop-portal` to be able to use the "Portal" option from the screenRecorder.
|
> And also any `xdg-desktop-portal` to be able to use the "Portal" option from the screenRecorder.
|
||||||
|
|
||||||
If you want to use the ArchUpdater please make sure to set your terminal with the `TERMINAL` environment variable.
|
|
||||||
```
|
|
||||||
sudo sed -i '/^TERMINAL=/d' /etc/environment && echo 'TERMINAL=/usr/bin/kitty' | sudo tee -a /etc/environment
|
|
||||||
|
|
||||||
```
|
|
||||||
In that command you **NEED** to replace kitty with whatever your terminal is (and perhaps edit the path depending on the distro).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
@ -276,14 +269,6 @@ The launcher supports special commands for enhanced functionality:
|
||||||
|
|
||||||
## Advanced Configuration
|
## Advanced Configuration
|
||||||
|
|
||||||
### Niri Configuration
|
|
||||||
|
|
||||||
Add this to your `layout` section for proper swww integration:
|
|
||||||
|
|
||||||
```
|
|
||||||
background-color "transparent"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Recommended Compositor Settings
|
### Recommended Compositor Settings
|
||||||
|
|
||||||
For Niri:
|
For Niri:
|
||||||
|
|
@ -294,11 +279,6 @@ window-rule {
|
||||||
clip-to-geometry true
|
clip-to-geometry true
|
||||||
}
|
}
|
||||||
|
|
||||||
layer-rule {
|
|
||||||
match namespace="^swww-daemon$"
|
|
||||||
place-within-backdrop true
|
|
||||||
}
|
|
||||||
|
|
||||||
layer-rule {
|
layer-rule {
|
||||||
match namespace="^quickshell-wallpaper$"
|
match namespace="^quickshell-wallpaper$"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,164 +8,28 @@ import qs.Commons
|
||||||
Singleton {
|
Singleton {
|
||||||
id: updateService
|
id: updateService
|
||||||
|
|
||||||
// ============================================================================
|
// Core properties
|
||||||
// CORE PROPERTIES
|
readonly property bool busy: checkupdatesProcess.running
|
||||||
// ============================================================================
|
readonly property bool aurBusy: checkAurUpdatesProcess.running
|
||||||
|
readonly property int updates: repoPackages.length
|
||||||
// Package data
|
readonly property int aurUpdates: aurPackages.length
|
||||||
|
readonly property int totalUpdates: updates + aurUpdates
|
||||||
property var repoPackages: []
|
property var repoPackages: []
|
||||||
property var aurPackages: []
|
property var aurPackages: []
|
||||||
property var selectedPackages: []
|
property var selectedPackages: []
|
||||||
property int selectedPackagesCount: 0
|
property int selectedPackagesCount: 0
|
||||||
|
|
||||||
// Update state
|
|
||||||
property bool updateInProgress: false
|
property bool updateInProgress: false
|
||||||
property bool updateFailed: false
|
|
||||||
property string lastUpdateError: ""
|
|
||||||
|
|
||||||
// Computed properties
|
|
||||||
readonly property bool busy: checkupdatesProcess.running
|
|
||||||
readonly property bool aurBusy: checkParuUpdatesProcess.running
|
|
||||||
readonly property int updates: repoPackages.length
|
|
||||||
readonly property int aurUpdates: aurPackages.length
|
|
||||||
readonly property int totalUpdates: updates + aurUpdates
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// TIMERS
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// Refresh timer for post-update polling
|
|
||||||
Timer {
|
|
||||||
id: refreshTimer
|
|
||||||
interval: 5000 // Increased delay to ensure updates complete
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
console.log("ArchUpdater: Refreshing package lists after update...")
|
|
||||||
// Just refresh package lists without syncing database
|
|
||||||
doPoll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timer to mark update as complete - with error handling
|
|
||||||
Timer {
|
|
||||||
id: updateCompleteTimer
|
|
||||||
interval: 30000 // Increased to 30 seconds to allow more time
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
console.log("ArchUpdater: Update timeout reached, checking for failures...")
|
|
||||||
checkForUpdateFailures()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timer to check if update processes are still running
|
|
||||||
Timer {
|
|
||||||
id: updateMonitorTimer
|
|
||||||
interval: 2000
|
|
||||||
repeat: true
|
|
||||||
running: updateInProgress
|
|
||||||
onTriggered: {
|
|
||||||
// Check if any update-related processes might still be running
|
|
||||||
checkUpdateStatus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// MONITORING PROCESSES
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// Process to monitor update completion
|
|
||||||
Process {
|
|
||||||
id: updateStatusProcess
|
|
||||||
command: ["pgrep", "-f", "(pacman|yay|paru).*(-S|-Syu)"]
|
|
||||||
onExited: function (exitCode) {
|
|
||||||
if (exitCode !== 0 && updateInProgress) {
|
|
||||||
// No update processes found, update likely completed
|
|
||||||
console.log("ArchUpdater: No update processes detected, marking update as complete")
|
|
||||||
updateInProgress = false
|
|
||||||
updateMonitorTimer.stop()
|
|
||||||
|
|
||||||
// Don't stop the complete timer - let it handle failures
|
|
||||||
// If the update actually failed, the timer will trigger and set updateFailed = true
|
|
||||||
|
|
||||||
// Refresh package lists after a short delay
|
|
||||||
Qt.callLater(() => {
|
|
||||||
doPoll()
|
|
||||||
}, 2000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process to check for errors in log file (only when update is in progress)
|
|
||||||
Process {
|
|
||||||
id: errorCheckProcess
|
|
||||||
command: ["sh", "-c", "if [ -f /tmp/archupdater_output.log ]; then grep -i 'error\\|failed\\|failed to build\\|ERROR_DETECTED' /tmp/archupdater_output.log | tail -1; fi"]
|
|
||||||
onExited: function (exitCode) {
|
|
||||||
if (exitCode === 0 && updateInProgress) {
|
|
||||||
// Error found in log
|
|
||||||
console.log("ArchUpdater: Error detected in log file")
|
|
||||||
updateInProgress = false
|
|
||||||
updateFailed = true
|
|
||||||
updateCompleteTimer.stop()
|
|
||||||
updateMonitorTimer.stop()
|
|
||||||
lastUpdateError = "Build or update error detected"
|
|
||||||
|
|
||||||
// Refresh to check actual state
|
|
||||||
Qt.callLater(() => {
|
|
||||||
doPoll()
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timer to check for errors more frequently when update is in progress
|
|
||||||
Timer {
|
|
||||||
id: errorCheckTimer
|
|
||||||
interval: 5000 // Check every 5 seconds
|
|
||||||
repeat: true
|
|
||||||
running: updateInProgress
|
|
||||||
onTriggered: {
|
|
||||||
if (updateInProgress && !errorCheckProcess.running) {
|
|
||||||
errorCheckProcess.running = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// MONITORING FUNCTIONS
|
|
||||||
// ============================================================================
|
|
||||||
function checkUpdateStatus() {
|
|
||||||
if (updateInProgress && !updateStatusProcess.running) {
|
|
||||||
updateStatusProcess.running = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkForUpdateFailures() {
|
|
||||||
console.log("ArchUpdater: Checking for update failures...")
|
|
||||||
updateInProgress = false
|
|
||||||
updateFailed = true
|
|
||||||
updateCompleteTimer.stop()
|
|
||||||
updateMonitorTimer.stop()
|
|
||||||
|
|
||||||
// Refresh to check actual state after a delay
|
|
||||||
Qt.callLater(() => {
|
|
||||||
doPoll()
|
|
||||||
}, 2000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initial check
|
// Initial check
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
getAurHelper()
|
|
||||||
doPoll()
|
doPoll()
|
||||||
|
doAurPoll()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// PACKAGE CHECKING PROCESSES
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// Process for checking repo updates
|
// Process for checking repo updates
|
||||||
Process {
|
Process {
|
||||||
id: checkupdatesProcess
|
id: checkupdatesProcess
|
||||||
command: ["checkupdates", "--nosync"]
|
command: ["checkupdates"]
|
||||||
onExited: function (exitCode) {
|
onExited: function (exitCode) {
|
||||||
if (exitCode !== 0 && exitCode !== 2) {
|
if (exitCode !== 0 && exitCode !== 2) {
|
||||||
Logger.warn("ArchUpdater", "checkupdates failed (code:", exitCode, ")")
|
Logger.warn("ArchUpdater", "checkupdates failed (code:", exitCode, ")")
|
||||||
|
|
@ -180,13 +44,13 @@ Singleton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process for checking AUR updates with paru specifically
|
// Process for checking AUR updates
|
||||||
Process {
|
Process {
|
||||||
id: checkParuUpdatesProcess
|
id: checkAurUpdatesProcess
|
||||||
command: ["paru", "-Qua"]
|
command: ["sh", "-c", "command -v yay >/dev/null 2>&1 && yay -Qua || command -v paru >/dev/null 2>&1 && paru -Qua || echo ''"]
|
||||||
onExited: function (exitCode) {
|
onExited: function (exitCode) {
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
Logger.warn("ArchUpdater", "paru check failed (code:", exitCode, ")")
|
Logger.warn("ArchUpdater", "AUR check failed (code:", exitCode, ")")
|
||||||
aurPackages = []
|
aurPackages = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -198,12 +62,8 @@ Singleton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// Parse checkupdates output
|
||||||
// PARSING FUNCTIONS
|
function parseCheckupdatesOutput(output) {
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// Generic package parsing function
|
|
||||||
function parsePackageOutput(output, source) {
|
|
||||||
const lines = output.trim().split('\n').filter(line => line.trim())
|
const lines = output.trim().split('\n').filter(line => line.trim())
|
||||||
const packages = []
|
const packages = []
|
||||||
|
|
||||||
|
|
@ -215,83 +75,65 @@ Singleton {
|
||||||
"oldVersion": m[2],
|
"oldVersion": m[2],
|
||||||
"newVersion": m[3],
|
"newVersion": m[3],
|
||||||
"description": `${m[1]} ${m[2]} -> ${m[3]}`,
|
"description": `${m[1]} ${m[2]} -> ${m[3]}`,
|
||||||
"source": source
|
"source": "repo"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only update if we have new data or if this is a fresh check
|
repoPackages = packages
|
||||||
if (packages.length > 0 || output.trim() === "") {
|
|
||||||
if (source === "repo") {
|
|
||||||
repoPackages = packages
|
|
||||||
} else {
|
|
||||||
aurPackages = packages
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse checkupdates output
|
|
||||||
function parseCheckupdatesOutput(output) {
|
|
||||||
parsePackageOutput(output, "repo")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse AUR updates output
|
// Parse AUR updates output
|
||||||
function parseAurUpdatesOutput(output) {
|
function parseAurUpdatesOutput(output) {
|
||||||
parsePackageOutput(output, "aur")
|
const lines = output.trim().split('\n').filter(line => line.trim())
|
||||||
|
const packages = []
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const m = line.match(/^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/)
|
||||||
|
if (m) {
|
||||||
|
packages.push({
|
||||||
|
"name": m[1],
|
||||||
|
"oldVersion": m[2],
|
||||||
|
"newVersion": m[3],
|
||||||
|
"description": `${m[1]} ${m[2]} -> ${m[3]}`,
|
||||||
|
"source": "aur"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aurPackages = packages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for updates
|
||||||
function doPoll() {
|
function doPoll() {
|
||||||
// Start repo updates check
|
if (busy)
|
||||||
if (!busy) {
|
return
|
||||||
checkupdatesProcess.running = true
|
checkupdatesProcess.running = true
|
||||||
}
|
|
||||||
|
|
||||||
// Start AUR updates check
|
|
||||||
if (!aurBusy) {
|
|
||||||
checkParuUpdatesProcess.running = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// Check for AUR updates
|
||||||
// UPDATE FUNCTIONS
|
function doAurPoll() {
|
||||||
// ============================================================================
|
if (aurBusy)
|
||||||
|
return
|
||||||
// Helper function to generate update command with error detection
|
checkAurUpdatesProcess.running = true
|
||||||
function generateUpdateCommand(baseCommand) {
|
|
||||||
return baseCommand + " 2>&1 | tee /tmp/archupdater_output.log; if [ $? -ne 0 ]; then echo 'ERROR_DETECTED'; fi; echo 'Update complete! Press Enter to close...'; read -p 'Press Enter to continue...'"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update all packages (repo + AUR)
|
// Update all packages (repo + AUR)
|
||||||
function runUpdate() {
|
function runUpdate() {
|
||||||
if (totalUpdates === 0) {
|
if (totalUpdates === 0) {
|
||||||
doPoll()
|
doPoll()
|
||||||
|
doAurPoll()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset any previous error states
|
|
||||||
updateFailed = false
|
|
||||||
lastUpdateError = ""
|
|
||||||
updateInProgress = true
|
updateInProgress = true
|
||||||
console.log("ArchUpdater: Starting full system update...")
|
// Update repos first, then AUR
|
||||||
|
Quickshell.execDetached(["pkexec", "pacman", "-Syu", "--noconfirm"])
|
||||||
|
Quickshell.execDetached(
|
||||||
|
["sh", "-c", "command -v yay >/dev/null 2>&1 && yay -Sua --noconfirm || command -v paru >/dev/null 2>&1 && paru -Sua --noconfirm || true"])
|
||||||
|
|
||||||
const terminal = Quickshell.env("TERMINAL") || "xterm"
|
// Refresh after updates with multiple attempts
|
||||||
|
refreshAfterUpdate()
|
||||||
// Check if we have an AUR helper for full system update
|
|
||||||
const aurHelper = getAurHelper()
|
|
||||||
if (aurHelper && (aurUpdates > 0 || updates > 0)) {
|
|
||||||
// Use AUR helper for full system update (handles both repo and AUR)
|
|
||||||
const command = generateUpdateCommand(aurHelper + " -Syu")
|
|
||||||
Quickshell.execDetached([terminal, "-e", "bash", "-c", command])
|
|
||||||
} else if (updates > 0) {
|
|
||||||
// Fallback to pacman if no AUR helper or only repo updates
|
|
||||||
const command = generateUpdateCommand("sudo pacman -Syu")
|
|
||||||
Quickshell.execDetached([terminal, "-e", "bash", "-c", command])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start monitoring and timeout timers
|
|
||||||
refreshTimer.start()
|
|
||||||
updateCompleteTimer.start()
|
|
||||||
updateMonitorTimer.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update selected packages
|
// Update selected packages
|
||||||
|
|
@ -299,13 +141,7 @@ Singleton {
|
||||||
if (selectedPackages.length === 0)
|
if (selectedPackages.length === 0)
|
||||||
return
|
return
|
||||||
|
|
||||||
// Reset any previous error states
|
|
||||||
updateFailed = false
|
|
||||||
lastUpdateError = ""
|
|
||||||
updateInProgress = true
|
updateInProgress = true
|
||||||
console.log("ArchUpdater: Starting selective update for", selectedPackages.length, "packages")
|
|
||||||
|
|
||||||
const terminal = Quickshell.env("TERMINAL") || "xterm"
|
|
||||||
|
|
||||||
// Split selected packages by source
|
// Split selected packages by source
|
||||||
const repoPkgs = []
|
const repoPkgs = []
|
||||||
|
|
@ -323,140 +159,48 @@ Singleton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update repo packages with sudo
|
// Update repo packages
|
||||||
if (repoPkgs.length > 0) {
|
if (repoPkgs.length > 0) {
|
||||||
const packageList = repoPkgs.join(" ")
|
const repoCommand = ["pkexec", "pacman", "-S", "--noconfirm"].concat(repoPkgs)
|
||||||
const command = generateUpdateCommand("sudo pacman -S " + packageList)
|
Logger.log("ArchUpdater", "Running repo command:", repoCommand.join(" "))
|
||||||
Quickshell.execDetached([terminal, "-e", "bash", "-c", command])
|
Quickshell.execDetached(repoCommand)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update AUR packages with yay/paru
|
// Update AUR packages
|
||||||
if (aurPkgs.length > 0) {
|
if (aurPkgs.length > 0) {
|
||||||
const aurHelper = getAurHelper()
|
const aurHelper = getAurHelper()
|
||||||
if (aurHelper) {
|
if (aurHelper) {
|
||||||
const packageList = aurPkgs.join(" ")
|
const aurCommand = [aurHelper, "-S", "--noconfirm"].concat(aurPkgs)
|
||||||
const command = generateUpdateCommand(aurHelper + " -S " + packageList)
|
Logger.log("ArchUpdater", "Running AUR command:", aurCommand.join(" "))
|
||||||
Quickshell.execDetached([terminal, "-e", "bash", "-c", command])
|
Quickshell.execDetached(aurCommand)
|
||||||
} else {
|
} else {
|
||||||
Logger.warn("ArchUpdater", "No AUR helper found for packages:", aurPkgs.join(", "))
|
Logger.warn("ArchUpdater", "No AUR helper found for packages:", aurPkgs.join(", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start monitoring and timeout timers
|
// Clear selection and refresh
|
||||||
refreshTimer.start()
|
selectedPackages = []
|
||||||
updateCompleteTimer.start()
|
selectedPackagesCount = 0
|
||||||
updateMonitorTimer.start()
|
refreshAfterUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset update state (useful for manual recovery)
|
|
||||||
function resetUpdateState() {
|
|
||||||
// If update is in progress, mark it as failed first
|
|
||||||
if (updateInProgress) {
|
|
||||||
updateFailed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
updateInProgress = false
|
|
||||||
lastUpdateError = ""
|
|
||||||
updateCompleteTimer.stop()
|
|
||||||
updateMonitorTimer.stop()
|
|
||||||
refreshTimer.stop()
|
|
||||||
|
|
||||||
// Refresh to get current state
|
|
||||||
doPoll()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manual refresh function
|
|
||||||
function forceRefresh() {
|
|
||||||
// Prevent multiple simultaneous refreshes
|
|
||||||
if (busy || aurBusy) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear error states when refreshing
|
|
||||||
updateFailed = false
|
|
||||||
lastUpdateError = ""
|
|
||||||
|
|
||||||
// Just refresh the package lists without syncing databases
|
|
||||||
doPoll()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// UTILITY PROCESSES
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// Process for checking yay availability
|
|
||||||
Process {
|
|
||||||
id: yayCheckProcess
|
|
||||||
command: ["which", "yay"]
|
|
||||||
onExited: function (exitCode) {
|
|
||||||
if (exitCode === 0) {
|
|
||||||
cachedAurHelper = "yay"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process for checking paru availability
|
|
||||||
Process {
|
|
||||||
id: paruCheckProcess
|
|
||||||
command: ["which", "paru"]
|
|
||||||
onExited: function (exitCode) {
|
|
||||||
if (exitCode === 0) {
|
|
||||||
if (cachedAurHelper === "") {
|
|
||||||
cachedAurHelper = "paru"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process for syncing package databases with sudo
|
|
||||||
Process {
|
|
||||||
id: syncDatabaseProcess
|
|
||||||
command: ["sudo", "pacman", "-Sy"]
|
|
||||||
onStarted: {
|
|
||||||
console.log("ArchUpdater: Starting database sync with sudo...")
|
|
||||||
}
|
|
||||||
onExited: function (exitCode) {
|
|
||||||
console.log("ArchUpdater: Database sync exited with code:", exitCode)
|
|
||||||
if (exitCode === 0) {
|
|
||||||
console.log("ArchUpdater: Database sync successful")
|
|
||||||
} else {
|
|
||||||
console.log("ArchUpdater: Database sync failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// After sync completes, wait a moment then refresh package lists
|
|
||||||
console.log("ArchUpdater: Database sync complete, waiting before refresh...")
|
|
||||||
Qt.callLater(() => {
|
|
||||||
console.log("ArchUpdater: Refreshing package lists after database sync...")
|
|
||||||
doPoll()
|
|
||||||
}, 2000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cached AUR helper detection
|
|
||||||
property string cachedAurHelper: ""
|
|
||||||
|
|
||||||
// Helper function to detect AUR helper
|
// Helper function to detect AUR helper
|
||||||
function getAurHelper() {
|
function getAurHelper() {
|
||||||
// Return cached result if available
|
// Check for yay first, then paru
|
||||||
if (cachedAurHelper !== "") {
|
const yayCheck = Quickshell.exec("command -v yay", true)
|
||||||
return cachedAurHelper
|
if (yayCheck.exitCode === 0 && yayCheck.stdout.trim()) {
|
||||||
|
return "yay"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for AUR helpers using Process objects
|
const paruCheck = Quickshell.exec("command -v paru", true)
|
||||||
console.log("ArchUpdater: Detecting AUR helper...")
|
if (paruCheck.exitCode === 0 && paruCheck.stdout.trim()) {
|
||||||
|
return "paru"
|
||||||
|
}
|
||||||
|
|
||||||
// Start the detection processes
|
return null
|
||||||
yayCheckProcess.running = true
|
|
||||||
paruCheckProcess.running = true
|
|
||||||
|
|
||||||
// For now, return a default (will be updated by the processes)
|
|
||||||
// In a real implementation, you'd want to wait for the processes to complete
|
|
||||||
return "paru" // Default fallback
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// Package selection functions
|
||||||
// PACKAGE SELECTION FUNCTIONS
|
|
||||||
// ============================================================================
|
|
||||||
function togglePackageSelection(packageName) {
|
function togglePackageSelection(packageName) {
|
||||||
const index = selectedPackages.indexOf(packageName)
|
const index = selectedPackages.indexOf(packageName)
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
|
|
@ -481,60 +225,47 @@ Singleton {
|
||||||
return selectedPackages.indexOf(packageName) > -1
|
return selectedPackages.indexOf(packageName) > -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// Robust refresh after updates
|
||||||
// REFRESH FUNCTIONS
|
function refreshAfterUpdate() {
|
||||||
// ============================================================================
|
// First refresh attempt after 3 seconds
|
||||||
|
Qt.callLater(() => {
|
||||||
|
doPoll()
|
||||||
|
doAurPoll()
|
||||||
|
}, 3000)
|
||||||
|
|
||||||
// Function to manually sync package databases (separate from refresh)
|
// Second refresh attempt after 8 seconds
|
||||||
function syncPackageDatabases() {
|
Qt.callLater(() => {
|
||||||
console.log("ArchUpdater: Manual database sync requested...")
|
doPoll()
|
||||||
const terminal = Quickshell.env("TERMINAL") || "xterm"
|
doAurPoll()
|
||||||
const command = "sudo pacman -Sy && echo 'Database sync complete! Press Enter to close...' && read -p 'Press Enter to continue...'"
|
}, 8000)
|
||||||
console.log("ArchUpdater: Executing sync command:", command)
|
|
||||||
console.log("ArchUpdater: Terminal:", terminal)
|
// Third refresh attempt after 15 seconds
|
||||||
Quickshell.execDetached([terminal, "-e", "bash", "-c", command])
|
Qt.callLater(() => {
|
||||||
|
doPoll()
|
||||||
|
doAurPoll()
|
||||||
|
updateInProgress = false
|
||||||
|
}, 15000)
|
||||||
|
|
||||||
|
// Final refresh attempt after 30 seconds
|
||||||
|
Qt.callLater(() => {
|
||||||
|
doPoll()
|
||||||
|
doAurPoll()
|
||||||
|
}, 30000)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to force a complete refresh (sync + check)
|
|
||||||
function forceCompleteRefresh() {
|
|
||||||
console.log("ArchUpdater: Force complete refresh requested...")
|
|
||||||
|
|
||||||
// Start database sync process (will trigger refresh when complete)
|
|
||||||
console.log("ArchUpdater: Starting complete refresh process...")
|
|
||||||
syncDatabaseProcess.running = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to sync database and refresh package lists
|
|
||||||
function syncDatabaseAndRefresh() {
|
|
||||||
console.log("ArchUpdater: Syncing database and refreshing package lists...")
|
|
||||||
|
|
||||||
// Start database sync process (will trigger refresh when complete)
|
|
||||||
console.log("ArchUpdater: Starting database sync process...")
|
|
||||||
syncDatabaseProcess.running = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// UTILITY FUNCTIONS
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// Notification helper
|
// Notification helper
|
||||||
function notify(title, body) {
|
function notify(title, body) {
|
||||||
Quickshell.execDetached(["notify-send", "-a", "UpdateService", "-i", "system-software-update", title, body])
|
Quickshell.execDetached(["notify-send", "-a", "UpdateService", "-i", "system-software-update", title, body])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// AUTO-POLL TIMER
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// Auto-poll every 15 minutes
|
// Auto-poll every 15 minutes
|
||||||
Timer {
|
Timer {
|
||||||
interval: 15 * 60 * 1000 // 15 minutes
|
interval: 15 * 60 * 1000 // 15 minutes
|
||||||
repeat: true
|
repeat: true
|
||||||
running: true
|
running: true
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (!updateInProgress) {
|
doPoll()
|
||||||
doPoll()
|
doAurPoll()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,11 @@ Singleton {
|
||||||
// Ensure cache dir exists
|
// Ensure cache dir exists
|
||||||
Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir])
|
Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir])
|
||||||
|
|
||||||
|
Logger.log("Matugen", "Generating from wallpaper on screen:", Screen.name)
|
||||||
|
var wp = WallpaperService.getWallpaper(Screen.name).replace(/'/g, "'\\''")
|
||||||
|
|
||||||
var content = buildConfigToml()
|
var content = buildConfigToml()
|
||||||
var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"
|
var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"
|
||||||
var wp = WallpaperService.currentWallpaper.replace(/'/g, "'\\''")
|
|
||||||
var pathEsc = dynamicConfigPath.replace(/'/g, "'\\''")
|
var pathEsc = dynamicConfigPath.replace(/'/g, "'\\''")
|
||||||
var extraRepo = (Quickshell.shellDir + "/Assets/Matugen/extra").replace(/'/g, "'\\''")
|
var extraRepo = (Quickshell.shellDir + "/Assets/Matugen/extra").replace(/'/g, "'\\''")
|
||||||
var extraUser = (Settings.configDir + "matugen.d").replace(/'/g, "'\\''")
|
var extraUser = (Settings.configDir + "matugen.d").replace(/'/g, "'\\''")
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ Singleton {
|
||||||
function scale(aScreen) {
|
function scale(aScreen) {
|
||||||
try {
|
try {
|
||||||
if (aScreen !== undefined && aScreen.name !== undefined) {
|
if (aScreen !== undefined && aScreen.name !== undefined) {
|
||||||
return scaleByName(aScreen.name)
|
return getMonitorScale(aScreen.name)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
||||||
|
|
@ -20,21 +20,46 @@ Singleton {
|
||||||
return 1.0
|
return 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
function scaleByName(aScreenName) {
|
// -------------------------------------------
|
||||||
|
function getMonitorScale(aScreenName) {
|
||||||
try {
|
try {
|
||||||
if (Settings.data.monitorsScaling !== undefined) {
|
var monitors = Settings.data.ui.monitorsScaling
|
||||||
if (Settings.data.monitorsScaling[aScreenName] !== undefined) {
|
if (monitors !== undefined) {
|
||||||
return Settings.data.monitorsScaling[aScreenName]
|
for (var i = 0; i < monitors.length; i++) {
|
||||||
|
if (monitors[i].name !== undefined && monitors[i].name === aScreenName) {
|
||||||
|
return monitors[i].scale
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
||||||
//Logger.warn(e)
|
//Logger.warn(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1.0
|
return 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------
|
||||||
|
function setMonitorScale(aScreenName, scale) {
|
||||||
|
try {
|
||||||
|
var monitors = Settings.data.ui.monitorsScaling
|
||||||
|
if (monitors !== undefined) {
|
||||||
|
for (var i = 0; i < monitors.length; i++) {
|
||||||
|
if (monitors[i].name !== undefined && monitors[i].name === aScreenName) {
|
||||||
|
monitors[i].scale = scale
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
monitors.push({
|
||||||
|
"name": aScreenName,
|
||||||
|
"scale": scale
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
//Logger.warn(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// Dynamic scaling based on resolution
|
// Dynamic scaling based on resolution
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,84 +10,197 @@ Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
Logger.log("Wallpapers", "Service started")
|
Logger.log("Wallpaper", "Service started")
|
||||||
listWallpapers()
|
|
||||||
|
|
||||||
// Wallpaper is set when the settings are loaded.
|
|
||||||
// Don't start random wallpaper during initialization
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property var wallpaperList: []
|
// All available wallpaper transitions
|
||||||
property string currentWallpaper: Settings.data.wallpaper.current
|
readonly property ListModel transitionsModel: ListModel {
|
||||||
property bool scanning: false
|
ListElement {
|
||||||
|
key: "none"
|
||||||
// SWWW
|
name: "None"
|
||||||
property string transitionType: Settings.data.wallpaper.swww.transitionType
|
|
||||||
property var randomChoices: ["simple", "fade", "left", "right", "top", "bottom", "wipe", "wave", "grow", "center", "any", "outer"]
|
|
||||||
|
|
||||||
function listWallpapers() {
|
|
||||||
Logger.log("Wallpapers", "Listing wallpapers")
|
|
||||||
scanning = true
|
|
||||||
wallpaperList = []
|
|
||||||
// Set the folder directly to avoid model reset issues
|
|
||||||
folderModel.folder = "file://" + (Settings.data.wallpaper.directory !== undefined ? Settings.data.wallpaper.directory : "")
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeWallpaper(path) {
|
|
||||||
Logger.log("Wallpapers", "Changing to:", path)
|
|
||||||
setCurrentWallpaper(path, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
function setCurrentWallpaper(path, isInitial) {
|
|
||||||
// Only regenerate colors if the wallpaper actually changed
|
|
||||||
var wallpaperChanged = currentWallpaper !== path
|
|
||||||
|
|
||||||
currentWallpaper = path
|
|
||||||
if (!isInitial) {
|
|
||||||
Settings.data.wallpaper.current = path
|
|
||||||
}
|
}
|
||||||
if (Settings.data.wallpaper.swww.enabled) {
|
ListElement {
|
||||||
if (Settings.data.wallpaper.swww.transitionType === "random") {
|
key: "fade"
|
||||||
transitionType = randomChoices[Math.floor(Math.random() * randomChoices.length)]
|
name: "Fade"
|
||||||
} else {
|
}
|
||||||
transitionType = Settings.data.wallpaper.swww.transitionType
|
ListElement {
|
||||||
|
key: "wipe_left"
|
||||||
|
name: "Wipe Left"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
key: "wipe_right"
|
||||||
|
name: "Wipe Right"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
key: "wipe_up"
|
||||||
|
name: "Wipe Up"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
key: "wipe_down"
|
||||||
|
name: "Wipe Down"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property var wallpaperLists: ({})
|
||||||
|
property int scanningCount: 0
|
||||||
|
readonly property bool scanning: (scanningCount > 0)
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Settings.data.wallpaper
|
||||||
|
function onDirectoryChanged() {
|
||||||
|
root.refreshWallpapersList()
|
||||||
|
}
|
||||||
|
function onRandomEnabledChanged() {
|
||||||
|
root.toggleRandomWallpaper()
|
||||||
|
}
|
||||||
|
function onRandomIntervalSecChanged() {
|
||||||
|
root.restartRandomWallpaperTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// Get specific monitor wallpaper data
|
||||||
|
function getMonitorConfig(screenName) {
|
||||||
|
var monitors = Settings.data.wallpaper.monitors
|
||||||
|
if (monitors !== undefined) {
|
||||||
|
for (var i = 0; i < monitors.length; i++) {
|
||||||
|
if (monitors[i].name !== undefined && monitors[i].name === screenName) {
|
||||||
|
return monitors[i]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
changeWallpaperProcess.running = true
|
// -------------------------------------------------------------------
|
||||||
} else {
|
// Get specific monitor directory
|
||||||
|
function getMonitorDirectory(screenName) {
|
||||||
// Fallback: update the settings directly for non-SWWW mode
|
if (!Settings.data.wallpaper.enableMultiMonitorDirectories) {
|
||||||
//Logger.log("Wallpapers", "Not using Swww, setting wallpaper directly")
|
return Settings.data.wallpaper.directory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var monitor = getMonitorConfig(screenName)
|
||||||
|
if (monitor !== undefined && monitor.directory !== undefined) {
|
||||||
|
return monitor.directory
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to the main/single directory
|
||||||
|
return Settings.data.wallpaper.directory
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// Set specific monitor directory
|
||||||
|
function setMonitorDirectory(screenName, directory) {
|
||||||
|
var monitor = getMonitorConfig(screenName)
|
||||||
|
if (monitor !== undefined) {
|
||||||
|
monitor.directory = directory
|
||||||
|
} else {
|
||||||
|
Settings.data.wallpaper.monitors.push({
|
||||||
|
"name": screenName,
|
||||||
|
"directory": directory,
|
||||||
|
"wallpaper": ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// Get specific monitor wallpaper
|
||||||
|
function getWallpaper(screenName) {
|
||||||
|
var monitor = getMonitorConfig(screenName)
|
||||||
|
if ((monitor !== undefined) && (monitor["wallpaper"] !== undefined)) {
|
||||||
|
return monitor["wallpaper"]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
function changeWallpaper(screenName, path) {
|
||||||
|
if (screenName !== undefined) {
|
||||||
|
setWallpaper(screenName, path)
|
||||||
|
} else {
|
||||||
|
// If no screenName specified change for all screens
|
||||||
|
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||||
|
setWallpaper(Quickshell.screens[i].name, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
function setWallpaper(screenName, path) {
|
||||||
|
if (path === "" || path === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screenName === undefined) {
|
||||||
|
Logger.warn("Wallpaper", "setWallpaper", "no screen specified")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.log("Wallpaper", "setWallpaper on", screenName, ": ", path)
|
||||||
|
|
||||||
|
var wallpaperChanged = false
|
||||||
|
|
||||||
|
var monitor = getMonitorConfig(screenName)
|
||||||
|
if (monitor !== undefined) {
|
||||||
|
wallpaperChanged = (monitor["wallpaper"] !== path)
|
||||||
|
monitor["wallpaper"] = path
|
||||||
|
} else {
|
||||||
|
wallpaperChanged = true
|
||||||
|
Settings.data.wallpaper.monitors.push({
|
||||||
|
"name": screenName,
|
||||||
|
"directory": getMonitorDirectory(screenName),
|
||||||
|
"wallpaper": path
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart the random wallpaper timer
|
||||||
if (randomWallpaperTimer.running) {
|
if (randomWallpaperTimer.running) {
|
||||||
randomWallpaperTimer.restart()
|
randomWallpaperTimer.restart()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only notify ColorScheme service if the wallpaper actually changed
|
// Notify ColorScheme service if the wallpaper actually changed
|
||||||
if (wallpaperChanged) {
|
if (wallpaperChanged) {
|
||||||
ColorSchemeService.changedWallpaper()
|
ColorSchemeService.changedWallpaper()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
function setRandomWallpaper() {
|
function setRandomWallpaper() {
|
||||||
var randomIndex = Math.floor(Math.random() * wallpaperList.length)
|
Logger.log("Wallpaper", "setRandomWallpaper")
|
||||||
var randomPath = wallpaperList[randomIndex]
|
|
||||||
if (!randomPath) {
|
if (Settings.data.wallpaper.enableMultiMonitorDirectories) {
|
||||||
return
|
// Pick a random wallpaper per screen
|
||||||
|
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||||
|
var screenName = Quickshell.screens[i].name
|
||||||
|
var wallpaperList = getWallpapersList(screenName)
|
||||||
|
|
||||||
|
if (wallpaperList.length > 0) {
|
||||||
|
var randomIndex = Math.floor(Math.random() * wallpaperList.length)
|
||||||
|
var randomPath = wallpaperList[randomIndex]
|
||||||
|
changeWallpaper(screenName, randomPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Pick a random wallpaper common to all screens
|
||||||
|
// We can use any screenName here, so we just pick the primary one.
|
||||||
|
var wallpaperList = getWallpapersList(Screen.name)
|
||||||
|
if (wallpaperList.length > 0) {
|
||||||
|
var randomIndex = Math.floor(Math.random() * wallpaperList.length)
|
||||||
|
var randomPath = wallpaperList[randomIndex]
|
||||||
|
changeWallpaper(undefined, randomPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setCurrentWallpaper(randomPath, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
function toggleRandomWallpaper() {
|
function toggleRandomWallpaper() {
|
||||||
if (Settings.data.wallpaper.isRandom && !randomWallpaperTimer.running) {
|
Logger.log("Wallpaper", "toggleRandomWallpaper")
|
||||||
randomWallpaperTimer.start()
|
if (Settings.data.wallpaper.randomEnabled) {
|
||||||
|
randomWallpaperTimer.restart()
|
||||||
setRandomWallpaper()
|
setRandomWallpaper()
|
||||||
} else if (!Settings.data.randomWallpaper && randomWallpaperTimer.running) {
|
|
||||||
randomWallpaperTimer.stop()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
function restartRandomWallpaperTimer() {
|
function restartRandomWallpaperTimer() {
|
||||||
if (Settings.data.wallpaper.isRandom) {
|
if (Settings.data.wallpaper.isRandom) {
|
||||||
randomWallpaperTimer.stop()
|
randomWallpaperTimer.stop()
|
||||||
|
|
@ -95,78 +208,81 @@ Singleton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function startSWWWDaemon() {
|
// -------------------------------------------------------------------
|
||||||
if (Settings.data.wallpaper.swww.enabled) {
|
function getWallpapersList(screenName) {
|
||||||
Logger.log("Swww", "Requesting swww-daemon")
|
if (screenName != undefined && wallpaperLists[screenName] != undefined) {
|
||||||
startDaemonProcess.running = true
|
return wallpaperLists[screenName]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
function refreshWallpapersList() {
|
||||||
|
Logger.log("Wallpaper", "refreshWallpapersList")
|
||||||
|
scanningCount = 0
|
||||||
|
|
||||||
|
// Force refresh by toggling the folder property on each FolderListModel
|
||||||
|
for (var i = 0; i < wallpaperScanners.count; i++) {
|
||||||
|
var scanner = wallpaperScanners.objectAt(i)
|
||||||
|
if (scanner) {
|
||||||
|
var currentFolder = scanner.folder
|
||||||
|
scanner.folder = ""
|
||||||
|
scanner.folder = currentFolder
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// -------------------------------------------------------------------
|
||||||
Timer {
|
Timer {
|
||||||
id: randomWallpaperTimer
|
id: randomWallpaperTimer
|
||||||
interval: Settings.data.wallpaper.randomInterval * 1000
|
interval: Settings.data.wallpaper.randomIntervalSec * 1000
|
||||||
running: false
|
running: Settings.data.wallpaper.randomEnabled
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: setRandomWallpaper()
|
onTriggered: setRandomWallpaper()
|
||||||
triggeredOnStart: false
|
triggeredOnStart: false
|
||||||
}
|
}
|
||||||
|
|
||||||
FolderListModel {
|
// Instantiator (not Repeater) to create FolderListModel for each monitor
|
||||||
id: folderModel
|
Instantiator {
|
||||||
// Swww supports many images format but Quickshell only support a subset of those.
|
id: wallpaperScanners
|
||||||
nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"]
|
model: Quickshell.screens
|
||||||
showDirs: false
|
delegate: FolderListModel {
|
||||||
sortField: FolderListModel.Name
|
property string screenName: modelData.name
|
||||||
onStatusChanged: {
|
|
||||||
if (status === FolderListModel.Ready) {
|
folder: "file://" + root.getMonitorDirectory(screenName)
|
||||||
var files = []
|
nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"]
|
||||||
for (var i = 0; i < count; i++) {
|
showDirs: false
|
||||||
var directory = (Settings.data.wallpaper.directory !== undefined ? Settings.data.wallpaper.directory : "")
|
sortField: FolderListModel.Name
|
||||||
var filepath = directory + "/" + get(i, "fileName")
|
onStatusChanged: {
|
||||||
files.push(filepath)
|
if (status === FolderListModel.Null) {
|
||||||
|
// Flush the list
|
||||||
|
var lists = root.wallpaperLists
|
||||||
|
lists[screenName] = []
|
||||||
|
root.wallpaperLists = lists
|
||||||
|
} else if (status === FolderListModel.Loading) {
|
||||||
|
// Flush the list
|
||||||
|
var lists = root.wallpaperLists
|
||||||
|
lists[screenName] = []
|
||||||
|
root.wallpaperLists = lists
|
||||||
|
|
||||||
|
scanningCount++
|
||||||
|
} else if (status === FolderListModel.Ready) {
|
||||||
|
var files = []
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
var directory = root.getMonitorDirectory(screenName)
|
||||||
|
var filepath = directory + "/" + get(i, "fileName")
|
||||||
|
files.push(filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var lists = root.wallpaperLists
|
||||||
|
lists[screenName] = files
|
||||||
|
root.wallpaperLists = lists
|
||||||
|
|
||||||
|
scanningCount--
|
||||||
|
Logger.log("Wallpaper", "List refreshed for", screenName, "count:", files.length)
|
||||||
}
|
}
|
||||||
wallpaperList = files
|
|
||||||
scanning = false
|
|
||||||
Logger.log("Wallpapers", "List refreshed, count:", wallpaperList.length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: changeWallpaperProcess
|
|
||||||
command: ["swww", "img", "--resize", Settings.data.wallpaper.swww.resizeMethod, "--transition-fps", Settings.data.wallpaper.swww.transitionFps.toString(
|
|
||||||
), "--transition-type", transitionType, "--transition-duration", Settings.data.wallpaper.swww.transitionDuration.toString(
|
|
||||||
), currentWallpaper]
|
|
||||||
running: false
|
|
||||||
|
|
||||||
onStarted: {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onExited: function (exitCode, exitStatus) {
|
|
||||||
Logger.log("Swww", "Process finished with exit code:", exitCode, "status:", exitStatus)
|
|
||||||
if (exitCode !== 0) {
|
|
||||||
Logger.log("Swww", "Process failed. Make sure swww-daemon is running with: swww-daemon")
|
|
||||||
Logger.log("Swww", "You can start it with: swww-daemon --format xrgb")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: startDaemonProcess
|
|
||||||
command: ["swww-daemon", "--format", "xrgb"]
|
|
||||||
running: false
|
|
||||||
|
|
||||||
onStarted: {
|
|
||||||
Logger.log("Swww", "Daemon start process initiated")
|
|
||||||
}
|
|
||||||
|
|
||||||
onExited: function (exitCode, exitStatus) {
|
|
||||||
Logger.log("Swww", "Daemon start process finished with exit code:", exitCode)
|
|
||||||
if (exitCode === 0) {
|
|
||||||
Logger.log("Swww", "Daemon started successfully")
|
|
||||||
} else {
|
|
||||||
Logger.log("Swww", "Failed to start daemon, may already be running")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,12 @@ import QtQuick.Layouts
|
||||||
import qs.Commons
|
import qs.Commons
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
property string label: ""
|
property string label: ""
|
||||||
property string description: ""
|
property string description: ""
|
||||||
|
property color labelColor: Color.mOnSurface
|
||||||
|
property color descriptionColor: Color.mOnSurfaceVariant
|
||||||
|
|
||||||
spacing: Style.marginXXS * scaling
|
spacing: Style.marginXXS * scaling
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
@ -13,14 +17,14 @@ ColumnLayout {
|
||||||
text: label
|
text: label
|
||||||
font.pointSize: Style.fontSizeL * scaling
|
font.pointSize: Style.fontSizeL * scaling
|
||||||
font.weight: Style.fontWeightBold
|
font.weight: Style.fontWeightBold
|
||||||
color: Color.mOnSurface
|
color: labelColor
|
||||||
visible: label !== ""
|
visible: label !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
NText {
|
NText {
|
||||||
text: description
|
text: description
|
||||||
font.pointSize: Style.fontSizeS * scaling
|
font.pointSize: Style.fontSizeS * scaling
|
||||||
color: Color.mOnSurfaceVariant
|
color: descriptionColor
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
visible: description !== ""
|
visible: description !== ""
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ ColumnLayout {
|
||||||
property bool readOnly: false
|
property bool readOnly: false
|
||||||
property bool enabled: true
|
property bool enabled: true
|
||||||
property int inputMaxWidth: 420 * scaling
|
property int inputMaxWidth: 420 * scaling
|
||||||
|
property color labelColor: Color.mOnSurface
|
||||||
|
property color descriptionColor: Color.mOnSurfaceVariant
|
||||||
|
|
||||||
property alias text: input.text
|
property alias text: input.text
|
||||||
property alias placeholderText: input.placeholderText
|
property alias placeholderText: input.placeholderText
|
||||||
|
|
@ -25,6 +27,8 @@ ColumnLayout {
|
||||||
NLabel {
|
NLabel {
|
||||||
label: root.label
|
label: root.label
|
||||||
description: root.description
|
description: root.description
|
||||||
|
labelColor: root.labelColor
|
||||||
|
descriptionColor: root.descriptionColor
|
||||||
visible: root.label !== "" || root.description !== ""
|
visible: root.label !== "" || root.description !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Logger.log("NWidgetLoader", "Loaded", widgetName, "on screen", item.screen.name)
|
//Logger.log("NWidgetLoader", "Loaded", widgetName, "on screen", item.screen.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@
|
||||||
libnotify
|
libnotify
|
||||||
matugen
|
matugen
|
||||||
networkmanager
|
networkmanager
|
||||||
swww
|
|
||||||
wl-clipboard
|
wl-clipboard
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue