init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_battery_saving_linux.h"
#include "base/battery_saving.h"
#include "base/integration.h"
#include <QtCore/QDir>
#include <gio/gio.h>
#include <dlfcn.h>
extern "C" {
typedef struct _GPowerProfileMonitor GPowerProfileMonitor;
} // extern "C"
namespace base::Platform {
namespace {
class BatterySaving final : public AbstractBatterySaving {
public:
BatterySaving(Fn<void()> changedCallback);
~BatterySaving();
std::optional<bool> enabled() const override;
private:
GPowerProfileMonitor *_monitor = nullptr;
gulong _handlerId = 0;
Fn<void()> _changedCallback;
};
BatterySaving::BatterySaving(Fn<void()> changedCallback)
: _changedCallback(std::move(changedCallback)) {
// Detect battery
if (QDir(u"/sys/class/power_supply"_q).isEmpty()) {
return;
}
// glib 2.70+, we keep glib 2.56+ compatibility
static const auto dup_default = [] {
// reset dlerror after dlsym call
const auto guard = gsl::finally([] { dlerror(); });
return reinterpret_cast<GPowerProfileMonitor*(*)()>(
dlsym(RTLD_DEFAULT, "g_power_profile_monitor_dup_default"));
}();
if (!dup_default) {
return;
}
_monitor = dup_default();
if (_changedCallback) {
_handlerId = g_signal_connect_swapped(
_monitor,
"notify::power-saver-enabled",
G_CALLBACK(+[](BatterySaving *instance) {
Integration::Instance().enterFromEventLoop([&] {
instance->_changedCallback();
});
}), this);
}
}
BatterySaving::~BatterySaving() {
if (_monitor) {
if (_handlerId) {
g_signal_handler_disconnect(_monitor, _handlerId);
}
g_object_unref(_monitor);
}
}
std::optional<bool> BatterySaving::enabled() const {
if (!_monitor) {
return std::nullopt;
}
// glib 2.70+, we keep glib 2.40+ compatibility
static const auto get_power_saver_enabled = [] {
// reset dlerror after dlsym call
const auto guard = gsl::finally([] { dlerror(); });
return reinterpret_cast<gboolean(*)(GPowerProfileMonitor*)>(
dlsym(
RTLD_DEFAULT,
"g_power_profile_monitor_get_power_saver_enabled"));
}();
if (!get_power_saver_enabled) {
return std::nullopt;
}
return get_power_saver_enabled(_monitor);
}
} // namespace
std::unique_ptr<AbstractBatterySaving> CreateBatterySaving(
Fn<void()> changedCallback) {
return std::make_unique<BatterySaving>(std::move(changedCallback));
}
} // namespace base::Platform

View File

@@ -0,0 +1,8 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once

View File

@@ -0,0 +1,222 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_file_utilities_linux.h"
#include "base/platform/base_platform_file_utilities.h"
#include "base/platform/linux/base_linux_xdp_utilities.h"
#include "base/platform/linux/base_linux_xdg_activation_token.h"
#include "base/algorithm.h"
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QStandardPaths>
#include <QtGui/QDesktopServices>
#include <xdpopenuri/xdpopenuri.hpp>
#include <xdgfilemanager1/xdgfilemanager1.hpp>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <stdio.h>
#include <fcntl.h>
namespace base::Platform {
namespace {
using namespace gi::repository;
namespace GObject = gi::repository::GObject;
void PortalShowInFolder(const QString &filepath, Fn<void()> fail) {
XdpOpenURI::OpenURIProxy::new_for_bus(
Gio::BusType::SESSION_,
Gio::DBusProxyFlags::NONE_,
XDP::kService,
XDP::kObjectPath,
[=](GObject::Object, Gio::AsyncResult res) {
auto interface = XdpOpenURI::OpenURI(
XdpOpenURI::OpenURIProxy::new_for_bus_finish(res, nullptr));
if (!interface) {
fail();
return;
}
const auto fd = open(
QFile::encodeName(filepath).constData(),
O_RDONLY | O_CLOEXEC);
if (fd == -1) {
fail();
return;
}
RunWithXdgActivationToken([=](
const QString &activationToken) mutable {
interface.call_open_directory(
XDP::ParentWindowID(),
GLib::Variant::new_handle(0),
GLib::Variant::new_array({
GLib::Variant::new_dict_entry(
GLib::Variant::new_string("activation_token"),
GLib::Variant::new_variant(
GLib::Variant::new_string(
activationToken.toStdString()))),
}),
Gio::UnixFDList::new_from_array(&fd, 1),
{},
[=](GObject::Object, Gio::AsyncResult res) mutable {
if (!interface.call_open_directory_finish(res)) {
fail();
}
});
});
});
}
void DBusShowInFolder(const QString &filepath, Fn<void()> fail) {
XdgFileManager1::FileManager1Proxy::new_for_bus(
Gio::BusType::SESSION_,
Gio::DBusProxyFlags::NONE_,
"org.freedesktop.FileManager1",
"/org/freedesktop/FileManager1",
[=](GObject::Object, Gio::AsyncResult res) {
auto interface = XdgFileManager1::FileManager1(
XdgFileManager1::FileManager1Proxy::new_for_bus_finish(
res,
nullptr));
if (!interface) {
fail();
return;
}
RunWithXdgActivationToken([=](const QString &startupId) mutable {
const auto callbackWrap = gi::unwrap(
Gio::AsyncReadyCallback(
[=](GObject::Object, Gio::AsyncResult res) mutable {
if (!interface.call_show_items_finish(res)) {
fail();
}
}
),
gi::scope_async);
xdg_file_manager1_file_manager1_call_show_items(
interface.gobj_(),
(std::array<const char*, 2>{
GLib::filename_to_uri(
filepath.toStdString(),
nullptr
).c_str(),
nullptr,
}).data(),
startupId.toStdString().c_str(),
nullptr,
&callbackWrap->wrapper,
callbackWrap);
});
});
}
} // namespace
void ShowInFolder(const QString &filepath) {
DBusShowInFolder(filepath, [=] {
PortalShowInFolder(filepath, [=] {
QDesktopServices::openUrl(
QUrl::fromLocalFile(QFileInfo(filepath).absolutePath()));
});
});
}
QString CurrentExecutablePath(int argc, char *argv[]) {
const auto exeLink = QFileInfo(u"/proc/%1/exe"_q.arg(getpid()));
if (exeLink.exists() && exeLink.isSymLink()) {
return exeLink.canonicalFilePath();
}
// Fallback to the first command line argument.
if (argc) {
const auto argv0 = QFile::decodeName(argv[0]);
if (!argv0.isEmpty() && !argv0.contains(QLatin1Char('/'))) {
const auto argv0InPath = QStandardPaths::findExecutable(argv0);
if (!argv0InPath.isEmpty()) {
return argv0InPath;
}
}
return QFileInfo(argv0).absoluteFilePath();
}
return QString();
}
void RemoveQuarantine(const QString &path) {
}
QString BundledResourcesPath() {
Unexpected("BundledResourcesPath not implemented.");
}
QString FileNameFromUserString(QString name) {
return name;
}
// From http://stackoverflow.com/questions/2256945/removing-a-non-empty-directory-programmatically-in-c-or-c
bool DeleteDirectory(QString path) {
if (path.endsWith('/')) {
path.chop(1);
}
const auto pathRaw = QFile::encodeName(path);
const auto d = opendir(pathRaw.constData());
if (!d) {
return false;
}
while (struct dirent *p = readdir(d)) {
// Skip the names "." and ".." as we don't want to recurse on them.
if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")) {
continue;
}
const auto fname = path + '/' + p->d_name;
const auto encoded = QFile::encodeName(fname);
struct stat statbuf;
if (!stat(encoded.constData(), &statbuf)) {
if (S_ISDIR(statbuf.st_mode)) {
if (!DeleteDirectory(fname)) {
closedir(d);
return false;
}
} else {
if (unlink(encoded.constData())) {
closedir(d);
return false;
}
}
}
}
closedir(d);
return !rmdir(pathRaw.constData());
}
bool RenameWithOverwrite(const QString &from, const QString &to) {
const auto fromPath = QFile::encodeName(from);
const auto toPath = QFile::encodeName(to);
return (rename(fromPath.constData(), toPath.constData()) == 0);
}
void FlushFileData(QFile &file) {
file.flush();
if (const auto descriptor = file.handle()) {
fsync(descriptor);
}
}
} // namespace base::Platform

View File

@@ -0,0 +1,7 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once

View File

