initial commit

This commit is contained in:
Vaxry 2024-10-14 17:55:01 +01:00
parent 2e810ff125
commit 2c97f97336
15 changed files with 788 additions and 0 deletions

65
.clang-format Normal file
View File

@ -0,0 +1,65 @@
---
Language: Cpp
BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: true
AlignConsecutiveAssignments: true
AlignEscapedNewlines: Right
AlignOperands: false
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: AfterColon
ColumnLimit: 180
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
IncludeBlocks: Preserve
IndentCaseLabels: true
IndentWidth: 4
PointerAlignment: Left
ReflowComments: false
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 4
UseTab: Never
AllowShortEnumsOnASingleLine: false
BraceWrapping:
AfterEnum: false
AlignConsecutiveDeclarations: AcrossEmptyLines
NamespaceIndentation: All

6
.gitignore vendored
View File

@ -30,3 +30,9 @@
*.exe *.exe
*.out *.out
*.app *.app
.vscode/
.cache/
build/
compile_commands.json

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "subprojects/sdbus-cpp"]
path = subprojects/sdbus-cpp
url = https://github.com/Kistler-Group/sdbus-cpp/

58
CMakeLists.txt Normal file
View File

@ -0,0 +1,58 @@
cmake_minimum_required(VERSION 3.16)
# Get version
file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW)
string(STRIP ${VER_RAW} VER)
project(hpa VERSION ${VER} LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2)
find_package(PkgConfig REQUIRED)
set(CMAKE_CXX_STANDARD 23)
pkg_check_modules(
deps
REQUIRED
IMPORTED_TARGET
hyprutils
polkit-agent-1
polkit-qt6-1)
qt_standard_project_setup(REQUIRES 6.5)
qt_add_executable(hyprpolkitagent
src/main.cpp
src/core/Agent.cpp
src/core/Agent.hpp
src/core/PolkitListener.hpp
src/core/PolkitListener.cpp
src/QMLIntegration.cpp
src/QMLIntegration.hpp
src/SigDaemon.hpp
src/SigDaemon.cpp
)
qt_add_qml_module(hyprpolkitagent
URI hpa
VERSION 1.0
QML_FILES
qml/main.qml
SOURCES
src/QMLIntegration.cpp
src/QMLIntegration.hpp
src/SigDaemon.hpp
src/SigDaemon.cpp
)
target_link_libraries(hyprpolkitagent
PRIVATE Qt6::Widgets Qt6::Quick Qt6::Gui Qt6::QuickControls2 PkgConfig::deps
)
include(GNUInstallDirs)
install(TARGETS hyprpolkitagent
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

1
VERSION Normal file
View File

@ -0,0 +1 @@
0.1.0

141
qml/main.qml Normal file
View File

@ -0,0 +1,141 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ApplicationWindow {
id: window
property var windowWidth: 550
property var windowHeight: 240
maximumWidth: windowWidth
maximumHeight: windowHeight
minimumWidth: windowWidth
minimumHeight: windowHeight
visible: true
FontMetrics {
id: fontMetrics
}
SystemPalette {
id: system
colorGroup: SystemPalette.Active
}
Item {
anchors.fill: parent
Keys.onEscapePressed: (e) => {
hpa.setResult("fail");
}
Keys.onReturnPressed: (e) => {
hpa.setResult("auth:" + passwordField.text);
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 4
Text {
color: Qt.darker(system.windowText, 0.8)
font.bold: true
font.pointSize: Math.round(fontMetrics.height * 1.05)
text: "Authenticating for " + hpa.getUser()
Layout.alignment: Qt.AlignHCenter
}
HSeparator {
Layout.topMargin: fontMetrics.height / 2
Layout.bottomMargin: fontMetrics.height / 2
}
Text {
color: system.windowText
text: hpa.getMessage()
}
TextField {
id: passwordField
Layout.topMargin: fontMetrics.height / 2
placeholderText: "Password"
Layout.alignment: Qt.AlignHCenter
hoverEnabled: true
persistentSelection: true
echoMode: TextInput.Password
focus: true
Connections {
target: hpa
onFocusField: () => {
passwordField.focus = true;
}
}
}
Text {
id: errorLabel
color: "red"
font.italic: true
Layout.topMargin: fontMetrics.height
text: ""
Layout.alignment: Qt.AlignHCenter
Connections {
target: hpa
onSetErrorString: (e) => {
errorLabel.text = e;
}
}
}
Rectangle {
color: "transparent"
Layout.fillHeight: true
}
HSeparator {
Layout.topMargin: fontMetrics.height / 2
Layout.bottomMargin: fontMetrics.height / 2
}
RowLayout {
Layout.alignment: Qt.AlignRight
Layout.rightMargin: fontMetrics.height / 2
Button {
text: "Cancel"
onClicked: (e) => {
hpa.setResult("fail");
}
}
Button {
text: "Authenticate"
onClicked: (e) => {
hpa.setResult("auth:" + passwordField.text);
}
}
}
}
}
component Separator: Rectangle {
color: Qt.darker(window.palette.text, 1.5)
}
component HSeparator: Separator {
implicitHeight: 1
Layout.fillWidth: true
Layout.leftMargin: fontMetrics.height * 8
Layout.rightMargin: fontMetrics.height * 8
}
}

29
src/QMLIntegration.cpp Normal file
View File

@ -0,0 +1,29 @@
#include "QMLIntegration.hpp"
#include "core/Agent.hpp"
#include "core/PolkitListener.hpp"
void CQMLIntegration::onExit() {
g_pAgent->submitResultThreadSafe(result.toStdString());
}
void CQMLIntegration::setResult(QString str) {
result = str;
g_pAgent->submitResultThreadSafe(result.toStdString());
}
QString CQMLIntegration::getMessage() {
return g_pAgent->listener.session.inProgress ? g_pAgent->listener.session.message : "An application is requesting authentication.";
}
QString CQMLIntegration::getUser() {
return g_pAgent->listener.session.inProgress ? g_pAgent->listener.session.selectedUser.toString() : "an unknown user";
}
void CQMLIntegration::setError(QString str) {
emit setErrorString(str);
}
void CQMLIntegration::focus() {
emit focusField();
}

36
src/QMLIntegration.hpp Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#include <QObject>
#include <QQmlApplicationEngine>
#include <QPixmap>
#include <QIcon>
class CQMLIntegration : public QObject {
Q_OBJECT;
Q_PROPERTY(QString errorText MEMBER errorText);
public:
explicit CQMLIntegration(QObject* parent = nullptr) : QObject(parent) {
;
}
virtual ~CQMLIntegration() {
;
}
void setError(QString str);
void focus();
QString result = "fail", errorText = "";
Q_INVOKABLE QString getMessage();
Q_INVOKABLE QString getUser();
Q_INVOKABLE void setResult(QString str);
public slots:
void onExit();
signals:
void setErrorString(QString err);
void focusField();
};

81
src/SigDaemon.cpp Normal file
View File

@ -0,0 +1,81 @@
#include "SigDaemon.hpp"
#include "core/Agent.hpp"
#include <print>
#include <sys/socket.h>
#include <sys/signal.h>
static int sighupFd[2];
static int sigtermFd[2];
static int sigintFd[2];
//
void CSigDaemon::onSignal(int signo) {
char a = 1;
if (signo == SIGHUP)
::write(sighupFd[0], &a, sizeof(a));
else if (signo == SIGINT)
::write(sigintFd[0], &a, sizeof(a));
else if (signo == SIGTERM)
::write(sigtermFd[0], &a, sizeof(a));
}
CSigDaemon::CSigDaemon(QObject* parent) : QObject(parent) {
if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sighupFd))
qFatal("Couldn't create HUP socketpair");
if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigtermFd))
qFatal("Couldn't create TERM socketpair");
if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigintFd))
qFatal("Couldn't create INT socketpair");
snHup = new QSocketNotifier(sighupFd[1], QSocketNotifier::Read, this);
connect(snHup, SIGNAL(activated(QSocketDescriptor)), this, SLOT(handleSigHup()));
snTerm = new QSocketNotifier(sigtermFd[1], QSocketNotifier::Read, this);
connect(snTerm, SIGNAL(activated(QSocketDescriptor)), this, SLOT(handleSigTerm()));
snInt = new QSocketNotifier(sigintFd[1], QSocketNotifier::Read, this);
connect(snInt, SIGNAL(activated(QSocketDescriptor)), this, SLOT(handleSigInt()));
struct sigaction sa;
sa.sa_handler = onSignal;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_flags |= SA_RESTART;
if (sigaction(SIGHUP, &sa, 0))
std::print(stderr, "sigaction for hup failed\n");
if (sigaction(SIGTERM, &sa, 0))
std::print(stderr, "sigaction for term failed\n");
if (sigaction(SIGINT, &sa, 0))
std::print(stderr, "sigaction for int failed\n");
}
void CSigDaemon::handleSigHup() {
std::print("> signal received: SIGHUP\n");
snHup->setEnabled(false);
char tmp;
::read(sighupFd[1], &tmp, sizeof(tmp));
g_pAgent->resetAuthState();
snHup->setEnabled(true);
}
void CSigDaemon::handleSigInt() {
std::print("> signal received: SIGINT\n");
snInt->setEnabled(false);
char tmp;
::read(sigintFd[1], &tmp, sizeof(tmp));
g_pAgent->resetAuthState();
snInt->setEnabled(true);
exit(0);
}
void CSigDaemon::handleSigTerm() {
std::print("> signal received: SIGTERM\n");
snTerm->setEnabled(false);
char tmp;
::read(sigtermFd[1], &tmp, sizeof(tmp));
g_pAgent->resetAuthState();
snTerm->setEnabled(true);
}

