Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ jobs:
libxcb \
libpipewire \
cli11 \
polkit \
jemalloc

- name: Build
Expand Down
7 changes: 7 additions & 0 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,13 @@ To disable: `-DSERVICE_PAM=OFF`

Dependencies: `pam`

### Polkit
This feature enables creating Polkit agents that can prompt user for authentication.

To disable: `-DSERVICE_POLKIT=OFF`

Dependencies: `polkit`, `glib`

### Hyprland
This feature enables hyprland specific integrations. It requires wayland support
but has no extra dependencies.
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ boption(SERVICE_STATUS_NOTIFIER "System Tray" ON)
boption(SERVICE_PIPEWIRE "PipeWire" ON)
boption(SERVICE_MPRIS "Mpris" ON)
boption(SERVICE_PAM "Pam" ON)
boption(SERVICE_POLKIT "Polkit" ON)
boption(SERVICE_GREETD "Greetd" ON)
boption(SERVICE_UPOWER "UPower" ON)
boption(SERVICE_NOTIFICATIONS "Notifications" ON)
Expand Down
7 changes: 6 additions & 1 deletion default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
libgbm ? null,
pipewire,
pam,
polkit,
glib,

gitRev ? (let
headExists = builtins.pathExists ./.git/HEAD;
Expand All @@ -43,6 +45,7 @@
withPam ? true,
withHyprland ? true,
withI3 ? true,
withPolkit ? true,
}: let
unwrapped = stdenv.mkDerivation {
pname = "quickshell${lib.optionalString debug "-debug"}";
Expand Down Expand Up @@ -76,7 +79,8 @@
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm ]
++ lib.optional withX11 xorg.libxcb
++ lib.optional withPam pam
++ lib.optional withPipewire pipewire;
++ lib.optional withPipewire pipewire
++ lib.optionals withPolkit [ polkit glib ];

cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";

Expand All @@ -91,6 +95,7 @@
(lib.cmakeBool "SCREENCOPY" (libgbm != null))
(lib.cmakeBool "SERVICE_PIPEWIRE" withPipewire)
(lib.cmakeBool "SERVICE_PAM" withPam)
(lib.cmakeBool "SERVICE_POLKIT" withPolkit)
(lib.cmakeBool "HYPRLAND" withHyprland)
(lib.cmakeBool "I3" withI3)
];
Expand Down
1 change: 1 addition & 0 deletions quickshell.scm
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
libxcb
libxkbcommon
linux-pam
polkit
mesa
pipewire
qtbase
Expand Down
4 changes: 4 additions & 0 deletions src/services/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ if (SERVICE_PAM)
add_subdirectory(pam)
endif()

if (SERVICE_POLKIT)
add_subdirectory(polkit)
endif()

if (SERVICE_GREETD)
add_subdirectory(greetd)
endif()
Expand Down
35 changes: 35 additions & 0 deletions src/services/polkit/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
find_package(PkgConfig REQUIRED)
pkg_check_modules(glib REQUIRED IMPORTED_TARGET glib-2.0>=2.36)
pkg_check_modules(gobject REQUIRED IMPORTED_TARGET gobject-2.0)
pkg_check_modules(polkit_agent REQUIRED IMPORTED_TARGET polkit-agent-1)
pkg_check_modules(polkit REQUIRED IMPORTED_TARGET polkit-gobject-1)

qt_add_library(quickshell-service-polkit STATIC
agentimpl.cpp
flow.cpp
identity.cpp
listener.cpp
session.cpp
qml.cpp
)

qt_add_qml_module(quickshell-service-polkit
URI Quickshell.Services.Polkit
VERSION 0.1
DEPENDENCIES QtQml
)

install_qml_module(quickshell-service-polkit)

target_link_libraries(quickshell-service-polkit PRIVATE
Qt::Qml
Qt::Quick
PkgConfig::glib
PkgConfig::gobject
PkgConfig::polkit_agent
PkgConfig::polkit
)

qs_module_pch(quickshell-service-polkit)

target_link_libraries(quickshell PRIVATE quickshell-service-polkitplugin)
183 changes: 183 additions & 0 deletions src/services/polkit/agentimpl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#include "agentimpl.hpp"
#include <algorithm>
#include <utility>