@@ -0,0 +1,672 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_global_shortcuts_linux.h"
#include "base/const_string.h"
#include "base/global_shortcuts_generic.h"
#include "base/platform/base_platform_info.h" // IsX11
#include "base/debug_log.h"
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
#include "base/platform/linux/base_linux_xcb_utilities.h" // CustomConnection, IsExtensionPresent
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
#include <QKeySequence>
#include <QSocketNotifier>
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
#include <xcb/record.h>
#include <xcb/xcb.h>
#include <xcb/xcb_keysyms.h> // xcb_key_symbols_*
#include <xcb/xcbext.h> // xcb_poll_for_reply
#include <xkbcommon/xkbcommon-keysyms.h>
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
class QKeyEvent;
namespace base::Platform::GlobalShortcuts {
namespace {
constexpr auto kShiftMouseButton = std::numeric_limits<uint64>::max() - 100;
Fn<void(GlobalShortcutKeyGeneric descriptor, bool down)> ProcessCallback;
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
using XcbReply = xcb_record_enable_context_reply_t;
bool IsKeypad(xcb_keysym_t keysym) {
return (xcb_is_keypad_key(keysym) || xcb_is_private_keypad_key(keysym));
}
bool SkipMouseButton(xcb_button_t b) {
return (b == 1) // Ignore the left button.
|| (b > 3 && b < 8); // Ignore the wheel.
}
class X11Manager final {
public:
X11Manager();
~X11Manager();
[[nodiscard]] bool available() const;
private:
void process(XcbReply *reply);
xcb_keysym_t computeKeysym(xcb_keycode_t detail, uint16_t state);
XCB::CustomConnection _connection;
std::unique_ptr<
xcb_key_symbols_t,
custom_delete<xcb_key_symbols_free>
> _keySymbols;
std::unique_ptr<QSocketNotifier> _notifier;
xcb_record_context_t _context = XCB_NONE;
xcb_record_enable_context_cookie_t _cookie = { XCB_NONE };
};
X11Manager::X11Manager()
: _keySymbols(xcb_key_symbols_alloc(_connection)) {
if (xcb_connection_has_error(_connection)) {
LOG((
"Global Shortcuts Manager: Error to open local display!"));
return;
}
if (!XCB::IsExtensionPresent(_connection, &xcb_record_id)) {
LOG(("Global Shortcuts Manager: "
"RECORD extension not supported on this X server!"));
return;
}
_context = xcb_generate_id(_connection);
const xcb_record_client_spec_t clientSpec[] = {
XCB_RECORD_CS_ALL_CLIENTS
};
const xcb_record_range_t recordRange[] = {
[] {
xcb_record_range_t rr;
memset(&rr, 0, sizeof(rr));
// XCB_KEY_PRESS = 2
// XCB_KEY_RELEASE = 3
// XCB_BUTTON_PRESS = 4
// XCB_BUTTON_RELEASE = 5
rr.device_events = { XCB_KEY_PRESS, XCB_BUTTON_RELEASE };
return rr;
}()
};
const auto createCookie = xcb_record_create_context_checked(
_connection,
_context,
0,
sizeof(clientSpec) / sizeof(clientSpec[0]),
sizeof(recordRange) / sizeof(recordRange[0]),
clientSpec,
recordRange);
if (const auto error = xcb_request_check(_connection, createCookie)) {
LOG((
"Global Shortcuts Manager: Could not create a record context!"));
_context = XCB_NONE;
free(error);
return;
}
_cookie = xcb_record_enable_context(_connection, _context);
xcb_flush(_connection);
_notifier = std::make_unique<QSocketNotifier>(
xcb_get_file_descriptor(_connection),
QSocketNotifier::Read);
QObject::connect(_notifier.get(), &QSocketNotifier::activated, [=] {
while (const auto event = xcb_poll_for_event(_connection)) {
free(event);
}
void *reply = nullptr;
xcb_generic_error_t *error = nullptr;
while (_cookie.sequence
&& xcb_poll_for_reply(
_connection,
_cookie.sequence,
&reply,
&error)) {
// The xcb_poll_for_reply method may set both reply and error
// to null if connection has error.
if (xcb_connection_has_error(_connection)) {
break;
}
if (error) {
free(error);
break;
}
if (!reply) {
continue;
}
process(reinterpret_cast<XcbReply*>(reply));
free(reply);
}
});
_notifier->setEnabled(true);
}
X11Manager::~X11Manager() {
if (_cookie.sequence) {
xcb_record_disable_context(_connection, _context);
_cookie = { XCB_NONE };
}
if (_context) {
xcb_record_free_context(_connection, _context);
_context = XCB_NONE;
}
}
void X11Manager::process(XcbReply *reply) {
if (!ProcessCallback) {
return;
}
// Seems like xcb_button_press_event_t and xcb_key_press_event_t structs
// are the same, so we can safely cast both of them
// to the xcb_key_press_event_t.
const auto events = reinterpret_cast<xcb_key_press_event_t*>(
xcb_record_enable_context_data(reply));
const auto countEvents = xcb_record_enable_context_data_length(reply) /
sizeof(xcb_key_press_event_t);
for (auto e = events; e < (events + countEvents); e++) {
const auto type = e->response_type;
const auto buttonPress = (type == XCB_BUTTON_PRESS);
const auto buttonRelease = (type == XCB_BUTTON_RELEASE);
const auto keyPress = (type == XCB_KEY_PRESS);
const auto keyRelease = (type == XCB_KEY_RELEASE);
const auto isButton = (buttonPress || buttonRelease);
if (!(keyPress || keyRelease || isButton)) {
continue;
}
const auto code = e->detail;
if (isButton && SkipMouseButton(code)) {
return;
}
const auto descriptor = isButton
? (kShiftMouseButton + code)
: GlobalShortcutKeyGeneric(computeKeysym(code, e->state));
ProcessCallback(descriptor, keyPress || buttonPress);
}
}
xcb_keysym_t X11Manager::computeKeysym(xcb_keycode_t detail, uint16_t state) {
// Perhaps XCB_MOD_MASK_1-5 are needed here.
const auto keySym1 = xcb_key_symbols_get_keysym(_keySymbols.get(), detail, 1);
if (IsKeypad(keySym1)) {
return keySym1;
}
if (keySym1 >= Qt::Key_A && keySym1 <= Qt::Key_Z) {
if (keySym1 != XCB_NO_SYMBOL) {
return keySym1;
}
}
return xcb_key_symbols_get_keysym(_keySymbols.get(), detail, 0);
}
bool X11Manager::available() const {
return _cookie.sequence;
}
std::unique_ptr<X11Manager> _x11Manager = nullptr;
void EnsureX11ShortcutManager() {
if (!_x11Manager) {
_x11Manager = std::make_unique<X11Manager>();
}
}
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
} // namespace
bool Available() {
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
if (::Platform::IsX11()) {
EnsureX11ShortcutManager();
return _x11Manager->available();
}
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
return false;
}
bool Allowed() {
return Available();
}
void Start(Fn<void(GlobalShortcutKeyGeneric descriptor, bool down)> process) {
ProcessCallback = std::move(process);
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
EnsureX11ShortcutManager();
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
}
void Stop() {
ProcessCallback = nullptr;
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
_x11Manager = nullptr;
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
}
QString KeyName(GlobalShortcutKeyGeneric descriptor) {
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
// Telegram/ThirdParty/fcitx-qt5/platforminputcontext/qtkey.cpp
static const auto KeyToString = flat_map<uint64, int>{
{ XKB_KEY_KP_Space, Qt::Key_Space },
{ XKB_KEY_KP_Tab, Qt::Key_Tab },
{ XKB_KEY_KP_Enter, Qt::Key_Enter },
{ XKB_KEY_KP_F1, Qt::Key_F1 },
{ XKB_KEY_KP_F2, Qt::Key_F2 },
{ XKB_KEY_KP_F3, Qt::Key_F3 },
{ XKB_KEY_KP_F4, Qt::Key_F4 },
{ XKB_KEY_KP_Home, Qt::Key_Home },
{ XKB_KEY_KP_Left, Qt::Key_Left },
{ XKB_KEY_KP_Up, Qt::Key_Up },
{ XKB_KEY_KP_Right, Qt::Key_Right },
{ XKB_KEY_KP_Down, Qt::Key_Down },
{ XKB_KEY_KP_Page_Up, Qt::Key_PageUp },
{ XKB_KEY_KP_Page_Down, Qt::Key_PageDown },
{ XKB_KEY_KP_End, Qt::Key_End },
{ XKB_KEY_KP_Begin, Qt::Key_Clear },
{ XKB_KEY_KP_Insert, Qt::Key_Insert },
{ XKB_KEY_KP_Delete, Qt::Key_Delete },
{ XKB_KEY_KP_Equal, Qt::Key_Equal },
{ XKB_KEY_KP_Multiply, Qt::Key_multiply },
{ XKB_KEY_KP_Add, Qt::Key_Plus },
{ XKB_KEY_KP_Separator, Qt::Key_Comma },
{ XKB_KEY_KP_Subtract, Qt::Key_Minus },
{ XKB_KEY_KP_Decimal, Qt::Key_Period },
{ XKB_KEY_KP_Divide, Qt::Key_Slash },
{ XKB_KEY_KP_0, Qt::Key_0 },
{ XKB_KEY_KP_1, Qt::Key_1 },
{ XKB_KEY_KP_2, Qt::Key_2 },
{ XKB_KEY_KP_3, Qt::Key_3 },
{ XKB_KEY_KP_4, Qt::Key_4 },
{ XKB_KEY_KP_5, Qt::Key_5 },
{ XKB_KEY_KP_6, Qt::Key_6 },
{ XKB_KEY_KP_7, Qt::Key_7 },
{ XKB_KEY_KP_8, Qt::Key_8 },
{ XKB_KEY_KP_9, Qt::Key_9 },
{ XKB_KEY_Escape, Qt::Key_Escape },
{ XKB_KEY_Tab, Qt::Key_Tab },
{ XKB_KEY_ISO_Left_Tab, Qt::Key_Tab },
{ XKB_KEY_BackSpace, Qt::Key_Backspace },
{ XKB_KEY_Return, Qt::Key_Return },
{ XKB_KEY_KP_Enter, Qt::Key_Enter },
{ XKB_KEY_Insert, Qt::Key_Insert },
{ XKB_KEY_Delete, Qt::Key_Delete },
{ XKB_KEY_Clear, Qt::Key_Delete },
{ XKB_KEY_Pause, Qt::Key_Pause },
{ XKB_KEY_Print, Qt::Key_Print },
{ XKB_KEY_Sys_Req, Qt::Key_SysReq },
{ XKB_KEY_SunSys_Req, Qt::Key_SysReq },
{ 0x1007ff00, Qt::Key_SysReq },
{ XKB_KEY_Home, Qt::Key_Home },
{ XKB_KEY_End, Qt::Key_End },
{ XKB_KEY_Left, Qt::Key_Left },
{ XKB_KEY_Up, Qt::Key_Up },
{ XKB_KEY_Right, Qt::Key_Right },
{ XKB_KEY_Down, Qt::Key_Down },
{ XKB_KEY_Page_Up, Qt::Key_PageUp },
{ XKB_KEY_Page_Down, Qt::Key_PageDown },
{ XKB_KEY_Shift_L, Qt::Key_Shift },
{ XKB_KEY_Shift_R, Qt::Key_Shift },
{ XKB_KEY_Shift_Lock, Qt::Key_Shift },
{ XKB_KEY_Control_L, Qt::Key_Control },
{ XKB_KEY_Control_R, Qt::Key_Control },
{ XKB_KEY_Meta_L, Qt::Key_Meta },
{ XKB_KEY_Meta_R, Qt::Key_Meta },
{ XKB_KEY_Alt_L, Qt::Key_Alt },
{ XKB_KEY_Alt_R, Qt::Key_Alt },
{ XKB_KEY_Caps_Lock, Qt::Key_CapsLock },
{ XKB_KEY_Num_Lock, Qt::Key_NumLock },
{ XKB_KEY_Scroll_Lock, Qt::Key_ScrollLock },
{ XKB_KEY_F1, Qt::Key_F1 },
{ XKB_KEY_F2, Qt::Key_F2 },
{ XKB_KEY_F3, Qt::Key_F3 },
{ XKB_KEY_F4, Qt::Key_F4 },
{ XKB_KEY_F5, Qt::Key_F5 },
{ XKB_KEY_F6, Qt::Key_F6 },
{ XKB_KEY_F7, Qt::Key_F7 },
{ XKB_KEY_F8, Qt::Key_F8 },
{ XKB_KEY_F9, Qt::Key_F9 },
{ XKB_KEY_F10, Qt::Key_F10 },
{ XKB_KEY_F11, Qt::Key_F11 },
{ XKB_KEY_F12, Qt::Key_F12 },
{ XKB_KEY_F13, Qt::Key_F13 },
{ XKB_KEY_F14, Qt::Key_F14 },
{ XKB_KEY_F15, Qt::Key_F15 },
{ XKB_KEY_F16, Qt::Key_F16 },
{ XKB_KEY_F17, Qt::Key_F17 },
{ XKB_KEY_F18, Qt::Key_F18 },
{ XKB_KEY_F19, Qt::Key_F19 },
{ XKB_KEY_F20, Qt::Key_F20 },
{ XKB_KEY_F21, Qt::Key_F21 },
{ XKB_KEY_F22, Qt::Key_F22 },
{ XKB_KEY_F23, Qt::Key_F23 },
{ XKB_KEY_F24, Qt::Key_F24 },
{ XKB_KEY_F25, Qt::Key_F25 },
{ XKB_KEY_F26, Qt::Key_F26 },
{ XKB_KEY_F27, Qt::Key_F27 },
{ XKB_KEY_F28, Qt::Key_F28 },
{ XKB_KEY_F29, Qt::Key_F29 },
{ XKB_KEY_F30, Qt::Key_F30 },
{ XKB_KEY_F31, Qt::Key_F31 },
{ XKB_KEY_F32, Qt::Key_F32 },
{ XKB_KEY_F33, Qt::Key_F33 },
{ XKB_KEY_F34, Qt::Key_F34 },
{ XKB_KEY_F35, Qt::Key_F35 },
{ XKB_KEY_Super_L, Qt::Key_Super_L },
{ XKB_KEY_Super_R, Qt::Key_Super_R },
{ XKB_KEY_Menu, Qt::Key_Menu },
{ XKB_KEY_Hyper_L, Qt::Key_Hyper_L },
{ XKB_KEY_Hyper_R, Qt::Key_Hyper_R },
{ XKB_KEY_Help, Qt::Key_Help },
{ XKB_KEY_ISO_Level3_Shift, Qt::Key_AltGr },
{ XKB_KEY_Multi_key, Qt::Key_Multi_key },
{ XKB_KEY_Codeinput, Qt::Key_Codeinput },
{ XKB_KEY_SingleCandidate, Qt::Key_SingleCandidate },
{ XKB_KEY_MultipleCandidate, Qt::Key_MultipleCandidate },
{ XKB_KEY_PreviousCandidate, Qt::Key_PreviousCandidate },
{ XKB_KEY_Mode_switch, Qt::Key_Mode_switch },
{ XKB_KEY_script_switch, Qt::Key_Mode_switch },
{ XKB_KEY_Kanji, Qt::Key_Kanji },
{ XKB_KEY_Muhenkan, Qt::Key_Muhenkan },
{ XKB_KEY_Henkan, Qt::Key_Henkan },
{ XKB_KEY_Romaji, Qt::Key_Romaji },
{ XKB_KEY_Hiragana, Qt::Key_Hiragana },
{ XKB_KEY_Katakana, Qt::Key_Katakana },
{ XKB_KEY_Hiragana_Katakana, Qt::Key_Hiragana_Katakana },
{ XKB_KEY_Zenkaku, Qt::Key_Zenkaku },
{ XKB_KEY_Hankaku, Qt::Key_Hankaku },
{ XKB_KEY_Zenkaku_Hankaku, Qt::Key_Zenkaku_Hankaku },
{ XKB_KEY_Touroku, Qt::Key_Touroku },
{ XKB_KEY_Massyo, Qt::Key_Massyo },
{ XKB_KEY_Kana_Lock, Qt::Key_Kana_Lock },
{ XKB_KEY_Kana_Shift, Qt::Key_Kana_Shift },
{ XKB_KEY_Eisu_Shift, Qt::Key_Eisu_Shift },
{ XKB_KEY_Eisu_toggle, Qt::Key_Eisu_toggle },
{ XKB_KEY_Kanji_Bangou, Qt::Key_Codeinput },
{ XKB_KEY_Zen_Koho, Qt::Key_MultipleCandidate },
{ XKB_KEY_Mae_Koho, Qt::Key_PreviousCandidate },
{ XKB_KEY_Hangul, Qt::Key_Hangul },
{ XKB_KEY_Hangul_Start, Qt::Key_Hangul_Start },
{ XKB_KEY_Hangul_End, Qt::Key_Hangul_End },
{ XKB_KEY_Hangul_Hanja, Qt::Key_Hangul_Hanja },
{ XKB_KEY_Hangul_Jamo, Qt::Key_Hangul_Jamo },
{ XKB_KEY_Hangul_Romaja, Qt::Key_Hangul_Romaja },
{ XKB_KEY_Hangul_Codeinput, Qt::Key_Codeinput },
{ XKB_KEY_Hangul_Jeonja, Qt::Key_Hangul_Jeonja },
{ XKB_KEY_Hangul_Banja, Qt::Key_Hangul_Banja },
{ XKB_KEY_Hangul_PreHanja, Qt::Key_Hangul_PreHanja },
{ XKB_KEY_Hangul_PostHanja, Qt::Key_Hangul_PostHanja },
{ XKB_KEY_Hangul_SingleCandidate, Qt::Key_SingleCandidate },
{ XKB_KEY_Hangul_MultipleCandidate, Qt::Key_MultipleCandidate },
{ XKB_KEY_Hangul_PreviousCandidate, Qt::Key_PreviousCandidate },
{ XKB_KEY_Hangul_Special, Qt::Key_Hangul_Special },
{ XKB_KEY_Hangul_switch, Qt::Key_Mode_switch },
{ XKB_KEY_dead_grave, Qt::Key_Dead_Grave },
{ XKB_KEY_dead_acute, Qt::Key_Dead_Acute },
{ XKB_KEY_dead_circumflex, Qt::Key_Dead_Circumflex },
{ XKB_KEY_dead_tilde, Qt::Key_Dead_Tilde },
{ XKB_KEY_dead_macron, Qt::Key_Dead_Macron },
{ XKB_KEY_dead_breve, Qt::Key_Dead_Breve },
{ XKB_KEY_dead_abovedot, Qt::Key_Dead_Abovedot },
{ XKB_KEY_dead_diaeresis, Qt::Key_Dead_Diaeresis },
{ XKB_KEY_dead_abovering, Qt::Key_Dead_Abovering },
{ XKB_KEY_dead_doubleacute, Qt::Key_Dead_Doubleacute },
{ XKB_KEY_dead_caron, Qt::Key_Dead_Caron },
{ XKB_KEY_dead_cedilla, Qt::Key_Dead_Cedilla },
{ XKB_KEY_dead_ogonek, Qt::Key_Dead_Ogonek },
{ XKB_KEY_dead_iota, Qt::Key_Dead_Iota },
{ XKB_KEY_dead_voiced_sound, Qt::Key_Dead_Voiced_Sound },
{ XKB_KEY_dead_semivoiced_sound, Qt::Key_Dead_Semivoiced_Sound },
{ XKB_KEY_dead_belowdot, Qt::Key_Dead_Belowdot },
{ XKB_KEY_dead_hook, Qt::Key_Dead_Hook },
{ XKB_KEY_dead_horn, Qt::Key_Dead_Horn },
{ XKB_KEY_XF86Back, Qt::Key_Back },
{ XKB_KEY_XF86Forward, Qt::Key_Forward },
{ XKB_KEY_XF86Stop, Qt::Key_Stop },
{ XKB_KEY_XF86Refresh, Qt::Key_Refresh },
{ XKB_KEY_XF86AudioLowerVolume, Qt::Key_VolumeDown },
{ XKB_KEY_XF86AudioMute, Qt::Key_VolumeMute },
{ XKB_KEY_XF86AudioRaiseVolume, Qt::Key_VolumeUp },
{ XKB_KEY_XF86AudioPlay, Qt::Key_MediaPlay },
{ XKB_KEY_XF86AudioStop, Qt::Key_MediaStop },
{ XKB_KEY_XF86AudioPrev, Qt::Key_MediaPrevious },
{ XKB_KEY_XF86AudioNext, Qt::Key_MediaNext },
{ XKB_KEY_XF86AudioRecord, Qt::Key_MediaRecord },
{ XKB_KEY_XF86AudioPause, Qt::Key_MediaPause },
{ XKB_KEY_XF86HomePage, Qt::Key_HomePage },
{ XKB_KEY_XF86Favorites, Qt::Key_Favorites },
{ XKB_KEY_XF86Search, Qt::Key_Search },
{ XKB_KEY_XF86Standby, Qt::Key_Standby },
{ XKB_KEY_XF86OpenURL, Qt::Key_OpenUrl },
{ XKB_KEY_XF86Mail, Qt::Key_LaunchMail },
{ XKB_KEY_XF86AudioMedia, Qt::Key_LaunchMedia },
{ XKB_KEY_XF86MyComputer, Qt::Key_Launch0 },
{ XKB_KEY_XF86Calculator, Qt::Key_Launch1 },
{ XKB_KEY_XF86Launch0, Qt::Key_Launch2 },
{ XKB_KEY_XF86Launch1, Qt::Key_Launch3 },
{ XKB_KEY_XF86Launch2, Qt::Key_Launch4 },
{ XKB_KEY_XF86Launch3, Qt::Key_Launch5 },
{ XKB_KEY_XF86Launch4, Qt::Key_Launch6 },
{ XKB_KEY_XF86Launch5, Qt::Key_Launch7 },
{ XKB_KEY_XF86Launch6, Qt::Key_Launch8 },
{ XKB_KEY_XF86Launch7, Qt::Key_Launch9 },
{ XKB_KEY_XF86Launch8, Qt::Key_LaunchA },
{ XKB_KEY_XF86Launch9, Qt::Key_LaunchB },
{ XKB_KEY_XF86LaunchA, Qt::Key_LaunchC },
{ XKB_KEY_XF86LaunchB, Qt::Key_LaunchD },
{ XKB_KEY_XF86LaunchC, Qt::Key_LaunchE },
{ XKB_KEY_XF86LaunchD, Qt::Key_LaunchF },
{ XKB_KEY_XF86MonBrightnessUp, Qt::Key_MonBrightnessUp },
{ XKB_KEY_XF86MonBrightnessDown, Qt::Key_MonBrightnessDown },
{ XKB_KEY_XF86KbdLightOnOff, Qt::Key_KeyboardLightOnOff },
{ XKB_KEY_XF86KbdBrightnessUp, Qt::Key_KeyboardBrightnessUp },
{ XKB_KEY_XF86PowerOff, Qt::Key_PowerOff },
{ XKB_KEY_XF86WakeUp, Qt::Key_WakeUp },
{ XKB_KEY_XF86Eject, Qt::Key_Eject },
{ XKB_KEY_XF86ScreenSaver, Qt::Key_ScreenSaver },
{ XKB_KEY_XF86WWW, Qt::Key_WWW },
{ XKB_KEY_XF86Memo, Qt::Key_Memo },
{ XKB_KEY_XF86LightBulb, Qt::Key_LightBulb },
{ XKB_KEY_XF86Shop, Qt::Key_Shop },
{ XKB_KEY_XF86History, Qt::Key_History },
{ XKB_KEY_XF86AddFavorite, Qt::Key_AddFavorite },
{ XKB_KEY_XF86HotLinks, Qt::Key_HotLinks },
{ XKB_KEY_XF86BrightnessAdjust, Qt::Key_BrightnessAdjust },
{ XKB_KEY_XF86Finance, Qt::Key_Finance },
{ XKB_KEY_XF86Community, Qt::Key_Community },
{ XKB_KEY_XF86AudioRewind, Qt::Key_AudioRewind },
{ XKB_KEY_XF86BackForward, Qt::Key_BackForward },
{ XKB_KEY_XF86ApplicationLeft, Qt::Key_ApplicationLeft },
{ XKB_KEY_XF86ApplicationRight, Qt::Key_ApplicationRight },
{ XKB_KEY_XF86Book, Qt::Key_Book },
{ XKB_KEY_XF86CD, Qt::Key_CD },
{ XKB_KEY_XF86Calculater, Qt::Key_Calculator },
{ XKB_KEY_XF86ToDoList, Qt::Key_ToDoList },
{ XKB_KEY_XF86ClearGrab, Qt::Key_ClearGrab },
{ XKB_KEY_XF86Close, Qt::Key_Close },
{ XKB_KEY_XF86Copy, Qt::Key_Copy },
{ XKB_KEY_XF86Cut, Qt::Key_Cut },
{ XKB_KEY_XF86Display, Qt::Key_Display },
{ XKB_KEY_XF86DOS, Qt::Key_DOS },
{ XKB_KEY_XF86Documents, Qt::Key_Documents },
{ XKB_KEY_XF86Excel, Qt::Key_Excel },
{ XKB_KEY_XF86Explorer, Qt::Key_Explorer },
{ XKB_KEY_XF86Game, Qt::Key_Game },
{ XKB_KEY_XF86Go, Qt::Key_Go },
{ XKB_KEY_XF86iTouch, Qt::Key_iTouch },
{ XKB_KEY_XF86LogOff, Qt::Key_LogOff },
{ XKB_KEY_XF86Market, Qt::Key_Market },
{ XKB_KEY_XF86Meeting, Qt::Key_Meeting },
{ XKB_KEY_XF86MenuKB, Qt::Key_MenuKB },
{ XKB_KEY_XF86MenuPB, Qt::Key_MenuPB },
{ XKB_KEY_XF86MySites, Qt::Key_MySites },
{ XKB_KEY_XF86News, Qt::Key_News },
{ XKB_KEY_XF86OfficeHome, Qt::Key_OfficeHome },
{ XKB_KEY_XF86Option, Qt::Key_Option },
{ XKB_KEY_XF86Paste, Qt::Key_Paste },
{ XKB_KEY_XF86Phone, Qt::Key_Phone },
{ XKB_KEY_XF86Calendar, Qt::Key_Calendar },
{ XKB_KEY_XF86Reply, Qt::Key_Reply },
{ XKB_KEY_XF86Reload, Qt::Key_Reload },
{ XKB_KEY_XF86RotateWindows, Qt::Key_RotateWindows },
{ XKB_KEY_XF86RotationPB, Qt::Key_RotationPB },
{ XKB_KEY_XF86RotationKB, Qt::Key_RotationKB },
{ XKB_KEY_XF86Save, Qt::Key_Save },
{ XKB_KEY_XF86Send, Qt::Key_Send },
{ XKB_KEY_XF86Spell, Qt::Key_Spell },
{ XKB_KEY_XF86SplitScreen, Qt::Key_SplitScreen },
{ XKB_KEY_XF86Support, Qt::Key_Support },
{ XKB_KEY_XF86TaskPane, Qt::Key_TaskPane },
{ XKB_KEY_XF86Terminal, Qt::Key_Terminal },
{ XKB_KEY_XF86Tools, Qt::Key_Tools },
{ XKB_KEY_XF86Travel, Qt::Key_Travel },
{ XKB_KEY_XF86Video, Qt::Key_Video },
{ XKB_KEY_XF86Word, Qt::Key_Word },
{ XKB_KEY_XF86Xfer, Qt::Key_Xfer },
{ XKB_KEY_XF86ZoomIn, Qt::Key_ZoomIn },
{ XKB_KEY_XF86ZoomOut, Qt::Key_ZoomOut },
{ XKB_KEY_XF86Away, Qt::Key_Away },
{ XKB_KEY_XF86Messenger, Qt::Key_Messenger },
{ XKB_KEY_XF86WebCam, Qt::Key_WebCam },
{ XKB_KEY_XF86MailForward, Qt::Key_MailForward },
{ XKB_KEY_XF86Pictures, Qt::Key_Pictures },
{ XKB_KEY_XF86Music, Qt::Key_Music },
{ XKB_KEY_XF86Battery, Qt::Key_Battery },
{ XKB_KEY_XF86Bluetooth, Qt::Key_Bluetooth },
{ XKB_KEY_XF86WLAN, Qt::Key_WLAN },
{ XKB_KEY_XF86UWB, Qt::Key_UWB },
{ XKB_KEY_XF86AudioForward, Qt::Key_AudioForward },
{ XKB_KEY_XF86AudioRepeat, Qt::Key_AudioRepeat },
{ XKB_KEY_XF86AudioRandomPlay, Qt::Key_AudioRandomPlay },
{ XKB_KEY_XF86Subtitle, Qt::Key_Subtitle },
{ XKB_KEY_XF86AudioCycleTrack, Qt::Key_AudioCycleTrack },
{ XKB_KEY_XF86Time, Qt::Key_Time },
{ XKB_KEY_XF86Hibernate, Qt::Key_Hibernate },
{ XKB_KEY_XF86View, Qt::Key_View },
{ XKB_KEY_XF86TopMenu, Qt::Key_TopMenu },
{ XKB_KEY_XF86PowerDown, Qt::Key_PowerDown },
{ XKB_KEY_XF86Suspend, Qt::Key_Suspend },
{ XKB_KEY_XF86ContrastAdjust, Qt::Key_ContrastAdjust },
{ XKB_KEY_XF86LaunchE, Qt::Key_LaunchG },
{ XKB_KEY_XF86LaunchF, Qt::Key_LaunchH },
{ XKB_KEY_XF86Select, Qt::Key_Select },
{ XKB_KEY_Cancel, Qt::Key_Cancel },
{ XKB_KEY_Execute, Qt::Key_Execute },
{ XKB_KEY_XF86Sleep, Qt::Key_Sleep },
};
// Mouse.
// Taken from QXcbConnection::translateMouseButton.
static const auto XcbButtonToQt = flat_map<uint64, Qt::MouseButton>{
// { 1, Qt::LeftButton }, // Ignore the left button.
{ 2, Qt::MiddleButton },
{ 3, Qt::RightButton },
// Button values 4-7 were already handled as Wheel events.
{ 8, Qt::BackButton },
{ 9, Qt::ForwardButton },
{ 10, Qt::ExtraButton3 },
{ 11, Qt::ExtraButton4 },
{ 12, Qt::ExtraButton5 },
{ 13, Qt::ExtraButton6 },
{ 14, Qt::ExtraButton7 },
{ 15, Qt::ExtraButton8 },
{ 16, Qt::ExtraButton9 },
{ 17, Qt::ExtraButton10 },
{ 18, Qt::ExtraButton11 },
{ 19, Qt::ExtraButton12 },
{ 20, Qt::ExtraButton13 },
{ 21, Qt::ExtraButton14 },
{ 22, Qt::ExtraButton15 },
{ 23, Qt::ExtraButton16 },
{ 24, Qt::ExtraButton17 },
{ 25, Qt::ExtraButton18 },
{ 26, Qt::ExtraButton19 },
{ 27, Qt::ExtraButton20 },
{ 28, Qt::ExtraButton21 },
{ 29, Qt::ExtraButton22 },
{ 30, Qt::ExtraButton23 },
{ 31, Qt::ExtraButton24 },
};
if (descriptor > kShiftMouseButton) {
const auto button = descriptor - kShiftMouseButton;
if (XcbButtonToQt.contains(button)) {
return QString("Mouse %1").arg(button);
}
}
//
// Modifiers.
static const auto ModifierToString = flat_map<uint64, const_string>{
{ XKB_KEY_Shift_L, "Shift" },
{ XKB_KEY_Shift_R, "Right Shift" },
{ XKB_KEY_Control_L, "Ctrl" },
{ XKB_KEY_Control_R, "Right Ctrl" },
{ XKB_KEY_Meta_L, "Meta" },
{ XKB_KEY_Meta_R, "Right Meta" },
{ XKB_KEY_Alt_L, "Alt" },
{ XKB_KEY_Alt_R, "Right Alt" },
{ XKB_KEY_Super_L, "Super" },
{ XKB_KEY_Super_R, "Right Super" },
};
const auto modIt = ModifierToString.find(descriptor);
if (modIt != end(ModifierToString)) {
return modIt->second.utf16();
}
//
const auto fromSequence = [](int k) {
return QKeySequence(k).toString(QKeySequence::NativeText);
};
// The conversion is not necessary,
// if the value in the range Qt::Key_Space - Qt::Key_QuoteLeft.
if (descriptor >= Qt::Key_Space && descriptor <= Qt::Key_QuoteLeft) {
return fromSequence(descriptor);
}
const auto prefix = IsKeypad(descriptor) ? "Num " : QString();
const auto keyIt = KeyToString.find(descriptor);
return (keyIt != end(KeyToString))
? prefix + fromSequence(keyIt->second)
: QString("\\x%1").arg(descriptor, 0, 16);
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
return {};
}
bool IsToggleFullScreenKey(not_null<QKeyEvent*> e) {
return false;
}
} // namespace base::Platform::GlobalShortcuts