23
src/SigDaemon.hpp Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include <QApplication>
#include <QObject>
#include <QSocketNotifier>
class CSigDaemon : public QObject {
Q_OBJECT;
public:
CSigDaemon(QObject* parent = nullptr);
static void onSignal(int signo);
public slots:
void handleSigHup();
void handleSigTerm();
void handleSigInt();
private:
QSocketNotifier* snHup = nullptr;
QSocketNotifier* snTerm = nullptr;
QSocketNotifier* snInt = nullptr;
};

96
src/core/Agent.cpp Normal file
View File

@ -0,0 +1,96 @@
#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE 1
#include <polkitagent/polkitagent.h>
#include <print>
#include "Agent.hpp"
#include "../QMLIntegration.hpp"
#include "../SigDaemon.hpp"
CAgent::CAgent() {
;
}
CAgent::~CAgent() {
;
}
bool CAgent::start() {
sessionSubject = makeShared<PolkitQt1::UnixSessionSubject>(getpid());
listener.registerListener(*sessionSubject, "/org/hyprland/PolicyKit1/AuthenticationAgent");
int argc = 1;
char* argv = (char*)"hyprpolkitagent";
QApplication app(argc, &argv);
sigDaemon = makeShared<CSigDaemon>();
app.setApplicationName("Hyprland Polkit Agent");
QGuiApplication::setQuitOnLastWindowClosed(false);
app.exec();
return true;
}
void CAgent::resetAuthState() {
if (authState.authing) {
authState.authing = false;
authState.qmlEngine.reset();
authState.qmlIntegration.reset();
}
}
void CAgent::initAuthPrompt() {
resetAuthState();
if (!listener.session.inProgress) {
std::print(stderr, "INTERNAL ERROR: Spawning qml prompt but session isn't in progress\n");
return;
}
std::print("Spawning qml prompt\n");
authState.qmlEngine.reset();
authState.authing = true;
authState.qmlIntegration = makeShared<CQMLIntegration>();
if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE"))
QQuickStyle::setStyle("org.kde.desktop");
authState.qmlEngine = makeShared<QQmlApplicationEngine>();
authState.qmlEngine->rootContext()->setContextProperty("hpa", authState.qmlIntegration.get());
authState.qmlEngine->load(QUrl{u"qrc:/qt/qml/hpa/qml/main.qml"_qs});
authState.qmlIntegration->focusField();
}
bool CAgent::resultReady() {
std::lock_guard<std::mutex> lg(lastAuthResult.resultMutex);
return !lastAuthResult.used;
}
void CAgent::submitResultThreadSafe(const std::string& result) {
std::lock_guard<std::mutex> lg(lastAuthResult.resultMutex);
lastAuthResult.used = false;
lastAuthResult.result = result;
const bool PASS = result.starts_with("auth:");
std::print("Got result from qml: {}\n", PASS ? "auth:**PASSWORD**" : result);
if (PASS)
listener.submitPassword(result.substr(result.find(":") + 1).c_str());
else
listener.cancelPending();
}
void CAgent::setAuthError(const QString& err) {
if (!authState.qmlIntegration)
return;
authState.qmlIntegration->setErrorString(err);
}

