SDDM is a display manager for X11 and Wayland windowing system, which provides us a login screen and helps us to launch a desktop environment such as KDE, LXQt, LXDE, and others. SDDM or Simple Desktop Display Manager was written from scratch in C++ and built upon Qt. It also offers theming support with QML markup.
On this blog post, I would like to share a trick on how to replace the SDDM login or greeting screen background with a video. The concept is fairly simple, add video player to the .qml (usually located at Main.qml) file and hide the existing background.
This trick, however, need a Qt module called “Qt Multimedia”, if you are using KDE then it’s installed by default.
Okay, let’s get started.
First off we need to choose the installed theme to edit, we can select it on the “login screen – system setting module” as shown below.
In this scenario, we use Breeze theme located at “/usr/share/sddm/themes/breeze” so let’s add a video that later will be used as the background in theme (breeze) folder.
$sudo cp /path/to/video.mp4 /usr/share/sddm/themes/breeze/
Open the .qml file and lets start coding
$sudo vim /usr/share/sddm/themes/breeze/Main.qml
Add import statement for QtMultimedia on the top section of .qml file. It looks like this.
import QtMultimedia 5.13
Add MediaPlayer and VideoOutput blocks below the Item block.
MediaPlayer {
id: videoPlayer
source: "video.mp4"
autoPlay: true
}
VideoOutput {
source: videoPlayer
anchors.fill: parent
}
Explanation:
MediaPlayer -> to add media playback to the login screen
id: videoPlayer -> set id to the MediaPlayer that later will be used as a reference for VideoOutput, we can set the id as we like.
source: “video.mp4” -> video filename
autoPlay: true -> set the playback to autplay
VideoOutput -> to render video
source: videoPlayer -> media playback reference
anchors.fill: parent -> for positioning
You can add more params to the syntax above so it can be fully customized according your needs, for example set playbackRate: 0.5 on MediaPlayer to set the playback speed to 0.5. For more reference about MediaPlayer, you can read more here.
Save your config above and let’s preview it with sddm-greeter
sddm-greeter --test-mode --theme /usr/share/sddm/themes/breeze
If there is no error it should preview your login screen like this.
Done! Make sure to activate the theme and reboot your computer. It should display SDDM login with video background. Here is full preview of my login page.
My Main.qml file looks like this
/*
* Copyright 2016 David Edmundson <davidedmundson@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import QtQuick 2.8
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.1
import QtGraphicalEffects 1.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.plasma.extras 2.0 as PlasmaExtras
import QtMultimedia 5.13
import "components"
PlasmaCore.ColorScope {
id: root
// If we're using software rendering, draw outlines instead of shadows
// See https://bugs.kde.org/show_bug.cgi?id=398317
readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
width: 1600
height: 900
property string notificationMessage
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
PlasmaCore.DataSource {
id: keystateSource
engine: "keystate"
connectedSources: "Caps Lock"
}
Item {
id: wallpaper
anchors.fill: parent
Repeater {
model: screenModel
Background {
x: geometry.x; y: geometry.y; width: geometry.width; height: geometry.height
sceneBackgroundType: config.type
sceneBackgroundColor: config.color
sceneBackgroundImage: config.background
visible: false
}
}
}
MediaPlayer {
id: videoPlayer
source: "coverr-volcano-postcard-1559213301682.mp4"
playbackRate: 0.9
autoPlay: true
loops: -1
}
VideoOutput {
source: videoPlayer
anchors.fill: parent
}
MouseArea {
id: loginScreenRoot
anchors.fill: parent
property bool uiVisible: true
property bool blockUI: mainStack.depth > 1 || userListComponent.mainPasswordBox.text.length > 0 || inputPanel.keyboardActive || config.type !== "image"
hoverEnabled: true
drag.filterChildren: true
onPressed: uiVisible = true;
onPositionChanged: uiVisible = true;
onUiVisibleChanged: {
if (blockUI) {
fadeoutTimer.running = false;
} else if (uiVisible) {
fadeoutTimer.restart();
}
}
onBlockUIChanged: {
if (blockUI) {
fadeoutTimer.running = false;
uiVisible = true;
} else {
fadeoutTimer.restart();
}
}
Keys.onPressed: {
uiVisible = true;
event.accepted = false;
}
//takes one full minute for the ui to disappear
Timer {
id: fadeoutTimer
running: true
interval: 60000
onTriggered: {
if (!loginScreenRoot.blockUI) {
loginScreenRoot.uiVisible = false;
}
}
}
WallpaperFader {
visible: config.type === "image"
anchors.fill: parent
state: loginScreenRoot.uiVisible ? "on" : "off"
source: wallpaper
mainStack: mainStack
footer: footer
clock: clock
}
DropShadow {
id: clockShadow
anchors.fill: clock
source: clock
visible: !softwareRendering
horizontalOffset: 1
verticalOffset: 1
radius: 6
samples: 14
spread: 0.3
color: "black" // matches Breeze window decoration and desktopcontainment
Behavior on opacity {
OpacityAnimator {
duration: 1000
easing.type: Easing.InOutQuad
}
}
}
Clock {
id: clock
visible: y > 0
property Item shadow: clockShadow
y: (userListComponent.userList.y + mainStack.y)/2 - height/2
anchors.horizontalCenter: parent.horizontalCenter
}
StackView {
id: mainStack
anchors {
left: parent.left
right: parent.right
}
height: root.height + units.gridUnit * 3
focus: true //StackView is an implicit focus scope, so we need to give this focus so the item inside will have it
Timer {
//SDDM has a bug in 0.13 where even though we set the focus on the right item within the window, the window doesn't have focus
//it is fixed in 6d5b36b28907b16280ff78995fef764bb0c573db which will be 0.14
//we need to call "window->activate()" *After* it's been shown. We can't control that in QML so we use a shoddy timer
//it's been this way for all Plasma 5.x without a huge problem
running: true
repeat: false
interval: 200
onTriggered: mainStack.forceActiveFocus()
}
initialItem: Login {
id: userListComponent
userListModel: userModel
loginScreenUiVisible: loginScreenRoot.uiVisible
userListCurrentIndex: userModel.lastIndex >= 0 ? userModel.lastIndex : 0
lastUserName: userModel.lastUser
showUserList: {
if ( !userListModel.hasOwnProperty("count")
|| !userListModel.hasOwnProperty("disableAvatarsThreshold"))
return (userList.y + mainStack.y) > 0
if ( userListModel.count === 0 ) return false
return userListModel.count <= userListModel.disableAvatarsThreshold && (userList.y + mainStack.y) > 0
}
notificationMessage: {
var text = ""
if (keystateSource.data["Caps Lock"]["Locked"]) {
text += i18nd("plasma_lookandfeel_org.kde.lookandfeel","Caps Lock is on")
if (root.notificationMessage) {
text += " • "
}
}
text += root.notificationMessage
return text
}
actionItems: [
ActionButton {
iconSource: "system-suspend"
text: i18ndc("plasma_lookandfeel_org.kde.lookandfeel","Suspend to RAM","Sleep")
onClicked: sddm.suspend()
enabled: sddm.canSuspend
visible: !inputPanel.keyboardActive
},
ActionButton {
iconSource: "system-reboot"
text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Restart")
onClicked: sddm.reboot()
enabled: sddm.canReboot
visible: !inputPanel.keyboardActive
},
ActionButton {
iconSource: "system-shutdown"
text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Shut Down")
onClicked: sddm.powerOff()
enabled: sddm.canPowerOff
visible: !inputPanel.keyboardActive
},
ActionButton {
iconSource: "system-user-prompt"
text: i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "For switching to a username and password prompt", "Other...")
onClicked: mainStack.push(userPromptComponent)
enabled: true
visible: !userListComponent.showUsernamePrompt && !inputPanel.keyboardActive
}]
onLoginRequest: {
root.notificationMessage = ""
sddm.login(username, password, sessionButton.currentIndex)
}
}
Behavior on opacity {
OpacityAnimator {
duration: units.longDuration
}
}
}
Loader {
id: inputPanel
state: "hidden"
property bool keyboardActive: item ? item.active : false
onKeyboardActiveChanged: {
if (keyboardActive) {
state = "visible"
} else {
state = "hidden";
}
}
source: "components/VirtualKeyboard.qml"
anchors {
left: parent.left
right: parent.right
}
function showHide() {
state = state == "hidden" ? "visible" : "hidden";
}
states: [
State {
name: "visible"
PropertyChanges {
target: mainStack
y: Math.min(0, root.height - inputPanel.height - userListComponent.visibleBoundary)
}
PropertyChanges {
target: inputPanel
y: root.height - inputPanel.height
opacity: 1
}
},
State {
name: "hidden"
PropertyChanges {
target: mainStack
y: 0
}
PropertyChanges {
target: inputPanel
y: root.height - root.height/4
opacity: 0
}
}
]
transitions: [
Transition {
from: "hidden"
to: "visible"
SequentialAnimation {
ScriptAction {
script: {
inputPanel.item.activated = true;
Qt.inputMethod.show();
}
}
ParallelAnimation {
NumberAnimation {
target: mainStack
property: "y"
duration: units.longDuration
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: inputPanel
property: "y"
duration: units.longDuration
easing.type: Easing.OutQuad
}
OpacityAnimator {
target: inputPanel
duration: units.longDuration
easing.type: Easing.OutQuad
}
}
}
},
Transition {
from: "visible"
to: "hidden"
SequentialAnimation {
ParallelAnimation {
NumberAnimation {
target: mainStack
property: "y"
duration: units.longDuration
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: inputPanel
property: "y"
duration: units.longDuration
easing.type: Easing.InQuad
}
OpacityAnimator {
target: inputPanel
duration: units.longDuration
easing.type: Easing.InQuad
}
}
ScriptAction {
script: {
Qt.inputMethod.hide();
}
}
}
}
]
}
Component {
id: userPromptComponent
Login {
showUsernamePrompt: true
notificationMessage: root.notificationMessage
loginScreenUiVisible: loginScreenRoot.uiVisible
// using a model rather than a QObject list to avoid QTBUG-75900
userListModel: ListModel {
ListElement {
name: ""
iconSource: ""
}
Component.onCompleted: {
// as we can't bind inside ListElement
setProperty(0, "name", i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Type in Username and Password"));
}
}
onLoginRequest: {
root.notificationMessage = ""
sddm.login(username, password, sessionButton.currentIndex)
}
actionItems: [
ActionButton {
iconSource: "system-suspend"
text: i18ndc("plasma_lookandfeel_org.kde.lookandfeel","Suspend to RAM","Sleep")
onClicked: sddm.suspend()
enabled: sddm.canSuspend
visible: !inputPanel.keyboardActive
},
ActionButton {
iconSource: "system-reboot"
text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Restart")
onClicked: sddm.reboot()
enabled: sddm.canReboot
visible: !inputPanel.keyboardActive
},
ActionButton {
iconSource: "system-shutdown"
text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Shut Down")
onClicked: sddm.powerOff()
enabled: sddm.canPowerOff
visible: !inputPanel.keyboardActive
},
ActionButton {
iconSource: "system-user-list"
text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","List Users")
onClicked: mainStack.pop()
visible: !inputPanel.keyboardActive
}
]
}
}
//Footer
RowLayout {
id: footer
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
margins: units.smallSpacing
}
Behavior on opacity {
OpacityAnimator {
duration: units.longDuration
}
}
PlasmaComponents.ToolButton {
text: i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "Button to show/hide virtual keyboard", "Virtual Keyboard")
font.pointSize: config.fontSize
iconName: inputPanel.keyboardActive ? "input-keyboard-virtual-on" : "input-keyboard-virtual-off"
onClicked: inputPanel.showHide()
visible: inputPanel.status == Loader.Ready
}
KeyboardButton {
}
SessionButton {
id: sessionButton
}
Item {
Layout.fillWidth: true
}
Battery { }
}
}
Connections {
target: sddm
onLoginFailed: {
notificationMessage = i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Login Failed")
footer.enabled = true
mainStack.enabled = true
userListComponent.userList.opacity = 1
}
onLoginSucceeded: {
//note SDDM will kill the greeter at some random point after this
//there is no certainty any transition will finish, it depends on the time it
//takes to complete the init
mainStack.opacity = 0
footer.opacity = 0
}
}
onNotificationMessageChanged: {
if (notificationMessage) {
notificationResetTimer.start();
}
}
Timer {
id: notificationResetTimer
interval: 3000
onTriggered: notificationMessage = ""
}
}
References
4 Comments