feat: wallpaper switcher
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import Qt.labs.folderlistmodel
|
||||||
|
import Quickshell
|
||||||
import Quickshell.Bluetooth
|
import Quickshell.Bluetooth
|
||||||
|
import Quickshell.Io
|
||||||
import Quickshell.Services.Pipewire
|
import Quickshell.Services.Pipewire
|
||||||
import "../.."
|
import "../.."
|
||||||
|
|
||||||
@@ -16,6 +19,8 @@ Item {
|
|||||||
property int menuLeft: 64
|
property int menuLeft: 64
|
||||||
property int menuTop: 140
|
property int menuTop: 140
|
||||||
property int lineHeight: 38 + 40 + 1
|
property int lineHeight: 38 + 40 + 1
|
||||||
|
property int visibleRows: Math.max(1, Math.floor((height - menuTop - 64) / lineHeight))
|
||||||
|
property int scrollOffset: 0
|
||||||
|
|
||||||
property int nameFontSize: 32
|
property int nameFontSize: 32
|
||||||
property int stateFontSize: 28
|
property int stateFontSize: 28
|
||||||
@@ -30,9 +35,14 @@ Item {
|
|||||||
property ShellStateManager manager: null
|
property ShellStateManager manager: null
|
||||||
property int activeSelection: 0
|
property int activeSelection: 0
|
||||||
property bool inBluetoothMenu: false
|
property bool inBluetoothMenu: false
|
||||||
|
property bool inWallpaperMenu: false
|
||||||
|
|
||||||
property bool isSelected: false
|
property bool isSelected: false
|
||||||
|
|
||||||
|
property string wallpapersDir: (Quickshell.env("HOME") || "") + "/Pictures/Wallpapers"
|
||||||
|
property string wallpaperCachePath: (Quickshell.env("HOME") || "") + "/.cache/.wallpaper"
|
||||||
|
property string currentWallpaperPath: ""
|
||||||
|
|
||||||
function clamp(v, lo, hi) {
|
function clamp(v, lo, hi) {
|
||||||
return Math.max(lo, Math.min(hi, v));
|
return Math.max(lo, Math.min(hi, v));
|
||||||
}
|
}
|
||||||
@@ -40,6 +50,8 @@ Item {
|
|||||||
function menuLength() {
|
function menuLength() {
|
||||||
if (root.inBluetoothMenu && bluetoothRepeater)
|
if (root.inBluetoothMenu && bluetoothRepeater)
|
||||||
return bluetoothRepeater.count;
|
return bluetoothRepeater.count;
|
||||||
|
if (root.inWallpaperMenu && wallpaperFolderModel)
|
||||||
|
return wallpaperFolderModel.count;
|
||||||
if (!menuModel)
|
if (!menuModel)
|
||||||
return 0;
|
return 0;
|
||||||
if (typeof menuModel.count === "function")
|
if (typeof menuModel.count === "function")
|
||||||
@@ -69,7 +81,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function currentAction() {
|
function currentAction() {
|
||||||
if (root.inBluetoothMenu)
|
if (root.inBluetoothMenu || root.inWallpaperMenu)
|
||||||
return null;
|
return null;
|
||||||
return menuLength() > 0 ? menuAt(activeSelection) : null;
|
return menuLength() > 0 ? menuAt(activeSelection) : null;
|
||||||
}
|
}
|
||||||
@@ -78,9 +90,26 @@ Item {
|
|||||||
const length = menuLength();
|
const length = menuLength();
|
||||||
if (length === 0) {
|
if (length === 0) {
|
||||||
activeSelection = 0;
|
activeSelection = 0;
|
||||||
|
scrollOffset = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
activeSelection = clamp(activeSelection, 0, length - 1);
|
activeSelection = clamp(activeSelection, 0, length - 1);
|
||||||
|
const maxOffset = Math.max(0, length - visibleRows);
|
||||||
|
scrollOffset = clamp(scrollOffset, 0, maxOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureVisible() {
|
||||||
|
if (!root.inWallpaperMenu)
|
||||||
|
return;
|
||||||
|
const length = menuLength();
|
||||||
|
if (length === 0) {
|
||||||
|
scrollOffset = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (activeSelection < scrollOffset)
|
||||||
|
scrollOffset = activeSelection;
|
||||||
|
else if (activeSelection >= scrollOffset + visibleRows)
|
||||||
|
scrollOffset = Math.max(0, activeSelection - visibleRows + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function bluetoothDisplayName(device) {
|
function bluetoothDisplayName(device) {
|
||||||
@@ -105,11 +134,67 @@ Item {
|
|||||||
return item.device || null;
|
return item.device || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function trimText(text) {
|
||||||
|
return (text || "").replace(/^\s+|\s+$/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizedPath(path) {
|
||||||
|
var value = trimText(path);
|
||||||
|
if (value.indexOf("file://") === 0)
|
||||||
|
value = value.slice(7);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function wallpaperNameAt(index) {
|
||||||
|
if (!wallpaperFolderModel || index < 0 || index >= wallpaperFolderModel.count)
|
||||||
|
return "";
|
||||||
|
var name = wallpaperFolderModel.get(index, "fileName") || "";
|
||||||
|
return name.replace(/\.[^/.]+$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function wallpaperPathAt(index) {
|
||||||
|
if (!wallpaperFolderModel || index < 0 || index >= wallpaperFolderModel.count)
|
||||||
|
return "";
|
||||||
|
return wallpaperFolderModel.get(index, "filePath") || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentWallpaperIndex() {
|
||||||
|
var current = normalizedPath(currentWallpaperPath);
|
||||||
|
if (!current || !wallpaperFolderModel)
|
||||||
|
return -1;
|
||||||
|
for (var i = 0; i < wallpaperFolderModel.count; i++) {
|
||||||
|
if (normalizedPath(wallpaperPathAt(i)) === current)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshCurrentWallpaper() {
|
||||||
|
root.currentWallpaperPath = normalizedPath(wallpaperFile.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyWallpaper(path) {
|
||||||
|
var normalized = normalizedPath(path);
|
||||||
|
if (!normalized || normalized.length === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
root.currentWallpaperPath = normalized;
|
||||||
|
wallpaperFile.setText(normalized + "\n");
|
||||||
|
|
||||||
|
wallpaperApplyProcess.running = false;
|
||||||
|
wallpaperApplyProcess.environment = {
|
||||||
|
"WALLPAPER_PATH": normalized
|
||||||
|
};
|
||||||
|
wallpaperApplyProcess.command = ["bash", "-lc", "rm -rf \"$HOME/.cache/wal\"; if [[ \"$(hostname)\" != \"gentoo\" ]]; then swww img \"$WALLPAPER_PATH\" --transition-type none; else HYPRPAPER_PID=\"$(pidof hyprpaper)\"; if [ ${#HYPRPAPER_PID} -lt 1 ]; then hyprctl dispatch exec hyprpaper; sleep 1; fi; hyprctl hyprpaper unload all; hyprctl hyprpaper preload \"$WALLPAPER_PATH\"; hyprctl hyprpaper wallpaper ,\"$WALLPAPER_PATH\"; fi"];
|
||||||
|
wallpaperApplyProcess.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
PwObjectTracker {
|
PwObjectTracker {
|
||||||
objects: [Pipewire.defaultAudioSink]
|
objects: [Pipewire.defaultAudioSink]
|
||||||
}
|
}
|
||||||
|
|
||||||
property int bluetoothActionIndex: 1
|
property int bluetoothActionIndex: 1
|
||||||
|
property int wallpaperActionIndex: 2
|
||||||
property var actions: [
|
property var actions: [
|
||||||
{
|
{
|
||||||
name: "Master Volume",
|
name: "Master Volume",
|
||||||
@@ -132,6 +217,7 @@ Item {
|
|||||||
name: "Bluetooth",
|
name: "Bluetooth",
|
||||||
ent: function () {
|
ent: function () {
|
||||||
root.inBluetoothMenu = true;
|
root.inBluetoothMenu = true;
|
||||||
|
root.inWallpaperMenu = false;
|
||||||
root.isSelected = false;
|
root.isSelected = false;
|
||||||
root.activeSelection = 0;
|
root.activeSelection = 0;
|
||||||
root.clampSelection();
|
root.clampSelection();
|
||||||
@@ -139,10 +225,55 @@ Item {
|
|||||||
getState: function () {
|
getState: function () {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Wallpaper",
|
||||||
|
ent: function () {
|
||||||
|
root.inWallpaperMenu = true;
|
||||||
|
root.inBluetoothMenu = false;
|
||||||
|
root.isSelected = false;
|
||||||
|
var currentIndex = root.currentWallpaperIndex();
|
||||||
|
root.activeSelection = currentIndex >= 0 ? currentIndex : 0;
|
||||||
|
root.scrollOffset = 0;
|
||||||
|
root.clampSelection();
|
||||||
|
root.ensureVisible();
|
||||||
|
},
|
||||||
|
getState: function () {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
property var menuModel: root.inBluetoothMenu ? Bluetooth.devices : actions
|
property var menuModel: root.inBluetoothMenu ? Bluetooth.devices : (root.inWallpaperMenu ? wallpaperFolderModel : actions)
|
||||||
|
|
||||||
|
FolderListModel {
|
||||||
|
id: wallpaperFolderModel
|
||||||
|
folder: "file://" + root.wallpapersDir
|
||||||
|
nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.webp", "*.bmp", "*.gif"]
|
||||||
|
showDirs: false
|
||||||
|
showDotAndDotDot: false
|
||||||
|
sortField: FolderListModel.Name
|
||||||
|
sortReversed: false
|
||||||
|
onCountChanged: {
|
||||||
|
if (root.inWallpaperMenu) {
|
||||||
|
root.clampSelection();
|
||||||
|
root.ensureVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: wallpaperFile
|
||||||
|
path: root.wallpaperCachePath
|
||||||
|
watchChanges: true
|
||||||
|
onLoaded: root.refreshCurrentWallpaper()
|
||||||
|
onFileChanged: reload()
|
||||||
|
onLoadFailed: root.currentWallpaperPath = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: wallpaperApplyProcess
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: Bluetooth.devices
|
target: Bluetooth.devices
|
||||||
@@ -165,8 +296,31 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: wallpaperFolderModel
|
||||||
|
ignoreUnknownSignals: true
|
||||||
|
function onModelReset() {
|
||||||
|
if (root.inWallpaperMenu) {
|
||||||
|
root.clampSelection();
|
||||||
|
root.ensureVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onRowsInserted() {
|
||||||
|
if (root.inWallpaperMenu) {
|
||||||
|
root.clampSelection();
|
||||||
|
root.ensureVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onRowsRemoved() {
|
||||||
|
if (root.inWallpaperMenu) {
|
||||||
|
root.clampSelection();
|
||||||
|
root.ensureVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: root.inBluetoothMenu ? "BLUETOOTH" : "CONFIG"
|
text: root.inBluetoothMenu ? "BLUETOOTH" : (root.inWallpaperMenu ? "WALLPAPER" : "CONFIG")
|
||||||
font.family: "8bitoperator JVE"
|
font.family: "8bitoperator JVE"
|
||||||
font.pixelSize: 71
|
font.pixelSize: 71
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
@@ -186,7 +340,8 @@ Item {
|
|||||||
width: root.width
|
width: root.width
|
||||||
height: lineHeight
|
height: lineHeight
|
||||||
x: 0
|
x: 0
|
||||||
y: menuTop + index * lineHeight
|
y: menuTop + (root.inWallpaperMenu ? (index - root.scrollOffset) * lineHeight : index * lineHeight)
|
||||||
|
visible: !root.inWallpaperMenu || (index >= root.scrollOffset && index < root.scrollOffset + root.visibleRows)
|
||||||
|
|
||||||
property var device: root.inBluetoothMenu ? modelData : null
|
property var device: root.inBluetoothMenu ? modelData : null
|
||||||
|
|
||||||
@@ -202,7 +357,8 @@ Item {
|
|||||||
Text {
|
Text {
|
||||||
x: 239
|
x: 239
|
||||||
y: 0
|
y: 0
|
||||||
text: root.inBluetoothMenu ? root.bluetoothDisplayName(modelData) : modelData.name
|
text: root.inBluetoothMenu ? root.bluetoothDisplayName(modelData) : (root.inWallpaperMenu ? root.wallpaperNameAt(index) : modelData.name)
|
||||||
|
width: root.width - 239 - (root.inWallpaperMenu ? 96 : 300)
|
||||||
font.family: "8bitoperator JVE"
|
font.family: "8bitoperator JVE"
|
||||||
font.pixelSize: 71
|
font.pixelSize: 71
|
||||||
font.letterSpacing: 1
|
font.letterSpacing: 1
|
||||||
@@ -210,13 +366,16 @@ Item {
|
|||||||
font.hintingPreference: Font.PreferNoHinting
|
font.hintingPreference: Font.PreferNoHinting
|
||||||
smooth: false
|
smooth: false
|
||||||
antialiasing: false
|
antialiasing: false
|
||||||
color: (root.activeSelection == index && root.isSelected == true) ? "#fefe00" : "#ffffff"
|
wrapMode: Text.NoWrap
|
||||||
|
elide: Text.ElideRight
|
||||||
|
color: (root.activeSelection == index && (root.isSelected == true || root.inBluetoothMenu || root.inWallpaperMenu)) ? "#fefe00" : "#ffffff"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option state
|
// Option state
|
||||||
Text {
|
Text {
|
||||||
x: menuLeft + stateColumnX
|
x: menuLeft + stateColumnX
|
||||||
y: 4
|
y: 4
|
||||||
|
visible: !root.inWallpaperMenu
|
||||||
text: root.inBluetoothMenu ? root.bluetoothDisplayState(modelData) : (modelData.getState ? modelData.getState() : "")
|
text: root.inBluetoothMenu ? root.bluetoothDisplayState(modelData) : (modelData.getState ? modelData.getState() : "")
|
||||||
font.family: "8bitoperator JVE"
|
font.family: "8bitoperator JVE"
|
||||||
font.pixelSize: 71
|
font.pixelSize: 71
|
||||||
@@ -225,7 +384,7 @@ Item {
|
|||||||
font.hintingPreference: Font.PreferNoHinting
|
font.hintingPreference: Font.PreferNoHinting
|
||||||
smooth: false
|
smooth: false
|
||||||
antialiasing: false
|
antialiasing: false
|
||||||
color: (root.activeSelection == index && root.isSelected == true) ? "#fefe00" : "#ffffff"
|
color: (root.activeSelection == index && (root.isSelected == true || root.inBluetoothMenu || root.inWallpaperMenu)) ? "#fefe00" : "#ffffff"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,12 +396,16 @@ Item {
|
|||||||
function handleKey(key) {
|
function handleKey(key) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case Qt.Key_Up:
|
case Qt.Key_Up:
|
||||||
if (root.inBluetoothMenu || root.isSelected === false)
|
if (root.inBluetoothMenu || root.inWallpaperMenu || root.isSelected === false) {
|
||||||
activeSelection = wrapIndex(activeSelection - 1);
|
activeSelection = wrapIndex(activeSelection - 1);
|
||||||
|
root.ensureVisible();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
case Qt.Key_Down:
|
case Qt.Key_Down:
|
||||||
if (root.inBluetoothMenu || root.isSelected === false)
|
if (root.inBluetoothMenu || root.inWallpaperMenu || root.isSelected === false) {
|
||||||
activeSelection = wrapIndex(activeSelection + 1);
|
activeSelection = wrapIndex(activeSelection + 1);
|
||||||
|
root.ensureVisible();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
case Qt.Key_Left:
|
case Qt.Key_Left:
|
||||||
{
|
{
|
||||||
@@ -273,6 +436,10 @@ Item {
|
|||||||
}
|
}
|
||||||
// device.connected = !device.connected;
|
// device.connected = !device.connected;
|
||||||
}
|
}
|
||||||
|
} else if (root.inWallpaperMenu) {
|
||||||
|
const wallpaperPath = wallpaperPathAt(activeSelection);
|
||||||
|
if (wallpaperPath && wallpaperPath.length > 0)
|
||||||
|
root.applyWallpaper(wallpaperPath);
|
||||||
} else {
|
} else {
|
||||||
const a = currentAction();
|
const a = currentAction();
|
||||||
if (a && a.ent) {
|
if (a && a.ent) {
|
||||||
@@ -286,7 +453,13 @@ Item {
|
|||||||
case Qt.Key_X:
|
case Qt.Key_X:
|
||||||
case Qt.Key_Shift:
|
case Qt.Key_Shift:
|
||||||
case Qt.Key_Escape:
|
case Qt.Key_Escape:
|
||||||
if (root.inBluetoothMenu) {
|
if (root.inWallpaperMenu) {
|
||||||
|
root.inWallpaperMenu = false;
|
||||||
|
root.isSelected = false;
|
||||||
|
root.scrollOffset = 0;
|
||||||
|
root.activeSelection = root.wallpaperActionIndex;
|
||||||
|
root.clampSelection();
|
||||||
|
} else if (root.inBluetoothMenu) {
|
||||||
root.inBluetoothMenu = false;
|
root.inBluetoothMenu = false;
|
||||||
root.isSelected = false;
|
root.isSelected = false;
|
||||||
root.activeSelection = root.bluetoothActionIndex;
|
root.activeSelection = root.bluetoothActionIndex;
|
||||||
@@ -307,6 +480,9 @@ Item {
|
|||||||
root.activeSelection = 0;
|
root.activeSelection = 0;
|
||||||
root.isSelected = false;
|
root.isSelected = false;
|
||||||
root.inBluetoothMenu = false;
|
root.inBluetoothMenu = false;
|
||||||
|
root.inWallpaperMenu = false;
|
||||||
|
root.scrollOffset = 0;
|
||||||
|
root.refreshCurrentWallpaper();
|
||||||
ShellInputManager.registerHandler("quickSettings", handleKey);
|
ShellInputManager.registerHandler("quickSettings", handleKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user