Skip to content

Commit e87903d

Browse files
committed
service/polkit: add service module to write PolKit agents
1 parent 3e2ce40 commit e87903d

File tree

22 files changed

+1590
-1
lines changed

22 files changed

+1590
-1
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ jobs:
5353
libxcb \
5454
libpipewire \
5555
cli11 \
56+
polkit \
5657
jemalloc
5758
5859
- name: Build

BUILD.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,13 @@ To disable: `-DSERVICE_PAM=OFF`
192192

193193
Dependencies: `pam`
194194

195+
### Polkit
196+
This feature enables creating Polkit agents that can prompt user for authentication.
197+
198+
To disable: `-DSERVICE_POLKIT=OFF`
199+
200+
Dependencies: `polkit`, `glib`
201+
195202
### Hyprland
196203
This feature enables hyprland specific integrations. It requires wayland support
197204
but has no extra dependencies.

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ boption(SERVICE_STATUS_NOTIFIER "System Tray" ON)
6767
boption(SERVICE_PIPEWIRE "PipeWire" ON)
6868
boption(SERVICE_MPRIS "Mpris" ON)
6969
boption(SERVICE_PAM "Pam" ON)
70+
boption(SERVICE_POLKIT "Polkit" ON)
7071
boption(SERVICE_GREETD "Greetd" ON)
7172
boption(SERVICE_UPOWER "UPower" ON)
7273
boption(SERVICE_NOTIFICATIONS "Notifications" ON)

default.nix

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
libgbm ? null,
2222
pipewire,
2323
pam,
24+
polkit,
25+
glib,
2426

2527
gitRev ? (let
2628
headExists = builtins.pathExists ./.git/HEAD;
@@ -43,6 +45,7 @@
4345
withPam ? true,
4446
withHyprland ? true,
4547
withI3 ? true,
48+
withPolkit ? true,
4649
}: let
4750
unwrapped = stdenv.mkDerivation {
4851
pname = "quickshell${lib.optionalString debug "-debug"}";
@@ -76,7 +79,8 @@
7679
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm ]
7780
++ lib.optional withX11 xorg.libxcb
7881
++ lib.optional withPam pam
79-
++ lib.optional withPipewire pipewire;
82+
++ lib.optional withPipewire pipewire
83+
++ lib.optionals withPolkit [ polkit glib ];
8084

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

@@ -91,6 +95,7 @@
9195
(lib.cmakeBool "SCREENCOPY" (libgbm != null))
9296
(lib.cmakeBool "SERVICE_PIPEWIRE" withPipewire)
9397
(lib.cmakeBool "SERVICE_PAM" withPam)
98+
(lib.cmakeBool "SERVICE_POLKIT" withPolkit)
9499
(lib.cmakeBool "HYPRLAND" withHyprland)
95100
(lib.cmakeBool "I3" withI3)
96101
];

quickshell.scm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
libxcb
4343
libxkbcommon
4444
linux-pam
45+
polkit
4546
mesa
4647
pipewire
4748
qtbase

src/services/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ if (SERVICE_PAM)
1414
add_subdirectory(pam)
1515
endif()
1616

17+
if (SERVICE_POLKIT)
18+
add_subdirectory(polkit)
19+
endif()
20+
1721
if (SERVICE_GREETD)
1822
add_subdirectory(greetd)
1923
endif()

src/services/polkit/CMakeLists.txt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
find_package(PkgConfig REQUIRED)
2+
pkg_check_modules(glib REQUIRED IMPORTED_TARGET glib-2.0>=2.36)
3+
pkg_check_modules(gobject REQUIRED IMPORTED_TARGET gobject-2.0)
4+
pkg_check_modules(polkit_agent REQUIRED IMPORTED_TARGET polkit-agent-1)
5+
pkg_check_modules(polkit REQUIRED IMPORTED_TARGET polkit-gobject-1)
6+
7+
qt_add_library(quickshell-service-polkit STATIC
8+
agentimpl.cpp
9+
flow.cpp
10+
identity.cpp
11+
listener.cpp
12+
session.cpp
13+
qml.cpp
14+
)
15+
16+
qt_add_qml_module(quickshell-service-polkit
17+
URI Quickshell.Services.Polkit
18+
VERSION 0.1
19+
DEPENDENCIES QtQml
20+
)
21+
22+
install_qml_module(quickshell-service-polkit)
23+
24+
target_link_libraries(quickshell-service-polkit PRIVATE
25+
Qt::Qml
26+
Qt::Quick
27+
PkgConfig::glib
28+
PkgConfig::gobject
29+
PkgConfig::polkit_agent
30+
PkgConfig::polkit
31+
)
32+
33+
qs_module_pch(quickshell-service-polkit)
34+
35+
target_link_libraries(quickshell PRIVATE quickshell-service-polkitplugin)

