1/* 2 * Copyright 2016 David Edmundson <davidedmundson@kde.org> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU Library General Public License as 6 * published by the Free Software Foundation; either version 2 or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details 13 * 14 * You should have received a copy of the GNU Library General Public 15 * License along with this program; if not, write to the 16 * Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 */ 19 20import QtQuick 2.8 21 22import QtQuick.Layouts 1.1 23import QtQuick.Controls 1.1 24import QtGraphicalEffects 1.0 25 26import org.kde.plasma.core 2.0 as PlasmaCore 27import org.kde.plasma.components 2.0 as PlasmaComponents 28import org.kde.plasma.extras 2.0 as PlasmaExtras 29 30import "components" 31 32PlasmaCore.ColorScope { 33 id: root 34 35 readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software 36 37 colorGroup: PlasmaCore.Theme.ComplementaryColorGroup 38 39 width: 1600 40 height: 900 41 42 property string notificationMessage 43 44 LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft 45 LayoutMirroring.childrenInherit: true 46 47 PlasmaCore.DataSource { 48 id: keystateSource 49 engine: "keystate" 50 connectedSources: "Caps Lock" 51 } 52 53 Image { 54 id: wallpaper 55 height: parent.height 56 width: parent.width 57 source: config.background || config.Background 58 asynchronous: true 59 cache: true 60 clip: true 61 } 62 63 MouseArea { 64 id: loginScreenRoot 65 anchors.fill: parent 66 67 property bool uiVisible: true 68 property bool blockUI: mainStack.depth > 1 || userListComponent.mainPasswordBox.text.length > 0 || inputPanel.keyboardActive || config.type != "image" 69 70 hoverEnabled: true 71 drag.filterChildren: true 72 onPressed: uiVisible = true; 73 onPositionChanged: uiVisible = true; 74 onUiVisibleChanged: { 75 if (blockUI) { 76 fadeoutTimer.running = false; 77 } else if (uiVisible) { 78 fadeoutTimer.restart(); 79 } 80 } 81 onBlockUIChanged: { 82 if (blockUI) { 83 fadeoutTimer.running = false; 84 uiVisible = true; 85 } else { 86 fadeoutTimer.restart(); 87 } 88 } 89 90 Keys.onPressed: { 91 uiVisible = true; 92 event.accepted = false; 93 } 94 95 //takes one full minute for the ui to disappear 96 Timer { 97 id: fadeoutTimer 98 running: true 99 interval: 60000 100 onTriggered: { 101 if (!loginScreenRoot.blockUI) { 102 loginScreenRoot.uiVisible = false; 103 } 104 } 105 } 106 107 StackView { 108 id: mainStack 109 anchors.centerIn: parent 110 height: root.height / 2 111 width: parent.width / 3 112 113 focus: true //StackView is an implicit focus scope, so we need to give this focus so the item inside will have it 114 115 Timer { 116 //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 117 //it is fixed in 6d5b36b28907b16280ff78995fef764bb0c573db which will be 0.14 118 //we need to call "window->activate()" *After* it's been shown. We can't control that in QML so we use a shoddy timer 119 //it's been this way for all Plasma 5.x without a huge problem 120 running: true 121 repeat: false 122 interval: 200 123 onTriggered: mainStack.forceActiveFocus() 124 } 125 126 initialItem: Login { 127 id: userListComponent 128 userListModel: userModel 129 loginScreenUiVisible: loginScreenRoot.uiVisible 130 userListCurrentIndex: userModel.lastIndex >= 0 ? userModel.lastIndex : 0 131 lastUserName: userModel.lastUser 132 133 showUserList: { 134 if ( !userListModel.hasOwnProperty("count") 135 || !userListModel.hasOwnProperty("disableAvatarsThreshold")) 136 return (userList.y + mainStack.y) > 0 137 138 if ( userListModel.count == 0 ) return false 139 140 return userListModel.count <= userListModel.disableAvatarsThreshold && (userList.y + mainStack.y) > 0 141 } 142 143 notificationMessage: { 144 var text = "" 145 if (keystateSource.data["Caps Lock"]["Locked"]) { 146 text += i18nd("plasma_lookandfeel_org.kde.lookandfeel","Caps Lock is on") 147 if (root.notificationMessage) { 148 text += " • " 149 } 150 } 151 text += root.notificationMessage 152 return text 153 } 154 155 actionItems: [ 156 ActionButton { 157 iconSource: "system-suspend" 158 text: i18ndc("plasma_lookandfeel_org.kde.lookandfeel","Suspend to RAM","Sleep") 159 onClicked: sddm.suspend() 160 enabled: sddm.canSuspend 161 visible: !inputPanel.keyboardActive 162 }, 163 ActionButton { 164 iconSource: "system-reboot" 165 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Restart") 166 onClicked: sddm.reboot() 167 enabled: sddm.canReboot 168 visible: !inputPanel.keyboardActive 169 }, 170 ActionButton { 171 iconSource: "system-shutdown" 172 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Shut Down") 173 onClicked: sddm.powerOff() 174 enabled: sddm.canPowerOff 175 visible: !inputPanel.keyboardActive 176 }, 177 ActionButton { 178 iconSource: Qt.resolvedUrl("assets/change_user.svg") 179 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Different User") 180 onClicked: mainStack.push(userPromptComponent) 181 enabled: true 182 visible: !userListComponent.showUsernamePrompt && !inputPanel.keyboardActive 183 }] 184 185 onLoginRequest: { 186 root.notificationMessage = "" 187 sddm.login(username, password, sessionButton.currentIndex) 188 } 189 } 190 191 Behavior on opacity { 192 OpacityAnimator { 193 duration: units.longDuration 194 } 195 } 196 } 197 198 Loader { 199 id: inputPanel 200 state: "hidden" 201 property bool keyboardActive: item ? item.active : false 202 onKeyboardActiveChanged: { 203 if (keyboardActive) { 204 state = "visible" 205 } else { 206 state = "hidden"; 207 } 208 } 209 source: "components/VirtualKeyboard.qml" 210 anchors { 211 left: parent.left 212 right: parent.right 213 } 214 215 function showHide() { 216 state = state == "hidden" ? "visible" : "hidden"; 217 } 218 219 states: [ 220 State { 221 name: "visible" 222 PropertyChanges { 223 target: mainStack 224 y: Math.min(0, root.height - inputPanel.height - userListComponent.visibleBoundary) 225 } 226 PropertyChanges { 227 target: inputPanel 228 y: root.height - inputPanel.height 229 opacity: 1 230 } 231 }, 232 State { 233 name: "hidden" 234 PropertyChanges { 235 target: mainStack 236 y: 0 237 } 238 PropertyChanges { 239 target: inputPanel 240 y: root.height - root.height/4 241 opacity: 0 242 } 243 } 244 ] 245 transitions: [ 246 Transition { 247 from: "hidden" 248 to: "visible" 249 SequentialAnimation { 250 ScriptAction { 251 script: { 252 inputPanel.item.activated = true; 253 Qt.inputMethod.show(); 254 } 255 } 256 ParallelAnimation { 257 NumberAnimation { 258 target: mainStack 259 property: "y" 260 duration: units.longDuration 261 easing.type: Easing.InOutQuad 262 } 263 NumberAnimation { 264 target: inputPanel 265 property: "y" 266 duration: units.longDuration 267 easing.type: Easing.OutQuad 268 } 269 OpacityAnimator { 270 target: inputPanel 271 duration: units.longDuration 272 easing.type: Easing.OutQuad 273 } 274 } 275 } 276 }, 277 Transition { 278 from: "visible" 279 to: "hidden" 280 SequentialAnimation { 281 ParallelAnimation { 282 NumberAnimation { 283 target: mainStack 284 property: "y" 285 duration: units.longDuration 286 easing.type: Easing.InOutQuad 287 } 288 NumberAnimation { 289 target: inputPanel 290 property: "y" 291 duration: units.longDuration 292 easing.type: Easing.InQuad 293 } 294 OpacityAnimator { 295 target: inputPanel 296 duration: units.longDuration 297 easing.type: Easing.InQuad 298 } 299 } 300 ScriptAction { 301 script: { 302 Qt.inputMethod.hide(); 303 } 304 } 305 } 306 } 307 ] 308 } 309 310 311 Component { 312 id: userPromptComponent 313 Login { 314 showUsernamePrompt: true 315 notificationMessage: root.notificationMessage 316 loginScreenUiVisible: loginScreenRoot.uiVisible 317 318 // using a model rather than a QObject list to avoid QTBUG-75900 319 userListModel: ListModel { 320 ListElement { 321 name: "" 322 iconSource: "" 323 } 324 Component.onCompleted: { 325 // as we can't bind inside ListElement 326 setProperty(0, "name", i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Type in Username and Password")); 327 } 328 } 329 330 onLoginRequest: { 331 root.notificationMessage = "" 332 sddm.login(username, password, sessionButton.currentIndex) 333 } 334 335 actionItems: [ 336 ActionButton { 337 iconSource: "system-suspend" 338 text: i18ndc("plasma_lookandfeel_org.kde.lookandfeel","Suspend to RAM","Sleep") 339 onClicked: sddm.suspend() 340 enabled: sddm.canSuspend 341 visible: !inputPanel.keyboardActive 342 }, 343 ActionButton { 344 iconSource: "system-reboot" 345 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Restart") 346 onClicked: sddm.reboot() 347 enabled: sddm.canReboot 348 visible: !inputPanel.keyboardActive 349 }, 350 ActionButton { 351 iconSource: "system-shutdown" 352 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Shut Down") 353 onClicked: sddm.powerOff() 354 enabled: sddm.canPowerOff 355 visible: !inputPanel.keyboardActive 356 }, 357 ActionButton { 358 iconSource: "go-previous" 359 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","List Users") 360 onClicked: mainStack.pop() 361 visible: !inputPanel.keyboardActive 362 } 363 ] 364 } 365 } 366 367 Rectangle { 368 id: blurBg 369 anchors.fill: parent 370 anchors.centerIn: parent 371 color: "#2e3440" 372 opacity: 0.1 373 z:-1 374 } 375 376 Rectangle { 377 id: formBg 378 width: mainStack.width 379 height: mainStack.height 380 x: root.width / 2 - width / 2 381 y: root.height / 2 - height / 3 382 radius: 7 383 color: "#2e3440" 384 opacity: 0.5 385 z:-1 386 } 387 388 // Rectangle { 389 // id: footerBg 390 // width: parent.width 391 // height: footer.height + 10 392 // anchors.left: parent.left 393 // anchors.bottom: parent.bottom 394 // radius: 7 395 // color: "#2e3440" 396 // opacity: 0.5 397 // z:-1 398 // } 399 ShaderEffectSource { 400 id: blurArea 401 sourceItem: wallpaper 402 width: blurBg.width 403 height: blurBg.height 404 anchors.centerIn: blurBg 405 sourceRect: Qt.rect(x,y,width,height) 406 visible: true 407 z:-2 408 } 409 410 GaussianBlur { 411 id: blur 412 height: blurBg.height 413 width: blurBg.width 414 source: blurArea 415 radius: 50 416 samples: 50 * 2 + 1 417 cached: true 418 anchors.centerIn: blurBg 419 visible: true 420 z:-2 421 } 422 423 //Footer 424 RowLayout { 425 id: footer 426 anchors { 427 bottom: parent.bottom 428 left: parent.left 429 margins: units.smallSpacing 430 } 431 432 Behavior on opacity { 433 OpacityAnimator { 434 duration: units.longDuration 435 } 436 } 437 438 PlasmaComponents.ToolButton { 439 text: i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "Button to show/hide virtual keyboard", "Virtual Keyboard") 440 iconName: inputPanel.keyboardActive ? "input-keyboard-virtual-on" : "input-keyboard-virtual-off" 441 onClicked: inputPanel.showHide() 442 visible: inputPanel.status == Loader.Ready 443 } 444 445 KeyboardButton { 446 } 447 448 SessionButton { 449 id: sessionButton 450 } 451 452 } 453 454 RowLayout { 455 id: footerRight 456 spacing: 10 457 458 anchors { 459 bottom: parent.bottom 460 right: parent.right 461 margins: 10 462 } 463 464 Behavior on opacity { 465 OpacityAnimator { 466 duration: units.longDuration 467 } 468 } 469 470 Battery {} 471 472 Clock { 473 id: clock 474 visible: true 475 } 476 } 477 } 478 479 Connections { 480 target: sddm 481 onLoginFailed: { 482 notificationMessage = i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Login Failed") 483 } 484 onLoginSucceeded: { 485 //note SDDM will kill the greeter at some random point after this 486 //there is no certainty any transition will finish, it depends on the time it 487 //takes to complete the init 488 mainStack.opacity = 0 489 footer.opacity = 0 490 footerRight.opacity = 0 491 } 492 } 493 494 onNotificationMessageChanged: { 495 if (notificationMessage) { 496 notificationResetTimer.start(); 497 } 498 } 499 500 Timer { 501 id: notificationResetTimer 502 interval: 3000 503 onTriggered: notificationMessage = "" 504 } 505} 506