mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-04 04:42:05 -04:00
feat(lockscreen): enable use of videos as screensaver in the lock screen (#1819)
* feat(lockscreen): enable use of videos as screensaver in the lock screen * reducing debug logs * feature becomes available only when QtMultimedia is available
This commit is contained in:
committed by
GitHub
parent
5d09acca4c
commit
bd6ad53875
@@ -2,8 +2,9 @@ pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
|
||||
Rectangle {
|
||||
FocusScope {
|
||||
id: root
|
||||
|
||||
required property WlSessionLock lock
|
||||
@@ -14,7 +15,17 @@ Rectangle {
|
||||
signal passwordChanged(string newPassword)
|
||||
signal unlockRequested
|
||||
|
||||
color: "transparent"
|
||||
Keys.onPressed: event => {
|
||||
if (videoScreensaver.active && videoScreensaver.inputEnabled) {
|
||||
videoScreensaver.dismiss();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
LockScreenContent {
|
||||
id: lockContent
|
||||
@@ -23,17 +34,38 @@ Rectangle {
|
||||
demoMode: false
|
||||
passwordBuffer: root.sharedPasswordBuffer
|
||||
screenName: root.screenName
|
||||
enabled: !videoScreensaver.active
|
||||
focus: !videoScreensaver.active
|
||||
opacity: videoScreensaver.active ? 0 : 1
|
||||
onUnlockRequested: root.unlockRequested()
|
||||
onPasswordBufferChanged: {
|
||||
if (root.sharedPasswordBuffer !== passwordBuffer) {
|
||||
root.passwordChanged(passwordBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VideoScreensaver {
|
||||
id: videoScreensaver
|
||||
anchors.fill: parent
|
||||
screenName: root.screenName
|
||||
}
|
||||
|
||||
Component.onCompleted: forceActiveFocus()
|
||||
|
||||
onIsLockedChanged: {
|
||||
if (isLocked) {
|
||||
forceActiveFocus();
|
||||
lockContent.resetLockState();
|
||||
if (SettingsData.lockScreenVideoEnabled) {
|
||||
videoScreensaver.start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
lockContent.unlocking = false;
|
||||
|
||||
200
quickshell/Modules/Lock/VideoScreensaver.qml
Normal file
200
quickshell/Modules/Lock/VideoScreensaver.qml
Normal file
@@ -0,0 +1,200 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property string screenName
|
||||
property bool active: false
|
||||
property string videoSource: ""
|
||||
property bool inputEnabled: false
|
||||
property point lastMousePos: Qt.point(-1, -1)
|
||||
property bool mouseInitialized: false
|
||||
property var videoPlayer: null
|
||||
|
||||
signal dismissed
|
||||
|
||||
visible: active
|
||||
z: 1000
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
color: "black"
|
||||
visible: root.active
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: inputEnableTimer
|
||||
interval: 500
|
||||
onTriggered: root.inputEnabled = true
|
||||
}
|
||||
|
||||
Process {
|
||||
id: videoPicker
|
||||
property string result: ""
|
||||
property string folder: ""
|
||||
|
||||
command: ["sh", "-c", "find '" + folder + "' -maxdepth 1 -type f \\( " + "-iname '*.mp4' -o -iname '*.mkv' -o -iname '*.webm' -o " + "-iname '*.mov' -o -iname '*.avi' -o -iname '*.m4v' " + "\\) 2>/dev/null | shuf -n1"]
|
||||
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
const path = data.trim();
|
||||
if (path) {
|
||||
videoPicker.result = path;
|
||||
root.videoSource = "file://" + path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode !== 0 || !videoPicker.result) {
|
||||
console.warn("VideoScreensaver: no video found in folder");
|
||||
ToastService.showError(I18n.tr("Video Screensaver"), I18n.tr("No video found in folder"));
|
||||
root.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fileChecker
|
||||
command: ["test", "-d", SettingsData.lockScreenVideoPath]
|
||||
|
||||
onExited: exitCode => {
|
||||
const isDir = exitCode === 0;
|
||||
const videoPath = SettingsData.lockScreenVideoPath;
|
||||
|
||||
if (isDir) {
|
||||
videoPicker.folder = videoPath;
|
||||
videoPicker.running = true;
|
||||
} else if (SettingsData.lockScreenVideoCycling) {
|
||||
const parentFolder = videoPath.substring(0, videoPath.lastIndexOf('/'));
|
||||
videoPicker.folder = parentFolder;
|
||||
videoPicker.running = true;
|
||||
} else {
|
||||
root.videoSource = "file://" + videoPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createVideoPlayer() {
|
||||
if (videoPlayer)
|
||||
return true;
|
||||
|
||||
try {
|
||||
videoPlayer = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import QtMultimedia
|
||||
Video {
|
||||
anchors.fill: parent
|
||||
fillMode: VideoOutput.PreserveAspectCrop
|
||||
loops: MediaPlayer.Infinite
|
||||
volume: 0
|
||||
}
|
||||
`, background, "VideoScreensaver.VideoPlayer");
|
||||
|
||||
videoPlayer.errorOccurred.connect((error, errorString) => {
|
||||
console.warn("VideoScreensaver: playback error:", errorString);
|
||||
ToastService.showError(I18n.tr("Video Screensaver"), I18n.tr("Playback error: ") + errorString);
|
||||
root.dismiss();
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.warn("VideoScreensaver: Failed to create video player:", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function destroyVideoPlayer() {
|
||||
if (videoPlayer) {
|
||||
videoPlayer.stop();
|
||||
videoPlayer.destroy();
|
||||
videoPlayer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (!SettingsData.lockScreenVideoEnabled || !SettingsData.lockScreenVideoPath)
|
||||
return;
|
||||
|
||||
if (!MultimediaService.available) {
|
||||
ToastService.showError(I18n.tr("Video Screensaver"), I18n.tr("QtMultimedia is not available"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!createVideoPlayer())
|
||||
return;
|
||||
|
||||
videoPicker.result = "";
|
||||
videoPicker.folder = "";
|
||||
inputEnabled = false;
|
||||
mouseInitialized = false;
|
||||
lastMousePos = Qt.point(-1, -1);
|
||||
active = true;
|
||||
inputEnableTimer.start();
|
||||
fileChecker.running = true;
|
||||
}
|
||||
|
||||
function dismiss() {
|
||||
if (!active)
|
||||
return;
|
||||
destroyVideoPlayer();
|
||||
inputEnabled = false;
|
||||
active = false;
|
||||
videoSource = "";
|
||||
dismissed();
|
||||
}
|
||||
|
||||
onVideoSourceChanged: {
|
||||
if (videoSource && active && videoPlayer) {
|
||||
videoPlayer.source = videoSource;
|
||||
videoPlayer.play();
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
enabled: root.active && root.inputEnabled
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: false
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (!root.mouseInitialized) {
|
||||
root.lastMousePos = Qt.point(mouse.x, mouse.y);
|
||||
root.mouseInitialized = true;
|
||||
return;
|
||||
}
|
||||
var dx = Math.abs(mouse.x - root.lastMousePos.x);
|
||||
var dy = Math.abs(mouse.y - root.lastMousePos.y);
|
||||
if (dx > 5 || dy > 5) {
|
||||
root.dismiss();
|
||||
}
|
||||
}
|
||||
onClicked: root.dismiss()
|
||||
onPressed: root.dismiss()
|
||||
onWheel: root.dismiss()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: IdleService
|
||||
|
||||
function onLockRequested() {
|
||||
if (SettingsData.lockScreenVideoEnabled && !root.active) {
|
||||
root.start();
|
||||
}
|
||||
}
|
||||
|
||||
function onFadeToLockRequested() {
|
||||
if (SettingsData.lockScreenVideoEnabled && !root.active) {
|
||||
IdleService.cancelFadeToLock();
|
||||
root.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Modals.FileBrowser
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.Settings.Widgets
|
||||
@@ -8,6 +9,16 @@ import qs.Modules.Settings.Widgets
|
||||
Item {
|
||||
id: root
|
||||
|
||||
FileBrowserModal {
|
||||
id: videoBrowserModal
|
||||
browserTitle: I18n.tr("Select Video or Folder")
|
||||
browserIcon: "movie"
|
||||
browserType: "video"
|
||||
showHiddenFiles: false
|
||||
fileExtensions: ["*.mp4", "*.mkv", "*.webm", "*.mov", "*.avi", "*.m4v"]
|
||||
onFileSelected: path => SettingsData.set("lockScreenVideoPath", path)
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
@@ -168,6 +179,87 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "movie"
|
||||
title: I18n.tr("Video Screensaver")
|
||||
settingKey: "videoScreensaver"
|
||||
|
||||
StyledText {
|
||||
visible: !MultimediaService.available
|
||||
text: I18n.tr("QtMultimedia is not available - video screensaver requires qt multimedia services")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.warning
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "lockScreenVideoEnabled"
|
||||
tags: ["lock", "screen", "video", "screensaver", "animation", "movie"]
|
||||
text: I18n.tr("Enable Video Screensaver")
|
||||
description: I18n.tr("Play a video when the screen locks.")
|
||||
enabled: MultimediaService.available
|
||||
checked: SettingsData.lockScreenVideoEnabled
|
||||
onToggled: checked => SettingsData.set("lockScreenVideoEnabled", checked)
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
visible: SettingsData.lockScreenVideoEnabled && MultimediaService.available
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Video Path")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Path to a video file or folder containing videos")
|
||||
font.pixelSize: Theme.fontSizeXSmall
|
||||
color: Theme.outlineVariant
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankTextField {
|
||||
id: videoPathField
|
||||
width: parent.width - browseVideoButton.width - Theme.spacingS
|
||||
placeholderText: I18n.tr("/path/to/videos")
|
||||
text: SettingsData.lockScreenVideoPath
|
||||
backgroundColor: Theme.surfaceContainerHighest
|
||||
onTextChanged: {
|
||||
if (text !== SettingsData.lockScreenVideoPath) {
|
||||
SettingsData.set("lockScreenVideoPath", text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankButton {
|
||||
id: browseVideoButton
|
||||
text: I18n.tr("Browse")
|
||||
onClicked: videoBrowserModal.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "lockScreenVideoCycling"
|
||||
tags: ["lock", "screen", "video", "screensaver", "cycling", "random", "shuffle"]
|
||||
text: I18n.tr("Automatic Cycling")
|
||||
description: I18n.tr("Pick a different random video each time from the same folder")
|
||||
visible: SettingsData.lockScreenVideoEnabled && MultimediaService.available
|
||||
enabled: MultimediaService.available
|
||||
checked: SettingsData.lockScreenVideoCycling
|
||||
onToggled: checked => SettingsData.set("lockScreenVideoCycling", checked)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "monitor"
|
||||
|
||||
Reference in New Issue
Block a user