From 195b777b8458ffdccbfd3d8dc3250d6492a4a563 Mon Sep 17 00:00:00 2001 From: fy59 Date: Fri, 9 Oct 2026 07:51:36 +0200 Subject: [PATCH] feat(quickshell): use native Polkit agent --- .assets/pkglist-pacman.txt | 1 - .../quickshell-polkit-demo-empty.png | Bin 0 -> 63373 bytes docs/installation.md | 6 +- .../.config/quickshell/labfy-sway/Bar.qml | 31 ++- .../labfy-sway/PolkitVisualFixture.qml | 143 +++++++++++ .../labfy-sway/controlcenter/LOCATION_TIME.md | 9 +- .../labfy-sway/overview/WorkspaceOverview.qml | 3 +- .../labfy-sway/polkit/PolkitCard.qml | 235 ++++++++++++++++++ .../labfy-sway/polkit/PolkitDialog.qml | 48 ++++ .../quickshell/labfy-sway/polkit/README.md | 152 +++++++++++ .../.config/quickshell/labfy-sway/shell.qml | 89 ++++++- sway/.config/sway/autostart | 5 +- 12 files changed, 708 insertions(+), 14 deletions(-) create mode 100644 docs/assets/screenshots/quickshell-polkit-demo-empty.png create mode 100644 quickshell/.config/quickshell/labfy-sway/PolkitVisualFixture.qml create mode 100644 quickshell/.config/quickshell/labfy-sway/polkit/PolkitCard.qml create mode 100644 quickshell/.config/quickshell/labfy-sway/polkit/PolkitDialog.qml create mode 100644 quickshell/.config/quickshell/labfy-sway/polkit/README.md diff --git a/.assets/pkglist-pacman.txt b/.assets/pkglist-pacman.txt index 9a5179734..72f2f0414 100644 --- a/.assets/pkglist-pacman.txt +++ b/.assets/pkglist-pacman.txt @@ -133,7 +133,6 @@ pipewire-alsa pipewire-jack pipewire-libcamera pipewire-pulse -polkit-gnome podman powertop protonmail-bridge diff --git a/docs/assets/screenshots/quickshell-polkit-demo-empty.png b/docs/assets/screenshots/quickshell-polkit-demo-empty.png new file mode 100644 index 0000000000000000000000000000000000000000..e813b903875dd6b1761fa01c72d08a998d9b55c5 GIT binary patch literal 63373 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 literal 0 HcmV?d00001 diff --git a/docs/installation.md b/docs/installation.md index d937543cc..21f1f6d54 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -87,9 +87,9 @@ Le package SwayFX 0.6 est un paquet foreign/local à reconstruire depuis sa source vérifiée ; `scenefx` et `quickshell` sont présents dans la liste Pacman officielle de cette machine. Voir les inventaires `.assets/`. -`polkit-gnome` fournit l'agent graphique requis pour authentifier les -changements de fuseau effectués par `timedatectl` depuis QuickShell. Le package -`sway` le lance une seule fois à l'ouverture de session via `autostart` ; +QuickShell fournit l'agent graphique Polkit requis pour authentifier les +changements de fuseau effectués par `timedatectl`. Le service utilisateur +`quickshell-labfy-sway.service` le lance une seule fois par session ; aucune règle `sudoers` propre au fuseau n'est nécessaire. Le backend batterie exige TLP et `/usr/local/sbin/batlimit-set`, déployé séparément avec les droits root selon `bin/root/README.md`. diff --git a/quickshell/.config/quickshell/labfy-sway/Bar.qml b/quickshell/.config/quickshell/labfy-sway/Bar.qml index 6ccdf3399..654b0d641 100644 --- a/quickshell/.config/quickshell/labfy-sway/Bar.qml +++ b/quickshell/.config/quickshell/labfy-sway/Bar.qml @@ -25,6 +25,7 @@ PanelWindow { required property bool startupHost required property bool resizeMode required property var applicationCoordinator + required property bool authenticationActive required property var keepAwakeController screen: modelData @@ -64,6 +65,7 @@ PanelWindow { (Quickshell.env("HOME") + "/.config")) + "/quickshell/labfy-sway/applications/backend.py" function closeApplications() { applicationCoordinator.applicationsOutput = ""; } function toggleApplications() { + if (authenticationActive) return false; if (applicationCoordinator.applicationsOutput !== screen.name) applicationsError = ""; applicationCoordinator.toggleApplications(screen.name); return true; @@ -100,6 +102,19 @@ PanelWindow { } Connections { target: bar.applicationCoordinator + function onAuthenticationOpening() { + bar.closeApplications(); + // INVARIANT: no Overview screenshot may contain the auth surface. + overviewCapture.running = false; + overviewCaptureRefresh.stop(); + if (overviewLoader.item) overviewLoader.item.skipDismissCaptures = true; + bar.closeOverview(); + bar.closeClipboard(); + controlCenter.visible = false; + dateCenter.requestClose(); + windowStrip.closeMenu(); + rightStatusArea.closeRemovableMedia(); + } function onApplicationsOpening(output) { bar.closeOverview(); bar.closeClipboard(); @@ -145,6 +160,7 @@ PanelWindow { } // CONTRACT: clic et IPC suivent la même exclusion que les panneaux de barre. function toggleClipboard() { + if (authenticationActive) return false; if (clipboardLoader.active) { closeClipboard(); } else { @@ -166,6 +182,7 @@ PanelWindow { if (overviewLoader.item) overviewLoader.item.dismiss(); } function toggleOverview() { + if (authenticationActive) return false; if (overviewRequested || overviewLoader.active) { closeOverview(); return true; } closeApplications(); closeClipboard(); @@ -201,7 +218,7 @@ PanelWindow { id: overviewCapture command: ["python3", "-B", bar.overviewBackend, "capture", "--output", bar.screen.name] onExited: (code, status) => { - if (bar.overviewRequested) overviewLoader.active = true; + if (bar.overviewRequested && !bar.authenticationActive) overviewLoader.active = true; else if (code !== 0) console.warn("Capture du workspace impossible"); } } @@ -217,7 +234,7 @@ PanelWindow { id: overviewCaptureRefresh interval: 350; repeat: false onTriggered: { - if (!overviewCapture.running && !overviewLoader.active && !clipboardLoader.active + if (!bar.authenticationActive && !overviewCapture.running && !overviewLoader.active && !clipboardLoader.active && !applicationsLoader.active && !controlCenter.visible && !dateCenter.visible) overviewCapture.running = true; @@ -228,7 +245,7 @@ PanelWindow { Timer { interval: 15000; repeat: true; running: true; triggeredOnStart: true onTriggered: { - if (!overviewCapture.running && !overviewLoader.active && !clipboardLoader.active + if (!bar.authenticationActive && !overviewCapture.running && !overviewLoader.active && !clipboardLoader.active && !applicationsLoader.active && !controlCenter.visible && !dateCenter.visible) overviewCapture.running = true; @@ -248,6 +265,7 @@ PanelWindow { IpcHandler { target: "appearanceUi-" + bar.screen.name function openPage(page: string): bool { + if (bar.authenticationActive) return false; if (controlCenter.pages[page] === undefined) return false; bar.closeApplications(); bar.closeClipboard(); @@ -258,8 +276,12 @@ PanelWindow { return true; } function close(): bool { controlCenter.visible = false; return true; } - function openDateCenter(): bool { bar.toggleDateCenter(); return true; } + function openDateCenter(): bool { + if (bar.authenticationActive) return false; + bar.toggleDateCenter(); return true; + } function openWindowMenu(): bool { + if (bar.authenticationActive) return false; bar.closeApplications(); bar.closeClipboard(); controlCenter.visible = false; @@ -296,6 +318,7 @@ PanelWindow { // CONTRACT: toute la capsule centrale partage le même popup et la même exclusion XOR. function toggleDateCenter() { + if (authenticationActive) return; const opening = !dateCenter.visible && !dateCenter.opening; closeApplications(); closeOverview(); diff --git a/quickshell/.config/quickshell/labfy-sway/PolkitVisualFixture.qml b/quickshell/.config/quickshell/labfy-sway/PolkitVisualFixture.qml new file mode 100644 index 000000000..b90eb0f34 --- /dev/null +++ b/quickshell/.config/quickshell/labfy-sway/PolkitVisualFixture.qml @@ -0,0 +1,143 @@ +//@ pragma UseQApplication +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Services.Polkit +import "polkit" + +ShellRoot { + id: root + property var shownFlow: fakeFlow + QtObject { id: account1; property string displayName: "Compte de test"; property string string: "test" + property int id: 1000; property bool isGroup: false } + QtObject { id: account2; property string displayName: "Autre compte"; property string string: "other" + property int id: 1001; property bool isGroup: false } + QtObject { + id: fakeFlow + property string message: "Autoriser une opération de démonstration sans données personnelles ?" + property string iconName: "" + property string actionId: "org.example.demo" + property var identities: [account1] + property var selectedIdentity: account1 + property bool isResponseRequired: true + property string inputPrompt: "Réponse de démonstration :" + property bool responseVisible: false + property string supplementaryMessage: "" + property bool supplementaryIsError: false + property bool isCompleted: false + property bool isCancelled: false + property bool failed: false + property int submissions: 0 + signal authenticationFailed() + function submit(value) { submissions++; isResponseRequired = false; } + function cancelAuthenticationRequest() { isCancelled = true; } + } + QtObject { + id: secondFlow + property string message: "Deuxième demande de test" + property string iconName: "" + property string actionId: "org.example.second" + property var identities: [account1] + property var selectedIdentity: account1 + property bool isResponseRequired: true + property string inputPrompt: "Réponse visible de test :" + property bool responseVisible: true + property string supplementaryMessage: "" + property bool supplementaryIsError: false + property bool isCompleted: false + property bool isCancelled: false + property bool failed: false + property int submissions: 0 + signal authenticationFailed() + function submit(value) { submissions++; isResponseRequired = false; } + function cancelAuthenticationRequest() { isCancelled = true; } + } + IpcHandler { + target: "polkitFixture" + function state(): string { + return JSON.stringify({waiting: fakeFlow.isResponseRequired, cancelled: fakeFlow.isCancelled, + submissions: fakeFlow.submissions, screen: Quickshell.screens.length, + canSubmit: dialogLoader.item ? dialogLoader.item.card.canSubmit : false}); + } + function scenario(name: string): bool { + if (name === "long") fakeFlow.message = "Cette opération de démonstration comporte un message volontairement long afin de contrôler le retour à la ligne et la hauteur du dialogue sur un écran compact. Aucune autorisation réelle ne sera demandée."; + else if (name === "multi") fakeFlow.identities = [account1, account2]; + else if (name === "identitySamePrompt") { + fakeFlow.identities = [account1, account2]; + dialogLoader.item.card.selectIdentity(account2); + } + else if (name === "visible") fakeFlow.responseVisible = true; + else if (name === "error") { fakeFlow.supplementaryMessage = "Échec de test"; fakeFlow.supplementaryIsError = true; fakeFlow.failed = true; fakeFlow.authenticationFailed(); fakeFlow.isResponseRequired = true; } + else if (name === "next") { fakeFlow.isResponseRequired = false; fakeFlow.isResponseRequired = true; } + else if (name === "cancel") fakeFlow.cancelAuthenticationRequest(); + else if (name === "reset") { + root.shownFlow = fakeFlow; + fakeFlow.message = "Autoriser une opération de démonstration sans données personnelles ?"; + fakeFlow.identities = [account1]; fakeFlow.selectedIdentity = account1; + fakeFlow.inputPrompt = "Réponse de démonstration :"; + fakeFlow.responseVisible = false; fakeFlow.supplementaryMessage = ""; + fakeFlow.isCancelled = false; fakeFlow.isCompleted = false; fakeFlow.failed = false; + fakeFlow.isResponseRequired = true; fakeFlow.submissions = 0; + if (dialogLoader.item) dialogLoader.item.card.clearResponse(); + } + else return false; + return true; + } + function checks(): string { + const card = dialogLoader.item.card; + function findField(item) { + if (item.objectName === "polkitResponse") return item; + for (const child of item.children || []) { + const found = findField(child); + if (found) return found; + } + return null; + } + const field = findField(card); + if (!field) return JSON.stringify({error: "champ absent"}); + fakeFlow.isCancelled = false; fakeFlow.isCompleted = false; + fakeFlow.isResponseRequired = true; card.busy = false; + secondFlow.submissions = 0; + field.clear(); + const emptySecretBlocked = card.canEdit && !card.canSubmit + && !card.submitResponse() && fakeFlow.submissions === 0; + field.text = "réponse fictive"; + const first = card.submitResponse(); + const second = card.submitResponse(); + const oneSubmit = first && !second && fakeFlow.submissions === 1 && field.text === ""; + fakeFlow.supplementaryMessage = "Échec de test"; + fakeFlow.supplementaryIsError = true; + fakeFlow.authenticationFailed(); fakeFlow.isResponseRequired = true; + const retry = card.canEdit && !card.canSubmit && field.text === ""; + field.text = "ancienne réponse"; + fakeFlow.inputPrompt = "Nouvelle question :"; + const successive = field.text === ""; + fakeFlow.identities = [account1, account2]; + fakeFlow.isResponseRequired = true; card.busy = false; + field.text = "à effacer au changement de compte"; + card.selectIdentity(account2); + fakeFlow.inputPrompt = "Nouvelle identité :"; + const identity = fakeFlow.selectedIdentity === account2 && field.text === "" + && card.canEdit && !card.canSubmit; + root.shownFlow = null; + const disappeared = field.text === "" && !card.canSubmit; + root.shownFlow = secondFlow; + secondFlow.isCancelled = false; secondFlow.isResponseRequired = true; + field.text = "réponse fictive 2"; + const nextFlow = card.submitResponse() && secondFlow.submissions === 1 + && fakeFlow.submissions === 1 && field.text === ""; + root.shownFlow = fakeFlow; + fakeFlow.isCancelled = false; fakeFlow.isResponseRequired = true; card.busy = false; + field.text = "à effacer"; + card.cancelRequest(); + const cancelled = fakeFlow.isCancelled && field.text === ""; + return JSON.stringify({emptySecretBlocked, oneSubmit, retry, + successive, identity, disappeared, nextFlow, cancelled}); + } + } + LazyLoader { + id: dialogLoader + active: Quickshell.screens.length > 0 + PolkitDialog { requestFlow: root.shownFlow; hostScreen: Quickshell.screens[0] } + } +} diff --git a/quickshell/.config/quickshell/labfy-sway/controlcenter/LOCATION_TIME.md b/quickshell/.config/quickshell/labfy-sway/controlcenter/LOCATION_TIME.md index 27b921b28..a60043755 100644 --- a/quickshell/.config/quickshell/labfy-sway/controlcenter/LOCATION_TIME.md +++ b/quickshell/.config/quickshell/labfy-sway/controlcenter/LOCATION_TIME.md @@ -10,7 +10,14 @@ Une sélection propose **Appliquer les deux**, **Fuseau uniquement** et **Positi ## Privilèges et transaction -`timedatectl set-timezone` est appelé directement, avec un tableau d'arguments, via l'authentification Polkit standard de la session. L'agent graphique `polkit-gnome` est lancé au démarrage Sway. QuickShell ne reçoit jamais le mot de passe, ne dispose d'aucune permission root permanente et ne modifie jamais `/etc/localtime` directement. Si l'authentification est annulée ou indisponible, l'opération échoue sans écrire la position solaire. +`timedatectl set-timezone` est appelé directement, avec un tableau d'arguments, +via l'authentification Polkit standard de la session. L'agent graphique natif +QuickShell présente la demande locale : sa fenêtre reçoit temporairement la +réponse masquée, la transmet à `AuthFlow.submit()` et efface le champ. Elle ne +l'écrit ni dans un argument, ni dans un fichier, ni dans un journal. QuickShell +ne dispose d'aucune permission root permanente et ne modifie jamais +`/etc/localtime` directement. Si l'authentification est annulée ou +indisponible, l'opération échoue sans écrire la position solaire. L'application combinée valide d'abord les entrées, mémorise l'état précédent, change le fuseau, écrit la position locale, réconcilie Night Light en Auto, puis vérifie le fuseau. Un échec de fuseau laisse la position intacte. Un échec ultérieur restaure la préférence et tente de restaurer le fuseau et le service. Un échec de rollback est affiché comme erreur explicite. diff --git a/quickshell/.config/quickshell/labfy-sway/overview/WorkspaceOverview.qml b/quickshell/.config/quickshell/labfy-sway/overview/WorkspaceOverview.qml index 28141c7b4..1600956a5 100644 --- a/quickshell/.config/quickshell/labfy-sway/overview/WorkspaceOverview.qml +++ b/quickshell/.config/quickshell/labfy-sway/overview/WorkspaceOverview.qml @@ -20,6 +20,7 @@ PanelWindow { property bool refreshPending: false property string actionName: "" property var dirtyCaptures: [] + property bool skipDismissCaptures: false property var pendingWindowNumbers: [] property var activeWindowNumbers: [] property int windowCaptureCursor: 0 @@ -49,7 +50,7 @@ PanelWindow { visible = false; // WHY: grim inclurait cette surface overlay. Après sa disparition, // seules les sorties visibles dont l'arbre a changé sont recapturées. - for (const card of dirtyCaptures) { + for (const card of skipDismissCaptures ? [] : dirtyCaptures) { Quickshell.execDetached(["python3", "-B", backend, "capture", "--output", card.output, "--number", String(card.number), "--workspace-id", String(card.workspaceId), "--revision", card.revision, diff --git a/quickshell/.config/quickshell/labfy-sway/polkit/PolkitCard.qml b/quickshell/.config/quickshell/labfy-sway/polkit/PolkitCard.qml new file mode 100644 index 000000000..9eba37a2f --- /dev/null +++ b/quickshell/.config/quickshell/labfy-sway/polkit/PolkitCard.qml @@ -0,0 +1,235 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import "../theme" + +Item { + id: card + required property var flow + property bool busy: false + property bool awaitingIdentityPrompt: false + property var pendingIdentity: null + property bool detailsOpen: false + readonly property bool waiting: !!flow && flow.isResponseRequired && !flow.isCompleted && !flow.isCancelled + readonly property bool canEdit: waiting && !busy + // INVARIANT: une touche Entrée sur un champ secret vide ne doit pas + // consommer une tentative PAM. Une invite visible peut admettre une + // réponse vide ; Polkit reste maître de sa validation. + readonly property bool canSubmit: canEdit && (flow.responseVisible || answer.text.length > 0) + implicitWidth: 480 + implicitHeight: content.implicitHeight + 36 + signal cancelled() + + // CONTRACT: only the bound native AuthFlow receives the response. The + // field is cleared immediately; no answer enters logs, IPC or storage. + function submitResponse() { + const target = flow; + if (!target || target !== card.flow || !canSubmit) return false; + busy = true; + target.submit(answer.text); + answer.clear(); + return true; + } + function cancelRequest() { + const target = flow; + answer.clear(); + busy = true; + if (target && !target.isCompleted && !target.isCancelled) + target.cancelAuthenticationRequest(); + cancelled(); + } + function selectIdentity(identity) { + const target = flow; + if (!target || !target.identities || target.identities.indexOf(identity) < 0) return; + answer.clear(); + busy = true; + awaitingIdentityPrompt = true; + pendingIdentity = identity; + target.selectedIdentity = identity; + } + function clearResponse() { answer.clear(); } + function identityLabel(identity) { + if (!identity) return ""; + return identity.displayName + " (" + identity.string + " · " + + (identity.isGroup ? "GID " : "UID ") + identity.id + ")"; + } + function focusResponse() { if (waiting) answer.forceActiveFocus(); else cancelButton.forceActiveFocus(); } + onFlowChanged: { answer.clear(); busy = false; awaitingIdentityPrompt = false; + pendingIdentity = null; detailsOpen = false; } + Timer { + id: identityPromptFallback + interval: 1000 + running: card.awaitingIdentityPrompt + // WHY: QuickShell 0.3.1 does not expose a session generation. When two + // identities receive the same prompt, its notify values do not change. + // The native setter has already replaced the session synchronously; + // this bounded wait permits the new conversation to present its prompt. + onTriggered: { + if (card.flow && card.flow.selectedIdentity === card.pendingIdentity + && card.flow.isResponseRequired && !card.flow.isCompleted) { + card.awaitingIdentityPrompt = false; + card.busy = false; + card.focusResponse(); + } + } + } + onWaitingChanged: { + if (waiting) { + if (!awaitingIdentityPrompt) busy = false; + answer.clear(); + Qt.callLater(focusResponse); + } else answer.clear(); + } + Connections { + target: card.flow + function onAuthenticationFailed() { card.busy = false; answer.clear(); } + function onSelectedIdentityChanged() { answer.clear(); } + function onInputPromptChanged() { + answer.clear(); + if (card.awaitingIdentityPrompt && card.flow && card.flow.isResponseRequired) { + card.awaitingIdentityPrompt = false; + card.busy = false; + Qt.callLater(card.focusResponse); + } + } + function onIsResponseRequiredChanged() { + if (card.awaitingIdentityPrompt && card.flow && card.flow.isResponseRequired) { + card.awaitingIdentityPrompt = false; + card.busy = false; + Qt.callLater(card.focusResponse); + } + } + function onIsCompletedChanged() { answer.clear(); } + function onIsCancelledChanged() { answer.clear(); } + } + + ColumnLayout { + id: content + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: 18 + spacing: 12 + RowLayout { + Layout.fillWidth: true + spacing: 12 + Rectangle { + width: 38; height: 38; radius: 4 + color: Theme.buttonBackground + Text { anchors.centerIn: parent; text: "󰌾"; color: Theme.accentForeground + font.family: "JetBrainsMono Nerd Font"; font.pixelSize: 23 } + } + Text { Layout.fillWidth: true; text: "Authentification requise"; color: Theme.foreground + font.pixelSize: 18; font.bold: true; wrapMode: Text.Wrap } + } + RowLayout { + Layout.fillWidth: true + spacing: 10 + Image { Layout.preferredWidth: 24; Layout.preferredHeight: 24 + source: card.flow && card.flow.iconName ? Quickshell.iconPath(card.flow.iconName) : "" + visible: status === Image.Ready; fillMode: Image.PreserveAspectFit } + Text { Layout.fillWidth: true; text: card.flow ? card.flow.message : "" + textFormat: Text.PlainText; color: Theme.foreground; wrapMode: Text.Wrap; font.pixelSize: 14 } + } + Text { Layout.fillWidth: true; text: "Compte d’authentification"; + color: Theme.secondaryForeground; font.pixelSize: 12 } + Text { Layout.fillWidth: true; visible: !!card.flow && card.flow.identities.length === 1 + text: card.flow && card.flow.selectedIdentity + ? card.flow.selectedIdentity.displayName + " (" + card.flow.selectedIdentity.string + ")" : "" + textFormat: Text.PlainText; color: Theme.foreground; font.pixelSize: 14; elide: Text.ElideRight } + ComboBox { + id: identityChoice + Layout.fillWidth: true + visible: !!card.flow && card.flow.identities.length > 1 + model: card.flow ? card.flow.identities.map(identity => ({ label: card.identityLabel(identity) })) : [] + textRole: "label" + currentIndex: card.flow && card.flow.identities + ? card.flow.identities.indexOf(card.flow.selectedIdentity) : -1 + onActivated: index => card.selectIdentity(card.flow.identities[index]) + background: Rectangle { radius: 4; color: Theme.inputBackground + border.color: identityChoice.activeFocus ? Theme.focus : Theme.inputBorder } + contentItem: Text { text: identityChoice.displayText; textFormat: Text.PlainText + color: Theme.foreground + verticalAlignment: Text.AlignVCenter; leftPadding: 10; elide: Text.ElideRight } + delegate: ItemDelegate { + width: identityChoice.width + text: modelData.label + contentItem: Text { text: parent.text; textFormat: Text.PlainText + color: Theme.foreground + verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight } + background: Rectangle { color: parent.highlighted ? Theme.buttonHover : Theme.popupBackground } + } + popup: Popup { + y: identityChoice.height + width: identityChoice.width + implicitHeight: Math.min(contentItem.implicitHeight, 240) + contentItem: ListView { clip: true; implicitHeight: contentHeight + model: identityChoice.popup.visible ? identityChoice.delegateModel : null + currentIndex: identityChoice.highlightedIndex } + background: Rectangle { radius: 4; color: Theme.popupBackground + border.color: Theme.outline } + } + } + Text { Layout.fillWidth: true + text: card.flow && card.flow.inputPrompt ? card.flow.inputPrompt : "En attente de la demande…" + textFormat: Text.PlainText; color: Theme.secondaryForeground; wrapMode: Text.Wrap; font.pixelSize: 12 } + TextField { + id: answer + objectName: "polkitResponse" + Layout.fillWidth: true + enabled: card.canEdit + visible: card.waiting + echoMode: card.flow && card.flow.responseVisible ? TextInput.Normal : TextInput.Password + color: Theme.foreground + placeholderTextColor: Theme.secondaryForeground + selectionColor: Theme.accent + selectedTextColor: Theme.onAccent + selectByMouse: true + placeholderText: card.flow && card.flow.responseVisible ? "Réponse" : "Réponse confidentielle" + Keys.onReturnPressed: event => { card.submitResponse(); event.accepted = true; } + Keys.onEnterPressed: event => { card.submitResponse(); event.accepted = true; } + background: Rectangle { radius: 4; color: Theme.inputBackground + border.color: answer.activeFocus ? Theme.focus : Theme.inputBorder } + } + Text { Layout.fillWidth: true + visible: !!card.flow && (!!card.flow.supplementaryMessage || card.flow.failed) + text: card.flow ? (card.flow.supplementaryMessage + || (card.flow.failed ? "Authentification refusée. Réessayez." : "")) : "" + textFormat: Text.PlainText + color: card.flow && (card.flow.supplementaryMessage + ? card.flow.supplementaryIsError : card.flow.failed) + ? Theme.danger : Theme.secondaryForeground + wrapMode: Text.Wrap; font.pixelSize: 12 } + Text { Layout.fillWidth: true; visible: !card.waiting + text: card.flow && card.flow.isCompleted ? "Demande terminée" : "En attente de l’agent d’authentification…" + color: Theme.secondaryForeground; font.pixelSize: 12 } + Button { + id: detailButton + text: card.detailsOpen ? "Masquer l’identifiant de l’action" : "Détails de l’action" + flat: true + onClicked: card.detailsOpen = !card.detailsOpen + contentItem: Text { text: detailButton.text; color: Theme.accentForeground + font.pixelSize: 12; verticalAlignment: Text.AlignVCenter } + } + Text { Layout.fillWidth: true; visible: card.detailsOpen + text: card.flow ? card.flow.actionId : ""; textFormat: Text.PlainText + color: Theme.secondaryForeground; wrapMode: Text.WrapAnywhere; font.pixelSize: 11 } + RowLayout { + Layout.fillWidth: true + Item { Layout.fillWidth: true } + Button { id: cancelButton; text: "Annuler"; onClicked: card.cancelRequest() + background: Rectangle { radius: 4; color: cancelButton.hovered ? Theme.buttonHover : Theme.buttonBackground + border.color: cancelButton.activeFocus ? Theme.focus : Theme.outline } + contentItem: Text { text: cancelButton.text; color: Theme.foreground + horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter } } + Button { id: submitButton; text: "Authentifier"; enabled: card.canSubmit + onClicked: card.submitResponse() + background: Rectangle { radius: 4; color: Theme.accent + border.color: submitButton.activeFocus ? Theme.foreground : Theme.accent } + contentItem: Text { text: submitButton.text; color: Theme.onAccent + horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter } } + } + } + Shortcut { sequence: "Escape"; context: Qt.WindowShortcut; onActivated: card.cancelRequest() } +} diff --git a/quickshell/.config/quickshell/labfy-sway/polkit/PolkitDialog.qml b/quickshell/.config/quickshell/labfy-sway/polkit/PolkitDialog.qml new file mode 100644 index 000000000..1d6b53cf3 --- /dev/null +++ b/quickshell/.config/quickshell/labfy-sway/polkit/PolkitDialog.qml @@ -0,0 +1,48 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import Quickshell.Wayland +import "../theme" + +PanelWindow { + id: window + required property var requestFlow + required property var hostScreen + readonly property var flow: requestFlow + readonly property Item card: form + screen: hostScreen + anchors { top: true; bottom: true; left: true; right: true } + exclusionMode: ExclusionMode.Ignore + exclusiveZone: 0 + aboveWindows: true + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + WlrLayershell.namespace: "labfy-polkit-authentication" + color: "transparent" + + // WHY: Sway gives keyboard focus to an exclusive layer surface. The + // surface exists only for this flow and never dismisses on outside click. + Rectangle { + id: frame + anchors.centerIn: parent + width: Math.max(0, Math.min(480, parent.width - 32)) + height: Math.max(0, Math.min(form.implicitHeight, parent.height - 32)) + radius: 4 + color: Theme.popupBackground + border.color: Theme.outline + border.width: 1 + clip: true + ScrollView { + anchors.fill: parent + contentWidth: availableWidth + PolkitCard { + id: form + width: parent.width + height: implicitHeight + flow: window.flow + } + } + } + Timer { interval: 120; running: true; onTriggered: form.focusResponse() } + Component.onDestruction: form.clearResponse() +} diff --git a/quickshell/.config/quickshell/labfy-sway/polkit/README.md b/quickshell/.config/quickshell/labfy-sway/polkit/README.md new file mode 100644 index 000000000..765a36ab4 --- /dev/null +++ b/quickshell/.config/quickshell/labfy-sway/polkit/README.md @@ -0,0 +1,152 @@ +# Agent graphique Polkit V1 + +État au 9 octobre 2026 : l'agent QuickShell est actif. Une demande réelle a +réussi, puis une autorisation `pkexec` a permis de retirer le paquet +`polkit-gnome`, désormais inutile. `faillock` est vide après ces deux +authentifications. Les fichiers actifs avant la reprise sont sauvegardés dans +`/home/fy59/.local/state/labfy-polkit-v2-rollback-20261009/` ; ceux du premier +essai restent dans `/home/fy59/.local/state/labfy-polkit-investigation-20261009/`. + +Sway ne lance plus `polkit-gnome-authentication-agent-1`. La session graphique +est SwayFX sur Wayland, et `quickshell-labfy-sway.service` lance QuickShell +sous l'utilisateur ordinaire. Son `shell.qml` possède l'unique `PolkitAgent` ; +le module natif `Quickshell.Services.Polkit` garde la file des demandes. +`PolkitDialog.qml` est créé uniquement pour le `flow` courant, sur la sortie du +workspace focalisé au début de la demande. Une sortie débranchée déclenche un +repli vers une sortie disponible. La surface layer-shell exclusive est détruite +après la demande ; un clic extérieur ne fait rien. Les panneaux de barre se +ferment et les captures automatiques Overview sont suspendues pendant la demande. + +La palette vient de `Theme.qml`, elle-même alimentée par l'état effectif +`AppearanceController` et `theme/catppuccin.json`. Le dialogue suit donc les +changements de thème QuickShell. La synchronisation avec les réglages GTK/Qt +reste celle du contrôleur d'apparence existant ; aucun thème GTK supplémentaire +n'est imposé par l'agent. + +`AuthFlow` fournit le message réel, l'icône, l'identifiant technique, les +identités admises, l'invite, `responseVisible`, le message complémentaire et son +état d'erreur. Une seule identité est affichée comme texte ; plusieurs passent +par un sélecteur dont les valeurs proviennent exclusivement de Polkit. +`submit()` reçoit directement le contenu du champ du flow courant lorsque +`isResponseRequired` est vrai. La réponse est effacée après soumission, échec, +changement d'identité, annulation, disparition ou remplacement du flow. +Une réponse secrète est masquée selon `responseVisible`. Le champ ne passe ni +par argv, ni par fichier, ni par IPC, ni par journal. Ceci limite les copies et +la durée de vie de la chaîne QML sans promettre un effacement physique de la +mémoire Qt. Le collage manuel reste disponible. Le succès est décidé par le +backend Polkit, jamais par l'appui sur Entrée. + +Limite de l'API 0.3.1 : un changement d'identité vers une conversation ayant +exactement le même texte d'invite peut ne produire aucun signal de changement +de `inputPrompt` ni de `isResponseRequired`. Le dialogue garde alors la +soumission bloquée pendant une seconde, puis réactive le champ seulement si +le même flow, la nouvelle identité et `isResponseRequired` sont toujours +présents. Cette temporisation reste locale à la présentation et ne crée pas +de deuxième conversation ni de file de réponses. Une invite secrète vide ne +peut plus être soumise accidentellement : le champ reste modifiable, mais +Entrée et le bouton ne transmettent rien tant qu'il est vide. + +L'API du paquet QuickShell 0.3.1 installé a été vérifiée dans +`/usr/lib/qt6/qml/Quickshell/Services/Polkit/quickshell-service-polkit.qmltypes` +et dans les sources de son paquet debug. `PolkitAgent` expose `isRegistered`, +`isActive`, `flow`, `authenticationRequestStarted`. `AuthFlow` expose les +propriétés ci-dessus, `submit()` et `cancelAuthenticationRequest()`. Le paquet +construit le sujet d'enregistrement depuis le PID QuickShell et annule la +requête en cours si l'agent est détruit. Si QuickShell s'arrête pendant une +demande, Polkit ne l'autorise pas par défaut : le demandeur reçoit une +annulation ou une erreur. Un redémarrage hors demande doit rétablir +`isRegistered=true`. + +## Vérifications et démonstration + +`PolkitVisualFixture.qml` est un shell de test autonome : il n'instancie +**aucun** `PolkitAgent`. Son IPC `polkitFixture` n'existe pas dans `shell.qml` +et ne peut pas autoriser une action Polkit. Il sert à vérifier message court ou +long, icône absente, une ou plusieurs identités, réponse visible ou masquée, +erreur et nouvelle invite, annulation et double soumission. La capture vide +[de démonstration](../../../../../docs/assets/screenshots/quickshell-polkit-demo-empty.png) +provient seulement de cette fixture, sans secret réel. + +```sh +qmllint ~/.config/quickshell/labfy-sway/{shell.qml,Bar.qml,polkit/*.qml} +qs -p ~/.config/quickshell/labfy-sway/PolkitVisualFixture.qml +qs ipc -p ~/.config/quickshell/labfy-sway/PolkitVisualFixture.qml call polkitFixture checks +qs -c labfy-sway ipc call polkitUi state +``` + +Pour vérifier une demande réelle sans opération privilégiée, `pkcheck` peut +contrôler `org.freedesktop.timedate1.set-timezone` avec +`--process PID,START_TIME,UID --allow-user-interaction`, le PID étant celui de +Sway dans la session graphique. Ne pas ajouter `--enable-internal-agent`. +Un résultat autorisé immédiatement ne valide pas le dialogue. Aucun véritable +mot de passe n'est utilisé dans les tests automatisés ; une authentification +réelle a été validée séparément dans le dialogue local lors de la reprise. + +### Premier essai + +Lors de la bascule du 9 octobre 2026, la fixture a confirmé soumission unique, +erreur puis nouvelle invite, changement d'identité, disparition du flow, +deuxième demande et annulation, avec toutes les assertions à `true`. Le cas +des deux identités ayant la même invite a maintenu la soumission bloquée puis +l'a réactivée après une seconde. Les 22 tests `unittest` ciblés sur le pont +d'inhibition, l'annotation de capture et les mises à jour ont passé ; +`qmllint`, `git diff --check` et la simulation Stow ont passé. Le contrôle +`sway -C` a retourné 0 avec un avertissement EGL d'énumération GPU. + +Le nouvel agent a répondu `registered=true` après l'arrêt ciblé de l'ancien. +`pkcheck` sur le sujet graphique complet `21858,320156,1000` a produit +`active=true`, `dialog=true` sur `eDP-1`. La disparition du demandeur a fermé +la surface (`active=false`, `dialog=false`). Une seconde demande a ouvert le +dialogue puis a été rejetée avec le code `3` lors d'un rechargement du shell ; +ce rechargement n'a donc accordé aucune autorisation. Un rechargement hors +demande a conservé `registered=true` avec un seul processus QuickShell. +Pendant les éditions QML suivantes, un autre rechargement hors demande a +rencontré l'erreur Wayland `xdg_popup has no parent` ; le service a redémarré +QuickShell et l'enregistrement est redevenu `true`, sans deuxième agent. +Cette erreur de popup lors d'un rechargement avec des panneaux existants reste +une limite observée de la session, sans autorisation implicite. +L'annulation par le bouton local reste à confirmer par une interaction +utilisateur hors essai d'authentification. Annuler une conversation PAM peut +consommer une tentative et ne doit pas être répété pour tester la présentation. + +### Incident de verrouillage et reprise + +Le premier essai a provoqué à 06:55–06:56 deux conversations Polkit échouées +sans mot de passe exploitable et une réponse refusée. `pam_faillock` a compté +trois échecs et bloqué temporairement Swaylock et `login`. Le mot de passe +n'avait pas changé. Le verrouillage a expiré et une session TTY2 a ensuite +réussi ; le redémarrage a effacé le compteur volatil. Aucune permission PAM ni +règle `sudoers` n'a été modifiée. + +La reprise a utilisé d'abord `PolkitVisualFixture.qml`, sans `PolkitAgent`. +Son IPC a confirmé notamment le blocage d'une réponse secrète vide, une seule +soumission, le remplacement du flow et l'effacement du champ. Avant la demande +réelle, `faillock --user fy59` était vide et QuickShell indiquait +`registered=true`. L'utilisateur a saisi le mot de passe une seule fois dans +la fenêtre locale pour un `pkcheck` sur +`org.freedesktop.timedate1.set-timezone` ; la commande a retourné 0 sans +changer le fuseau. Le dialogue s'est fermé et `faillock` est resté vide. +Une autre demande Polkit, initiée par `pkexec` pour retirer uniquement le paquet +`polkit-gnome`, a également réussi. Aucun secret n'a été transmis à cette +conversation, à un argument de commande ou à un journal. + +## Retour arrière ciblé + +Le paquet GNOME a été retiré. Pour revenir à cet agent, le réinstaller depuis +un TTY (`sudo pacman -S polkit-gnome`) si l'agent QuickShell ne fonctionne pas. +Hors demande d'authentification, la copie persistante créée avant la reprise +retire ensuite l'agent QuickShell et rétablit l'autostart GNOME : + +```sh +base=/home/fy59/.dotfiles +backup=/home/fy59/.local/state/labfy-polkit-v2-rollback-20261009 +cp "$backup/shell.qml" "$base/quickshell/.config/quickshell/labfy-sway/shell.qml" +cp "$backup/Bar.qml" "$base/quickshell/.config/quickshell/labfy-sway/Bar.qml" +cp "$backup/WorkspaceOverview.qml" "$base/quickshell/.config/quickshell/labfy-sway/overview/WorkspaceOverview.qml" +cp "$backup/autostart" "$base/sway/.config/sway/autostart" +systemctl --user restart quickshell-labfy-sway.service +swaymsg exec /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 +``` + +Ne pas lancer ce retour arrière pendant une demande active : interrompre une +conversation PAM peut consommer une tentative. Ne pas arrêter `polkitd`. diff --git a/quickshell/.config/quickshell/labfy-sway/shell.qml b/quickshell/.config/quickshell/labfy-sway/shell.qml index f2a8136bb..59f374de2 100644 --- a/quickshell/.config/quickshell/labfy-sway/shell.qml +++ b/quickshell/.config/quickshell/labfy-sway/shell.qml @@ -3,12 +3,98 @@ import QtQuick import Quickshell import Quickshell.Io import Quickshell.I3 +import Quickshell.Services.Polkit import "notifications" import "idlebridge" import "theme" +import "polkit" ShellRoot { id: shell + // CONTRACT: one PolkitAgent owns the native request queue for the entire + // user session. A dialog is constructed only for its current flow. + PolkitAgent { id: polkitAgent } + property var polkitScreen: null + readonly property bool authenticationActive: polkitAgent.flow !== null + signal authenticationOpening() + function choosePolkitScreen(output) { + const screens = Quickshell.screens; + polkitScreen = screens.find(screen => screen.name === output) + || (screens.length ? screens[0] : null); + } + function beginPolkitPresentation() { + polkitScreen = null; + if (!polkitAgent.flow) { + polkitOutput.running = false; + return; + } + authenticationOpening(); + // WHY: the focused Sway workspace identifies the output at request + // start. Later focus changes do not move the authentication surface. + if (!polkitOutput.running) polkitOutput.running = true; + else choosePolkitScreen(""); + } + Connections { + target: polkitAgent + function onFlowChanged() { shell.beginPolkitPresentation(); } + function onAuthenticationRequestStarted() { + if (!polkitOutput.running && !shell.polkitScreen) + shell.beginPolkitPresentation(); + } + } + Timer { + // WHY: a missing or disconnected Sway IPC must not leave an active + // Polkit request with no visible way to cancel it. + interval: 500 + running: !!polkitAgent.flow && !shell.polkitScreen + onTriggered: { + shell.choosePolkitScreen(""); + polkitOutput.running = false; + } + } + Process { + id: polkitOutput + command: ["swaymsg", "-t", "get_workspaces", "-r"] + stdout: StdioCollector { id: polkitOutputData; waitForEnd: true } + onExited: (code, status) => { + if (!polkitAgent.flow || shell.polkitScreen) return; + let output = ""; + if (code === 0) { + try { + const focused = JSON.parse(polkitOutputData.text).find(item => item.focused); + output = focused ? focused.output : ""; + } catch (_) {} + } + shell.choosePolkitScreen(output); + } + } + // INVARIANT: a vanished output moves the same flow to a surviving screen; + // it never creates a second PolkitAgent or another request queue. + Connections { + target: Quickshell + function onScreensChanged() { + if (polkitAgent.flow && Quickshell.screens.indexOf(shell.polkitScreen) < 0) + shell.choosePolkitScreen(""); + } + } + LazyLoader { + id: polkitDialog + active: !!polkitAgent.flow && !!shell.polkitScreen + PolkitDialog { requestFlow: polkitAgent.flow; hostScreen: shell.polkitScreen } + } + IpcHandler { + target: "polkitUi" + function state(): string { + return JSON.stringify({ registered: polkitAgent.isRegistered, + active: polkitAgent.isActive, dialog: polkitDialog.active, + screen: shell.polkitScreen ? shell.polkitScreen.name : "", + queryRunning: polkitOutput.running }); + } + } + Component.onCompleted: { + modeSnapshot.running = true; + if (polkitAgent.flow) beginPolkitPresentation(); + } property bool resizeMode: false // CONTRACT: une seule sélection pour toutes les barres. L'inhibition est // volontairement éphémère : un redémarrage de QuickShell la libère. @@ -51,6 +137,7 @@ ShellRoot { property string applicationsOutput: "" signal applicationsOpening(string output) function toggleApplications(output) { + if (authenticationActive) return; if (applicationsOutput === output) { applicationsOutput = ""; return; } applicationsOpening(output); applicationsOutput = output; @@ -81,7 +168,6 @@ ShellRoot { catch (error) { console.warn("État mode Sway invalide", error); } } } - Component.onCompleted: modeSnapshot.running = true Connections { target: I3 function onConnected() { if (!modeSnapshot.running) modeSnapshot.running = true; } @@ -115,6 +201,7 @@ ShellRoot { Bar { applicationCoordinator: shell + authenticationActive: shell.authenticationActive keepAwakeController: shell resizeMode: shell.resizeMode notificationService: rootNotificationService diff --git a/sway/.config/sway/autostart b/sway/.config/sway/autostart index 4cb5890a3..6fb4cd62c 100644 --- a/sway/.config/sway/autostart +++ b/sway/.config/sway/autostart @@ -9,9 +9,8 @@ # Listener IPC démarré une fois par session ; exec ne se rejoue pas au reload. exec python3 ~/.config/sway/scripts/inactive-windows-transparency.py --opacity $inactive_opacity -# CONTRACT: l'agent Polkit affiche l'authentification standard de timedated. -# exec n'est lancé qu'à l'ouverture de session, pas à chaque reload Sway. -exec /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 +# CONTRACT: QuickShell possède l'unique agent graphique Polkit de la session. +# Aucun deuxième agent n'est lancé par Sway à la prochaine connexion. # ================================================= # Règles d'assignation automatique