53
src/core/Agent.hpp Normal file
View File

@ -0,0 +1,53 @@
#pragma once
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickStyle>
#include <QScreen>
#include "PolkitListener.hpp"
#include <polkitqt1-subject.h>
#include <hyprutils/memory/WeakPtr.hpp>
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer
class CQMLIntegration;
class CSigDaemon;
class CAgent {
public:
CAgent();
~CAgent();
void submitResultThreadSafe(const std::string& result);
void resetAuthState();
bool start();
void initAuthPrompt();
void setAuthError(const QString& err);
private:
struct {
bool authing = false;
SP<QQmlApplicationEngine> qmlEngine;
SP<CQMLIntegration> qmlIntegration;
} authState;
struct {
std::mutex resultMutex;
std::string result;
bool used = true;
} lastAuthResult;
CPolkitListener listener;
SP<CSigDaemon> sigDaemon;
SP<PolkitQt1::UnixSessionSubject> sessionSubject;
bool resultReady();
friend class CQMLIntegration;
};
inline std::unique_ptr<CAgent> g_pAgent;

142
src/core/PolkitListener.cpp Normal file
View File

@ -0,0 +1,142 @@
#include <QDebug>
#include <QInputDialog>
#include "PolkitListener.hpp"
#include "Agent.hpp"
#include <polkitqt1-agent-session.h>
#include <print>
using namespace PolkitQt1::Agent;
CPolkitListener::CPolkitListener(QObject* parent) : Listener(parent) {
;
}
void CPolkitListener::initiateAuthentication(const QString& actionId, const QString& message, const QString& iconName, const PolkitQt1::Details& details, const QString& cookie,
const PolkitQt1::Identity::List& identities, AsyncResult* result) {
std::print("> New authentication session\n");
if (session.inProgress) {
result->setError("Authentication in progress");
result->setCompleted();
std::print("> REJECTING: Another session present\n");
return;
}
if (identities.isEmpty()) {
result->setError("No identities, this is a problem with your system configuration.");
result->setCompleted();
std::print("> REJECTING: No idents\n");
return;
}
session.selectedUser = identities.at(0);
session.cookie = cookie;
session.result = result;
session.actionId = actionId;
session.message = message;
session.iconName = iconName;
session.gainedAuth = false;
session.cancelled = false;
session.inProgress = true;
g_pAgent->initAuthPrompt();
reattempt();
}
void CPolkitListener::reattempt() {
session.cancelled = false;
session.session = new Session(session.selectedUser, session.cookie, session.result);
connect(session.session, SIGNAL(request(QString, bool)), this, SLOT(request(QString, bool)));
connect(session.session, SIGNAL(completed(bool)), this, SLOT(completed(bool)));
connect(session.session, SIGNAL(showError(QString)), this, SLOT(showError(QString)));
connect(session.session, SIGNAL(showInfo(QString)), this, SLOT(showInfo(QString)));
session.session->initiate();
}
bool CPolkitListener::initiateAuthenticationFinish() {
std::print("> initiateAuthenticationFinish()\n");
return true;
}
void CPolkitListener::cancelAuthentication() {
std::print("> cancelAuthentication()\n");
session.cancelled = true;
finishAuth();
}
void CPolkitListener::request(const QString& request, bool echo) {
std::print("> PKS request: {} echo: {}\n", request.toStdString(), echo);
}
void CPolkitListener::completed(bool gainedAuthorization) {
std::print("> PKS completed: {}\n", gainedAuthorization ? "Auth successful" : "Auth unsuccessful");
session.gainedAuth = gainedAuthorization;
if (!gainedAuthorization)
g_pAgent->setAuthError("Authentication failed");
finishAuth();
}
void CPolkitListener::showError(const QString& text) {
std::print("> PKS showError: {}\n", text.toStdString());
g_pAgent->setAuthError(text);
}
void CPolkitListener::showInfo(const QString& text) {
std::print("> PKS showInfo: {}\n", text.toStdString());
}
void CPolkitListener::finishAuth() {
if (!session.inProgress) {
std::print("> finishAuth: ODD. !session.inProgress\n");
return;
}
if (!session.gainedAuth && !session.cancelled) {
std::print("> finishAuth: Did not gain auth. Reattempting.\n");
session.session->deleteLater();
reattempt();
return;
}
std::print("> finishAuth: Gained auth, cleaning up.\n");
session.inProgress = false;
if (session.session) {
session.session->result()->setCompleted();
session.session->deleteLater();
} else
session.result->setCompleted();
g_pAgent->resetAuthState();
}
void CPolkitListener::submitPassword(const QString& pass) {
if (!session.session)
return;
session.session->setResponse(pass);
}
void CPolkitListener::cancelPending() {
if (!session.session)
return;
session.session->cancel();
session.cancelled = true;
finishAuth();
}