#include <qlist.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qtmetamacros.h>

#include "../../core/generation.hpp"
#include "../../core/logcat.hpp"
#include "gobjectref.hpp"
#include "listener.hpp"
#include "qml.hpp"

namespace {
QS_LOGGING_CATEGORY(logPolkit, "quickshell.service.polkit");
}

namespace qs::service::polkit {
PolkitAgentImpl* PolkitAgentImpl::instance = nullptr;

PolkitAgentImpl::PolkitAgentImpl(PolkitAgent* agent)
: QObject(nullptr)
, listener(qs_polkit_agent_new(this), G_OBJECT_NO_REF)
, qmlAgent(agent)
, path(this->qmlAgent->path()) {
auto utf8Path = this->path.toUtf8();
qs_polkit_agent_register(this->listener.get(), utf8Path.constData());
}

PolkitAgentImpl::~PolkitAgentImpl() { this->cancelAllRequests("PolkitAgent is being destroyed"); }

void PolkitAgentImpl::cancelAllRequests(const QString& reason) {
for (; !this->queuedRequests.empty(); this->queuedRequests.pop_back()) {
AuthRequest* req = this->queuedRequests.back();
qCDebug(logPolkit) << "destroying queued authentication request for action" << req->actionId;
req->cancel(reason);
delete req;
}

auto* flow = this->bActiveFlow.value();
if (flow) {
flow->cancelAuthenticationRequest();
flow->deleteLater();
}

if (this->bIsRegistered.value()) qs_polkit_agent_unregister(this->listener.get());
}

PolkitAgentImpl* PolkitAgentImpl::tryGetOrCreate(PolkitAgent* agent) {
if (instance == nullptr) instance = new PolkitAgentImpl(agent);
if (instance->qmlAgent == agent) return instance;
return nullptr;
}

PolkitAgentImpl* PolkitAgentImpl::tryGet(const PolkitAgent* agent) {
if (instance == nullptr) return nullptr;
if (instance->qmlAgent == agent) return instance;
return nullptr;
}

PolkitAgentImpl* PolkitAgentImpl::tryTakeover(PolkitAgent* agent) {
if (instance == nullptr) return nullptr;

auto* prevGen = EngineGeneration::findObjectGeneration(instance->qmlAgent);
auto* myGen = EngineGeneration::findObjectGeneration(agent);
if (prevGen == myGen) return nullptr;

qCDebug(logPolkit) << "taking over listener from previous generation";
instance->qmlAgent = agent;

return instance;
}

void PolkitAgentImpl::onEndOfQmlAgent(PolkitAgent* agent) {
if (instance != nullptr && instance->qmlAgent == agent) {
delete instance;
instance = nullptr;
}
}

QBindable<AuthFlow*> PolkitAgentImpl::activeFlow() { return &this->bActiveFlow; }
QBindable<bool> PolkitAgentImpl::isRegistered() { return &this->bIsRegistered; }

const QString& PolkitAgentImpl::getPath() const { return this->path; }

void PolkitAgentImpl::setPath(const QString& path) {
if (this->path == path) return;

this->path = path;
auto utf8Path = path.toUtf8();

this->cancelAllRequests("PolkitAgent path changed");
qs_polkit_agent_unregister(this->listener.get());
this->bIsRegistered = false;

qs_polkit_agent_register(this->listener.get(), utf8Path.constData());
}

void PolkitAgentImpl::registerComplete(bool success) {
if (success) this->bIsRegistered = true;
else qCWarning(logPolkit) << "failed to register listener on path" << this->qmlAgent->path();
}

void PolkitAgentImpl::initiateAuthentication(AuthRequest* request) {
qCDebug(logPolkit) << "incoming authentication request for action" << request->actionId;

this->queuedRequests.emplace_back(request);

if (this->queuedRequests.size() == 1) {
this->activateAuthenticationRequest();
}
}

void PolkitAgentImpl::cancelAuthentication(AuthRequest* request) {
qCDebug(logPolkit) << "cancelling authentication request from agent";

auto* flow = this->bActiveFlow.value();
if (flow && flow->authRequest() == request) {
flow->cancelFromAgent();
} else if (auto it = std::ranges::find(this->queuedRequests, request);
it != this->queuedRequests.end())
{
qCDebug(logPolkit) << "removing queued authentication request for action" << (*it)->actionId;
(*it)->cancel("Authentication request was cancelled");
delete (*it);
this->queuedRequests.erase(it);
} else {
qCWarning(logPolkit) << "the cancelled request was not found in the queue.";
}
}

void PolkitAgentImpl::activateAuthenticationRequest() {
if (this->queuedRequests.empty()) return;

AuthRequest* req = this->queuedRequests.front();
this->queuedRequests.pop_front();
qCDebug(logPolkit) << "activating authentication request for action" << req->actionId
<< ", cookie: " << req->cookie;

QList<Identity*> identities;
for (auto& identity: req->identities) {
auto* obj = Identity::fromPolkitIdentity(identity);
if (obj) identities.append(obj);
}
if (identities.isEmpty()) {
qCWarning(logPolkit
) << "no supported identities available for authentication request, cancelling.";
req->cancel("Error requesting authentication: no supported identities available.");
delete req;
return;
}

this->bActiveFlow = new AuthFlow(req, std::move(identities));

QObject::connect(
this->bActiveFlow.value(),
&AuthFlow::isCompletedChanged,
this,
&PolkitAgentImpl::finishAuthenticationRequest
);

emit this->qmlAgent->isActiveChanged();
emit this->qmlAgent->flowChanged();
emit this->qmlAgent->authenticationRequestStarted();
}

void PolkitAgentImpl::finishAuthenticationRequest() {
if (!this->bActiveFlow.value()) return;

qCDebug(logPolkit) << "finishing authentication request for action"
<< this->bActiveFlow.value()->actionId();

this->bActiveFlow.value()->deleteLater();

if (!this->queuedRequests.empty()) {
this->activateAuthenticationRequest();
} else {
this->bActiveFlow = nullptr;
}
}
} // namespace qs::service::polkit
74 changes: 74 additions & 0 deletions src/services/polkit/agentimpl.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#pragma once