View File

@@ -0,0 +1,9 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/global_shortcuts.h"

View File

@@ -0,0 +1,53 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_haptic_linux.h"
#include <QtGui/QGuiApplication>
#include <sigxcpufeedback/sigxcpufeedback.hpp>
namespace base::Platform {
namespace {
using namespace gi::repository;
namespace GObject = gi::repository::GObject;
} // namespace
void Haptic() {
SigxcpuFeedback::HapticProxy::new_for_bus(
Gio::BusType::SESSION_,
Gio::DBusProxyFlags::NONE_,
"org.sigxcpu.Feedback",
"/org/sigxcpu/Feedback",
[=](GObject::Object, Gio::AsyncResult res) {
auto interface = SigxcpuFeedback::Haptic(
SigxcpuFeedback::HapticProxy::new_for_bus_finish(
res,
nullptr));
if (!interface) {
return;
}
interface.call_vibrate(
QGuiApplication::desktopFileName().toStdString(),
GLib::Variant::new_array({
GLib::Variant::new_tuple({
GLib::Variant::new_double(0.2),
GLib::Variant::new_uint32(5),
}),
}),
nullptr);
});
}
bool IsSwipeBackEnabled() {
return true;
}
} // namespace base::Platform

View File

@@ -0,0 +1,9 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/platform/base_platform_haptic.h"

View File

@@ -0,0 +1,386 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_info_linux.h"
#include "base/algorithm.h"
#include "base/platform/linux/base_linux_library.h"
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
#include "base/platform/linux/base_linux_xcb_utilities.h"
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
#include <QtCore/QJsonObject>
#include <QtCore/QLocale>
#include <QtCore/QVersionNumber>
#include <QtCore/QDate>
#include <QtCore/QFile>
#include <QtCore/QProcess>
#include <QtGui/QGuiApplication>
#include <sys/utsname.h>
#ifdef __GLIBC__
#include <gnu/libc-version.h>
#endif // __GLIBC__
extern "C" {
struct wl_display;
} // extern "C"
namespace Platform {
namespace {
[[nodiscard]] QStringList GetDesktopEnvironment() {
const auto list = qEnvironmentVariable("XDG_CURRENT_DESKTOP").split(':');
return list | ranges::views::transform([](const auto &item) {
return item.simplified();
}) | ranges::to<QStringList>;
}
[[nodiscard]] QString ChassisTypeToString(uint type) {
switch (type) {
case 0x3: /* Desktop */
case 0x4: /* Low Profile Desktop */
case 0x6: /* Mini Tower */
case 0x7: /* Tower */
case 0xD: /* All in one (i.e. PC built into monitor) */
return "Desktop";
case 0x8: /* Portable */
case 0x9: /* Laptop */
case 0xA: /* Notebook */
case 0xE: /* Sub Notebook */
return "Laptop";
case 0xB: /* Hand Held */
return "Handset";
case 0x11: /* Main Server Chassis */
case 0x1C: /* Blade */
case 0x1D: /* Blade Enclosure */
return "Server";
case 0x1E: /* Tablet */
return "Tablet";
case 0x1F: /* Convertible */
case 0x20: /* Detachable */
return "Convertible";
default:
return "";
}
}
[[nodiscard]] bool IsGlibcLess228() {
static const auto result = [] {
const auto libcName = GetLibcName();
const auto libcVersion = GetLibcVersion();
return (libcName == qstr("glibc"))
&& !libcVersion.isEmpty()
&& (QVersionNumber::fromString(libcVersion)
< QVersionNumber(2, 28));
}();
return result;
}
} // namespace
QString DeviceModelPretty() {
using namespace base::Platform;
static const auto result = FinalizeDeviceModel([&] {
const auto value = [](const char *key) {
auto file = QFile(u"/sys/class/dmi/id/"_q + key);
return (file.open(QIODevice::ReadOnly | QIODevice::Text))
? SimplifyDeviceModel(QString(file.readAll()))
: QString();
};
const auto productName = value("product_name");
if (const auto model = ProductNameToDeviceModel(productName)
; !model.isEmpty()) {
return model;
}
const auto productFamily = value("product_family");
const auto boardName = value("board_name");
const auto familyName = SimplifyDeviceModel(
productFamily + ' ' + boardName);
if (IsDeviceModelOk(familyName)) {
return familyName;
} else if (IsDeviceModelOk(boardName)) {
return boardName;
} else if (IsDeviceModelOk(productFamily)) {
return productFamily;
}
const auto virtualization = []() -> QString {
QProcess process;
process.start("systemd-detect-virt", QStringList());
process.waitForFinished();
return process.readAll().simplified().toUpper();
}();
if (!virtualization.isEmpty() && virtualization != qstr("NONE")) {
return virtualization;
}
const auto chassisType = ChassisTypeToString(
value("chassis_type").toUInt());
if (!chassisType.isEmpty()) {
return chassisType;
}
return QString();
}());
return result;
}
QString SystemVersionPretty() {
static const auto result = [&] {
QStringList resultList{};
struct utsname u;
if (uname(&u) == 0) {
resultList << u.sysname;
#ifndef Q_OS_LINUX
resultList << u.release;
#endif // !Q_OS_LINUX
} else {
resultList << "Unknown";
}
if (const auto desktopEnvironment = GetDesktopEnvironment();
!desktopEnvironment.isEmpty()) {
resultList << desktopEnvironment;
} else if (const auto windowManager = GetWindowManager();
!windowManager.isEmpty()) {
resultList << windowManager;
}
if (IsWayland()) {
resultList << "Wayland";
} else if (IsXwayland()) {
resultList << "Xwayland";
} else if (IsX11()) {
resultList << "X11";
}
const auto libcName = GetLibcName();
const auto libcVersion = GetLibcVersion();
if (!libcVersion.isEmpty()) {
if (!libcName.isEmpty()) {
resultList << libcName;
} else {
resultList << "libc";
}
resultList << libcVersion;
}
return resultList.join(' ');
}();
return result;
}
QString SystemCountry() {
return QLocale::system().name().split('_').last();
}
QString SystemLanguage() {
const auto system = QLocale::system();
const auto languages = system.uiLanguages();
return languages.isEmpty()
? system.name().split('_').first()
: languages.front();
}
QDate WhenSystemBecomesOutdated() {
if (IsGlibcLess228()) {
return QDate(2023, 7, 1); // Older than CentOS 8.
}
return QDate();
}
int AutoUpdateVersion() {
if (IsGlibcLess228()) {
return 2;
}
return 4;
}
QString AutoUpdateKey() {
return "linux";
}
QString GetLibcName() {
#ifdef __GLIBC__
return "glibc";
#endif // __GLIBC__
return QString();
}
QString GetLibcVersion() {
#ifdef __GLIBC__
static const auto result = [&] {
const auto version = QString::fromLatin1(gnu_get_libc_version());
return QVersionNumber::fromString(version).isNull() ? QString() : version;
}();
return result;
#endif // __GLIBC__
return QString();
}
QString GetWindowManager() {
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
const base::Platform::XCB::Connection connection;
if (!connection || xcb_connection_has_error(connection)) {
return {};
}
const auto root = base::Platform::XCB::GetRootWindow(connection);
if (!root) {
return {};
}
const auto nameAtom = base::Platform::XCB::GetAtom(
connection,
"_NET_WM_NAME");
const auto utf8Atom = base::Platform::XCB::GetAtom(
connection,
"UTF8_STRING");
const auto supportingWindow = base::Platform::XCB::GetSupportingWMCheck(
connection,
root);
if (!nameAtom || !utf8Atom || !supportingWindow) {
return {};
}
const auto cookie = xcb_get_property(
connection,
false,
supportingWindow,
nameAtom,
utf8Atom,
0,
1024);
const auto reply = base::Platform::XCB::MakeReplyPointer(
xcb_get_property_reply(
connection,
cookie,
nullptr));
if (!reply) {
return {};
}
return (reply->format == 8 && reply->type == utf8Atom)
? QString::fromUtf8(
reinterpret_cast<const char*>(
xcb_get_property_value(reply.get())),
xcb_get_property_value_length(reply.get())).simplified()
: QString();
#else // !DESKTOP_APP_DISABLE_X11_INTEGRATION
return QString();
#endif // DESKTOP_APP_DISABLE_X11_INTEGRATION
}
bool IsX11() {
if (!qApp) {
static const auto result = []() -> bool {
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
const base::Platform::XCB::Connection connection;
return connection && !xcb_connection_has_error(connection);
#else // !DESKTOP_APP_DISABLE_X11_INTEGRATION
return qEnvironmentVariableIsSet("DISPLAY");
#endif // DESKTOP_APP_DISABLE_X11_INTEGRATION
}();
return result;
}
static const bool result =
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
#if defined QT_FEATURE_xcb && QT_CONFIG(xcb)
qApp->nativeInterface<QNativeInterface::QX11Application>()
#else // xcb
false
#endif // !xcb
#else // Qt >= 6.2.0
QGuiApplication::platformName() == "xcb"
#endif // Qt < 6.2.0
;
return result;
}
bool IsWayland() {
if (!qApp) {
static const auto result = []() -> bool {
struct wl_display *(*wl_display_connect)(const char *name);
void (*wl_display_disconnect)(struct wl_display *display);
if (const auto lib = base::Platform::LoadLibrary(
"libwayland-client.so.0",
RTLD_NODELETE); lib
&& LOAD_LIBRARY_SYMBOL(lib, wl_display_connect)
&& LOAD_LIBRARY_SYMBOL(lib, wl_display_disconnect)) {
const auto display = wl_display_connect(nullptr);
if (display) {
wl_display_disconnect(display);
}
return display;
}
return qEnvironmentVariableIsSet("WAYLAND_DISPLAY");
}();
return result;
}
static const bool result =
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
#if defined QT_FEATURE_wayland && QT_CONFIG(wayland)
qApp->nativeInterface<QNativeInterface::QWaylandApplication>()
#else // wayland
false
#endif // !wayland
#else // Qt >= 6.7.0
QGuiApplication::platformName().startsWith("wayland")
#endif // Qt < 6.7.0
;
return result;
}
bool IsXwayland() {
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
static const auto result = []() -> bool {
const base::Platform::XCB::Connection connection;
if (!connection || xcb_connection_has_error(connection)) {
return false;
}
constexpr auto kXWAYLAND = "XWAYLAND";
const auto reply = base::Platform::XCB::MakeReplyPointer(
xcb_query_extension_reply(
connection,
xcb_query_extension(
connection,
strlen(kXWAYLAND),
kXWAYLAND),
nullptr));
if (!reply) {
return false;
}
return reply->present;
}();
return result;
#else // !DESKTOP_APP_DISABLE_X11_INTEGRATION
return IsX11() && qEnvironmentVariableIsSet("WAYLAND_DISPLAY");
#endif // DESKTOP_APP_DISABLE_X11_INTEGRATION
}
void Start(QJsonObject options) {
}
void Finish() {
}
} // namespace Platform

