qml: major foundation design updates

This commit is contained in:
j4kuuu 2026-01-24 16:42:32 +01:00
parent 7e4054410f
commit 1a845a5124
6 changed files with 241 additions and 46 deletions

21
.gitignore vendored
View file

@ -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

View file

@ -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
}
}

View file

@ -3,6 +3,8 @@
#include "core/Agent.hpp"
#include "core/PolkitListener.hpp"
#include <QStringList>
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";
}

View file

@ -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);

View file

@ -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;

View file

@ -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;