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