import QtQuick 2.8 import SddmComponents 2.0 import QtQuick.Layouts 1.15 import QtGraphicalEffects 1.0 import QtQuick.Controls 2.8 as QQC2 import "SMOD" as SMOD 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 org.kde.kirigami 2.13 as Kirigami import org.kde.plasma.workspace.components 2.0 as PW Item { id: root LayoutMirroring.enabled : Qt.locale().textDirection == Qt.RightToLeft LayoutMirroring.childrenInherit : true property bool m_forceUserSelect: config.boolValue("forceUserSelect") property bool m_biggerUserFrame: config.boolValue("biggerUserFrame") property bool m_biggerMultiUserFrame: config.boolValue("biggerMultiUserFrame") enum LoginPage { SelectUser, Login, LoginFailed } // copied from sddm/src/greeter/UserModel.h because // it doesn't seem accessible via e.g. SDDM.UserModel.NameRole enum UserRoles { NameRole = 257, RealNameRole, HomeDirRole, IconRole, NeedsPasswordRole } Connections { target: sddm function onLoginFailed() { password.text = "" pages.currentIndex = Main.LoginPage.LoginFailed } } Keys.onEscapePressed: { if (pages.currentIndex === Main.LoginPage.Login && switchuser.enabled) { password.text = "" pages.currentIndex = Main.LoginPage.SelectUser } else if (pages.currentIndex === Main.LoginPage.LoginFailed) { pages.currentIndex = Main.LoginPage.Login } } PlasmaCore.DataSource { id: keystateSource engine: "keystate" connectedSources: "Caps Lock" } FontLoader { id: mainfont source: "font.ttf" } Background { anchors.fill: parent fillMode: Image.Stretch source: config.stringValue("background") } Component { id: userDelegate Column { id: delegateColumn width: listView.cellWidth height: listView.cellHeight Item { id: avatarparent width: m_biggerMultiUserFrame ? 100 : 80 height: width anchors.centerIn: parent Item { width: m_biggerMultiUserFrame ? 60 : 48 height: width anchors.centerIn: parent Rectangle { id: maskmini anchors.fill: parent anchors.centerIn: parent radius: 2 visible: false } Image { id: avatarmini property string m_fallbackPicture: "Assets/user.png" onStatusChanged: { if (avatarmini.status == Image.Error) { avatarmini.source = avatarmini.m_fallbackPicture; } } source: model.icon fillMode: Image.PreserveAspectCrop anchors.fill: parent anchors.centerIn: parent layer.enabled: true layer.effect: OpacityMask { maskSource: maskmini } } } Image { id: avatarminiframe property bool m_hovered: false source: { if (m_biggerMultiUserFrame) { if (m_hovered && delegateColumn.focus) return "Assets/12235.png" if (m_hovered && !delegateColumn.focus) return "Assets/12233.png" if (!m_hovered && delegateColumn.focus) return "Assets/12234.png" if (!m_hovered && !delegateColumn.focus) return "Assets/12237.png" } else { if (m_hovered && delegateColumn.focus) return "Assets/12220.png" if (m_hovered && !delegateColumn.focus) return "Assets/12218.png" if (!m_hovered && delegateColumn.focus) return "Assets/12219.png" if (!m_hovered && !delegateColumn.focus) return "Assets/12222.png" } } anchors.fill: parent anchors.centerIn: parent } } Text { text: (model.realName === "") ? model.name : model.realName color: "white" font.pixelSize: 12 //font.family: mainfont.name renderType: Text.NativeRendering font.hintingPreference: Font.PreferFullHinting font.kerning: false anchors.top: avatarparent.bottom //anchors.topMargin: -3 anchors.horizontalCenter: avatarparent.horizontalCenter layer.enabled: true layer.effect: DropShadow { verticalOffset: 1 color: "#000" radius: 7 samples: 20 } } MouseArea { anchors.fill: delegateColumn hoverEnabled: true onEntered: { avatarminiframe.m_hovered = true } onExited: { avatarminiframe.m_hovered = false } } Keys.onReturnPressed: { if (focus) { let username = model.name if (username != null) { let realname = model.realName let pic = model.icon let needspassword = model.needsPassword if (needspassword) { userNameLabel.text = realname avatar.source = pic listView.currentIndex = index pages.currentIndex = Main.LoginPage.Login } else { sddm.login(username, password.text, session.index) } } } } } } StackLayout { id: pages anchors.fill: parent onCurrentIndexChanged: { if (currentIndex == Main.LoginPage.SelectUser) { listView.forceActiveFocus() } else if (currentIndex == Main.LoginPage.Login) { password.forceActiveFocus() } else if (currentIndex == Main.LoginPage.LoginFailed) { dismissButton.forceActiveFocus() } } // for testing failed login //currentIndex: Main.LoginPage.LoginFailed Component.onCompleted: { let singleusermode = userModel.count < 2 && !m_forceUserSelect if (singleusermode) { let index = 0; let username = userModel.data(userModel.index(index, 0), Main.UserRoles.NameRole) if (username != null) { let userDisplayName = userModel.data(userModel.index(index, 0), Main.UserRoles.RealNameRole) let userPicture = userModel.data(userModel.index(index, 0), Main.UserRoles.IconRole) //console.log(userDisplayName) userNameLabel.text = userDisplayName avatar.source = userPicture switchuser.enabled = false switchuser.visible = false pages.currentIndex = Main.LoginPage.Login } } } Item { id: userlistpage anchors.fill: parent GridView { id: listView anchors.centerIn: parent anchors.verticalCenterOffset: { if (count < 5) { return 72 } else { return 0 } } width: { if (count < 5) { return cellWidth * count } else { return cellWidth * 5 } } height: { if (count > 5) { return 200 * 2 } else { return 200 } } clip: true interactive: true keyNavigationEnabled: true keyNavigationWraps: false focus: true boundsBehavior: Flickable.StopAtBounds QQC2.ScrollBar.vertical: QQC2.ScrollBar {} cellWidth: 200 cellHeight: 200 model: userModel delegate: userDelegate currentIndex: userModel.lastIndex KeyNavigation.backtab: rebootButton KeyNavigation.tab: accessbutton MouseArea { anchors.fill: parent onClicked: { let posInGridView = Qt.point(mouse.x, mouse.y) let posInContentItem = mapToItem(listView.contentItem, posInGridView) let index = listView.indexAt(posInContentItem.x, posInContentItem.y) let username = userModel.data(userModel.index(index, 0), Main.UserRoles.NameRole) if (username != null) { let realname = userModel.data(userModel.index(index, 0), Main.UserRoles.RealNameRole) let pic = userModel.data(userModel.index(index, 0), Main.UserRoles.IconRole) let needspassword = userModel.data(userModel.index(index, 0), Main.UserRoles.NeedsPasswordRole) if (needspassword) { userNameLabel.text = realname == "" ? username : realname; avatar.source = pic listView.currentIndex = index pages.currentIndex = Main.LoginPage.Login } else { sddm.login(username, password.text, session.index) } } } } } } Item { id: loginpage Column { id: mainColumn anchors.centerIn: parent Item { id: userpic anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenterOffset: -1 anchors.bottom: parent.verticalCenter anchors.bottomMargin: m_biggerUserFrame ? -64 : -56 width: m_biggerUserFrame ? 238 : 190 height: width Item { width: m_biggerUserFrame ? 158 : 126 height: width anchors.centerIn: parent Rectangle { id: mask anchors.fill: parent anchors.centerIn: parent radius: 2 visible: false } Image { id: avatar fillMode: Image.PreserveAspectCrop source: "" property string defaultPic: "Assets/user.png" onStatusChanged: { if (avatar.status == Image.Error) { avatar.source = avatar.defaultPic; } } anchors.fill: parent anchors.centerIn: parent layer.enabled: true layer.effect: OpacityMask { maskSource: mask } } } Image { source: m_biggerUserFrame ? "Assets/12238.png" : "Assets/12223.png" anchors.fill: parent anchors.centerIn: parent } } Item { id: loginbox anchors.horizontalCenter: parent.horizontalCenter anchors.top: userpic.bottom anchors.topMargin: 4 QQC2.Label { id: userNameLabel anchors.horizontalCenter: parent.horizontalCenter text: "" color: "white" //font.family: mainfont.name font.pixelSize: 23 font.kerning: false renderType: Text.NativeRendering font.hintingPreference: Font.PreferVerticalHinting //font.weight: Font.Medium layer.enabled: true layer.effect: DropShadow { verticalOffset: 1 color: "#ef000000" radius: 9 samples: 80 } } QQC2.TextField { id: password anchors.top: userNameLabel.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 8 width: 225 height: 25 leftPadding: 7 font.pointSize: { if (password.length > 0) { return 7 } return 9 } //font.family: mainfont.name placeholderTextColor: "#555" background: Image { source: { if (password.focus) return "Assets/input-focus.png" if (password.hovered) return "Assets/input-hover.png" return "Assets/input.png" } } placeholderText: "Password" selectByMouse: true echoMode : TextInput.Password inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText KeyNavigation.backtab: switchLayoutButton KeyNavigation.tab: loginButton KeyNavigation.down: { if (switchuser.enabled) { return switchuser } else { return accessbutton } } Keys.onPressed : { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { let index = listView.currentIndex let username = userModel.data(userModel.index(index, 0), Main.UserRoles.NameRole) if (username != null) { sddm.login(username, password.text, session.index) } event.accepted = true } else if ( event.matches(StandardKey.Undo) || event.matches(StandardKey.Redo) || event.matches(StandardKey.Cut) || event.matches(StandardKey.Copy) || event.matches(StandardKey.Paste) ) { // disable these events event.accepted = true } } } RowLayout { spacing: 2 anchors.top: password.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 2 visible: keystateSource.data["Caps Lock"]["Locked"] && password.visible Image { id: iconSmall width: PlasmaCore.Units.iconSizes.small; height: PlasmaCore.Units.iconSizes.small; source: "./Assets/dialog-warning.png" sourceSize.width: iconSmall.width sourceSize.height: iconSmall.height } QQC2.Label { id: notificationsLabel font.pointSize: 9 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Caps Lock is on"); width: implicitWidth color: "white" } } QQC2.Button { id: loginButton anchors.left: password.right anchors.verticalCenter: password.verticalCenter anchors.verticalCenterOffset: -1 anchors.leftMargin: 4 width: 30 height: 30 background: Image { source: loginButton.pressed ? "Assets/gopressed.png" : (loginButton.focus || loginButton.hovered ? "Assets/gohover.png" : "Assets/go.png") /*source: { if (loginButton.pressed) return "Assets/12275.png" if (loginButton.hovered && loginButton.focus) return "Assets/12274.png" if (loginButton.hovered && !loginButton.focus) return "Assets/12274.png" if (!loginButton.hovered && loginButton.focus) return "Assets/12276.png" return "Assets/12276.png" }*/ } onClicked: { let index = listView.currentIndex let username = userModel.data(userModel.index(index, 0), Main.UserRoles.NameRole) if (username != null) { sddm.login(username, password.text, session.index) } } KeyNavigation.backtab: password KeyNavigation.tab: { if (switchuser.enabled) { return switchuser } else { return accessbutton } } KeyNavigation.down: { if (switchuser.enabled) { return switchuser } else { return accessbutton } } } } QQC2.Button { id: switchuser anchors.horizontalCenter: parent.horizontalCenter anchors.top: userpic.bottom anchors.topMargin: 124 //anchors.bottom: parent.bottom //anchors.bottomMargin: 332 width: 108 height: 28 background: Image { source: { if (switchuser.pressed) return "Assets/switch-user-button-active.png" if (switchuser.hovered && switchuser.focus) return "Assets/switch-user-button-hover-focus.png" if (switchuser.hovered && !switchuser.focus) return "Assets/switch-user-button-hover.png" if (!switchuser.hovered && switchuser.focus) return "Assets/switch-user-button-focus.png" return "Assets/switch-user-button.png" } } contentItem: Text { text: "Switch User" color: "white" //font.family: mainfont.name font.pointSize: 11 font.kerning: false renderType: Text.NativeRendering font.hintingPreference: Font.PreferFullHinting horizontalAlignment : Text.AlignHCenter verticalAlignment : Text.AlignVCenter bottomPadding: 3 } KeyNavigation.backtab: password KeyNavigation.up: password KeyNavigation.tab: accessbutton KeyNavigation.down: accessbutton onClicked: { password.text = "" pages.currentIndex = Main.LoginPage.SelectUser } Keys.onReturnPressed: { clicked() event.accepted = true } } } } Item { id: loginfailedpage Keys.onEscapePressed: { pages.currentIndex = Main.LoginPage.Login } Image { id: currentMessageIcon anchors.right: currentMessage.left anchors.verticalCenter: currentMessage.verticalCenter anchors.verticalCenterOffset: -2 anchors.rightMargin: 11 source: "Assets/dialog-error.png" width: 32 height: 32 focus: false smooth: false } QQC2.Label { id: currentMessage anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenterOffset: 126 anchors.horizontalCenterOffset: 14 text: "The user name or password is incorrect." Layout.alignment: Qt.AlignHCenter font.pointSize: 9 focus: false width: implicitWidth color: "white" horizontalAlignment: Text.AlignCenter layer.enabled: true layer.effect: DropShadow { horizontalOffset: 0 verticalOffset: 1 radius: 6 samples: 14 spread: 0.0001 color: "#bf000000" } } QQC2.Button { id: dismissButton anchors.top: currentMessage.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 46 width: 93 height: 28 background: Image { source: { if (dismissButton.pressed) return "Assets/button-active.png" if (dismissButton.hovered && dismissButton.focus) return "Assets/button-hover-focus.png" if (dismissButton.hovered && !dismissButton.focus) return "Assets/button-hover.png" if (!dismissButton.hovered && dismissButton.focus) return "Assets/button-focus.png" return "Assets/button.png" } } contentItem: Text { text: "OK" color: "white" //font.family: mainfont.name font.pointSize: 11 horizontalAlignment : Text.AlignHCenter verticalAlignment : Text.AlignVCenter bottomPadding: 3 rightPadding: 1 } onClicked: { pages.currentIndex = Main.LoginPage.Login } Keys.onReturnPressed: { clicked() event.accepted = true } } } } Image { id: branding anchors.bottom: parent.bottom anchors.bottomMargin: 23 anchors.horizontalCenter: parent.horizontalCenter source: "Assets/branding-white.png" } SMOD.GenericButton { id: switchLayoutButton anchors { top: parent.top topMargin: 5 left: parent.left leftMargin: 7 } implicitWidth: 35 implicitHeight: 28 label.font.pointSize: 9 label.font.capitalization: Font.AllUppercase //focusPolicy: Qt.TabFocus Accessible.description: i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "Button to change keyboard layout", "Switch layout") KeyNavigation.backtab: rebootButton KeyNavigation.tab: pages.currentIndex === Main.LoginPage.SelectUser ? listView : password KeyNavigation.down: pages.currentIndex === Main.LoginPage.SelectUser ? listView : password PW.KeyboardLayoutSwitcher { id: keyboardLayoutSwitcher anchors.fill: parent acceptedButtons: Qt.NoButton } text: keyboardLayoutSwitcher.layoutNames.shortName onClicked: keyboardLayoutSwitcher.keyboardLayout.switchToNextLayout() visible: keyboardLayoutSwitcher.hasMultipleKeyboardLayouts } QQC2.CheckBox { id: accessbutton anchors.bottom: parent.bottom anchors.left: parent.left anchors.bottomMargin: 34 anchors.leftMargin: 34 width: 38 height: 28 visible: pages.currentIndex != Main.LoginPage.LoginFailed indicator.width: 0 indicator.height: 0 background: Image { source: { if (accessbutton.pressed) return "Assets/access-button-active.png" if (accessbutton.hovered && accessbutton.focus) return "Assets/access-button-hover-focus.png" if (accessbutton.hovered && !accessbutton.focus) return "Assets/access-button-hover.png" if (!accessbutton.hovered && accessbutton.focus) return "Assets/access-button-focus.png" return "Assets/access-button.png" } } contentItem: Item { anchors.centerIn: parent width: 24 height: 24 Image { anchors.centerIn: parent width: 24 height: 24 source: "Assets/12213.png" smooth: false } } onClicked: { session.visible = !session.visible session.enabled = session.visible } Keys.onReturnPressed: { clicked() event.accepted = true } KeyNavigation.backtab: { if (pages.currentIndex === Main.LoginPage.SelectUser) { return listView } else if (switchuser.enabled) { return switchuser } return password } KeyNavigation.up: { if (pages.currentIndex === Main.LoginPage.Login && switchuser.enabled) { return switchuser } return accessbutton } KeyNavigation.tab: shutdownButton KeyNavigation.right: shutdownButton Item { anchors.bottom: parent.top anchors.left: parent.left //anchors.bottom: parent.bottom anchors.bottomMargin: -32 //anchors.leftMargin: 94 visible: pages.currentIndex != Main.LoginPage.LoginFailed enabled: visible width: 128 height: 64 // SMOD.ComboBox is a copy of SddmComponents.ComboBox // with the only change being the cursor shape // because there is no public API to change it SMOD.ComboBox { id: session width: parent.width height: 24 visible: false enabled: false font.pixelSize: 10 //font.family: mainfont.name model: sessionModel index: sessionModel.lastIndex borderColor: "#0c191c" color: "#eaeaec" menuColor : "#f4f4f8" textColor : "#323232" hoverColor : "#36a1d3" focusColor : "#36a1d3" } } } Item { anchors.bottom: parent.bottom anchors.right: parent.right anchors.bottomMargin: 34 anchors.rightMargin: 30 visible: pages.currentIndex != Main.LoginPage.LoginFailed width: 62 height: 28 QQC2.Button { id: shutdownButton anchors.bottom: parent.bottom anchors.left: parent.left width: 38 height: 28 background: Image { source: { if (shutdownButton.pressed) return "Assets/power-active.png" if (shutdownButton.hovered && shutdownButton.focus) return "Assets/power-hover-focus.png" if (shutdownButton.hovered && !shutdownButton.focus) return "Assets/power-hover.png" if (!shutdownButton.hovered && shutdownButton.focus) return "Assets/power-focus.png" return "Assets/power.png" } } contentItem: Item { anchors.centerIn: parent width: 24 height: 24 Image { anchors.centerIn: parent width: 24 height: 24 source: "Assets/power-glyph.png" smooth: false } } onClicked : sddm.powerOff() Keys.onReturnPressed: { clicked() event.accepted = true } KeyNavigation.backtab: accessbutton KeyNavigation.left: accessbutton KeyNavigation.tab: rebootButton KeyNavigation.right: rebootButton KeyNavigation.up: { if (pages.currentIndex === Main.LoginPage.Login && switchuser.enabled) { return switchuser } return shutdownButton } QQC2.ToolTip.visible: hovered QQC2.ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval QQC2.ToolTip.text: qsTr("Shut down") } QQC2.Button { id: rebootButton anchors.bottom: parent.bottom anchors.left: shutdownButton.right width: 20 height: 28 background: Image { source: { if (rebootButton.pressed) return "Assets/12301.png" if (rebootButton.hovered && rebootButton.focus) return "Assets/12298.png" if (rebootButton.hovered && !rebootButton.focus) return "Assets/12300.png" if (!rebootButton.hovered && rebootButton.focus) return "Assets/12299.png" return "Assets/12302.png" } } contentItem: Item { anchors.centerIn: parent width: 9 height: 6 Image { anchors.centerIn: parent width: 9 height: 6 source: "Assets/power-glyph-arrow.png" smooth: false } } onClicked: sddm.reboot() Keys.onReturnPressed: { clicked() event.accepted = true } KeyNavigation.backtab: shutdownButton KeyNavigation.left: shutdownButton KeyNavigation.up: { if (pages.currentIndex === Main.LoginPage.Login && switchuser.enabled) { return switchuser } return rebootButton } KeyNavigation.tab: switchLayoutButton } } }