#include <deque>

#include <qobject.h>
#include <qproperty.h>

#include "flow.hpp"
#include "gobjectref.hpp"
#include "listener.hpp"

namespace qs::service::polkit {
class PolkitAgent;

class PolkitAgentImpl
: public QObject
, public ListenerCb {
Q_OBJECT;
Q_DISABLE_COPY_MOVE(PolkitAgentImpl);

public:
~PolkitAgentImpl() override;

static PolkitAgentImpl* tryGetOrCreate(PolkitAgent* agent);
static PolkitAgentImpl* tryGet(const PolkitAgent* agent);
static PolkitAgentImpl* tryTakeover(PolkitAgent* agent);
static void onEndOfQmlAgent(PolkitAgent* agent);

[[nodiscard]] QBindable<AuthFlow*> activeFlow();
[[nodiscard]] QBindable<bool> isRegistered();

[[nodiscard]] const QString& getPath() const;
void setPath(const QString& path);

void initiateAuthentication(AuthRequest* request) override;
void cancelAuthentication(AuthRequest* request) override;
void registerComplete(bool success) override;

void cancelAllRequests(const QString& reason);

signals:
void activeFlowChanged();
void isRegisteredChanged();

private:
PolkitAgentImpl(PolkitAgent* agent);

static PolkitAgentImpl* instance;

/// Start handling of the next authentication request in the queue.
void activateAuthenticationRequest();
/// Finalize and remove the current authentication request.
void finishAuthenticationRequest();

GObjectRef<QsPolkitAgent> listener;
PolkitAgent* qmlAgent = nullptr;
QString path;

std::deque<AuthRequest*> queuedRequests;

Q_OBJECT_BINDABLE_PROPERTY(
PolkitAgentImpl,
AuthFlow*,
bActiveFlow,
&PolkitAgentImpl::activeFlowChanged
);
Q_OBJECT_BINDABLE_PROPERTY(
PolkitAgentImpl,
bool,
bIsRegistered,
&PolkitAgentImpl::isRegisteredChanged
);
};
} // namespace qs::service::polkit
Loading