View File

@@ -0,0 +1,40 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/platform/base_platform_info.h"
namespace Platform {
inline OutdateReason WhySystemBecomesOutdated() {
return OutdateReason::IsOld;
}
inline constexpr bool IsLinux() {
return true;
}
inline constexpr bool IsWindows() { return false; }
inline constexpr bool IsWindows32Bit() { return false; }
inline constexpr bool IsWindows64Bit() { return false; }
inline constexpr bool IsWindowsARM64() { return false; }
inline constexpr bool IsWindowsStoreBuild() { return false; }
inline bool IsWindows7OrGreater() { return false; }
inline bool IsWindows8OrGreater() { return false; }
inline bool IsWindows8Point1OrGreater() { return false; }
inline bool IsWindows10OrGreater() { return false; }
inline bool IsWindows11OrGreater() { return false; }
inline constexpr bool IsMac() { return false; }
inline constexpr bool IsMacStoreBuild() { return false; }
inline bool IsMac10_12OrGreater() { return false; }
inline bool IsMac10_13OrGreater() { return false; }
inline bool IsMac10_14OrGreater() { return false; }
inline bool IsMac10_15OrGreater() { return false; }
inline bool IsMac11_0OrGreater() { return false; }
inline bool IsMac26_0OrGreater() { return false; }
} // namespace Platform

View File

@@ -0,0 +1,104 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_last_input_linux.h"
#include "base/debug_log.h"
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
#include "base/platform/linux/base_linux_xcb_utilities.h"
#include <xcb/screensaver.h>
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
#include <mutteridlemonitor/mutteridlemonitor.hpp>
namespace base::Platform {
namespace {
using namespace gi::repository;
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
std::optional<crl::time> XCBLastUserInputTime() {
const XCB::Connection connection;
if (!connection || xcb_connection_has_error(connection)) {
return std::nullopt;
}
if (!XCB::IsExtensionPresent(connection, &xcb_screensaver_id)) {
return std::nullopt;
}
const auto root = XCB::GetRootWindow(connection);
if (!root) {
return std::nullopt;
}
const auto cookie = xcb_screensaver_query_info(
connection,
root);
const auto reply = XCB::MakeReplyPointer(
xcb_screensaver_query_info_reply(
connection,
cookie,
nullptr));
if (!reply) {
return std::nullopt;
}
return (crl::now() - static_cast<crl::time>(reply->ms_since_user_input));
}
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
std::optional<crl::time> MutterDBusLastUserInputTime() {
static auto NotSupported = false;
if (NotSupported) {
return std::nullopt;
}
auto interface = MutterIdleMonitor::IdleMonitor(
MutterIdleMonitor::IdleMonitorProxy::new_for_bus_sync(
Gio::BusType::SESSION_,
Gio::DBusProxyFlags::DO_NOT_AUTO_START_AT_CONSTRUCTION_,
"org.gnome.Mutter.IdleMonitor",
"/org/gnome/Mutter/IdleMonitor/Core",
nullptr));
if (!interface) {
NotSupported = true;
return std::nullopt;
}
const auto result = interface.call_get_idletime_sync();
if (!result) {
NotSupported = true;
return std::nullopt;
}
return (crl::now() - static_cast<crl::time>(std::get<1>(*result)));
}
} // namespace
std::optional<crl::time> LastUserInputTime() {
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
const auto xcbResult = XCBLastUserInputTime();
if (xcbResult.has_value()) {
return xcbResult;
}
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
const auto mutterResult = MutterDBusLastUserInputTime();
if (mutterResult.has_value()) {
return mutterResult;
}
return std::nullopt;
}
} // namespace base::Platform

View File

@@ -0,0 +1,8 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once

View File

@@ -0,0 +1,15 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_layout_switch_linux.h"
namespace base::Platform {
bool SwitchKeyboardLayoutToEnglish() {
return false;
}
} // namespace base::Platform

View File

@@ -0,0 +1,8 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once

View File

@@ -0,0 +1,175 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_linux_allocation_tracer.h"
#include "base/debug_log.h"
#include <mutex>
#include <stdio.h>
#include <unistd.h>
void SetMallocLogger(void (*logger)(size_t, void *));
void SetVallocLogger(void (*logger)(size_t, void *));
void SetPVallocLogger(void (*logger)(size_t, void *));
void SetReallocLogger(void (*logger)(void *, size_t, void *));
void SetFreeLogger(void (*logger)(void *));
void SetMemAlignLogger(void (*logger)(size_t, size_t, void *));
void SetAlignedAllocLogger(void (*logger)(size_t, size_t, void *));
void SetPosixMemAlignLogger(void (*logger)(size_t, size_t, void *));
void SetCallocLogger(void (*logger)(size_t, size_t, void *));
namespace base::Platform {
namespace {
#ifdef DESKTOP_APP_USE_ALLOCATION_TRACER
constexpr auto kBufferSize = 1024 * 1024;
char *Buffer/* = nullptr*/;
FILE *File/* = 0*/;
char *Data/* = nullptr*/;
std::mutex Mutex;
void WriteBlock() {
if (Data > Buffer) {
fwrite(Buffer, Data - Buffer, 1, File);
fflush(File);
fdatasync(fileno(File));
Data = Buffer;
}
}
template <size_t Size>
void AppendEntry(char (&bytes)[Size]) {
std::unique_lock<std::mutex> lock(Mutex);
if (!File) {
return;
} else if (Data + Size > Buffer + kBufferSize) {
WriteBlock();
}
*reinterpret_cast<std::uint32_t*>(bytes + 1) = uint32_t(time(nullptr));
memcpy(Data, bytes, Size);
Data += Size;
}
void MallocLogger(size_t size, void *result) {
char entry[5 + sizeof(std::uint64_t) * 2];
entry[0] = 1;
*reinterpret_cast<std::uint64_t*>(entry + 5)
= static_cast<std::uint64_t>(size);
*reinterpret_cast<std::uint64_t*>(entry + 5 + sizeof(std::uint64_t))
= reinterpret_cast<std::uint64_t>(result);
AppendEntry(entry);
}
void VallocLogger(size_t size, void *result) {
MallocLogger(size, result);
}
void PVallocLogger(size_t size, void *result) {
MallocLogger(size, result);
}
void CallocLogger(size_t num, size_t size, void *result) {
MallocLogger(num * size, result);
}
void ReallocLogger(void *ptr, size_t size, void *result) {
if (!ptr) {
return MallocLogger(size, result);
}
char entry[5 + sizeof(std::uint64_t) * 3];
entry[0] = 2;
*reinterpret_cast<std::uint64_t*>(entry + 5)
= reinterpret_cast<std::uint64_t>(ptr);
*reinterpret_cast<std::uint64_t*>(entry + 5 + sizeof(std::uint64_t))
= static_cast<std::uint64_t>(size);
*reinterpret_cast<std::uint64_t*>(entry + 5 + sizeof(std::uint64_t) * 2)
= reinterpret_cast<std::uint64_t>(result);
AppendEntry(entry);
}
void MemAlignLogger(size_t alignment, size_t size, void *result) {
MallocLogger(size, result);
}
void AlignedAllocLogger(size_t alignment, size_t size, void *result) {
MallocLogger(size, result);
}
void PosixMemAlignLogger(size_t alignment, size_t size, void *result) {
MallocLogger(size, result);
}
void FreeLogger(void *ptr) {
if (ptr) {
char entry[5 + sizeof(std::uint64_t)];
entry[0] = 3;
*reinterpret_cast<std::uint64_t*>(entry + 5)
= reinterpret_cast<std::uint64_t>(ptr);
AppendEntry(entry);
}
}
void InstallLoggers() {
SetMallocLogger(MallocLogger);
SetVallocLogger(VallocLogger);
SetPVallocLogger(PVallocLogger);
SetCallocLogger(CallocLogger);
SetReallocLogger(ReallocLogger);
SetMemAlignLogger(MemAlignLogger);
SetAlignedAllocLogger(AlignedAllocLogger);
SetPosixMemAlignLogger(PosixMemAlignLogger);
SetFreeLogger(FreeLogger);
}
void RemoveLoggers() {
SetMallocLogger(nullptr);
SetVallocLogger(nullptr);
SetPVallocLogger(nullptr);
SetCallocLogger(nullptr);
SetReallocLogger(nullptr);
SetMemAlignLogger(nullptr);
SetAlignedAllocLogger(nullptr);
SetPosixMemAlignLogger(nullptr);
SetFreeLogger(nullptr);
}
#endif // DESKTOP_APP_USE_ALLOCATION_TRACER
} // namespace
void SetAllocationTracerPath(const QString &path) {
#ifdef DESKTOP_APP_USE_ALLOCATION_TRACER
Expects(!Buffer);
Data = Buffer = new char[kBufferSize];
if (!Buffer) {
return;
}
File = fopen(path.toStdString().c_str(), "wb");
if (!File) {
return;
}
InstallLoggers();
#endif // DESKTOP_APP_USE_ALLOCATION_TRACER
}
void FinishAllocationTracer() {
#ifdef DESKTOP_APP_USE_ALLOCATION_TRACER
if (File) {
RemoveLoggers();
std::unique_lock<std::mutex> lock(Mutex);
WriteBlock();
fclose(File);
File = nullptr;
}
#endif // DESKTOP_APP_USE_ALLOCATION_TRACER
}
} // namespace base::Platform

View File

@@ -0,0 +1,14 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
namespace base::Platform {
void SetAllocationTracerPath(const QString &path);
void FinishAllocationTracer();
} // namespace base::Platform

View File

@@ -0,0 +1,48 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "base/platform/linux/base_linux_app_launch_context.h"
#include "base/platform/linux/base_linux_xdp_utilities.h"
#include "base/platform/linux/base_linux_xdg_activation_token.h"
#include <gio/gio.hpp>
namespace base::Platform {
namespace internal {
namespace {
using namespace gi::repository;
class AppLaunchContext : public Gio::impl::AppLaunchContextImpl {
public:
AppLaunchContext() : Gio::impl::AppLaunchContextImpl(this) {
if (const auto parentWindowId = XDP::ParentWindowID()
; !parentWindowId.empty()) {
setenv("PARENT_WINDOW_ID", parentWindowId);
}
}
gi::cstring get_startup_notify_id_(
Gio::AppInfo,
gi::Collection<GList, ::GFile*, gi::transfer_none_t>
) noexcept override {
if (const auto token = XdgActivationToken(); !token.isNull()) {
return token.toStdString();
}
return {};
}
};
} // namespace
} // namespace internal
gi::repository::Gio::AppLaunchContext AppLaunchContext() {
return *gi::make_ref<internal::AppLaunchContext>();
}
} // namespace base::Platform

View File

@@ -0,0 +1,18 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace gi::repository::Gio {
class AppLaunchContext;
} // namespace gi::repository::Gio
namespace base::Platform {
gi::repository::Gio::AppLaunchContext AppLaunchContext();
} // namespace base::Platform

View File

@@ -0,0 +1,152 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_linux_dbus_utilities.h"
#include <xdgdbus/xdgdbus.hpp>
namespace base::Platform::DBus {
namespace {
using namespace gi::repository;
namespace GObject = gi::repository::GObject;
gi::result<XdgDBus::DBus> MakeInterface(const GDBusConnection *connection) {
return XdgDBus::DBusProxy::new_sync(
gi::wrap(connection, gi::transfer_none),
Gio::DBusProxyFlags::NONE_,
kService,
kObjectPath,
nullptr);
}
auto MakeUnexpected(GLib::Error &&error) {
return make_unexpected(std::make_unique<GLib::Error>(std::move(error)));
}
} // namespace
Result<bool> NameHasOwner(
const GDBusConnection *connection,
const std::string &name) {
auto interface = MakeInterface(connection);
if (!interface) {
return MakeUnexpected(std::move(interface.error()));
}
auto result = interface->call_name_has_owner_sync(name);
if (!result) {
return MakeUnexpected(std::move(result.error()));
}
return std::get<1>(*result);
}
Result<std::vector<std::string>> ListActivatableNames(
const GDBusConnection *connection) {
auto interface = MakeInterface(connection);
if (!interface) {
return MakeUnexpected(std::move(interface.error()));
}
auto result = interface->call_list_activatable_names_sync();
if (!result) {
return MakeUnexpected(std::move(result.error()));
}
return std::get<1>(*result) | ranges::to<std::vector<std::string>>;
}
Result<StartReply> StartServiceByName(
const GDBusConnection *connection,
const std::string &name) {
auto interface = MakeInterface(connection);
if (!interface) {
return MakeUnexpected(std::move(interface.error()));
}
auto result = interface->call_start_service_by_name_sync(name, 0);
if (!result) {
return MakeUnexpected(std::move(result.error()));
}
return StartReply(std::get<1>(*result));
}
void StartServiceByNameAsync(
const GDBusConnection *connection,
const std::string &name,
Fn<void(Fn<Result<StartReply>()>)> callback) {
auto interface = MakeInterface(connection);
if (!interface) {
const auto error = std::make_shared<GLib::Error>(
std::move(interface.error()));
callback([=]() -> Result<StartReply> {
return MakeUnexpected(std::move(*error));
});
return;
}
interface->call_start_service_by_name(name, 0, [
=,
interface = *interface
](GObject::Object source_object, Gio::AsyncResult res) mutable {
callback([=]() mutable -> Result<StartReply> {
auto result = interface.call_start_service_by_name_finish(res);
if (!result) {
return MakeUnexpected(std::move(result.error()));
}
return StartReply(std::get<1>(*result));
});
});
}
struct ServiceWatcher::Private {
XdgDBus::DBus interface;
};
ServiceWatcher::ServiceWatcher(
const GDBusConnection *connection,
const std::string &service,
Fn<void(
const std::string &,
const std::string &,
const std::string &)> callback)
: _private(std::make_unique<Private>()) {
XdgDBus::DBusProxy::new_(
gi::wrap(connection, gi::transfer_none),
Gio::DBusProxyFlags::NONE_,
kService,
kObjectPath,
nullptr,
crl::guard(this, [=](GObject::Object, Gio::AsyncResult res) {
_private->interface = XdgDBus::DBus(
XdgDBus::DBusProxy::new_finish(res, nullptr));
if (!_private->interface) {
return;
}
_private->interface.signal_name_owner_changed().connect([=](
XdgDBus::DBus,
std::string name,
std::string oldOwner,
std::string newOwner) {
if (name != service) {
return;
}
callback(name, oldOwner, newOwner);
});
}));
}
ServiceWatcher::~ServiceWatcher() = default;
} // namespace base::Platform::DBus

View File

@@ -0,0 +1,61 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/expected.h"
#include "base/weak_ptr.h"
typedef struct _GDBusConnection GDBusConnection;
namespace base::Platform::DBus {
inline constexpr auto kService = "org.freedesktop.DBus";
inline constexpr auto kObjectPath = "/org/freedesktop/DBus";
inline constexpr auto kInterface = kService;
template <typename T>
using Result = expected<T, std::unique_ptr<std::exception>>;
enum class StartReply {
Success,
AlreadyRunning,
};
Result<bool> NameHasOwner(
const GDBusConnection *connection,
const std::string &name);
Result<std::vector<std::string>> ListActivatableNames(
const GDBusConnection *connection);
Result<StartReply> StartServiceByName(
const GDBusConnection *connection,
const std::string &name);
void StartServiceByNameAsync(
const GDBusConnection *connection,
const std::string &name,
Fn<void(Fn<Result<StartReply>()>)> callback);
class ServiceWatcher : public has_weak_ptr {
public:
ServiceWatcher(
const GDBusConnection *connection,
const std::string &service,
Fn<void(
const std::string &,
const std::string &,
const std::string &)> callback);
~ServiceWatcher();
private:
struct Private;
std::unique_ptr<Private> _private;
};
} // namespace base::Platform::DBus

View File

@@ -0,0 +1,35 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_linux_library.h"
#include "base/debug_log.h"
namespace base {
namespace Platform {
LibraryHandle LoadLibrary(const char *name, int flags) {
DEBUG_LOG(("Loading '%1'...").arg(name));
if (auto lib = LibraryHandle(dlopen(name, RTLD_LAZY | flags))) {
DEBUG_LOG(("Loaded '%1'!").arg(name));
return lib;
}
LOG(("Could not load '%1'! Error: %2").arg(name, dlerror()));
return nullptr;
}
void *LoadSymbolGeneric(const LibraryHandle &lib, const char *name) {
if (!lib) {
return nullptr;
} else if (const auto result = dlsym(lib.get(), name)) {
return result;
}
LOG(("Error: failed to load '%1' function: %2").arg(name, dlerror()));
return nullptr;
}
} // namespace Platform
} // namespace base

