mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-10 15:38:31 -04:00
feat(keybinds): add keybind for PAM U2F on lock screen (#2930)
* feat(keybinds): add keybind for PAM U2F on lock screen * add: tooltip, settings json & error handling ---------
This commit is contained in:
@@ -271,3 +271,61 @@ function getConflictingBinds(keyCombo, currentAction, allBinds, modKey) {
|
|||||||
}
|
}
|
||||||
return conflicts;
|
return conflicts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function qtKeyFromName(name) {
|
||||||
|
var n = (name || "").toUpperCase();
|
||||||
|
if (n.length === 1 && n >= "A" && n <= "Z")
|
||||||
|
return Qt.Key_A + (n.charCodeAt(0) - 65);
|
||||||
|
if (n.length === 1 && n >= "0" && n <= "9")
|
||||||
|
return Qt.Key_0 + (n.charCodeAt(0) - 48);
|
||||||
|
if (n.length >= 2 && n[0] === "F") {
|
||||||
|
var f = parseInt(n.slice(1), 10);
|
||||||
|
if (f >= 1 && f <= 12)
|
||||||
|
return Qt.Key_F1 + (f - 1);
|
||||||
|
}
|
||||||
|
var named = {
|
||||||
|
"SPACE": Qt.Key_Space,
|
||||||
|
"TAB": Qt.Key_Tab,
|
||||||
|
"RETURN": Qt.Key_Return,
|
||||||
|
"ENTER": Qt.Key_Enter,
|
||||||
|
"BACKSPACE": Qt.Key_Backspace,
|
||||||
|
"DELETE": Qt.Key_Delete,
|
||||||
|
"HOME": Qt.Key_Home,
|
||||||
|
"END": Qt.Key_End,
|
||||||
|
"UP": Qt.Key_Up,
|
||||||
|
"DOWN": Qt.Key_Down,
|
||||||
|
"LEFT": Qt.Key_Left,
|
||||||
|
"RIGHT": Qt.Key_Right
|
||||||
|
};
|
||||||
|
return named[n] || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isModifierKey(qk) {
|
||||||
|
return qk === Qt.Key_Control || qk === Qt.Key_Shift || qk === Qt.Key_Alt || qk === Qt.Key_Meta
|
||||||
|
|| qk === Qt.Key_NumLock || qk === Qt.Key_CapsLock || qk === Qt.Key_ScrollLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
function eventMatchesCombo(event, combo) {
|
||||||
|
if (!combo)
|
||||||
|
return false;
|
||||||
|
var parts = combo.split("+");
|
||||||
|
var keyName = parts[parts.length - 1].trim().toUpperCase();
|
||||||
|
var wantsShift = false;
|
||||||
|
var hasCtrl = false;
|
||||||
|
for (var i = 0; i < parts.length - 1; i++) {
|
||||||
|
var mod = parts[i].trim().toLowerCase();
|
||||||
|
if (mod === "shift")
|
||||||
|
wantsShift = true;
|
||||||
|
else if (mod === "ctrl" || mod === "control")
|
||||||
|
hasCtrl = true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasCtrl && !(event.modifiers & Qt.ControlModifier))
|
||||||
|
return false;
|
||||||
|
if (event.modifiers & (Qt.AltModifier | Qt.MetaModifier))
|
||||||
|
return false;
|
||||||
|
if (((event.modifiers & Qt.ShiftModifier) !== 0) !== wantsShift)
|
||||||
|
return false;
|
||||||
|
return event.key === qtKeyFromName(keyName);
|
||||||
|
}
|
||||||
|
|||||||
@@ -922,6 +922,8 @@ Singleton {
|
|||||||
property bool lockPamInlineU2f: false
|
property bool lockPamInlineU2f: false
|
||||||
property bool lockPamExternallyManaged: false
|
property bool lockPamExternallyManaged: false
|
||||||
property string lockU2fPamPath: ""
|
property string lockU2fPamPath: ""
|
||||||
|
property string lockScreenSecurityKeyShortcut: "Ctrl+Q"
|
||||||
|
property bool lockScreenSecurityKeyShortcutEnabled: false
|
||||||
property bool greeterPamExternallyManaged: false
|
property bool greeterPamExternallyManaged: false
|
||||||
property string lockScreenInactiveColor: "#000000"
|
property string lockScreenInactiveColor: "#000000"
|
||||||
property int lockScreenNotificationMode: 0
|
property int lockScreenNotificationMode: 0
|
||||||
|
|||||||
@@ -473,6 +473,8 @@ var SPEC = {
|
|||||||
enableU2f: { def: false, onChange: "scheduleAuthApply" },
|
enableU2f: { def: false, onChange: "scheduleAuthApply" },
|
||||||
u2fMode: { def: "or" },
|
u2fMode: { def: "or" },
|
||||||
lockPamPath: { def: "" },
|
lockPamPath: { def: "" },
|
||||||
|
lockScreenSecurityKeyShortcut: { def: "Ctrl+Q" },
|
||||||
|
lockScreenSecurityKeyShortcutEnabled: { def: false },
|
||||||
lockPamInlineFprint: { def: false },
|
lockPamInlineFprint: { def: false },
|
||||||
lockPamInlineU2f: { def: false },
|
lockPamInlineU2f: { def: false },
|
||||||
lockPamExternallyManaged: { def: false },
|
lockPamExternallyManaged: { def: false },
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
import qs.DankCommon.Session
|
import qs.DankCommon.Session
|
||||||
import "../../DankCommon/Common/LayoutCodes.js" as LayoutCodes
|
import "../../DankCommon/Common/LayoutCodes.js" as LayoutCodes
|
||||||
|
import "../../Common/KeyUtils.js" as KeyUtils
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -98,6 +99,18 @@ Item {
|
|||||||
return !demoMode && pam && pam.u2f && pam.u2f.available && SettingsData.enableU2f && SettingsData.u2fMode === "or" && !pam.passwd.active && !pam.u2f.active && !pam.u2fPending && !root.unlocking;
|
return !demoMode && pam && pam.u2f && pam.u2f.available && SettingsData.enableU2f && SettingsData.u2fMode === "or" && !pam.passwd.active && !pam.u2f.active && !pam.u2fPending && !root.unlocking;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function triggerSecurityKeyUnlock() {
|
||||||
|
if (!canStartSecurityKeyUnlock())
|
||||||
|
return;
|
||||||
|
passwordField.clear();
|
||||||
|
pam.u2f.startForAlternativeAuth();
|
||||||
|
}
|
||||||
|
|
||||||
|
function securityKeyShortcutMatches(event) {
|
||||||
|
return SettingsData.lockScreenSecurityKeyShortcutEnabled
|
||||||
|
&& KeyUtils.eventMatchesCombo(event, SettingsData.lockScreenSecurityKeyShortcut);
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
WeatherService.addRef();
|
WeatherService.addRef();
|
||||||
UserInfoService.getUserInfo();
|
UserInfoService.getUserInfo();
|
||||||
@@ -962,6 +975,12 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((event.modifiers & Qt.ControlModifier) && !(event.modifiers & (Qt.AltModifier | Qt.MetaModifier))) {
|
if ((event.modifiers & Qt.ControlModifier) && !(event.modifiers & (Qt.AltModifier | Qt.MetaModifier))) {
|
||||||
|
if (securityKeyShortcutMatches(event) && canStartSecurityKeyUnlock()) {
|
||||||
|
triggerSecurityKeyUnlock();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Qt.Key_A:
|
case Qt.Key_A:
|
||||||
cursorPosition = 0;
|
cursorPosition = 0;
|
||||||
@@ -1238,10 +1257,8 @@ Item {
|
|||||||
buttonSize: 32
|
buttonSize: 32
|
||||||
visible: root.canStartSecurityKeyUnlock()
|
visible: root.canStartSecurityKeyUnlock()
|
||||||
enabled: visible
|
enabled: visible
|
||||||
onClicked: {
|
tooltipText: SettingsData.lockScreenSecurityKeyShortcutEnabled ? I18n.tr("Security key (%1)", "lock screen security key button tooltip with shortcut").arg(SettingsData.lockScreenSecurityKeyShortcut) : I18n.tr("Security key", "lock screen security key button tooltip")
|
||||||
passwordField.clear();
|
onClicked: root.triggerSecurityKeyUnlock()
|
||||||
pam.u2f.startForAlternativeAuth();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
id: virtualKeyboardButton
|
id: virtualKeyboardButton
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import qs.Modals.FileBrowser
|
|||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
import qs.Modules.Settings.Widgets
|
import qs.Modules.Settings.Widgets
|
||||||
|
import "../../Common/KeyUtils.js" as KeyUtils
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -518,6 +519,117 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
settingKey: "lockScreenSecurityKeyShortcutEnabled"
|
||||||
|
tags: ["lock", "screen", "u2f", "yubikey", "security", "key", "shortcut", "keybind", "authentication"]
|
||||||
|
text: I18n.tr("Security key shortcut", "lock screen security key shortcut toggle")
|
||||||
|
description: I18n.tr("Keyboard shortcut to start security key unlock", "lock screen security key shortcut setting")
|
||||||
|
checked: SettingsData.lockScreenSecurityKeyShortcutEnabled
|
||||||
|
visible: SettingsData.enableU2f && SettingsData.u2fMode === "or" && !root.lockU2fControlledByPrimary
|
||||||
|
onToggled: checked => SettingsData.set("lockScreenSecurityKeyShortcutEnabled", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
visible: SettingsData.lockScreenSecurityKeyShortcutEnabled && SettingsData.enableU2f && SettingsData.u2fMode === "or" && !root.lockU2fControlledByPrimary
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - securityKeyCapture.width - parent.spacing
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Key combination", "lock screen security key shortcut key combination setting")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: securityKeyCapture.captureError !== "" ? securityKeyCapture.captureError : I18n.tr("Press Ctrl+key to set. Esc cancels.", "lock screen security key shortcut key combination capture hint")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: securityKeyCapture.captureError !== "" ? Theme.warning : Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
id: securityKeyCapture
|
||||||
|
width: 200
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
focus: capturing
|
||||||
|
text: capturing ? I18n.tr("Press key...", "lock screen security key shortcut key combination capture prompt") : SettingsData.lockScreenSecurityKeyShortcut
|
||||||
|
backgroundColor: capturing ? Theme.primaryContainer : Theme.surfaceContainer
|
||||||
|
textColor: Theme.surfaceText
|
||||||
|
|
||||||
|
property bool capturing: false
|
||||||
|
property string captureError: ""
|
||||||
|
readonly property var reservedKeys: ["A", "E", "B", "F", "U", "K", "W", "H", "D"]
|
||||||
|
|
||||||
|
function startCapture() {
|
||||||
|
captureError = "";
|
||||||
|
capturing = true;
|
||||||
|
securityKeyCapture.forceActiveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopCapture() {
|
||||||
|
capturing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (capturing)
|
||||||
|
stopCapture();
|
||||||
|
else
|
||||||
|
startCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onPressed: event => {
|
||||||
|
if (!securityKeyCapture.capturing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (KeyUtils.isModifierKey(event.key))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
securityKeyCapture.stopCapture();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mods = KeyUtils.modsFromEvent(event.modifiers);
|
||||||
|
const hasCtrl = mods.includes("Ctrl");
|
||||||
|
const hasAlt = mods.includes("Alt") || mods.includes("Super");
|
||||||
|
if (!hasCtrl || hasAlt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const hasShift = mods.includes("Shift");
|
||||||
|
const key = KeyUtils.xkbKeyFromQtKey(event.key, !!(event.modifiers & Qt.KeypadModifier), hasShift);
|
||||||
|
if (!key)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!KeyUtils.qtKeyFromName(key))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!hasShift && securityKeyCapture.reservedKeys.indexOf(key.toUpperCase()) !== -1) {
|
||||||
|
securityKeyCapture.captureError = I18n.tr("Ctrl+%1 is used for password editing", "lock screen security key shortcut reserved key warning").arg(key.toUpperCase());
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsData.set("lockScreenSecurityKeyShortcut", KeyUtils.formatToken(mods, key));
|
||||||
|
securityKeyCapture.captureError = "";
|
||||||
|
securityKeyCapture.stopCapture();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsDropdownRow {
|
SettingsDropdownRow {
|
||||||
settingKey: "lockU2fPamPath"
|
settingKey: "lockU2fPamPath"
|
||||||
tags: ["lock", "screen", "pam", "u2f", "security", "key", "source", "service"]
|
tags: ["lock", "screen", "pam", "u2f", "security", "key", "source", "service"]
|
||||||
|
|||||||
@@ -5453,6 +5453,29 @@
|
|||||||
"yubikey"
|
"yubikey"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"section": "lockScreenSecurityKeyShortcutEnabled",
|
||||||
|
"label": "Security key shortcut",
|
||||||
|
"tabIndex": 11,
|
||||||
|
"category": "Lock Screen",
|
||||||
|
"keywords": [
|
||||||
|
"authentication",
|
||||||
|
"key",
|
||||||
|
"keybind",
|
||||||
|
"keyboard",
|
||||||
|
"lock",
|
||||||
|
"login",
|
||||||
|
"password",
|
||||||
|
"screen",
|
||||||
|
"security",
|
||||||
|
"shortcut",
|
||||||
|
"start",
|
||||||
|
"u2f",
|
||||||
|
"unlock",
|
||||||
|
"yubikey"
|
||||||
|
],
|
||||||
|
"description": "Keyboard shortcut to start security key unlock"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"section": "lockScreenShowMediaPlayer",
|
"section": "lockScreenShowMediaPlayer",
|
||||||
"label": "Show Media Player",
|
"label": "Show Media Player",
|
||||||
|
|||||||
Reference in New Issue
Block a user