1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

polkit: support for polkit escalation prompts

This commit is contained in:
bbedward
2025-10-31 09:40:05 -04:00
parent 53ae8ac917
commit c5efd28781
4 changed files with 428 additions and 0 deletions

View File

@@ -213,6 +213,10 @@ Item {
}
}
PolkitAuthModal {
id: polkitAuthModal
}
property string lastCredentialsToken: ""
property var lastCredentialsTime: 0

283
Modals/PolkitAuthModal.qml Normal file
View File

@@ -0,0 +1,283 @@
import QtQuick
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
property string passwordInput: ""
function show() {
passwordInput = ""
open()
Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.passwordField) {
contentLoader.item.passwordField.forceActiveFocus()
}
})
}
shouldBeVisible: false
width: 420
height: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 240
onShouldBeVisibleChanged: () => {
if (!shouldBeVisible) {
passwordInput = ""
}
}
onOpened: {
Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.passwordField) {
contentLoader.item.passwordField.forceActiveFocus()
}
})
}
onBackgroundClicked: () => {
PolkitService.cancel()
close()
passwordInput = ""
}
Connections {
target: PolkitService
function onAuthenticationRequested() {
show()
}
function onAuthenticationCompleted() {
close()
passwordInput = ""
}
function onIsResponseRequiredChanged() {
if (PolkitService.isResponseRequired && root.shouldBeVisible) {
passwordInput = ""
if (contentLoader.item && contentLoader.item.passwordField) {
contentLoader.item.passwordField.forceActiveFocus()
}
}
}
}
content: Component {
FocusScope {
id: authContent
property alias passwordField: passwordField
anchors.fill: parent
focus: true
implicitHeight: mainColumn.implicitHeight
Keys.onEscapePressed: event => {
PolkitService.cancel()
close()
passwordInput = ""
event.accepted = true
}
Column {
id: mainColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingM
spacing: Theme.spacingM
Row {
width: parent.width
Column {
width: parent.width - 40
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Authentication Required")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
Column {
width: parent.width
spacing: Theme.spacingXS
StyledText {
text: PolkitService.message
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
width: parent.width
wrapMode: Text.Wrap
}
StyledText {
visible: PolkitService.supplementaryMessage !== ""
text: PolkitService.supplementaryMessage
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
width: parent.width
wrapMode: Text.Wrap
opacity: 0.8
}
StyledText {
visible: PolkitService.failed
text: I18n.tr("Authentication failed, please try again")
font.pixelSize: Theme.fontSizeSmall
color: Theme.error
width: parent.width
}
}
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: () => {
PolkitService.cancel()
close()
passwordInput = ""
}
}
}
StyledText {
text: PolkitService.inputPrompt
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: parent.width
visible: PolkitService.inputPrompt !== ""
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: passwordField.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: passwordField.activeFocus ? 2 : 1
MouseArea {
anchors.fill: parent
onClicked: () => {
passwordField.forceActiveFocus()
}
}
DankTextField {
id: passwordField
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: passwordInput
echoMode: PolkitService.responseVisible ? TextInput.Normal : TextInput.Password
placeholderText: I18n.tr("Password")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
passwordInput = text
}
onAccepted: () => {
if (passwordInput.length > 0) {
PolkitService.submit(passwordInput)
passwordInput = ""
}
}
}
}
Item {
width: parent.width
height: 40
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
Rectangle {
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
border.color: Theme.surfaceVariantAlpha
border.width: 1
StyledText {
id: cancelText
anchors.centerIn: parent
text: I18n.tr("Cancel")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
MouseArea {
id: cancelArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
PolkitService.cancel()
close()
passwordInput = ""
}
}
}
Rectangle {
width: Math.max(80, authText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: authArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: passwordInput.length > 0 || !PolkitService.isResponseRequired
opacity: enabled ? 1 : 0.5
StyledText {
id: authText
anchors.centerIn: parent
text: I18n.tr("Authenticate")
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
font.weight: Font.Medium
}
MouseArea {
id: authArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: parent.enabled
onClicked: () => {
PolkitService.submit(passwordInput)
passwordInput = ""
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
}
}
}

101
Services/PolkitService.qml Normal file
View File

@@ -0,0 +1,101 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
readonly property bool disablePolkitIntegration: Quickshell.env("DMS_DISABLE_POLKIT") === "1"
property bool polkitAvailable: false
property var agent: null
property var currentFlow: null
property bool isActive: false
property string message: ""
property string supplementaryMessage: ""
property string inputPrompt: ""
property bool failed: false
property bool responseVisible: false
property bool isResponseRequired: false
signal authenticationRequested()
signal authenticationCompleted()
signal authenticationFailed()
function createPolkitAgent() {
try {
const qmlString = `
import QtQuick
import Quickshell.Services.Polkit
PolkitAgent {
}
`
agent = Qt.createQmlObject(qmlString, root, "PolkitService.Agent")
agent.isActiveChanged.connect(function() {
root.isActive = agent.isActive
if (agent.isActive) {
root.authenticationRequested()
} else {
root.authenticationCompleted()
}
})
agent.flowChanged.connect(function() {
currentFlow = agent.flow
if (currentFlow) {
updateFlowProperties()
if (currentFlow.messageChanged) currentFlow.messageChanged.connect(() => updateFlowProperties())
if (currentFlow.supplementaryMessageChanged) currentFlow.supplementaryMessageChanged.connect(() => updateFlowProperties())
if (currentFlow.inputPromptChanged) currentFlow.inputPromptChanged.connect(() => updateFlowProperties())
if (currentFlow.failedChanged) currentFlow.failedChanged.connect(() => updateFlowProperties())
if (currentFlow.responseVisibleChanged) currentFlow.responseVisibleChanged.connect(() => updateFlowProperties())
if (currentFlow.isResponseRequiredChanged) currentFlow.isResponseRequiredChanged.connect(() => updateFlowProperties())
}
})
polkitAvailable = true
console.info("PolkitService: Initialized successfully")
} catch (e) {
polkitAvailable = false
console.warn("PolkitService: Polkit not available - authentication prompts disabled. This requires a newer version of Quickshell.")
}
}
function updateFlowProperties() {
if (!currentFlow) return
message = currentFlow.message !== undefined ? currentFlow.message : ""
supplementaryMessage = currentFlow.supplementaryMessage !== undefined ? currentFlow.supplementaryMessage : ""
inputPrompt = currentFlow.inputPrompt !== undefined ? currentFlow.inputPrompt : ""
failed = currentFlow.failed !== undefined ? currentFlow.failed : false
responseVisible = currentFlow.responseVisible !== undefined ? currentFlow.responseVisible : false
isResponseRequired = currentFlow.isResponseRequired !== undefined ? currentFlow.isResponseRequired : false
}
function submit(response) {
if (currentFlow && isResponseRequired) {
currentFlow.submit(response)
}
}
function cancel() {
if (currentFlow) {
currentFlow.cancelAuthenticationRequest()
}
}
Component.onCompleted: {
if (disablePolkitIntegration) {
return
}
createPolkitAgent()
}
}

40
assets/danklogo.svg Normal file
View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="500px" height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g>
<g>
<path fill="#808080" d="M151.583,133.43c-1.255,1.946,0.646,1.946,5.704,0C155.385,133.43,153.484,133.43,151.583,133.43z"/>
<path fill="#808080" d="M423.762,406.435C351.624,511.72,195.549,530.36,100.813,443.681
c-29.726-27.249-61.828-70.379-45.267-112.843c9.16-23.487,37.102-24.426,57.716-32.065
c27.527-10.201,54.303-25.636,71.783-49.842c1.823,22.976,23.814,52.576,33.269,73.388c9.772,21.51,19.334,43.117,28.757,64.783
c0.991-8.282,16.502-92.821-14.752-76.345c19.458-21.14,35.548-31.5,54.594,0.311c-2.329-1.542-4.659-3.084-6.988-4.625
c-13.702,28.822,6.996,68.66,14.663,97.275c-0.159-44.012,1.277-88.943-3.305-132.758c25.284,18.425,52.786,29.832,81.891,41.012
C411.181,326.572,412.863,370.402,423.762,406.435C416.231,417.427,422.675,402.841,423.762,406.435z"/>
<path fill="#808080" d="M141.639,217.359c-1.449,30.049-3.187,60.252-7.215,90.078c-1.049,7.765-10.07,31.329,4.856,29.922
c5.631-0.531,26.431-6.722,28.324-12.94c7.661-25.155,15.321-50.31,22.982-75.465
C194.325,236.673,152.531,222.984,141.639,217.359z"/>
<path fill="#808080" d="M151.583,133.43c-1.255,1.946,0.646,1.946,5.704,0C155.385,133.43,153.484,133.43,151.583,133.43z"/>
<path fill="#808080" d="M380.027,190.172c2.551-3.381,12.116-35.014,7.979-40.841c-21.139-9.962-38.084,1.086-58.839,0.144
c-28.481-1.292-52.16-10.216-82.194-5.857c-20.152,2.925-40.476,6.822-59.248,14.892c-5.085,2.171-33.296,22.994-33.035,23.122
c-14.027-6.836,43.648-35.195,48.133-36.619c33.084-10.501,67.297-12.315,101.608-15.552c25.236-2.379,54.641-1.008,78.47-11.112
c16.618-7.047,20.647-25.35,3.137-33.427c-22.945-10.585-53.247,1.025-64.749-21.947c-9.865-19.703-19.126-50.833-42.862-57.923
c-36.643-10.945-98.407,4.036-125.468,30.683c-14.664,14.44-12.628,32.211-13.789,51.294c-0.771,12.674,2.771,18.959-9.236,22.648
c-11.996,3.686-35.259,9.105-41.089,21.843c-9.907,21.645,60.404,18.041,70.524,17.445
c-48.116,78.556,9.309,154.103,95.828,138.485c21.662-3.91,46.538-11.144,63.301-26.06c14.268-12.696,18.851-26.329,38.884-31.764
c22.88-6.207,48.618-2.853,71.213,3.225C425.427,211.414,397.813,197.701,380.027,190.172z M147.943,113.559
c32.398-5.478,64.75-11.227,97.122-16.857c18.865-3.281,37.73-6.561,56.594-9.842c2.704-0.47,37.591-8.61,26.975,6.476
c-8.441,11.996-61.367,8.371-74.078,9.64C219.023,106.522,183.499,110.243,147.943,113.559
C162.475,111.102,162.63,112.189,147.943,113.559z M370.763,155.911c8.064,4.793,6.789,19.245,0,26.465
C372.129,173.756,372.669,164.493,370.763,155.911z M300.604,159.526c19.572-0.816,8.747,23.495-2.02,28.95
C303.101,186.108,301.518,163.22,300.604,159.526z M284.787,217.004c-13.756-0.207-27.836-4.341-34.89-17.079
c-3.743-6.76-3.246-24.814-13.996-23.708c-17.975,1.85-35.949,3.701-53.924,5.551c17.165-8.213,32.693-15.006,51.887-17.165
c24.854-2.796,13.115,15.487,25.149,31.147c14.31,18.621,50.25,11.317,61.727-7.041c2.807-4.491,3.5-9.537,5.626-14.35
c9.82-22.233,13.955,4.169,18.087,9.218c-10.884-13.363-14.883,7.391-19.096,13.609
C316.276,210.59,300.73,217.253,284.787,217.004C252.941,216.524,316.35,217.498,284.787,217.004z M327.761,210.296
c11.885-7.741,12.4-23.74,26.261-19.392c19.134,6.002,40.641,14.199,54.49,29.334
C382.501,213.437,354.709,208.449,327.761,210.296z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB