Skip to content

Commit 55dc0b8

Browse files
Cu3PO42outfoxxed
authored andcommitted
service/polkit: add service module to write Polkit agents
1 parent 3e2ce40 commit 55dc0b8

File tree

23 files changed

+1551
-1
lines changed

23 files changed

+1551
-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)

changelog/next.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ set shell id.
1111

1212
## New Features
1313

14+
- Added support for creating Polkit agents.
1415
- Added support for creating wayland idle inhibitors.
1516
- Added support for wayland idle timeouts.
1617
- Added the ability to override Quickshell.cacheDir with a custom path.
@@ -22,3 +23,7 @@ set shell id.
2223
## Bug Fixes
2324

2425
- Fixed volume control breaking with pipewire pro audio mode.
26+
27+
## Packaging Changes
28+
29+
`glib` and `polkit` have been added as dependencies when compiling with polkit agent support.

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

src/services/polkit/agentimpl.hpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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* tryTakeoverOrCreate(PolkitAgent* agent);
27+
static void onEndOfQmlAgent(PolkitAgent* agent);
28+
29+
[[nodiscard]] QBindable<AuthFlow*> activeFlow() { return &this->bActiveFlow; };
30+
[[nodiscard]] QBindable<bool> isRegistered() { return &this->bIsRegistered; };
31+
32+
[[nodiscard]] const QString& getPath() const { return this->path; }
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+
// clang-format off
62+
Q_OBJECT_BINDABLE_PROPERTY(PolkitAgentImpl, AuthFlow*, bActiveFlow, &PolkitAgentImpl::activeFlowChanged);
63+
Q_OBJECT_BINDABLE_PROPERTY(PolkitAgentImpl, bool, bIsRegistered, &PolkitAgentImpl::isRegisteredChanged);
64+
// clang-format on
65+
};
66+
} // namespace qs::service::polkit

0 commit comments

Comments
 (0)