From 1a845a5124bc3909727ed2314a10e2f00df6a9de Mon Sep 17 00:00:00 2001 From: j4kuuu Date: Sat, 24 Jan 2026 16:42:32 +0100 Subject: [PATCH] qml: major foundation design updates --- .gitignore | 21 ++++ qml/main.qml | 226 ++++++++++++++++++++++++++++-------- src/QMLIntegration.cpp | 37 ++++++ src/QMLIntegration.hpp | 1 + src/core/PolkitListener.cpp | 1 + src/core/PolkitListener.hpp | 1 + 6 files changed, 241 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index 49a9e3b..b3796d8 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,24 @@ build/ compile_commands.json + +# CMake / Qt generated (in-source build artifacts) +.qt/ +.rcc/ +hpa/ +hyprpolkitagent_autogen/ +meta_types/ +qmltypes/ + +CMakeFiles/ +CMakeCache.txt +cmake_install.cmake +Makefile + +# Built binaries / generated sources +hyprpolkitagent +hyprpolkitagent_qmltyperegistrations.cpp + +# Generated unit/service files (from build/install) +hyprpolkitagent.service +hyprpolkitagent-dbus.service diff --git a/qml/main.qml b/qml/main.qml index 3966ea5..2477162 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -5,15 +5,81 @@ import QtQuick.Layouts ApplicationWindow { id: window - property var windowWidth: Math.round(fontMetrics.height * 32.2856) - property var windowHeight: Math.round(fontMetrics.height * 13.9528) - property var heightSafeMargin: 15 + property real windowWidth: fontMetrics.height * 34 + property real baseWindowHeight: fontMetrics.height * 16 + property real windowMargin: fontMetrics.height * 1.2 - minimumWidth: Math.max(windowWidth, mainLayout.Layout.minimumWidth) + mainLayout.anchors.margins * 2 - minimumHeight: Math.max(windowHeight, mainLayout.Layout.minimumHeight) + mainLayout.anchors.margins * 2 + heightSafeMargin - maximumWidth: minimumWidth + // Clean up user string - remove "unix-user:" prefix if present + property string cleanUser: { + var user = hpa.getUser(); + if (user.startsWith("unix-user:")) { + return user.substring(10); + } + return user; + } + + // Parse the message to extract command if present + // Polkit commonly uses backticks or single quotes around commands. + property string rawMessage: hpa.getMessage() + property string commandText: { + var candidate = ""; + var fromDetails = ""; + if (hpa.getCommand) { + fromDetails = hpa.getCommand(); + } + if (fromDetails && fromDetails.length > 0) { + candidate = fromDetails; + } else { + var match = rawMessage.match(/`([^`]+)`/); + if (!match) { + match = rawMessage.match(/'([^']+)'/); + } + if (!match) { + match = rawMessage.match(/"([^"]+)"/); + } + if (!match) { + match = rawMessage.match(/run\s+(.+?)(?:\s+as\s+the\s+|\s+as\s+|\.?$)/i); + } + candidate = match ? match[1] : ""; + } + return normalizeCommandText(candidate); + } + property string messageText: { + if (commandText !== "") { + return "Authentication is needed to run"; + } + return rawMessage; + } + + function isQuoteChar(ch) { + return ch === "`" || ch === "'" || ch === "\""; + } + + function normalizeCommandText(cmd) { + var cleaned = (cmd || "").trim(); + var changed = true; + while (changed && cleaned.length >= 2) { + var first = cleaned[0]; + var last = cleaned[cleaned.length - 1]; + if (isQuoteChar(first) && isQuoteChar(last)) { + cleaned = cleaned.slice(1, -1).trim(); + } else { + changed = false; + } + } + return cleaned; + } + + flags: Qt.Dialog | Qt.WindowStaysOnTopHint + + width: windowWidth + height: minimumHeight + minimumWidth: windowWidth + maximumWidth: windowWidth + minimumHeight: Math.max(baseWindowHeight, contentLayout.implicitHeight + windowMargin * 2) maximumHeight: minimumHeight visible: true + onClosing: { hpa.setResult("fail"); } @@ -23,11 +89,15 @@ ApplicationWindow { } SystemPalette { - id: system - + id: activePalette colorGroup: SystemPalette.Active } + SystemPalette { + id: disabledPalette + colorGroup: SystemPalette.Disabled + } + Item { id: mainLayout @@ -43,39 +113,102 @@ ApplicationWindow { } ColumnLayout { + id: contentLayout anchors.fill: parent - anchors.margins: 4 + anchors.margins: windowMargin + spacing: 0 + // Header - centered Label { - color: Qt.darker(system.windowText, 0.8) + text: "Authentication Required" + color: activePalette.windowText font.bold: true - font.pointSize: Math.round(fontMetrics.height * 1.05) - text: "Authenticating for " + hpa.getUser() + font.pointSize: Math.round(fontMetrics.height * 1.2) + Layout.alignment: Qt.AlignHCenter + } + + // User info - centered, slightly muted + Label { + text: "for " + cleanUser + color: disabledPalette.windowText + font.pointSize: Math.round(fontMetrics.height * 0.9) + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: fontMetrics.height * 0.2 + } + + // Spacing after header + Item { Layout.preferredHeight: fontMetrics.height * 1.0 } + + HSeparator {} + + // Spacing after separator + Item { Layout.preferredHeight: fontMetrics.height * 0.8 } + + // Message text (without command) + Label { + text: messageText + color: activePalette.windowText + font.pointSize: Math.round(fontMetrics.height * 0.95) Layout.alignment: Qt.AlignHCenter Layout.maximumWidth: parent.width - elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap } - HSeparator { - Layout.topMargin: fontMetrics.height / 2 - Layout.bottomMargin: fontMetrics.height / 2 + // Command box - only shown if there's a command + Item { + Layout.preferredHeight: fontMetrics.height * 0.5 + visible: commandText !== "" } - Label { - color: system.windowText - text: hpa.getMessage() - Layout.maximumWidth: parent.width - elide: Text.ElideRight - wrapMode: Text.WordWrap + Rectangle { + id: commandBox + visible: commandText !== "" + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: false + Layout.preferredWidth: Math.min(commandLabel.implicitWidth + fontMetrics.height * 1.0, windowWidth - fontMetrics.height * 4) + Layout.maximumWidth: windowWidth - fontMetrics.height * 4 + Layout.preferredHeight: commandLabel.implicitHeight + fontMetrics.height * 1.0 + Layout.minimumHeight: fontMetrics.height * 2 + + // More visible background using base color (input field background) + color: activePalette.base + border.color: Qt.rgba(activePalette.windowText.r, activePalette.windowText.g, activePalette.windowText.b, 0.25) + border.width: 1 + radius: 6 + + Label { + id: commandLabel + anchors.fill: parent + anchors.margins: fontMetrics.height * 0.5 + text: commandText + color: activePalette.windowText + font.family: "monospace" + font.pointSize: Math.round(fontMetrics.height * 0.9) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WrapAnywhere + elide: Text.ElideNone + } } + // Spacing before password + Item { Layout.preferredHeight: fontMetrics.height * 1.0 } + + HSeparator {} + + // Spacing after separator + Item { Layout.preferredHeight: fontMetrics.height * 0.8 } + + // Password field - uses system styling TextField { id: passwordField - Layout.topMargin: fontMetrics.height / 2 + Layout.fillWidth: true + Layout.leftMargin: fontMetrics.height * 3 + Layout.rightMargin: fontMetrics.height * 3 placeholderText: "Password" - Layout.alignment: Qt.AlignHCenter + horizontalAlignment: TextInput.AlignHCenter hoverEnabled: true persistentSelection: true echoMode: TextInput.Password @@ -94,17 +227,18 @@ ApplicationWindow { } } } - } + // Error label Label { id: errorLabel - - color: "red" + color: activePalette.link font.italic: true - Layout.topMargin: 0 + font.pointSize: Math.round(fontMetrics.height * 0.85) text: "" + visible: text !== "" Layout.alignment: Qt.AlignHCenter + Layout.topMargin: fontMetrics.height * 0.3 Connections { target: hpa @@ -112,52 +246,52 @@ ApplicationWindow { errorLabel.text = e; } } - } - Rectangle { - color: "transparent" + // Flexible spacer - minimal, just for visual balance + Item { + Layout.preferredHeight: fontMetrics.height * 0.5 Layout.fillHeight: true + Layout.maximumHeight: fontMetrics.height * 2 } - HSeparator { - Layout.topMargin: fontMetrics.height / 2 - Layout.bottomMargin: fontMetrics.height / 2 - } + HSeparator {} + // Spacing before buttons + Item { Layout.preferredHeight: fontMetrics.height * 0.8 } + + // Action buttons - centered RowLayout { - Layout.alignment: Qt.AlignRight - Layout.rightMargin: fontMetrics.height / 2 + Layout.alignment: Qt.AlignHCenter + spacing: fontMetrics.height * 1.0 Button { text: "Cancel" - onClicked: (e) => { + onClicked: { hpa.setResult("fail"); } } Button { text: "Authenticate" - onClicked: (e) => { + highlighted: true + onClicked: { hpa.setResult("auth:" + passwordField.text); } } - } - } - } + // Separator using system palette component Separator: Rectangle { - color: Qt.darker(window.palette.text, 1.5) + color: Qt.rgba(activePalette.windowText.r, activePalette.windowText.g, activePalette.windowText.b, 0.12) } component HSeparator: Separator { implicitHeight: 1 Layout.fillWidth: true - Layout.leftMargin: fontMetrics.height * 8 - Layout.rightMargin: fontMetrics.height * 8 + Layout.leftMargin: fontMetrics.height * 2 + Layout.rightMargin: fontMetrics.height * 2 } - } diff --git a/src/QMLIntegration.cpp b/src/QMLIntegration.cpp index 66dbaa4..0bfe729 100644 --- a/src/QMLIntegration.cpp +++ b/src/QMLIntegration.cpp @@ -3,6 +3,8 @@ #include "core/Agent.hpp" #include "core/PolkitListener.hpp" +#include + void CQMLIntegration::onExit() { g_pAgent->submitResultThreadSafe(result.toStdString()); } @@ -16,6 +18,41 @@ QString CQMLIntegration::getMessage() { return g_pAgent->listener.session.inProgress ? g_pAgent->listener.session.message : "An application is requesting authentication."; } +QString CQMLIntegration::getCommand() { + if (!g_pAgent->listener.session.inProgress) + return ""; + + const auto& details = g_pAgent->listener.session.details; + const QStringList keys = details.keys(); + + const QStringList preferredKeys = { + "command_line", + "command-line", + "command", + "cmdline", + "cmd", + "program", + "exec", + }; + + for (const auto& key : preferredKeys) { + const QString value = details.lookup(key); + if (!value.isEmpty()) + return value; + } + + for (const auto& key : keys) { + const QString lowered = key.toLower(); + if (lowered.contains("command") || lowered.contains("cmd") || lowered.contains("program") || lowered.contains("exec")) { + const QString value = details.lookup(key); + if (!value.isEmpty()) + return value; + } + } + + return ""; +} + QString CQMLIntegration::getUser() { return g_pAgent->listener.session.inProgress ? g_pAgent->listener.session.selectedUser.toString() : "an unknown user"; } diff --git a/src/QMLIntegration.hpp b/src/QMLIntegration.hpp index eae432f..e0bff47 100644 --- a/src/QMLIntegration.hpp +++ b/src/QMLIntegration.hpp @@ -24,6 +24,7 @@ class CQMLIntegration : public QObject { QString result = "fail", errorText = ""; Q_INVOKABLE QString getMessage(); + Q_INVOKABLE QString getCommand(); Q_INVOKABLE QString getUser(); Q_INVOKABLE void setResult(QString str); diff --git a/src/core/PolkitListener.cpp b/src/core/PolkitListener.cpp index 52092ca..814efbb 100644 --- a/src/core/PolkitListener.cpp +++ b/src/core/PolkitListener.cpp @@ -39,6 +39,7 @@ void CPolkitListener::initiateAuthentication(const QString& actionId, const QStr session.actionId = actionId; session.message = message; session.iconName = iconName; + session.details = details; session.gainedAuth = false; session.cancelled = false; session.inProgress = true; diff --git a/src/core/PolkitListener.hpp b/src/core/PolkitListener.hpp index fae981b..2e9f55a 100644 --- a/src/core/PolkitListener.hpp +++ b/src/core/PolkitListener.hpp @@ -34,6 +34,7 @@ class CPolkitListener : public PolkitQt1::Agent::Listener { struct { bool inProgress = false, cancelled = false, gainedAuth = false; QString cookie, message, iconName, actionId; + PolkitQt1::Details details; PolkitQt1::Agent::AsyncResult* result = nullptr; PolkitQt1::Identity selectedUser; PolkitQt1::Agent::Session* session = nullptr;