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
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:
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
53
Telegram/lib_base/base/platform/linux/base_haptic_linux.cpp
Normal file
53
Telegram/lib_base/base/platform/linux/base_haptic_linux.cpp
Normal 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
|
||||
@@ -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"
|
||||
386
Telegram/lib_base/base/platform/linux/base_info_linux.cpp
Normal file
386
Telegram/lib_base/base/platform/linux/base_info_linux.cpp
Normal 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
|
||||
40
Telegram/lib_base/base/platform/linux/base_info_linux.h
Normal file
40
Telegram/lib_base/base/platform/linux/base_info_linux.h
Normal 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
|
||||
104
Telegram/lib_base/base/platform/linux/base_last_input_linux.cpp
Normal file
104
Telegram/lib_base/base/platform/linux/base_last_input_linux.cpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
35
Telegram/lib_base/base/platform/linux/base_linux_library.cpp
Normal file
35
Telegram/lib_base/base/platform/linux/base_linux_library.cpp
Normal 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
|
||||
38
Telegram/lib_base/base/platform/linux/base_linux_library.h
Normal file
38
Telegram/lib_base/base/platform/linux/base_linux_library.h
Normal 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
|
||||
@@ -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 ¬ifier = *v::get<std::unique_ptr<QSocketNotifier>>(
|
||||
it->second.first);
|
||||
|
||||
QObject::connect(
|
||||
QCoreApplication::eventDispatcher(),
|
||||
&QAbstractEventDispatcher::aboutToBlock,
|
||||
¬ifier,
|
||||
[=] {
|
||||
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
|
||||
145
Telegram/lib_base/base/platform/linux/base_linux_xcb_utilities.h
Normal file
145
Telegram/lib_base/base/platform/linux/base_linux_xcb_utilities.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
158
Telegram/lib_base/base/platform/linux/base_linux_xdp_utilities.h
Normal file
158
Telegram/lib_base/base/platform/linux/base_linux_xdp_utilities.h
Normal 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
|
||||
332
Telegram/lib_base/base/platform/linux/base_linux_xsettings.cpp
Normal file
332
Telegram/lib_base/base/platform/linux/base_linux_xsettings.cpp
Normal 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
|
||||
45
Telegram/lib_base/base/platform/linux/base_linux_xsettings.h
Normal file
45
Telegram/lib_base/base/platform/linux/base_linux_xsettings.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
110
Telegram/lib_base/base/platform/linux/base_process_linux.cpp
Normal file
110
Telegram/lib_base/base/platform/linux/base_process_linux.cpp
Normal 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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
169
Telegram/lib_base/base/platform/linux/base_url_scheme_linux.cpp
Normal file
169
Telegram/lib_base/base/platform/linux/base_url_scheme_linux.cpp
Normal 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
|
||||
@@ -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"
|
||||
33
Telegram/lib_base/base/platform/linux/io.snapcraft.Settings.xml
Executable file
33
Telegram/lib_base/base/platform/linux/io.snapcraft.Settings.xml
Executable 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>
|
||||
56
Telegram/lib_base/base/platform/linux/mpris.xml
Normal file
56
Telegram/lib_base/base/platform/linux/mpris.xml
Normal 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>
|
||||
144
Telegram/lib_base/base/platform/linux/org.freedesktop.DBus.xml
Normal file
144
Telegram/lib_base/base/platform/linux/org.freedesktop.DBus.xml
Normal 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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user