View File

@ -0,0 +1,47 @@
#pragma once
#include <QObject>
#include <QString>
#include <polkitqt1-agent-listener.h>
#include <polkitqt1-identity.h>
#include <polkitqt1-details.h>
#include <polkitqt1-agent-session.h>
class CPolkitListener : public PolkitQt1::Agent::Listener {
Q_OBJECT;
Q_DISABLE_COPY(CPolkitListener);
public:
CPolkitListener(QObject* parent = nullptr);
~CPolkitListener() override {};
void submitPassword(const QString& pass);
void cancelPending();
public Q_SLOTS:
void initiateAuthentication(const QString& actionId, const QString& message, const QString& iconName, const PolkitQt1::Details& details, const QString& cookie,
const PolkitQt1::Identity::List& identities, PolkitQt1::Agent::AsyncResult* result) override;
bool initiateAuthenticationFinish() override;
void cancelAuthentication() override;
void request(const QString& request, bool echo);
void completed(bool gainedAuthorization);
void showError(const QString& text);
void showInfo(const QString& text);
private:
struct {
bool inProgress = false, cancelled = false, gainedAuth = false;
QString cookie, message, iconName, actionId;
PolkitQt1::Agent::AsyncResult* result = nullptr;
PolkitQt1::Identity selectedUser;
PolkitQt1::Agent::Session* session = nullptr;
} session;
void reattempt();
void finishAuth();
friend class CAgent;
friend class CQMLIntegration;
};

7
src/main.cpp Normal file
View File

@ -0,0 +1,7 @@
#include "core/Agent.hpp"
int main(int argc, char* argv[]) {
g_pAgent = std::make_unique<CAgent>();
return g_pAgent->start() == false ? 1 : 0;
}