summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolas Lécureuil <neoclust@mageia.org>2021-02-14 18:00:48 +0100
committerNicolas Lécureuil <neoclust@mageia.org>2021-02-14 18:00:48 +0100
commit828de2993de509f8805eb63d203e4c0ee9fdb3fa (patch)
tree2ab75ad165d0af597de666dadb1857ffed172c73
downloadmageia-sddm-828de2993de509f8805eb63d203e4c0ee9fdb3fa.tar
mageia-sddm-828de2993de509f8805eb63d203e4c0ee9fdb3fa.tar.gz
mageia-sddm-828de2993de509f8805eb63d203e4c0ee9fdb3fa.tar.bz2
mageia-sddm-828de2993de509f8805eb63d203e4c0ee9fdb3fa.tar.xz
mageia-sddm-828de2993de509f8805eb63d203e4c0ee9fdb3fa.zip
Initial commit
-rw-r--r--Background.qml69
-rw-r--r--BreezeMenuStyle.qml25
-rw-r--r--KeyboardButton.qml37
-rw-r--r--Login.qml123
-rw-r--r--Main.qml541
-rw-r--r--SessionButton.qml57
-rw-r--r--components/ActionButton.qml128
-rw-r--r--components/Battery.qml56
-rw-r--r--components/Clock.qml50
-rw-r--r--components/KeyboardLayoutButton.qml47
-rw-r--r--components/SessionManagementScreen.qml122
-rw-r--r--components/UserDelegate.qml191
-rw-r--r--components/UserList.qml99
-rw-r--r--components/VirtualKeyboard.qml28
-rw-r--r--components/WallpaperFader.qml184
-rw-r--r--components/artwork/logout_primary.svgzbin0 -> 2514 bytes
-rw-r--r--components/artwork/restart_primary.svgzbin0 -> 1860 bytes
-rw-r--r--components/artwork/shutdown_primary.svgzbin0 -> 1738 bytes
-rw-r--r--default-logo.svg4
-rw-r--r--faces/.face.icon14
-rw-r--r--metadata.desktop14
-rw-r--r--preview.pngbin0 -> 563330 bytes
-rw-r--r--theme.conf6
-rw-r--r--theme.conf.user3
24 files changed, 1798 insertions, 0 deletions
diff --git a/Background.qml b/Background.qml
new file mode 100644
index 0000000..a6a85c7
--- /dev/null
+++ b/Background.qml
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016 Boudhayan Gupta <bgupta@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.2
+
+FocusScope {
+ id: sceneBackground
+
+ property var sceneBackgroundType
+ property alias sceneBackgroundColor: sceneColorBackground.color
+ property alias sceneBackgroundImage: sceneImageBackground.source
+
+ Rectangle {
+ id: sceneColorBackground
+ anchors.fill: parent
+ }
+
+ Image {
+ id: sceneImageBackground
+ anchors.fill: parent
+ sourceSize.width: parent.width
+ sourceSize.height: parent.height
+ fillMode: Image.PreserveAspectCrop
+ smooth: true;
+ }
+
+ states: [
+ State {
+ name: "imageBackground"
+ when: sceneBackgroundType == "image"
+ PropertyChanges {
+ target: sceneColorBackground
+ visible: false
+ }
+ PropertyChanges {
+ target: sceneImageBackground
+ visible: true
+ }
+ },
+ State {
+ name: "colorBackground"
+ when: sceneBackgroundType != "image"
+ PropertyChanges {
+ target: sceneColorBackground
+ visible: true
+ }
+ PropertyChanges {
+ target: sceneImageBackground
+ visible: false
+ }
+ }
+ ]
+}
diff --git a/BreezeMenuStyle.qml b/BreezeMenuStyle.qml
new file mode 100644
index 0000000..7d56c14
--- /dev/null
+++ b/BreezeMenuStyle.qml
@@ -0,0 +1,25 @@
+import QtQuick 2.2
+
+import org.kde.plasma.core 2.0 as PlasmaCore
+
+import QtQuick.Controls.Styles 1.4 as QQCS
+import QtQuick.Controls 1.3 as QQC
+
+QQCS.MenuStyle {
+ frame: Rectangle {
+ color: PlasmaCore.ColorScope.backgroundColor
+ border.color: Qt.tint(PlasmaCore.ColorScope.textColor, Qt.rgba(color.r, color.g, color.b, 0.7))
+ border.width: 1
+ }
+ itemDelegate.label: QQC.Label {
+ height: contentHeight * 1.2
+ verticalAlignment: Text.AlignVCenter
+ color: styleData.selected ? PlasmaCore.ColorScope.highlightedTextColor : PlasmaCore.ColorScope.textColor
+ font.pointSize: config.fontSize
+ text: styleData.text
+ }
+ itemDelegate.background: Rectangle {
+ visible: styleData.selected
+ color: PlasmaCore.ColorScope.highlightColor
+ }
+}
diff --git a/KeyboardButton.qml b/KeyboardButton.qml
new file mode 100644
index 0000000..d5d5a44
--- /dev/null
+++ b/KeyboardButton.qml
@@ -0,0 +1,37 @@
+import QtQuick 2.2
+
+import org.kde.plasma.core 2.0 as PlasmaCore
+import org.kde.plasma.components 2.0 as PlasmaComponents // Because PC3 ToolButton can't take a menu
+
+import QtQuick.Controls 1.3 as QQC
+
+PlasmaComponents.ToolButton {
+ id: keyboardButton
+
+ property int currentIndex: -1
+
+ text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Keyboard Layout: %1", instantiator.objectAt(currentIndex).shortName)
+ implicitWidth: minimumWidth
+
+ visible: menu.items.length > 1
+
+ Component.onCompleted: currentIndex = Qt.binding(function() {return keyboard.currentLayout});
+
+ menu: QQC.Menu {
+ id: keyboardMenu
+ style: BreezeMenuStyle {}
+ Instantiator {
+ id: instantiator
+ model: keyboard.layouts
+ onObjectAdded: keyboardMenu.insertItem(index, object)
+ onObjectRemoved: keyboardMenu.removeItem( object )
+ delegate: QQC.MenuItem {
+ text: modelData.longName
+ property string shortName: modelData.shortName
+ onTriggered: {
+ keyboard.currentLayout = model.index
+ }
+ }
+ }
+ }
+}
diff --git a/Login.qml b/Login.qml
new file mode 100644
index 0000000..d063244
--- /dev/null
+++ b/Login.qml
@@ -0,0 +1,123 @@
+import "components"
+
+import QtQuick 2.0
+import QtQuick.Layouts 1.2
+
+import org.kde.plasma.core 2.0 as PlasmaCore
+import org.kde.plasma.components 3.0 as PlasmaComponents3
+
+SessionManagementScreen {
+ id: root
+ property Item mainPasswordBox: passwordBox
+
+ property bool showUsernamePrompt: !showUserList
+
+ property string lastUserName
+ property bool loginScreenUiVisible: false
+
+ //the y position that should be ensured visible when the on screen keyboard is visible
+ property int visibleBoundary: mapFromItem(loginButton, 0, 0).y
+ onHeightChanged: visibleBoundary = mapFromItem(loginButton, 0, 0).y + loginButton.height + units.smallSpacing
+
+ property int fontSize: parseInt(config.fontSize)
+
+ signal loginRequest(string username, string password)
+
+ onShowUsernamePromptChanged: {
+ if (!showUsernamePrompt) {
+ lastUserName = ""
+ }
+ }
+
+ /*
+ * Login has been requested with the following username and password
+ * If username field is visible, it will be taken from that, otherwise from the "name" property of the currentIndex
+ */
+ function startLogin() {
+ var username = showUsernamePrompt ? userNameInput.text : userList.selectedUser
+ var password = passwordBox.text
+
+ footer.enabled = false
+ mainStack.enabled = false
+ userListComponent.userList.opacity = 0.5
+
+ //this is partly because it looks nicer
+ //but more importantly it works round a Qt bug that can trigger if the app is closed with a TextField focused
+ //DAVE REPORT THE FRICKING THING AND PUT A LINK
+ loginButton.forceActiveFocus();
+ loginRequest(username, password);
+ }
+
+ PlasmaComponents3.TextField {
+ id: userNameInput
+ font.pointSize: fontSize + 1
+ Layout.fillWidth: true
+
+ text: lastUserName
+ visible: showUsernamePrompt
+ focus: showUsernamePrompt && !lastUserName //if there's a username prompt it gets focus first, otherwise password does
+ placeholderText: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Username")
+
+ onAccepted:
+ if (root.loginScreenUiVisible) {
+ passwordBox.forceActiveFocus()
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ PlasmaComponents3.TextField {
+ id: passwordBox
+ font.pointSize: fontSize + 1
+ Layout.fillWidth: true
+
+ placeholderText: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Password")
+ focus: !showUsernamePrompt || lastUserName
+ echoMode: TextInput.Password
+ revealPasswordButtonShown: false // Disabled whilst SDDM does not have the breeze icon set loaded
+
+ onAccepted: {
+ if (root.loginScreenUiVisible) {
+ startLogin();
+ }
+ }
+
+ Keys.onEscapePressed: {
+ mainStack.currentItem.forceActiveFocus();
+ }
+
+ //if empty and left or right is pressed change selection in user switch
+ //this cannot be in keys.onLeftPressed as then it doesn't reach the password box
+ Keys.onPressed: {
+ if (event.key === Qt.Key_Left && !text) {
+ userList.decrementCurrentIndex();
+ event.accepted = true
+ }
+ if (event.key === Qt.Key_Right && !text) {
+ userList.incrementCurrentIndex();
+ event.accepted = true
+ }
+ }
+
+ Connections {
+ target: sddm
+ function onLoginFailed() {
+ passwordBox.selectAll()
+ passwordBox.forceActiveFocus()
+ }
+ }
+ }
+
+ PlasmaComponents3.Button {
+ id: loginButton
+ Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Log In")
+ Layout.preferredHeight: passwordBox.implicitHeight
+ Layout.preferredWidth: loginButton.Layout.preferredHeight
+
+ icon.name: "go-next"
+
+ onClicked: startLogin();
+ }
+ }
+}
diff --git a/Main.qml b/Main.qml
new file mode 100644
index 0000000..43e444e
--- /dev/null
+++ b/Main.qml
@@ -0,0 +1,541 @@
+/*
+ * 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 3.0 as PlasmaComponents3
+import org.kde.plasma.extras 2.0 as PlasmaExtras
+
+import "components"
+
+// TODO: Once SDDM 0.19 is released and we are setting the font size using the
+// SDDM KCM's syncing feature, remove the `config.fontSize` overrides here and
+// the fontSize properties in various components, because the theme's default
+// font size will be correctly propagated to the login screen
+
+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
+ readonly property bool lightBackground: Math.max(PlasmaCore.ColorScope.backgroundColor.r, PlasmaCore.ColorScope.backgroundColor.g, PlasmaCore.ColorScope.backgroundColor.b) > 0.5
+
+ 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
+ }
+ }
+ }
+
+ 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: root.lightBackground ? PlasmaCore.ColorScope.backgroundColor : "black" // 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
+
+ if ( userListModel.hasOwnProperty("containsAllUsers") && !userListModel.containsAllUsers ) 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")
+ fontSize: parseInt(config.fontSize) + 1
+ onClicked: sddm.suspend()
+ enabled: sddm.canSuspend
+ visible: !inputPanel.keyboardActive
+ },
+ ActionButton {
+ iconSource: "system-reboot"
+ text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Restart")
+ fontSize: parseInt(config.fontSize) + 1
+ onClicked: sddm.reboot()
+ enabled: sddm.canReboot
+ visible: !inputPanel.keyboardActive
+ },
+ ActionButton {
+ iconSource: "system-shutdown"
+ text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Shut Down")
+ fontSize: parseInt(config.fontSize) + 1
+ 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...")
+ fontSize: parseInt(config.fontSize) + 1
+ 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
+ fontSize: parseInt(config.fontSize) + 2
+
+ // 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")
+ fontSize: parseInt(config.fontSize) + 1
+ onClicked: sddm.suspend()
+ enabled: sddm.canSuspend
+ visible: !inputPanel.keyboardActive
+ },
+ ActionButton {
+ iconSource: "system-reboot"
+ text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Restart")
+ fontSize: parseInt(config.fontSize) + 1
+ onClicked: sddm.reboot()
+ enabled: sddm.canReboot
+ visible: !inputPanel.keyboardActive
+ },
+ ActionButton {
+ iconSource: "system-shutdown"
+ text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Shut Down")
+ fontSize: parseInt(config.fontSize) + 1
+ onClicked: sddm.powerOff()
+ enabled: sddm.canPowerOff
+ visible: !inputPanel.keyboardActive
+ },
+ ActionButton {
+ iconSource: "system-user-list"
+ text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","List Users")
+ fontSize: parseInt(config.fontSize) + 1
+ onClicked: mainStack.pop()
+ visible: !inputPanel.keyboardActive
+ }
+ ]
+ }
+ }
+
+ DropShadow {
+ id: logoShadow
+ anchors.fill: logo
+ source: logo
+ visible: !softwareRendering && config.showlogo == "shown"
+ horizontalOffset: 1
+ verticalOffset: 1
+ radius: 6
+ samples: 14
+ spread: 0.3
+ color: "black" // matches Breeze window decoration and desktopcontainment
+ opacity: loginScreenRoot.uiVisible ? 0 : 1
+ Behavior on opacity {
+ //OpacityAnimator when starting from 0 is buggy (it shows one frame with opacity 1)"
+ NumberAnimation {
+ duration: units.longDuration
+ easing.type: Easing.InOutQuad
+ }
+ }
+ }
+
+ Image {
+ id: logo
+ visible: config.showlogo == "shown"
+ source: config.logo
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: footer.top
+ anchors.bottomMargin: units.largeSpacing
+ asynchronous: true
+ sourceSize.height: height
+ opacity: loginScreenRoot.uiVisible ? 0 : 1
+ fillMode: Image.PreserveAspectFit
+ height: Math.round(units.gridUnit * 3.5)
+ Behavior on opacity {
+ // OpacityAnimator when starting from 0 is buggy (it shows one frame with opacity 1)"
+ NumberAnimation {
+ duration: units.longDuration
+ easing.type: Easing.InOutQuad
+ }
+ }
+ }
+
+ //Footer
+ RowLayout {
+ id: footer
+ anchors {
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ margins: PlasmaCore.Units.smallSpacing
+ }
+
+ Behavior on opacity {
+ OpacityAnimator {
+ duration: units.longDuration
+ }
+ }
+
+ PlasmaComponents3.ToolButton {
+ text: i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "Button to show/hide virtual keyboard", "Virtual Keyboard")
+ font.pointSize: config.fontSize
+ icon.name: inputPanel.keyboardActive ? "input-keyboard-virtual-on" : "input-keyboard-virtual-off"
+ onClicked: inputPanel.showHide()
+ visible: inputPanel.status == Loader.Ready
+ }
+
+ KeyboardButton {
+ font.pointSize: config.fontSize
+ }
+
+ SessionButton {
+ id: sessionButton
+ font.pointSize: config.fontSize
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ Battery {
+ fontSize: config.fontSize
+ }
+ }
+ }
+
+ Connections {
+ target: sddm
+ function onLoginFailed() {
+ notificationMessage = i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Login Failed")
+ footer.enabled = true
+ mainStack.enabled = true
+ userListComponent.userList.opacity = 1
+ }
+ function 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 = ""
+ }
+}
diff --git a/SessionButton.qml b/SessionButton.qml
new file mode 100644
index 0000000..cdd730a
--- /dev/null
+++ b/SessionButton.qml
@@ -0,0 +1,57 @@
+/*
+ * 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.2
+
+import org.kde.plasma.core 2.0 as PlasmaCore
+import org.kde.plasma.components 2.0 as PlasmaComponents // Because PC3 ToolButton can't take a menu
+
+import QtQuick.Controls 1.3 as QQC
+
+PlasmaComponents.ToolButton {
+ id: root
+ property int currentIndex: -1
+
+ implicitWidth: minimumWidth
+
+ visible: menu.items.length > 1
+
+ text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Desktop Session: %1", instantiator.objectAt(currentIndex).text || "")
+
+ Component.onCompleted: {
+ currentIndex = sessionModel.lastIndex
+ }
+
+ menu: QQC.Menu {
+ id: menu
+ style: BreezeMenuStyle {}
+ Instantiator {
+ id: instantiator
+ model: sessionModel
+ onObjectAdded: menu.insertItem(index, object)
+ onObjectRemoved: menu.removeItem( object )
+ delegate: QQC.MenuItem {
+ text: model.name
+ onTriggered: {
+ root.currentIndex = model.index
+ }
+ }
+ }
+ }
+}
diff --git a/components/ActionButton.qml b/components/ActionButton.qml
new file mode 100644
index 0000000..342e0f9
--- /dev/null
+++ b/components/ActionButton.qml
@@ -0,0 +1,128 @@
+/*
+ * 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 org.kde.plasma.core 2.0 as PlasmaCore
+import org.kde.plasma.components 3.0 as PlasmaComponents3
+
+Item {
+ id: root
+ property alias text: label.text
+ property alias iconSource: icon.source
+ property alias containsMouse: mouseArea.containsMouse
+ property alias font: label.font
+ property alias labelRendering: label.renderType
+ property alias circleOpacity: iconCircle.opacity
+ property alias circleVisiblity: iconCircle.visible
+ property int fontSize: PlasmaCore.Theme.defaultFont.pointSize + 1
+ readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software
+ signal clicked
+
+ activeFocusOnTab: true
+
+ property int iconSize: units.gridUnit * 3
+
+ implicitWidth: Math.max(iconSize + units.largeSpacing * 2, label.contentWidth)
+ implicitHeight: iconSize + units.smallSpacing + label.implicitHeight
+
+ opacity: activeFocus || containsMouse ? 1 : 0.85
+ Behavior on opacity {
+ PropertyAnimation { // OpacityAnimator makes it turn black at random intervals
+ duration: units.longDuration
+ easing.type: Easing.InOutQuad
+ }
+ }
+
+ Rectangle {
+ id: iconCircle
+ anchors.centerIn: icon
+ width: iconSize + units.smallSpacing
+ height: width
+ radius: width / 2
+ color: softwareRendering ? PlasmaCore.ColorScope.backgroundColor : PlasmaCore.ColorScope.textColor
+ opacity: activeFocus || containsMouse ? (softwareRendering ? 0.8 : 0.15) : (softwareRendering ? 0.6 : 0)
+ Behavior on opacity {
+ PropertyAnimation { // OpacityAnimator makes it turn black at random intervals
+ duration: units.longDuration
+ easing.type: Easing.InOutQuad
+ }
+ }
+ }
+
+ Rectangle {
+ anchors.centerIn: iconCircle
+ width: iconCircle.width
+ height: width
+ radius: width / 2
+ scale: mouseArea.containsPress ? 1 : 0
+ color: PlasmaCore.ColorScope.textColor
+ opacity: 0.15
+ Behavior on scale {
+ PropertyAnimation {
+ duration: units.shortDuration
+ easing.type: Easing.InOutQuart
+ }
+ }
+ }
+
+ PlasmaCore.IconItem {
+ id: icon
+ anchors {
+ top: parent.top
+ horizontalCenter: parent.horizontalCenter
+ }
+ width: iconSize
+ height: iconSize
+
+ colorGroup: PlasmaCore.ColorScope.colorGroup
+ active: mouseArea.containsMouse || root.activeFocus
+ }
+
+ PlasmaComponents3.Label {
+ id: label
+ font.pointSize: root.fontSize
+ anchors {
+ top: icon.bottom
+ topMargin: (softwareRendering ? 1.5 : 1) * units.smallSpacing
+ left: parent.left
+ right: parent.right
+ }
+ style: softwareRendering ? Text.Outline : Text.Normal
+ styleColor: softwareRendering ? PlasmaCore.ColorScope.backgroundColor : "transparent" //no outline, doesn't matter
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignTop
+ wrapMode: Text.WordWrap
+ font.underline: root.activeFocus
+ }
+
+ MouseArea {
+ id: mouseArea
+ hoverEnabled: true
+ onClicked: root.clicked()
+ anchors.fill: parent
+ }
+
+ Keys.onEnterPressed: clicked()
+ Keys.onReturnPressed: clicked()
+ Keys.onSpacePressed: clicked()
+
+ Accessible.onPressAction: clicked()
+ Accessible.role: Accessible.Button
+ Accessible.name: label.text
+}
diff --git a/components/Battery.qml b/components/Battery.qml
new file mode 100644
index 0000000..93796c6
--- /dev/null
+++ b/components/Battery.qml
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016 Kai Uwe Broulik <kde@privat.broulik.de>
+ *
+ * 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.2
+
+import org.kde.plasma.core 2.0 as PlasmaCore
+import org.kde.plasma.components 3.0 as PlasmaComponents3
+import org.kde.plasma.workspace.components 2.0 as PW
+
+Row {
+ id: row
+
+ property int fontSize: PlasmaCore.Theme.defaultFont.pointSize
+
+ spacing: PlasmaCore.Units.smallSpacing
+ visible: pmSource.data["Battery"]["Has Cumulative"]
+
+ PlasmaCore.DataSource {
+ id: pmSource
+ engine: "powermanagement"
+ connectedSources: ["Battery", "AC Adapter"]
+ }
+
+ PW.BatteryIcon {
+ id: battery
+ hasBattery: pmSource.data["Battery"]["Has Battery"] || false
+ percent: pmSource.data["Battery"]["Percent"] || 0
+ pluggedIn: pmSource.data["AC Adapter"] ? pmSource.data["AC Adapter"]["Plugged in"] : false
+
+ height: batteryLabel.height
+ width: height
+ }
+
+ PlasmaComponents3.Label {
+ id: batteryLabel
+ font.pointSize: row.fontSize
+ text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","%1%", battery.percent)
+ Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Battery at %1%", battery.percent)
+ }
+}
diff --git a/components/Clock.qml b/components/Clock.qml
new file mode 100644
index 0000000..cece7be
--- /dev/null
+++ b/components/Clock.qml
@@ -0,0 +1,50 @@
+/*
+ * 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 2.5
+import org.kde.plasma.core 2.0
+
+ColumnLayout {
+ readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software
+
+ Label {
+ text: Qt.formatTime(timeSource.data["Local"]["DateTime"])
+ color: ColorScope.textColor
+ style: softwareRendering ? Text.Outline : Text.Normal
+ styleColor: softwareRendering ? ColorScope.backgroundColor : "transparent" //no outline, doesn't matter
+ font.pointSize: 48
+ Layout.alignment: Qt.AlignHCenter
+ }
+ Label {
+ text: Qt.formatDate(timeSource.data["Local"]["DateTime"], Qt.DefaultLocaleLongDate)
+ color: ColorScope.textColor
+ style: softwareRendering ? Text.Outline : Text.Normal
+ styleColor: softwareRendering ? ColorScope.backgroundColor : "transparent" //no outline, doesn't matter
+ font.pointSize: 24
+ Layout.alignment: Qt.AlignHCenter
+ }
+ DataSource {
+ id: timeSource
+ engine: "time"
+ connectedSources: ["Local"]
+ interval: 1000
+ }
+}
diff --git a/components/KeyboardLayoutButton.qml b/components/KeyboardLayoutButton.qml
new file mode 100644
index 0000000..483ca25
--- /dev/null
+++ b/components/KeyboardLayoutButton.qml
@@ -0,0 +1,47 @@
+/***************************************************************************
+ * Copyright (C) 2014 by Daniel Vrátil <dvratil@redhat.com> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, 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 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.1
+import QtQuick.Controls 1.1 as QQC
+
+import org.kde.plasma.components 3.0 as PlasmaComponents3
+
+import org.kde.plasma.workspace.keyboardlayout 1.0
+
+PlasmaComponents3.ToolButton {
+ id: kbLayoutButton
+
+ icon.name: "input-keyboard"
+ text: layout.currentLayoutDisplayName
+
+ Accessible.name: i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "Button to change keyboard layout", "Switch layout")
+
+ visible: layout.layouts.length > 1
+
+ onClicked: layout.nextLayout()
+
+ KeyboardLayout {
+ id: layout
+ function nextLayout() {
+ var layouts = layout.layouts;
+ var index = (layouts.indexOf(layout.currentLayout)+1) % layouts.length;
+ layout.currentLayout = layouts[index];
+ }
+ }
+}
diff --git a/components/SessionManagementScreen.qml b/components/SessionManagementScreen.qml
new file mode 100644
index 0000000..58110c5
--- /dev/null
+++ b/components/SessionManagementScreen.qml
@@ -0,0 +1,122 @@
+/*
+ * 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.2
+
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 1.1
+
+import org.kde.plasma.core 2.0 as PlasmaCore
+import org.kde.plasma.components 3.0 as PlasmaComponents3
+
+Item {
+ id: root
+
+ /*
+ * Any message to be displayed to the user, visible above the text fields
+ */
+ property alias notificationMessage: notificationsLabel.text
+
+ /*
+ * A list of Items (typically ActionButtons) to be shown in a Row beneath the prompts
+ */
+ property alias actionItems: actionItemsLayout.children
+
+ /*
+ * A model with a list of users to show in the view
+ * The following roles should exist:
+ * - name
+ * - iconSource
+ *
+ * The following are also handled:
+ * - vtNumber
+ * - displayNumber
+ * - session
+ * - isTty
+ */
+ property alias userListModel: userListView.model
+
+ /*
+ * Self explanatory
+ */
+ property alias userListCurrentIndex: userListView.currentIndex
+ property var userListCurrentModelData: userListView.currentItem === null ? [] : userListView.currentItem.m
+ property bool showUserList: true
+
+ property alias userList: userListView
+
+ property int fontSize: PlasmaCore.Theme.defaultFont.pointSize + 2
+
+ default property alias _children: innerLayout.children
+
+ UserList {
+ id: userListView
+ visible: showUserList && y > 0
+ anchors {
+ bottom: parent.verticalCenter
+ left: parent.left
+ right: parent.right
+ }
+ fontSize: root.fontSize
+ }
+
+ //goal is to show the prompts, in ~16 grid units high, then the action buttons
+ //but collapse the space between the prompts and actions if there's no room
+ //ui is constrained to 16 grid units wide, or the screen
+ ColumnLayout {
+ id: prompts
+ anchors.top: parent.verticalCenter
+ anchors.topMargin: units.gridUnit * 0.5
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ PlasmaComponents3.Label {
+ id: notificationsLabel
+ font.pointSize: root.fontSize
+ Layout.maximumWidth: units.gridUnit * 16
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.WordWrap
+ font.italic: true
+ }
+ ColumnLayout {
+ Layout.minimumHeight: implicitHeight
+ Layout.maximumHeight: units.gridUnit * 10
+ Layout.maximumWidth: units.gridUnit * 16
+ Layout.alignment: Qt.AlignHCenter
+ ColumnLayout {
+ id: innerLayout
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ }
+ Item {
+ Layout.fillHeight: true
+ }
+ }
+ Row { //deliberately not rowlayout as I'm not trying to resize child items
+ id: actionItemsLayout
+ spacing: units.largeSpacing / 2
+ Layout.alignment: Qt.AlignHCenter
+ }
+ Item {
+ Layout.fillHeight: true
+ }
+ }
+}
diff --git a/components/UserDelegate.qml b/components/UserDelegate.qml
new file mode 100644
index 0000000..8658b85
--- /dev/null
+++ b/components/UserDelegate.qml
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2014 David Edmundson <davidedmundson@kde.org>
+ * Copyright 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
+ *
+ * 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 QtGraphicalEffects 1.0
+
+import org.kde.plasma.core 2.0 as PlasmaCore
+import org.kde.plasma.components 3.0 as PlasmaComponents3
+
+Item {
+ id: wrapper
+
+ // 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
+
+ property bool isCurrent: true
+
+ readonly property var m: model
+ property string name
+ property string userName
+ property string avatarPath
+ property string iconSource
+ property bool constrainText: true
+ property alias nameFontSize: usernameDelegate.font.pointSize
+ property int fontSize: PlasmaCore.Theme.defaultFont.pointSize + 2
+ signal clicked()
+
+ property real faceSize: units.gridUnit * 7
+
+ opacity: isCurrent ? 1.0 : 0.5
+
+ Behavior on opacity {
+ OpacityAnimator {
+ duration: units.longDuration
+ }
+ }
+
+ // Draw a translucent background circle under the user picture
+ Rectangle {
+ anchors.centerIn: imageSource
+ width: imageSource.width - 2 // Subtract to prevent fringing
+ height: width
+ radius: width / 2
+
+ color: PlasmaCore.ColorScope.backgroundColor
+ opacity: 0.9
+ }
+
+ Item {
+ id: imageSource
+ anchors {
+ bottom: usernameDelegate.top
+ bottomMargin: units.largeSpacing
+ horizontalCenter: parent.horizontalCenter
+ }
+ Behavior on width {
+ PropertyAnimation {
+ from: faceSize
+ duration: units.longDuration;
+ }
+ }
+ width: isCurrent ? faceSize : faceSize - units.largeSpacing
+ height: width
+
+ //Image takes priority, taking a full path to a file, if that doesn't exist we show an icon
+ Image {
+ id: face
+ source: wrapper.avatarPath
+ sourceSize: Qt.size(faceSize, faceSize)
+ fillMode: Image.PreserveAspectCrop
+ anchors.fill: parent
+ }
+
+ PlasmaCore.IconItem {
+ id: faceIcon
+ source: iconSource
+ visible: (face.status == Image.Error || face.status == Image.Null)
+ anchors.fill: parent
+ anchors.margins: units.gridUnit * 0.5 // because mockup says so...
+ colorGroup: PlasmaCore.ColorScope.colorGroup
+ }
+ }
+
+ ShaderEffect {
+ anchors {
+ bottom: usernameDelegate.top
+ bottomMargin: units.largeSpacing
+ horizontalCenter: parent.horizontalCenter
+ }
+
+ width: imageSource.width
+ height: imageSource.height
+
+ supportsAtlasTextures: true
+
+ property var source: ShaderEffectSource {
+ sourceItem: imageSource
+ // software rendering is just a fallback so we can accept not having a rounded avatar here
+ hideSource: wrapper.GraphicsInfo.api !== GraphicsInfo.Software
+ live: true // otherwise the user in focus will show a blurred avatar
+ }
+
+ property var colorBorder: PlasmaCore.ColorScope.textColor
+
+ //draw a circle with an antialiased border
+ //innerRadius = size of the inner circle with contents
+ //outerRadius = size of the border
+ //blend = area to blend between two colours
+ //all sizes are normalised so 0.5 == half the width of the texture
+
+ //if copying into another project don't forget to connect themeChanged to update()
+ //but in SDDM that's a bit pointless
+ fragmentShader: "
+ varying highp vec2 qt_TexCoord0;
+ uniform highp float qt_Opacity;
+ uniform lowp sampler2D source;
+
+ uniform lowp vec4 colorBorder;
+ highp float blend = 0.01;
+ highp float innerRadius = 0.47;
+ highp float outerRadius = 0.49;
+ lowp vec4 colorEmpty = vec4(0.0, 0.0, 0.0, 0.0);
+
+ void main() {
+ lowp vec4 colorSource = texture2D(source, qt_TexCoord0.st);
+
+ highp vec2 m = qt_TexCoord0 - vec2(0.5, 0.5);
+ highp float dist = sqrt(m.x * m.x + m.y * m.y);
+
+ if (dist < innerRadius)
+ gl_FragColor = colorSource;
+ else if (dist < innerRadius + blend)
+ gl_FragColor = mix(colorSource, colorBorder, ((dist - innerRadius) / blend));
+ else if (dist < outerRadius)
+ gl_FragColor = colorBorder;
+ else if (dist < outerRadius + blend)
+ gl_FragColor = mix(colorBorder, colorEmpty, ((dist - outerRadius) / blend));
+ else
+ gl_FragColor = colorEmpty ;
+
+ gl_FragColor = gl_FragColor * qt_Opacity;
+ }
+ "
+ }
+
+ PlasmaComponents3.Label {
+ id: usernameDelegate
+ font.pointSize: wrapper.fontSize
+ anchors {
+ bottom: parent.bottom
+ horizontalCenter: parent.horizontalCenter
+ }
+ width: constrainText ? parent.width : implicitWidth
+ text: wrapper.name
+ style: softwareRendering ? Text.Outline : Text.Normal
+ styleColor: softwareRendering ? PlasmaCore.ColorScope.backgroundColor : "transparent" //no outline, doesn't matter
+ elide: Text.ElideRight
+ horizontalAlignment: Text.AlignHCenter
+ //make an indication that this has active focus, this only happens when reached with keyboard navigation
+ font.underline: wrapper.activeFocus
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true
+
+ onClicked: wrapper.clicked();
+ }
+
+ Accessible.name: name
+ Accessible.role: Accessible.Button
+ function accessiblePressAction() { wrapper.clicked() }
+}
diff --git a/components/UserList.qml b/components/UserList.qml
new file mode 100644
index 0000000..dbac102
--- /dev/null
+++ b/components/UserList.qml
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2014 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.2
+import org.kde.plasma.core 2.0 as PlasmaCore
+
+ListView {
+ id: view
+ readonly property string selectedUser: currentItem ? currentItem.userName : ""
+ readonly property int userItemWidth: units.gridUnit * 8
+ readonly property int userItemHeight: units.gridUnit * 8
+ property int fontSize: PlasmaCore.Theme.defaultFont.pointSize + 2
+
+ implicitHeight: userItemHeight
+
+ activeFocusOnTab : true
+
+ /*
+ * Signals that a user was explicitly selected
+ */
+ signal userSelected;
+
+ orientation: ListView.Horizontal
+ highlightRangeMode: ListView.StrictlyEnforceRange
+
+ //centre align selected item (which implicitly centre aligns the rest
+ preferredHighlightBegin: width/2 - userItemWidth/2
+ preferredHighlightEnd: preferredHighlightBegin
+
+ // Disable flicking if we only have on user (like on the lockscreen)
+ interactive: count > 1
+
+ delegate: UserDelegate {
+ avatarPath: model.icon || ""
+ iconSource: model.iconName || "user-identity"
+ fontSize: view.fontSize
+
+ name: {
+ var displayName = model.realName || model.name
+
+ if (model.vtNumber === undefined || model.vtNumber < 0) {
+ return displayName
+ }
+
+ if (!model.session) {
+ return i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "Nobody logged in on that session", "Unused")
+ }
+
+
+ var location = ""
+ if (model.isTty) {
+ location = i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "User logged in on console number", "TTY %1", model.vtNumber)
+ } else if (model.displayNumber) {
+ location = i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "User logged in on console (X display number)", "on TTY %1 (Display %2)", model.vtNumber, model.displayNumber)
+ }
+
+ if (location) {
+ return i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "Username (location)", "%1 (%2)", displayName, location)
+ }
+
+ return displayName
+ }
+
+ userName: model.name
+
+ width: userItemWidth
+ height: userItemHeight
+
+ //if we only have one delegate, we don't need to clip the text as it won't be overlapping with anything
+ constrainText: ListView.view.count > 1
+
+ isCurrent: ListView.isCurrentItem
+
+ onClicked: {
+ ListView.view.currentIndex = index;
+ ListView.view.userSelected();
+ }
+ }
+
+ Keys.onEscapePressed: view.userSelected()
+ Keys.onEnterPressed: view.userSelected()
+ Keys.onReturnPressed: view.userSelected()
+}
diff --git a/components/VirtualKeyboard.qml b/components/VirtualKeyboard.qml
new file mode 100644
index 0000000..7848b75
--- /dev/null
+++ b/components/VirtualKeyboard.qml
@@ -0,0 +1,28 @@
+/********************************************************************
+ This file is part of the KDE project.
+
+Copyright (C) 2017 Martin Gräßlin <mgraesslin@kde.org>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, 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 General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*********************************************************************/
+import QtQuick 2.5
+import QtQuick.VirtualKeyboard 2.1
+
+InputPanel {
+ id: inputPanel
+ property bool activated: false
+ active: activated && Qt.inputMethod.visible
+ visible: active
+ width: parent.width
+}
diff --git a/components/WallpaperFader.qml b/components/WallpaperFader.qml
new file mode 100644
index 0000000..f474acc
--- /dev/null
+++ b/components/WallpaperFader.qml
@@ -0,0 +1,184 @@
+/********************************************************************
+ This file is part of the KDE project.
+
+Copyright (C) 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, 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 General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*********************************************************************/
+
+import QtQuick 2.6
+import QtQuick.Controls 1.1
+import QtQuick.Layouts 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.private.sessions 2.0
+import "../components"
+
+Item {
+ id: wallpaperFader
+ property Item clock
+ property Item mainStack
+ property Item footer
+ property alias source: wallpaperBlur.source
+ state: lockScreenRoot.uiVisible ? "on" : "off"
+ property real factor: 0
+ readonly property bool lightBackground: Math.max(PlasmaCore.ColorScope.backgroundColor.r, PlasmaCore.ColorScope.backgroundColor.g, PlasmaCore.ColorScope.backgroundColor.b) > 0.5
+
+ property bool alwaysShowClock: typeof config === "undefined" || typeof config.alwaysShowClock === "undefined" || config.alwaysShowClock === true
+
+ Behavior on factor {
+ NumberAnimation {
+ target: wallpaperFader
+ property: "factor"
+ duration: 1000
+ easing.type: Easing.InOutQuad
+ }
+ }
+ FastBlur {
+ id: wallpaperBlur
+ anchors.fill: parent
+ radius: -100 * wallpaperFader.factor
+ }
+ ShaderEffect {
+ id: wallpaperShader
+ anchors.fill: parent
+ supportsAtlasTextures: true
+ property var source: ShaderEffectSource {
+ sourceItem: wallpaperBlur
+ live: true
+ hideSource: true
+ textureMirroring: ShaderEffectSource.NoMirroring
+ }
+
+ readonly property real contrast: 0.45 * wallpaperFader.factor + (1 - wallpaperFader.factor)
+ readonly property real saturation: 1.7 * wallpaperFader.factor + (1 - wallpaperFader.factor)
+ readonly property real intensity: (wallpaperFader.lightBackground ? 1.7 : 0.45) * wallpaperFader.factor + (1 - wallpaperFader.factor)
+
+ readonly property real transl: (1.0 - contrast) / 2.0;
+ readonly property real rval: (1.0 - saturation) * 0.2126;
+ readonly property real gval: (1.0 - saturation) * 0.7152;
+ readonly property real bval: (1.0 - saturation) * 0.0722;
+
+ property var colorMatrix: Qt.matrix4x4(
+ contrast, 0, 0, 0.0,
+ 0, contrast, 0, 0.0,
+ 0, 0, contrast, 0.0,
+ transl, transl, transl, 1.0).times(Qt.matrix4x4(
+ rval + saturation, rval, rval, 0.0,
+ gval, gval + saturation, gval, 0.0,
+ bval, bval, bval + saturation, 0.0,
+ 0, 0, 0, 1.0)).times(Qt.matrix4x4(
+ intensity, 0, 0, 0,
+ 0, intensity, 0, 0,
+ 0, 0, intensity, 0,
+ 0, 0, 0, 1
+ ));
+
+
+ fragmentShader: "
+ uniform mediump mat4 colorMatrix;
+ uniform mediump sampler2D source;
+ varying mediump vec2 qt_TexCoord0;
+ uniform lowp float qt_Opacity;
+
+ void main(void)
+ {
+ mediump vec4 tex = texture2D(source, qt_TexCoord0);
+ gl_FragColor = tex * colorMatrix * qt_Opacity;
+ }"
+ }
+
+ states: [
+ State {
+ name: "on"
+ PropertyChanges {
+ target: mainStack
+ opacity: 1
+ }
+ PropertyChanges {
+ target: footer
+ opacity: 1
+ }
+ PropertyChanges {
+ target: wallpaperFader
+ factor: 1
+ }
+ PropertyChanges {
+ target: clock.shadow
+ opacity: 0
+ }
+ },
+ State {
+ name: "off"
+ PropertyChanges {
+ target: mainStack
+ opacity: 0
+ }
+ PropertyChanges {
+ target: footer
+ opacity: 0
+ }
+ PropertyChanges {
+ target: wallpaperFader
+ factor: 0
+ }
+ PropertyChanges {
+ target: clock.shadow
+ opacity: 1
+ }
+ }
+ ]
+ transitions: [
+ Transition {
+ from: "off"
+ to: "on"
+ //Note: can't use animators as they don't play well with parallelanimations
+ ParallelAnimation {
+ NumberAnimation {
+ target: mainStack
+ property: "opacity"
+ duration: units.longDuration
+ easing.type: Easing.InOutQuad
+ }
+ NumberAnimation {
+ target: footer
+ property: "opacity"
+ duration: units.longDuration
+ easing.type: Easing.InOutQuad
+ }
+ }
+ },
+ Transition {
+ from: "on"
+ to: "off"
+ ParallelAnimation {
+ NumberAnimation {
+ target: mainStack
+ property: "opacity"
+ duration: 500
+ easing.type: Easing.InOutQuad
+ }
+ NumberAnimation {
+ target: footer
+ property: "opacity"
+ duration: 500
+ easing.type: Easing.InOutQuad
+ }
+ }
+ }
+ ]
+}
diff --git a/components/artwork/logout_primary.svgz b/components/artwork/logout_primary.svgz
new file mode 100644
index 0000000..6a9423f
--- /dev/null
+++ b/components/artwork/logout_primary.svgz
Binary files differ
diff --git a/components/artwork/restart_primary.svgz b/components/artwork/restart_primary.svgz
new file mode 100644
index 0000000..e2312bd
--- /dev/null
+++ b/components/artwork/restart_primary.svgz
Binary files differ
diff --git a/components/artwork/shutdown_primary.svgz b/components/artwork/shutdown_primary.svgz
new file mode 100644
index 0000000..1c60152
--- /dev/null
+++ b/components/artwork/shutdown_primary.svgz
Binary files differ
diff --git a/default-logo.svg b/default-logo.svg
new file mode 100644
index 0000000..722113d
--- /dev/null
+++ b/default-logo.svg
@@ -0,0 +1,4 @@
+<svg width="4626" height="1044" viewBox="0 0 4626 1044" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M712.182 149.566C686.396 149.566 665.636 170.325 665.636 196.111C665.636 221.897 686.396 242.656 712.182 242.656C737.968 242.656 758.727 221.897 758.727 196.111C758.727 170.325 737.968 149.566 712.182 149.566ZM1038 149.566L944.909 242.656L1084.55 382.293L944.909 521.929L1038 615.02L1177.64 475.384L1270.73 382.293L1038 149.566ZM595.818 428.838C557.139 428.838 526 459.977 526 498.656C526 537.336 557.139 568.475 595.818 568.475C634.497 568.475 665.636 537.336 665.636 498.656C665.636 459.977 634.497 428.838 595.818 428.838V428.838ZM805.273 708.111C753.7 708.111 712.182 749.63 712.182 801.202C712.182 852.774 753.7 894.293 805.273 894.293C856.845 894.293 898.364 852.774 898.364 801.202C898.364 749.63 856.845 708.111 805.273 708.111Z" fill="white"/>
+<path d="M1736.7 279.263C1806.2 279.263 1856.85 293.413 1888.62 321.712C1920.4 349.516 1936.28 388.49 1936.28 438.635C1936.28 468.424 1929.58 496.227 1916.18 522.045C1903.27 547.862 1881.42 568.963 1850.64 585.347C1819.86 601.234 1778.15 609.178 1725.52 609.178H1668.18V811H1587.75V279.263H1736.7ZM1730.74 347.033H1668.18V541.408H1716.59C1761.77 541.408 1795.78 533.96 1818.62 519.066C1841.95 503.675 1853.62 477.857 1853.62 441.614C1853.62 409.839 1843.69 386.256 1823.83 370.865C1803.97 354.977 1772.94 347.033 1730.74 347.033ZM2113.76 811H2034.81V245.005H2113.76V811ZM2393.37 402.143C2443.52 402.143 2481 413.314 2505.83 435.656C2531.15 457.501 2543.81 492.007 2543.81 539.174V811H2487.95L2472.31 755.145H2469.34C2451.96 776.991 2433.59 793.126 2414.23 803.553C2395.36 813.482 2369.04 818.447 2335.28 818.447C2299.04 818.447 2269 808.766 2245.17 789.403C2221.34 769.543 2209.42 738.761 2209.42 697.056C2209.42 655.848 2224.82 624.817 2255.6 603.965C2286.88 582.616 2334.29 570.949 2397.84 568.963L2466.36 566.729V544.387C2466.36 515.094 2459.65 494.49 2446.25 482.574C2432.84 470.162 2413.98 463.956 2389.65 463.956C2369.29 463.956 2349.68 466.935 2330.82 472.893C2311.95 478.85 2293.83 486.049 2276.45 494.49L2251.13 437.146C2270 427.216 2291.59 419.024 2315.92 412.569C2340.75 405.619 2366.56 402.143 2393.37 402.143ZM2465.61 618.115L2411.99 619.604C2366.31 621.59 2334.54 629.286 2316.67 642.691C2299.29 656.096 2290.6 674.466 2290.6 697.801C2290.6 718.653 2296.81 733.796 2309.22 743.23C2321.63 752.663 2337.77 757.379 2357.63 757.379C2388.41 757.379 2413.98 748.691 2434.33 731.314C2455.19 713.937 2465.61 687.871 2465.61 653.117V618.115ZM2931.98 697.801C2931.98 737.52 2917.58 767.557 2888.78 787.913C2860.48 808.269 2820.51 818.447 2768.88 818.447C2740.58 818.447 2716.25 816.461 2695.9 812.489C2676.04 808.518 2657.67 802.311 2640.79 793.871V724.611C2658.16 733.052 2678.52 740.747 2701.85 747.698C2725.69 754.152 2748.77 757.379 2771.11 757.379C2800.9 757.379 2822.25 752.663 2835.16 743.23C2848.57 733.796 2855.27 721.136 2855.27 705.248C2855.27 696.312 2852.79 688.368 2847.82 681.417C2842.86 673.97 2833.42 666.522 2819.52 659.075C2806.12 651.131 2786.26 642.195 2759.94 632.265C2734.13 622.335 2712.28 612.157 2694.41 601.731C2677.03 591.305 2663.62 578.892 2654.19 564.494C2644.76 550.096 2640.04 531.974 2640.04 510.129C2640.04 475.375 2653.94 448.813 2681.75 430.443C2710.05 411.576 2747.28 402.143 2793.46 402.143C2817.78 402.143 2840.62 404.626 2861.97 409.591C2883.82 414.555 2904.92 421.506 2925.27 430.443L2899.21 490.766C2881.83 483.319 2863.96 477.113 2845.59 472.148C2827.22 466.686 2808.6 463.956 2789.73 463.956C2741.08 463.956 2716.75 478.106 2716.75 506.405C2716.75 515.839 2719.48 524.031 2724.94 530.982C2730.9 537.932 2740.83 544.883 2754.73 551.834C2769.13 558.785 2788.99 567.225 2814.31 577.155C2838.64 586.588 2859.49 596.518 2876.87 606.944C2894.74 616.874 2908.39 629.038 2917.83 643.436C2927.26 657.834 2931.98 675.956 2931.98 697.801ZM3475.91 402.143C3521.09 402.143 3555.1 413.811 3577.94 437.146C3600.77 460.48 3612.19 497.965 3612.19 549.6V811H3533.25V560.026C3533.25 497.965 3508.18 466.935 3458.03 466.935C3422.29 466.935 3396.72 478.106 3381.33 500.448C3365.94 522.293 3358.24 553.82 3358.24 595.028V811H3280.04V560.026C3280.04 497.965 3254.72 466.935 3204.08 466.935C3166.35 466.935 3140.28 479.099 3125.88 503.427C3111.98 527.754 3105.03 562.757 3105.03 608.433V811H3026.09V409.591H3088.65L3099.82 463.211H3104.29C3116.7 442.359 3133.33 426.968 3154.18 417.038C3175.53 407.108 3198.12 402.143 3221.95 402.143C3283.52 402.143 3324.23 423.74 3344.09 466.935H3350.05C3362.96 445.089 3380.83 428.953 3403.67 418.527C3426.51 407.605 3450.59 402.143 3475.91 402.143ZM3888.65 402.143C3938.8 402.143 3976.28 413.314 4001.11 435.656C4026.43 457.501 4039.09 492.007 4039.09 539.174V811H3983.23L3967.59 755.145H3964.61C3947.24 776.991 3928.87 793.126 3909.5 803.553C3890.64 813.482 3864.32 818.447 3830.56 818.447C3794.32 818.447 3764.28 808.766 3740.45 789.403C3716.62 769.543 3704.7 738.761 3704.7 697.056C3704.7 655.848 3720.09 624.817 3750.88 603.965C3782.15 582.616 3829.57 570.949 3893.12 568.963L3961.63 566.729V544.387C3961.63 515.094 3954.93 494.49 3941.53 482.574C3928.12 470.162 3909.26 463.956 3884.93 463.956C3864.57 463.956 3844.96 466.935 3826.09 472.893C3807.23 478.85 3789.11 486.049 3771.73 494.49L3746.41 437.146C3765.27 427.216 3786.87 419.024 3811.2 412.569C3836.02 405.619 3861.84 402.143 3888.65 402.143ZM3960.89 618.115L3907.27 619.604C3861.59 621.59 3829.82 629.286 3811.94 642.691C3794.57 656.096 3785.88 674.466 3785.88 697.801C3785.88 718.653 3792.08 733.796 3804.5 743.23C3816.91 752.663 3833.04 757.379 3852.9 757.379C3883.69 757.379 3909.26 748.691 3929.61 731.314C3950.46 713.937 3960.89 687.871 3960.89 653.117V618.115Z" fill="white"/>
+</svg>
diff --git a/faces/.face.icon b/faces/.face.icon
new file mode 100644
index 0000000..ecee991
--- /dev/null
+++ b/faces/.face.icon
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#f2f2f2;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 11 3 A 3.9999902 4.0000296 0 0 0 7 7 A 3.9999902 4.0000296 0 0 0 11 11 A 3.9999902 4.0000296 0 0 0 15 7 A 3.9999902 4.0000296 0 0 0 11 3 z M 11 4 A 3 3.0000296 0 0 1 14 7 A 3 3.0000296 0 0 1 11 10 A 3 3.0000296 0 0 1 8 7 A 3 3.0000296 0 0 1 11 4 z M 11 12 A 7.9999504 8.0000296 0 0 0 3.0722656 19 L 4.0800781 19 A 6.9999604 7.0000296 0 0 1 11 13 A 6.9999604 7.0000296 0 0 1 17.921875 19 L 18.929688 19 A 7.9999504 8.0000296 0 0 0 11 12 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/metadata.desktop b/metadata.desktop
new file mode 100644
index 0000000..6a16594
--- /dev/null
+++ b/metadata.desktop
@@ -0,0 +1,14 @@
+[SddmGreeterTheme]
+Name=Mageia
+Description=Mageia Theme
+Author=Mageia
+License=CC-By-SA
+Type=sddm-theme
+Version=2.0
+Screenshot=preview.png
+MainScript=Main.qml
+ConfigFile=theme.conf
+TranslationsDirectory=translations
+Theme-Id=mageia
+Theme-API=2.0
+
diff --git a/preview.png b/preview.png
new file mode 100644
index 0000000..a09110a
--- /dev/null
+++ b/preview.png
Binary files differ
diff --git a/theme.conf b/theme.conf
new file mode 100644
index 0000000..975c0c2
--- /dev/null
+++ b/theme.conf
@@ -0,0 +1,6 @@
+[General]
+type=image
+color=#1d99f3
+background=/usr/share/mga/backgrounds/default.png
+blur=false
+fontSize=10
diff --git a/theme.conf.user b/theme.conf.user
new file mode 100644
index 0000000..09d5fa5
--- /dev/null
+++ b/theme.conf.user
@@ -0,0 +1,3 @@
+[General]
+background=
+type=image