src/services/polkit/agentimpl.cpp

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
#include "agentimpl.hpp"
2+
#include <algorithm>
3+
#include <utility>
4+
5+
#include <qlist.h>
6+
#include <qloggingcategory.h>
7+
#include <qobject.h>
8+
#include <qtmetamacros.h>
9+
10+
#include "../../core/generation.hpp"
11+
#include "../../core/logcat.hpp"
12+
#include "gobjectref.hpp"
13+
#include "listener.hpp"
14+
#include "qml.hpp"
15+
16+
namespace {
17+
QS_LOGGING_CATEGORY(logPolkit, "quickshell.service.polkit");
18+
}
19+
20+
namespace qs::service::polkit {
21+
PolkitAgentImpl* PolkitAgentImpl::instance = nullptr;
22+
23+
PolkitAgentImpl::PolkitAgentImpl(PolkitAgent* agent)
24+
: QObject(nullptr)
25+
, listener(qs_polkit_agent_new(this), G_OBJECT_NO_REF)
26+
, qmlAgent(agent)
27+
, path(this->qmlAgent->path()) {
28+
auto utf8Path = this->path.toUtf8();
29+
qs_polkit_agent_register(this->listener.get(), utf8Path.constData());
30+
}
31+
32+
PolkitAgentImpl::~PolkitAgentImpl() { this->cancelAllRequests("PolkitAgent is being destroyed"); }
33+
34+
void PolkitAgentImpl::cancelAllRequests(const QString& reason) {
35+
for (; !this->queuedRequests.empty(); this->queuedRequests.pop_back()) {
36+
AuthRequest* req = this->queuedRequests.back();
37+
qCDebug(logPolkit) << "destroying queued authentication request for action" << req->actionId;
38+
req->cancel(reason);
39+
delete req;
40+
}
41+
42+
auto* flow = this->bActiveFlow.value();
43+
if (flow) {
44+
flow->cancelAuthenticationRequest();
45+
flow->deleteLater();
46+
}
47+
48+
if (this->bIsRegistered.value()) qs_polkit_agent_unregister(this->listener.get());
49+
}
50+
51+
PolkitAgentImpl* PolkitAgentImpl::tryGetOrCreate(PolkitAgent* agent) {
52+
if (instance == nullptr) instance = new PolkitAgentImpl(agent);
53+
if (instance->qmlAgent == agent) return instance;
54+
return nullptr;
55+
}
56+
57+
PolkitAgentImpl* PolkitAgentImpl::tryGet(const PolkitAgent* agent) {
58+
if (instance == nullptr) return nullptr;
59+
if (instance->qmlAgent == agent) return instance;
60+
return nullptr;
61+
}
62+
63+
PolkitAgentImpl* PolkitAgentImpl::tryTakeover(PolkitAgent* agent) {
64+
if (instance == nullptr) return nullptr;
65+
66+
auto* prevGen = EngineGeneration::findObjectGeneration(instance->qmlAgent);
67+
auto* myGen = EngineGeneration::findObjectGeneration(agent);
68+
if (prevGen == myGen) return nullptr;
69+
70+
qCDebug(logPolkit) << "taking over listener from previous generation";
71+
instance->qmlAgent = agent;
72+
73+
return instance;
74+
}
75+
76+
void PolkitAgentImpl::onEndOfQmlAgent(PolkitAgent* agent) {
77+
if (instance != nullptr && instance->qmlAgent == agent) {
78+
delete instance;
79+
instance = nullptr;
80+
}
81+
}
82+
83+
QBindable<AuthFlow*> PolkitAgentImpl::activeFlow() { return &this->bActiveFlow; }
84+
QBindable<bool> PolkitAgentImpl::isRegistered() { return &this->bIsRegistered; }
85+
86+
const QString& PolkitAgentImpl::getPath() const { return this->path; }
87+
88+
void PolkitAgentImpl::setPath(const QString& path) {
89+
if (this->path == path) return;
90+
91+
this->path = path;
92+
auto utf8Path = path.toUtf8();
93+
94+
this->cancelAllRequests("PolkitAgent path changed");
95+
qs_polkit_agent_unregister(this->listener.get());
96+
this->bIsRegistered = false;
97+
98+
qs_polkit_agent_register(this->listener.get(), utf8Path.constData());
99+
}
100+
101+
void PolkitAgentImpl::registerComplete(bool success) {
102+
if (success) this->bIsRegistered = true;
103+
else qCWarning(logPolkit) << "failed to register listener on path" << this->qmlAgent->path();
104+
}
105+
106+
void PolkitAgentImpl::initiateAuthentication(AuthRequest* request) {
107+
qCDebug(logPolkit) << "incoming authentication request for action" << request->actionId;
108+
109+
this->queuedRequests.emplace_back(request);
110+
111+
if (this->queuedRequests.size() == 1) {
112+
this->activateAuthenticationRequest();
113+
}
114+
}
115+
116+
void PolkitAgentImpl::cancelAuthentication(AuthRequest* request) {
117+
qCDebug(logPolkit) << "cancelling authentication request from agent";
118+
119+
auto* flow = this->bActiveFlow.value();
120+
if (flow && flow->authRequest() == request) {
121+
flow->cancelFromAgent();
122+
} else if (auto it = std::ranges::find(this->queuedRequests, request);
123+
it != this->queuedRequests.end())
124+
{
125+
qCDebug(logPolkit) << "removing queued authentication request for action" << (*it)->actionId;
126+
(*it)->cancel("Authentication request was cancelled");
127+
delete (*it);
128+
this->queuedRequests.erase(it);
129+
} else {
130+
qCWarning(logPolkit) << "the cancelled request was not found in the queue.";
131+
}
132+
}
133+
134+
void PolkitAgentImpl::activateAuthenticationRequest() {
135+
if (this->queuedRequests.empty()) return;
136+
137+
AuthRequest* req = this->queuedRequests.front();
138+
this->queuedRequests.pop_front();
139+
qCDebug(logPolkit) << "activating authentication request for action" << req->actionId
140+
<< ", cookie: " << req->cookie;
141+
142+
QList<Identity*> identities;
143+
for (auto& identity: req->identities) {
144+
auto* obj = Identity::fromPolkitIdentity(identity);
145+
if (obj) identities.append(obj);
146+
}
147+
if (identities.isEmpty()) {
148+
qCWarning(logPolkit
149+
) << "no supported identities available for authentication request, cancelling.";
150+
req->cancel("Error requesting authentication: no supported identities available.");
151+
delete req;
152+
return;
153+
}
154+
155+
this->bActiveFlow = new AuthFlow(req, std::move(identities));
156+
157+
QObject::connect(
158+
this->bActiveFlow.value(),
159+
&AuthFlow::isCompletedChanged,
160+
this,
161+
&PolkitAgentImpl::finishAuthenticationRequest
162+
);
163+
164+
emit this->qmlAgent->isActiveChanged();
165+
emit this->qmlAgent->flowChanged();
166+
emit this->qmlAgent->authenticationRequestStarted();
167+
}
168+
169+
void PolkitAgentImpl::finishAuthenticationRequest() {
170+
if (!this->bActiveFlow.value()) return;
171+
172+
qCDebug(logPolkit) << "finishing authentication request for action"
173+
<< this->bActiveFlow.value()->actionId();
174+
175+
this->bActiveFlow.value()->deleteLater();
176+
177+
if (!this->queuedRequests.empty()) {
178+
this->activateAuthenticationRequest();
179+
} else {
180+
this->bActiveFlow = nullptr;
181+
}
182+
}
183+
} // namespace qs::service::polkit