View File

@@ -0,0 +1,38 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/custom_delete.h"
#include <dlfcn.h>
#include <memory>
#define LOAD_LIBRARY_SYMBOL(lib, func) \
::base::Platform::LoadSymbol(lib, #func, func)
namespace base {
namespace Platform {
using LibraryHandle = std::unique_ptr<void, custom_delete<dlclose>>;
LibraryHandle LoadLibrary(const char *name, int flags = 0);
[[nodiscard]] void *LoadSymbolGeneric(
const LibraryHandle &lib,
const char *name);
template <typename Function>
inline bool LoadSymbol(
const LibraryHandle &lib,
const char *name,
Function &func) {
func = reinterpret_cast<Function>(LoadSymbolGeneric(lib, name));
return (func != nullptr);
}
} // namespace Platform
} // namespace base

View File

@@ -0,0 +1,485 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_linux_xcb_utilities.h"
#include "base/qt/qt_common_adapters.h"
#include <QtCore/QAbstractEventDispatcher>
#include <QtCore/QAbstractNativeEventFilter>
#include <QtCore/QSocketNotifier>
#include <QtGui/QGuiApplication>
#if QT_VERSION < QT_VERSION_CHECK(6, 2, 0)
#include <qpa/qplatformnativeinterface.h>
#endif // Qt < 6.2.0
namespace base::Platform::XCB {
namespace {
class QtEventFilter : public QAbstractNativeEventFilter {
public:
QtEventFilter(Fn<void(xcb_generic_event_t*)> handler)
: _handler(handler) {
QCoreApplication::instance()->installNativeEventFilter(this);
}
private:
bool nativeEventFilter(
const QByteArray &eventType,
void *message,
native_event_filter_result *result) override {
_handler(reinterpret_cast<xcb_generic_event_t*>(message));
return false;
}
Fn<void(xcb_generic_event_t*)> _handler;
};
} // namespace
SharedConnection::SharedConnection()
: std::shared_ptr<CustomConnection>([] {
static std::weak_ptr<CustomConnection> Weak;
auto result = Weak.lock();
if (!result) {
Weak = result = std::make_shared<CustomConnection>();
}
return result;
}()) {}
xcb_connection_t *GetConnectionFromQt() {
#if defined QT_FEATURE_xcb && QT_CONFIG(xcb)
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
using namespace QNativeInterface;
const auto native = qApp->nativeInterface<QX11Application>();
#else // Qt >= 6.2.0
const auto native = QGuiApplication::platformNativeInterface();
#endif // Qt < 6.2.0
if (!native) {
return nullptr;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
return native->connection();
#else // Qt >= 6.2.0
return reinterpret_cast<xcb_connection_t*>(
native->nativeResourceForIntegration(QByteArray("connection")));
#endif // Qt < 6.2.0
#else // xcb
return nullptr;
#endif // !xcb
}
rpl::lifetime InstallEventHandler(
xcb_connection_t *connection,
Fn<void(xcb_generic_event_t*)> handler) {
if (!connection || xcb_connection_has_error(connection)) {
return rpl::lifetime();
}
static base::flat_map<
xcb_connection_t*,
std::pair<
std::variant<
v::null_t,
std::unique_ptr<QSocketNotifier>,
std::unique_ptr<QtEventFilter>
>,
std::vector<std::unique_ptr<Fn<void(xcb_generic_event_t*)>>>
>
> EventHandlers;
auto it = EventHandlers.find(connection);
if (it == EventHandlers.cend()) {
it = EventHandlers.emplace(connection).first;
if (connection == GetConnectionFromQt()) {
it->second.first = std::make_unique<QtEventFilter>([=](
xcb_generic_event_t *event) {
const auto it = EventHandlers.find(connection);
for (const auto &handler : it->second.second) {
(*handler)(event);
}
});
} else {
it->second.first = std::make_unique<QSocketNotifier>(
xcb_get_file_descriptor(connection),
QSocketNotifier::Read);
auto &notifier = *v::get<std::unique_ptr<QSocketNotifier>>(
it->second.first);
QObject::connect(
QCoreApplication::eventDispatcher(),
&QAbstractEventDispatcher::aboutToBlock,
&notifier,
[=] {
const auto it = EventHandlers.find(connection);
EventPointer<xcb_generic_event_t> event;
while (!xcb_connection_has_error(connection)
&& (event = MakeEventPointer(
xcb_poll_for_event(connection)))) {
for (const auto &handler : it->second.second) {
(*handler)(event.get());
}
}
// Let handlers handle the error
if (xcb_connection_has_error(connection)) {
for (const auto &handler : it->second.second) {
(*handler)(nullptr);
}
it->second.first = v::null;
}
});
notifier.setEnabled(true);
}
}
const auto ptr = it->second.second.emplace_back(new Fn(handler)).get();
return rpl::lifetime([=] {
const auto it = EventHandlers.find(connection);
it->second.second.erase(
ranges::remove(
it->second.second,
ptr,
&decltype(it->second.second)::value_type::get),
it->second.second.end());
if (it->second.second.empty()) {
EventHandlers.remove(connection);
}
});
}
xcb_timestamp_t GetTimestamp(xcb_connection_t *connection) {
if (!connection || xcb_connection_has_error(connection)) {
return XCB_CURRENT_TIME;
}
const auto window = GetRootWindow(connection);
if (!window) {
return XCB_CURRENT_TIME;
}
const auto atom = GetAtom(connection, "_DESKTOP_APP_GET_TIMESTAMP");
if (!atom) {
return XCB_CURRENT_TIME;
}
const auto eventMask = ChangeWindowEventMask(
connection,
window,
XCB_EVENT_MASK_PROPERTY_CHANGE);
if (!eventMask) {
return XCB_CURRENT_TIME;
}
QEventLoop loop;
xcb_timestamp_t timestamp = XCB_CURRENT_TIME;
const auto lifetime = InstallEventHandler(
connection,
[&](xcb_generic_event_t *event) {
if (!event) {
loop.quit();
return;
}
const auto guard = gsl::finally([&] {
free(
xcb_get_input_focus_reply(
connection,
xcb_get_input_focus(connection),
nullptr));
});
if ((event->response_type & ~0x80) != XCB_PROPERTY_NOTIFY) {
return;
}
const auto pn = reinterpret_cast<xcb_property_notify_event_t*>(
event);
if (pn->window != window || pn->atom != atom) {
return;
}
timestamp = pn->time;
loop.quit();
});
if (!lifetime) {
return XCB_CURRENT_TIME;
}
const auto error = MakeErrorPointer(
xcb_request_check(
connection,
xcb_change_property_checked(
connection,
XCB_PROP_MODE_REPLACE,
window,
atom,
XCB_ATOM_INTEGER,
32,
0,
nullptr)));
if (error) {
return XCB_CURRENT_TIME;
}
loop.exec();
return timestamp;
}
xcb_window_t GetRootWindow(xcb_connection_t *connection) {
if (!connection || xcb_connection_has_error(connection)) {
return XCB_NONE;
}
const auto screen = xcb_setup_roots_iterator(
xcb_get_setup(connection)).data;
if (!screen) {
return XCB_NONE;
}
return screen->root;
}
xcb_atom_t GetAtom(xcb_connection_t *connection, const QString &name) {
if (!connection || xcb_connection_has_error(connection)) {
return XCB_NONE;
}
const auto cookie = xcb_intern_atom(
connection,
0,
name.size(),
name.toUtf8().constData());
const auto reply = MakeReplyPointer(xcb_intern_atom_reply(
connection,
cookie,
nullptr));
if (!reply) {
return XCB_NONE;
}
return reply->atom;
}
bool IsExtensionPresent(
xcb_connection_t *connection,
xcb_extension_t *ext) {
if (!connection || xcb_connection_has_error(connection)) {
return false;
}
const auto reply = xcb_get_extension_data(
connection,
ext);
if (!reply) {
return false;
}
return reply->present;
}
std::vector<xcb_atom_t> GetWMSupported(
xcb_connection_t *connection,
xcb_window_t root) {
auto netWmAtoms = std::vector<xcb_atom_t>{};
if (!connection || xcb_connection_has_error(connection)) {
return netWmAtoms;
}
const auto supportedAtom = GetAtom(connection, "_NET_SUPPORTED");
if (!supportedAtom) {
return netWmAtoms;
}
auto offset = 0;
auto remaining = 0;
do {
const auto cookie = xcb_get_property(
connection,
false,
root,
supportedAtom,
XCB_ATOM_ATOM,
offset,
1024);
const auto reply = MakeReplyPointer(xcb_get_property_reply(
connection,
cookie,
nullptr));
if (!reply) {
break;
}
remaining = 0;
if (reply->type == XCB_ATOM_ATOM && reply->format == 32) {
const auto len = xcb_get_property_value_length(reply.get())
/ sizeof(xcb_atom_t);
const auto atoms = reinterpret_cast<xcb_atom_t*>(
xcb_get_property_value(reply.get()));
const auto s = netWmAtoms.size();
netWmAtoms.resize(s + len);
memcpy(netWmAtoms.data() + s, atoms, len * sizeof(xcb_atom_t));
remaining = reply->bytes_after;
offset += len;
}
} while (remaining > 0);
return netWmAtoms;
}
xcb_window_t GetSupportingWMCheck(
xcb_connection_t *connection,
xcb_window_t root) {
if (!connection || xcb_connection_has_error(connection)) {
return XCB_NONE;
}
const auto supportingAtom = base::Platform::XCB::GetAtom(
connection,
"_NET_SUPPORTING_WM_CHECK");
if (!supportingAtom) {
return XCB_NONE;
}
const auto cookie = xcb_get_property(
connection,
false,
root,
supportingAtom,
XCB_ATOM_WINDOW,
0,
1024);
const auto reply = MakeReplyPointer(xcb_get_property_reply(
connection,
cookie,
nullptr));
if (!reply) {
return XCB_NONE;
}
return (reply->format == 32 && reply->type == XCB_ATOM_WINDOW)
? *reinterpret_cast<xcb_window_t*>(
xcb_get_property_value(reply.get()))
: XCB_NONE;
}
bool IsSupportedByWM(xcb_connection_t *connection, const QString &atomName) {
if (!connection || xcb_connection_has_error(connection)) {
return false;
}
const auto root = GetRootWindow(connection);
if (!root) {
return false;
}
const auto atom = GetAtom(connection, atomName);
if (!atom) {
return false;
}
return ranges::contains(GetWMSupported(connection, root), atom);
}
rpl::lifetime ChangeWindowEventMask(
xcb_connection_t *connection,
xcb_window_t window,
uint mask,
ChangeWindowEventMaskMode mode,
bool revert) {
using Mode = ChangeWindowEventMaskMode;
if (!connection || xcb_connection_has_error(connection)) {
return rpl::lifetime();
}
const auto windowAttribsCookie = xcb_get_window_attributes(
connection,
window);
const auto windowAttribs = MakeReplyPointer(
xcb_get_window_attributes_reply(
connection,
windowAttribsCookie,
nullptr));
const uint oldMask = windowAttribs ? windowAttribs->your_event_mask : 0;
if ((mode == Mode::Add) && (oldMask & mask)) {
return rpl::lifetime([] {});
} else if ((mode == Mode::Remove) && !(oldMask & mask)) {
return rpl::lifetime([] {});
} else if (oldMask == mask) {
return rpl::lifetime([] {});
}
const uint value[] = {
mode == Mode::Add
? oldMask | mask
: mode == Mode::Remove
? oldMask & ~mask
: mask
};
const auto error = MakeErrorPointer(
xcb_request_check(
connection,
xcb_change_window_attributes_checked(
connection,
window,
XCB_CW_EVENT_MASK,
value)));
if (error) {
return rpl::lifetime();
}
if (!revert) {
return rpl::lifetime([] {});
}
return rpl::lifetime([=] {
if (xcb_connection_has_error(connection)) {
return;
}
const uint value[] = {
oldMask
};
free(
xcb_request_check(
connection,
xcb_change_window_attributes_checked(
connection,
window,
XCB_CW_EVENT_MASK,
value)));
});
}
} // namespace base::Platform::XCB

View File

@@ -0,0 +1,145 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/custom_delete.h"
#include <xcb/xcb.h>
namespace base::Platform::XCB {
using ConnectionPointer = std::unique_ptr<
xcb_connection_t,
custom_delete<xcb_disconnect>
>;
class CustomConnection : public ConnectionPointer {
public:
CustomConnection()
: ConnectionPointer(xcb_connect(nullptr, nullptr)) {
}
[[nodiscard]] operator xcb_connection_t*() const {
return get();
}
};
class SharedConnection : public std::shared_ptr<CustomConnection> {
public:
SharedConnection();
[[nodiscard]] operator xcb_connection_t*() const {
return get() ? get()->get() : nullptr;
}
};
template <typename T>
using EventPointer = std::unique_ptr<T, custom_delete<free>>;
template <typename T>
[[nodiscard]] EventPointer<T> MakeEventPointer(T *event) {
return EventPointer<T>(event);
}
template <typename T>
using ReplyPointer = std::unique_ptr<T, custom_delete<free>>;
template <typename T>
[[nodiscard]] ReplyPointer<T> MakeReplyPointer(T *reply) {
return ReplyPointer<T>(reply);
}
template <typename T>
using ErrorPointer = std::unique_ptr<T, custom_delete<free>>;
template <typename T>
[[nodiscard]] ErrorPointer<T> MakeErrorPointer(T *error) {
return ErrorPointer<T>(error);
}
[[nodiscard]] xcb_connection_t *GetConnectionFromQt();
[[nodiscard]] rpl::lifetime InstallEventHandler(
xcb_connection_t *connection,
Fn<void(xcb_generic_event_t*)> handler);
[[nodiscard]] xcb_timestamp_t GetTimestamp(xcb_connection_t *connection);
[[nodiscard]] xcb_window_t GetRootWindow(xcb_connection_t *connection);
[[nodiscard]] xcb_atom_t GetAtom(
xcb_connection_t *connection,
const QString &name);
[[nodiscard]] bool IsExtensionPresent(
xcb_connection_t *connection,
xcb_extension_t *ext);
[[nodiscard]] std::vector<xcb_atom_t> GetWMSupported(
xcb_connection_t *connection,
xcb_window_t root);
[[nodiscard]] xcb_window_t GetSupportingWMCheck(
xcb_connection_t *connection,
xcb_window_t root);
[[nodiscard]] bool IsSupportedByWM(
xcb_connection_t *connection,
const QString &atomName);
enum class ChangeWindowEventMaskMode {
Add,
Remove,
Replace,
};
[[nodiscard]] rpl::lifetime ChangeWindowEventMask(
xcb_connection_t *connection,
xcb_window_t window,
uint mask,
ChangeWindowEventMaskMode mode = ChangeWindowEventMaskMode::Add,
bool revert = true);
class Connection {
public:
Connection()
: _qtConnection(GetConnectionFromQt())
, _sharedConnection(_qtConnection
? std::optional<SharedConnection>()
: std::optional<SharedConnection>(std::in_place)) {
}
[[nodiscard]] operator xcb_connection_t*() const {
return _sharedConnection
? static_cast<xcb_connection_t*>(*_sharedConnection)
: _qtConnection;
}
private:
xcb_connection_t * const _qtConnection = nullptr;
const std::optional<SharedConnection> _sharedConnection;
};
template <typename Object, auto constructor, auto destructor>
class ObjectWithConnection
: public std::unique_ptr<Object, custom_delete<destructor>> {
public:
ObjectWithConnection() {
if (_connection && !xcb_connection_has_error(_connection)) {
this->reset(constructor(_connection));
}
}
~ObjectWithConnection() {
this->reset(nullptr);
}
private:
const Connection _connection;
};
} // namespace base::Platform::XCB

View File

@@ -0,0 +1,65 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_linux_xdg_activation_token.h"
#include "base/invoke_queued.h"
#include <QtGui/QGuiApplication>
#include <QtGui/QWindow>
#include <qpa/qplatformwindow_p.h>
namespace base::Platform {
void RunWithXdgActivationToken(Fn<void(QString)> callback) {
#if defined QT_FEATURE_wayland && QT_CONFIG(wayland)
const auto window = QGuiApplication::focusWindow();
if (!window) {
callback({});
return;
} else if (!window->isVisible()) {
InvokeQueued(qApp, [=] {
RunWithXdgActivationToken(callback);
});
return;
}
using namespace QNativeInterface;
using namespace QNativeInterface::Private;
const auto native = qApp->nativeInterface<QWaylandApplication>();
const auto nativeWindow = window->nativeInterface<QWaylandWindow>();
if (!native || !nativeWindow) {
callback({});
return;
}
QObject::connect(
nativeWindow,
&QWaylandWindow::xdgActivationTokenCreated,
nativeWindow,
callback,
Qt::SingleShotConnection);
nativeWindow->requestXdgActivationToken(native->lastInputSerial());
#else // wayland
callback({});
#endif // !wayland
}
QString XdgActivationToken() {
QString result;
QEventLoop loop;
InvokeQueued(qApp, [&] {
RunWithXdgActivationToken([&](QString token) {
result = token;
loop.quit();
});
});
loop.exec();
return result;
}
} // namespace base::Platform

View File

@@ -0,0 +1,14 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
namespace base::Platform {
void RunWithXdgActivationToken(Fn<void(QString)> callback);
QString XdgActivationToken();
} // namespace base::Platform

View File

@@ -0,0 +1,122 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_linux_xdp_utilities.h"
#include "base/platform/base_platform_info.h"
#include <xdpsettings/xdpsettings.hpp>
#include <QtGui/QGuiApplication>
#include <QtGui/QWindow>
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
#include <qpa/qplatformintegration.h>
#include <private/qguiapplication_p.h>
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
#include <private/qdesktopunixservices_p.h>
#else // Qt >= 6.9.0
#include <private/qgenericunixservices_p.h>
#endif // Qt < 6.9.0
#endif // Qt >= 6.5.0
#include <sstream>
namespace base::Platform::XDP {
namespace {
using namespace gi::repository;
namespace GObject = gi::repository::GObject;
} // namespace
std::string ParentWindowID() {
return ParentWindowID(QGuiApplication::focusWindow());
}
std::string ParentWindowID(QWindow *window) {
if (!window) {
return {};
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
using QDesktopUnixServices = QGenericUnixServices;
#endif // Qt < 6.9.0
if (const auto services = dynamic_cast<QDesktopUnixServices*>(
QGuiApplicationPrivate::platformIntegration()->services())) {
return services->portalWindowIdentifier(window).toStdString();
}
#endif // Qt >= 6.5.0
if (::Platform::IsX11()) {
std::stringstream result;
result << "x11:" << std::hex << window->winId();
return result.str();
}
return {};
}
gi::result<GLib::Variant> ReadSetting(
const std::string &group,
const std::string &key) {
auto interface = gi::result<XdpSettings::Settings>(
XdpSettings::SettingsProxy::new_for_bus_sync(
Gio::BusType::SESSION_,
Gio::DBusProxyFlags::NONE_,
kService,
kObjectPath));
if (!interface) {
return make_unexpected(std::move(interface.error()));
}
auto result = interface->call_read_one_sync(group, key);
if (!result) {
return make_unexpected(std::move(result.error()));
}
return std::get<1>(*result).get_variant();
}
class SettingWatcher::Private {
public:
XdpSettings::Settings interface;
};
SettingWatcher::SettingWatcher(
Fn<void(
const std::string &,
const std::string &,
GLib::Variant)> callback)
: _private(std::make_unique<Private>()) {
XdpSettings::SettingsProxy::new_for_bus(
Gio::BusType::SESSION_,
Gio::DBusProxyFlags::NONE_,
kService,
kObjectPath,
crl::guard(this, [=](GObject::Object, Gio::AsyncResult res) {
_private->interface = XdpSettings::Settings(
XdpSettings::SettingsProxy::new_for_bus_finish(res, nullptr));
if (!_private->interface) {
return;
}
_private->interface.signal_setting_changed().connect([=](
XdpSettings::Settings,
std::string group,
std::string key,
GLib::Variant value) {
callback(group, key, value.get_variant());
});
}));
}
SettingWatcher::~SettingWatcher() = default;
} // namespace base::Platform::XDP

View File

@@ -0,0 +1,158 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/expected.h"
#include "base/weak_ptr.h"
#include <glib/glib.hpp>
#if __has_include(<glibmm.h>)
#include <glibmm.h>
#endif // __has_include(<glibmm.h>)
class QWindow;
namespace base::Platform::XDP {
inline constexpr auto kService = "org.freedesktop.portal.Desktop";
inline constexpr auto kObjectPath = "/org/freedesktop/portal/desktop";
inline constexpr auto kRequestInterface = "org.freedesktop.portal.Request";
inline constexpr auto kSettingsInterface = "org.freedesktop.portal.Settings";
std::string ParentWindowID();
std::string ParentWindowID(QWindow *window);
gi::result<gi::repository::GLib::Variant> ReadSetting(
const std::string &group,
const std::string &key);
#if __has_include(<glibmm.h>)
template <typename T>
gi::result<T> ReadSetting(
const std::string &group,
const std::string &key) {
try {
auto value = ReadSetting(group, key);
if (value) {
return Glib::wrap(value->gobj_copy_()).get_dynamic<T>();
}
return make_unexpected(std::move(value.error()));
} catch (std::invalid_argument &e) {
return make_unexpected(
std::make_unique<std::invalid_argument>(std::move(e)));
} catch (std::bad_cast &e) {
return make_unexpected(std::make_unique<std::bad_cast>(std::move(e)));
} catch (...) {
return make_unexpected(std::make_unique<std::exception>());
}
}
#endif
class SettingWatcher : public has_weak_ptr {
public:
SettingWatcher(
Fn<void(
const std::string &,
const std::string &,
gi::repository::GLib::Variant)> callback);
SettingWatcher(
const std::string &group,
const std::string &key,
Fn<void(gi::repository::GLib::Variant)> callback)
: SettingWatcher([=](
const std::string &group2,
const std::string &key2,
gi::repository::GLib::Variant value) {
if (group == group2 && key == key2) {
callback(value);
}
}) {
}
SettingWatcher(
const std::string &group,
const std::string &key,
Fn<void()> callback)
: SettingWatcher(group, key, [=](gi::repository::GLib::Variant) {
callback();
}) {
}
#if __has_include(<glibmm.h>)
template <typename ...Args>
SettingWatcher(
Fn<void(
const std::string &,
const std::string &,
Args...)> callback)
: SettingWatcher([=](
const std::string &group,
const std::string &key,
gi::repository::GLib::Variant value) {
if constexpr (sizeof...(Args) == 0) {
callback(group, key);
return;
}
try {
using Tuple = std::tuple<std::decay_t<Args>...>;
if constexpr (sizeof...(Args) == 1) {
using Arg0 = std::tuple_element_t<0, Tuple>;
callback(
group,
key,
Glib::wrap(value.gobj_copy_()).get_dynamic<Arg0>());
} else {
std::apply(
callback,
std::tuple_cat(
std::forward_as_tuple(group, key),
Glib::wrap(value.gobj_copy_()).get_dynamic<Tuple>()));
}
} catch (...) {
}
}) {
}
template <typename Callback>
SettingWatcher(Callback callback)
: SettingWatcher(std::function(callback)) {
}
template <typename ...Args>
SettingWatcher(
const std::string &group,
const std::string &key,
Fn<void(Args...)> callback)
: SettingWatcher([=](
const std::string &group2,
const std::string &key2,
Args &&...value) {
if (group == group2 && key == key2) {
callback(std::forward<decltype(value)>(value)...);
}
}) {
}
template <typename Callback>
SettingWatcher(
const std::string &group,
const std::string &key,
Callback callback)
: SettingWatcher(group, key, std::function(callback)) {
}
#endif // __has_include(<glibmm.h>)
~SettingWatcher();
private:
class Private;
const std::unique_ptr<Private> _private;
};
} // namespace base::Platform::XDP

View File

@@ -0,0 +1,332 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_linux_xsettings.h"
#include <QtCore/QByteArray>
#include <QtCore/QtEndian>
#include <QtGui/QColor>
#include <vector>
#include <algorithm>
namespace base::Platform::XCB {
/* Implementation of http://standards.freedesktop.org/xsettings-spec/xsettings-0.5.html */
class XSettings::PropertyValue {
public:
PropertyValue() {
}
void updateValue(
xcb_connection_t *connection,
const QByteArray &name,
const QVariant &value,
int last_change_serial) {
if (last_change_serial <= this->last_change_serial)
return;
this->value = value;
this->last_change_serial = last_change_serial;
for (const auto &callback : callback_links)
(*callback)(connection, name, value);
}
rpl::lifetime addCallback(PropertyChangeFunc func) {
const auto handle = Instance();
callback_links.push_back(std::make_unique<PropertyChangeFunc>(func));
const auto ptr = callback_links.back().get();
return rpl::lifetime([=] {
(void)handle;
callback_links.erase(
ranges::remove(
callback_links,
ptr,
&decltype(callback_links)::value_type::get),
callback_links.end());
});
}
QVariant value;
int last_change_serial = -1;
std::vector<std::unique_ptr<PropertyChangeFunc>> callback_links;
};
class XSettings::Private {
public:
Private() {
}
QByteArray getSettings() {
int offset = 0;
QByteArray settings;
const auto _xsettings_atom = GetAtom(
connection,
"_XSETTINGS_SETTINGS");
if (!_xsettings_atom)
return settings;
while (1) {
const auto cookie = xcb_get_property(
connection,
false,
x_settings_window,
_xsettings_atom,
_xsettings_atom,
offset/4,
8192);
const auto reply = MakeReplyPointer(xcb_get_property_reply(
connection,
cookie,
nullptr));
bool more = false;
if (!reply)
return settings;
const auto property_value_length = xcb_get_property_value_length(
reply.get());
settings.append(
static_cast<const char *>(xcb_get_property_value(
reply.get())),
property_value_length);
offset += property_value_length;
more = reply->bytes_after != 0;
if (!more)
break;
}
return settings;
}
static int round_to_nearest_multiple_of_4(int value) {
int remainder = value % 4;
if (!remainder)
return value;
return value + 4 - remainder;
}
void populateSettings(const QByteArray &xSettings) {
if (xSettings.length() < 12)
return;
char byteOrder = xSettings.at(0);
if (byteOrder != XCB_IMAGE_ORDER_LSB_FIRST
&& byteOrder != XCB_IMAGE_ORDER_MSB_FIRST) {
qWarning("ByteOrder byte %d not 0 or 1", byteOrder);
return;
}
#define ADJUST_BO(b, t, x) \
((b == XCB_IMAGE_ORDER_LSB_FIRST) ? \
qFromLittleEndian<t>(x) : \
qFromBigEndian<t>(x))
#define VALIDATE_LENGTH(x) \
if ((size_t)xSettings.length() < (offset + local_offset + 12 + x)) { \
qWarning("Length %d runs past end of data", x); \
return; \
}
uint number_of_settings = ADJUST_BO(
byteOrder,
quint32,
xSettings.mid(8,4).constData());
const char *data = xSettings.constData() + 12;
size_t offset = 0;
for (uint i = 0; i < number_of_settings; i++) {
int local_offset = 0;
VALIDATE_LENGTH(2);
Type type = static_cast<Type>(
*reinterpret_cast<const quint8 *>(data + offset));
local_offset += 2;
VALIDATE_LENGTH(2);
quint16 name_len = ADJUST_BO(
byteOrder,
quint16,
data + offset + local_offset);
local_offset += 2;
VALIDATE_LENGTH(name_len);
QByteArray name(data + offset + local_offset, name_len);
local_offset += round_to_nearest_multiple_of_4(name_len);
VALIDATE_LENGTH(4);
int last_change_serial = ADJUST_BO(
byteOrder,
qint32,
data + offset + local_offset);
Q_UNUSED(last_change_serial);
local_offset += 4;
QVariant value;
if (type == Type::String) {
VALIDATE_LENGTH(4);
int value_length = ADJUST_BO(
byteOrder,
qint32,
data + offset + local_offset);
local_offset+=4;
VALIDATE_LENGTH(value_length);
QByteArray value_string(
data + offset + local_offset,
value_length);
value.setValue(value_string);
local_offset += round_to_nearest_multiple_of_4(value_length);
} else if (type == Type::Integer) {
VALIDATE_LENGTH(4);
int value_length = ADJUST_BO(
byteOrder,
qint32,
data + offset + local_offset);
local_offset += 4;
value.setValue(value_length);
} else if (type == Type::Color) {
VALIDATE_LENGTH(2*4);
quint16 red = ADJUST_BO(
byteOrder,
quint16,
data + offset + local_offset);
local_offset += 2;
quint16 green = ADJUST_BO(
byteOrder,
quint16,
data + offset + local_offset);
local_offset += 2;
quint16 blue = ADJUST_BO(
byteOrder,
quint16,
data + offset + local_offset);
local_offset += 2;
quint16 alpha = ADJUST_BO(
byteOrder,
quint16,
data + offset + local_offset);
local_offset += 2;
QColor color_value(red,green,blue,alpha);
value.setValue(color_value);
}
offset += local_offset;
settings[name].updateValue(
connection,
name,
value,
last_change_serial);
}
}
const Connection connection;
xcb_window_t x_settings_window = XCB_NONE;
base::flat_map<QByteArray, PropertyValue> settings;
bool initialized = false;
rpl::lifetime lifetime;
};
XSettings::XSettings()
: _private(std::make_unique<Private>()) {
if (!_private->connection)
return;
if (xcb_connection_has_error(_private->connection))
return;
const auto selection_owner_atom = GetAtom(
_private->connection,
"_XSETTINGS_S0");
if (!selection_owner_atom)
return;
const auto selection_cookie = xcb_get_selection_owner(
_private->connection,
selection_owner_atom);
const auto selection_result = MakeReplyPointer(
xcb_get_selection_owner_reply(
_private->connection,
selection_cookie,
nullptr));
if (!selection_result)
return;
_private->x_settings_window = selection_result->owner;
if (!_private->x_settings_window)
return;
auto event_handler = InstallEventHandler(
_private->connection,
[=](xcb_generic_event_t *event) {
if (!event)
return;
const auto response_type = event->response_type & ~0x80;
if (response_type != XCB_PROPERTY_NOTIFY)
return;
const auto pn = reinterpret_cast<xcb_property_notify_event_t*>(
event);
if (pn->window != _private->x_settings_window)
return;
_private->populateSettings(_private->getSettings());
});
if (!event_handler)
return;
_private->lifetime.add(std::move(event_handler));
auto event_mask = ChangeWindowEventMask(
_private->connection,
_private->x_settings_window,
XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE);
if (!event_mask)
return;
_private->lifetime.add(std::move(event_mask));
_private->populateSettings(_private->getSettings());
_private->initialized = true;
}
XSettings::~XSettings() = default;
std::shared_ptr<XSettings> XSettings::Instance() {
static std::weak_ptr<XSettings> Weak;
auto result = Weak.lock();
if (!result) {
Weak = result = std::shared_ptr<XSettings>(
new XSettings,
[](XSettings *ptr) { delete ptr; });
}
return result;
}
bool XSettings::initialized() const {
return _private->initialized;
}
rpl::lifetime XSettings::registerCallbackForProperty(
const QByteArray &property,
PropertyChangeFunc func) {
return _private->settings[property].addCallback(func);
}
QVariant XSettings::setting(const QByteArray &property) const {
return _private->settings[property].value;
}
} // namespace base::Platform::XCB

View File

@@ -0,0 +1,45 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/platform/linux/base_linux_xcb_utilities.h"
namespace base::Platform::XCB {
class XSettings {
public:
[[nodiscard]] static std::shared_ptr<XSettings> Instance();
[[nodiscard]] bool initialized() const;
[[nodiscard]] QVariant setting(const QByteArray &property) const;
using PropertyChangeFunc = Fn<void(
xcb_connection_t *connection,
const QByteArray &name,
const QVariant &property)>;
[[nodiscard]] rpl::lifetime registerCallbackForProperty(
const QByteArray &property,
PropertyChangeFunc func);
private:
XSettings();
~XSettings();
enum class Type {
Integer,
String,
Color,
};
class PropertyValue;
class Private;
const std::unique_ptr<Private> _private;
};
} // namespace base::Platform::XCB

View File

@@ -0,0 +1,15 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/base_platform_network_reachability.h"
namespace base::Platform {
std::unique_ptr<NetworkReachability> NetworkReachability::Create() {
return nullptr;
}
} // namespace base::Platform

View File

@@ -0,0 +1,214 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_power_save_blocker_linux.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/linux/base_linux_xdp_utilities.h"
#include "base/timer_rpl.h"
#include "base/weak_ptr.h"
#include "base/random.h"
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
#include "base/platform/linux/base_linux_xcb_utilities.h"
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
#include <QtGui/QWindow>
#include <xdpinhibit/xdpinhibit.hpp>
#include <xdprequest/xdprequest.hpp>
namespace base::Platform {
namespace {
using namespace gi::repository;
namespace GObject = gi::repository::GObject;
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
constexpr auto kResetScreenSaverTimeout = 10 * crl::time(1000);
// Use the basic reset API
// due to https://gitlab.freedesktop.org/xorg/xserver/-/issues/363
void XCBPreventDisplaySleep(bool prevent) {
static rpl::lifetime lifetime;
if (!prevent) {
lifetime.destroy();
return;
} else if (lifetime) {
return;
}
timer_each(
kResetScreenSaverTimeout
) | rpl::map([connection = XCB::Connection()] {
return connection;
}) | rpl::filter([](xcb_connection_t *connection) {
return connection && !xcb_connection_has_error(connection);
}) | rpl::on_next([](xcb_connection_t *connection) {
free(
xcb_request_check(
connection,
xcb_force_screen_saver_checked(
connection,
XCB_SCREEN_SAVER_RESET)));
}, lifetime);
}
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
/* https://flatpak.github.io/xdg-desktop-portal/#gdbus-org.freedesktop.portal.Inhibit
* 1: Logout
* 2: User Switch
* 4: Suspend
* 8: Idle
*/
class PortalInhibit final : public has_weak_ptr {
public:
PortalInhibit(uint flags = 0, const QString &description = {}) {
XdpInhibit::InhibitProxy::new_for_bus(
Gio::BusType::SESSION_,
Gio::DBusProxyFlags::NONE_,
XDP::kService,
XDP::kObjectPath,
crl::guard(this, [=](GObject::Object, Gio::AsyncResult res) {
auto proxy = XdpInhibit::InhibitProxy::new_for_bus_finish(
res,
nullptr);
if (!proxy) {
return;
}
const auto handleToken = "desktop_app"
+ std::to_string(RandomValue<uint>());
auto uniqueName = std::string(
proxy.get_connection().get_unique_name());
uniqueName.erase(0, 1);
uniqueName.replace(uniqueName.find('.'), 1, 1, '_');
XdpRequest::RequestProxy::new_(
proxy.get_connection(),
Gio::DBusProxyFlags::NONE_,
XDP::kService,
XDP::kObjectPath
+ std::string("/request/")
+ uniqueName
+ '/'
+ handleToken,
nullptr,
crl::guard(this, [=](
GObject::Object,
Gio::AsyncResult res) mutable {
_request = XdpRequest::RequestProxy::new_finish(
res,
nullptr);
auto request = _request; // take a ref
const auto weak = make_weak(this);
const auto signalId = std::make_shared<ulong>();
*signalId = _request.signal_response().connect([=](
XdpRequest::Request,
guint,
GLib::Variant) mutable {
if (!weak) {
request.call_close(nullptr);
}
request.disconnect(*signalId);
});
XdpInhibit::Inhibit(proxy).call_inhibit(
XDP::ParentWindowID(),
flags,
GLib::Variant::new_array({
GLib::Variant::new_dict_entry(
GLib::Variant::new_string("handle_token"),
GLib::Variant::new_variant(
GLib::Variant::new_string(
handleToken))),
GLib::Variant::new_dict_entry(
GLib::Variant::new_string("reason"),
GLib::Variant::new_variant(
GLib::Variant::new_string(
description.toStdString()))),
}),
nullptr);
}));
}));
}
~PortalInhibit() {
if (!_request) {
return;
}
_request.call_close(nullptr);
}
private:
XdpRequest::Request _request;
};
void PortalPreventDisplaySleep(
bool prevent,
const QString &description = {}) {
static std::optional<PortalInhibit> instance;
if (prevent && !instance) {
instance.emplace(8 /* Idle */, description);
} else if (!prevent && instance) {
instance.reset();
}
}
void PortalPreventAppSuspension(
bool prevent,
const QString &description = {}) {
static std::optional<PortalInhibit> instance;
if (prevent && !instance) {
instance.emplace(4 /* Suspend */, description);
} else if (!prevent && instance) {
instance.reset();
}
}
} // namespace
void BlockPowerSave(
PowerSaveBlockType type,
const QString &description,
QPointer<QWindow> window) {
crl::on_main([=] {
switch (type) {
case PowerSaveBlockType::PreventAppSuspension:
PortalPreventAppSuspension(true, description);
break;
case PowerSaveBlockType::PreventDisplaySleep:
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
XCBPreventDisplaySleep(true);
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
PortalPreventDisplaySleep(true, description);
break;
}
});
}
void UnblockPowerSave(PowerSaveBlockType type, QPointer<QWindow> window) {
crl::on_main([=] {
switch (type) {
case PowerSaveBlockType::PreventAppSuspension:
PortalPreventAppSuspension(false);
break;
case PowerSaveBlockType::PreventDisplaySleep:
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
XCBPreventDisplaySleep(false);
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
PortalPreventDisplaySleep(false);
break;
}
});
}
} // namespace base::Platform

View File

@@ -0,0 +1,9 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/platform/base_platform_power_save_blocker.h"

View File

@@ -0,0 +1,110 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_process_linux.h"
#include "base/platform/base_platform_info.h"
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
#include "base/platform/linux/base_linux_xcb_utilities.h"
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
#include <QtGui/QGuiApplication>
namespace base::Platform {
namespace {
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
void XCBActivateWindow(WId window) {
const XCB::Connection connection;
if (!connection || xcb_connection_has_error(connection)) {
return;
}
const auto root = XCB::GetRootWindow(connection);
if (!root) {
return;
}
const auto timestamp = XCB::GetTimestamp(connection);
if (!timestamp) {
return;
}
const auto activeWindowAtom = XCB::GetAtom(
connection,
"_NET_ACTIVE_WINDOW");
if (!activeWindowAtom) {
return;
}
const auto focusWindow = QGuiApplication::focusWindow();
// map the window first
free(
xcb_request_check(
connection,
xcb_map_window_checked(connection, window)));
// now raise (restack) the window
uint32_t values[] = { XCB_STACK_MODE_ABOVE };
free(
xcb_request_check(
connection,
xcb_configure_window_checked(
connection,
window,
XCB_CONFIG_WINDOW_STACK_MODE,
values)));
// and, finally, make it the active window
xcb_client_message_event_t xev;
xev.response_type = XCB_CLIENT_MESSAGE;
xev.format = 32;
xev.sequence = 0;
xev.window = window;
xev.type = activeWindowAtom;
xev.data.data32[0] = 1; // source: 1=application 2=pager
xev.data.data32[1] = timestamp; // timestamp
xev.data.data32[2] = focusWindow // currently active window
? focusWindow->winId()
: XCB_NONE;
xev.data.data32[3] = 0;
xev.data.data32[4] = 0;
free(
xcb_request_check(
connection,
xcb_send_event_checked(
connection,
false,
root,
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
| XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY,
reinterpret_cast<const char *>(&xev))));
}
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
} // namespace
void ActivateProcessWindow(int64 pid, WId windowId) {
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
if (::Platform::IsX11()) {
XCBActivateWindow(windowId);
}
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
}
void ActivateThisProcessWindow(WId windowId) {
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
if (::Platform::IsX11()) {
XCBActivateWindow(windowId);
}
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
}
} // namespace base::Platform

View File

@@ -0,0 +1,9 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/platform/base_platform_process.h"

View File

@@ -0,0 +1,485 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/base_platform_system_media_controls.h"
#include "base/integration.h"
#include "base/platform/base_platform_info.h" // IsWayland
#include <mpris/mpris.hpp>
#include <QtCore/QBuffer>
#include <QtGui/QGuiApplication>
#include <QtGui/QImage>
#include <ksandbox.h>
namespace base::Platform {
namespace {
using namespace gi::repository;
namespace GObject = gi::repository::GObject;
// QString to GLib::Variant.
inline auto Q2V(const QString &s) {
return GLib::Variant::new_string(s.toStdString());
}
std::string ConvertPlaybackStatus(
SystemMediaControls::PlaybackStatus status) {
using Status = SystemMediaControls::PlaybackStatus;
switch (status) {
case Status::Playing: return "Playing";
case Status::Paused: return "Paused";
case Status::Stopped: return "Stopped";
}
Unexpected("ConvertPlaybackStatus in SystemMediaControls");
}
std::string ConvertLoopStatus(SystemMediaControls::LoopStatus status) {
using Status = SystemMediaControls::LoopStatus;
switch (status) {
case Status::None: return "None";
case Status::Track: return "Track";
case Status::Playlist: return "Playlist";
}
Unexpected("ConvertLoopStatus in SystemMediaControls");
}
auto EventToCommand(const std::string &event) {
using Command = SystemMediaControls::Command;
if (event == "Pause") {
return Command::Pause;
} else if (event == "Play") {
return Command::Play;
} else if (event == "Stop") {
return Command::Stop;
} else if (event == "PlayPause") {
return Command::PlayPause;
} else if (event == "Next") {
return Command::Next;
} else if (event == "Previous") {
return Command::Previous;
} else if (event == "Quit") {
return Command::Quit;
} else if (event == "Raise") {
return Command::Raise;
}
Unexpected("EventToCommand in SystemMediaControls");
}
auto LoopStatusToCommand(const std::string &status) {
using Command = SystemMediaControls::Command;
if (status == "None") {
return Command::LoopNone;
} else if (status == "Track") {
return Command::LoopTrack;
} else if (status == "Playlist") {
return Command::LoopPlaylist;
}
return Command::None;
}
} // namespace
struct SystemMediaControls::Private : public Mpris::MediaPlayer2 {
public:
struct PlayerData {
int64 duration = 0;
bool inSetShuffle = false;
};
Private();
void init();
void deinit();
[[nodiscard]] bool dbusAvailable() {
return static_cast<bool>(_dbus.connection);
}
[[nodiscard]] Mpris::MediaPlayer2Player player() {
return *_player;
}
[[nodiscard]] PlayerData &playerData() {
return _playerData;
}
[[nodiscard]] rpl::producer<Command> commandRequests() const {
return _commandRequests.events();
}
[[nodiscard]] rpl::producer<int64> seekRequests() const {
return _seekRequests.events();
}
[[nodiscard]] rpl::producer<float64> volumeChangeRequests() const {
return _volumeChangeRequests.events();
}
[[nodiscard]] rpl::producer<> updatePositionRequests() const {
return _updatePositionRequests.events();
}
private:
class Player : public Mpris::impl::MediaPlayer2PlayerSkeletonImpl {
public:
struct DefinitionData {
GI_DEFINES_MEMBER(
Gio::impl::internal::DBusInterfaceSkeletonClassDef,
get_info,
true)
GI_DEFINES_MEMBER(
Gio::impl::internal::DBusInterfaceIfaceDef,
get_info,
false)
};
Player(not_null<Private*> parent)
: Mpris::impl::MediaPlayer2PlayerSkeletonImpl(this)
, _parent(parent)
, _position(this) {
}
private:
const not_null<Private*> _parent;
class Position : public gi::property<int64> {
public:
Position(not_null<Player*> parent)
: gi::property<int64>(parent, "position")
, _parent(parent) {
}
protected:
void get_property(GValue *value) override {
// prevent recursion via SystemMediaControls::setPosition
if (!_updating) {
_updating = true;
_parent->_parent->_updatePositionRequests.fire({});
_updating = false;
}
gi::property<int64>::get_property(value);
}
private:
const not_null<Player*> _parent;
bool _updating = false;
} _position;
};
gi::ref_ptr<Player> _player;
struct {
Gio::DBusConnection connection;
Gio::DBusObjectManagerServer objectManager;
uint ownId = 0;
} _dbus;
PlayerData _playerData;
rpl::event_stream<Command> _commandRequests;
rpl::event_stream<int64> _seekRequests;
rpl::event_stream<float64> _volumeChangeRequests;
rpl::event_stream<> _updatePositionRequests;
};
SystemMediaControls::Private::Private()
: Mpris::MediaPlayer2(Mpris::MediaPlayer2Skeleton::new_())
, _player(gi::make_ref<Player>(this))
, _dbus({
.connection = Gio::bus_get_sync(Gio::BusType::SESSION_, nullptr)
}) {
set_can_quit(true);
set_can_raise(!::Platform::IsWayland());
set_desktop_entry(QGuiApplication::desktopFileName().toStdString());
set_identity(QGuiApplication::desktopFileName().toStdString());
player().set_can_control(true);
player().set_can_seek(true);
player().set_maximum_rate(1.0);
player().set_minimum_rate(1.0);
player().set_playback_status("Stopped");
player().set_loop_status("None");
player().set_rate(1.0);
const auto executeCommand = [=](
GObject::Object,
Gio::DBusMethodInvocation invocation) {
base::Integration::Instance().enterFromEventLoop([&] {
_commandRequests.fire_copy(
EventToCommand(invocation.get_method_name()));
});
invocation.return_value();
return true;
};
signal_handle_quit().connect(executeCommand);
signal_handle_raise().connect(executeCommand);
player().signal_handle_next().connect(executeCommand);
player().signal_handle_pause().connect(executeCommand);
player().signal_handle_play().connect(executeCommand);
player().signal_handle_play_pause().connect(executeCommand);
player().signal_handle_previous().connect(executeCommand);
player().signal_handle_stop().connect(executeCommand);
player().signal_handle_seek().connect([=](
Mpris::MediaPlayer2Player,
Gio::DBusMethodInvocation invocation,
int64 offset) {
base::Integration::Instance().enterFromEventLoop([&] {
_seekRequests.fire_copy(
player().property_position().get() + offset);
});
player().complete_seek(invocation);
return true;
});
player().signal_handle_set_position().connect([=](
Mpris::MediaPlayer2Player,
Gio::DBusMethodInvocation invocation,
const std::string &trackId,
int64 position) {
base::Integration::Instance().enterFromEventLoop([&] {
_seekRequests.fire_copy(position);
});
player().complete_set_position(invocation);
return true;
});
player().property_loop_status().signal_notify().connect([=](
GObject::Object,
GObject::ParamSpec) {
base::Integration::Instance().enterFromEventLoop([&] {
_commandRequests.fire_copy(
LoopStatusToCommand(player().get_loop_status()));
});
});
player().property_shuffle().signal_notify().connect([=](
GObject::Object,
GObject::ParamSpec) {
if (playerData().inSetShuffle) {
return;
}
base::Integration::Instance().enterFromEventLoop([&] {
_commandRequests.fire_copy(Command::Shuffle);
});
});
player().property_volume().signal_notify().connect([=](
GObject::Object,
GObject::ParamSpec) {
base::Integration::Instance().enterFromEventLoop([&] {
_volumeChangeRequests.fire_copy(player().get_volume());
});
});
}
void SystemMediaControls::Private::init() {
if (!_dbus.connection || _dbus.ownId) {
return;
}
auto object = Mpris::ObjectSkeleton::new_("/org/mpris/MediaPlayer2");
object.set_media_player2(*this);
object.set_media_player2_player(player());
_dbus.objectManager = Gio::DBusObjectManagerServer::new_("/org/mpris");
_dbus.objectManager.export_(object);
_dbus.objectManager.set_connection(_dbus.connection);
_dbus.ownId = Gio::bus_own_name_on_connection(
_dbus.connection,
"org.mpris.MediaPlayer2." + (KSandbox::isFlatpak()
? qEnvironmentVariable("FLATPAK_ID").toStdString()
: KSandbox::isSnap()
? qEnvironmentVariable("SNAP_INSTANCE_NAME").toStdString()
: QCoreApplication::applicationName().toStdString()),
Gio::BusNameOwnerFlags::NONE_);
}
void SystemMediaControls::Private::deinit() {
if (_dbus.ownId) {
Gio::bus_unown_name(_dbus.ownId);
_dbus.ownId = 0;
}
_dbus.objectManager = {};
}
SystemMediaControls::SystemMediaControls()
: _private(std::make_unique<Private>()) {
}
SystemMediaControls::~SystemMediaControls() {
_private->deinit();
}
bool SystemMediaControls::init() {
clearMetadata();
return _private->dbusAvailable();
}
void SystemMediaControls::setApplicationName(const QString &name) {
_private->set_identity(name.toStdString());
}
void SystemMediaControls::setEnabled(bool enabled) {
if (enabled) {
_private->init();
} else {
_private->deinit();
}
}
void SystemMediaControls::setIsNextEnabled(bool value) {
_private->player().set_can_go_next(value);
}
void SystemMediaControls::setIsPreviousEnabled(bool value) {
_private->player().set_can_go_previous(value);
}
void SystemMediaControls::setIsPlayPauseEnabled(bool value) {
_private->player().set_can_play(value);
_private->player().set_can_pause(value);
}
void SystemMediaControls::setIsStopEnabled(bool value) {
}
void SystemMediaControls::setPlaybackStatus(PlaybackStatus status) {
_private->player().set_playback_status(ConvertPlaybackStatus(status));
}
void SystemMediaControls::setLoopStatus(LoopStatus status) {
// prevent property update -> rpl event -> property update recursion
const auto statusString = ConvertLoopStatus(status);
if (_private->player().get_loop_status() != statusString) {
_private->player().set_loop_status(ConvertLoopStatus(status));
}
}
void SystemMediaControls::setShuffle(bool value) {
// prevent property update -> rpl event -> property update recursion
_private->playerData().inSetShuffle = true;
if (_private->player().get_shuffle() != value) {
_private->player().set_shuffle(value);
}
_private->playerData().inSetShuffle = false;
}
void SystemMediaControls::setTitle(const QString &title) {
auto metadata = GLib::VariantDict::new_(
_private->player().get_metadata());
metadata.insert_value("xesam:title", Q2V(title));
_private->player().set_metadata(metadata.end());
}
void SystemMediaControls::setArtist(const QString &artist) {
auto metadata = GLib::VariantDict::new_(
_private->player().get_metadata());
metadata.insert_value(
"xesam:artist",
GLib::Variant::new_array({ Q2V(artist) }));
_private->player().set_metadata(metadata.end());
}
void SystemMediaControls::setThumbnail(const QImage &thumbnail) {
QByteArray thumbnailData;
QBuffer thumbnailBuffer(&thumbnailData);
thumbnail.save(&thumbnailBuffer, "JPG", 87);
auto metadata = GLib::VariantDict::new_(
_private->player().get_metadata());
metadata.insert_value(
"mpris:artUrl",
Q2V("data:image/jpeg;base64," + thumbnailData.toBase64()));
_private->player().set_metadata(metadata.end());
}
void SystemMediaControls::setDuration(int duration) {
_private->playerData().duration = duration * 1000;
auto metadata = GLib::VariantDict::new_(
_private->player().get_metadata());
metadata.insert_value(
"mpris:length",
GLib::Variant::new_int64(_private->playerData().duration));
_private->player().set_metadata(metadata.end());
}
void SystemMediaControls::setPosition(int position) {
// get_position reads the value directly from variable
// in the generated C code and doesn't account for the
// property override. It's possible to override get_position
// but the C++ bindings don't let do this due to
// https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/183
//
// Once this is fixed, Player should be able to override get_position
// and it will be possible to use (get|set)_position here
// like other functions do with properties.
auto prop = _private->player().property_position();
const auto was = prop.get();
prop.set(position * 1000);
const auto playerPosition = prop.get();
const auto positionDifference = was - playerPosition;
if (positionDifference > 1000000 || positionDifference < -1000000) {
_private->player().emit_seeked(playerPosition);
}
}
void SystemMediaControls::setVolume(float64 volume) {
// prevent property update -> rpl event -> property update recursion
if (_private->player().get_volume() != volume) {
_private->player().set_volume(volume);
}
}
void SystemMediaControls::clearThumbnail() {
auto metadata = GLib::VariantDict::new_(
_private->player().get_metadata());
metadata.remove("mpris:artUrl");
_private->player().set_metadata(metadata.end());
}
void SystemMediaControls::clearMetadata() {
_private->player().set_metadata(GLib::Variant::new_array({
GLib::Variant::new_dict_entry(
GLib::Variant::new_string("mpris:trackid"),
// fake path
GLib::Variant::new_variant(
GLib::Variant::new_object_path("/org/desktop_app/track/0"))),
}));
}
void SystemMediaControls::updateDisplay() {
}
auto SystemMediaControls::commandRequests() const
-> rpl::producer<SystemMediaControls::Command> {
return _private->commandRequests();
}
rpl::producer<float64> SystemMediaControls::seekRequests() const {
return _private->seekRequests(
) | rpl::map([=](int64 position) {
return float64(position) / _private->playerData().duration;
});
}
rpl::producer<float64> SystemMediaControls::volumeChangeRequests() const {
return _private->volumeChangeRequests();
}
rpl::producer<> SystemMediaControls::updatePositionRequests() const {
return _private->updatePositionRequests();
}
bool SystemMediaControls::seekingSupported() const {
return true;
}
bool SystemMediaControls::volumeSupported() const {
return true;
}
bool SystemMediaControls::Supported() {
return true;
}
} // namespace base::Platform

View File

@@ -0,0 +1,23 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_system_unlock_linux.h"
namespace base {
rpl::producer<SystemUnlockAvailability> SystemUnlockStatus(
bool lookupDetails) {
return rpl::single(SystemUnlockAvailability{ .known = true });
}
void SuggestSystemUnlock(
not_null<QWidget*> parent,
const QString &text,
Fn<void(SystemUnlockResult)> done) {
done(SystemUnlockResult::Cancelled);
}
} // namespace base

View File

@@ -0,0 +1,9 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/system_unlock.h"

View File

@@ -0,0 +1,169 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/platform/linux/base_url_scheme_linux.h"
#include "base/integration.h"
#include "base/debug_log.h"
#include <QtGui/QGuiApplication>
#include <kshell.h>
#include <ksandbox.h>
#include <gio/gio.hpp>
#include <snapcraft/snapcraft.hpp>
#if __has_include(<giounix/giounix.hpp>)
#include <giounix/giounix.hpp>
#else // __has_include(<giounix/giounix.hpp>)
#define GioUnix Gio
#endif // !__has_include(<giounix/giounix.hpp>)
namespace base::Platform {
namespace {
using namespace gi::repository;
namespace GObject = gi::repository::GObject;
void SnapDefaultHandler(const QString &protocol) {
Snapcraft::SettingsProxy::new_for_bus(
Gio::BusType::SESSION_,
Gio::DBusProxyFlags::NONE_,
"io.snapcraft.Settings",
"/io/snapcraft/Settings",
[=](GObject::Object, Gio::AsyncResult res) {
auto interface = Snapcraft::Settings(
Snapcraft::SettingsProxy::new_for_bus_finish(res, nullptr));
if (!interface) {
return;
}
interface.call_get_sub(
"default-url-scheme-handler",
protocol.toStdString(),
[=](GObject::Object, Gio::AsyncResult res) mutable {
const auto currentHandler = [&]()
-> std::optional<std::string> {
if (auto result = interface.call_get_sub_finish(
res)) {
return std::get<1>(*result).opt_();
}
return std::nullopt;
}();
if (!currentHandler) {
return;
}
const auto &integration = Integration::Instance();
const auto expectedHandler = integration.executableName()
+ u".desktop"_q;
if (currentHandler->c_str() == expectedHandler) {
return;
}
interface.call_set_sub(
"default-url-scheme-handler",
protocol.toStdString(),
expectedHandler.toStdString(),
nullptr);
});
});
}
} // namespace
bool CheckUrlScheme(const UrlSchemeDescriptor &descriptor) {
const auto handlerType = "x-scheme-handler/"
+ descriptor.protocol.toStdString();
const auto neededCommandline = KShell::joinArgs(QStringList{
descriptor.executable,
} + KShell::splitArgs(descriptor.arguments) + QStringList{
"--",
}).toStdString();
auto currentAppInfo = Gio::AppInfo::get_default_for_type(
handlerType,
true);
if (currentAppInfo) {
return currentAppInfo.get_commandline() == neededCommandline + " %u"
|| currentAppInfo.get_commandline() == neededCommandline + " %U";
}
return false;
}
void RegisterUrlScheme(const UrlSchemeDescriptor &descriptor) {
if (KSandbox::isSnap()) {
SnapDefaultHandler(descriptor.protocol);
return;
}
if (CheckUrlScheme(descriptor)) {
return;
}
UnregisterUrlScheme(descriptor);
const auto handlerType = "x-scheme-handler/"
+ descriptor.protocol.toStdString();
const auto commandlineForCreator = KShell::joinArgs(QStringList{
descriptor.executable,
} + KShell::splitArgs(descriptor.arguments) + QStringList{
"--",
}).toStdString();
const auto appId = QGuiApplication::desktopFileName().toStdString();
if (!appId.empty()) {
Gio::AppInfo appInfo = GioUnix::DesktopAppInfo::new_(appId + ".desktop");
if (appInfo) {
if (appInfo.get_commandline() == commandlineForCreator + " %u"
|| appInfo.get_commandline() == commandlineForCreator + " %U") {
appInfo.set_as_default_for_type(handlerType);
return;
}
}
}
auto newAppInfo = Gio::AppInfo::create_from_commandline(
commandlineForCreator,
descriptor.displayAppName.toStdString(),
Gio::AppInfoCreateFlags::SUPPORTS_URIS_
| Gio::AppInfoCreateFlags::SUPPORTS_STARTUP_NOTIFICATION_,
nullptr);
if (newAppInfo) {
newAppInfo.set_as_default_for_type(handlerType);
}
}
void UnregisterUrlScheme(const UrlSchemeDescriptor &descriptor) {
const auto handlerType = "x-scheme-handler/"
+ descriptor.protocol.toStdString();
const auto neededCommandline = KShell::joinArgs(QStringList{
descriptor.executable,
} + KShell::splitArgs(descriptor.arguments) + QStringList{
"--",
"%u",
}).toStdString();
auto registeredAppInfos = Gio::AppInfo::get_recommended_for_type(
handlerType);
for (auto &appInfo : registeredAppInfos) {
if (appInfo.get_commandline() == neededCommandline
&& !std::string(appInfo.get_id()).compare(0, 8, "userapp-")) {
appInfo.delete_();
}
}
}
} // namespace base::Platform

View File

@@ -0,0 +1,9 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/platform/base_platform_url_scheme.h"

View File

@@ -0,0 +1,33 @@
<node>
<interface name='io.snapcraft.Settings'>
<method name='Check'>
<arg type='s' name='setting' direction='in'/>
<arg type='s' name='check' direction='in'/>
<arg type='s' name='result' direction='out'/>
</method>
<method name='CheckSub'>
<arg type='s' name='setting' direction='in'/>
<arg type='s' name='subproperty' direction='in'/>
<arg type='s' name='check' direction='in'/>
<arg type='s' name='result' direction='out'/>
</method>
<method name='Get'>
<arg type='s' name='setting' direction='in'/>
<arg type='s' name='result' direction='out'/>
</method>
<method name='GetSub'>
<arg type='s' name='setting' direction='in'/>
<arg type='s' name='subproperty' direction='in'/>
<arg type='s' name='result' direction='out'/>
</method>
<method name='Set'>
<arg type='s' name='setting' direction='in'/>
<arg type='s' name='value' direction='in'/>
</method>
<method name='SetSub'>
<arg type='s' name='setting' direction='in'/>
<arg type='s' name='subproperty' direction='in'/>
<arg type='s' name='value' direction='in'/>
</method>
</interface>
</node>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0"?>
<node>
<interface name='org.mpris.MediaPlayer2'>
<method name='Raise'/>
<method name='Quit'/>
<property name='CanQuit' type='b' access='read'/>
<property name='CanRaise' type='b' access='read'/>
<property name='HasTrackList' type='b' access='read'/>
<property name='Identity' type='s' access='read'/>
<property name='DesktopEntry' type='s' access='read'/>
<property name='SupportedUriSchemes' type='as' access='read'/>
<property name='SupportedMimeTypes' type='as' access='read'/>
<property name='Fullscreen' type='b' access='read'/>
<property name='CanSetFullscreen' type='b' access='read'/>
</interface>
<interface name='org.mpris.MediaPlayer2.Player'>
<method name='Next'/>
<method name='Previous'/>
<method name='Pause'/>
<method name='PlayPause'/>
<method name='Stop'/>
<method name='Play'/>
<method name='Seek'>
<arg direction='in' name='Offset' type='x'/>
</method>
<method name='SetPosition'>
<arg direction='in' name='TrackId' type='o'/>
<arg direction='in' name='Position' type='x'/>
</method>
<method name='OpenUri'>
<arg direction='in' name='Uri' type='s'/>
</method>
<signal name='Seeked'>
<arg name='Position' type='x'/>
</signal>
<property name='PlaybackStatus' type='s' access='read'/>
<property name='LoopStatus' type='s' access='readwrite'/>
<property name='Rate' type='d' access='read'/>
<property name='Shuffle' type='b' access='readwrite'/>
<property name='Metadata' type='a{sv}' access='read'>
<annotation
name="org.qtproject.QtDBus.QtTypeName"
value="QVariantMap"/>
</property>
<property name='Volume' type='d' access='readwrite'/>
<property name='Position' type='x' access='read'/>
<property name='MinimumRate' type='d' access='read'/>
<property name='MaximumRate' type='d' access='read'/>
<property name='CanGoNext' type='b' access='read'/>
<property name='CanGoPrevious' type='b' access='read'/>
<property name='CanPlay' type='b' access='read'/>
<property name='CanPause' type='b' access='read'/>
<property name='CanSeek' type='b' access='read'/>
<property name='CanControl' type='b' access='read'/>
</interface>
</node>

View File

@@ -0,0 +1,144 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.freedesktop.DBus">
<method name="Hello">
<arg direction="out" type="s"/>
</method>
<method name="RequestName">
<arg direction="in" type="s"/>
<arg direction="in" type="u"/>
<arg direction="out" type="u"/>
</method>
<method name="ReleaseName">
<arg direction="in" type="s"/>
<arg direction="out" type="u"/>
</method>
<method name="StartServiceByName">
<arg direction="in" type="s"/>
<arg direction="in" type="u"/>
<arg direction="out" type="u"/>
</method>
<method name="UpdateActivationEnvironment">
<arg direction="in" type="a{ss}"/>
</method>
<method name="NameHasOwner">
<arg direction="in" type="s"/>
<arg direction="out" type="b"/>
</method>
<method name="ListNames">
<arg direction="out" type="as"/>
</method>
<method name="ListActivatableNames">
<arg direction="out" type="as"/>
</method>
<method name="AddMatch">
<arg direction="in" type="s"/>
</method>
<method name="RemoveMatch">
<arg direction="in" type="s"/>
</method>
<method name="GetNameOwner">
<arg direction="in" type="s"/>
<arg direction="out" type="s"/>
</method>
<method name="ListQueuedOwners">
<arg direction="in" type="s"/>
<arg direction="out" type="as"/>
</method>
<method name="GetConnectionUnixUser">
<arg direction="in" type="s"/>
<arg direction="out" type="u"/>
</method>
<method name="GetConnectionUnixProcessID">
<arg direction="in" type="s"/>
<arg direction="out" type="u"/>
</method>
<method name="GetAdtAuditSessionData">
<arg direction="in" type="s"/>
<arg direction="out" type="ay"/>
</method>
<method name="GetConnectionSELinuxSecurityContext">
<arg direction="in" type="s"/>
<arg direction="out" type="ay"/>
</method>
<method name="ReloadConfig">
</method>
<method name="GetId">
<arg direction="out" type="s"/>
</method>
<method name="GetConnectionCredentials">
<arg direction="in" type="s"/>
<arg direction="out" type="a{sv}"/>
</method>
<property name="Features" type="as" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
</property>
<property name="Interfaces" type="as" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
</property>
<signal name="NameOwnerChanged">
<arg type="s"/>
<arg type="s"/>
<arg type="s"/>
</signal>
<signal name="NameLost">
<arg type="s"/>
</signal>
<signal name="NameAcquired">
<arg type="s"/>
</signal>
</interface>
<interface name="org.freedesktop.DBus.Properties">
<method name="Get">
<arg direction="in" type="s"/>
<arg direction="in" type="s"/>
<arg direction="out" type="v"/>
</method>
<method name="GetAll">
<arg direction="in" type="s"/>
<arg direction="out" type="a{sv}"/>
</method>
<method name="Set">
<arg direction="in" type="s"/>
<arg direction="in" type="s"/>
<arg direction="in" type="v"/>
</method>
<signal name="PropertiesChanged">
<arg type="s" name="interface_name"/>
<arg type="a{sv}" name="changed_properties"/>
<arg type="as" name="invalidated_properties"/>
</signal>
</interface>
<interface name="org.freedesktop.DBus.Introspectable">
<method name="Introspect">
<arg direction="out" type="s"/>
</method>
</interface>
<interface name="org.freedesktop.DBus.Monitoring">
<method name="BecomeMonitor">
<arg direction="in" type="as"/>
<arg direction="in" type="u"/>
</method>
</interface>
<interface name="org.freedesktop.DBus.Peer">
<method name="GetMachineId">
<arg direction="out" type="s"/>
</method>
<method name="Ping">
</method>
</interface>
<interface name="org.freedesktop.DBus.Debug.Stats">
<method name="GetStats">
<arg direction="out" type="a{sv}"/>
</method>
<method name="GetConnectionStats">
<arg direction="in" type="s"/>
<arg direction="out" type="a{sv}"/>
</method>
<method name="GetAllMatchRules">
<arg direction="out" type="a{sas}"/>
</method>
</interface>
</node>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<node>
<interface name='org.freedesktop.FileManager1'>
<method name='ShowFolders'>
<arg type='as' name='URIs' direction='in'/>
<arg type='s' name='StartupId' direction='in'/>
</method>
<method name='ShowItems'>
<arg type='as' name='URIs' direction='in'/>
<arg type='s' name='StartupId' direction='in'/>
</method>
<method name='ShowItemProperties'>
<arg type='as' name='URIs' direction='in'/>
<arg type='s' name='StartupId' direction='in'/>
</method>
</interface>
</node>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE node PUBLIC
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
<node>
<!--
org.gnome.Mutter.IdleMonitor:
@short_description: idle monitor interface
This interface is used by gnome-desktop to implement
user activity monitoring.
-->
<interface name="org.gnome.Mutter.IdleMonitor">
<method name="GetIdletime">
<arg name="idletime" direction="out" type="t"/>
</method>
<method name="AddIdleWatch">
<arg name="interval" direction="in" type="t" />
<arg name="id" direction="out" type="u" />
</method>
<method name="AddUserActiveWatch">
<arg name="id" direction="out" type="u" />
</method>
<method name="RemoveWatch">
<arg name="id" direction="in" type="u" />
</method>
<signal name="WatchFired">
<arg name="id" direction="out" type="u" />
</signal>
</interface>
</node>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<node>
<!-- org.sigxcpu.Feedback.Haptic
@short_description: Interface to make a device vibrate
This D-Bus interface is used to make a device's haptic motor
vibrate. This is can be useful e.g. for games.
To provider user feedback the event based interface should be
preferred.
-->
<interface name="org.sigxcpu.Feedback.Haptic">
<!--
Vibrate:
@app_id: The application id usually in "reverse DNS" format
@pattern: The vibration pattern.
@success: Whether vibration was triggered
Triggers the given vibration pattern on the haptic device. The
pattern is a sequence of relative amplitude and duration pairs.
The amplitude must be between 0.0 and 1.0.
-->
<method name="Vibrate">
<arg direction="in" name="app_id" type="s"/>
<arg direction="in" name="pattern" type="a(du)"/>
<arg direction="out" name="success" type="b"/>
</method>
</interface>
</node>