diff --git a/lookandfeel/contents/lockscreen/LockScreenUi.qml b/lookandfeel/contents/lockscreen/LockScreenUi.qml index 0369036c1..da0454ddc 100644 --- a/lookandfeel/contents/lockscreen/LockScreenUi.qml +++ b/lookandfeel/contents/lockscreen/LockScreenUi.qml @@ -1,257 +1,264 @@ /******************************************************************** This file is part of the KDE project. Copyright (C) 2014 Aleix Pol Gonzalez 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 . *********************************************************************/ import QtQuick 2.5 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 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" PlasmaCore.ColorScope { colorGroup: PlasmaCore.Theme.ComplementaryColorGroup Connections { target: authenticator onFailed: { root.notification = i18nd("plasma_lookandfeel_org.kde.lookandfeel","Unlocking failed"); } onGraceLockedChanged: { if (!authenticator.graceLocked) { root.notification = ""; root.clearPassword(); } } onMessage: { root.notification = msg; } onError: { root.notification = err; } } SessionsModel { id: sessionsModel showNewSessionEntry: true } PlasmaCore.DataSource { id: keystateSource engine: "keystate" connectedSources: "Caps Lock" } Loader { id: changeSessionComponent active: false source: "ChangeSession.qml" visible: false } Item { id: lockScreenRoot x: parent.x y: parent.y width: parent.width height: parent.height Component.onCompleted: PropertyAnimation { id: launchAnimation; target: lockScreenRoot; property: "opacity"; from: 0; to: 1; duration: 1000 } states: [ State { name: "onOtherSession" // for slide out animation PropertyChanges { target: lockScreenRoot; y: lockScreenRoot.height } // we also change the opacity just to be sure it's not visible even on unexpected screen dimension changes with possible race conditions PropertyChanges { target: lockScreenRoot; opacity: 0 } } ] transitions: Transition { // we only animate switchting to another session, because kscreenlocker doesn't get notified when // coming from another session back and so we wouldn't know when to trigger the animation exactly from: "" to: "onOtherSession" PropertyAnimation { id: stateChangeAnimation; properties: "y"; duration: 300; easing: Easing.InQuad} PropertyAnimation { properties: "opacity"; duration: 300} onRunningChanged: { // after the animation has finished switch session: since we only animate the transition TO state "onOtherSession" // and not the other way around, we don't have to check the state we transitioned into if (/* lockScreenRoot.state == "onOtherSession" && */ !running) { mainStack.currentItem.switchSession() } } } Clock { anchors.bottom: parent.verticalCenter anchors.bottomMargin: units.gridUnit * 13 anchors.horizontalCenter: parent.horizontalCenter } ListModel { id: users Component.onCompleted: { users.append({name: kscreenlocker_userName, realName: kscreenlocker_userName, icon: kscreenlocker_userImage, }) } } StackView { id: mainStack anchors.fill: parent focus: true //StackView is an implicit focus scope, so we need to give this focus so the item inside will have it initialItem: MainBlock { id: mainBlock Stack.onStatusChanged: { // prepare for presenting again to the user if (Stack.status == Stack.Activating) { mainPasswordBox.remove(0, mainPasswordBox.length) mainPasswordBox.focus = true } } userListModel: users notificationMessage: { var text = "" if (keystateSource.data["Caps Lock"]["Locked"]) { text += i18nd("plasma_lookandfeel_org.kde.lookandfeel","Caps Lock is on") if (root.notification) { text += " • " } } text += root.notification return text } onLoginRequest: { root.notification = "" authenticator.tryUnlock(password) } actionItems: [ ActionButton { text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Switch User") iconSource: "system-switch-user" onClicked: mainStack.push(switchSessionPage) // the current session isn't listed in the model, hence a check for greater than zero, not one visible: (sessionsModel.count > 0 || sessionsModel.canStartNewSession) && sessionsModel.canSwitchUser } ] + + Loader { + Layout.fillWidth: true + Layout.preferredHeight: item ? item.implicitHeight : 0 + active: true // TODO configurable + source: "MediaControls.qml" + } } } Component { id: switchSessionPage SessionManagementScreen { property var switchSession: finalSwitchSession Stack.onStatusChanged: { if (Stack.status == Stack.Activating) { focus = true } } userListModel: sessionsModel // initiating animation of lockscreen for session switch function initSwitchSession() { lockScreenRoot.state = 'onOtherSession' } // initiating session switch and preparing lockscreen for possible return of user function finalSwitchSession() { mainStack.pop({immediate:true}) sessionsModel.switchUser(userListCurrentModelData.vtNumber) lockScreenRoot.state = '' } Keys.onLeftPressed: userList.decrementCurrentIndex() Keys.onRightPressed: userList.incrementCurrentIndex() Keys.onEnterPressed: initSwitchSession() Keys.onReturnPressed: initSwitchSession() Keys.onEscapePressed: mainStack.pop() PlasmaComponents.Button { Layout.fillWidth: true // the magic "-1" vtNumber indicates the "New Session" entry text: userListCurrentModelData.vtNumber === -1 ? i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Start New Session") : i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Switch Session") onClicked: initSwitchSession() } actionItems: [ ActionButton { iconSource: "go-previous" text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Back") onClicked: mainStack.pop() } ] } } Loader { active: root.viewVisible source: "LockOsd.qml" anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom } } RowLayout { id: footer anchors { bottom: parent.bottom left: parent.left right: parent.right margins: units.smallSpacing } KeyboardLayoutButton { } Item { Layout.fillWidth: true } Battery {} } } Component.onCompleted: { // version support checks if (root.interfaceVersion < 1) { // ksmserver of 5.4, with greeter of 5.5 root.viewVisible = true; } } } diff --git a/lookandfeel/contents/lockscreen/MediaControls.qml b/lookandfeel/contents/lockscreen/MediaControls.qml new file mode 100644 index 000000000..2265a0b55 --- /dev/null +++ b/lookandfeel/contents/lockscreen/MediaControls.qml @@ -0,0 +1,152 @@ +/******************************************************************** + This file is part of the KDE project. + +Copyright (C) 2016 Kai Uwe Broulik + +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 . +*********************************************************************/ + +import QtQuick 2.5 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.extras 2.0 as PlasmaExtras + +Item { + visible: mpris2Source.hasPlayer + implicitHeight: controlsRow.height + controlsRow.y + + RowLayout { + id: controlsRow + anchors.bottom: parent.bottom + y: units.smallSpacing // some distance to the password field + width: parent.width + height: units.gridUnit * 3 + spacing: 0 + + enabled: mpris2Source.canControl + + PlasmaCore.DataSource { + id: mpris2Source + + readonly property string source: "@multiplex" + readonly property var playerData: data[source] + + readonly property bool hasPlayer: sources.length > 1 && !!playerData + readonly property string identity: hasPlayer ? playerData.Identity : "" + readonly property bool playing: hasPlayer && playerData.PlaybackStatus === "Playing" + readonly property bool canControl: hasPlayer && playerData.CanControl + readonly property bool canGoBack: hasPlayer && playerData.CanGoPrevious + readonly property bool canGoNext: hasPlayer && playerData.CanGoNext + + readonly property var currentMetadata: hasPlayer ? playerData.Metadata : ({}) + + readonly property string track: { + var xesamTitle = currentMetadata["xesam:title"] + if (xesamTitle) { + return xesamTitle + } + // if no track title is given, print out the file name + var xesamUrl = currentMetadata["xesam:url"] ? currentMetadata["xesam:url"].toString() : "" + if (!xesamUrl) { + return "" + } + var lastSlashPos = xesamUrl.lastIndexOf('/') + if (lastSlashPos < 0) { + return "" + } + var lastUrlPart = xesamUrl.substring(lastSlashPos + 1) + return decodeURIComponent(lastUrlPart) + } + readonly property string artist: currentMetadata["xesam:artist"] || "" + readonly property string albumArt: currentMetadata["mpris:artUrl"] || "" + + engine: "mpris2" + connectedSources: [source] + + function startOperation(op) { + var service = serviceForSource(source) + var operation = service.operationDescription(op) + return service.startOperationCall(operation) + } + + function goPrevious() { + startOperation("Previous"); + } + function goNext() { + startOperation("Next"); + } + function playPause(source) { + startOperation("PlayPause"); + } + } + + Image { + id: albumArt + Layout.preferredWidth: height + Layout.fillHeight: true + asynchronous: true + source: mpris2Source.albumArt + sourceSize: Qt.size(width, height) + visible: status === Image.Loading || status === Image.Ready + } + + Item { // spacer + width: units.smallSpacing + height: 1 + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + PlasmaExtras.Heading { + Layout.fillWidth: true + level: 4 + wrapMode: Text.NoWrap + elide: Text.ElideRight + text: mpris2Source.track || i18n("No media playing") + } + + PlasmaExtras.Heading { + Layout.fillWidth: true + level: 5 + wrapMode: Text.NoWrap + elide: Text.ElideRight + // if no artist is given, show player name instead + text: mpris2Source.artist || mpris2Source.identity || "" + } + } + + PlasmaComponents.ToolButton { + enabled: mpris2Source.canGoBack + iconName: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward" + onClicked: mpris2Source.goPrevious() + } + + PlasmaComponents.ToolButton { + Layout.fillHeight: true + Layout.preferredWidth: height // make this button bigger + iconName: mpris2Source.playing ? "media-playback-pause" : "media-playback-start" + onClicked: mpris2Source.playPause() + } + + PlasmaComponents.ToolButton { + enabled: mpris2Source.canGoNext + iconName: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" + onClicked: mpris2Source.goNext() + } + } +}