src/services/polkit/agentimpl.hpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#pragma once
2+
3+
#include <deque>
4+
5+
#include <qobject.h>
6+
#include <qproperty.h>
7+
8+
#include "flow.hpp"
9+
#include "gobjectref.hpp"
10+
#include "listener.hpp"
11+
12+
namespace qs::service::polkit {
13+
class PolkitAgent;
14+
15+
class PolkitAgentImpl
16+
: public QObject
17+
, public ListenerCb {
18+
Q_OBJECT;
19+
Q_DISABLE_COPY_MOVE(PolkitAgentImpl);
20+
21+
public:
22+
~PolkitAgentImpl() override;
23+
24+
static PolkitAgentImpl* tryGetOrCreate(PolkitAgent* agent);
25+
static PolkitAgentImpl* tryGet(const PolkitAgent* agent);
26+
static PolkitAgentImpl* tryTakeover(PolkitAgent* agent);
27+
static void onEndOfQmlAgent(PolkitAgent* agent);
28+
29+
[[nodiscard]] QBindable<AuthFlow*> activeFlow();
30+
[[nodiscard]] QBindable<bool> isRegistered();
31+
32+
[[nodiscard]] const QString& getPath() const;
33+
void setPath(const QString& path);
34+
35+
void initiateAuthentication(AuthRequest* request) override;
36+
void cancelAuthentication(AuthRequest* request) override;
37+
void registerComplete(bool success) override;
38+
39+
void cancelAllRequests(const QString& reason);
40+
41+
signals:
42+
void activeFlowChanged();
43+
void isRegisteredChanged();
44+
45+
private:
46+
PolkitAgentImpl(PolkitAgent* agent);
47+
48+
static PolkitAgentImpl* instance;
49+
50+
/// Start handling of the next authentication request in the queue.
51+
void activateAuthenticationRequest();
52+
/// Finalize and remove the current authentication request.
53+
void finishAuthenticationRequest();
54+
55+
GObjectRef<QsPolkitAgent> listener;
56+
PolkitAgent* qmlAgent = nullptr;
57+
QString path;
58+
59+
std::deque<AuthRequest*> queuedRequests;
60+
61+
Q_OBJECT_BINDABLE_PROPERTY(
62+
PolkitAgentImpl,
63+
AuthFlow*,
64+
bActiveFlow,
65+
&PolkitAgentImpl::activeFlowChanged
66+
);
67+
Q_OBJECT_BINDABLE_PROPERTY(
68+
PolkitAgentImpl,
69+
bool,
70+
bIsRegistered,
71+
&PolkitAgentImpl::isRegisteredChanged
72+
);
73+
};
74+
} // namespace qs::service::polkit

0 commit comments

Comments
 (0)