forked from Shinonome/dots-hyprland
lock: add option to require password for poweroff/reboot (#2085)
This commit is contained in:
@@ -268,7 +268,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
property bool centerClock: true
|
property bool centerClock: true
|
||||||
property bool showLockedText: true
|
property bool showLockedText: true
|
||||||
property bool unlockKeyring: true
|
property JsonObject security: JsonObject {
|
||||||
|
property bool unlockKeyring: true
|
||||||
|
property bool requirePasswordToPower: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property JsonObject media: JsonObject {
|
property JsonObject media: JsonObject {
|
||||||
|
|||||||
@@ -10,18 +10,50 @@ import Quickshell.Hyprland
|
|||||||
|
|
||||||
Scope {
|
Scope {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
function unlockKeyring() {
|
||||||
|
Quickshell.execDetached({
|
||||||
|
environment: ({
|
||||||
|
UNLOCK_PASSWORD: root.currentText
|
||||||
|
}),
|
||||||
|
command: ["bash", "-c", Quickshell.shellPath("scripts/keyring/unlock.sh")]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// This stores all the information shared between the lock surfaces on each screen.
|
// This stores all the information shared between the lock surfaces on each screen.
|
||||||
// https://github.com/quickshell-mirror/quickshell-examples/tree/master/lockscreen
|
// https://github.com/quickshell-mirror/quickshell-examples/tree/master/lockscreen
|
||||||
LockContext {
|
LockContext {
|
||||||
id: lockContext
|
id: lockContext
|
||||||
|
|
||||||
onUnlocked: {
|
Connections {
|
||||||
|
target: GlobalStates
|
||||||
|
function onScreenLockedChanged() {
|
||||||
|
if (GlobalStates.screenLocked) lockContext.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnlocked: (targetAction) => {
|
||||||
|
// Perform the target action if it's not just unlocking
|
||||||
|
if (targetAction == LockContext.ActionEnum.Poweroff) {
|
||||||
|
Session.poweroff();
|
||||||
|
return;
|
||||||
|
} else if (targetAction == LockContext.ActionEnum.Reboot) {
|
||||||
|
Session.reboot();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock the keyring if configured to do so
|
||||||
|
if (Config.options.lock.security.unlockKeyring) root.unlockKeyring();
|
||||||
|
|
||||||
// Unlock the screen before exiting, or the compositor will display a
|
// Unlock the screen before exiting, or the compositor will display a
|
||||||
// fallback lock you can't interact with.
|
// fallback lock you can't interact with.
|
||||||
GlobalStates.screenLocked = false;
|
GlobalStates.screenLocked = false;
|
||||||
|
|
||||||
// Refocus last focused window on unlock (hack)
|
// Refocus last focused window on unlock (hack)
|
||||||
Quickshell.execDetached(["bash", "-c", `sleep 0.2; hyprctl --batch "dispatch togglespecialworkspace; dispatch togglespecialworkspace"`])
|
Quickshell.execDetached(["bash", "-c", `sleep 0.2; hyprctl --batch "dispatch togglespecialworkspace; dispatch togglespecialworkspace"`])
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
lockContext.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +122,6 @@ Scope {
|
|||||||
+ "decides to keyboard-unfocus the lock screen"
|
+ "decides to keyboard-unfocus the lock screen"
|
||||||
|
|
||||||
onPressed: {
|
onPressed: {
|
||||||
// console.log("I BEG FOR PLEAS REFOCUZ")
|
|
||||||
lockContext.shouldReFocus();
|
lockContext.shouldReFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ import Quickshell.Services.Pam
|
|||||||
|
|
||||||
Scope {
|
Scope {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
enum ActionEnum { Unlock, Poweroff, Reboot }
|
||||||
|
|
||||||
signal shouldReFocus()
|
signal shouldReFocus()
|
||||||
signal unlocked()
|
signal unlocked(targetAction: var)
|
||||||
signal failed()
|
signal failed()
|
||||||
|
|
||||||
// These properties are in the context and not individual lock surfaces
|
// These properties are in the context and not individual lock surfaces
|
||||||
@@ -15,16 +18,31 @@ Scope {
|
|||||||
property string currentText: ""
|
property string currentText: ""
|
||||||
property bool unlockInProgress: false
|
property bool unlockInProgress: false
|
||||||
property bool showFailure: false
|
property bool showFailure: false
|
||||||
|
property var targetAction: LockContext.ActionEnum.Unlock
|
||||||
|
|
||||||
|
function resetTargetAction() {
|
||||||
|
root.targetAction = LockContext.ActionEnum.Unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearText() {
|
||||||
|
root.currentText = "";
|
||||||
|
}
|
||||||
|
|
||||||
function resetClearTimer() {
|
function resetClearTimer() {
|
||||||
passwordClearTimer.restart();
|
passwordClearTimer.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
root.resetTargetAction();
|
||||||
|
root.clearText();
|
||||||
|
root.unlockInProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: passwordClearTimer
|
id: passwordClearTimer
|
||||||
interval: 10000
|
interval: 10000
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
root.currentText = "";
|
root.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,24 +73,14 @@ Scope {
|
|||||||
// pam_unix won't send any important messages so all we need is the completion status.
|
// pam_unix won't send any important messages so all we need is the completion status.
|
||||||
onCompleted: result => {
|
onCompleted: result => {
|
||||||
if (result == PamResult.Success) {
|
if (result == PamResult.Success) {
|
||||||
root.unlocked();
|
root.unlocked(root.targetAction);
|
||||||
if (Config.options.lock.unlockKeyring) root.unlockKeyring();
|
|
||||||
} else {
|
} else {
|
||||||
root.showFailure = true;
|
root.clearText();
|
||||||
|
root.unlockInProgress = false;
|
||||||
GlobalStates.screenUnlockFailed = true;
|
GlobalStates.screenUnlockFailed = true;
|
||||||
|
root.showFailure = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
root.currentText = "";
|
|
||||||
root.unlockInProgress = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function unlockKeyring() {
|
|
||||||
Quickshell.execDetached({
|
|
||||||
environment: ({
|
|
||||||
UNLOCK_PASSWORD: root.currentText
|
|
||||||
}),
|
|
||||||
command: ["bash", "-c", Quickshell.shellPath("scripts/keyring/unlock.sh")]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ MouseArea {
|
|||||||
required property LockContext context
|
required property LockContext context
|
||||||
property bool active: false
|
property bool active: false
|
||||||
property bool showInputField: active || context.currentText.length > 0
|
property bool showInputField: active || context.currentText.length > 0
|
||||||
|
readonly property bool requirePasswordToPower: Config.options.lock.security.requirePasswordToPower
|
||||||
|
|
||||||
// Force focus on entry
|
// Force focus on entry
|
||||||
function forceFieldFocus() {
|
function forceFieldFocus() {
|
||||||
@@ -73,7 +74,10 @@ MouseArea {
|
|||||||
// }
|
// }
|
||||||
// implicitHeight: 40
|
// implicitHeight: 40
|
||||||
// colBackground: Appearance.colors.colLayer2
|
// colBackground: Appearance.colors.colLayer2
|
||||||
// onClicked: context.unlocked()
|
// onClicked: {
|
||||||
|
// context.unlocked(LockContext.ActionEnum.Unlock);
|
||||||
|
// GlobalStates.screenLocked = false;
|
||||||
|
// }
|
||||||
// contentItem: StyledText {
|
// contentItem: StyledText {
|
||||||
// text: "[[ DEBUG BYPASS ]]"
|
// text: "[[ DEBUG BYPASS ]]"
|
||||||
// }
|
// }
|
||||||
@@ -136,7 +140,15 @@ MouseArea {
|
|||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
iconSize: 24
|
iconSize: 24
|
||||||
text: "arrow_right_alt"
|
text: {
|
||||||
|
if (root.context.targetAction === LockContext.ActionEnum.Unlock) {
|
||||||
|
return "arrow_right_alt";
|
||||||
|
} else if (root.context.targetAction === LockContext.ActionEnum.Poweroff) {
|
||||||
|
return "power_settings_new";
|
||||||
|
} else if (root.context.targetAction === LockContext.ActionEnum.Reboot) {
|
||||||
|
return "restart_alt";
|
||||||
|
}
|
||||||
|
}
|
||||||
color: confirmButton.enabled ? Appearance.colors.colOnPrimary : Appearance.colors.colSubtext
|
color: confirmButton.enabled ? Appearance.colors.colOnPrimary : Appearance.colors.colSubtext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,29 +167,14 @@ MouseArea {
|
|||||||
opacity: root.toolbarOpacity
|
opacity: root.toolbarOpacity
|
||||||
|
|
||||||
// Username
|
// Username
|
||||||
Row {
|
IconAndTextPair {
|
||||||
spacing: 6
|
|
||||||
Layout.leftMargin: 8
|
Layout.leftMargin: 8
|
||||||
Layout.fillHeight: true
|
icon: "account_circle"
|
||||||
|
text: SystemInfo.username
|
||||||
MaterialSymbol {
|
|
||||||
id: userIcon
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
fill: 1
|
|
||||||
text: "account_circle"
|
|
||||||
iconSize: Appearance.font.pixelSize.huge
|
|
||||||
color: Appearance.colors.colOnSurfaceVariant
|
|
||||||
}
|
|
||||||
StyledText {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: SystemInfo.username
|
|
||||||
color: Appearance.colors.colOnSurfaceVariant
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keyboard layout (Xkb)
|
// Keyboard layout (Xkb)
|
||||||
Loader {
|
Loader {
|
||||||
Layout.leftMargin: 8
|
|
||||||
Layout.rightMargin: 8
|
Layout.rightMargin: 8
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
@@ -230,77 +227,94 @@ MouseArea {
|
|||||||
scale: root.toolbarScale
|
scale: root.toolbarScale
|
||||||
opacity: root.toolbarOpacity
|
opacity: root.toolbarOpacity
|
||||||
|
|
||||||
Row {
|
IconAndTextPair {
|
||||||
visible: UPower.displayDevice.isLaptopBattery
|
visible: UPower.displayDevice.isLaptopBattery
|
||||||
spacing: 4
|
icon: Battery.isCharging ? "bolt" : "battery_android_full"
|
||||||
Layout.fillHeight: true
|
text: Math.round(Battery.percentage * 100)
|
||||||
Layout.leftMargin: 10
|
color: (Battery.isLow && !Battery.isCharging) ? Appearance.colors.colError : Appearance.colors.colOnSurfaceVariant
|
||||||
Layout.rightMargin: 10
|
|
||||||
|
|
||||||
MaterialSymbol {
|
|
||||||
id: boltIcon
|
|
||||||
anchors {
|
|
||||||
verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
fill: 1
|
|
||||||
text: Battery.isCharging ? "bolt" : "battery_android_full"
|
|
||||||
iconSize: Appearance.font.pixelSize.huge
|
|
||||||
animateChange: true
|
|
||||||
color: (Battery.isLow && !Battery.isCharging) ? Appearance.colors.colError : Appearance.colors.colOnSurfaceVariant
|
|
||||||
}
|
|
||||||
StyledText {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: Math.round(Battery.percentage * 100)
|
|
||||||
color: (Battery.isLow && !Battery.isCharging) ? Appearance.colors.colError : Appearance.colors.colOnSurfaceVariant
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolbarButton {
|
ActionToolbarIconButton {
|
||||||
id: sleepButton
|
id: sleepButton
|
||||||
implicitWidth: height
|
|
||||||
|
|
||||||
onClicked: Session.suspend()
|
onClicked: Session.suspend()
|
||||||
|
text: "dark_mode"
|
||||||
contentItem: MaterialSymbol {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
iconSize: 24
|
|
||||||
text: "dark_mode"
|
|
||||||
color: Appearance.colors.colOnSurfaceVariant
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolbarButton {
|
PasswordGuardedActionToolbarIconButton {
|
||||||
id: powerButton
|
id: powerButton
|
||||||
implicitWidth: height
|
text: "power_settings_new"
|
||||||
|
targetAction: LockContext.ActionEnum.Poweroff
|
||||||
onClicked: Session.poweroff()
|
|
||||||
|
|
||||||
contentItem: MaterialSymbol {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
iconSize: 24
|
|
||||||
text: "power_settings_new"
|
|
||||||
color: Appearance.colors.colOnSurfaceVariant
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolbarButton {
|
PasswordGuardedActionToolbarIconButton {
|
||||||
id: rebootButton
|
id: rebootButton
|
||||||
implicitWidth: height
|
text: "restart_alt"
|
||||||
|
targetAction: LockContext.ActionEnum.Reboot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onClicked: Session.reboot()
|
component PasswordGuardedActionToolbarIconButton: ActionToolbarIconButton {
|
||||||
|
id: guardedBtn
|
||||||
|
required property var targetAction
|
||||||
|
|
||||||
contentItem: MaterialSymbol {
|
toggled: root.context.targetAction === guardedBtn.targetAction
|
||||||
anchors.centerIn: parent
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
onClicked: {
|
||||||
verticalAlignment: Text.AlignVCenter
|
if (!root.requirePasswordToPower) {
|
||||||
iconSize: 24
|
root.context.unlocked(guardedBtn.targetAction);
|
||||||
text: "restart_alt"
|
return;
|
||||||
color: Appearance.colors.colOnSurfaceVariant
|
}
|
||||||
|
if (root.context.targetAction === guardedBtn.targetAction) {
|
||||||
|
root.context.resetTargetAction();
|
||||||
|
} else {
|
||||||
|
root.context.targetAction = guardedBtn.targetAction;
|
||||||
|
root.context.shouldReFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
component ActionToolbarIconButton: ToolbarButton {
|
||||||
|
id: iconBtn
|
||||||
|
implicitWidth: height
|
||||||
|
|
||||||
|
colBackgroundToggled: Appearance.colors.colSecondaryContainer
|
||||||
|
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
|
||||||
|
colRippleToggled: Appearance.colors.colSecondaryContainerActive
|
||||||
|
|
||||||
|
contentItem: MaterialSymbol {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
iconSize: 24
|
||||||
|
text: iconBtn.text
|
||||||
|
color: iconBtn.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnSurfaceVariant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component IconAndTextPair: Row {
|
||||||
|
id: pair
|
||||||
|
required property string icon
|
||||||
|
required property string text
|
||||||
|
property color color: Appearance.colors.colOnSurfaceVariant
|
||||||
|
|
||||||
|
spacing: 4
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.leftMargin: 10
|
||||||
|
Layout.rightMargin: 10
|
||||||
|
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
fill: 1
|
||||||
|
text: pair.icon
|
||||||
|
iconSize: Appearance.font.pixelSize.huge
|
||||||
|
animateChange: true
|
||||||
|
color: pair.color
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: pair.text
|
||||||
|
color: pair.color
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user