mirror of
https://github.com/hyprwm/hyprpolkitagent.git
synced 2026-05-09 07:28:10 +02:00
qml: major foundation design updates
This commit is contained in:
parent
7e4054410f
commit
1a845a5124
6 changed files with 241 additions and 46 deletions
21
.gitignore
vendored
21
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
226
qml/main.qml
226
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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue