feat(privacy): list and remove apps allowed to control the computer

main
reionwong 1 week ago
parent 32e5fef464
commit 7d08ca2e45

@ -3,7 +3,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: cutefish-settings\n" "Project-Id-Version: cutefish-settings\n"
"Report-Msgid-Bugs-To: https://github.com/cutefishos\n" "Report-Msgid-Bugs-To: https://github.com/cutefishos\n"
"POT-Creation-Date: 2026-10-19 23:02+0800\n" "POT-Creation-Date: 2026-10-20 09:33+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
@ -1457,7 +1457,7 @@ msgstr ""
msgid "Peer" msgid "Peer"
msgstr "" msgstr ""
#: src/pages/network/vpn/vpneditor.cpp #: src/pages/network/vpn/vpneditor.cpp src/pages/privacy/privacypage.cpp
#: src/pages/shortcuts/shortcuteditdialog.cpp #: src/pages/shortcuts/shortcuteditdialog.cpp
msgid "Remove" msgid "Remove"
msgstr "" msgstr ""
@ -1470,7 +1470,7 @@ msgstr ""
msgid "Preshared key" msgid "Preshared key"
msgstr "" msgstr ""
#. TRANSLATORS: a placeholder: the stored preshared key stays unless a new one is typed. #. TRANSLATORS: a placeholder: the stored key stays unless a new one is typed.
#: src/pages/network/vpn/vpneditor.cpp #: src/pages/network/vpn/vpneditor.cpp
msgid "Kept" msgid "Kept"
msgstr "" msgstr ""
@ -1687,6 +1687,26 @@ msgstr ""
msgid "Prevent automatic sleeping when the display is off" msgid "Prevent automatic sleeping when the display is off"
msgstr "" msgstr ""
#: src/pages/privacy/privacypage.cpp src/window/pagelist.cpp
msgid "Privacy"
msgstr ""
#: src/pages/privacy/privacypage.cpp
msgid "Computer control"
msgstr ""
#: src/pages/privacy/privacypage.cpp
msgid "Computer control is managed by the Cutefish compositor, which is not running."
msgstr ""
#: src/pages/privacy/privacypage.cpp
msgid "No apps are allowed to control this computer without asking."
msgstr ""
#: src/pages/privacy/privacypage.cpp
msgid "An app you always allow can let its agent see the screen and use the pointer and keyboard without asking first. The Stop button in the status bar still shows while it is in control."
msgstr ""
#: src/pages/shortcuts/shortcuteditdialog.cpp #: src/pages/shortcuts/shortcuteditdialog.cpp
msgid "Press the new shortcut…" msgid "Press the new shortcut…"
msgstr "" msgstr ""
@ -2259,7 +2279,7 @@ msgstr ""
msgid "Add Wallpaper Folder" msgid "Add Wallpaper Folder"
msgstr "" msgstr ""
#: src/widgets/searchfield.cpp #: src/widgets/searchfield.cpp src/window/sidebar.cpp
msgid "Search" msgid "Search"
msgstr "" msgstr ""
@ -2347,6 +2367,10 @@ msgstr ""
msgid "web browser, email client, terminal" msgid "web browser, email client, terminal"
msgstr "" msgstr ""
#: src/window/pagelist.cpp
msgid "computer control, agents, allowed apps, permissions"
msgstr ""
#: src/window/pagelist.cpp #: src/window/pagelist.cpp
msgid "charge, charging limit, battery health, capacity, history, percentage" msgid "charge, charging limit, battery health, capacity, history, percentage"
msgstr "" msgstr ""

@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: BSD-3-Clause
#include "pages/privacy/computercontrol.h"
#include <cstdio>
using namespace cutefish::base;
namespace settings {
static constexpr const char* kService = "org.cutefish.ComputerControl1";
static constexpr const char* kPath = "/org/cutefish/ComputerControl1";
static std::vector<ComputerControl::App> appsOf(const dbus::Reply& reply)
{
std::vector<ComputerControl::App> apps;
for (const dbus::VariantMap& entry : reply.value<std::vector<dbus::VariantMap>>().value_or(
std::vector<dbus::VariantMap>())) {
ComputerControl::App app;
if (const auto it = entry.find("path"); it != entry.end())
app.path = it->second.value<std::string>();
if (const auto it = entry.find("name"); it != entry.end())
app.name = it->second.value<std::string>();
if (const auto it = entry.find("app_id"); it != entry.end())
app.appId = it->second.value<std::string>();
if (!app.path.empty())
apps.push_back(std::move(app));
}
return apps;
}
ComputerControl::ComputerControl()
: m_control(dbus::Connection::session(), kService, kPath, kService)
{
dbus::Connection* bus = m_control.connection();
if (!bus)
return;
// Read at once, so the page has the list for its first frame.
if (const dbus::Reply reply = m_control.call("allowedApps"); !reply.isError()) {
m_apps = appsOf(reply);
m_available = true;
}
m_changedMatch = m_control.onSignal("changed", [this](const dbus::Message&) { reload(); });
m_watch = bus->watchName(kService, [this](const std::string& owner) {
if (!owner.empty()) {
reload();
} else if (m_available) {
m_available = false;
m_apps.clear();
changed.emit();
}
});
}
void ComputerControl::reload()
{
m_reload = m_control.callAsync("allowedApps", [this](const dbus::Reply& reply) {
if (reply.isError())
return;
m_apps = appsOf(reply);
m_available = true;
changed.emit();
});
}
void ComputerControl::forget(const std::string& path)
{
if (!m_available)
return;
m_forget = m_control.callAsync("forget", [](const dbus::Reply& reply) {
if (reply.isError())
fprintf(stderr, "Cannot forget the app: %s\n", reply.error().message.c_str());
}, path);
}
} // namespace settings

@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <string>
#include <vector>
#include "base/dbus.h"
#include "base/signal.h"
namespace settings {
// The compositor's org.cutefish.ComputerControl1: the apps whose agents may
// control the computer without asking.
class ComputerControl
{
public:
struct App
{
std::string path;
// The agent's name for itself, and its application's desktop entry.
std::string name;
std::string appId;
};
ComputerControl();
bool isAvailable() const { return m_available; }
const std::vector<App>& allowedApps() const { return m_apps; }
void forget(const std::string& path);
cutefish::base::Signal<> changed;
private:
void reload();
cutefish::base::dbus::Proxy m_control;
cutefish::base::dbus::NameWatch m_watch;
cutefish::base::dbus::Match m_changedMatch;
cutefish::base::dbus::PendingCall m_reload;
cutefish::base::dbus::PendingCall m_forget;
std::vector<App> m_apps;
bool m_available = false;
};
} // namespace settings

