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,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
|
||||
|
||||
class QString;
|
||||
|
||||
namespace base::Platform {
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
|
||||
std::optional<uint64> SetCustomAppIcon(QImage image);
|
||||
std::optional<uint64> SetCustomAppIcon(const QString &path);
|
||||
std::optional<uint64> CurrentCustomAppIconDigest();
|
||||
bool ClearCustomAppIcon();
|
||||
|
||||
#else // Q_OS_MAC
|
||||
|
||||
inline std::optional<uint64> SetCustomAppIcon(QImage image) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
inline std::optional<uint64> SetCustomAppIcon(const QString &path) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
inline std::optional<uint64> CurrentCustomAppIconDigest() {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
inline bool ClearCustomAppIcon() {
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // Q_OS_MAC
|
||||
|
||||
} // 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
|
||||
//
|
||||
#include "base/platform/base_platform_file_utilities.h"
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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
|
||||
|
||||
class QString;
|
||||
class QFile;
|
||||
|
||||
namespace base::Platform {
|
||||
|
||||
void ShowInFolder(const QString &filepath);
|
||||
[[nodiscard]] QString FileNameFromUserString(QString name);
|
||||
|
||||
bool DeleteDirectory(QString path);
|
||||
void RemoveQuarantine(const QString &path);
|
||||
|
||||
[[nodiscard]] QString CurrentExecutablePath(int argc, char *argv[]);
|
||||
[[nodiscard]] QString BundledResourcesPath();
|
||||
|
||||
bool RenameWithOverwrite(const QString &from, const QString &to);
|
||||
void FlushFileData(QFile &file);
|
||||
|
||||
} // namespace base::Platform
|
||||
@@ -0,0 +1,25 @@
|
||||
// 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_generic.h"
|
||||
|
||||
class QKeyEvent;
|
||||
|
||||
namespace base::Platform::GlobalShortcuts {
|
||||
|
||||
[[nodiscard]] bool Available();
|
||||
[[nodiscard]] bool Allowed();
|
||||
|
||||
void Start(Fn<void(GlobalShortcutKeyGeneric descriptor, bool down)> process);
|
||||
void Stop();
|
||||
|
||||
[[nodiscard]] QString KeyName(GlobalShortcutKeyGeneric descriptor);
|
||||
|
||||
[[nodiscard]] bool IsToggleFullScreenKey(not_null<QKeyEvent*> e);
|
||||
|
||||
} // namespace base::Platform::GlobalShortcuts
|
||||
14
Telegram/lib_base/base/platform/base_platform_haptic.h
Normal file
14
Telegram/lib_base/base/platform/base_platform_haptic.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
namespace base::Platform {
|
||||
|
||||
void Haptic();
|
||||
[[nodiscard]] bool IsSwipeBackEnabled();
|
||||
|
||||
} // namespace base::Platform
|
||||
63
Telegram/lib_base/base/platform/base_platform_info.cpp
Normal file
63
Telegram/lib_base/base/platform/base_platform_info.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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_info.h"
|
||||
|
||||
#include "base/algorithm.h"
|
||||
|
||||
namespace base::Platform {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxDeviceModelLength = 15;
|
||||
constexpr auto kMaxGoodDeviceModelLength = 32;
|
||||
|
||||
} // namespace
|
||||
|
||||
bool IsDeviceModelOk(const QString &model) {
|
||||
return !model.isEmpty() && (model.size() <= kMaxDeviceModelLength);
|
||||
}
|
||||
|
||||
QString SimplifyDeviceModel(QString model) {
|
||||
return CleanAndSimplify(model.replace(QChar('_'), QString()));
|
||||
}
|
||||
|
||||
QString SimplifyGoodDeviceModel(QString model, std::vector<QString> remove) {
|
||||
const auto limit = kMaxGoodDeviceModelLength;
|
||||
auto result = QString();
|
||||
for (const auto &word : model.split(QChar(' '))) {
|
||||
if (ranges::contains(remove, word.toLower())) {
|
||||
continue;
|
||||
} else if (result.isEmpty()) {
|
||||
result = word;
|
||||
} else if (result.size() + word.size() + 1 > limit) {
|
||||
return result;
|
||||
} else {
|
||||
result += ' ' + word;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString ProductNameToDeviceModel(const QString &productName) {
|
||||
if (productName.startsWith("HP ")) {
|
||||
// Some special cases for good strings, like HP laptops.
|
||||
return SimplifyGoodDeviceModel(
|
||||
productName,
|
||||
{ "notebook", "desktop", "mobile", "workstation", "pc" });
|
||||
} else if (IsDeviceModelOk(productName)) {
|
||||
return productName;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString FinalizeDeviceModel(QString model) {
|
||||
using namespace ::Platform;
|
||||
|
||||
model = std::move(model).trimmed();
|
||||
return !model.isEmpty() ? model : IsMac() ? u"Mac"_q : u"Desktop"_q;
|
||||
}
|
||||
|
||||
} // namespace base::Platform
|
||||
84
Telegram/lib_base/base/platform/base_platform_info.h
Normal file
84
Telegram/lib_base/base/platform/base_platform_info.h
Normal file
@@ -0,0 +1,84 @@
|
||||
// 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 <vector>
|
||||
|
||||
class QJsonObject;
|
||||
class QString;
|
||||
class QDate;
|
||||
|
||||
namespace base::Platform {
|
||||
|
||||
[[nodiscard]] bool IsDeviceModelOk(const QString &model);
|
||||
|
||||
[[nodiscard]] QString SimplifyDeviceModel(QString model);
|
||||
[[nodiscard]] QString SimplifyGoodDeviceModel(
|
||||
QString model,
|
||||
std::vector<QString> remove);
|
||||
[[nodiscard]] QString ProductNameToDeviceModel(const QString &productName);
|
||||
[[nodiscard]] QString FinalizeDeviceModel(QString model);
|
||||
|
||||
} // namespace base::Platform
|
||||
|
||||
namespace Platform {
|
||||
|
||||
enum class OutdateReason {
|
||||
IsOld,
|
||||
Is32Bit,
|
||||
};
|
||||
|
||||
[[nodiscard]] QString DeviceModelPretty();
|
||||
[[nodiscard]] QString SystemVersionPretty();
|
||||
[[nodiscard]] QString SystemCountry();
|
||||
[[nodiscard]] QString SystemLanguage();
|
||||
[[nodiscard]] QDate WhenSystemBecomesOutdated();
|
||||
[[nodiscard]] OutdateReason WhySystemBecomesOutdated();
|
||||
[[nodiscard]] int AutoUpdateVersion();
|
||||
[[nodiscard]] QString AutoUpdateKey();
|
||||
|
||||
[[nodiscard]] constexpr bool IsWindows();
|
||||
[[nodiscard]] constexpr bool IsWindows32Bit();
|
||||
[[nodiscard]] constexpr bool IsWindows64Bit();
|
||||
[[nodiscard]] constexpr bool IsWindowsARM64();
|
||||
[[nodiscard]] constexpr bool IsWindowsStoreBuild();
|
||||
[[nodiscard]] bool IsWindows7OrGreater();
|
||||
[[nodiscard]] bool IsWindows8OrGreater();
|
||||
[[nodiscard]] bool IsWindows8Point1OrGreater();
|
||||
[[nodiscard]] bool IsWindows10OrGreater();
|
||||
[[nodiscard]] bool IsWindows11OrGreater();
|
||||
|
||||
[[nodiscard]] constexpr bool IsMac();
|
||||
[[nodiscard]] constexpr bool IsMacStoreBuild();
|
||||
[[nodiscard]] bool IsMac10_12OrGreater();
|
||||
[[nodiscard]] bool IsMac10_13OrGreater();
|
||||
[[nodiscard]] bool IsMac10_14OrGreater();
|
||||
[[nodiscard]] bool IsMac10_15OrGreater();
|
||||
[[nodiscard]] bool IsMac11_0OrGreater();
|
||||
[[nodiscard]] bool IsMac26_0OrGreater();
|
||||
|
||||
[[nodiscard]] constexpr bool IsLinux();
|
||||
[[nodiscard]] bool IsX11();
|
||||
[[nodiscard]] bool IsWayland();
|
||||
[[nodiscard]] bool IsXwayland();
|
||||
|
||||
[[nodiscard]] QString GetLibcName();
|
||||
[[nodiscard]] QString GetLibcVersion();
|
||||
[[nodiscard]] QString GetWindowManager();
|
||||
|
||||
void Start(QJsonObject settings);
|
||||
void Finish();
|
||||
|
||||
} // namespace Platform
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "base/platform/win/base_info_win.h"
|
||||
#elif defined Q_OS_MAC // Q_OS_WIN
|
||||
#include "base/platform/mac/base_info_mac.h"
|
||||
#else // Q_OS_WIN || Q_OS_MAC
|
||||
#include "base/platform/linux/base_info_linux.h"
|
||||
#endif // else for Q_OS_WIN || Q_OS_MAC
|
||||
16
Telegram/lib_base/base/platform/base_platform_last_input.h
Normal file
16
Telegram/lib_base/base/platform/base_platform_last_input.h
Normal file
@@ -0,0 +1,16 @@
|
||||
// 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 {
|
||||
|
||||
[[nodiscard]] std::optional<crl::time> LastUserInputTime();
|
||||
[[nodiscard]] inline bool LastUserInputTimeSupported() {
|
||||
return LastUserInputTime().has_value();
|
||||
}
|
||||
|
||||
} // namespace base::Platform
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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 {
|
||||
|
||||
bool SwitchKeyboardLayoutToEnglish();
|
||||
|
||||
} // namespace base::Platform
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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 {
|
||||
|
||||
class NetworkReachability {
|
||||
public:
|
||||
virtual ~NetworkReachability() = default;
|
||||
|
||||
static std::unique_ptr<NetworkReachability> Create();
|
||||
|
||||
[[nodiscard]] virtual rpl::producer<bool> availableValue() const = 0;
|
||||
};
|
||||
|
||||
} // namespace base::Platform
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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/power_save_blocker.h"
|
||||
|
||||
namespace base::Platform {
|
||||
|
||||
inline constexpr int kPowerSaveBlockTypeCount = static_cast<int>(
|
||||
PowerSaveBlockType::kCount);
|
||||
|
||||
[[nodiscard]] inline constexpr int PowerSaveBlockTypeIndex(
|
||||
PowerSaveBlockType type) {
|
||||
Expects(static_cast<int>(type) >= 0);
|
||||
Expects(type < PowerSaveBlockType::kCount);
|
||||
|
||||
return static_cast<int>(type);
|
||||
}
|
||||
|
||||
// window may be null.
|
||||
void BlockPowerSave(
|
||||
PowerSaveBlockType type,
|
||||
const QString &description,
|
||||
QPointer<QWindow> window);
|
||||
void UnblockPowerSave(PowerSaveBlockType type, QPointer<QWindow> window);
|
||||
|
||||
} // namespace base::Platform
|
||||
18
Telegram/lib_base/base/platform/base_platform_process.h
Normal file
18
Telegram/lib_base/base/platform/base_platform_process.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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/basic_types.h"
|
||||
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
namespace base::Platform {
|
||||
|
||||
void ActivateProcessWindow(int64 pid, WId windowId);
|
||||
void ActivateThisProcessWindow(WId windowId);
|
||||
|
||||
} // namespace base::Platform
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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 {
|
||||
|
||||
class SystemMediaControls {
|
||||
public:
|
||||
enum class Command {
|
||||
PlayPause,
|
||||
Play,
|
||||
Pause,
|
||||
Next,
|
||||
Previous,
|
||||
Stop,
|
||||
Quit,
|
||||
Raise,
|
||||
LoopNone,
|
||||
LoopTrack,
|
||||
LoopPlaylist,
|
||||
Shuffle,
|
||||
None,
|
||||
};
|
||||
|
||||
enum class PlaybackStatus {
|
||||
Playing,
|
||||
Paused,
|
||||
Stopped,
|
||||
};
|
||||
|
||||
enum class LoopStatus {
|
||||
None,
|
||||
Track,
|
||||
Playlist,
|
||||
};
|
||||
|
||||
SystemMediaControls();
|
||||
~SystemMediaControls();
|
||||
|
||||
bool init();
|
||||
|
||||
[[nodiscard]] bool seekingSupported() const;
|
||||
[[nodiscard]] bool volumeSupported() const;
|
||||
|
||||
void setApplicationName(const QString &name);
|
||||
|
||||
void setEnabled(bool enabled);
|
||||
void setIsNextEnabled(bool value);
|
||||
void setIsPreviousEnabled(bool value);
|
||||
void setIsPlayPauseEnabled(bool value);
|
||||
void setIsStopEnabled(bool value);
|
||||
void setPlaybackStatus(PlaybackStatus status);
|
||||
void setLoopStatus(LoopStatus status);
|
||||
void setShuffle(bool value);
|
||||
void setTitle(const QString &title);
|
||||
void setArtist(const QString &artist);
|
||||
void setThumbnail(const QImage &thumbnail);
|
||||
void setDuration(int duration);
|
||||
void setPosition(int position);
|
||||
void setVolume(float64 volume);
|
||||
void clearThumbnail();
|
||||
void clearMetadata();
|
||||
void updateDisplay();
|
||||
|
||||
[[nodiscard]] rpl::producer<Command> commandRequests() const;
|
||||
[[nodiscard]] rpl::producer<float64> seekRequests() const;
|
||||
[[nodiscard]] rpl::producer<float64> volumeChangeRequests() const;
|
||||
[[nodiscard]] rpl::producer<> updatePositionRequests() const;
|
||||
|
||||
static bool Supported();
|
||||
|
||||
private:
|
||||
struct Private;
|
||||
|
||||
const std::unique_ptr<Private> _private;
|
||||
};
|
||||
|
||||
} // namespace base::Platform
|
||||
26
Telegram/lib_base/base/platform/base_platform_url_scheme.h
Normal file
26
Telegram/lib_base/base/platform/base_platform_url_scheme.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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 {
|
||||
|
||||
struct UrlSchemeDescriptor {
|
||||
QString executable; // Full path.
|
||||
QString arguments; // Additional arguments.
|
||||
QString protocol; // 'myprotocol'
|
||||
QString protocolName; // "My Protocol Link"
|
||||
QString shortAppName; // "myapp"
|
||||
QString longAppName; // "MyApplication"
|
||||
QString displayAppName; // "My Application"
|
||||
QString displayAppDescription; // "My Nice Application"
|
||||
};
|
||||
|
||||
[[nodiscard]] bool CheckUrlScheme(const UrlSchemeDescriptor &descriptor);
|
||||
void RegisterUrlScheme(const UrlSchemeDescriptor &descriptor);
|
||||
void UnregisterUrlScheme(const UrlSchemeDescriptor &descriptor);
|
||||
|
||||
} // namespace base::Platform
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
202
Telegram/lib_base/base/platform/mac/base_battery_saving_mac.mm
Normal file
202
Telegram/lib_base/base/platform/mac/base_battery_saving_mac.mm
Normal file
@@ -0,0 +1,202 @@
|
||||
// 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/mac/base_battery_saving_mac.h"
|
||||
|
||||
#include "base/battery_saving.h"
|
||||
#include "base/integration.h"
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <IOKit/ps/IOPSKeys.h>
|
||||
#include <IOKit/ps/IOPowerSources.h>
|
||||
|
||||
@interface LowPowerModeObserver : NSObject {
|
||||
}
|
||||
|
||||
- (id) initWithCallback:(Fn<void()>)callback;
|
||||
- (void) powerStateChanged:(NSNotification*)aNotification;
|
||||
|
||||
@end // @interface LowPowerModeObserver
|
||||
|
||||
@implementation LowPowerModeObserver {
|
||||
Fn<void()> _callback;
|
||||
}
|
||||
|
||||
- (id) initWithCallback:(Fn<void()>)callback {
|
||||
if (self = [super init]) {
|
||||
_callback = std::move(callback);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) powerStateChanged:(NSNotification*)aNotification {
|
||||
_callback();
|
||||
}
|
||||
|
||||
@end // @implementation LowPowerModeObserver
|
||||
|
||||
namespace base::Platform {
|
||||
namespace {
|
||||
|
||||
class BatterySaving final : public AbstractBatterySaving {
|
||||
public:
|
||||
BatterySaving(Fn<void()> changedCallback);
|
||||
~BatterySaving();
|
||||
|
||||
std::optional<bool> enabled() const override;
|
||||
|
||||
private:
|
||||
static void RunLoopCallback(void *callback) {
|
||||
const auto observer = static_cast<LowPowerModeObserver*>(callback);
|
||||
[observer powerStateChanged:nil];
|
||||
}
|
||||
|
||||
LowPowerModeObserver *_observer = nil;
|
||||
CFRunLoopSourceRef _runLoopSource = nullptr;
|
||||
|
||||
};
|
||||
|
||||
struct BatteryState {
|
||||
bool has = false;
|
||||
bool draining = false;
|
||||
bool low = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] BatteryState DetectBatteryState() {
|
||||
CFTypeRef info = IOPSCopyPowerSourcesInfo();
|
||||
if (!info) {
|
||||
return {};
|
||||
}
|
||||
const auto infoGuard = gsl::finally([&] { CFRelease(info); });
|
||||
|
||||
CFArrayRef list = IOPSCopyPowerSourcesList(info);
|
||||
if (!list) {
|
||||
return {};
|
||||
}
|
||||
const auto listGuard = gsl::finally([&] { CFRelease(list); });
|
||||
|
||||
CFIndex count = CFArrayGetCount(list);
|
||||
|
||||
auto result = BatteryState();
|
||||
for (CFIndex i = 0; i < count; ++i) {
|
||||
const auto description = CFDictionaryRef(
|
||||
IOPSGetPowerSourceDescription(info, CFArrayGetValueAtIndex(list, i)));
|
||||
if (!description) {
|
||||
continue;
|
||||
}
|
||||
const auto type = CFStringRef(CFDictionaryGetValue(description, CFSTR(kIOPSTransportTypeKey)));
|
||||
if (!type) {
|
||||
continue;
|
||||
}
|
||||
const auto isInternal = (CFStringCompare(type, CFSTR(kIOPSInternalType), 0) == kCFCompareEqualTo);
|
||||
if (!isInternal) {
|
||||
continue;
|
||||
}
|
||||
const auto isPresent = CFBooleanRef(CFDictionaryGetValue(description, CFSTR(kIOPSIsPresentKey)));
|
||||
if (!isPresent || !CFBooleanGetValue(isPresent)) {
|
||||
continue;
|
||||
}
|
||||
const auto state = CFStringRef(CFDictionaryGetValue(description, CFSTR(kIOPSPowerSourceStateKey)));
|
||||
if (!state) {
|
||||
continue;
|
||||
}
|
||||
const auto isDraining = (CFStringCompare(state, CFSTR(kIOPSBatteryPowerValue), 0) == kCFCompareEqualTo);
|
||||
result.has = true;
|
||||
result.draining = isDraining;
|
||||
|
||||
const auto lowWarnLevel = CFNumberRef(CFDictionaryGetValue(description, CFSTR(kIOPSLowWarnLevelKey)));
|
||||
const auto nowCapacity = CFNumberRef(CFDictionaryGetValue(description, CFSTR(kIOPSCurrentCapacityKey)));
|
||||
const auto maxCapacity = CFNumberRef(CFDictionaryGetValue(description, CFSTR(kIOPSMaxCapacityKey)));
|
||||
if (!lowWarnLevel || !nowCapacity || !maxCapacity) {
|
||||
continue;
|
||||
}
|
||||
auto lowWarnLevelValue = int64_t();
|
||||
auto nowCapacityValue = int64_t();
|
||||
auto maxCapacityValue = int64_t();
|
||||
if (!CFNumberGetValue(lowWarnLevel, kCFNumberSInt64Type, &lowWarnLevelValue)
|
||||
|| !CFNumberGetValue(nowCapacity, kCFNumberSInt64Type, &nowCapacityValue)
|
||||
|| !CFNumberGetValue(maxCapacity, kCFNumberSInt64Type, &maxCapacityValue)
|
||||
|| (lowWarnLevelValue <= 0 || lowWarnLevelValue >= 100)
|
||||
|| (nowCapacityValue < 0 || nowCapacityValue > maxCapacityValue || !maxCapacityValue)) {
|
||||
continue;
|
||||
}
|
||||
result.low = (nowCapacityValue / double(maxCapacityValue) <= lowWarnLevelValue / 100.);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
BatterySaving::BatterySaving(Fn<void()> changedCallback) {
|
||||
if (!DetectBatteryState().has) {
|
||||
return;
|
||||
}
|
||||
auto wrapped = [copy = std::move(changedCallback)] {
|
||||
Integration::Instance().enterFromEventLoop(copy);
|
||||
};
|
||||
_observer = [[LowPowerModeObserver alloc] initWithCallback:std::move(wrapped)];
|
||||
|
||||
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
||||
if (@available(macOS 12.0, *)) {
|
||||
[center
|
||||
addObserver: _observer
|
||||
selector: @selector(powerStateChanged:)
|
||||
name: NSProcessInfoPowerStateDidChangeNotification
|
||||
object: nil];
|
||||
}
|
||||
[center
|
||||
addObserver: _observer
|
||||
selector: @selector(powerStateChanged:)
|
||||
name: NSProcessInfoThermalStateDidChangeNotification
|
||||
object: nil];
|
||||
|
||||
_runLoopSource = IOPSNotificationCreateRunLoopSource(
|
||||
RunLoopCallback,
|
||||
static_cast<void*>(_observer));
|
||||
CFRunLoopAddSource(
|
||||
CFRunLoopGetCurrent(),
|
||||
_runLoopSource,
|
||||
kCFRunLoopDefaultMode);
|
||||
}
|
||||
|
||||
BatterySaving::~BatterySaving() {
|
||||
if (_runLoopSource) {
|
||||
CFRunLoopRemoveSource(
|
||||
CFRunLoopGetCurrent(),
|
||||
_runLoopSource,
|
||||
kCFRunLoopDefaultMode);
|
||||
CFRelease(_runLoopSource);
|
||||
}
|
||||
if (_observer) {
|
||||
[_observer release];
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<bool> BatterySaving::enabled() const {
|
||||
if (!_observer) {
|
||||
return std::nullopt;
|
||||
}
|
||||
NSProcessInfo *info = [NSProcessInfo processInfo];
|
||||
if (@available(macOS 12.0, *)) {
|
||||
if ([info isLowPowerModeEnabled]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const auto state = DetectBatteryState();
|
||||
if (!state.has || !state.draining) {
|
||||
return false;
|
||||
} else if ([info thermalState] == NSProcessInfoThermalStateCritical) {
|
||||
return true;
|
||||
}
|
||||
return state.low;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<AbstractBatterySaving> CreateBatterySaving(
|
||||
Fn<void()> changedCallback) {
|
||||
return std::make_unique<BatterySaving>(std::move(changedCallback));
|
||||
}
|
||||
|
||||
} // namespace base::Platform
|
||||
14
Telegram/lib_base/base/platform/mac/base_confirm_quit.h
Normal file
14
Telegram/lib_base/base/platform/mac/base_confirm_quit.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
namespace Platform::ConfirmQuit {
|
||||
|
||||
[[nodiscard]] bool RunModal(QString text);
|
||||
[[nodiscard]] QString QuitKeysString();
|
||||
|
||||
} // namespace Platform::ConfirmQuit
|
||||
462
Telegram/lib_base/base/platform/mac/base_confirm_quit.mm
Normal file
462
Telegram/lib_base/base/platform/mac/base_confirm_quit.mm
Normal file
@@ -0,0 +1,462 @@
|
||||
// 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/mac/base_confirm_quit.h"
|
||||
|
||||
#include "base/platform/mac/base_utilities_mac.h"
|
||||
|
||||
// Thanks Chromium: chrome/browser/ui/cocoa/confirm_quit*
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
namespace {
|
||||
|
||||
// How long the user must hold down Cmd+Q to confirm the quit.
|
||||
constexpr auto kShowDuration = crl::time(1500);
|
||||
|
||||
// Duration of the window fade out animation.
|
||||
constexpr auto kWindowFadeOutDuration = crl::time(200);
|
||||
|
||||
// For metrics recording only: How long the user must hold the keys to
|
||||
// differentitate kDoubleTap from kTapHold.
|
||||
constexpr auto kDoubleTapTimeDelta = crl::time(320);
|
||||
|
||||
// Leeway between the |targetDate| and the current time that will confirm a
|
||||
// quit.
|
||||
constexpr auto kTimeDeltaFuzzFactor = crl::time(1000);
|
||||
|
||||
} // namespace
|
||||
|
||||
@class ConfirmQuitFrameView;
|
||||
|
||||
// The ConfirmQuitPanelController manages the black HUD window that tells users
|
||||
// to "Hold Cmd+Q to Quit".
|
||||
@interface ConfirmQuitPanelController : NSWindowController<NSWindowDelegate> {
|
||||
@private
|
||||
// The content view of the window that this controller manages.
|
||||
ConfirmQuitFrameView* _contentView; // Weak, owned by the window.
|
||||
NSString *_message;
|
||||
}
|
||||
|
||||
// Returns a singleton instance of the Controller. This will create one if it
|
||||
// does not currently exist.
|
||||
+ (ConfirmQuitPanelController*)sharedControllerWithMessage:(NSString*)message;
|
||||
|
||||
// Runs a modal loop that brings up the panel and handles the logic for if and
|
||||
// when to terminate. Returns YES if the quit should continue.
|
||||
- (BOOL)runModalLoopForApplication:(NSApplication*)app;
|
||||
|
||||
// Shows the window.
|
||||
- (void)showWindow:(id)sender;
|
||||
|
||||
// If the user did not confirm quit, send this message to give the user
|
||||
// instructions on how to quit.
|
||||
- (void)dismissPanel;
|
||||
|
||||
@end
|
||||
|
||||
// The content view of the window that draws a custom frame.
|
||||
@interface ConfirmQuitFrameView : NSView {
|
||||
@private
|
||||
NSTextField* _message; // Weak, owned by the view hierarchy.
|
||||
}
|
||||
- (void)setMessageText:(NSString*)text;
|
||||
@end
|
||||
|
||||
@implementation ConfirmQuitFrameView
|
||||
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect {
|
||||
if ((self = [super initWithFrame:frameRect])) {
|
||||
// The frame will be fixed up when |-setMessageText:| is called.
|
||||
_message = [[NSTextField alloc] initWithFrame:NSZeroRect];
|
||||
[_message setEditable:NO];
|
||||
[_message setSelectable:NO];
|
||||
[_message setBezeled:NO];
|
||||
[_message setDrawsBackground:NO];
|
||||
[_message setFont:[NSFont boldSystemFontOfSize:24]];
|
||||
[_message setTextColor:[NSColor whiteColor]];
|
||||
[self addSubview:_message];
|
||||
[_message release];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect {
|
||||
const CGFloat kCornerRadius = 9.0;
|
||||
NSBezierPath* path = [NSBezierPath
|
||||
bezierPathWithRoundedRect:[self bounds]
|
||||
xRadius:kCornerRadius
|
||||
yRadius:kCornerRadius];
|
||||
|
||||
NSColor* fillColor = [NSColor colorWithCalibratedWhite:0.2 alpha:0.75];
|
||||
[fillColor set];
|
||||
[path fill];
|
||||
}
|
||||
|
||||
- (void)setMessageText:(NSString*)text {
|
||||
const CGFloat kHorizontalPadding = 30; // In view coordinates.
|
||||
|
||||
// Style the string.
|
||||
NSMutableAttributedString *attrString
|
||||
= [[NSMutableAttributedString alloc] initWithString:text];
|
||||
NSShadow *textShadow = [[NSShadow alloc] init];
|
||||
const auto guard = gsl::finally([&] {
|
||||
[textShadow release];
|
||||
[attrString release];
|
||||
});
|
||||
[textShadow
|
||||
setShadowColor:[NSColor
|
||||
colorWithCalibratedWhite:0
|
||||
alpha:0.6]];
|
||||
[textShadow setShadowOffset:NSMakeSize(0, -1)];
|
||||
[textShadow setShadowBlurRadius:1.0];
|
||||
[attrString addAttribute:NSShadowAttributeName
|
||||
value:textShadow
|
||||
range:NSMakeRange(0, [text length])];
|
||||
[_message setAttributedStringValue:attrString];
|
||||
|
||||
// Fixup the frame of the string.
|
||||
[_message sizeToFit];
|
||||
NSRect messageFrame = [_message frame];
|
||||
NSRect frameInViewSpace
|
||||
= [_message convertRect:[[self window] frame] fromView:nil];
|
||||
|
||||
if (NSWidth(messageFrame) > NSWidth(frameInViewSpace)) {
|
||||
frameInViewSpace.size.width = NSWidth(messageFrame) + kHorizontalPadding;
|
||||
}
|
||||
|
||||
messageFrame.origin.x = NSWidth(frameInViewSpace) / 2 - NSMidX(messageFrame);
|
||||
messageFrame.origin.y = NSHeight(frameInViewSpace) / 2 - NSMidY(messageFrame);
|
||||
|
||||
[[self window]
|
||||
setFrame:[_message convertRect:frameInViewSpace toView:nil]
|
||||
display:YES];
|
||||
[_message setFrame:messageFrame];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Animation ///////////////////////////////////////////////////////////////////
|
||||
|
||||
// This animation will run through all the windows of the passed-in
|
||||
// NSApplication and will fade their alpha value to 0.0. When the animation is
|
||||
// complete, this will release itself.
|
||||
@interface FadeAllWindowsAnimation : NSAnimation<NSAnimationDelegate> {
|
||||
@private
|
||||
NSApplication* _application;
|
||||
}
|
||||
- (instancetype)initWithApplication:(NSApplication*)app
|
||||
animationDuration:(NSTimeInterval)duration;
|
||||
@end
|
||||
|
||||
@implementation FadeAllWindowsAnimation
|
||||
|
||||
- (instancetype)initWithApplication:(NSApplication*)app
|
||||
animationDuration:(NSTimeInterval)duration {
|
||||
if ((self = [super initWithDuration:duration
|
||||
animationCurve:NSAnimationLinear])) {
|
||||
_application = app;
|
||||
[self setDelegate:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setCurrentProgress:(NSAnimationProgress)progress {
|
||||
for (NSWindow* window in [_application windows]) {
|
||||
[window setAlphaValue:1.0 - progress];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)animationDidStop:(NSAnimation*)anim {
|
||||
[self autorelease];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Private Interface ///////////////////////////////////////////////////////////
|
||||
|
||||
@interface ConfirmQuitPanelController (Private) <CAAnimationDelegate>
|
||||
- (void)animateFadeOut;
|
||||
- (NSEvent*)pumpEventQueueForKeyUp:(NSApplication*)app untilDate:(NSDate*)date;
|
||||
- (void)hideAllWindowsForApplication:(NSApplication*)app
|
||||
withDuration:(NSTimeInterval)duration;
|
||||
- (void)sendAccessibilityAnnouncement;
|
||||
@end
|
||||
|
||||
ConfirmQuitPanelController* g_confirmQuitPanelController = nil;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation ConfirmQuitPanelController
|
||||
|
||||
+ (ConfirmQuitPanelController*)sharedControllerWithMessage:(NSString*)message {
|
||||
if (!g_confirmQuitPanelController) {
|
||||
g_confirmQuitPanelController =
|
||||
[[ConfirmQuitPanelController alloc] initWithMessage:message];
|
||||
}
|
||||
return [[g_confirmQuitPanelController retain] autorelease];
|
||||
}
|
||||
|
||||
- (instancetype)initWithMessage:(NSString*)message {
|
||||
const NSRect kWindowFrame = NSMakeRect(0, 0, 350, 70);
|
||||
NSWindow *window
|
||||
= [[NSWindow alloc]
|
||||
initWithContentRect:kWindowFrame
|
||||
styleMask:NSWindowStyleMaskBorderless
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
const auto guard = gsl::finally([&] { [window release]; });
|
||||
|
||||
if ((self = [super initWithWindow:window])) {
|
||||
[window setDelegate:self];
|
||||
[window setBackgroundColor:[NSColor clearColor]];
|
||||
[window setOpaque:NO];
|
||||
[window setHasShadow:NO];
|
||||
|
||||
// Create the content view. Take the frame from the existing content view.
|
||||
NSRect frame = [[window contentView] frame];
|
||||
_contentView = [[ConfirmQuitFrameView alloc] initWithFrame:frame];
|
||||
|
||||
[window setContentView:_contentView];
|
||||
|
||||
// Set the proper string.
|
||||
_message = [message retain];
|
||||
[_contentView setMessageText:_message];
|
||||
[_contentView release];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)runModalLoopForApplication:(NSApplication*)app {
|
||||
ConfirmQuitPanelController *keepAlive = [self retain];
|
||||
const auto guard = gsl::finally([&] { [keepAlive release]; });
|
||||
|
||||
// If this is the second of two such attempts to quit within a certain time
|
||||
// interval, then just quit.
|
||||
// Time of last quit attempt, if any.
|
||||
static auto lastQuitAttempt = crl::time();
|
||||
const auto timeNow = crl::now();
|
||||
if (lastQuitAttempt && (timeNow - lastQuitAttempt) < kTimeDeltaFuzzFactor) {
|
||||
// The panel tells users to Hold Cmd+Q. However, we also want to have a
|
||||
// double-tap shortcut that allows for a quick quit path. For the users who
|
||||
// tap Cmd+Q and then hold it with the window still open, this double-tap
|
||||
// logic will run and cause the quit to get committed. If the key
|
||||
// combination held down, the system will start sending the Cmd+Q event to
|
||||
// the next key application, and so on. This is bad, so instead we hide all
|
||||
// the windows (without animation) to look like we've "quit" and then wait
|
||||
// for the KeyUp event to commit the quit.
|
||||
[self hideAllWindowsForApplication:app withDuration:0];
|
||||
NSEvent* nextEvent = [self
|
||||
pumpEventQueueForKeyUp:app
|
||||
untilDate:[NSDate distantFuture]];
|
||||
[app discardEventsMatchingMask:NSEventMaskAny beforeEvent:nextEvent];
|
||||
return YES;
|
||||
} else {
|
||||
lastQuitAttempt = timeNow;
|
||||
}
|
||||
|
||||
// Show the info panel that explains what the user must to do confirm quit.
|
||||
[self showWindow:self];
|
||||
|
||||
// Explicitly announce the hold-to-quit message. For an ordinary modal dialog
|
||||
// VoiceOver would announce it and read its message, but VoiceOver does not do
|
||||
// this for windows whose styleMask is NSWindowStyleMaskBorderless, so do it
|
||||
// manually here. Without this screenreader users have no way to know why
|
||||
// their quit hotkey seems not to work.
|
||||
[self sendAccessibilityAnnouncement];
|
||||
|
||||
// Spin a nested run loop until the |targetDate| is reached or a KeyUp event
|
||||
// is sent.
|
||||
const auto targetDate = crl::now() + kShowDuration;
|
||||
BOOL willQuit = NO;
|
||||
NSEvent* nextEvent = nil;
|
||||
do {
|
||||
// Dequeue events until a key up is received. To avoid busy waiting, figure
|
||||
// out the amount of time that the thread can sleep before taking further
|
||||
// action.
|
||||
NSDate* waitDate = [NSDate
|
||||
dateWithTimeIntervalSinceNow:(kShowDuration - kTimeDeltaFuzzFactor) / 1000.];
|
||||
nextEvent = [self pumpEventQueueForKeyUp:app untilDate:waitDate];
|
||||
|
||||
// Wait for the time expiry to happen. Once past the hold threshold,
|
||||
// commit to quitting and hide all the open windows.
|
||||
if (!willQuit) {
|
||||
const auto now = crl::now();
|
||||
const auto difference = (targetDate - now);
|
||||
if (difference < kTimeDeltaFuzzFactor) {
|
||||
willQuit = YES;
|
||||
|
||||
// At this point, the quit has been confirmed and windows should all
|
||||
// fade out to convince the user to release the key combo to finalize
|
||||
// the quit.
|
||||
[self
|
||||
hideAllWindowsForApplication:app
|
||||
withDuration:kWindowFadeOutDuration / 1000.];
|
||||
}
|
||||
}
|
||||
} while (!nextEvent);
|
||||
|
||||
// The user has released the key combo. Discard any events (i.e. the
|
||||
// repeated KeyDown Cmd+Q).
|
||||
[app discardEventsMatchingMask:NSEventMaskAny beforeEvent:nextEvent];
|
||||
|
||||
if (willQuit) {
|
||||
// The user held down the combination long enough that quitting should
|
||||
// happen.
|
||||
return YES;
|
||||
} else {
|
||||
// Slowly fade the confirm window out in case the user doesn't
|
||||
// understand what they have to do to quit.
|
||||
[self dismissPanel];
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Default case: terminate.
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)windowWillClose:(NSNotification*)notif {
|
||||
// Release all animations because CAAnimation retains its delegate (self),
|
||||
// which will cause a retain cycle. Break it!
|
||||
[[self window] setAnimations:@{}];
|
||||
g_confirmQuitPanelController = nil;
|
||||
[self autorelease];
|
||||
}
|
||||
|
||||
- (void)showWindow:(id)sender {
|
||||
// If a panel that is fading out is going to be reused here, make sure it
|
||||
// does not get released when the animation finishes.
|
||||
ConfirmQuitPanelController *keepAlive = [self retain];
|
||||
const auto guard = gsl::finally([&] { [keepAlive release]; });
|
||||
[[self window] setAnimations:@{}];
|
||||
[[self window] center];
|
||||
[[self window] setAlphaValue:1.0];
|
||||
[super showWindow:sender];
|
||||
}
|
||||
|
||||
- (void)dismissPanel {
|
||||
[self
|
||||
performSelector:@selector(animateFadeOut)
|
||||
withObject:nil
|
||||
afterDelay:1.0];
|
||||
}
|
||||
|
||||
- (void)animateFadeOut {
|
||||
NSWindow* window = [self window];
|
||||
CAAnimation *animation
|
||||
= [[window animationForKey:@"alphaValue"] copy];
|
||||
const auto guard = gsl::finally([&] { [animation release]; });
|
||||
[animation setDelegate:self];
|
||||
[animation setDuration:0.2];
|
||||
NSMutableDictionary* dictionary
|
||||
= [NSMutableDictionary dictionaryWithDictionary:[window animations]];
|
||||
dictionary[@"alphaValue"] = animation;
|
||||
[window setAnimations:dictionary];
|
||||
[[window animator] setAlphaValue:0.0];
|
||||
}
|
||||
|
||||
- (void)animationDidStart:(CAAnimation*)theAnimation {
|
||||
// CAAnimationDelegate method added on OSX 10.12.
|
||||
}
|
||||
|
||||
- (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)finished {
|
||||
[self close];
|
||||
}
|
||||
|
||||
// Runs a nested loop that pumps the event queue until the next KeyUp event.
|
||||
- (NSEvent*)pumpEventQueueForKeyUp:(NSApplication*)app untilDate:(NSDate*)date {
|
||||
return [app
|
||||
nextEventMatchingMask:NSEventMaskKeyUp
|
||||
untilDate:date
|
||||
inMode:NSEventTrackingRunLoopMode
|
||||
dequeue:YES];
|
||||
}
|
||||
|
||||
// Iterates through the list of open windows and hides them all.
|
||||
- (void)hideAllWindowsForApplication:(NSApplication*)app
|
||||
withDuration:(NSTimeInterval)duration {
|
||||
FadeAllWindowsAnimation* animation =
|
||||
[[FadeAllWindowsAnimation alloc] initWithApplication:app
|
||||
animationDuration:duration];
|
||||
// Releases itself when the animation stops.
|
||||
[animation startAnimation];
|
||||
}
|
||||
|
||||
- (void)sendAccessibilityAnnouncement {
|
||||
NSAccessibilityPostNotificationWithUserInfo(
|
||||
[NSApp mainWindow], NSAccessibilityAnnouncementRequestedNotification, @{
|
||||
NSAccessibilityAnnouncementKey : _message,
|
||||
NSAccessibilityPriorityKey : @(NSAccessibilityPriorityHigh),
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace Platform::ConfirmQuit {
|
||||
namespace {
|
||||
|
||||
// This returns the NSMenuItem that quits the application.
|
||||
[[nodiscard]] NSMenuItem *QuitMenuItem() {
|
||||
NSMenu* mainMenu = [NSApp mainMenu];
|
||||
// Get the application menu (i.e. Chromium).
|
||||
NSMenu* appMenu = [[mainMenu itemAtIndex:0] submenu];
|
||||
for (NSMenuItem* item in [appMenu itemArray]) {
|
||||
// Find the Quit item.
|
||||
if ([item action] == @selector(terminate:)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to Cmd+Q.
|
||||
NSMenuItem* item = [[[NSMenuItem alloc]
|
||||
initWithTitle:@""
|
||||
action:@selector(terminate:)
|
||||
keyEquivalent:@"q"] autorelease];
|
||||
item.keyEquivalentModifierMask = NSEventModifierFlagCommand;
|
||||
return item;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString KeyCombinationForMenuItem(NSMenuItem *item) {
|
||||
auto result = QString();
|
||||
|
||||
NSUInteger modifiers = item.keyEquivalentModifierMask;
|
||||
if (modifiers & NSEventModifierFlagCommand) {
|
||||
result.append(QChar(0x2318));
|
||||
}
|
||||
if (modifiers & NSEventModifierFlagControl) {
|
||||
result.append(QChar(0x2303));
|
||||
}
|
||||
if (modifiers & NSEventModifierFlagOption) {
|
||||
result.append(QChar(0x2325));
|
||||
}
|
||||
if (modifiers & NSEventModifierFlagShift) {
|
||||
result.append(QChar(0x21E7));
|
||||
}
|
||||
result.append(NS2QString([item.keyEquivalent uppercaseString]));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// This looks at the Main Menu and determines what the user has set as the
|
||||
// key combination for quit. It then gets the modifiers and builds a string
|
||||
// to display them.
|
||||
[[nodiscard]] QString KeyCommandString() {
|
||||
return KeyCombinationForMenuItem(QuitMenuItem());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool RunModal(QString text) {
|
||||
return [[ConfirmQuitPanelController sharedControllerWithMessage:Q2NSString(text)]
|
||||
runModalLoopForApplication:NSApp];
|
||||
}
|
||||
|
||||
QString QuitKeysString() {
|
||||
return KeyCommandString();
|
||||
}
|
||||
|
||||
} // namespace Platform::ConfirmQuit
|
||||
@@ -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_custom_app_icon.h"
|
||||
387
Telegram/lib_base/base/platform/mac/base_custom_app_icon_mac.mm
Normal file
387
Telegram/lib_base/base/platform/mac/base_custom_app_icon_mac.mm
Normal file
@@ -0,0 +1,387 @@
|
||||
// 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/mac/base_custom_app_icon_mac.h"
|
||||
|
||||
#include "base/debug_log.h"
|
||||
#include "base/platform/mac/base_utilities_mac.h"
|
||||
|
||||
#include <QtGui/QImage>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QTemporaryFile>
|
||||
|
||||
#include <sys/xattr.h>
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace base::Platform {
|
||||
namespace {
|
||||
|
||||
using namespace ::Platform;
|
||||
|
||||
constexpr auto kFinderInfo = "com.apple.FinderInfo";
|
||||
constexpr auto kResourceFork = "com.apple.ResourceFork";
|
||||
|
||||
// We want to write [8], so just in case.
|
||||
constexpr auto kFinderInfoMinSize = 16;
|
||||
|
||||
// Usually.
|
||||
constexpr auto kFinderInfoSize = 32;
|
||||
|
||||
// Just in case.
|
||||
constexpr auto kFinderInfoMaxSize = 256;
|
||||
|
||||
// Limit custom icons to 10 MB.
|
||||
constexpr auto kResourceForkMaxSize = 10 * 1024 * 1024;
|
||||
|
||||
[[nodiscard]] QString BundlePath() {
|
||||
@autoreleasepool {
|
||||
|
||||
NSString *path = @"";
|
||||
@try {
|
||||
path = [[NSBundle mainBundle] bundlePath];
|
||||
if (!path) {
|
||||
Unexpected("Could not get bundled path!");
|
||||
}
|
||||
return QFile::decodeName([path fileSystemRepresentation]);
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
Unexpected("Exception in resource registering.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
int Launch(const QString &command, const QStringList &arguments) {
|
||||
@autoreleasepool {
|
||||
|
||||
@try {
|
||||
|
||||
NSMutableArray *list = [NSMutableArray arrayWithCapacity:arguments.size()];
|
||||
for (const auto &argument : arguments) {
|
||||
[list addObject:Q2NSString(argument)];
|
||||
}
|
||||
|
||||
NSTask *task = [[NSTask alloc] init];
|
||||
task.launchPath = Q2NSString(command);
|
||||
task.arguments = list;
|
||||
|
||||
[task launch];
|
||||
[task waitUntilExit];
|
||||
|
||||
return [task terminationStatus];
|
||||
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
return -888;
|
||||
}
|
||||
@finally {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<std::string> ReadCustomIconAttribute(const QString &bundle) {
|
||||
const auto native = QFile::encodeName(bundle);
|
||||
auto info = std::array<char, kFinderInfoMaxSize>();
|
||||
const auto result = getxattr(
|
||||
native.data(),
|
||||
kFinderInfo,
|
||||
info.data(),
|
||||
info.size(),
|
||||
0, // position
|
||||
XATTR_NOFOLLOW);
|
||||
const auto error = (result < 0) ? errno : 0;
|
||||
if (result < 0) {
|
||||
if (error == ENOATTR) {
|
||||
return std::string();
|
||||
} else {
|
||||
LOG(("Icon Error: Could not get %1 xattr, error: %2."
|
||||
).arg(kFinderInfo
|
||||
).arg(error));
|
||||
return std::nullopt;
|
||||
}
|
||||
} else if (result < kFinderInfoMinSize) {
|
||||
LOG(("Icon Error: Bad existing %1 xattr size: %2."
|
||||
).arg(kFinderInfo
|
||||
).arg(error));
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::string(info.data(), result);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool WriteCustomIconAttribute(
|
||||
const QString &bundle,
|
||||
const std::string &value) {
|
||||
const auto native = QFile::encodeName(bundle);
|
||||
const auto result = setxattr(
|
||||
native.data(),
|
||||
kFinderInfo,
|
||||
value.data(),
|
||||
value.size(),
|
||||
0, // position
|
||||
XATTR_NOFOLLOW);
|
||||
if (result != 0) {
|
||||
LOG(("Icon Error: Could not set %1 xattr, error: %2."
|
||||
).arg(kFinderInfo
|
||||
).arg(errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool DeleteCustomIconAttribute(const QString &bundle) {
|
||||
const auto native = QFile::encodeName(bundle);
|
||||
const auto result = removexattr(
|
||||
native.data(),
|
||||
kFinderInfo,
|
||||
XATTR_NOFOLLOW);
|
||||
if (result != 0) {
|
||||
LOG(("Icon Error: Could not remove %1 xattr, error: %2."
|
||||
).arg(kFinderInfo
|
||||
).arg(errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool EnableCustomIcon(const QString &bundle) {
|
||||
auto info = ReadCustomIconAttribute(bundle);
|
||||
if (!info.has_value()) {
|
||||
return false;
|
||||
} else if (info->empty()) {
|
||||
*info = std::string(kFinderInfoSize, char(0));
|
||||
}
|
||||
if ((*info)[8] & 0x04) {
|
||||
(*info)[8] &= ~0x04;
|
||||
if (!WriteCustomIconAttribute(bundle, *info)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
(*info)[8] |= 4;
|
||||
return WriteCustomIconAttribute(bundle, *info);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool DisableCustomIcon(const QString &bundle) {
|
||||
auto info = ReadCustomIconAttribute(bundle);
|
||||
if (!info.has_value()) {
|
||||
return false;
|
||||
} else if (info->empty()) {
|
||||
return true;
|
||||
}
|
||||
return DeleteCustomIconAttribute(bundle);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool RefreshDock() {
|
||||
Launch("/bin/bash", { "-c", "rm /var/folders/*/*/*/com.apple.dock.iconcache" });
|
||||
|
||||
const auto killall = Launch("/usr/bin/killall", { "Dock" });
|
||||
if (killall != 0) {
|
||||
LOG(("Icon Error: Failed to run `killall Dock`, result: %1.").arg(killall));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString TempPath(const QString &extension) {
|
||||
auto file = QTemporaryFile(
|
||||
QDir::tempPath() + "/custom_icon_XXXXXX." + extension);
|
||||
file.setAutoRemove(false);
|
||||
const auto result = file.open() ? file.fileName() : QString();
|
||||
if (result.isEmpty()) {
|
||||
LOG(("Icon Error: Could not obtain a temporary file name."));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<std::string> ReadResourceFork(
|
||||
const QString &path) {
|
||||
const auto native = QFile::encodeName(path);
|
||||
auto buffer = std::string(kResourceForkMaxSize + 1, char(0));
|
||||
const auto result = getxattr(
|
||||
native.data(),
|
||||
kResourceFork,
|
||||
buffer.data(),
|
||||
buffer.size(),
|
||||
0, // position
|
||||
XATTR_NOFOLLOW);
|
||||
const auto error = (result < 0) ? errno : 0;
|
||||
if (result < 0) {
|
||||
if (error == ENOATTR) {
|
||||
return std::string();
|
||||
} else {
|
||||
LOG(("Icon Error: Could not get %1 xattr, error: %2."
|
||||
).arg(kResourceFork
|
||||
).arg(error));
|
||||
return std::nullopt;
|
||||
}
|
||||
} else if (result > kResourceForkMaxSize) {
|
||||
LOG(("Icon Error: Got too large %1 xattr, size: %2."
|
||||
).arg(kResourceFork
|
||||
).arg(result));
|
||||
return std::nullopt;
|
||||
}
|
||||
buffer.resize(result);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool WriteResourceFork(
|
||||
const QString &path,
|
||||
const std::string &data) {
|
||||
const auto native = QFile::encodeName(path);
|
||||
const auto result = setxattr(
|
||||
native.data(),
|
||||
kResourceFork,
|
||||
data.data(),
|
||||
data.size(),
|
||||
0, // position
|
||||
XATTR_NOFOLLOW);
|
||||
if (result != 0) {
|
||||
LOG(("Icon Error: Could not set %1 xattr, error: %2."
|
||||
).arg(kResourceFork
|
||||
).arg(errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64 Digest(const std::string &data) {
|
||||
return XXH64(data.data(), data.size(), 0);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<uint64> SetPreparedIcon(const QString &path) {
|
||||
const auto sips = Launch("/usr/bin/sips", {
|
||||
"-i",
|
||||
path
|
||||
});
|
||||
if (sips != 0) {
|
||||
LOG(("Icon Error: Failed to run `sips -i \"%1\"`, result: %2."
|
||||
).arg(path
|
||||
).arg(sips));
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto bundle = BundlePath();
|
||||
const auto icon = bundle + "/Icon\r";
|
||||
const auto touch = Launch("/usr/bin/touch", { icon });
|
||||
if (touch != 0) {
|
||||
LOG(("Icon Error: Failed to run `touch \"%1\"`, result: %2."
|
||||
).arg(icon
|
||||
).arg(touch));
|
||||
return std::nullopt;
|
||||
}
|
||||
#if 0 // Faster, but without a digest.
|
||||
const auto from = path + "/..namedfork/rsrc";
|
||||
const auto to = icon + "/..namedfork/rsrc";
|
||||
const auto cp = Launch("/bin/cp", { from, to });
|
||||
if (cp != 0) {
|
||||
LOG(("Icon Error: Failed to run `cp \"%1\" \"%2\"`, result: %3."
|
||||
).arg(from
|
||||
).arg(to
|
||||
).arg(cp));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
auto rsrc = ReadResourceFork(path);
|
||||
if (!rsrc) {
|
||||
return false;
|
||||
} else if (rsrc->empty()) {
|
||||
LOG(("Icon Error: Empty resource fork after sips in \"%1\".").arg(path));
|
||||
return false;
|
||||
} else if (!WriteResourceFork(icon, *rsrc) || !EnableCustomIcon(bundle)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return RefreshDock()
|
||||
? std::make_optional(Digest(*rsrc))
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<uint64> SetCustomAppIcon(QImage image) {
|
||||
if (image.isNull()) {
|
||||
LOG(("Icon Error: Null image received."));
|
||||
return std::nullopt;
|
||||
}
|
||||
if (image.format() != QImage::Format_ARGB32_Premultiplied
|
||||
&& image.format() != QImage::Format_ARGB32
|
||||
&& image.format() != QImage::Format_RGB32) {
|
||||
image = std::move(image).convertToFormat(QImage::Format_ARGB32);
|
||||
if (image.isNull()) {
|
||||
LOG(("Icon Error: Failed to convert image to ARGB32."));
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
const auto temp = TempPath("icns");
|
||||
if (temp.isEmpty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto guard = gsl::finally([&] { QFile::remove(temp); });
|
||||
if (!image.save(temp, "PNG")) {
|
||||
LOG(("Icon Error: Failed to save image to \"%1\".").arg(temp));
|
||||
return std::nullopt;
|
||||
}
|
||||
return SetPreparedIcon(temp);
|
||||
}
|
||||
|
||||
std::optional<uint64> SetCustomAppIcon(const QString &path) {
|
||||
const auto icns = path.endsWith(".icns", Qt::CaseInsensitive);
|
||||
if (!icns) {
|
||||
auto image = QImage(path);
|
||||
if (image.isNull()) {
|
||||
LOG(("Icon Error: Failed to read image from \"%1\".").arg(path));
|
||||
return std::nullopt;
|
||||
}
|
||||
return SetCustomAppIcon(std::move(image));
|
||||
}
|
||||
const auto temp = TempPath("icns");
|
||||
if (temp.isEmpty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto guard = gsl::finally([&] { QFile::remove(temp); });
|
||||
QFile::remove(temp);
|
||||
if (!QFile(path).copy(temp)) {
|
||||
LOG(("Icon Error: Failed to copy icon from \"%1\" to \"%2\"."
|
||||
).arg(path
|
||||
).arg(temp));
|
||||
return std::nullopt;
|
||||
}
|
||||
return SetPreparedIcon(temp);
|
||||
}
|
||||
|
||||
std::optional<uint64> CurrentCustomAppIconDigest() {
|
||||
const auto bundle = BundlePath();
|
||||
const auto icon = bundle + "/Icon\r";
|
||||
const auto attr = ReadCustomIconAttribute(bundle);
|
||||
if (!attr) {
|
||||
return std::nullopt;
|
||||
} else if (attr->empty()) {
|
||||
return 0;
|
||||
}
|
||||
const auto value = ReadResourceFork(icon);
|
||||
if (!value) {
|
||||
return std::nullopt;
|
||||
} else if (value->empty()) {
|
||||
return 0;
|
||||
}
|
||||
return Digest(*value);
|
||||
}
|
||||
|
||||
bool ClearCustomAppIcon() {
|
||||
const auto bundle = BundlePath();
|
||||
const auto icon = bundle + "/Icon\r";
|
||||
Launch("/bin/rm", { icon });
|
||||
auto info = ReadCustomIconAttribute(bundle);
|
||||
if (!info.has_value()) {
|
||||
return false;
|
||||
} else if (info->empty()) {
|
||||
return true;
|
||||
} else if (!DeleteCustomIconAttribute(bundle)) {
|
||||
return false;
|
||||
}
|
||||
return RefreshDock();
|
||||
}
|
||||
|
||||
} // 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,93 @@
|
||||
// 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/mac/base_file_utilities_mac.h"
|
||||
|
||||
#include "base/platform/mac/base_utilities_mac.h"
|
||||
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <sys/xattr.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace base::Platform {
|
||||
|
||||
using namespace ::Platform;
|
||||
|
||||
void ShowInFolder(const QString &filepath) {
|
||||
const auto folder = QFileInfo(filepath).absolutePath();
|
||||
|
||||
@autoreleasepool {
|
||||
|
||||
[[NSWorkspace sharedWorkspace] selectFile:Q2NSString(filepath) inFileViewerRootedAtPath:Q2NSString(folder)];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveQuarantine(const QString &path) {
|
||||
const auto kQuarantineAttribute = "com.apple.quarantine";
|
||||
|
||||
const auto local = QFile::encodeName(path);
|
||||
removexattr(local.data(), kQuarantineAttribute, 0);
|
||||
}
|
||||
|
||||
QString BundledResourcesPath() {
|
||||
@autoreleasepool {
|
||||
|
||||
NSString *path = @"";
|
||||
@try {
|
||||
path = [[NSBundle mainBundle] bundlePath];
|
||||
if (!path) {
|
||||
Unexpected("Could not get bundled path!");
|
||||
}
|
||||
path = [path stringByAppendingString:@"/Contents/Resources"];
|
||||
return QFile::decodeName([path fileSystemRepresentation]);
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
Unexpected("Exception in resource registering.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
QString FileNameFromUserString(QString name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
bool DeleteDirectory(QString path) {
|
||||
if (path.endsWith('/')) {
|
||||
path.chop(1);
|
||||
}
|
||||
|
||||
BOOL result = NO;
|
||||
|
||||
@autoreleasepool {
|
||||
|
||||
result = [[NSFileManager defaultManager] removeItemAtPath:Q2NSString(path) error:nil];
|
||||
|
||||
}
|
||||
|
||||
return (result != NO);
|
||||
}
|
||||
|
||||
QString CurrentExecutablePath(int argc, char *argv[]) {
|
||||
return NS2QString([[NSBundle mainBundle] bundlePath]);
|
||||
}
|
||||
|
||||
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,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_global_shortcuts.h"
|
||||
345
Telegram/lib_base/base/platform/mac/base_global_shortcuts_mac.mm
Normal file
345
Telegram/lib_base/base/platform/mac/base_global_shortcuts_mac.mm
Normal file
@@ -0,0 +1,345 @@
|
||||
// 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/mac/base_global_shortcuts_mac.h"
|
||||
|
||||
#include "base/platform/mac/base_utilities_mac.h"
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/const_string.h"
|
||||
|
||||
#include <QtGui/QtEvents>
|
||||
|
||||
#include <Carbon/Carbon.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <IOKit/hidsystem/IOHIDLib.h>
|
||||
|
||||
namespace base::Platform::GlobalShortcuts {
|
||||
namespace {
|
||||
|
||||
constexpr auto kShiftMouseButton = std::numeric_limits<uint64>::max() - 100;
|
||||
|
||||
CFMachPortRef EventPort = nullptr;
|
||||
CFRunLoopSourceRef EventPortSource = nullptr;
|
||||
CFRunLoopRef ThreadRunLoop = nullptr;
|
||||
std::thread Thread;
|
||||
Fn<void(GlobalShortcutKeyGeneric descriptor, bool down)> ProcessCallback;
|
||||
|
||||
struct EventData {
|
||||
GlobalShortcutKeyGeneric descriptor = 0;
|
||||
bool down = false;
|
||||
};
|
||||
using MaybeEventData = std::optional<EventData>;
|
||||
|
||||
MaybeEventData ProcessKeyEvent(CGEventType type, CGEventRef event) {
|
||||
if (CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto keycode = CGEventGetIntegerValueField(
|
||||
event,
|
||||
kCGKeyboardEventKeycode);
|
||||
|
||||
if (keycode == 0xB3) {
|
||||
// Some KeyDown+KeyUp sent when quickly pressing and releasing Fn.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto flags = CGEventGetFlags(event);
|
||||
const auto maybeDown = [&]() -> std::optional<bool> {
|
||||
if (type == kCGEventKeyDown) {
|
||||
return true;
|
||||
} else if (type == kCGEventKeyUp) {
|
||||
return false;
|
||||
} else if (type != kCGEventFlagsChanged) {
|
||||
return std::nullopt;
|
||||
}
|
||||
switch (keycode) {
|
||||
case kVK_CapsLock:
|
||||
return (flags & kCGEventFlagMaskAlphaShift) != 0;
|
||||
case kVK_Shift:
|
||||
case kVK_RightShift:
|
||||
return (flags & kCGEventFlagMaskShift) != 0;
|
||||
case kVK_Control:
|
||||
case kVK_RightControl:
|
||||
return (flags & kCGEventFlagMaskControl) != 0;
|
||||
case kVK_Option:
|
||||
case kVK_RightOption:
|
||||
return (flags & kCGEventFlagMaskAlternate) != 0;
|
||||
case kVK_Command:
|
||||
case kVK_RightCommand:
|
||||
return (flags & kCGEventFlagMaskCommand) != 0;
|
||||
case kVK_Function:
|
||||
return (flags & kCGEventFlagMaskSecondaryFn) != 0;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}();
|
||||
if (!maybeDown) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto descriptor = GlobalShortcutKeyGeneric(keycode);
|
||||
const auto down = *maybeDown;
|
||||
|
||||
return EventData{ descriptor, down };
|
||||
}
|
||||
|
||||
MaybeEventData ProcessMouseEvent(CGEventType type, CGEventRef event) {
|
||||
const auto button = CGEventGetIntegerValueField(
|
||||
event,
|
||||
kCGMouseEventButtonNumber);
|
||||
if (!button) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto code = GlobalShortcutKeyGeneric(kShiftMouseButton
|
||||
+ button
|
||||
// Increase the value by 1, because the right button = 1.
|
||||
+ 1);
|
||||
|
||||
const auto down = (type == kCGEventOtherMouseDown)
|
||||
|| (type == kCGEventRightMouseDown);
|
||||
|
||||
return EventData{ code, down };
|
||||
}
|
||||
|
||||
CGEventRef EventTapCallback(
|
||||
CGEventTapProxy,
|
||||
CGEventType type,
|
||||
CGEventRef event,
|
||||
void*) {
|
||||
const auto isKey = (type == kCGEventKeyDown)
|
||||
|| (type == kCGEventKeyUp)
|
||||
|| (type == kCGEventFlagsChanged);
|
||||
|
||||
const auto maybeData = isKey
|
||||
? ProcessKeyEvent(type, event)
|
||||
: ProcessMouseEvent(type, event);
|
||||
|
||||
if (maybeData) {
|
||||
ProcessCallback(maybeData->descriptor, maybeData->down);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Available() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Allowed() {
|
||||
if (@available(macOS 10.15, *)) {
|
||||
// Input Monitoring is required on macOS 10.15 an later.
|
||||
// Even if user grants access, restart is required.
|
||||
static const auto result = IOHIDCheckAccess(
|
||||
kIOHIDRequestTypeListenEvent);
|
||||
return (result == kIOHIDAccessTypeGranted);
|
||||
} else if (@available(macOS 10.14, *)) {
|
||||
// Accessibility is required on macOS 10.14.
|
||||
NSDictionary *const options=
|
||||
@{(__bridge NSString *)kAXTrustedCheckOptionPrompt: @FALSE};
|
||||
return AXIsProcessTrustedWithOptions(
|
||||
(__bridge CFDictionaryRef)options);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Start(Fn<void(GlobalShortcutKeyGeneric descriptor, bool down)> process) {
|
||||
Expects(!EventPort);
|
||||
Expects(!EventPortSource);
|
||||
|
||||
ProcessCallback = std::move(process);
|
||||
EventPort = CGEventTapCreate(
|
||||
kCGHIDEventTap,
|
||||
kCGHeadInsertEventTap,
|
||||
kCGEventTapOptionListenOnly,
|
||||
(CGEventMaskBit(kCGEventKeyDown)
|
||||
| CGEventMaskBit(kCGEventKeyUp)
|
||||
| CGEventMaskBit(kCGEventOtherMouseDown)
|
||||
| CGEventMaskBit(kCGEventOtherMouseUp)
|
||||
| CGEventMaskBit(kCGEventRightMouseDown)
|
||||
| CGEventMaskBit(kCGEventRightMouseUp)
|
||||
| CGEventMaskBit(kCGEventFlagsChanged)),
|
||||
EventTapCallback,
|
||||
nullptr);
|
||||
if (!EventPort) {
|
||||
ProcessCallback = nullptr;
|
||||
return;
|
||||
}
|
||||
EventPortSource = CFMachPortCreateRunLoopSource(
|
||||
kCFAllocatorDefault,
|
||||
EventPort,
|
||||
0);
|
||||
if (!EventPortSource) {
|
||||
CFMachPortInvalidate(EventPort);
|
||||
CFRelease(EventPort);
|
||||
EventPort = nullptr;
|
||||
ProcessCallback = nullptr;
|
||||
return;
|
||||
}
|
||||
Thread = std::thread([] {
|
||||
ThreadRunLoop = CFRunLoopGetCurrent();
|
||||
CFRunLoopAddSource(
|
||||
ThreadRunLoop,
|
||||
EventPortSource,
|
||||
kCFRunLoopCommonModes);
|
||||
CGEventTapEnable(EventPort, true);
|
||||
CFRunLoopRun();
|
||||
});
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
if (!EventPort) {
|
||||
return;
|
||||
}
|
||||
CFRunLoopStop(ThreadRunLoop);
|
||||
Thread.join();
|
||||
|
||||
CFMachPortInvalidate(EventPort);
|
||||
CFRelease(EventPort);
|
||||
EventPort = nullptr;
|
||||
|
||||
CFRelease(EventPortSource);
|
||||
EventPortSource = nullptr;
|
||||
|
||||
ProcessCallback = nullptr;
|
||||
}
|
||||
|
||||
QString KeyName(GlobalShortcutKeyGeneric descriptor) {
|
||||
static const auto KeyToString = flat_map<uint64, const_string>{
|
||||
{ kVK_Return, "\xE2\x8F\x8E" },
|
||||
{ kVK_Tab, "\xE2\x87\xA5" },
|
||||
{ kVK_Space, "\xE2\x90\xA3" },
|
||||
{ kVK_Delete, "\xE2\x8C\xAB" },
|
||||
{ kVK_Escape, "\xE2\x8E\x8B" },
|
||||
{ kVK_Command, "\xE2\x8C\x98" },
|
||||
{ kVK_Shift, "\xE2\x87\xA7" },
|
||||
{ kVK_CapsLock, "Caps Lock" },
|
||||
{ kVK_Option, "\xE2\x8C\xA5" },
|
||||
{ kVK_Control, "\xE2\x8C\x83" },
|
||||
{ kVK_RightCommand, "Right \xE2\x8C\x98" },
|
||||
{ kVK_RightShift, "Right \xE2\x87\xA7" },
|
||||
{ kVK_RightOption, "Right \xE2\x8C\xA5" },
|
||||
{ kVK_RightControl, "Right \xE2\x8C\x83" },
|
||||
{ kVK_Function, "Fn" },
|
||||
{ kVK_F17, "F17" },
|
||||
{ kVK_VolumeUp, "Volume Up" },
|
||||
{ kVK_VolumeDown, "Volume Down" },
|
||||
{ kVK_Mute, "Mute" },
|
||||
{ kVK_F18, "F18" },
|
||||
{ kVK_F19, "F19" },
|
||||
{ kVK_F20, "F20" },
|
||||
{ kVK_F5, "F5" },
|
||||
{ kVK_F6, "F6" },
|
||||
{ kVK_F7, "F7" },
|
||||
{ kVK_F3, "F3" },
|
||||
{ kVK_F8, "F8" },
|
||||
{ kVK_F9, "F9" },
|
||||
{ kVK_F11, "F11" },
|
||||
{ kVK_F13, "F13" },
|
||||
{ kVK_F16, "F16" },
|
||||
{ kVK_F14, "F14" },
|
||||
{ kVK_F10, "F10" },
|
||||
{ kVK_F12, "F12" },
|
||||
{ kVK_F15, "F15" },
|
||||
{ kVK_Help, "Help" },
|
||||
{ kVK_Home, "\xE2\x86\x96" },
|
||||
{ kVK_PageUp, "Page Up" },
|
||||
{ kVK_ForwardDelete, "\xe2\x8c\xa6" },
|
||||
{ kVK_F4, "F4" },
|
||||
{ kVK_End, "\xE2\x86\x98" },
|
||||
{ kVK_F2, "F2" },
|
||||
{ kVK_PageDown, "Page Down" },
|
||||
{ kVK_F1, "F1" },
|
||||
{ kVK_LeftArrow, "\xE2\x86\x90" },
|
||||
{ kVK_RightArrow, "\xE2\x86\x92" },
|
||||
{ kVK_DownArrow, "\xE2\x86\x93" },
|
||||
{ kVK_UpArrow, "\xE2\x86\x91" },
|
||||
|
||||
{ kVK_ANSI_A, "A" },
|
||||
{ kVK_ANSI_S, "S" },
|
||||
{ kVK_ANSI_D, "D" },
|
||||
{ kVK_ANSI_F, "F" },
|
||||
{ kVK_ANSI_H, "H" },
|
||||
{ kVK_ANSI_G, "G" },
|
||||
{ kVK_ANSI_Z, "Z" },
|
||||
{ kVK_ANSI_X, "X" },
|
||||
{ kVK_ANSI_C, "C" },
|
||||
{ kVK_ANSI_V, "V" },
|
||||
{ kVK_ANSI_B, "B" },
|
||||
{ kVK_ANSI_Q, "Q" },
|
||||
{ kVK_ANSI_W, "W" },
|
||||
{ kVK_ANSI_E, "E" },
|
||||
{ kVK_ANSI_R, "R" },
|
||||
{ kVK_ANSI_Y, "Y" },
|
||||
{ kVK_ANSI_T, "T" },
|
||||
{ kVK_ANSI_1, "1" },
|
||||
{ kVK_ANSI_2, "2" },
|
||||
{ kVK_ANSI_3, "3" },
|
||||
{ kVK_ANSI_4, "4" },
|
||||
{ kVK_ANSI_6, "6" },
|
||||
{ kVK_ANSI_5, "5" },
|
||||
{ kVK_ANSI_Equal, "=" },
|
||||
{ kVK_ANSI_9, "9" },
|
||||
{ kVK_ANSI_7, "7" },
|
||||
{ kVK_ANSI_Minus, "-" },
|
||||
{ kVK_ANSI_8, "8" },
|
||||
{ kVK_ANSI_0, "0" },
|
||||
{ kVK_ANSI_RightBracket, "]" },
|
||||
{ kVK_ANSI_O, "O" },
|
||||
{ kVK_ANSI_U, "U" },
|
||||
{ kVK_ANSI_LeftBracket, "[" },
|
||||
{ kVK_ANSI_I, "I" },
|
||||
{ kVK_ANSI_P, "P" },
|
||||
{ kVK_ANSI_L, "L" },
|
||||
{ kVK_ANSI_J, "J" },
|
||||
{ kVK_ANSI_Quote, "'" },
|
||||
{ kVK_ANSI_K, "K" },
|
||||
{ kVK_ANSI_Semicolon, "/" },
|
||||
{ kVK_ANSI_Backslash, "\\" },
|
||||
{ kVK_ANSI_Comma, "," },
|
||||
{ kVK_ANSI_Slash, "/" },
|
||||
{ kVK_ANSI_N, "N" },
|
||||
{ kVK_ANSI_M, "M" },
|
||||
{ kVK_ANSI_Period, "." },
|
||||
{ kVK_ANSI_Grave, "`" },
|
||||
{ kVK_ANSI_KeypadDecimal, "Num ." },
|
||||
{ kVK_ANSI_KeypadMultiply, "Num *" },
|
||||
{ kVK_ANSI_KeypadPlus, "Num +" },
|
||||
{ kVK_ANSI_KeypadClear, "Num Clear" },
|
||||
{ kVK_ANSI_KeypadDivide, "Num /" },
|
||||
{ kVK_ANSI_KeypadEnter, "Num Enter" },
|
||||
{ kVK_ANSI_KeypadMinus, "Num -" },
|
||||
{ kVK_ANSI_KeypadEquals, "Num =" },
|
||||
{ kVK_ANSI_Keypad0, "Num 0" },
|
||||
{ kVK_ANSI_Keypad1, "Num 1" },
|
||||
{ kVK_ANSI_Keypad2, "Num 2" },
|
||||
{ kVK_ANSI_Keypad3, "Num 3" },
|
||||
{ kVK_ANSI_Keypad4, "Num 4" },
|
||||
{ kVK_ANSI_Keypad5, "Num 5" },
|
||||
{ kVK_ANSI_Keypad6, "Num 6" },
|
||||
{ kVK_ANSI_Keypad7, "Num 7" },
|
||||
{ kVK_ANSI_Keypad8, "Num 8" },
|
||||
{ kVK_ANSI_Keypad9, "Num 9" },
|
||||
};
|
||||
|
||||
if (descriptor > kShiftMouseButton) {
|
||||
return QString("Mouse %1").arg(descriptor - kShiftMouseButton);
|
||||
}
|
||||
|
||||
const auto i = KeyToString.find(descriptor);
|
||||
return (i != end(KeyToString))
|
||||
? i->second.utf16()
|
||||
: QString("\\x%1").arg(descriptor, 0, 16);
|
||||
}
|
||||
|
||||
bool IsToggleFullScreenKey(not_null<QKeyEvent*> e) {
|
||||
const auto mods = e->nativeModifiers()
|
||||
& NSEventModifierFlagDeviceIndependentFlagsMask;
|
||||
return (mods == NSEventModifierFlagFunction) // Fn
|
||||
&& (e->nativeVirtualKey() == 3); // F
|
||||
}
|
||||
|
||||
} // namespace base::Platform::GlobalShortcuts
|
||||
9
Telegram/lib_base/base/platform/mac/base_haptic_mac.h
Normal file
9
Telegram/lib_base/base/platform/mac/base_haptic_mac.h
Normal file
@@ -0,0 +1,9 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/platform/base_platform_haptic.h"
|
||||
33
Telegram/lib_base/base/platform/mac/base_haptic_mac.mm
Normal file
33
Telegram/lib_base/base/platform/mac/base_haptic_mac.mm
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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/mac/base_haptic_mac.h"
|
||||
|
||||
#include "base/integration.h"
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
namespace base::Platform {
|
||||
|
||||
void Haptic() {
|
||||
Integration::Instance().enterFromEventLoop([=] {
|
||||
[[NSHapticFeedbackManager defaultPerformer]
|
||||
performFeedbackPattern:NSHapticFeedbackPatternGeneric
|
||||
performanceTime:NSHapticFeedbackPerformanceTimeDrawCompleted];
|
||||
});
|
||||
}
|
||||
|
||||
bool IsSwipeBackEnabled() {
|
||||
static const auto cached = [] {
|
||||
const auto defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSNumber *setting = [defaults
|
||||
objectForKey:@"AppleEnableSwipeNavigateWithScrolls"];
|
||||
return setting ? [setting boolValue] : true;
|
||||
}();
|
||||
return cached;
|
||||
}
|
||||
|
||||
} // namespace base::Platform
|
||||
51
Telegram/lib_base/base/platform/mac/base_info_mac.h
Normal file
51
Telegram/lib_base/base/platform/mac/base_info_mac.h
Normal file
@@ -0,0 +1,51 @@
|
||||
// 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 IsMac() {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline constexpr bool IsMacStoreBuild() {
|
||||
#ifdef OS_MAC_STORE
|
||||
return true;
|
||||
#else // OS_MAC_STORE
|
||||
return false;
|
||||
#endif // OS_MAC_STORE
|
||||
}
|
||||
|
||||
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 IsLinux() { return false; }
|
||||
inline bool IsX11() { return false; }
|
||||
inline bool IsWayland() { return false; }
|
||||
inline bool IsXwayland() { return false; }
|
||||
inline QString GetLibcName() { return QString(); }
|
||||
inline QString GetLibcVersion() { return QString(); }
|
||||
inline QString GetWindowManager() { return QString(); }
|
||||
|
||||
void OpenInputMonitoringPrivacySettings();
|
||||
void OpenDesktopCapturePrivacySettings();
|
||||
void OpenAccessibilityPrivacySettings();
|
||||
|
||||
} // namespace Platform
|
||||
292
Telegram/lib_base/base/platform/mac/base_info_mac.mm
Normal file
292
Telegram/lib_base/base/platform/mac/base_info_mac.mm
Normal file
@@ -0,0 +1,292 @@
|
||||
// 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/mac/base_info_mac.h"
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "base/algorithm.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/mac/base_utilities_mac.h"
|
||||
|
||||
#include <QtCore/QDate>
|
||||
#include <QtCore/QProcess>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonValue>
|
||||
#include <QtCore/QOperatingSystemVersion>
|
||||
#include <sys/sysctl.h>
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#import <IOKit/hidsystem/IOHIDLib.h>
|
||||
|
||||
@interface WakeUpObserver : NSObject {
|
||||
}
|
||||
|
||||
- (void) receiveWakeNote:(NSNotification*)note;
|
||||
|
||||
@end // @interface WakeUpObserver
|
||||
|
||||
@implementation WakeUpObserver {
|
||||
}
|
||||
|
||||
- (void) receiveWakeNote:(NSNotification*)aNotification {
|
||||
base::CheckLocalTime();
|
||||
}
|
||||
|
||||
@end // @implementation WakeUpObserver
|
||||
|
||||
namespace Platform {
|
||||
namespace {
|
||||
|
||||
WakeUpObserver *GlobalWakeUpObserver = nil;
|
||||
|
||||
QString FromIdentifier(const QString &model) {
|
||||
if (model.isEmpty() || model.toLower().indexOf("mac") < 0) {
|
||||
return QString();
|
||||
}
|
||||
QStringList words;
|
||||
QString word;
|
||||
for (const QChar &ch : model) {
|
||||
if (!ch.isLetter()) {
|
||||
continue;
|
||||
}
|
||||
if (ch.isUpper()) {
|
||||
if (!word.isEmpty()) {
|
||||
words.push_back(word);
|
||||
word = QString();
|
||||
}
|
||||
}
|
||||
word.append(ch);
|
||||
}
|
||||
if (!word.isEmpty()) {
|
||||
words.push_back(word);
|
||||
}
|
||||
QString result;
|
||||
for (const QString &word : words) {
|
||||
if (!result.isEmpty()
|
||||
&& word != "Mac"
|
||||
&& word != "Book") {
|
||||
result.append(' ');
|
||||
}
|
||||
result.append(word);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] int MajorVersion() {
|
||||
static const auto current = QOperatingSystemVersion::current();
|
||||
return current.majorVersion();
|
||||
}
|
||||
|
||||
[[nodiscard]] int MinorVersion() {
|
||||
static const auto current = QOperatingSystemVersion::current();
|
||||
return current.minorVersion();
|
||||
}
|
||||
|
||||
[[nodiscard]] int PatchVersion() {
|
||||
static const auto current = QOperatingSystemVersion::current();
|
||||
return current.microVersion();
|
||||
}
|
||||
|
||||
template <int Major, int Minor>
|
||||
bool IsMacThatOrGreater() {
|
||||
static const auto result = (MajorVersion() >= Major)
|
||||
&& ((MajorVersion() > Major) || (MinorVersion() >= Minor));
|
||||
return result;
|
||||
}
|
||||
|
||||
template <int Minor>
|
||||
[[nodiscard]] bool IsMac10ThatOrGreater() {
|
||||
return IsMacThatOrGreater<10, Minor>();
|
||||
}
|
||||
|
||||
[[nodiscard]] NSURL *PrivacySettingsUrl(const QString §ion) {
|
||||
NSString *url = Q2NSString(
|
||||
"x-apple.systempreferences:com.apple.preference.security?" + section
|
||||
);
|
||||
return [NSURL URLWithString:url];
|
||||
}
|
||||
|
||||
[[nodiscard]] bool RunningThroughRosetta() {
|
||||
auto result = int(0);
|
||||
auto size = sizeof(result);
|
||||
sysctlbyname("sysctl.proc_translated", &result, &size, nullptr, 0);
|
||||
return (result == 1);
|
||||
}
|
||||
|
||||
[[nodiscard]] QString DeviceFromSystemProfiler() {
|
||||
// Starting with MacBook M2 the hw.model returns simply Mac[digits],[digits].
|
||||
// So we try reading "system_profiler" output.
|
||||
auto process = QProcess();
|
||||
process.start(
|
||||
"system_profiler",
|
||||
{ "-json", "SPHardwareDataType", "-detailLevel", "mini" });
|
||||
process.waitForFinished();
|
||||
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
|
||||
const auto document = QJsonDocument::fromJson(process.readAll(), &error);
|
||||
if (error.error != QJsonParseError::NoError || !document.isObject()) {
|
||||
return {};
|
||||
}
|
||||
const auto fields = document.object()["SPHardwareDataType"].toArray()[0].toObject();
|
||||
const auto result = fields["machine_name"].toString();
|
||||
if (result.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
const auto chip = fields["chip_type"].toString();
|
||||
return chip.startsWith("Apple ") ? (result + chip.mid(5)) : result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QString DeviceModelPretty() {
|
||||
using namespace base::Platform;
|
||||
static const auto result = FinalizeDeviceModel([&] {
|
||||
const auto fromSystemProfiler = DeviceFromSystemProfiler();
|
||||
if (!fromSystemProfiler.isEmpty()) {
|
||||
return fromSystemProfiler;
|
||||
}
|
||||
size_t length = 0;
|
||||
sysctlbyname("hw.model", nullptr, &length, nullptr, 0);
|
||||
if (length > 0) {
|
||||
QByteArray bytes(length, Qt::Uninitialized);
|
||||
sysctlbyname("hw.model", bytes.data(), &length, nullptr, 0);
|
||||
const auto parsed = base::CleanAndSimplify(
|
||||
FromIdentifier(QString::fromUtf8(bytes)));
|
||||
if (!parsed.isEmpty()) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}());
|
||||
return result;
|
||||
}
|
||||
|
||||
QString SystemVersionPretty() {
|
||||
const auto major = MajorVersion();
|
||||
const auto minor = MinorVersion();
|
||||
const auto patch = PatchVersion();
|
||||
const auto addAsPatch = (patch > 0) ? u".%1"_q.arg(patch) : QString();
|
||||
if (major < 10) {
|
||||
return "OS X";
|
||||
} else if (major == 10 && minor < 12) {
|
||||
return QString("OS X 10.%1").arg(minor) + addAsPatch;
|
||||
}
|
||||
return QString("macOS %1.%2").arg(major).arg(minor) + addAsPatch;
|
||||
}
|
||||
|
||||
QString SystemCountry() {
|
||||
NSLocale *currentLocale = [NSLocale currentLocale]; // get the current locale.
|
||||
NSString *countryCode = [currentLocale objectForKey:NSLocaleCountryCode];
|
||||
return countryCode ? NS2QString(countryCode) : QString();
|
||||
}
|
||||
|
||||
QString SystemLanguage() {
|
||||
if (auto currentLocale = [NSLocale currentLocale]) { // get the current locale.
|
||||
if (NSString *collator = [currentLocale objectForKey:NSLocaleCollatorIdentifier]) {
|
||||
return NS2QString(collator);
|
||||
}
|
||||
if (NSString *identifier = [currentLocale objectForKey:NSLocaleIdentifier]) {
|
||||
return NS2QString(identifier);
|
||||
}
|
||||
if (NSString *language = [currentLocale objectForKey:NSLocaleLanguageCode]) {
|
||||
return NS2QString(language);
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QDate WhenSystemBecomesOutdated() {
|
||||
if (!IsMac10_13OrGreater()) {
|
||||
return QDate(2023, 7, 1);
|
||||
}
|
||||
return QDate();
|
||||
}
|
||||
|
||||
int AutoUpdateVersion() {
|
||||
if (!IsMac10_13OrGreater()) {
|
||||
return 2;
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
|
||||
QString AutoUpdateKey() {
|
||||
if (QSysInfo::currentCpuArchitecture().startsWith("arm")
|
||||
|| RunningThroughRosetta()) {
|
||||
return "armac";
|
||||
} else {
|
||||
return "mac";
|
||||
}
|
||||
}
|
||||
|
||||
bool IsMac10_12OrGreater() {
|
||||
return IsMac10ThatOrGreater<12>();
|
||||
}
|
||||
|
||||
bool IsMac10_13OrGreater() {
|
||||
return IsMac10ThatOrGreater<13>();
|
||||
}
|
||||
|
||||
bool IsMac10_14OrGreater() {
|
||||
return IsMac10ThatOrGreater<14>();
|
||||
}
|
||||
|
||||
bool IsMac10_15OrGreater() {
|
||||
return IsMac10ThatOrGreater<15>();
|
||||
}
|
||||
|
||||
bool IsMac11_0OrGreater() {
|
||||
return IsMacThatOrGreater<11, 0>();
|
||||
}
|
||||
|
||||
bool IsMac26_0OrGreater() {
|
||||
return IsMacThatOrGreater<26, 0>();
|
||||
}
|
||||
|
||||
void Start(QJsonObject settings) {
|
||||
Expects(GlobalWakeUpObserver == nil);
|
||||
|
||||
GlobalWakeUpObserver = [[WakeUpObserver alloc] init];
|
||||
|
||||
NSNotificationCenter *center = [[NSWorkspace sharedWorkspace] notificationCenter];
|
||||
Assert(center != nil);
|
||||
|
||||
[center
|
||||
addObserver: GlobalWakeUpObserver
|
||||
selector: @selector(receiveWakeNote:)
|
||||
name: NSWorkspaceDidWakeNotification
|
||||
object: nil];
|
||||
|
||||
Ensures(GlobalWakeUpObserver != nil);
|
||||
}
|
||||
|
||||
void Finish() {
|
||||
Expects(GlobalWakeUpObserver != nil);
|
||||
|
||||
[GlobalWakeUpObserver release];
|
||||
GlobalWakeUpObserver = nil;
|
||||
}
|
||||
|
||||
void OpenInputMonitoringPrivacySettings() {
|
||||
if (@available(macOS 10.15, *)) {
|
||||
IOHIDRequestAccess(kIOHIDRequestTypeListenEvent);
|
||||
}
|
||||
[[NSWorkspace sharedWorkspace] openURL:PrivacySettingsUrl("Privacy_ListenEvent")];
|
||||
}
|
||||
|
||||
void OpenDesktopCapturePrivacySettings() {
|
||||
if (@available(macOS 11.0, *)) {
|
||||
CGRequestScreenCaptureAccess();
|
||||
}
|
||||
[[NSWorkspace sharedWorkspace] openURL:PrivacySettingsUrl("Privacy_ScreenCapture")];
|
||||
}
|
||||
|
||||
void OpenAccessibilityPrivacySettings() {
|
||||
NSDictionary *const options=@{(__bridge NSString *)kAXTrustedCheckOptionPrompt: @TRUE};
|
||||
AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)options);
|
||||
}
|
||||
|
||||
} // namespace 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
|
||||
|
||||
68
Telegram/lib_base/base/platform/mac/base_last_input_mac.mm
Normal file
68
Telegram/lib_base/base/platform/mac/base_last_input_mac.mm
Normal file
@@ -0,0 +1,68 @@
|
||||
// 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/mac/base_last_input_mac.h"
|
||||
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
namespace base::Platform {
|
||||
|
||||
// Taken from https://github.com/trueinteractions/tint/issues/53.
|
||||
std::optional<crl::time> LastUserInputTime() {
|
||||
CFMutableDictionaryRef properties = 0;
|
||||
CFTypeRef obj;
|
||||
mach_port_t masterPort;
|
||||
io_iterator_t iter;
|
||||
io_registry_entry_t curObj;
|
||||
|
||||
IOMasterPort(MACH_PORT_NULL, &masterPort);
|
||||
|
||||
/* Get IOHIDSystem */
|
||||
IOServiceGetMatchingServices(masterPort, IOServiceMatching("IOHIDSystem"), &iter);
|
||||
if (iter == 0) {
|
||||
return std::nullopt;
|
||||
} else {
|
||||
curObj = IOIteratorNext(iter);
|
||||
}
|
||||
if (IORegistryEntryCreateCFProperties(curObj, &properties, kCFAllocatorDefault, 0) == KERN_SUCCESS && properties != NULL) {
|
||||
obj = CFDictionaryGetValue(properties, CFSTR("HIDIdleTime"));
|
||||
CFRetain(obj);
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
uint64 err = ~0L, idleTime = err;
|
||||
if (obj) {
|
||||
CFTypeID type = CFGetTypeID(obj);
|
||||
|
||||
if (type == CFDataGetTypeID()) {
|
||||
CFDataGetBytes((CFDataRef) obj, CFRangeMake(0, sizeof(idleTime)), (UInt8*)&idleTime);
|
||||
} else if (type == CFNumberGetTypeID()) {
|
||||
CFNumberGetValue((CFNumberRef)obj, kCFNumberSInt64Type, &idleTime);
|
||||
} else {
|
||||
// error
|
||||
}
|
||||
|
||||
CFRelease(obj);
|
||||
|
||||
if (idleTime != err) {
|
||||
idleTime /= 1000000; // return as ms
|
||||
}
|
||||
} else {
|
||||
// error
|
||||
}
|
||||
|
||||
CFRelease((CFTypeRef)properties);
|
||||
IOObjectRelease(curObj);
|
||||
IOObjectRelease(iter);
|
||||
if (idleTime == err) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return (crl::now() - static_cast<crl::time>(idleTime));
|
||||
}
|
||||
|
||||
} // 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,66 @@
|
||||
// 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/mac/base_layout_switch_mac.h"
|
||||
|
||||
#include <Carbon/Carbon.h>
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
namespace base::Platform {
|
||||
|
||||
bool SwitchKeyboardLayoutToEnglish() {
|
||||
auto result = false;
|
||||
|
||||
@autoreleasepool {
|
||||
|
||||
const auto kEnglish = base::flat_map<NSString*, int>{
|
||||
{ @"com.apple.keylayout.USExtended", 0 },
|
||||
{ @"com.apple.keylayout.ABC", 0 },
|
||||
{ @"com.apple.keylayout.Australian", 1 },
|
||||
{ @"com.apple.keylayout.British", 2 },
|
||||
{ @"com.apple.keylayout.British-PC", 3 },
|
||||
{ @"com.apple.keylayout.Canadian", 1 },
|
||||
{ @"com.apple.keylayout.Colemak", 1 },
|
||||
{ @"com.apple.keylayout.Dvorak", 0 },
|
||||
{ @"com.apple.keylayout.Dvorak-Left", 0 },
|
||||
{ @"com.apple.keylayout.DVORAK-QWERTYCMD", 0 },
|
||||
{ @"com.apple.keylayout.Dvorak-Right", 0 },
|
||||
{ @"com.apple.keylayout.Irish", 1 },
|
||||
{ @"com.apple.keylayout.USInternational-PC", 4 },
|
||||
{ @"com.apple.keylayout.US", 5 },
|
||||
};
|
||||
|
||||
auto selectedLayout = (NSObject*)NULL;
|
||||
auto selectedLevel = 0;
|
||||
const auto offer = [&](NSObject *layout, int level) {
|
||||
if (level > selectedLevel) {
|
||||
selectedLayout = layout;
|
||||
selectedLevel = level;
|
||||
}
|
||||
};
|
||||
|
||||
NSArray *list = [(NSArray *)TISCreateInputSourceList(NULL,NO) autorelease];
|
||||
for (NSObject *layout in list) {
|
||||
NSString *layoutId = (NSString*)TISGetInputSourceProperty(
|
||||
(TISInputSourceRef)layout,
|
||||
kTISPropertyInputSourceID);
|
||||
for (const auto &[checkId, checkLevel] : kEnglish) {
|
||||
if ([layoutId isEqualToString:checkId]) {
|
||||
offer(layout, checkLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedLayout != nullptr) {
|
||||
TISSelectInputSource(TISInputSourceRef(selectedLayout));
|
||||
result = true;
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace base::Platform
|
||||
@@ -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,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"
|
||||
@@ -0,0 +1,99 @@
|
||||
// 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/mac/base_power_save_blocker_mac.h"
|
||||
|
||||
#include "base/debug_log.h"
|
||||
|
||||
#include <crl/crl_object_on_thread.h>
|
||||
|
||||
// Thanks Chromium: services/device/wake_lock/power_save_blocker
|
||||
|
||||
#include <IOKit/pwr_mgt/IOPMLib.h>
|
||||
|
||||
namespace base::Platform {
|
||||
namespace {
|
||||
|
||||
// Power management cannot be done on the UI thread. IOPMAssertionCreate does a
|
||||
// synchronous MIG call to configd, so if it is called on the main thread the UI
|
||||
// is at the mercy of another process. See http://crbug.com/79559 and
|
||||
// http://www.opensource.apple.com/source/IOKitUser/IOKitUser-514.16.31/pwr_mgt.subproj/IOPMLibPrivate.c .
|
||||
|
||||
class BlockManager final {
|
||||
public:
|
||||
explicit BlockManager(crl::weak_on_thread<BlockManager> weak);
|
||||
|
||||
void block(PowerSaveBlockType type, const QString &description);
|
||||
void unblock(PowerSaveBlockType type);
|
||||
|
||||
private:
|
||||
crl::weak_on_thread<BlockManager> _weak;
|
||||
IOPMAssertionID _assertions[kPowerSaveBlockTypeCount] = {};
|
||||
|
||||
};
|
||||
|
||||
BlockManager::BlockManager(crl::weak_on_thread<BlockManager> weak)
|
||||
: _weak(weak) {
|
||||
}
|
||||
|
||||
void BlockManager::block(PowerSaveBlockType type, const QString &description) {
|
||||
const auto level = CFStringRef([&] {
|
||||
// See QA1340 <http://developer.apple.com/library/mac/#qa/qa1340/> for more
|
||||
// details.
|
||||
switch (type) {
|
||||
case PowerSaveBlockType::PreventAppSuspension:
|
||||
return kIOPMAssertionTypeNoIdleSleep;
|
||||
case PowerSaveBlockType::PreventDisplaySleep:
|
||||
return kIOPMAssertionTypeNoDisplaySleep;
|
||||
}
|
||||
Unexpected("Type in BlockManager::block.");
|
||||
}());
|
||||
const auto reason = description.toCFString();
|
||||
IOReturn result = IOPMAssertionCreateWithName(
|
||||
level,
|
||||
kIOPMAssertionLevelOn,
|
||||
reason,
|
||||
&_assertions[PowerSaveBlockTypeIndex(type)]);
|
||||
CFRelease(reason);
|
||||
if (result != kIOReturnSuccess) {
|
||||
LOG(("System Error: IOPMAssertionCreate: %1").arg(result));
|
||||
}
|
||||
}
|
||||
|
||||
void BlockManager::unblock(PowerSaveBlockType type) {
|
||||
const auto index = PowerSaveBlockTypeIndex(type);
|
||||
if (_assertions[index] != kIOPMNullAssertionID) {
|
||||
IOReturn result = IOPMAssertionRelease(_assertions[index]);
|
||||
_assertions[index] = kIOPMNullAssertionID;
|
||||
if (result != kIOReturnSuccess) {
|
||||
LOG(("System Error: IOPMAssertionRelease: %1").arg(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] crl::object_on_thread<BlockManager> &Manager() {
|
||||
static auto result = crl::object_on_thread<BlockManager>();
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void BlockPowerSave(
|
||||
PowerSaveBlockType type,
|
||||
const QString &description,
|
||||
QPointer<QWindow> window) {
|
||||
Manager().with([=](BlockManager &instance) {
|
||||
instance.block(type, description);
|
||||
});
|
||||
}
|
||||
|
||||
void UnblockPowerSave(PowerSaveBlockType type, QPointer<QWindow> window) {
|
||||
Manager().with([=](BlockManager &instance) {
|
||||
instance.unblock(type);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace base::Platform
|
||||
9
Telegram/lib_base/base/platform/mac/base_process_mac.h
Normal file
9
Telegram/lib_base/base/platform/mac/base_process_mac.h
Normal file
@@ -0,0 +1,9 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/platform/base_platform_process.h"
|
||||
23
Telegram/lib_base/base/platform/mac/base_process_mac.mm
Normal file
23
Telegram/lib_base/base/platform/mac/base_process_mac.mm
Normal file
@@ -0,0 +1,23 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "base/platform/mac/base_process_mac.h"
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
namespace base::Platform {
|
||||
|
||||
void ActivateProcessWindow(int64 pid, WId windowId) {
|
||||
}
|
||||
|
||||
void ActivateThisProcessWindow(WId windowId) {
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
if (const auto view = reinterpret_cast<NSView*>(windowId)) {
|
||||
[[view window] makeKeyAndOrderFront:NSApp];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace base::Platform
|
||||
@@ -0,0 +1,379 @@
|
||||
// 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/mac/base_utilities_mac.h"
|
||||
|
||||
#import <MediaPlayer/MediaPlayer.h>
|
||||
|
||||
#include <QtGui/QImage>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
namespace {
|
||||
|
||||
using Command = base::Platform::SystemMediaControls::Command;
|
||||
|
||||
using ::Platform::Q2NSString;
|
||||
using ::Platform::Q2NSImage;
|
||||
|
||||
inline auto CommandCenter() {
|
||||
return [MPRemoteCommandCenter sharedCommandCenter];
|
||||
}
|
||||
|
||||
MPNowPlayingPlaybackState ConvertPlaybackStatus(
|
||||
base::Platform::SystemMediaControls::PlaybackStatus status) {
|
||||
using Status = base::Platform::SystemMediaControls::PlaybackStatus;
|
||||
switch (status) {
|
||||
case Status::Playing: return MPNowPlayingPlaybackStatePlaying;
|
||||
case Status::Paused: return MPNowPlayingPlaybackStatePaused;
|
||||
case Status::Stopped: return MPNowPlayingPlaybackStateStopped;
|
||||
}
|
||||
Unexpected("ConvertPlaybackStatus in SystemMediaControls");
|
||||
}
|
||||
|
||||
auto EventToCommand(MPRemoteCommandEvent *event) {
|
||||
const auto commandCenter = CommandCenter();
|
||||
const auto command = event.command;
|
||||
if (command == commandCenter.pauseCommand) {
|
||||
return Command::Pause;
|
||||
} else if (command == commandCenter.playCommand) {
|
||||
return Command::Play;
|
||||
} else if (command == commandCenter.stopCommand) {
|
||||
return Command::Stop;
|
||||
} else if (command == commandCenter.togglePlayPauseCommand) {
|
||||
return Command::PlayPause;
|
||||
} else if (command == commandCenter.nextTrackCommand) {
|
||||
return Command::Next;
|
||||
} else if (command == commandCenter.previousTrackCommand) {
|
||||
return Command::Previous;
|
||||
}
|
||||
return Command::None;
|
||||
}
|
||||
|
||||
struct RemoteCommand {
|
||||
const MPRemoteCommand *command;
|
||||
bool lastEnabled = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#pragma mark - CommandHandler
|
||||
|
||||
@interface CommandHandler : NSObject {
|
||||
}
|
||||
@end // @interface CommandHandler
|
||||
|
||||
@implementation CommandHandler {
|
||||
rpl::event_stream<Command> _commandRequests;
|
||||
rpl::event_stream<int> _seekRequests;
|
||||
std::vector<RemoteCommand> _commands;
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
|
||||
const auto center = CommandCenter();
|
||||
|
||||
_commands = {
|
||||
{ .command = center.pauseCommand },
|
||||
{ .command = center.playCommand },
|
||||
{ .command = center.stopCommand },
|
||||
{ .command = center.togglePlayPauseCommand },
|
||||
{ .command = center.nextTrackCommand },
|
||||
{ .command = center.previousTrackCommand },
|
||||
{ .command = center.changeRepeatModeCommand },
|
||||
{ .command = center.changeShuffleModeCommand },
|
||||
{ .command = center.changePlaybackRateCommand },
|
||||
{ .command = center.seekBackwardCommand },
|
||||
{ .command = center.seekForwardCommand },
|
||||
{ .command = center.skipBackwardCommand },
|
||||
{ .command = center.skipForwardCommand },
|
||||
{ .command = center.changePlaybackPositionCommand },
|
||||
{ .command = center.ratingCommand },
|
||||
{ .command = center.likeCommand },
|
||||
{ .command = center.dislikeCommand },
|
||||
{ .command = center.bookmarkCommand },
|
||||
{ .command = center.enableLanguageOptionCommand },
|
||||
{ .command = center.disableLanguageOptionCommand },
|
||||
};
|
||||
|
||||
[self initCommands];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initCommands {
|
||||
for (const auto &c : _commands) {
|
||||
c.command.enabled = c.lastEnabled;
|
||||
}
|
||||
|
||||
const auto center = CommandCenter();
|
||||
const auto selector = @selector(onCommand:);
|
||||
[center.pauseCommand addTarget:self action:selector];
|
||||
[center.playCommand addTarget:self action:selector];
|
||||
[center.stopCommand addTarget:self action:selector];
|
||||
[center.togglePlayPauseCommand addTarget:self action:selector];
|
||||
[center.nextTrackCommand addTarget:self action:selector];
|
||||
[center.previousTrackCommand addTarget:self action:selector];
|
||||
|
||||
[center.changePlaybackPositionCommand
|
||||
addTarget:self
|
||||
action:@selector(onSeek:)];
|
||||
}
|
||||
|
||||
- (void)clearCommands {
|
||||
const auto center = CommandCenter();
|
||||
|
||||
for (auto &c : _commands) {
|
||||
c.lastEnabled = c.command.enabled;
|
||||
c.command.enabled = false;
|
||||
}
|
||||
|
||||
[center.pauseCommand removeTarget:self];
|
||||
[center.playCommand removeTarget:self];
|
||||
[center.stopCommand removeTarget:self];
|
||||
[center.togglePlayPauseCommand removeTarget:self];
|
||||
[center.nextTrackCommand removeTarget:self];
|
||||
[center.previousTrackCommand removeTarget:self];
|
||||
[center.changePlaybackPositionCommand removeTarget:self];
|
||||
}
|
||||
|
||||
- (rpl::producer<Command>)commandRequests {
|
||||
return _commandRequests.events();
|
||||
}
|
||||
|
||||
- (rpl::producer<int>)seekRequests {
|
||||
return _seekRequests.events();
|
||||
}
|
||||
|
||||
- (MPRemoteCommandHandlerStatus)onCommand:(MPRemoteCommandEvent*)event {
|
||||
base::Integration::Instance().enterFromEventLoop([&] {
|
||||
self->_commandRequests.fire_copy(EventToCommand(event));
|
||||
});
|
||||
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
}
|
||||
|
||||
- (MPRemoteCommandHandlerStatus)onSeek:(
|
||||
MPChangePlaybackPositionCommandEvent*)event {
|
||||
base::Integration::Instance().enterFromEventLoop([&] {
|
||||
self->_seekRequests.fire(event.positionTime * 1000);
|
||||
});
|
||||
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self clearCommands];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end // @@implementation CommandHandler
|
||||
|
||||
namespace base::Platform {
|
||||
|
||||
struct SystemMediaControls::Private {
|
||||
Private(
|
||||
not_null<NSMutableDictionary*> info,
|
||||
not_null<CommandHandler*> commandHandler)
|
||||
: info(info)
|
||||
, commandHandler(commandHandler) {
|
||||
}
|
||||
|
||||
[[nodiscard]] float64 duration() const {
|
||||
return ((NSNumber*)[info
|
||||
objectForKey:MPMediaItemPropertyPlaybackDuration]).doubleValue;
|
||||
}
|
||||
|
||||
const not_null<NSMutableDictionary*> info;
|
||||
const not_null<CommandHandler*> commandHandler;
|
||||
bool enabled = false;
|
||||
};
|
||||
|
||||
SystemMediaControls::SystemMediaControls()
|
||||
: _private(std::make_unique<Private>(
|
||||
[[NSMutableDictionary alloc] init],
|
||||
[[CommandHandler alloc] init])) {
|
||||
}
|
||||
|
||||
SystemMediaControls::~SystemMediaControls() {
|
||||
setEnabled(false);
|
||||
[_private->info release];
|
||||
[_private->commandHandler release];
|
||||
}
|
||||
|
||||
bool SystemMediaControls::init() {
|
||||
clearMetadata();
|
||||
updateDisplay();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SystemMediaControls::setApplicationName(const QString &name) {
|
||||
}
|
||||
|
||||
void SystemMediaControls::setEnabled(bool enabled) {
|
||||
if (_private->enabled == enabled) {
|
||||
return;
|
||||
}
|
||||
_private->enabled = enabled;
|
||||
if (enabled) {
|
||||
[_private->commandHandler initCommands];
|
||||
} else {
|
||||
[_private->commandHandler clearCommands];
|
||||
}
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
void SystemMediaControls::setIsNextEnabled(bool value) {
|
||||
CommandCenter().nextTrackCommand.enabled = value;
|
||||
}
|
||||
|
||||
void SystemMediaControls::setIsPreviousEnabled(bool value) {
|
||||
CommandCenter().previousTrackCommand.enabled = value;
|
||||
}
|
||||
|
||||
void SystemMediaControls::setIsPlayPauseEnabled(bool value) {
|
||||
CommandCenter().togglePlayPauseCommand.enabled = value;
|
||||
}
|
||||
|
||||
void SystemMediaControls::setIsStopEnabled(bool value) {
|
||||
CommandCenter().stopCommand.enabled = value;
|
||||
}
|
||||
|
||||
void SystemMediaControls::setPlaybackStatus(
|
||||
SystemMediaControls::PlaybackStatus status) {
|
||||
[MPNowPlayingInfoCenter defaultCenter].playbackState =
|
||||
ConvertPlaybackStatus(status);
|
||||
}
|
||||
|
||||
void SystemMediaControls::setLoopStatus(LoopStatus status) {
|
||||
}
|
||||
|
||||
void SystemMediaControls::setShuffle(bool value) {
|
||||
}
|
||||
|
||||
void SystemMediaControls::setTitle(const QString &title) {
|
||||
[_private->info
|
||||
setObject:Q2NSString(title)
|
||||
forKey:MPMediaItemPropertyTitle];
|
||||
}
|
||||
|
||||
void SystemMediaControls::setArtist(const QString &artist) {
|
||||
[_private->info
|
||||
setObject:Q2NSString(artist)
|
||||
forKey:MPMediaItemPropertyArtist];
|
||||
}
|
||||
|
||||
void SystemMediaControls::setThumbnail(const QImage &thumbnail) {
|
||||
if (thumbnail.isNull()) {
|
||||
return;
|
||||
}
|
||||
if (@available(macOS 10.13.2, *)) {
|
||||
const auto copy = thumbnail;
|
||||
[_private->info
|
||||
setObject:[[[MPMediaItemArtwork alloc]
|
||||
initWithBoundsSize:CGSizeMake(copy.width(), copy.height())
|
||||
requestHandler:^NSImage *(CGSize size) {
|
||||
return Q2NSImage(copy.scaled(
|
||||
int(size.width),
|
||||
int(size.height)));
|
||||
}] autorelease]
|
||||
forKey:MPMediaItemPropertyArtwork];
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
void SystemMediaControls::setDuration(int duration) {
|
||||
[_private->info
|
||||
setObject:[NSNumber numberWithDouble:(duration / 1000.)]
|
||||
forKey:MPMediaItemPropertyPlaybackDuration];
|
||||
}
|
||||
|
||||
void SystemMediaControls::setPosition(int position) {
|
||||
[_private->info
|
||||
setObject:[NSNumber numberWithDouble:(position / 1000.)]
|
||||
forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
|
||||
}
|
||||
|
||||
void SystemMediaControls::setVolume(float64 volume) {
|
||||
}
|
||||
|
||||
void SystemMediaControls::clearThumbnail() {
|
||||
if (@available(macOS 10.13.2, *)) {
|
||||
[_private->info removeObjectForKey:MPMediaItemPropertyArtwork];
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
void SystemMediaControls::clearMetadata() {
|
||||
const auto zeroNumber = [NSNumber numberWithInt:0];
|
||||
const auto oneNumber = [NSNumber numberWithInt:1];
|
||||
const auto &info = _private->info;
|
||||
[info
|
||||
setObject:zeroNumber
|
||||
forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
|
||||
[info setObject:zeroNumber forKey:MPMediaItemPropertyPlaybackDuration];
|
||||
[info setObject:oneNumber forKey:MPNowPlayingInfoPropertyPlaybackRate];
|
||||
[info
|
||||
setObject:oneNumber
|
||||
forKey:MPNowPlayingInfoPropertyDefaultPlaybackRate];
|
||||
[info setObject:@"" forKey:MPMediaItemPropertyTitle];
|
||||
[info setObject:@"" forKey:MPMediaItemPropertyArtist];
|
||||
|
||||
[info
|
||||
setObject:@(MPNowPlayingInfoMediaTypeAudio)
|
||||
forKey:MPNowPlayingInfoPropertyMediaType];
|
||||
}
|
||||
|
||||
void SystemMediaControls::updateDisplay() {
|
||||
[[MPNowPlayingInfoCenter defaultCenter]
|
||||
performSelectorOnMainThread:@selector(setNowPlayingInfo:)
|
||||
withObject:((_private->enabled && _private->duration())
|
||||
? _private->info
|
||||
: nil)
|
||||
waitUntilDone:false];
|
||||
}
|
||||
|
||||
auto SystemMediaControls::commandRequests() const
|
||||
-> rpl::producer<SystemMediaControls::Command> {
|
||||
return [_private->commandHandler commandRequests];
|
||||
}
|
||||
|
||||
rpl::producer<float64> SystemMediaControls::seekRequests() const {
|
||||
return (
|
||||
[_private->commandHandler seekRequests]
|
||||
) | rpl::map([=](int position) {
|
||||
return float64(position) / (_private->duration() * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<float64> SystemMediaControls::volumeChangeRequests() const {
|
||||
return rpl::never<float64>();
|
||||
}
|
||||
|
||||
rpl::producer<> SystemMediaControls::updatePositionRequests() const {
|
||||
return rpl::never<>();
|
||||
}
|
||||
|
||||
bool SystemMediaControls::seekingSupported() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemMediaControls::volumeSupported() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SystemMediaControls::Supported() {
|
||||
if (@available(macOS 10.12.2, *)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // 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/system_unlock.h"
|
||||
@@ -0,0 +1,87 @@
|
||||
// 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/mac/base_system_unlock_mac.h"
|
||||
|
||||
#include "base/platform/mac/base_utilities_mac.h"
|
||||
|
||||
#include <Foundation/Foundation.h>
|
||||
#include <LocalAuthentication/LocalAuthentication.h>
|
||||
|
||||
namespace base {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] bool Available(LAContext *context, LAPolicy policy) {
|
||||
NSError *error = nil;
|
||||
return [context canEvaluatePolicy:policy error:&error];
|
||||
}
|
||||
|
||||
[[nodiscard]] SystemUnlockAvailability Available(bool lookupDetails) {
|
||||
LAContext *context = [[LAContext alloc] init];
|
||||
|
||||
auto result = SystemUnlockAvailability{
|
||||
.known = true,
|
||||
.available = Available(
|
||||
context,
|
||||
LAPolicyDeviceOwnerAuthentication),
|
||||
.withBiometrics = lookupDetails && Available(
|
||||
context,
|
||||
LAPolicyDeviceOwnerAuthenticationWithBiometrics),
|
||||
};
|
||||
if (lookupDetails) {
|
||||
if (@available(macOS 10.15, *)) {
|
||||
result.withCompanion = Available(
|
||||
context,
|
||||
LAPolicyDeviceOwnerAuthenticationWithWatch);
|
||||
}
|
||||
}
|
||||
[context release];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
rpl::producer<SystemUnlockAvailability> SystemUnlockStatus(
|
||||
bool lookupDetails) {
|
||||
static auto result = rpl::variable<SystemUnlockAvailability>();
|
||||
|
||||
auto refreshed = Available(lookupDetails);
|
||||
if (!lookupDetails) {
|
||||
const auto now = result.current();
|
||||
refreshed.withBiometrics = now.available && now.withBiometrics;
|
||||
refreshed.withCompanion = now.available && now.withCompanion;
|
||||
}
|
||||
result = refreshed;
|
||||
|
||||
return result.value();
|
||||
}
|
||||
|
||||
void SuggestSystemUnlock(
|
||||
not_null<QWidget*> parent,
|
||||
const QString &text,
|
||||
Fn<void(SystemUnlockResult)> done) {
|
||||
LAContext *context = [[LAContext alloc] init];
|
||||
if (Available(context, LAPolicyDeviceOwnerAuthentication)) {
|
||||
[context
|
||||
evaluatePolicy:LAPolicyDeviceOwnerAuthentication
|
||||
localizedReason:Platform::Q2NSString(text)
|
||||
reply:^(BOOL success, NSError *error) {
|
||||
const auto code = int(error.code);
|
||||
if (success) {
|
||||
done(SystemUnlockResult::Success);
|
||||
} else if (error.code == LAErrorTouchIDLockout) {
|
||||
done(SystemUnlockResult::FloodError);
|
||||
} else {
|
||||
done(SystemUnlockResult::Cancelled);
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
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/platform/base_platform_url_scheme.h"
|
||||
39
Telegram/lib_base/base/platform/mac/base_url_scheme_mac.mm
Normal file
39
Telegram/lib_base/base/platform/mac/base_url_scheme_mac.mm
Normal file
@@ -0,0 +1,39 @@
|
||||
// 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/mac/base_url_scheme_mac.h"
|
||||
|
||||
#include <Carbon/Carbon.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
namespace base::Platform {
|
||||
|
||||
bool CheckUrlScheme(const UrlSchemeDescriptor &descriptor) {
|
||||
const auto name = descriptor.protocol.toStdString();
|
||||
const auto str = CFStringCreateWithCString(nullptr, name.c_str(), kCFStringEncodingASCII);
|
||||
const auto current = LSCopyDefaultHandlerForURLScheme(str);
|
||||
const auto result = CFStringCompare(
|
||||
current,
|
||||
(CFStringRef)[[NSBundle mainBundle] bundleIdentifier],
|
||||
kCFCompareCaseInsensitive);
|
||||
CFRelease(str);
|
||||
return (result == kCFCompareEqualTo);
|
||||
}
|
||||
|
||||
void RegisterUrlScheme(const UrlSchemeDescriptor &descriptor) {
|
||||
const auto name = descriptor.protocol.toStdString();
|
||||
const auto str = CFStringCreateWithCString(nullptr, name.c_str(), kCFStringEncodingASCII);
|
||||
LSSetDefaultHandlerForURLScheme(
|
||||
str,
|
||||
(CFStringRef)[[NSBundle mainBundle] bundleIdentifier]);
|
||||
CFRelease(str);
|
||||
}
|
||||
|
||||
void UnregisterUrlScheme(const UrlSchemeDescriptor &descriptor) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // namespace base::Platform
|
||||
58
Telegram/lib_base/base/platform/mac/base_utilities_mac.h
Normal file
58
Telegram/lib_base/base/platform/mac/base_utilities_mac.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// 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 <QtGui/QImage>
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
namespace Platform {
|
||||
|
||||
inline NSString *Q2NSString(const QString &str) {
|
||||
return [NSString stringWithUTF8String:str.toUtf8().constData()];
|
||||
}
|
||||
|
||||
inline NSString *Q2NSString(QStringView str) {
|
||||
return [NSString stringWithUTF8String:str.toUtf8().constData()];
|
||||
}
|
||||
|
||||
inline QString NS2QString(NSString *str) {
|
||||
return QString::fromUtf8([str cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
}
|
||||
|
||||
template <int Size>
|
||||
inline QString MakeFromLetters(const uint32 (&letters)[Size]) {
|
||||
QString result;
|
||||
result.reserve(Size);
|
||||
for (int32 i = 0; i < Size; ++i) {
|
||||
auto code = letters[i];
|
||||
auto salt1 = (code >> 8) & 0xFFU;
|
||||
auto salt2 = (code >> 24) & 0xFFU;
|
||||
auto part1 = ((code & 0xFFU) ^ (salt1 ^ salt2)) & 0xFFU;
|
||||
auto part2 = (((code >> 16) & 0xFFU) ^ (salt1 ^ ~salt2)) & 0xFFU;
|
||||
result.push_back(QChar((part2 << 8) | part1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline NSImage *Q2NSImage(const QImage &image) {
|
||||
if (image.isNull()) {
|
||||
return nil;
|
||||
}
|
||||
CGImageRef cgImage = image.toCGImage();
|
||||
if (!cgImage) {
|
||||
return nil;
|
||||
}
|
||||
auto nsImage = [[NSImage alloc] initWithSize:NSZeroSize];
|
||||
auto *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
|
||||
imageRep.size = (image.size() / image.devicePixelRatioF()).toCGSize();
|
||||
[nsImage addRepresentation:[imageRep autorelease]];
|
||||
CFRelease(cgImage);
|
||||
|
||||
return [nsImage autorelease];
|
||||
}
|
||||
|
||||
} // namespace 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
|
||||
//
|
||||
#include "base/platform/mac/base_utilities_mac.h"
|
||||
107
Telegram/lib_base/base/platform/win/base_battery_saving_win.cpp
Normal file
107
Telegram/lib_base/base/platform/win/base_battery_saving_win.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
// 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/win/base_battery_saving_win.h"
|
||||
|
||||
#include "base/battery_saving.h"
|
||||
#include "base/integration.h"
|
||||
|
||||
#include <QtCore/QAbstractNativeEventFilter>
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace base::Platform {
|
||||
namespace {
|
||||
|
||||
class BatterySaving final
|
||||
: public AbstractBatterySaving
|
||||
, public QAbstractNativeEventFilter {
|
||||
public:
|
||||
BatterySaving(Fn<void()> changedCallback);
|
||||
~BatterySaving();
|
||||
|
||||
std::optional<bool> enabled() const override;
|
||||
|
||||
private:
|
||||
bool nativeEventFilter(
|
||||
const QByteArray &eventType,
|
||||
void *message,
|
||||
native_event_filter_result *result) override;
|
||||
|
||||
QWidget _fake;
|
||||
HWND _hwnd = nullptr;
|
||||
HPOWERNOTIFY _notify = nullptr;
|
||||
Fn<void()> _changedCallback;
|
||||
|
||||
};
|
||||
|
||||
BatterySaving::BatterySaving(Fn<void()> changedCallback)
|
||||
: _changedCallback(std::move(changedCallback)) {
|
||||
if (!_changedCallback) {
|
||||
return;
|
||||
}
|
||||
_fake.hide();
|
||||
_fake.createWinId();
|
||||
const auto window = _fake.windowHandle();
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
_hwnd = reinterpret_cast<HWND>(window->winId());
|
||||
if (!_hwnd) {
|
||||
return;
|
||||
}
|
||||
_notify = RegisterPowerSettingNotification(
|
||||
_hwnd,
|
||||
&GUID_POWER_SAVING_STATUS,
|
||||
DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||
if (!_notify) {
|
||||
return;
|
||||
}
|
||||
qApp->installNativeEventFilter(this);
|
||||
}
|
||||
|
||||
BatterySaving::~BatterySaving() {
|
||||
if (_notify) {
|
||||
qApp->removeNativeEventFilter(this);
|
||||
UnregisterPowerSettingNotification(_notify);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<bool> BatterySaving::enabled() const {
|
||||
if (_changedCallback && !_notify) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto status = SYSTEM_POWER_STATUS();
|
||||
if (!GetSystemPowerStatus(&status) || (status.BatteryFlag & 128)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return (status.SystemStatusFlag == 1);
|
||||
}
|
||||
|
||||
bool BatterySaving::nativeEventFilter(
|
||||
const QByteArray &eventType,
|
||||
void *message,
|
||||
native_event_filter_result *result) {
|
||||
Expects(_hwnd != nullptr);
|
||||
|
||||
const auto msg = static_cast<MSG*>(message);
|
||||
if (msg->hwnd == _hwnd && msg->message == WM_POWERBROADCAST) {
|
||||
Integration::Instance().enterFromEventLoop(_changedCallback);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<AbstractBatterySaving> CreateBatterySaving(
|
||||
Fn<void()> changedCallback) {
|
||||
return std::make_unique<BatterySaving>(std::move(changedCallback));
|
||||
}
|
||||
|
||||
} // 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
|
||||
247
Telegram/lib_base/base/platform/win/base_file_utilities_win.cpp
Normal file
247
Telegram/lib_base/base/platform/win/base_file_utilities_win.cpp
Normal file
@@ -0,0 +1,247 @@
|
||||
// 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/win/base_file_utilities_win.h"
|
||||
|
||||
#include "base/platform/win/base_windows_safe_library.h"
|
||||
#include "base/platform/win/base_windows_shlobj_h.h"
|
||||
#include "base/algorithm.h"
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QDir>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <shellapi.h>
|
||||
#include <Shlwapi.h>
|
||||
#include <RestartManager.h>
|
||||
#include <io.h>
|
||||
|
||||
#define LOAD_SYMBOL(lib, name) ::base::Platform::LoadMethod(lib, #name, name)
|
||||
|
||||
namespace base::Platform {
|
||||
namespace {
|
||||
|
||||
// RSTRTMGR.DLL
|
||||
|
||||
DWORD(__stdcall *RmStartSession)(
|
||||
_Out_ DWORD *pSessionHandle,
|
||||
_Reserved_ DWORD dwSessionFlags,
|
||||
_Out_writes_(CCH_RM_SESSION_KEY + 1) WCHAR strSessionKey[]);
|
||||
DWORD(__stdcall *RmRegisterResources)(
|
||||
_In_ DWORD dwSessionHandle,
|
||||
_In_ UINT nFiles,
|
||||
_In_reads_opt_(nFiles) LPCWSTR rgsFileNames[],
|
||||
_In_ UINT nApplications,
|
||||
_In_reads_opt_(nApplications) RM_UNIQUE_PROCESS rgApplications[],
|
||||
_In_ UINT nServices,
|
||||
_In_reads_opt_(nServices) LPCWSTR rgsServiceNames[]);
|
||||
DWORD(__stdcall *RmGetList)(
|
||||
_In_ DWORD dwSessionHandle,
|
||||
_Out_ UINT *pnProcInfoNeeded,
|
||||
_Inout_ UINT *pnProcInfo,
|
||||
_Inout_updates_opt_(*pnProcInfo) RM_PROCESS_INFO rgAffectedApps[],
|
||||
_Out_ LPDWORD lpdwRebootReasons);
|
||||
DWORD(__stdcall *RmShutdown)(
|
||||
_In_ DWORD dwSessionHandle,
|
||||
_In_ ULONG lActionFlags,
|
||||
_In_opt_ RM_WRITE_STATUS_CALLBACK fnStatus);
|
||||
DWORD(__stdcall *RmEndSession)(
|
||||
_In_ DWORD dwSessionHandle);
|
||||
|
||||
} // namespace
|
||||
|
||||
void ShowInFolder(const QString &filepath) {
|
||||
auto nativePath = QDir::toNativeSeparators(filepath);
|
||||
const auto path = nativePath.toStdWString();
|
||||
if (const auto pidl = ILCreateFromPathW(path.c_str())) {
|
||||
SHOpenFolderAndSelectItems(pidl, 0, nullptr, 0);
|
||||
ILFree(pidl);
|
||||
return;
|
||||
}
|
||||
const auto pathEscaped = nativePath.replace('"', QString("\"\""));
|
||||
const auto command = ("/select," + pathEscaped).toStdWString();
|
||||
ShellExecute(
|
||||
0,
|
||||
0,
|
||||
L"explorer",
|
||||
command.c_str(),
|
||||
0,
|
||||
SW_SHOWNORMAL);
|
||||
}
|
||||
|
||||
QString FileNameFromUserString(QString name) {
|
||||
const auto kBadExtensions = { qstr(".lnk"), qstr(".scf") };
|
||||
const auto kMaskExtension = qstr(".download");
|
||||
for (const auto extension : kBadExtensions) {
|
||||
if (name.endsWith(extension, Qt::CaseInsensitive)) {
|
||||
name += kMaskExtension;
|
||||
}
|
||||
}
|
||||
|
||||
static const auto BadNames = {
|
||||
qstr("CON"),
|
||||
qstr("PRN"),
|
||||
qstr("AUX"),
|
||||
qstr("NUL"),
|
||||
qstr("COM1"),
|
||||
qstr("COM2"),
|
||||
qstr("COM3"),
|
||||
qstr("COM4"),
|
||||
qstr("COM5"),
|
||||
qstr("COM6"),
|
||||
qstr("COM7"),
|
||||
qstr("COM8"),
|
||||
qstr("COM9"),
|
||||
qstr("LPT1"),
|
||||
qstr("LPT2"),
|
||||
qstr("LPT3"),
|
||||
qstr("LPT4"),
|
||||
qstr("LPT5"),
|
||||
qstr("LPT6"),
|
||||
qstr("LPT7"),
|
||||
qstr("LPT8"),
|
||||
qstr("LPT9")
|
||||
};
|
||||
for (const auto bad : BadNames) {
|
||||
if (name.startsWith(bad, Qt::CaseInsensitive)) {
|
||||
if (name.size() == bad.size() || name[bad.size()] == '.') {
|
||||
name = '_' + name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
bool DeleteDirectory(QString path) {
|
||||
if (path.endsWith('/')) {
|
||||
path.chop(1);
|
||||
}
|
||||
const auto wide = QDir::toNativeSeparators(path).toStdWString()
|
||||
+ wchar_t(0)
|
||||
+ wchar_t(0);
|
||||
SHFILEOPSTRUCT file_op = {
|
||||
NULL,
|
||||
FO_DELETE,
|
||||
wide.data(),
|
||||
L"",
|
||||
FOF_NOCONFIRMATION |
|
||||
FOF_NOERRORUI |
|
||||
FOF_SILENT,
|
||||
false,
|
||||
0,
|
||||
L""
|
||||
};
|
||||
return (SHFileOperation(&file_op) == 0);
|
||||
}
|
||||
|
||||
void RemoveQuarantine(const QString &path) {
|
||||
}
|
||||
|
||||
QString BundledResourcesPath() {
|
||||
Unexpected("BundledResourcesPath not implemented.");
|
||||
}
|
||||
|
||||
QString CurrentExecutablePath(int argc, char *argv[]) {
|
||||
auto result = std::array<WCHAR, MAX_PATH + 1>{ 0 };
|
||||
const auto count = GetModuleFileName(
|
||||
nullptr,
|
||||
result.data(),
|
||||
MAX_PATH + 1);
|
||||
if (count < MAX_PATH + 1) {
|
||||
const auto info = QFileInfo(QDir::fromNativeSeparators(
|
||||
QString::fromWCharArray(result.data(), count)));
|
||||
return info.absoluteFilePath();
|
||||
}
|
||||
|
||||
// Fallback to the first command line argument.
|
||||
auto argsCount = 0;
|
||||
if (const auto args = CommandLineToArgvW(GetCommandLine(), &argsCount)) {
|
||||
auto info = QFileInfo(QDir::fromNativeSeparators(
|
||||
QString::fromWCharArray(args[0])));
|
||||
LocalFree(args);
|
||||
return info.absoluteFilePath();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool CloseProcesses(const QString &filename) {
|
||||
static const auto loaded = [&] {
|
||||
const auto LibRstrtMgr = SafeLoadLibrary(L"rstrtmgr.dll");
|
||||
return LOAD_SYMBOL(LibRstrtMgr, RmStartSession)
|
||||
&& LOAD_SYMBOL(LibRstrtMgr, RmRegisterResources)
|
||||
&& LOAD_SYMBOL(LibRstrtMgr, RmGetList)
|
||||
&& LOAD_SYMBOL(LibRstrtMgr, RmShutdown)
|
||||
&& LOAD_SYMBOL(LibRstrtMgr, RmEndSession);
|
||||
}();
|
||||
if (!loaded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto session = DWORD();
|
||||
auto sessionKey = std::wstring(CCH_RM_SESSION_KEY + 1, wchar_t(0));
|
||||
auto error = RmStartSession(&session, 0, sessionKey.data());
|
||||
if (error != ERROR_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
const auto guard = gsl::finally([&] { RmEndSession(session); });
|
||||
|
||||
const auto path = QDir::toNativeSeparators(filename).toStdWString();
|
||||
auto nullterm = path.c_str();
|
||||
error = RmRegisterResources(
|
||||
session,
|
||||
1,
|
||||
&nullterm,
|
||||
0,
|
||||
nullptr,
|
||||
0,
|
||||
nullptr);
|
||||
if (error != ERROR_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto processInfoNeeded = UINT(0);
|
||||
auto processInfoCount = UINT(0);
|
||||
auto reason = DWORD();
|
||||
|
||||
error = RmGetList(
|
||||
session,
|
||||
&processInfoNeeded,
|
||||
&processInfoCount,
|
||||
nullptr,
|
||||
&reason);
|
||||
if (error != ERROR_SUCCESS && error != ERROR_MORE_DATA) {
|
||||
return false;
|
||||
} else if (processInfoNeeded <= 0) {
|
||||
return true;
|
||||
}
|
||||
error = RmShutdown(session, RmForceShutdown, NULL);
|
||||
if (error != ERROR_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenameWithOverwrite(const QString &from, const QString &to) {
|
||||
const auto fromPath = QDir::toNativeSeparators(from).toStdWString();
|
||||
const auto toPath = QDir::toNativeSeparators(to).toStdWString();
|
||||
return MoveFileEx(
|
||||
fromPath.c_str(),
|
||||
toPath.c_str(),
|
||||
MOVEFILE_REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
void FlushFileData(QFile &file) {
|
||||
file.flush();
|
||||
if (const auto descriptor = file.handle()) {
|
||||
if (const auto handle = HANDLE(_get_osfhandle(descriptor))) {
|
||||
FlushFileBuffers(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace base::Platform
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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 {
|
||||
|
||||
bool CloseProcesses(const QString &filename);
|
||||
|
||||
} // namespace base::Platform
|
||||
@@ -0,0 +1,224 @@
|
||||
// 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/win/base_global_shortcuts_win.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace base::Platform::GlobalShortcuts {
|
||||
namespace {
|
||||
|
||||
constexpr auto kShiftMouseButton = std::numeric_limits<uint64>::max() - 100;
|
||||
|
||||
HHOOK GlobalHookKeyboard = nullptr;
|
||||
HHOOK GlobalHookMouse = nullptr;
|
||||
HANDLE ThreadHandle = nullptr;
|
||||
HANDLE ThreadEvent = nullptr;
|
||||
DWORD ThreadId = 0;
|
||||
Fn<void(GlobalShortcutKeyGeneric descriptor, bool down)> ProcessCallback;
|
||||
|
||||
[[nodiscard]] GlobalShortcutKeyGeneric MakeDescriptor(
|
||||
uint32 virtualKeyCode,
|
||||
uint32 lParam) {
|
||||
return GlobalShortcutKeyGeneric(
|
||||
(uint64(virtualKeyCode) << 32) | uint64(lParam));
|
||||
}
|
||||
|
||||
[[nodiscard]] GlobalShortcutKeyGeneric MakeMouseDescriptor(uint8 button) {
|
||||
Expects(button > 0 && button < 100);
|
||||
|
||||
return kShiftMouseButton + button;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32 GetVirtualKeyCode(GlobalShortcutKeyGeneric descriptor) {
|
||||
return uint32(uint64(descriptor) >> 32);
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32 GetLParam(GlobalShortcutKeyGeneric descriptor) {
|
||||
return uint32(uint64(descriptor) & 0xFFFFFFFFULL);
|
||||
}
|
||||
|
||||
void ProcessHookedKeyboardEvent(WPARAM wParam, LPARAM lParam) {
|
||||
const auto press = (PKBDLLHOOKSTRUCT)lParam;
|
||||
const auto repeatCount = uint32(0);
|
||||
const auto extendedBit = ((press->flags & LLKHF_EXTENDED) != 0);
|
||||
//const auto contextBit = ((press->flags & LLKHF_ALTDOWN) != 0);
|
||||
//const auto transitionState = ((press->flags & LLKHF_UP) != 0);
|
||||
const auto lParamForEvent = (repeatCount & 0x0000FFFFU)
|
||||
| ((uint32(press->scanCode) & 0xFFU) << 16)
|
||||
| (extendedBit ? (uint32(KF_EXTENDED) << 16) : 0);
|
||||
//| (contextBit ? (uint32(KF_ALTDOWN) << 16) : 0); // Alt pressed.
|
||||
//| (transitionState ? (uint32(KF_UP) << 16) : 0); // Is pressed.
|
||||
const auto descriptor = MakeDescriptor(press->vkCode, lParamForEvent);
|
||||
const auto down = (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN);
|
||||
|
||||
ProcessCallback(descriptor, down);
|
||||
}
|
||||
|
||||
void ProcessHookedMouseEvent(WPARAM wParam, LPARAM lParam) {
|
||||
if (wParam != WM_RBUTTONDOWN
|
||||
&& wParam != WM_RBUTTONUP
|
||||
&& wParam != WM_MBUTTONDOWN
|
||||
&& wParam != WM_MBUTTONUP
|
||||
&& wParam != WM_XBUTTONDOWN
|
||||
&& wParam != WM_XBUTTONUP) {
|
||||
return;
|
||||
}
|
||||
const auto button = [&] {
|
||||
if (wParam == WM_RBUTTONDOWN || wParam == WM_RBUTTONUP) {
|
||||
return 2;
|
||||
} else if (wParam == WM_MBUTTONDOWN || wParam == WM_MBUTTONUP) {
|
||||
return 3;
|
||||
}
|
||||
const auto press = (PMSLLHOOKSTRUCT)lParam;
|
||||
const auto xbutton = ((press->mouseData >> 16) & 0xFFU);
|
||||
return (xbutton >= 0x01 && xbutton <= 0x18) ? int(xbutton + 3) : 0;
|
||||
}();
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
const auto descriptor = MakeMouseDescriptor(button);
|
||||
const auto down = (wParam == WM_RBUTTONDOWN)
|
||||
|| (wParam == WM_MBUTTONDOWN)
|
||||
|| (wParam == WM_XBUTTONDOWN);
|
||||
|
||||
ProcessCallback(descriptor, down);
|
||||
}
|
||||
|
||||
LRESULT CALLBACK LowLevelKeyboardProc(
|
||||
_In_ int nCode,
|
||||
_In_ WPARAM wParam,
|
||||
_In_ LPARAM lParam) {
|
||||
if (nCode == HC_ACTION) {
|
||||
ProcessHookedKeyboardEvent(wParam, lParam);
|
||||
}
|
||||
return CallNextHookEx(GlobalHookKeyboard, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
LRESULT CALLBACK LowLevelMouseProc(
|
||||
_In_ int nCode,
|
||||
_In_ WPARAM wParam,
|
||||
_In_ LPARAM lParam) {
|
||||
if (nCode == HC_ACTION) {
|
||||
ProcessHookedMouseEvent(wParam, lParam);
|
||||
}
|
||||
return CallNextHookEx(GlobalHookMouse, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
DWORD WINAPI RunThread(LPVOID) {
|
||||
auto message = MSG();
|
||||
|
||||
// Force message loop creation.
|
||||
PeekMessage(&message, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
|
||||
SetEvent(ThreadEvent);
|
||||
|
||||
const auto guard = gsl::finally([&] {
|
||||
if (GlobalHookKeyboard) {
|
||||
UnhookWindowsHookEx(GlobalHookKeyboard);
|
||||
GlobalHookKeyboard = nullptr;
|
||||
}
|
||||
if (GlobalHookMouse) {
|
||||
UnhookWindowsHookEx(GlobalHookMouse);
|
||||
GlobalHookMouse = nullptr;
|
||||
}
|
||||
});
|
||||
GlobalHookKeyboard = SetWindowsHookEx(
|
||||
WH_KEYBOARD_LL,
|
||||
LowLevelKeyboardProc,
|
||||
nullptr,
|
||||
0);
|
||||
GlobalHookMouse = SetWindowsHookEx(
|
||||
WH_MOUSE_LL,
|
||||
LowLevelMouseProc,
|
||||
nullptr,
|
||||
0);
|
||||
if (!GlobalHookKeyboard || !GlobalHookMouse) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (GetMessage(&message, nullptr, 0, 0)) {
|
||||
if (message.message == WM_QUIT) {
|
||||
break;
|
||||
}
|
||||
TranslateMessage(&message);
|
||||
DispatchMessage(&message);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Available() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Allowed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Start(Fn<void(GlobalShortcutKeyGeneric descriptor, bool down)> process) {
|
||||
Expects(!ThreadHandle);
|
||||
Expects(!ThreadId);
|
||||
|
||||
ProcessCallback = std::move(process);
|
||||
ThreadEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
||||
if (!ThreadEvent) {
|
||||
ProcessCallback = nullptr;
|
||||
return;
|
||||
}
|
||||
ThreadHandle = CreateThread(
|
||||
nullptr,
|
||||
0,
|
||||
&RunThread,
|
||||
nullptr,
|
||||
0,
|
||||
&ThreadId);
|
||||
if (!ThreadHandle) {
|
||||
CloseHandle(ThreadEvent);
|
||||
ThreadEvent = nullptr;
|
||||
ThreadId = 0;
|
||||
ProcessCallback = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
if (!ThreadHandle) {
|
||||
return;
|
||||
}
|
||||
WaitForSingleObject(ThreadEvent, INFINITE);
|
||||
PostThreadMessage(ThreadId, WM_QUIT, 0, 0);
|
||||
WaitForSingleObject(ThreadHandle, INFINITE);
|
||||
CloseHandle(ThreadHandle);
|
||||
CloseHandle(ThreadEvent);
|
||||
ThreadHandle = ThreadEvent = nullptr;
|
||||
ThreadId = 0;
|
||||
ProcessCallback = nullptr;
|
||||
}
|
||||
|
||||
QString KeyName(GlobalShortcutKeyGeneric descriptor) {
|
||||
if (descriptor > kShiftMouseButton) {
|
||||
return QString("Mouse %1").arg(descriptor - kShiftMouseButton);
|
||||
}
|
||||
|
||||
constexpr auto kLimit = 1024;
|
||||
|
||||
WCHAR buffer[kLimit + 1] = { 0 };
|
||||
|
||||
// Remove 25 bit, we want to differentiate between left and right Ctrl-s.
|
||||
auto lParam = LONG(GetLParam(descriptor) & ~(1U << 25));
|
||||
|
||||
return GetKeyNameText(lParam, buffer, kLimit)
|
||||
? QString::fromWCharArray(buffer)
|
||||
: (GetVirtualKeyCode(descriptor) == VK_RSHIFT)
|
||||
? QString("Right Shift")
|
||||
: QString("\\x%1").arg(GetVirtualKeyCode(descriptor), 0, 16);
|
||||
}
|
||||
|
||||
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:z
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/platform/base_platform_global_shortcuts.h"
|
||||
18
Telegram/lib_base/base/platform/win/base_haptic_win.cpp
Normal file
18
Telegram/lib_base/base/platform/win/base_haptic_win.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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/win/base_haptic_win.h"
|
||||
|
||||
namespace base::Platform {
|
||||
|
||||
void Haptic() {
|
||||
}
|
||||
|
||||
bool IsSwipeBackEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace base::Platform
|
||||
9
Telegram/lib_base/base/platform/win/base_haptic_win.h
Normal file
9
Telegram/lib_base/base/platform/win/base_haptic_win.h
Normal file
@@ -0,0 +1,9 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/platform/base_platform_haptic.h"
|
||||
371
Telegram/lib_base/base/platform/win/base_info_win.cpp
Normal file
371
Telegram/lib_base/base/platform/win/base_info_win.cpp
Normal file
@@ -0,0 +1,371 @@
|
||||
// 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/win/base_info_win.h"
|
||||
|
||||
#include "base/algorithm.h"
|
||||
#include "base/platform/win/base_windows_safe_library.h"
|
||||
|
||||
#include <QtCore/QOperatingSystemVersion>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QDate>
|
||||
#include <QtCore/QSettings>
|
||||
|
||||
#include <windows.h>
|
||||
#include <VersionHelpers.h>
|
||||
|
||||
namespace Platform {
|
||||
namespace {
|
||||
|
||||
decltype(&::IsWow64Process2) IsWow64Process2;
|
||||
|
||||
bool IsWow64Process2Supported() {
|
||||
static const auto Result = [&] {
|
||||
#define LOAD_SYMBOL(lib, name) base::Platform::LoadMethod(lib, #name, name)
|
||||
const auto lib = base::Platform::SafeLoadLibrary(L"Kernel32.dll");
|
||||
return LOAD_SYMBOL(lib, IsWow64Process2);
|
||||
#undef LOAD_SYMBOL
|
||||
}();
|
||||
return Result;
|
||||
}
|
||||
|
||||
QString GetLangCodeById(unsigned int lngId) {
|
||||
const auto primary = (lngId & 0xFFU);
|
||||
switch (primary) {
|
||||
case 0x36: return u"af"_q;
|
||||
case 0x1C: return u"sq"_q;
|
||||
case 0x5E: return u"am"_q;
|
||||
case 0x01: return u"ar"_q;
|
||||
case 0x2B: return u"hy"_q;
|
||||
case 0x4D: return u"as"_q;
|
||||
case 0x2C: return u"az"_q;
|
||||
case 0x45: return u"bn"_q;
|
||||
case 0x6D: return u"ba"_q;
|
||||
case 0x2D: return u"eu"_q;
|
||||
case 0x23: return u"be"_q;
|
||||
case 0x1A:
|
||||
return (lngId == LANG_CROATIAN)
|
||||
? u"hr"_q
|
||||
: (lngId == LANG_BOSNIAN_NEUTRAL || lngId == LANG_BOSNIAN)
|
||||
? u"bs"_q
|
||||
: u"sr"_q;
|
||||
case 0x7E: return u"br"_q;
|
||||
case 0x02: return u"bg"_q;
|
||||
case 0x92: return u"ku"_q;
|
||||
case 0x03: return u"ca"_q;
|
||||
case 0x04: return u"zh"_q;
|
||||
case 0x83: return u"co"_q;
|
||||
case 0x05: return u"cs"_q;
|
||||
case 0x06: return u"da"_q;
|
||||
case 0x65: return u"dv"_q;
|
||||
case 0x13: return u"nl"_q;
|
||||
case 0x09: return u"en"_q;
|
||||
case 0x25: return u"et"_q;
|
||||
case 0x38: return u"fo"_q;
|
||||
case 0x0B: return u"fi"_q;
|
||||
case 0x0c: return u"fr"_q;
|
||||
case 0x62: return u"fy"_q;
|
||||
case 0x56: return u"gl"_q;
|
||||
case 0x37: return u"ka"_q;
|
||||
case 0x07: return u"de"_q;
|
||||
case 0x08: return u"el"_q;
|
||||
case 0x6F: return u"kl"_q;
|
||||
case 0x47: return u"gu"_q;
|
||||
case 0x68: return u"ha"_q;
|
||||
case 0x0D: return u"he"_q;
|
||||
case 0x39: return u"hi"_q;
|
||||
case 0x0E: return u"hu"_q;
|
||||
case 0x0F: return u"is"_q;
|
||||
case 0x70: return u"ig"_q;
|
||||
case 0x21: return u"id"_q;
|
||||
case 0x5D: return u"iu"_q;
|
||||
case 0x3C: return u"ga"_q;
|
||||
case 0x34: return u"xh"_q;
|
||||
case 0x35: return u"zu"_q;
|
||||
case 0x10: return u"it"_q;
|
||||
case 0x11: return u"ja"_q;
|
||||
case 0x4B: return u"kn"_q;
|
||||
case 0x3F: return u"kk"_q;
|
||||
case 0x53: return u"kh"_q;
|
||||
case 0x87: return u"rw"_q;
|
||||
case 0x12: return u"ko"_q;
|
||||
case 0x40: return u"ky"_q;
|
||||
case 0x54: return u"lo"_q;
|
||||
case 0x26: return u"lv"_q;
|
||||
case 0x27: return u"lt"_q;
|
||||
case 0x6E: return u"lb"_q;
|
||||
case 0x2F: return u"mk"_q;
|
||||
case 0x3E: return u"ms"_q;
|
||||
case 0x4C: return u"ml"_q;
|
||||
case 0x3A: return u"mt"_q;
|
||||
case 0x81: return u"mi"_q;
|
||||
case 0x4E: return u"mr"_q;
|
||||
case 0x50: return u"mn"_q;
|
||||
case 0x61: return u"ne"_q;
|
||||
case 0x14: return u"no"_q;
|
||||
case 0x82: return u"oc"_q;
|
||||
case 0x48: return u"or"_q;
|
||||
case 0x63: return u"ps"_q;
|
||||
case 0x29: return u"fa"_q;
|
||||
case 0x15: return u"pl"_q;
|
||||
case 0x16: return u"pt"_q;
|
||||
case 0x67: return u"ff"_q;
|
||||
case 0x46: return u"pa"_q;
|
||||
case 0x18: return u"ro"_q;
|
||||
case 0x17: return u"rm"_q;
|
||||
case 0x19: return u"ru"_q;
|
||||
case 0x3B: return u"se"_q;
|
||||
case 0x4F: return u"sa"_q;
|
||||
case 0x32: return u"tn"_q;
|
||||
case 0x59: return u"sd"_q;
|
||||
case 0x5B: return u"si"_q;
|
||||
case 0x1B: return u"sk"_q;
|
||||
case 0x24: return u"sl"_q;
|
||||
case 0x0A: return u"es"_q;
|
||||
case 0x41: return u"sw"_q;
|
||||
case 0x1D: return u"sv"_q;
|
||||
case 0x28: return u"tg"_q;
|
||||
case 0x49: return u"ta"_q;
|
||||
case 0x44: return u"tt"_q;
|
||||
case 0x4A: return u"te"_q;
|
||||
case 0x1E: return u"th"_q;
|
||||
case 0x51: return u"bo"_q;
|
||||
case 0x73: return u"ti"_q;
|
||||
case 0x1F: return u"tr"_q;
|
||||
case 0x42: return u"tk"_q;
|
||||
case 0x22: return u"uk"_q;
|
||||
case 0x20: return u"ur"_q;
|
||||
case 0x80: return u"ug"_q;
|
||||
case 0x43: return u"uz"_q;
|
||||
case 0x2A: return u"vi"_q;
|
||||
case 0x52: return u"cy"_q;
|
||||
case 0x88: return u"wo"_q;
|
||||
case 0x78: return u"ii"_q;
|
||||
case 0x6A: return u"yo"_q;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QString DeviceModelPretty() {
|
||||
using namespace base::Platform;
|
||||
static const auto result = FinalizeDeviceModel([&] {
|
||||
const auto bios = QSettings(
|
||||
"HKEY_LOCAL_MACHINE\\HARDWARE\\DESCRIPTION\\System\\BIOS",
|
||||
QSettings::NativeFormat);
|
||||
const auto value = [&](const char *key) {
|
||||
return SimplifyDeviceModel(bios.value(key).toString());
|
||||
};
|
||||
|
||||
const auto systemProductName = value("SystemProductName");
|
||||
if (const auto model = ProductNameToDeviceModel(systemProductName)
|
||||
; !model.isEmpty()) {
|
||||
return model;
|
||||
}
|
||||
|
||||
const auto systemFamily = value("SystemFamily");
|
||||
const auto baseBoardProduct = value("BaseBoardProduct");
|
||||
const auto familyBoard = SimplifyDeviceModel(
|
||||
systemFamily + ' ' + baseBoardProduct);
|
||||
|
||||
if (IsDeviceModelOk(familyBoard)) {
|
||||
return familyBoard;
|
||||
} else if (IsDeviceModelOk(baseBoardProduct)) {
|
||||
return baseBoardProduct;
|
||||
} else if (IsDeviceModelOk(systemFamily)) {
|
||||
return systemFamily;
|
||||
}
|
||||
return QString();
|
||||
}());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString SystemVersionPretty() {
|
||||
static const auto result = [&] {
|
||||
QStringList resultList;
|
||||
|
||||
if (IsWindows11OrGreater()) {
|
||||
resultList << "Windows 11";
|
||||
} else if (IsWindows10OrGreater()) {
|
||||
resultList << "Windows 10";
|
||||
} else if (IsWindows8Point1OrGreater()) {
|
||||
resultList << "Windows 8.1";
|
||||
} else if (IsWindows8OrGreater()) {
|
||||
resultList << "Windows 8";
|
||||
} else if (IsWindows7OrGreater()) {
|
||||
resultList << "Windows 7";
|
||||
} else {
|
||||
resultList << QSysInfo::prettyProductName();
|
||||
}
|
||||
|
||||
USHORT processMachine, nativeMachine{};
|
||||
if (IsWow64Process2Supported()) {
|
||||
IsWow64Process2(
|
||||
GetCurrentProcess(),
|
||||
&processMachine,
|
||||
&nativeMachine);
|
||||
} else {
|
||||
BOOL isWow64{};
|
||||
IsWow64Process(GetCurrentProcess(), &isWow64);
|
||||
if (isWow64) {
|
||||
nativeMachine = IMAGE_FILE_MACHINE_AMD64;
|
||||
}
|
||||
}
|
||||
|
||||
switch (nativeMachine) {
|
||||
case IMAGE_FILE_MACHINE_AMD64: {
|
||||
resultList << "x64";
|
||||
break;
|
||||
}
|
||||
case IMAGE_FILE_MACHINE_ARM64: {
|
||||
resultList << "arm64";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return resultList.join(' ');
|
||||
}();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString SystemCountry() {
|
||||
auto key = HKEY();
|
||||
const auto result = RegOpenKeyEx(
|
||||
HKEY_CURRENT_USER,
|
||||
L"Control Panel\\International\\Geo",
|
||||
0,
|
||||
KEY_READ,
|
||||
&key);
|
||||
if (result == ERROR_SUCCESS) {
|
||||
constexpr auto kBufSize = 4;
|
||||
auto checkType = DWORD();
|
||||
auto checkSize = DWORD(kBufSize * 2);
|
||||
auto checkStr = std::array<WCHAR, kBufSize>{ 0 };
|
||||
const auto result = RegQueryValueEx(
|
||||
key,
|
||||
L"Name",
|
||||
0,
|
||||
&checkType,
|
||||
reinterpret_cast<BYTE*>(checkStr.data()),
|
||||
&checkSize);
|
||||
if (result == ERROR_SUCCESS && checkSize == 6) { // 2 wchars + null
|
||||
return QString::fromWCharArray(checkStr.data());
|
||||
}
|
||||
}
|
||||
|
||||
int chCount = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, 0, 0);
|
||||
if (chCount && chCount < 128) {
|
||||
WCHAR wstrCountry[128];
|
||||
int len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, wstrCountry, chCount);
|
||||
if (len) {
|
||||
return QString::fromStdWString(std::wstring(wstrCountry));
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString SystemLanguage() {
|
||||
constexpr auto kMaxLanguageLength = 128;
|
||||
|
||||
auto uiLanguageId = GetUserDefaultUILanguage();
|
||||
auto uiLanguageLength = GetLocaleInfo(uiLanguageId, LOCALE_SNAME, nullptr, 0);
|
||||
if (uiLanguageLength > 0 && uiLanguageLength < kMaxLanguageLength) {
|
||||
WCHAR uiLanguageWideString[kMaxLanguageLength] = { 0 };
|
||||
uiLanguageLength = GetLocaleInfo(uiLanguageId, LOCALE_SNAME, uiLanguageWideString, uiLanguageLength);
|
||||
if (uiLanguageLength <= 0) {
|
||||
return QString();
|
||||
}
|
||||
return QString::fromWCharArray(uiLanguageWideString);
|
||||
}
|
||||
auto uiLanguageCodeLength = GetLocaleInfo(uiLanguageId, LOCALE_ILANGUAGE, nullptr, 0);
|
||||
if (uiLanguageCodeLength > 0 && uiLanguageCodeLength < kMaxLanguageLength) {
|
||||
WCHAR uiLanguageCodeWideString[kMaxLanguageLength] = { 0 };
|
||||
uiLanguageCodeLength = GetLocaleInfo(uiLanguageId, LOCALE_ILANGUAGE, uiLanguageCodeWideString, uiLanguageCodeLength);
|
||||
if (uiLanguageCodeLength <= 0) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
auto languageCode = 0U;
|
||||
for (auto i = 0; i != uiLanguageCodeLength; ++i) {
|
||||
auto ch = uiLanguageCodeWideString[i];
|
||||
if (!ch) {
|
||||
break;
|
||||
}
|
||||
languageCode *= 0x10U;
|
||||
if (ch >= WCHAR('0') && ch <= WCHAR('9')) {
|
||||
languageCode += static_cast<unsigned>(int(ch) - int(WCHAR('0')));
|
||||
} else if (ch >= WCHAR('A') && ch <= WCHAR('F')) {
|
||||
languageCode += static_cast<unsigned>(0x0A + int(ch) - int(WCHAR('A')));
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
return GetLangCodeById(languageCode);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QDate WhenSystemBecomesOutdated() {
|
||||
return QDate();
|
||||
}
|
||||
|
||||
int AutoUpdateVersion() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
QString AutoUpdateKey() {
|
||||
if (IsWindowsARM64()) {
|
||||
return "winarm";
|
||||
} else if (IsWindows64Bit()) {
|
||||
return "win64";
|
||||
} else {
|
||||
return "win";
|
||||
}
|
||||
}
|
||||
|
||||
bool IsWindows7OrGreater() {
|
||||
static const auto result = ::IsWindows7OrGreater();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IsWindows8OrGreater() {
|
||||
static const auto result = ::IsWindows8OrGreater();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IsWindows8Point1OrGreater() {
|
||||
static const auto result = ::IsWindows8Point1OrGreater();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IsWindows10OrGreater() {
|
||||
static const auto result = ::IsWindows10OrGreater();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IsWindows11OrGreater() {
|
||||
static const auto result = [&] {
|
||||
if (!IsWindows10OrGreater()) {
|
||||
return false;
|
||||
}
|
||||
const auto version = QOperatingSystemVersion::current();
|
||||
return (version.majorVersion() > 10)
|
||||
|| (version.microVersion() >= 22000);
|
||||
}();
|
||||
return result;
|
||||
}
|
||||
|
||||
void Start(QJsonObject settings) {
|
||||
SetDllDirectory(L"");
|
||||
}
|
||||
|
||||
void Finish() {
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
69
Telegram/lib_base/base/platform/win/base_info_win.h
Normal file
69
Telegram/lib_base/base/platform/win/base_info_win.h
Normal file
@@ -0,0 +1,69 @@
|
||||
// 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 IsWindows() {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline constexpr bool IsWindows32Bit() {
|
||||
#ifdef Q_PROCESSOR_X86_32
|
||||
return true;
|
||||
#else // Q_PROCESSOR_X86_32
|
||||
return false;
|
||||
#endif // Q_PROCESSOR_X86_32
|
||||
}
|
||||
|
||||
inline constexpr bool IsWindows64Bit() {
|
||||
#ifdef Q_PROCESSOR_X86_64
|
||||
return true;
|
||||
#else // Q_PROCESSOR_X86_64
|
||||
return false;
|
||||
#endif // Q_PROCESSOR_X86_64
|
||||
}
|
||||
|
||||
inline constexpr bool IsWindowsARM64() {
|
||||
#ifdef Q_PROCESSOR_ARM_64
|
||||
return true;
|
||||
#else // Q_PROCESSOR_ARM_64
|
||||
return false;
|
||||
#endif // Q_PROCESSOR_ARM_64
|
||||
}
|
||||
|
||||
inline constexpr bool IsWindowsStoreBuild() {
|
||||
#ifdef OS_WIN_STORE
|
||||
return true;
|
||||
#else // OS_WIN_STORE
|
||||
return false;
|
||||
#endif // OS_WIN_STORE
|
||||
}
|
||||
|
||||
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; }
|
||||
inline constexpr bool IsLinux() { return false; }
|
||||
inline bool IsX11() { return false; }
|
||||
inline bool IsWayland() { return false; }
|
||||
inline bool IsXwayland() { return false; }
|
||||
inline QString GetLibcName() { return QString(); }
|
||||
inline QString GetLibcVersion() { return QString(); }
|
||||
inline QString GetWindowManager() { return QString(); }
|
||||
|
||||
} // namespace Platform
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user