@ -0,0 +1,150 @@
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: BSD-3-Clause
#include <memory>
#include <string>
#include <vector>
#include "base/application_registry.h"
#include "base/desktop_entry.h"
#include "base/i18n.h"
#include "base/signal.h"
#include "pages/privacy/computercontrol.h"
#include "ui/button.h"
#include "ui/cell_layout.h"
#include "ui/icon.h"
#include "ui/label.h"
#include "ui/theme.h"
#include "widgets/card.h"
#include "widgets/listrow.h"
#include "widgets/metrics.h"
#include "widgets/sections.h"
#include "window/page.h"
#include "window/pagelist.h"
using namespace cutefish::ui;
using cutefish::base::ApplicationRegistry;
using cutefish::i18n::tr;
namespace settings {
namespace {
// A label in the theme's secondary text colour.
class SecondaryLabel : public Label
{
public:
using Label::Label;
protected:
void layout() override
{
setColor(Theme::current().disabledText);
Label::layout();
}
};
// An app allowed to control the computer: its name and icon over the program
// trusted, and Remove.
class AllowedAppRow : public ListRow
{
public:
AllowedAppRow(const ComputerControl::App& app, std::function<void()> remove)
{
setInteractive(false);
const auto entry = app.appId.empty() ? nullptr : ApplicationRegistry::instance().byId(app.appId);
std::string title = entry && !entry->name.empty() ? entry->name : app.name;
if (title.empty())
title = app.path;
setLeading<ThemeIcon>(entry && !entry->icon.empty() ? entry->icon : std::string("computer"), 24);
ColumnLayout* texts = addContent<ColumnLayout>();
texts->setSpacing(metrics::textSpacing);
texts->setLayoutHints({.fillWidth = true, .verticalAlignment = Alignment::Center});
Label* name = texts->addChild<Label>(title);
name->setElide(Label::Elide::Right);
name->setLayoutHints({.fillWidth = true});
SecondaryLabel* path = texts->addChild<SecondaryLabel>(app.path);
path->setElide(Label::Elide::Middle);
path->setLayoutHints({.fillWidth = true});
path->setVisible(title != app.path);
Button* button = addContent<Button>(tr("Remove"));
button->onClicked = std::move(remove);
}
};
class PrivacyPage : public Page
{
public:
explicit PrivacyPage(SettingsWindow& window)
: Page(window, tr("Privacy"))
{
Section* section = content()->addChild<Section>(tr("Computer control"));
m_unavailable = section->addChild<Card>();
m_unavailableLabel = m_unavailable->addChild<Label>(
tr("Computer control is managed by the Cutefish compositor, which is not running."));
m_unavailableLabel->setWrap(true);
m_unavailableLabel->setLayoutHints({.fillWidth = true});
m_empty = section->addChild<Card>();
m_emptyLabel = m_empty->addChild<Label>(tr("No apps are allowed to control this computer without asking."));
m_emptyLabel->setWrap(true);
m_emptyLabel->setLayoutHints({.fillWidth = true});
m_list = section->addChild<Card>(Card::Style::List);
m_note = content()->addChild<Footnote>(
tr("An app you always allow can let its agent see the screen and use the pointer and keyboard "
"without asking first. The Stop button in the status bar still shows while it is in control."));
m_subscription = m_control.changed.connect([this] { update(); });
// Names and icons come once the registry has read the desktop entries.
m_registry = ApplicationRegistry::instance().changed.connect([this] { update(); });
update();
}
protected:
void layout() override
{
m_unavailableLabel->setColor(Theme::current().disabledText);
m_emptyLabel->setColor(Theme::current().disabledText);
Page::layout();
}
private:
void update()
{
const bool available = m_control.isAvailable();
const std::vector<ComputerControl::App>& apps = m_control.allowedApps();
m_unavailable->setVisible(!available);
m_empty->setVisible(available && apps.empty());
m_list->setVisible(available && !apps.empty());
m_note->setVisible(available);
// The rows go only once the list changed, after a Remove returned.
for (ListRow* row : m_rows)
m_list->takeChild(row);
m_rows.clear();
for (const ComputerControl::App& app : apps) {
const std::string path = app.path;
m_rows.push_back(m_list->addChild<AllowedAppRow>(app, [this, path] { m_control.forget(path); }));
}
}
ComputerControl m_control;
cutefish::base::Subscription m_subscription;
cutefish::base::Subscription m_registry;
Card* m_unavailable;
Label* m_unavailableLabel;
Card* m_empty;
Label* m_emptyLabel;
Card* m_list;
Footnote* m_note;
std::vector<ListRow*> m_rows;
};
} // namespace
static const PageRegistration registration("privacy", [](SettingsWindow& window) -> std::unique_ptr<Page> {
return std::make_unique<PrivacyPage>(window);
});
} // namespace settings

@ -74,6 +74,9 @@ static std::vector<PageInfo> makePages()
add({.id = "defaultapps", .name = "defaultapps", .title = trNoop("Default Applications"), add({.id = "defaultapps", .name = "defaultapps", .title = trNoop("Default Applications"),
.icon = "defaultapps.svg", .iconColor = Color::rgb(0x418CFF), .category = trNoop("System"), .icon = "defaultapps.svg", .iconColor = Color::rgb(0x418CFF), .category = trNoop("System"),
.keywords = trNoop("web browser, email client, terminal")}); .keywords = trNoop("web browser, email client, terminal")});
add({.id = "privacy", .name = "privacy", .title = trNoop("Privacy"), .icon = "lock",
.iconColor = Color::rgb(0x418CFF), .category = trNoop("System"),
.keywords = trNoop("computer control, agents, allowed apps, permissions")});
add({.id = "battery", .name = "battery", .title = trNoop("Battery"), .icon = "battery.svg", add({.id = "battery", .name = "battery", .title = trNoop("Battery"), .icon = "battery.svg",
.iconColor = Color::rgb(0x2EC347), .category = trNoop("System"), .iconColor = Color::rgb(0x2EC347), .category = trNoop("System"),
.keywords = trNoop("charge, charging limit, battery health, capacity, history, percentage")}); .keywords = trNoop("charge, charging limit, battery health, capacity, history, percentage")});

Loading…
Cancel
Save