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
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s

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

View File

@@ -0,0 +1,322 @@
# 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
add_library(lib_base STATIC)
add_library(desktop-app::lib_base ALIAS lib_base)
init_target(lib_base)
add_library(lib_base_crash_report_writer STATIC)
add_library(desktop-app::lib_base_crash_report_writer ALIAS lib_base_crash_report_writer)
init_target(lib_base_crash_report_writer)
get_filename_component(src_loc . REALPATH)
target_precompile_headers(lib_base PRIVATE $<$<COMPILE_LANGUAGE:CXX,OBJCXX>:${src_loc}/base/base_pch.h>)
nice_target_sources(lib_base ${src_loc}
PRIVATE
base/platform/linux/base_battery_saving_linux.cpp
base/platform/linux/base_battery_saving_linux.h
base/platform/linux/base_file_utilities_linux.cpp
base/platform/linux/base_file_utilities_linux.h
base/platform/linux/base_global_shortcuts_linux.cpp
base/platform/linux/base_global_shortcuts_linux.h
base/platform/linux/base_haptic_linux.cpp
base/platform/linux/base_haptic_linux.h
base/platform/linux/base_info_linux.cpp
base/platform/linux/base_info_linux.h
base/platform/linux/base_last_input_linux.cpp
base/platform/linux/base_last_input_linux.h
base/platform/linux/base_layout_switch_linux.cpp
base/platform/linux/base_layout_switch_linux.h
base/platform/linux/base_linux_allocation_tracer.cpp
base/platform/linux/base_linux_allocation_tracer.h
base/platform/linux/base_linux_app_launch_context.cpp
base/platform/linux/base_linux_app_launch_context.h
base/platform/linux/base_linux_dbus_utilities.cpp
base/platform/linux/base_linux_dbus_utilities.h
base/platform/linux/base_linux_library.cpp
base/platform/linux/base_linux_library.h
base/platform/linux/base_linux_xcb_utilities.cpp
base/platform/linux/base_linux_xcb_utilities.h
base/platform/linux/base_linux_xdg_activation_token.cpp
base/platform/linux/base_linux_xdg_activation_token.h
base/platform/linux/base_linux_xdp_utilities.cpp
base/platform/linux/base_linux_xdp_utilities.h
base/platform/linux/base_linux_xsettings.cpp
base/platform/linux/base_linux_xsettings.h
base/platform/linux/base_network_reachability_linux.cpp
base/platform/linux/base_power_save_blocker_linux.cpp
base/platform/linux/base_power_save_blocker_linux.h
base/platform/linux/base_process_linux.cpp
base/platform/linux/base_process_linux.h
base/platform/linux/base_system_media_controls_linux.cpp
base/platform/linux/base_system_unlock_linux.cpp
base/platform/linux/base_system_unlock_linux.h
base/platform/linux/base_url_scheme_linux.cpp
base/platform/linux/base_url_scheme_linux.h
base/platform/mac/base_battery_saving_mac.mm
base/platform/mac/base_battery_saving_mac.h
base/platform/mac/base_confirm_quit.h
base/platform/mac/base_confirm_quit.mm
base/platform/mac/base_custom_app_icon_mac.h
base/platform/mac/base_custom_app_icon_mac.mm
base/platform/mac/base_file_utilities_mac.h
base/platform/mac/base_file_utilities_mac.mm
base/platform/mac/base_global_shortcuts_mac.mm
base/platform/mac/base_global_shortcuts_mac.h
base/platform/mac/base_haptic_mac.h
base/platform/mac/base_haptic_mac.mm
base/platform/mac/base_info_mac.h
base/platform/mac/base_info_mac.mm
base/platform/mac/base_last_input_mac.h
base/platform/mac/base_last_input_mac.mm
base/platform/mac/base_layout_switch_mac.h
base/platform/mac/base_layout_switch_mac.mm
base/platform/mac/base_network_reachability_mac.mm
base/platform/mac/base_power_save_blocker_mac.h
base/platform/mac/base_power_save_blocker_mac.mm
base/platform/mac/base_process_mac.h
base/platform/mac/base_process_mac.mm
base/platform/mac/base_system_media_controls_mac.mm
base/platform/mac/base_system_unlock_mac.h
base/platform/mac/base_system_unlock_mac.mm
base/platform/mac/base_url_scheme_mac.h
base/platform/mac/base_url_scheme_mac.mm
base/platform/mac/base_utilities_mac.h
base/platform/mac/base_utilities_mac.mm
base/platform/win/base_battery_saving_win.cpp
base/platform/win/base_battery_saving_win.h
base/platform/win/base_file_utilities_win.cpp
base/platform/win/base_file_utilities_win.h
base/platform/win/base_global_shortcuts_win.cpp
base/platform/win/base_global_shortcuts_win.h
base/platform/win/base_haptic_win.cpp
base/platform/win/base_haptic_win.h
base/platform/win/base_info_win.cpp
base/platform/win/base_info_win.h
base/platform/win/base_last_input_win.cpp
base/platform/win/base_last_input_win.h
base/platform/win/base_layout_switch_win.cpp
base/platform/win/base_layout_switch_win.h
base/platform/win/base_network_reachability_win.cpp
base/platform/win/base_power_save_blocker_win.cpp
base/platform/win/base_power_save_blocker_win.h
base/platform/win/base_process_win.cpp
base/platform/win/base_process_win.h
base/platform/win/base_system_media_controls_win.cpp
base/platform/win/base_system_unlock_win.cpp
base/platform/win/base_system_unlock_win.h
base/platform/win/base_url_scheme_win.cpp
base/platform/win/base_url_scheme_win.h
base/platform/win/base_windows_co_task_mem.h
base/platform/win/base_windows_gdiplus_h.h
base/platform/win/base_windows_rpcndr_h.h
base/platform/win/base_windows_safe_library.cpp
base/platform/win/base_windows_safe_library.h
base/platform/win/base_windows_shlobj_h.h
base/platform/win/base_windows_winrt.cpp
base/platform/win/base_windows_winrt.h
base/platform/win/base_windows_wrl.cpp
base/platform/win/base_windows_wrl.h
base/platform/win/wrl/wrl_implements_h.h
base/platform/win/wrl/wrl_module_h.h
base/platform/base_platform_custom_app_icon.h
base/platform/base_platform_global_shortcuts.h
base/platform/base_platform_haptic.h
base/platform/base_platform_info.cpp
base/platform/base_platform_info.h
base/platform/base_platform_last_input.h
base/platform/base_platform_layout_switch.h
base/platform/base_platform_network_reachability.h
base/platform/base_platform_file_utilities.h
base/platform/base_platform_power_save_blocker.h
base/platform/base_platform_process.h
base/platform/base_platform_url_scheme.h
base/platform/base_platform_system_media_controls.h
base/algorithm.cpp
base/algorithm.h
base/assertion.cpp
base/assertion.h
base/atomic.h
base/base_file_utilities.cpp
base/base_file_utilities.h
base/basic_types.h
base/battery_saving.cpp
base/battery_saving.h
base/binary_guard.h
base/build_config.h
base/bytes.cpp
base/bytes.h
base/call_delayed.cpp
base/call_delayed.h
base/crc32hash.cpp
base/crc32hash.h
base/concurrent_timer.cpp
base/concurrent_timer.h
base/const_string.h
base/custom_app_icon.h
base/custom_delete.h
base/debug_destroy_informer.h
base/debug_log.cpp
base/debug_log.h
base/enum_mask.h
base/event_filter.cpp
base/event_filter.h
base/expected.h
base/file_lock.h
base/file_lock_win.cpp
base/file_lock_posix.cpp
base/flags.h
base/flat_map.h
base/flat_set.h
base/functors.h
base/global_shortcuts.h
base/global_shortcuts_generic.cpp
base/global_shortcuts_generic.h
base/index_based_iterator.h
base/integration.cpp
base/integration.h
base/invoke_queued.h
base/last_used_cache.h
base/last_user_input.cpp
base/last_user_input.h
base/match_method.h
base/network_reachability.cpp
base/network_reachability.h
base/never_freed_pointer.h
base/object_ptr.h
base/ordered_set.h
base/openssl_help.h
base/optional.h
base/options.cpp
base/options.h
base/overload.h
base/parse_helper.cpp
base/parse_helper.h
base/power_save_blocker.cpp
base/power_save_blocker.h
base/qthelp_regex.h
base/qthelp_url.cpp
base/qthelp_url.h
base/qt_connection.h
base/qt_signal_producer.h
base/random.cpp
base/random.h
base/required.h
base/runtime_composer.cpp
base/runtime_composer.h
base/screen_reader_state.cpp
base/screen_reader_state.h
base/single_instance.cpp
base/single_instance.h
base/system_unlock.h
base/thread_safe_wrap.h
base/timer.cpp
base/timer.h
base/timer_rpl.h
base/type_traits.h
base/unique_any.h
base/unique_function.h
base/unique_qptr.h
base/unixtime.cpp
base/unixtime.h
base/variant.h
base/virtual_method.h
base/weak_ptr.h
base/weak_qptr.h
base/zlib_help.h
base/qt/qt_common_adapters.h
base/qt/qt_compare.h
base/qt/qt_key_modifiers.h
base/qt/qt_string_view.h
base/qt/qt_tab_key.cpp
base/qt/qt_tab_key.h
base/base_pch.h
)
if (DESKTOP_APP_DISABLE_X11_INTEGRATION)
remove_target_sources(lib_base ${src_loc}
base/platform/linux/base_linux_xcb_utilities.cpp
base/platform/linux/base_linux_xcb_utilities.h
base/platform/linux/base_linux_xsettings.cpp
base/platform/linux/base_linux_xsettings.h
)
endif()
target_include_directories(lib_base
PUBLIC
${src_loc}
)
target_link_libraries(lib_base
PUBLIC
desktop-app::lib_rpl
desktop-app::lib_crl
desktop-app::external_qt
desktop-app::external_openssl
desktop-app::external_crash_reports
desktop-app::external_ranges
desktop-app::external_gsl
desktop-app::external_expected
PRIVATE
desktop-app::external_xxhash
)
if (LINUX)
target_link_libraries(lib_base
PUBLIC
desktop-app::external_kcoreaddons
)
include(${cmake_helpers_loc}/external/glib/generate_dbus.cmake)
generate_dbus(lib_base org.freedesktop. XdgDBus ${src_loc}/base/platform/linux/org.freedesktop.DBus.xml)
generate_dbus(lib_base org.freedesktop.portal. XdpInhibit ${third_party_loc}/xdg-desktop-portal/data/org.freedesktop.portal.Inhibit.xml)
generate_dbus(lib_base org.freedesktop.portal. XdpOpenURI ${third_party_loc}/xdg-desktop-portal/data/org.freedesktop.portal.OpenURI.xml)
generate_dbus(lib_base org.freedesktop.portal. XdpRequest ${third_party_loc}/xdg-desktop-portal/data/org.freedesktop.portal.Request.xml)
generate_dbus(lib_base org.freedesktop.portal. XdpSettings ${third_party_loc}/xdg-desktop-portal/data/org.freedesktop.portal.Settings.xml)
generate_dbus(lib_base org.freedesktop. XdgFileManager1 ${src_loc}/base/platform/linux/org.freedesktop.FileManager1.xml)
generate_dbus(lib_base org.gnome.Mutter. MutterIdleMonitor ${src_loc}/base/platform/linux/org.gnome.Mutter.IdleMonitor.xml)
generate_dbus(lib_base org.sigxcpu.Feedback. SigxcpuFeedback ${src_loc}/base/platform/linux/org.sigxcpu.Feedback.Haptic.xml)
generate_dbus(lib_base org.mpris. Mpris ${src_loc}/base/platform/linux/mpris.xml)
generate_dbus(lib_base io.snapcraft. Snapcraft ${src_loc}/base/platform/linux/io.snapcraft.Settings.xml)
if (NOT DESKTOP_APP_DISABLE_X11_INTEGRATION)
target_link_libraries(lib_base
PUBLIC
desktop-app::external_xcb_keysyms
desktop-app::external_xcb_record
desktop-app::external_xcb_screensaver
desktop-app::external_xcb
)
endif()
endif()
if (DESKTOP_APP_USE_ALLOCATION_TRACER)
target_compile_definitions(lib_base
PRIVATE
DESKTOP_APP_USE_ALLOCATION_TRACER
)
endif()
#target_precompile_headers(lib_base_crash_report_writer REUSE_FROM lib_base)
target_precompile_headers(lib_base_crash_report_writer PRIVATE ${src_loc}/base/base_pch.h)
nice_target_sources(lib_base_crash_report_writer ${src_loc}
PRIVATE
base/crash_report_header.cpp
base/crash_report_header.h
base/crash_report_writer.cpp
base/crash_report_writer.h
)
if (DESKTOP_APP_DISABLE_CRASH_REPORTS)
remove_target_sources(lib_base_crash_report_writer ${src_loc}
base/crash_report_writer.cpp
base/crash_report_writer.h
)
endif()
target_link_libraries(lib_base_crash_report_writer PUBLIC desktop-app::lib_base)

View File

@@ -0,0 +1,44 @@
// 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/algorithm.h"
#include "base/debug_log.h"
#include <cfenv>
namespace base {
[[nodiscard]] double SafeRound(double value) {
Expects(!std::isnan(value));
if (const auto result = std::round(value); !std::isnan(result)) {
return result;
}
const auto errors = std::fetestexcept(FE_ALL_EXCEPT);
if (const auto result = std::round(value); !std::isnan(result)) {
return result;
}
LOG(("Streaming Error: Got second NAN in std::round(%1), fe: %2."
).arg(value
).arg(errors));
std::feclearexcept(FE_ALL_EXCEPT);
if (const auto result = std::round(value); !std::isnan(result)) {
return result;
}
Unexpected("NAN after third std::round.");
}
QString CleanAndSimplify(QString text) {
for (auto &ch : text) {
if (ch.unicode() < 32) {
ch = QChar(' ');
}
}
return text.simplified();
}
} // namespace base

View File

@@ -0,0 +1,153 @@
// 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 <QtCore/QLatin1String>
#include <QtCore/QString>
#include <memory>
namespace base {
template <typename Type>
inline constexpr Type take(Type &value) noexcept {
return std::exchange(value, Type {});
}
template <typename Type>
inline constexpr Type duplicate(const Type &value) {
return value;
}
template <typename Type, size_t Size>
inline constexpr size_t array_size(const Type(&)[Size]) noexcept {
return Size;
}
template <typename Container, typename T>
inline bool contains(const Container &container, const T &value) {
const auto end = std::end(container);
return std::find(std::begin(container), end, value) != end;
}
template <typename Container>
inline void reorder(Container &container, int oldPosition, int newPosition) {
const auto b = container.begin();
if (oldPosition < newPosition) {
std::rotate(
b + oldPosition,
b + oldPosition + 1,
b + newPosition + 1);
} else if (newPosition < oldPosition) {
std::rotate(
b + newPosition,
b + oldPosition,
b + oldPosition + 1);
}
}
[[nodiscard]] inline int reorder_index(
int index,
int oldPosition,
int newPosition) {
return (index == oldPosition)
? newPosition
: (oldPosition < index && index <= newPosition)
? (index - 1)
: (oldPosition > index && index >= newPosition)
? (index + 1)
: index;
}
template <typename D, typename T>
inline constexpr D up_cast(T object) {
using DV = std::decay_t<decltype(*D())>;
using TV = std::decay_t<decltype(*T())>;
if constexpr (std::is_base_of_v<DV, TV>) {
return object;
} else {
return nullptr;
}
}
// We need a custom comparator for set<std::unique_ptr<T>>::find to work with pointers.
// thanks to http://stackoverflow.com/questions/18939882/raw-pointer-lookup-for-sets-of-unique-ptrs
template <typename T>
struct pointer_comparator {
using is_transparent = std::true_type;
// helper does some magic in order to reduce the number of
// pairs of types we need to know how to compare: it turns
// everything into a pointer, and then uses `std::less<T*>`
// to do the comparison:
struct helper {
const T *ptr = nullptr;
helper() = default;
helper(const helper &other) = default;
helper(const T *p) : ptr(p) {
}
template <typename ...Ts>
helper(const std::shared_ptr<Ts...> &other) : ptr(other.get()) {
}
template <typename ...Ts>
helper(const std::unique_ptr<Ts...> &other) : ptr(other.get()) {
}
bool operator<(helper other) const {
return std::less<const T*>()(ptr, other.ptr);
}
};
// without helper, we'd need 2^n different overloads, where
// n is the number of types we want to support (so, 8 with
// raw pointers, unique pointers, and shared pointers). That
// seems silly.
// && helps enforce rvalue use only
bool operator()(const helper &&lhs, const helper &&rhs) const {
return lhs < rhs;
}
};
inline QString FromUtf8Safe(const char *string, int size = -1) {
if (!string || !size) {
return QString();
} else if (size < 0) {
size = strlen(string);
}
const auto result = QString::fromUtf8(string, size);
const auto back = result.toUtf8();
return (back.size() != size || memcmp(back.constData(), string, size))
? QString::fromLocal8Bit(string, size)
: result;
}
inline QString FromUtf8Safe(const QByteArray &string) {
return FromUtf8Safe(string.constData(), string.size());
}
[[nodiscard]] double SafeRound(double value);
[[nodiscard]] QString CleanAndSimplify(QString text);
template <typename T>
[[nodiscard]] bool OppositeSigns(T a, T b) {
// For zero returns false!
return (a < 0 && b > 0) || (a > 0 && b < 0);
}
} // namespace base
template <typename T>
inline void accumulate_max(T &a, const T &b) { if (a < b) a = b; }
template <typename T>
inline void accumulate_min(T &a, const T &b) { if (a > b) a = b; }
template <size_t Size>
QLatin1String qstr(const char(&string)[Size]) {
return QLatin1String(string, int(Size) - 1);
}

View File

@@ -0,0 +1,49 @@
// 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 <catch.hpp>
#include "base/index_based_iterator.h"
TEST_CASE("index_based_iterator tests", "[base::algorithm]") {
auto v = std::vector<int>();
v.insert(v.end(), { 1, 2, 3, 4, 5, 4, 3, 2, 1 });
auto push_back_safe_remove_if = [](auto &v, auto predicate) {
auto begin = base::index_based_begin(v);
auto end = base::index_based_end(v);
auto from = std::remove_if(begin, end, predicate);
if (from != end) {
auto newEnd = base::index_based_end(v);
if (newEnd != end) {
REQUIRE(newEnd > end);
while (end != newEnd) {
*from++ = *end++;
}
}
v.erase(from.base(), newEnd.base());
}
};
SECTION("allows to push_back from predicate") {
push_back_safe_remove_if(v, [&v](int value) {
v.push_back(value);
return (value % 2) == 1;
});
auto expected = std::vector<int> { 2, 4, 4, 2, 1, 2, 3, 4, 5, 4, 3, 2, 1 };
REQUIRE(v == expected);
}
SECTION("allows to push_back while removing all") {
push_back_safe_remove_if(v, [&v](int value) {
if (value == 5) {
v.push_back(value);
}
return true;
});
auto expected = std::vector<int> { 5 };
REQUIRE(v == expected);
}
}

View File

@@ -0,0 +1,24 @@
// 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/assertion.h"
#include "base/integration.h"
namespace base::assertion {
void log(const char *message, const char *file, int line) {
if (Integration::Exists()) {
const auto info = message
+ QString(' ')
+ file
+ ':'
+ QString::number(line);
Integration::Instance().logAssertionViolation(info);
}
}
} // namespace base::assertion

View File

@@ -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
//
#pragma once
#include <cstdlib>
// Ensures/Expects.
#include <gsl/assert>
namespace base {
namespace assertion {
void log(const char *message, const char *file, int line);
// Release build assertions.
inline constexpr void noop() {
}
[[noreturn]] inline void fail(
const char *message,
const char *file,
int line) {
log(message, file, line);
// Crash with access violation and generate crash report.
volatile auto nullptr_value = (int*)nullptr;
*nullptr_value = 0;
// Silent the possible failure to comply noreturn warning.
std::abort();
}
constexpr const char* extract_basename(const char* path, size_t size) {
while (size != 0 && path[size - 1] != '/' && path[size - 1] != '\\') {
--size;
}
return path + size;
}
} // namespace assertion
} // namespace base
#if defined(__clang__) || defined(__GNUC__)
#define AssertUnlikelyHelper(x) __builtin_expect(!!(x), 0)
#else
#define AssertUnlikelyHelper(x) (!!(x))
#endif
#define AssertValidationCondition(condition, message, file, line)\
((AssertUnlikelyHelper(!(condition)))\
? ::base::assertion::fail(message, file, line)\
: ::base::assertion::noop())
#define SOURCE_FILE_BASENAME (::base::assertion::extract_basename(\
__FILE__,\
sizeof(__FILE__)))
#define AssertCustom(condition, message) (AssertValidationCondition(\
condition,\
message,\
SOURCE_FILE_BASENAME,\
__LINE__))
#define Assert(condition) AssertCustom(condition, "\"" #condition "\"")
// Define our own versions of Expects() and Ensures().
// Let them crash with reports and logging.
#ifdef Expects
#undef Expects
#endif // Expects
#define Expects(condition) (AssertValidationCondition(\
condition,\
"\"" #condition "\"",\
SOURCE_FILE_BASENAME,\
__LINE__))
#ifdef Ensures
#undef Ensures
#endif // Ensures
#define Ensures(condition) (AssertValidationCondition(\
condition,\
"\"" #condition "\"",\
SOURCE_FILE_BASENAME,\
__LINE__))
#ifdef Unexpected
#undef Unexpected
#endif // Unexpected
#define Unexpected(message) (::base::assertion::fail(\
"Unexpected: " message,\
SOURCE_FILE_BASENAME,\
__LINE__))
#ifdef _DEBUG
#define AssertIsDebug(...)
#endif // _DEBUG

View File

@@ -0,0 +1,65 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <mutex>
#include <memory>
#include <condition_variable>
namespace base {
// Helper for macOS < 11.0 and GCC < 11 that don't have wait/notify.
#if defined __cpp_lib_atomic_wait && !defined Q_OS_MAC
using std::atomic;
using std::atomic_notify_all;
#else // __cpp_lib_atomic_wait
template <typename Type>
class atomic final {
public:
atomic(Type value) : _value(value) {
}
atomic &operator=(Type value) {
_value = value;
return *this;
}
[[nodiscard]] Type load() const {
return _value.load();
}
void wait(Type whileEquals) {
if (_value == whileEquals) {
std::unique_lock<std::mutex> lock(_mutex);
while (_value == whileEquals) {
_condition.wait(lock);
}
}
}
friend inline bool operator==(const atomic &a, Type b) {
return (a._value == b);
}
friend void atomic_notify_all(atomic *value) {
std::unique_lock<std::mutex> lock(value->_mutex);
value->_condition.notify_all();
}
private:
std::atomic<Type> _value;
std::mutex _mutex;
std::condition_variable _condition;
};
#endif // __cpp_lib_atomic_wait
} // namespace base

View File

@@ -0,0 +1,47 @@
// 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/base_file_utilities.h"
#include "base/platform/base_platform_file_utilities.h"
#include <QtCore/QResource>
namespace base {
QString FileNameFromUserString(QString name) {
// We don't want LTR/RTL mark/embedding/override/isolate chars
// in filenames, because they introduce a security issue, when
// an executable "Fil[x]gepj.exe" may look like "Filexe.jpeg".
const ushort kBad[] = {
0x200E, // LTR Mark
0x200F, // RTL Mark
0x202A, // LTR Embedding
0x202B, // RTL Embedding
0x202D, // LTR Override
0x202E, // RTL Override
0x2066, // LTR Isolate
0x2067, // RTL Isolate
'/', '\\', '<', '>', ':', '"', '|', '?', '*' };
for (auto &ch : name) {
if (ch.unicode() < 32 || ranges::find(kBad, ch.unicode()) != end(kBad)) {
ch = '_';
}
}
if (name.isEmpty() || name.endsWith(' ') || name.endsWith('.')) {
name.append('_');
}
return Platform::FileNameFromUserString(std::move(name));
}
void RegisterBundledResources(const QString &name) {
const auto location = Platform::BundledResourcesPath();
if (!QResource::registerResource(location + '/' + name)) {
Unexpected("Packed resources not found.");
}
}
} // namespace base

View File

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

View File

@@ -0,0 +1,30 @@
// 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 <QtCore/QByteArray>
#include <QtCore/QString>
#include <QtCore/QUrl>
#include <QtCore/QMutex>
#include <QtCore/QRegularExpression>
#include <QtCore/QThread>
#include <QtCore/QCoreApplication>
#include <crl/crl.h>
#include <rpl/rpl.h>
#include <vector>
#include <unordered_map>
#include <set>
#include <range/v3/all.hpp>
#include "base/flat_map.h"
#include "base/flat_set.h"
#include "base/optional.h"
#include "base/algorithm.h"
#include "base/basic_types.h"
#include "base/weak_qptr.h"

View File

@@ -0,0 +1,176 @@
// 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/build_config.h"
#include "base/ordered_set.h"
#include "base/unique_function.h"
#include "base/functors.h"
#include "base/required.h"
#include <QtGlobal>
#include <QtCore/QByteArray>
#include <QtCore/QString>
#include <string>
#include <exception>
#include <memory>
#include <ctime>
#include <functional>
#include <gsl/gsl>
namespace func = base::functors;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
using native_event_filter_result = qintptr;
#else // Qt >= 6.0.0
using native_event_filter_result = long;
#endif // Qt >= 6.0.0
using gsl::not_null;
using index_type = gsl::index;
using size_type = gsl::index;
using base::required;
template <typename Signature>
using Fn = std::function<Signature>;
template <typename Signature>
using FnMut = base::unique_function<Signature>;
//using uchar = unsigned char; // Qt has uchar
using int8 = qint8;
using uint8 = quint8;
using int16 = qint16;
using uint16 = quint16;
using int32 = qint32;
using uint32 = quint32;
using int64 = qint64;
using uint64 = quint64;
using float32 = float;
using float64 = double;
using TimeId = int32;
#ifndef _DEBUG
#if defined _MSC_VER && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#define DESKTOP_APP_USE_NO_ALLOCATION_LITERAL
#endif // _MSC_VER
#endif // !_DEBUG
#ifdef DESKTOP_APP_USE_NO_ALLOCATION_LITERAL
namespace base::details {
template <size_t N>
struct LiteralResolver {
template <size_t ...I>
constexpr LiteralResolver(
const char16_t (&text)[N],
std::index_sequence<I...>)
: utf16text{ text[I]... } {
}
template <size_t ...I>
constexpr LiteralResolver(
const char (&text)[N],
std::index_sequence<I...>)
: utf8text{ text[I]... }
, utf8(true) {
}
constexpr LiteralResolver(const char16_t (&text)[N])
: LiteralResolver(text, std::make_index_sequence<N>{}) {
}
constexpr LiteralResolver(const char (&text)[N])
: LiteralResolver(text, std::make_index_sequence<N>{}) {
}
constexpr auto operator<=>(const LiteralResolver &) const = default;
const char16_t utf16text[N]{};
const char utf8text[N]{};
size_t length = N;
bool utf8 = false;
};
template <size_t N>
struct StaticStringData {
template <std::size_t... I>
constexpr StaticStringData(
const char16_t (&text)[N],
std::index_sequence<I...>)
: data Q_STATIC_STRING_DATA_HEADER_INITIALIZER(N - 1)
, text{ text[I]... } {
}
QArrayData data;
char16_t text[N];
QStringData *pointer() {
Q_ASSERT(data.ref.isStatic());
return static_cast<QStringData*>(&data);
}
};
template <size_t N>
struct StaticByteArrayData {
template <std::size_t... I>
constexpr StaticByteArrayData(
const char (&text)[N],
std::index_sequence<I...>)
: data Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER(N - 1)
, text{ text[I]... } {
}
QByteArrayData data;
char text[N];
QByteArrayData *pointer() {
Q_ASSERT(data.ref.isStatic());
return &data;
}
};
template <LiteralResolver Resolve>
using q_literal_type = std::conditional_t<Resolve.utf8, QByteArray, QString>;
} // namespace base::details
template <base::details::LiteralResolver Resolve>
base::details::q_literal_type<Resolve> operator""_q() {
static_assert(Resolve.length > 0);
using namespace base::details;
if constexpr (Resolve.utf8) {
static auto Literal = StaticByteArrayData<Resolve.length>(
Resolve.utf8text,
std::make_index_sequence<Resolve.length>{});
return QByteArray{ QByteArrayDataPtr{ Literal.pointer() } };
} else {
static auto Literal = StaticStringData<Resolve.length>(
Resolve.utf16text,
std::make_index_sequence<Resolve.length>{});
return QString{ QStringDataPtr{ Literal.pointer() } };
}
};
#else // DESKTOP_APP_USE_NO_ALLOCATION_LITERAL
[[nodiscard]] inline QByteArray operator""_q(
const char *data,
std::size_t size) {
return QByteArray::fromRawData(data, size);
}
[[nodiscard]] inline QString operator""_q(
const char16_t *data,
std::size_t size) {
return QString::fromRawData(
reinterpret_cast<const QChar*>(data),
size);
}
#endif // DESKTOP_APP_USE_NO_ALLOCATION_LITERAL

View File

@@ -0,0 +1,30 @@
// 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/battery_saving.h"
namespace base {
BatterySaving::BatterySaving()
: _helper(Platform::CreateBatterySaving([=] {
_value = _helper->enabled();
}))
, _value(_helper->enabled()) {
}
BatterySaving::~BatterySaving() = default;
std::optional<bool> BatterySaving::enabled() const {
return _value.current();
}
rpl::producer<bool> BatterySaving::value() const {
return _value.value() | rpl::map([=](std::optional<bool> maybe) {
return maybe.value_or(false);
});
}
} // namespace base

View File

@@ -0,0 +1,43 @@
// 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 AbstractBatterySaving {
public:
virtual ~AbstractBatterySaving() = default;
virtual std::optional<bool> enabled() const = 0;
};
[[nodiscard]] std::unique_ptr<AbstractBatterySaving> CreateBatterySaving(
Fn<void()> changeCallback);
} // namespace base::Platform
namespace base {
class BatterySaving final {
public:
BatterySaving();
~BatterySaving();
[[nodiscard]] bool supported() const {
return enabled().has_value();
}
[[nodiscard]] std::optional<bool> enabled() const;
[[nodiscard]] rpl::producer<bool> value() const;
private:
const std::unique_ptr<Platform::AbstractBatterySaving> _helper;
rpl::variable<std::optional<bool>> _value;
};
} // namespace base

View File

@@ -0,0 +1,100 @@
// 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/algorithm.h"
#include <atomic>
namespace base {
class binary_guard {
public:
binary_guard() = default;
binary_guard(binary_guard &&other);
binary_guard &operator=(binary_guard &&other);
~binary_guard();
binary_guard &operator=(std::nullptr_t);
bool alive() const;
binary_guard make_guard();
explicit operator bool() const;
private:
void destroy();
std::atomic<bool> *_bothAlive = nullptr;
};
inline binary_guard::binary_guard(binary_guard &&other)
: _bothAlive(base::take(other._bothAlive)) {
}
inline binary_guard &binary_guard::operator=(binary_guard &&other) {
if (this != &other) {
destroy();
_bothAlive = base::take(other._bothAlive);
}
return *this;
}
inline binary_guard::~binary_guard() {
destroy();
}
inline binary_guard &binary_guard::operator=(std::nullptr_t) {
destroy();
return *this;
}
inline binary_guard::operator bool() const {
return alive();
}
inline bool binary_guard::alive() const {
return _bothAlive && _bothAlive->load();
}
inline void binary_guard::destroy() {
if (const auto both = base::take(_bothAlive)) {
auto old = true;
if (!both->compare_exchange_strong(old, false)) {
delete both;
}
}
}
inline binary_guard binary_guard::make_guard() {
destroy();
auto result = binary_guard();
_bothAlive = result._bothAlive = new std::atomic<bool>(true);
return result;
}
} // namespace base
namespace crl {
template <typename T, typename Enable>
struct guard_traits;
template <>
struct guard_traits<base::binary_guard, void> {
static base::binary_guard create(base::binary_guard value) {
return value;
}
static bool check(const base::binary_guard &guard) {
return guard.alive();
}
};
} // namespace crl

View File

@@ -0,0 +1,46 @@
// 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 <stdint.h>
// thanks Chromium
// Compiler detection.
#if defined(__clang__)
#define COMPILER_CLANG 1
#elif defined(__GNUC__) // __clang__
#define COMPILER_GCC 1
#elif defined(_MSC_VER) // __clang__ || __GNUC__
#define COMPILER_MSVC 1
#endif // _MSC_VER || __clang__ || __GNUC__
// Processor architecture detection.
#if defined(_M_X64) || defined(__x86_64__)
#define ARCH_CPU_X86_FAMILY 1
#define ARCH_CPU_X86_64 1
#elif defined(_M_IX86) || defined(__i386__)
#define ARCH_CPU_X86_FAMILY 1
#define ARCH_CPU_X86 1
#endif
// _LP64 is defined by GCC, others by MSVC
#if defined _LP64 || defined _M_X64 || defined _M_ARM64 || defined _M_ALPHA
#define ARCH_CPU_64_BITS 1
#else
#define ARCH_CPU_32_BITS 1
#endif
#if defined(__GNUC__)
#define TG_FORCE_INLINE inline __attribute__((always_inline))
#elif defined(_MSC_VER)
#define TG_FORCE_INLINE __forceinline
#else
#define TG_FORCE_INLINE inline
#endif
#include <climits>
static_assert(CHAR_BIT == 8, "Not supported char size.");

View File

@@ -0,0 +1,19 @@
// 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/bytes.h"
#include "base/random.h"
namespace bytes {
void set_random(span destination) {
if (!destination.empty()) {
base::RandomFill(destination.data(), destination.size());
}
}
} // namespace bytes

View File

@@ -0,0 +1,170 @@
// 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 <gsl/gsl>
#include <gsl/byte>
#include <vector>
#include <array>
namespace bytes {
using type = gsl::byte;
using span = gsl::span<type>;
using const_span = gsl::span<const type>;
using vector = std::vector<type>;
template <gsl::index Size>
using array = std::array<type, Size>;
inline span make_detached_span(QByteArray &container) {
return gsl::as_writable_bytes(gsl::make_span(container));
}
template <
typename Container,
typename = std::enable_if_t<
!std::is_const_v<Container>
&& !std::is_same_v<Container, QByteArray>>>
inline span make_span(Container &container) {
return gsl::as_writable_bytes(gsl::make_span(container));
}
template<>
inline span make_span<span, void>(span &container) {
return container;
}
template <typename Container>
inline const_span make_span(const Container &container) {
return gsl::as_bytes(gsl::make_span(container));
}
inline const_span make_span(const_span &container) {
return container;
}
template <typename Type, std::ptrdiff_t Extent>
inline span make_span(gsl::span<Type, Extent> container) {
return gsl::as_writable_bytes(container);
}
template <typename Type, std::ptrdiff_t Extent>
inline const_span make_span(gsl::span<const Type, Extent> container) {
return gsl::as_bytes(container);
}
template <typename Type>
inline span make_span(Type *value, std::size_t count) {
return gsl::as_writable_bytes(gsl::make_span(value, count));
}
template <typename Type>
inline const_span make_span(const Type *value, std::size_t count) {
return gsl::as_bytes(gsl::make_span(value, count));
}
template <typename Type>
inline span object_as_span(Type *value) {
return bytes::make_span(value, 1);
}
template <typename Type>
inline const_span object_as_span(const Type *value) {
return bytes::make_span(value, 1);
}
template <typename Container>
inline vector make_vector(const Container &container) {
const auto buffer = bytes::make_span(container);
return { buffer.begin(), buffer.end() };
}
inline void copy(span destination, const_span source) {
Expects(destination.size() >= source.size());
memcpy(destination.data(), source.data(), source.size());
}
inline void move(span destination, const_span source) {
Expects(destination.size() >= source.size());
memmove(destination.data(), source.data(), source.size());
}
inline void set_with_const(span destination, type value) {
memset(
destination.data(),
gsl::to_integer<unsigned char>(value),
destination.size());
}
inline int compare(const_span a, const_span b) {
const auto aSize = a.size(), bSize = b.size();
return (aSize > bSize)
? 1
: (aSize < bSize)
? -1
: memcmp(a.data(), b.data(), aSize);
}
namespace details {
template <typename Arg>
std::size_t spansLength(Arg &&arg) {
return bytes::make_span(arg).size();
}
template <typename Arg, typename ...Args>
std::size_t spansLength(Arg &&arg, Args &&...args) {
return bytes::make_span(arg).size() + spansLength(args...);
}
template <typename Arg>
void spansAppend(span destination, Arg &&arg) {
bytes::copy(destination, bytes::make_span(arg));
}
template <typename Arg, typename ...Args>
void spansAppend(span destination, Arg &&arg, Args &&...args) {
const auto data = bytes::make_span(arg);
bytes::copy(destination, data);
spansAppend(destination.subspan(data.size()), args...);
}
} // namespace details
template <
typename ...Args,
typename = std::enable_if_t<(sizeof...(Args) > 1)>>
vector concatenate(Args &&...args) {
const auto size = details::spansLength(args...);
auto result = vector(size);
details::spansAppend(make_span(result), args...);
return result;
}
template <typename SpanRange>
vector concatenate(SpanRange args) {
auto size = std::size_t(0);
for (const auto &arg : args) {
size += bytes::make_span(arg).size();
}
auto result = vector(size);
auto buffer = make_span(result);
for (const auto &arg : args) {
const auto part = bytes::make_span(arg);
bytes::copy(buffer, part);
buffer = buffer.subspan(part.size());
}
return result;
}
void set_random(span destination);
} // namespace bytes

View File

@@ -0,0 +1,49 @@
// 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/call_delayed.h"
#include "base/timer.h"
#include <QtCore/QCoreApplication>
namespace base {
namespace {
DelayedCallTimer *GlobalTimer = nullptr;
bool Finished = false;
void CreateGlobalTimer() {
Expects(QCoreApplication::instance() != nullptr);
Expects(!GlobalTimer);
const auto instance = QCoreApplication::instance();
Assert(instance != nullptr);
GlobalTimer = new DelayedCallTimer();
instance->connect(instance, &QCoreApplication::aboutToQuit, [] {
Finished = true;
});
instance->connect(instance, &QCoreApplication::destroyed, [] {
Finished = true;
delete GlobalTimer;
GlobalTimer = nullptr;
});
}
} // namespace
void call_delayed(crl::time delay, FnMut<void()> &&callable) {
if (Finished) {
return;
}
if (!GlobalTimer) {
CreateGlobalTimer();
}
GlobalTimer->call(delay, std::move(callable));
}
} // namespace base

View File

@@ -0,0 +1,42 @@
// 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 {
void call_delayed(crl::time delay, FnMut<void()> &&callable);
template <
typename Guard,
typename Callable,
typename GuardTraits = crl::guard_traits<std::decay_t<Guard>>,
typename = std::enable_if_t<
sizeof(GuardTraits) != crl::details::dependent_zero<GuardTraits>>>
inline void call_delayed(
crl::time delay,
Guard &&object,
Callable &&callable) {
return call_delayed(delay, crl::guard(
std::forward<Guard>(object),
std::forward<Callable>(callable)));
}
template <typename Guard, typename Callable>
[[nodiscard]] inline auto fn_delayed(
crl::time delay,
Guard &&object,
Callable &&callable) {
auto guarded = crl::guard(
std::forward<Guard>(object),
std::forward<Callable>(callable));
return [saved = std::move(guarded), delay] {
auto copy = saved;
base::call_delayed(delay, std::move(copy));
};
}
} // namespace base

View File

@@ -0,0 +1,369 @@
// 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/concurrent_timer.h"
#include <QtCore/QThread>
#include <QtCore/QCoreApplication>
using namespace base::details;
namespace base {
namespace details {
namespace {
auto CallDelayedEventType() {
static const auto Result = QEvent::Type(QEvent::registerEventType());
return Result;
}
auto CancelTimerEventType() {
static const auto Result = QEvent::Type(QEvent::registerEventType());
return Result;
}
ConcurrentTimerEnvironment *Environment/* = nullptr*/;
QMutex EnvironmentMutex;
class CallDelayedEvent : public QEvent {
public:
CallDelayedEvent(
crl::time timeout,
Qt::TimerType type,
FnMut<void()> method);
crl::time timeout() const;
Qt::TimerType type() const;
FnMut<void()> takeMethod();
private:
crl::time _timeout = 0;
Qt::TimerType _type = Qt::PreciseTimer;
FnMut<void()> _method;
};
class CancelTimerEvent : public QEvent {
public:
CancelTimerEvent();
};
CallDelayedEvent::CallDelayedEvent(
crl::time timeout,
Qt::TimerType type,
FnMut<void()> method)
: QEvent(CallDelayedEventType())
, _timeout(timeout)
, _type(type)
, _method(std::move(method)) {
Expects(_timeout >= 0 && _timeout < std::numeric_limits<int>::max());
}
crl::time CallDelayedEvent::timeout() const {
return _timeout;
}
Qt::TimerType CallDelayedEvent::type() const {
return _type;
}
FnMut<void()> CallDelayedEvent::takeMethod() {
return base::take(_method);
}
CancelTimerEvent::CancelTimerEvent() : QEvent(CancelTimerEventType()) {
}
} // namespace
class TimerObject : public QObject {
public:
TimerObject(
not_null<QThread*> thread,
not_null<QObject*> adjuster,
Fn<void()> adjust);
protected:
bool event(QEvent *e) override;
private:
void callDelayed(not_null<CallDelayedEvent*> e);
void callNow();
void cancel();
void adjust();
FnMut<void()> _next;
Fn<void()> _adjust;
int _timerId = 0;
};
TimerObject::TimerObject(
not_null<QThread*> thread,
not_null<QObject*> adjuster,
Fn<void()> adjust)
: _adjust(std::move(adjust)) {
moveToThread(thread);
connect(
adjuster,
&QObject::destroyed,
this,
&TimerObject::adjust,
Qt::DirectConnection);
}
bool TimerObject::event(QEvent *e) {
const auto type = e->type();
if (type == CallDelayedEventType()) {
callDelayed(static_cast<CallDelayedEvent*>(e));
return true;
} else if (type == CancelTimerEventType()) {
cancel();
return true;
} else if (type == QEvent::Timer) {
callNow();
return true;
}
return QObject::event(e);
}
void TimerObject::callDelayed(not_null<CallDelayedEvent*> e) {
cancel();
const auto timeout = e->timeout();
const auto type = e->type();
_next = e->takeMethod();
if (timeout > 0) {
_timerId = startTimer(timeout, type);
} else {
base::take(_next)();
}
}
void TimerObject::cancel() {
if (const auto id = base::take(_timerId)) {
killTimer(id);
}
_next = nullptr;
}
void TimerObject::callNow() {
auto next = base::take(_next);
cancel();
next();
}
void TimerObject::adjust() {
if (_adjust) {
_adjust();
}
}
TimerObjectWrap::TimerObjectWrap(Fn<void()> adjust) {
QMutexLocker lock(&EnvironmentMutex);
if (Environment) {
_value = Environment->createTimer(std::move(adjust));
}
}
TimerObjectWrap::~TimerObjectWrap() {
if (_value) {
QMutexLocker lock(&EnvironmentMutex);
if (Environment) {
_value.release()->deleteLater();
}
}
}
void TimerObjectWrap::call(
crl::time timeout,
Qt::TimerType type,
FnMut<void()> method) {
sendEvent(std::make_unique<CallDelayedEvent>(
timeout,
type,
std::move(method)));
}
void TimerObjectWrap::cancel() {
sendEvent(std::make_unique<CancelTimerEvent>());
}
void TimerObjectWrap::sendEvent(std::unique_ptr<QEvent> event) {
if (!_value) {
return;
}
QCoreApplication::postEvent(
_value.get(),
event.release(),
Qt::HighEventPriority);
}
} // namespace details
ConcurrentTimerEnvironment::ConcurrentTimerEnvironment() {
_thread.setObjectName("Concurrent Timer Thread");
_thread.start();
_adjuster.moveToThread(&_thread);
acquire();
}
ConcurrentTimerEnvironment::~ConcurrentTimerEnvironment() {
_thread.quit();
release();
_thread.wait();
QObject::disconnect(&_adjuster, &QObject::destroyed, nullptr, nullptr);
}
std::unique_ptr<TimerObject> ConcurrentTimerEnvironment::createTimer(
Fn<void()> adjust) {
return std::make_unique<TimerObject>(
&_thread,
&_adjuster,
std::move(adjust));
}
void ConcurrentTimerEnvironment::Adjust() {
QMutexLocker lock(&EnvironmentMutex);
if (Environment) {
Environment->adjustTimers();
}
}
void ConcurrentTimerEnvironment::adjustTimers() {
QObject emitter;
QObject::connect(
&emitter,
&QObject::destroyed,
&_adjuster,
&QObject::destroyed,
Qt::QueuedConnection);
}
void ConcurrentTimerEnvironment::acquire() {
Expects(Environment == nullptr);
QMutexLocker lock(&EnvironmentMutex);
Environment = this;
}
void ConcurrentTimerEnvironment::release() {
Expects(Environment == this);
QMutexLocker lock(&EnvironmentMutex);
Environment = nullptr;
}
ConcurrentTimer::ConcurrentTimer(
Fn<void(FnMut<void()>)> runner,
Fn<void()> callback)
: _runner(std::move(runner))
, _object(createAdjuster())
, _callback(std::move(callback))
, _type(Qt::PreciseTimer) {
setRepeat(Repeat::Interval);
}
Fn<void()> ConcurrentTimer::createAdjuster() {
_guard = std::make_shared<bool>(true);
return [=, runner = _runner, guard = std::weak_ptr<bool>(_guard)] {
runner([=] {
if (!guard.lock()) {
return;
}
adjust();
});
};
}
void ConcurrentTimer::start(
crl::time timeout,
Qt::TimerType type,
Repeat repeat) {
_type = type;
setRepeat(repeat);
_adjusted = false;
setTimeout(timeout);
cancelAndSchedule(_timeout);
_next = crl::now() + _timeout;
}
void ConcurrentTimer::cancelAndSchedule(int timeout) {
auto method = [
=,
runner = _runner,
guard = _running.make_guard()
]() mutable {
if (!guard) {
return;
}
runner([=, guard = std::move(guard)] {
if (!guard) {
return;
}
timerEvent();
});
};
_object.call(timeout, _type, std::move(method));
}
void ConcurrentTimer::timerEvent() {
if (repeat() == Repeat::Interval) {
if (_adjusted) {
start(_timeout, _type, repeat());
} else {
_next = crl::now() + _timeout;
}
} else {
cancel();
}
if (_callback) {
_callback();
}
}
void ConcurrentTimer::cancel() {
_running = {};
if (isActive()) {
_running = base::binary_guard();
_object.cancel();
}
}
crl::time ConcurrentTimer::remainingTime() const {
if (!isActive()) {
return -1;
}
const auto now = crl::now();
return (_next > now) ? (_next - now) : crl::time(0);
}
void ConcurrentTimer::adjust() {
auto remaining = remainingTime();
if (remaining >= 0) {
cancelAndSchedule(remaining);
_adjusted = true;
}
}
void ConcurrentTimer::setTimeout(crl::time timeout) {
Expects(timeout >= 0 && timeout <= std::numeric_limits<int>::max());
_timeout = static_cast<unsigned int>(timeout);
}
int ConcurrentTimer::timeout() const {
return _timeout;
}
} // namespace base

View File

@@ -0,0 +1,145 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/binary_guard.h"
#include <crl/crl_time.h>
#include <crl/crl_object_on_queue.h>
#include <QtCore/QThread>
namespace base {
namespace details {
class TimerObject;
class TimerObjectWrap {
public:
explicit TimerObjectWrap(Fn<void()> adjust);
~TimerObjectWrap();
void call(
crl::time timeout,
Qt::TimerType type,
FnMut<void()> method);
void cancel();
private:
void sendEvent(std::unique_ptr<QEvent> event);
std::unique_ptr<TimerObject> _value;
};
} // namespace details
class ConcurrentTimerEnvironment {
public:
ConcurrentTimerEnvironment();
~ConcurrentTimerEnvironment();
std::unique_ptr<details::TimerObject> createTimer(Fn<void()> adjust);
static void Adjust();
private:
void acquire();
void release();
void adjustTimers();
QThread _thread;
QObject _adjuster;
};
class ConcurrentTimer {
public:
explicit ConcurrentTimer(
Fn<void(FnMut<void()>)> runner,
Fn<void()> callback = nullptr);
template <typename Policy, typename Object>
explicit ConcurrentTimer(
crl::details::weak_async<Policy, Object> weak,
Fn<void()> callback = nullptr);
static Qt::TimerType DefaultType(crl::time timeout) {
constexpr auto kThreshold = crl::time(1000);
return (timeout > kThreshold) ? Qt::CoarseTimer : Qt::PreciseTimer;
}
void setCallback(Fn<void()> callback) {
_callback = std::move(callback);
}
void callOnce(crl::time timeout) {
callOnce(timeout, DefaultType(timeout));
}
void callEach(crl::time timeout) {
callEach(timeout, DefaultType(timeout));
}
void callOnce(crl::time timeout, Qt::TimerType type) {
start(timeout, type, Repeat::SingleShot);
}
void callEach(crl::time timeout, Qt::TimerType type) {
start(timeout, type, Repeat::Interval);
}
bool isActive() const {
return _running.alive();
}
void cancel();
crl::time remainingTime() const;
private:
enum class Repeat : unsigned {
Interval = 0,
SingleShot = 1,
};
Fn<void()> createAdjuster();
void start(crl::time timeout, Qt::TimerType type, Repeat repeat);
void adjust();
void cancelAndSchedule(int timeout);
void setTimeout(crl::time timeout);
int timeout() const;
void timerEvent();
void setRepeat(Repeat repeat) {
_repeat = static_cast<unsigned>(repeat);
}
Repeat repeat() const {
return static_cast<Repeat>(_repeat);
}
Fn<void(FnMut<void()>)> _runner;
std::shared_ptr<bool> _guard; // Must be before _object.
details::TimerObjectWrap _object;
Fn<void()> _callback;
base::binary_guard _running;
crl::time _next = 0;
int _timeout = 0;
Qt::TimerType _type : 2;
bool _adjusted : 1 = false;
unsigned _repeat : 1 = 0;
};
template <typename Policy, typename Object>
ConcurrentTimer::ConcurrentTimer(
crl::details::weak_async<Policy, Object> weak,
Fn<void()> callback)
: ConcurrentTimer(weak.runner(), std::move(callback)) {
}
} // namespace base

View File

@@ -0,0 +1,35 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <QtCore/QString>
#include <QtCore/QByteArray>
#include <string_view>
namespace base {
class const_string final : public std::string_view {
public:
using std::string_view::string_view;
[[nodiscard]] QString utf16() const {
return QString::fromUtf8(data(), size());
}
[[nodiscard]] QByteArray utf8() const {
return QByteArray::fromRawData(data(), size());
}
};
} // namespace base
[[nodiscard]] inline constexpr base::const_string operator""_cs(
const char *data,
std::size_t size) {
return { data, size };
}

View File

@@ -0,0 +1,112 @@
// 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/crash_report_header.h"
namespace base::details {
namespace {
std::array<char, kReportHeaderSizeLimit> Bytes;
int Length = 0;
void SafeWriteChar(char ch) {
if (Length < kReportHeaderSizeLimit) {
Bytes[Length++] = ch;
}
}
template <typename Type>
void SafeWriteNumber(Type number) {
if constexpr (Type(-1) < Type(0)) {
if (number < 0) {
SafeWriteChar('-');
number = -number;
}
}
Type upper = 1, prev = number / 10;
while (prev >= upper) {
upper *= 10;
}
while (upper > 0) {
int digit = (number / upper);
SafeWriteChar('0' + digit);
number -= digit * upper;
upper /= 10;
}
}
} // namespace
ReportHeaderWriter operator<<(ReportHeaderWriter, const char *str) {
if (str) {
while (const auto ch = *str++) {
SafeWriteChar(ch);
}
}
return ReportHeaderWriter();
}
ReportHeaderWriter operator<<(ReportHeaderWriter, const wchar_t *str) {
for (int i = 0, l = wcslen(str); i < l; ++i) {
if (
#if !defined(__WCHAR_UNSIGNED__)
str[i] >= 0 &&
#endif
str[i] < 128) {
SafeWriteChar(char(str[i]));
} else {
SafeWriteChar('?');
}
}
return ReportHeaderWriter();
}
ReportHeaderWriter operator<<(ReportHeaderWriter, int num) {
SafeWriteNumber(num);
return ReportHeaderWriter();
}
ReportHeaderWriter operator<<(ReportHeaderWriter, unsigned int num) {
SafeWriteNumber(num);
return ReportHeaderWriter();
}
ReportHeaderWriter operator<<(ReportHeaderWriter, unsigned long num) {
SafeWriteNumber(num);
return ReportHeaderWriter();
}
ReportHeaderWriter operator<<(ReportHeaderWriter, unsigned long long num) {
SafeWriteNumber(num);
return ReportHeaderWriter();
}
ReportHeaderWriter operator<<(ReportHeaderWriter, double num) {
if (num < 0) {
SafeWriteChar('-');
num = -num;
}
SafeWriteNumber(uint64(floor(num)));
SafeWriteChar('.');
num -= floor(num);
for (int i = 0; i < 4; ++i) {
num *= 10;
int digit = int(floor(num));
SafeWriteChar('0' + digit);
num -= digit;
}
return ReportHeaderWriter();
}
const char *ReportHeaderBytes() {
return Bytes.data();
}
int ReportHeaderLength() {
return Length;
}
} // namespace base::details

View File

@@ -0,0 +1,27 @@
// 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::details {
inline constexpr auto kReportHeaderSizeLimit = 64 * 1024;
struct ReportHeaderWriter {
};
ReportHeaderWriter operator<<(ReportHeaderWriter, const char *str);
ReportHeaderWriter operator<<(ReportHeaderWriter, const wchar_t *str);
ReportHeaderWriter operator<<(ReportHeaderWriter, int num);
ReportHeaderWriter operator<<(ReportHeaderWriter, unsigned int num);
ReportHeaderWriter operator<<(ReportHeaderWriter, unsigned long num);
ReportHeaderWriter operator<<(ReportHeaderWriter, unsigned long long num);
ReportHeaderWriter operator<<(ReportHeaderWriter, double num);
[[nodiscard]] const char *ReportHeaderBytes();
[[nodiscard]] int ReportHeaderLength();
} // namespace base::details

View 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/crash_report_writer.h"
#include "base/platform/base_platform_info.h"
#include "base/integration.h"
#include "base/crash_report_header.h"
#include <QtCore/QDir>
#include <QtCore/QDateTime>
#include <QtCore/QMutex>
#include <QtCore/QMutexLocker>
#include <signal.h>
#include <new>
#include <mutex>
#if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
#define USE_BREAKPAD
#endif // !Q_OS_MAC || MAC_USE_BREAKPAD
// see https://blog.inventic.eu/2012/08/qt-and-google-breakpad/
#ifdef Q_OS_WIN
#include <io.h>
#include <fcntl.h>
#pragma warning(push)
#pragma warning(disable:4091)
#include <client/windows/handler/exception_handler.h>
#pragma warning(pop)
#elif defined Q_OS_MAC // Q_OS_WIN
#include <execinfo.h>
#include <signal.h>
#include <sys/syscall.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <unistd.h>
#ifdef USE_BREAKPAD
#include <client/mac/handler/exception_handler.h>
#else // USE_BREAKPAD
#include <client/crashpad_client.h>
#endif // USE_BREAKPAD
#else // Q_OS_MAC
#include <execinfo.h>
#include <signal.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <client/linux/handler/exception_handler.h>
#endif // else for Q_OS_WIN || Q_OS_MAC
namespace base::Platform {
using namespace ::Platform;
} // namespace base::Platform
namespace base {
namespace {
using namespace details;
CrashReportWriter *Instance = nullptr;
QMutex AnnotationsMutex;
std::map<std::string, std::string> Annotations;
int ReportFileNo = -1;
std::atomic<Qt::HANDLE> ReportingThreadId = nullptr;
bool SkipWriteReportHeader = false;
bool ReportingHeaderWritten = false;
QMutex ReportingMutex;
#ifdef Q_OS_WIN
const wchar_t *BreakpadDumpId = nullptr;
std::wstring FinalReportPath;
#else // Q_OS_WIN
const char *BreakpadDumpId = nullptr;
std::string FinalReportPath;
#endif // Q_OS_WIN
using ReservedMemoryChunk = std::array<gsl::byte, 1024 * 1024>;
std::unique_ptr<ReservedMemoryChunk> ReservedMemory;
const char *PlatformString() {
if (Platform::IsWindowsStoreBuild()) {
return Platform::IsWindows64Bit()
? "WinStore64Bit"
: "WinStore32Bit";
} else if (Platform::IsWindows32Bit()) {
return "Windows32Bit";
} else if (Platform::IsWindows64Bit()) {
return "Windows64Bit";
} else if (Platform::IsMacStoreBuild()) {
return "MacAppStore";
} else if (Platform::IsMac()) {
return "MacOS";
} else if (Platform::IsLinux()) {
return "Linux";
}
Unexpected("Platform in CrashReports::PlatformString.");
}
void AddAnnotation(std::string key, std::string value) {
QMutexLocker lock(&AnnotationsMutex);
Annotations.emplace(std::move(key), std::move(value));
}
void InstallOperatorNewHandler() {
ReservedMemory = std::make_unique<ReservedMemoryChunk>();
std::set_new_handler([] {
std::set_new_handler(nullptr);
ReservedMemory.reset();
Unexpected("Could not allocate!");
});
}
void InstallQtMessageHandler() {
static QtMessageHandler original = nullptr;
original = qInstallMessageHandler([](
QtMsgType type,
const QMessageLogContext &context,
const QString &message) {
if (original) {
original(type, context, message);
}
if (type == QtFatalMsg && Instance) {
AddAnnotation("QtFatal", message.toStdString());
Unexpected("Qt FATAL message was generated!");
}
});
}
#ifdef Q_OS_WIN
void SignalHandler(int signum) {
#else // Q_OS_WIN
struct sigaction SIG_def[32];
void SignalHandler(int signum, siginfo_t *info, void *ucontext) {
if (signum > 0) {
sigaction(signum, &SIG_def[signum], 0);
}
#endif // else for Q_OS_WIN
const char* name = 0;
switch (signum) {
case SIGABRT: name = "SIGABRT"; break;
case SIGSEGV: name = "SIGSEGV"; break;
case SIGILL: name = "SIGILL"; break;
case SIGFPE: name = "SIGFPE"; break;
#ifndef Q_OS_WIN
case SIGBUS: name = "SIGBUS"; break;
case SIGSYS: name = "SIGSYS"; break;
#endif // !Q_OS_WIN
}
const auto thread = QThread::currentThreadId();
if (thread == ReportingThreadId) {
return;
}
QMutexLocker lock(&ReportingMutex);
ReportingThreadId = thread;
if (SkipWriteReportHeader || ReportFileNo < 0) {
return;
}
if (!ReportingHeaderWritten) {
ReportingHeaderWritten = true;
QMutexLocker lock(&AnnotationsMutex);
for (const auto &i : Annotations) {
ReportHeaderWriter() << i.first.c_str() << ": " << i.second.c_str() << "\n";
}
ReportHeaderWriter() << "\n";
}
if (name) {
ReportHeaderWriter() << "Caught signal " << signum << " (" << name << ") in thread " << uint64(thread) << "\n";
} else if (signum == -1) {
ReportHeaderWriter() << "Google Breakpad caught a crash, minidump written in thread " << uint64(thread) << "\n";
if (BreakpadDumpId) {
ReportHeaderWriter() << "Minidump: " << BreakpadDumpId << "\n";
}
} else {
ReportHeaderWriter() << "Caught signal " << signum << " in thread " << uint64(thread) << "\n";
}
#ifdef Q_OS_WIN
_write(ReportFileNo, ReportHeaderBytes(), ReportHeaderLength());
_close(ReportFileNo);
#else // Q_OS_WIN
[[maybe_unused]] auto result_ = write(ReportFileNo, ReportHeaderBytes(), ReportHeaderLength());
close(ReportFileNo);
#endif // Q_OS_WIN
ReportFileNo = -1;
#ifdef Q_OS_WIN
if (BreakpadDumpId) {
FinalReportPath.append(BreakpadDumpId);
FinalReportPath.append(L".txt");
auto handle = int();
const auto errcode = _wsopen_s(
&handle,
FinalReportPath.c_str(),
_O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY,
_SH_DENYWR,
_S_IWRITE);
if (!errcode) {
_write(handle, ReportHeaderBytes(), ReportHeaderLength());
_close(handle);
}
}
#else // Q_OS_WIN
if (BreakpadDumpId) {
FinalReportPath.append(BreakpadDumpId);
const auto good = int(FinalReportPath.size()) - 4;
if (good > 0 && !strcmp(FinalReportPath.c_str() + good, ".dmp")) {
FinalReportPath.erase(FinalReportPath.begin() + good, FinalReportPath.end());
}
FinalReportPath.append(".txt");
const auto handle = open(
FinalReportPath.c_str(),
O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (handle >= 0) {
[[maybe_unused]] auto result_ = write(handle, ReportHeaderBytes(), ReportHeaderLength());
close(handle);
}
}
#endif // Q_OS_WIN
ReportingThreadId = nullptr;
}
bool SetSignalHandlers = Platform::IsLinux() || Platform::IsMac();
bool CrashLogged = false;
#ifdef USE_BREAKPAD
google_breakpad::ExceptionHandler* BreakpadExceptionHandler = 0;
#ifdef Q_OS_WIN
bool DumpCallback(const wchar_t* _dump_dir, const wchar_t* _minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool success)
#elif defined Q_OS_MAC // Q_OS_WIN
bool DumpCallback(const char* _dump_dir, const char* _minidump_id, void *context, bool success)
#else // Q_OS_MAC
bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context, bool success)
#endif // else for Q_OS_WIN || Q_OS_MAC
{
if (CrashLogged) return success;
CrashLogged = true;
#ifdef Q_OS_WIN
BreakpadDumpId = _minidump_id;
SignalHandler(-1);
#else // Q_OS_WIN
#ifdef Q_OS_MAC
BreakpadDumpId = _minidump_id;
#else // Q_OS_MAC
BreakpadDumpId = md.path();
auto afterLastSlash = BreakpadDumpId;
for (auto ch = afterLastSlash; *ch != 0; ++ch) {
if (*ch == '/') {
afterLastSlash = (ch + 1);
}
}
if (*afterLastSlash) {
BreakpadDumpId = afterLastSlash;
}
#endif // else for Q_OS_MAC
SignalHandler(-1, 0, 0);
#endif // else for Q_OS_WIN
return success;
}
#endif // USE_BREAKPAD
} // namespace
CrashReportWriter::CrashReportWriter(const QString &path) : _path(path) {
Expects(Instance == nullptr);
Expects(_path.endsWith('/'));
Instance = this;
_previousReport = readPreviousReport();
}
CrashReportWriter::~CrashReportWriter() {
Expects(Instance == this);
finishCatching();
closeReport();
Instance = nullptr;
}
void CrashReportWriter::start() {
AddAnnotation(
"Launched",
QDateTime::currentDateTime().toString(
"dd.MM.yyyy hh:mm:ss"
).toStdString());
AddAnnotation("Platform", PlatformString());
QDir().mkpath(_path);
openReport();
startCatching();
}
bool CrashReportWriter::openReport() {
if (ReportFileNo >= 0) {
return true;
}
// Try to lock the report file to kill
// all remaining processes that opened it.
_reportFile.setFileName(reportPath());
if (!_reportLock.lock(_reportFile, QIODevice::WriteOnly)) {
return false;
}
ReportFileNo = _reportFile.handle();
if (ReportFileNo < 0) {
return false;
}
#ifdef Q_OS_WIN
FinalReportPath = _path.toStdWString();
#else // Q_OS_WIN
FinalReportPath = QFile::encodeName(_path).toStdString();
#endif // Q_OS_WIN
FinalReportPath.reserve(FinalReportPath.size() + 1024);
if (SetSignalHandlers) {
#ifndef Q_OS_WIN
struct sigaction sigact;
sigact.sa_sigaction = SignalHandler;
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
sigaction(SIGABRT, &sigact, &SIG_def[SIGABRT]);
sigaction(SIGSEGV, &sigact, &SIG_def[SIGSEGV]);
sigaction(SIGILL, &sigact, &SIG_def[SIGILL]);
sigaction(SIGFPE, &sigact, &SIG_def[SIGFPE]);
sigaction(SIGBUS, &sigact, &SIG_def[SIGBUS]);
sigaction(SIGSYS, &sigact, &SIG_def[SIGSYS]);
#else // !Q_OS_WIN
signal(SIGABRT, SignalHandler);
signal(SIGSEGV, SignalHandler);
signal(SIGILL, SignalHandler);
signal(SIGFPE, SignalHandler);
#endif // else for !Q_OS_WIN
}
InstallOperatorNewHandler();
InstallQtMessageHandler();
return true;
}
void CrashReportWriter::closeReport() {
QMutexLocker lock(&ReportingMutex);
if (SkipWriteReportHeader) {
return;
}
SkipWriteReportHeader = true;
lock.unlock();
_reportLock.unlock();
_reportFile.close();
_reportFile.remove();
ReportFileNo = -1;
}
void CrashReportWriter::startCatching() {
#ifdef Q_OS_WIN
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
_path.toStdWString(),
google_breakpad::ExceptionHandler::FilterCallback(nullptr),
DumpCallback,
(void*)nullptr, // callback_context
google_breakpad::ExceptionHandler::HANDLER_ALL,
MINIDUMP_TYPE(MiniDumpNormal),
// MINIDUMP_TYPE(MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithProcessThreadData | MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules | MiniDumpWithFullAuxiliaryState | MiniDumpIgnoreInaccessibleMemory | MiniDumpWithTokenInformation),
(const wchar_t*)nullptr, // pipe_name
(const google_breakpad::CustomClientInfo*)nullptr
);
#elif defined Q_OS_MAC // Q_OS_WIN
#ifdef USE_BREAKPAD
#ifndef _DEBUG
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
QFile::encodeName(_path).toStdString(),
/*FilterCallback*/ 0,
DumpCallback,
/*context*/ 0,
true,
0
);
#endif // !_DEBUG
#else // USE_BREAKPAD
crashpad::CrashpadClient crashpad_client;
const auto handler = (Integration::Instance().executablePath() + "/Contents/Helpers/crashpad_handler").toStdString();
const auto database = QFile::encodeName(_path).constData();
if (crashpad_client.StartHandler(
base::FilePath(handler),
base::FilePath(database),
{}, // metrics_dir
std::string(), // url
Annotations,
std::vector<std::string>(), // arguments
false, // restartable
false)) { // asynchronous_start
}
#endif // USE_BREAKPAD
#else
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
google_breakpad::MinidumpDescriptor(QFile::encodeName(_path).toStdString()),
/*FilterCallback*/ 0,
DumpCallback,
/*context*/ 0,
true,
-1
);
#endif // else for Q_OS_WIN || Q_OS_MAC
}
void CrashReportWriter::finishCatching() {
#ifdef USE_BREAKPAD
delete base::take(BreakpadExceptionHandler);
#endif // USE_BREAKPAD
}
void CrashReportWriter::addAnnotation(std::string key, std::string value) {
AddAnnotation(std::move(key), std::move(value));
}
QString CrashReportWriter::reportPath() const {
return _path + "report";
}
std::optional<QByteArray> CrashReportWriter::readPreviousReport() {
auto file = QFile(reportPath());
if (file.open(QIODevice::ReadOnly)) {
return file.readAll();
}
return std::nullopt;
}
} // namespace CrashReports

View File

@@ -0,0 +1,38 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/file_lock.h"
namespace base {
class CrashReportWriter final {
public:
CrashReportWriter(const QString &path);
~CrashReportWriter();
void start();
void addAnnotation(std::string key, std::string value);
private:
[[nodiscard]] QString reportPath() const;
[[nodiscard]] std::optional<QByteArray> readPreviousReport();
bool openReport();
void closeReport();
void startCatching();
void finishCatching();
const QString _path;
FileLock _reportLock;
QFile _reportFile;
std::optional<QByteArray> _previousReport;
std::map<std::string, std::string> _annotations;
};
} // namespace base

View File

@@ -0,0 +1,60 @@
// 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/crc32hash.h"
namespace base {
namespace {
class Crc32Table {
public:
Crc32Table() {
auto poly = std::uint32_t(0x04c11db7);
for (auto i = 0; i != 256; ++i) {
_data[i] = reflect(i, 8) << 24;
for (auto j = 0; j != 8; ++j) {
_data[i] = (_data[i] << 1) ^ (_data[i] & (1 << 31) ? poly : 0);
}
_data[i] = reflect(_data[i], 32);
}
}
std::uint32_t operator[](int index) const {
return _data[index];
}
private:
std::uint32_t reflect(std::uint32_t val, char ch) {
auto result = std::uint32_t(0);
for (int i = 1; i < (ch + 1); ++i) {
if (val & 1) {
result |= 1 << (ch - i);
}
val >>= 1;
}
return result;
}
std::uint32_t _data[256];
};
} // namespace
std::int32_t crc32(const void *data, int len) {
static const auto kTable = Crc32Table();
const auto buffer = static_cast<const std::uint8_t*>(data);
auto crc = std::uint32_t(0xffffffff);
for (auto i = 0; i != len; ++i) {
crc = (crc >> 8) ^ kTable[(crc & 0xFF) ^ buffer[i]];
}
return static_cast<std::int32_t>(crc ^ 0xffffffff);
}
} // namespace base

View File

@@ -0,0 +1,15 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <cstdint>
namespace base {
std::int32_t crc32(const void *data, int len);
} // namespace base

View File

@@ -0,0 +1,29 @@
// 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"
namespace base {
inline std::optional<uint64> SetCustomAppIcon(QImage image) {
return Platform::SetCustomAppIcon(std::move(image));
}
inline std::optional<uint64> SetCustomAppIcon(const QString &path) {
return Platform::SetCustomAppIcon(path);
}
inline std::optional<uint64> CurrentCustomAppIconDigest() {
return Platform::CurrentCustomAppIconDigest();
}
inline bool ClearCustomAppIcon() {
return Platform::ClearCustomAppIcon();
}
} // namespace base

View File

@@ -0,0 +1,21 @@
// 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 {
template <auto fn>
struct custom_delete {
template <typename T>
constexpr void operator()(T* value) const {
if (value) {
fn(value);
}
}
};
} // namespace base

View File

@@ -0,0 +1,35 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#ifdef _DEBUG
#pragma once
#include <iostream>
template <typename T>
class PrintDead : public T {
public:
template <typename... Args>
PrintDead(Args&&... args) : T(std::forward<Args>(args)...) {}
~PrintDead() {
std::cout
<< "\033[32m" // Green.
<< "Instance of class "
<< typeid(T).name()
<< " is dead!"
<< "\033[0m"
<< std::endl;
}
};
// #include "base/debug_destroy_informer.h"
// Usage: std::make_shared<PrintDead<QSvgRenderer>>(...);
// Usage: PrintDead<QSvgRenderer>(...);
#endif // _DEBUG

View File

@@ -0,0 +1,37 @@
// 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/debug_log.h"
#include "base/integration.h"
namespace base {
void LogWriteMain(const QString &message) {
if (Integration::Exists()) {
Integration::Instance().logMessage(message);
}
}
void LogWriteDebug(const QString &message, const char *file, int line) {
Expects(!LogSkipDebug());
Integration::Instance().logMessageDebug(QString("%1 (%2 : %3)").arg(
message,
QString::fromUtf8(file),
QString::number(__LINE__)));
}
bool LogSkipDebug() {
return !Integration::Exists() || Integration::Instance().logSkipDebug();
}
QString LogProfilePrefix() {
const auto now = crl::profile();
return '[' + QString::number(now / 1000., 'f', 3) + "] ";
}
} // namespace base

View File

@@ -0,0 +1,38 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/assertion.h" // SOURCE_FILE_BASENAME
#include <QtCore/QString>
namespace base {
void LogWriteMain(const QString &message);
void LogWriteDebug(const QString &message, const char *file, int line);
[[nodiscard]] bool LogSkipDebug();
[[nodiscard]] QString LogProfilePrefix();
} // namespace base
#define LOG(message) (::base::LogWriteMain(QString message))
//usage LOG(("log: %1 %2").arg(1).arg(2))
#define PROFILE_LOG(message) {\
if (!::base::LogSkipDebug()) {\
::base::LogWriteMain(::base::LogProfilePrefix() + QString message);\
}\
}
//usage PROFILE_LOG(("step: %1 %2").arg(1).arg(2))
#define DEBUG_LOG(message) {\
if (!::base::LogSkipDebug()) {\
::base::LogWriteDebug(QString message, SOURCE_FILE_BASENAME, __LINE__);\
}\
}
//usage DEBUG_LOG(("log: %1 %2").arg(1).arg(2))

View File

@@ -0,0 +1,52 @@
// 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 {
template <typename Enum>
class enum_mask {
using Type = std::uint32_t;
public:
static_assert(static_cast<int>(Enum::kCount) <= 32, "We have only 32 bit.");
enum_mask() = default;
enum_mask(Enum value) : _value(ToBit(value)) {
}
static enum_mask All() {
auto result = enum_mask();
result._value = ~Type(0);
return result;
}
enum_mask added(enum_mask other) const {
auto result = *this;
result.set(other);
return result;
}
void set(enum_mask other) {
_value |= other._value;
}
bool test(Enum value) const {
return _value & ToBit(value);
}
explicit operator bool() const {
return _value != 0;
}
private:
inline static Type ToBit(Enum value) {
return 1 << static_cast<Type>(value);
}
Type _value = 0;
};
} // namespace base

View File

@@ -0,0 +1,61 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "base/event_filter.h"
#include "base/weak_qptr.h"
namespace base {
namespace details {
EventFilter::EventFilter(
not_null<QObject*> parent,
not_null<QObject*> object,
Fn<EventFilterResult(not_null<QEvent*>)> filter)
: QObject(parent)
, _filter(std::move(filter)) {
object->installEventFilter(this);
}
bool EventFilter::eventFilter(QObject *watched, QEvent *event) {
return (_filter(event) == EventFilterResult::Cancel);
}
} // namespace details
not_null<QObject*> install_event_filter(
not_null<QObject*> object,
Fn<EventFilterResult(not_null<QEvent*>)> filter) {
return install_event_filter(object, object, std::move(filter));
}
not_null<QObject*> install_event_filter(
not_null<QObject*> context,
not_null<QObject*> object,
Fn<EventFilterResult(not_null<QEvent*>)> filter) {
return new details::EventFilter(context, object, std::move(filter));
}
void install_event_filter(
not_null<QObject*> object,
Fn<EventFilterResult(not_null<QEvent*>)> filter,
rpl::lifetime &lifetime) {
// Not safe in case object is deleted before lifetime.
//
//lifetime.make_state<details::EventFilter>(
// object,
// object,
// std::move(filter));
const auto raw = install_event_filter(object, std::move(filter));
lifetime.add([weak = make_weak(raw)] {
if (const auto strong = weak.get()) {
delete strong;
}
});
}
} // namespace base

View File

@@ -0,0 +1,52 @@
// 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 <QtCore/QObject>
namespace base {
enum class EventFilterResult {
Continue,
Cancel,
};
not_null<QObject*> install_event_filter(
not_null<QObject*> object,
Fn<EventFilterResult(not_null<QEvent*>)> filter);
not_null<QObject*> install_event_filter(
not_null<QObject*> context,
not_null<QObject*> object,
Fn<EventFilterResult(not_null<QEvent*>)> filter);
void install_event_filter(
not_null<QObject*> object,
Fn<EventFilterResult(not_null<QEvent*>)> filter,
rpl::lifetime &lifetime);
namespace details {
class EventFilter : public QObject {
public:
EventFilter(
not_null<QObject*> parent,
not_null<QObject*> object,
Fn<EventFilterResult(not_null<QEvent*>)> filter);
protected:
bool eventFilter(QObject *watched, QEvent *event);
private:
Fn<EventFilterResult(not_null<QEvent*>)> _filter;
};
} // namespace details
} // namespace base

View File

@@ -0,0 +1,17 @@
// 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 <tl/expected.hpp>
namespace base {
using ::tl::expected;
using ::tl::unexpected;
using ::tl::make_unexpected;
} // namespace base

View File

@@ -0,0 +1,38 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/basic_types.h"
#include <QtCore/QFile>
namespace base {
class FileLock {
public:
FileLock();
bool lock(QFile &file, QIODevice::OpenMode mode);
[[nodiscard]] bool locked() const;
void unlock();
static constexpr auto kSkipBytes = size_type(4);
~FileLock();
private:
class Lock;
struct Descriptor;
struct LockingPid;
static constexpr auto kLockOffset = index_type(0);
static constexpr auto kLockLimit = kSkipBytes;
std::unique_ptr<Lock> _lock;
};
} // namespace base

View File

@@ -0,0 +1,126 @@
// 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/file_lock.h"
#include <variant>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <signal.h>
namespace base {
namespace {
bool KillProcess(pid_t pid) {
auto signal = SIGTERM;
auto attempts = 0;
while (true) {
const auto result = kill(pid, signal);
if (result < 0) {
return (errno == ESRCH);
}
usleep(10000);
if (++attempts == 50) {
signal = SIGKILL;
}
}
}
} // namespace
struct FileLock::Descriptor {
int value;
};
struct FileLock::LockingPid {
pid_t value;
};
class FileLock::Lock {
public:
using Result = std::variant<Descriptor, LockingPid>;
static Result Acquire(const QFile &file);
explicit Lock(int descriptor);
~Lock();
private:
int _descriptor = 0;
};
FileLock::Lock::Result FileLock::Lock::Acquire(const QFile &file) {
const auto descriptor = file.handle();
if (!descriptor || !file.isOpen()) {
return Descriptor{ 0 };
}
while (true) {
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = kLockOffset;
lock.l_len = kLockLimit;
if (fcntl(descriptor, F_SETLK, &lock) == 0) {
return Descriptor{ descriptor };
} else if (fcntl(descriptor, F_GETLK, &lock) < 0) {
return LockingPid{ 0 };
} else if (lock.l_type != F_UNLCK) {
return LockingPid{ lock.l_pid };
}
}
}
FileLock::Lock::Lock(int descriptor) : _descriptor(descriptor) {
}
FileLock::Lock::~Lock() {
struct flock unlock;
unlock.l_type = F_UNLCK;
unlock.l_whence = SEEK_SET;
unlock.l_start = kLockOffset;
unlock.l_len = kLockLimit;
fcntl(_descriptor, F_SETLK, &unlock);
}
FileLock::FileLock() = default;
bool FileLock::lock(QFile &file, QIODevice::OpenMode mode) {
Expects(_lock == nullptr || file.isOpen());
unlock();
file.close();
if (!file.open(mode)) {
return false;
}
while (true) {
const auto result = Lock::Acquire(file);
if (const auto descriptor = std::get_if<Descriptor>(&result)) {
if (descriptor->value > 0) {
_lock = std::make_unique<Lock>(descriptor->value);
return true;
}
break;
} else if (const auto pid = std::get_if<LockingPid>(&result)) {
if (pid->value <= 0 || !KillProcess(pid->value)) {
break;
}
}
}
return false;
}
bool FileLock::locked() const {
return (_lock != nullptr);
}
void FileLock::unlock() {
_lock = nullptr;
}
FileLock::~FileLock() = default;
} // namespace base

View File

@@ -0,0 +1,89 @@
// 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/file_lock.h"
#include "base/platform/win/base_file_utilities_win.h"
#include <QtCore/QDir>
#include <windows.h>
#include <io.h>
#include <fileapi.h>
namespace base {
class FileLock::Lock {
public:
static int Acquire(const QFile &file);
explicit Lock(int descriptor);
~Lock();
private:
static constexpr auto offsetLow = DWORD(kLockOffset);
static constexpr auto offsetHigh = DWORD(0);
static constexpr auto limitLow = DWORD(kLockLimit);
static constexpr auto limitHigh = DWORD(0);
int _descriptor = 0;
};
int FileLock::Lock::Acquire(const QFile &file) {
const auto descriptor = file.handle();
if (!descriptor || !file.isOpen()) {
return false;
}
const auto handle = HANDLE(_get_osfhandle(descriptor));
if (!handle) {
return false;
}
return LockFile(handle, offsetLow, offsetHigh, limitLow, limitHigh)
? descriptor
: 0;
}
FileLock::Lock::Lock(int descriptor) : _descriptor(descriptor) {
}
FileLock::Lock::~Lock() {
if (const auto handle = HANDLE(_get_osfhandle(_descriptor))) {
UnlockFile(handle, offsetLow, offsetHigh, limitLow, limitHigh);
}
}
FileLock::FileLock() = default;
bool FileLock::lock(QFile &file, QIODevice::OpenMode mode) {
Expects(_lock == nullptr || file.isOpen());
unlock();
file.close();
do {
if (!file.open(mode)) {
return false;
} else if (const auto descriptor = Lock::Acquire(file)) {
_lock = std::make_unique<Lock>(descriptor);
return true;
}
file.close();
} while (Platform::CloseProcesses(file.fileName()));
return false;
}
bool FileLock::locked() const {
return (_lock != nullptr);
}
void FileLock::unlock() {
_lock = nullptr;
}
FileLock::~FileLock() = default;
} // namespace base

View File

@@ -0,0 +1,423 @@
// 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 <type_traits>
namespace base {
template <typename EnumType>
class flags;
template <typename ExtendedEnum>
struct extended_flags;
template <typename ExtendedEnum>
using extended_flags_t = typename extended_flags<ExtendedEnum>::type;
namespace details {
struct flags_zero_helper_struct {
};
using flags_zero_helper = void(base::details::flags_zero_helper_struct::*)();
template <typename ExtendedEnum,
typename Enum = typename base::extended_flags<ExtendedEnum>::type>
inline constexpr auto extended_flag_convert(ExtendedEnum value) {
return static_cast<Enum>(value);
}
template <typename ExtendedEnum,
typename Enum = typename base::extended_flags<ExtendedEnum>::type>
inline constexpr auto extended_flags_convert(ExtendedEnum value) {
return flags<Enum>(extended_flag_convert(value));
}
} // namespace details
template <typename EnumType>
class flags {
public:
using Enum = EnumType;
using Type = std::underlying_type_t<Enum>;
constexpr flags() = default;
constexpr flags(details::flags_zero_helper) noexcept {
}
constexpr flags(Enum value) noexcept
: _value(static_cast<Type>(value)) {
}
static constexpr flags from_raw(Type value) noexcept {
return flags(static_cast<Enum>(value));
}
constexpr auto value() const noexcept {
return _value;
}
constexpr operator Type() const noexcept {
return value();
}
constexpr auto &operator|=(flags b) noexcept {
_value |= b.value();
return *this;
}
constexpr auto &operator&=(flags b) noexcept {
_value &= b.value();
return *this;
}
constexpr auto &operator^=(flags b) noexcept {
_value ^= b.value();
return *this;
}
constexpr auto operator~() const noexcept {
return from_raw(~value());
}
constexpr auto operator|(flags b) const noexcept {
return (flags(*this) |= b);
}
constexpr auto operator&(flags b) const noexcept {
return (flags(*this) &= b);
}
constexpr auto operator^(flags b) const noexcept {
return (flags(*this) ^= b);
}
constexpr auto operator|(Enum b) const noexcept {
return (flags(*this) |= b);
}
constexpr auto operator&(Enum b) const noexcept {
return (flags(*this) &= b);
}
constexpr auto operator^(Enum b) const noexcept {
return (flags(*this) ^= b);
}
constexpr auto operator==(Enum b) const noexcept {
return (value() == static_cast<Type>(b));
}
constexpr auto operator!=(Enum b) const noexcept {
return !(*this == b);
}
constexpr auto operator<(Enum b) const noexcept {
return value() < static_cast<Type>(b);
}
constexpr auto operator>(Enum b) const noexcept {
return (b < *this);
}
constexpr auto operator<=(Enum b) const noexcept {
return !(b < *this);
}
constexpr auto operator>=(Enum b) const noexcept {
return !(*this < b);
}
private:
Type _value = 0;
};
template <typename Enum>
constexpr auto make_flags(Enum value) noexcept {
return flags<Enum>(value);
}
template <typename Enum,
typename = std::enable_if_t<std::is_enum<Enum>::value>,
typename = std::enable_if_t<is_flag_type(Enum{})>>
inline constexpr auto operator|(Enum a, flags<Enum> b) noexcept {
return b | a;
}
template <typename Enum,
typename = std::enable_if_t<std::is_enum<Enum>::value>,
typename = std::enable_if_t<is_flag_type(Enum{})>>
inline constexpr auto operator&(Enum a, flags<Enum> b) noexcept {
return b & a;
}
template <typename Enum,
typename = std::enable_if_t<std::is_enum<Enum>::value>,
typename = std::enable_if_t<is_flag_type(Enum{})>>
inline constexpr auto operator^(Enum a, flags<Enum> b) noexcept {
return b ^ a;
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator|(flags<extended_flags_t<ExtendedEnum>> a, ExtendedEnum b) {
return a | details::extended_flags_convert(b);
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator|(ExtendedEnum a, flags<extended_flags_t<ExtendedEnum>> b) {
return b | a;
}
template <typename ExtendedEnum,
typename = extended_flags_t<ExtendedEnum>>
inline constexpr auto operator&(flags<extended_flags_t<ExtendedEnum>> a, ExtendedEnum b) {
return a & details::extended_flags_convert(b);
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator&(ExtendedEnum a, flags<extended_flags_t<ExtendedEnum>> b) {
return b & a;
}
template <typename ExtendedEnum,
typename = extended_flags_t<ExtendedEnum>>
inline constexpr auto operator^(flags<extended_flags_t<ExtendedEnum>> a, ExtendedEnum b) {
return a ^ details::extended_flags_convert(b);
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator^(ExtendedEnum a, flags<extended_flags_t<ExtendedEnum>> b) {
return b ^ a;
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto &operator&=(flags<extended_flags_t<ExtendedEnum>> &a, ExtendedEnum b) {
return (a &= details::extended_flags_convert(b));
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto &operator|=(flags<extended_flags_t<ExtendedEnum>> &a, ExtendedEnum b) {
return (a |= details::extended_flags_convert(b));
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto &operator^=(flags<extended_flags_t<ExtendedEnum>> &a, ExtendedEnum b) {
return (a ^= details::extended_flags_convert(b));
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator==(flags<extended_flags_t<ExtendedEnum>> a, ExtendedEnum b) {
return a == details::extended_flags_convert(b);
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator==(ExtendedEnum a, flags<extended_flags_t<ExtendedEnum>> b) {
return (b == a);
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator!=(flags<extended_flags_t<ExtendedEnum>> a, ExtendedEnum b) {
return !(a == b);
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator!=(ExtendedEnum a, flags<extended_flags_t<ExtendedEnum>> b) {
return !(a == b);
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator<(flags<extended_flags_t<ExtendedEnum>> a, ExtendedEnum b) {
return a < details::extended_flags_convert(b);
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator<(ExtendedEnum a, flags<extended_flags_t<ExtendedEnum>> b) {
return details::extended_flags_convert(a) < b;
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator>(flags<extended_flags_t<ExtendedEnum>> a, ExtendedEnum b) {
return (b < a);
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator>(ExtendedEnum a, flags<extended_flags_t<ExtendedEnum>> b) {
return (b < a);
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator<=(flags<extended_flags_t<ExtendedEnum>> a, ExtendedEnum b) {
return !(b < a);
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator<=(ExtendedEnum a, flags<extended_flags_t<ExtendedEnum>> b) {
return !(b < a);
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator>=(flags<extended_flags_t<ExtendedEnum>> a, ExtendedEnum b) {
return !(a < b);
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator>=(ExtendedEnum a, flags<extended_flags_t<ExtendedEnum>> b) {
return !(a < b);
}
// For bare-enum operators to work inside namespace base we duplicate them here.
template <typename Enum,
typename = std::enable_if_t<std::is_enum<Enum>::value>,
typename = std::enable_if_t<is_flag_type(Enum{})>>
inline constexpr auto operator!(Enum a) noexcept {
return !make_flags(a);
}
template <typename Enum,
typename = std::enable_if_t<std::is_enum<Enum>::value>,
typename = std::enable_if_t<is_flag_type(Enum{})>>
inline constexpr auto operator~(Enum a) noexcept {
return ~make_flags(a);
}
template <typename Enum,
typename = std::enable_if_t<std::is_enum<Enum>::value>,
typename = std::enable_if_t<is_flag_type(Enum{})>>
inline constexpr auto operator|(Enum a, Enum b) noexcept {
return make_flags(a) | b;
}
template <typename Enum,
typename = std::enable_if_t<std::is_enum<Enum>::value>,
typename = std::enable_if_t<is_flag_type(Enum{})>>
inline constexpr auto operator|(Enum a, details::flags_zero_helper) noexcept {
return make_flags(a);
}
template <typename Enum,
typename = std::enable_if_t<std::is_enum<Enum>::value>,
typename = std::enable_if_t<is_flag_type(Enum{})>>
inline constexpr auto operator|(details::flags_zero_helper, Enum b) noexcept {
return make_flags(b);
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator|(ExtendedEnum a, ExtendedEnum b) {
return details::extended_flags_convert(a) | b;
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator|(ExtendedEnum a, typename extended_flags<ExtendedEnum>::type b) {
return details::extended_flags_convert(a) | b;
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator|(typename extended_flags<ExtendedEnum>::type a, ExtendedEnum b) {
return b | a;
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator|(details::flags_zero_helper, ExtendedEnum b) {
return 0 | details::extended_flag_convert(b);
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator|(ExtendedEnum a, details::flags_zero_helper) {
return details::extended_flag_convert(a) | 0;
}
template <typename ExtendedEnum,
typename = typename extended_flags<ExtendedEnum>::type>
inline constexpr auto operator~(ExtendedEnum b) {
return ~details::extended_flags_convert(b);
}
} // namespace base
template <typename Enum,
typename = std::enable_if_t<std::is_enum<Enum>::value>,
typename = std::enable_if_t<is_flag_type(Enum{})>>
inline constexpr auto operator!(Enum a) noexcept {
return !base::make_flags(a);
}
template <typename Enum,
typename = std::enable_if_t<std::is_enum<Enum>::value>,
typename = std::enable_if_t<is_flag_type(Enum{})>>
inline constexpr auto operator~(Enum a) noexcept {
return ~base::make_flags(a);
}
template <typename Enum,
typename = std::enable_if_t<std::is_enum<Enum>::value>,
typename = std::enable_if_t<is_flag_type(Enum{})>>
inline constexpr auto operator|(Enum a, Enum b) noexcept {
return base::make_flags(a) | b;
}
template <typename Enum,
typename = std::enable_if_t<std::is_enum<Enum>::value>,
typename = std::enable_if_t<is_flag_type(Enum{})>>
inline constexpr auto operator|(Enum a, base::details::flags_zero_helper) noexcept {
return base::make_flags(a);
}
template <typename Enum,
typename = std::enable_if_t<std::is_enum<Enum>::value>,
typename = std::enable_if_t<is_flag_type(Enum{})>>
inline constexpr auto operator|(base::details::flags_zero_helper, Enum b) noexcept {
return base::make_flags(b);
}
template <typename ExtendedEnum,
typename = typename base::extended_flags<ExtendedEnum>::type>
inline constexpr auto operator|(ExtendedEnum a, ExtendedEnum b) {
return base::details::extended_flags_convert(a) | b;
}
template <typename ExtendedEnum,
typename = typename base::extended_flags<ExtendedEnum>::type>
inline constexpr auto operator|(ExtendedEnum a, typename base::extended_flags<ExtendedEnum>::type b) {
return base::details::extended_flags_convert(a) | b;
}
template <typename ExtendedEnum,
typename = typename base::extended_flags<ExtendedEnum>::type>
inline constexpr auto operator|(typename base::extended_flags<ExtendedEnum>::type a, ExtendedEnum b) {
return b | a;
}
template <typename ExtendedEnum,
typename = typename base::extended_flags<ExtendedEnum>::type>
inline constexpr auto operator|(base::details::flags_zero_helper, ExtendedEnum b) {
return 0 | base::details::extended_flag_convert(b);
}
template <typename ExtendedEnum,
typename = typename base::extended_flags<ExtendedEnum>::type>
inline constexpr auto operator|(ExtendedEnum a, base::details::flags_zero_helper) {
return base::details::extended_flag_convert(a) | 0;
}
template <typename ExtendedEnum,
typename = typename base::extended_flags<ExtendedEnum>::type>
inline constexpr auto operator~(ExtendedEnum b) {
return ~base::details::extended_flags_convert(b);
}

View File

@@ -0,0 +1,128 @@
// 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 <catch.hpp>
#include "base/flags.h"
namespace MethodNamespace {
template <typename Enum>
void TestFlags(Enum a, Enum b, Enum c) {
auto abc = a | b;
abc |= c;
auto test = abc != a;
CHECK(abc != a);
CHECK(abc != (a | b));
CHECK((abc & a) == a);
CHECK((abc & b) == b);
CHECK((abc & c) == c);
CHECK((abc & ~a) == (b | c));
CHECK((abc & ~(b | c)) == a);
CHECK((abc ^ a) == (abc & ~a));
auto another = a | b;
another |= c;
CHECK(abc == another);
another &= ~b;
CHECK(another == (a | c));
another ^= a;
CHECK(another == c);
another = 0;
another = nullptr;
auto is_zero = ((another & abc) == 0);
CHECK(is_zero);
CHECK(!(another & abc));
auto more = a | another;
auto just = a | 0;
CHECK(more == just);
CHECK(just);
}
} // namespace MethodNamespace
namespace FlagsNamespace {
enum class Flag : int {
one = (1 << 0),
two = (1 << 1),
three = (1 << 2),
};
inline constexpr auto is_flag_type(Flag) { return true; }
class Class {
public:
enum class Public : long {
one = (1 << 2),
two = (1 << 1),
three = (1 << 0),
};
friend inline constexpr auto is_flag_type(Public) { return true; }
static void TestPrivate();
private:
enum class Private : long {
one = (1 << 0),
two = (1 << 1),
three = (1 << 2),
};
friend inline constexpr auto is_flag_type(Private) { return true; }
};
void Class::TestPrivate() {
MethodNamespace::TestFlags(Private::one, Private::two, Private::three);
}
} // namespace FlagsNamespace
namespace ExtendedNamespace {
enum class Flag : int {
one = (1 << 3),
two = (1 << 4),
three = (1 << 5),
};
} // namespace ExtendedNamespace
namespace base {
template<>
struct extended_flags<ExtendedNamespace::Flag> {
using type = FlagsNamespace::Flag;
};
} // namespace base
TEST_CASE("flags operators on scoped enums", "[flags]") {
SECTION("testing non-member flags") {
MethodNamespace::TestFlags(
FlagsNamespace::Flag::one,
FlagsNamespace::Flag::two,
FlagsNamespace::Flag::three);
}
SECTION("testing public member flags") {
MethodNamespace::TestFlags(
FlagsNamespace::Class::Public::one,
FlagsNamespace::Class::Public::two,
FlagsNamespace::Class::Public::three);
}
SECTION("testing private member flags") {
FlagsNamespace::Class::TestPrivate();
}
SECTION("testing extended flags") {
MethodNamespace::TestFlags(
ExtendedNamespace::Flag::one,
ExtendedNamespace::Flag::two,
ExtendedNamespace::Flag::three);
auto onetwo = FlagsNamespace::Flag::one | ExtendedNamespace::Flag::two;
auto twoone = ExtendedNamespace::Flag::two | FlagsNamespace::Flag::one;
CHECK(onetwo == twoone);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,120 @@
// 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 <catch.hpp>
#include "base/flat_map.h"
#include <string>
struct int_wrap {
int value;
};
struct int_wrap_comparator {
inline bool operator()(const int_wrap &a, const int_wrap &b) const {
return a.value < b.value;
}
};
using namespace std;
TEST_CASE("flat_maps should keep items sorted by key", "[flat_map]") {
base::flat_map<int, string> v;
v.emplace(0, "a");
v.emplace(5, "b");
v.emplace(4, "d");
v.emplace(2, "e");
auto checkSorted = [&] {
auto prev = v.begin();
REQUIRE(prev != v.end());
for (auto i = prev + 1; i != v.end(); prev = i, ++i) {
REQUIRE(prev->first < i->first);
}
};
REQUIRE(v.size() == 4);
checkSorted();
SECTION("adding item puts it in the right position") {
v.emplace(3, "c");
REQUIRE(v.size() == 5);
REQUIRE(v.find(3) != v.end());
checkSorted();
}
}
TEST_CASE("simple flat_maps tests", "[flat_map]") {
SECTION("copy constructor") {
base::flat_map<int, string> v;
v.emplace(0, "a");
v.emplace(2, "b");
auto u = v;
REQUIRE(u.size() == 2);
REQUIRE(u.find(0) == u.begin());
REQUIRE(u.find(2) == u.end() - 1);
}
SECTION("assignment") {
base::flat_map<int, string> v, u;
v.emplace(0, "a");
v.emplace(2, "b");
u = v;
REQUIRE(u.size() == 2);
REQUIRE(u.find(0) == u.begin());
REQUIRE(u.find(2) == u.end() - 1);
}
}
TEST_CASE("flat_maps custom comparator", "[flat_map]") {
base::flat_map<int_wrap, string, int_wrap_comparator> v;
v.emplace(int_wrap{ 0 }, "a");
v.emplace(int_wrap{ 5 }, "b");
v.emplace(int_wrap{ 4 }, "d");
v.emplace(int_wrap{ 2 }, "e");
auto checkSorted = [&] {
auto prev = v.begin();
REQUIRE(prev != v.end());
for (auto i = prev + 1; i != v.end(); prev = i, ++i) {
REQUIRE(int_wrap_comparator()(prev->first, i->first));
}
};
REQUIRE(v.size() == 4);
checkSorted();
SECTION("adding item puts it in the right position") {
v.emplace(int_wrap{ 3 }, "c");
REQUIRE(v.size() == 5);
REQUIRE(v.find({ 3 }) != v.end());
checkSorted();
}
}
TEST_CASE("flat_maps structured bindings", "[flat_map]") {
base::flat_map<int, std::unique_ptr<double>> v;
v.emplace(0, std::make_unique<double>(0.));
v.emplace(1, std::make_unique<double>(1.));
SECTION("structred binded range-based for loop") {
for (const auto &[key, value] : v) {
REQUIRE(key == int(std::round(*value)));
}
}
SECTION("non-const structured binded range-based for loop") {
base::flat_map<int, int> second = {
{ 1, 1 },
{ 2, 2 },
{ 2, 3 },
{ 3, 3 },
};
REQUIRE(second.size() == 3);
//for (auto [a, b] : second) { // #MSVC Bug, reported
// REQUIRE(a == b);
//}
for (const auto [a, b] : second) {
REQUIRE(a == b);
}
}
}

View File

@@ -0,0 +1,841 @@
// 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>
#include <algorithm>
namespace base {
using std::begin;
using std::end;
template <typename Type, typename Compare = std::less<>>
class flat_set;
template <typename Type, typename Compare = std::less<>>
class flat_multi_set;
template <typename Type, typename iterator_impl>
class flat_multi_set_iterator_impl;
template <typename Type, typename iterator_impl>
class flat_multi_set_iterator_impl {
public:
using iterator_category = typename iterator_impl::iterator_category;
using value_type = Type;
using difference_type = typename iterator_impl::difference_type;
using pointer = const Type*;
using reference = const Type&;
constexpr flat_multi_set_iterator_impl(
iterator_impl impl = iterator_impl()) noexcept
: _impl(impl) {
}
template <typename other_iterator_impl>
constexpr flat_multi_set_iterator_impl(
const flat_multi_set_iterator_impl<
Type,
other_iterator_impl> &other) noexcept
: _impl(other._impl) {
}
constexpr reference operator*() const noexcept {
return *_impl;
}
constexpr pointer operator->() const noexcept {
return std::addressof(**this);
}
constexpr flat_multi_set_iterator_impl &operator++() noexcept {
++_impl;
return *this;
}
constexpr flat_multi_set_iterator_impl operator++(int) noexcept {
return _impl++;
}
constexpr flat_multi_set_iterator_impl &operator--() noexcept {
--_impl;
return *this;
}
constexpr flat_multi_set_iterator_impl operator--(int) noexcept {
return _impl--;
}
constexpr flat_multi_set_iterator_impl &operator+=(
difference_type offset) noexcept {
_impl += offset;
return *this;
}
constexpr flat_multi_set_iterator_impl operator+(
difference_type offset) const noexcept {
return _impl + offset;
}
constexpr flat_multi_set_iterator_impl &operator-=(
difference_type offset) noexcept {
_impl -= offset;
return *this;
}
constexpr flat_multi_set_iterator_impl operator-(
difference_type offset) const noexcept {
return _impl - offset;
}
template <typename other_iterator_impl>
constexpr difference_type operator-(
const flat_multi_set_iterator_impl<
Type,
other_iterator_impl> &right) const noexcept {
return _impl - right._impl;
}
constexpr reference operator[](difference_type offset) const noexcept {
return _impl[offset];
}
template <typename other_iterator_impl>
constexpr bool operator==(
const flat_multi_set_iterator_impl<
Type,
other_iterator_impl> &right) const noexcept {
return _impl == right._impl;
}
template <typename other_iterator_impl>
constexpr bool operator!=(
const flat_multi_set_iterator_impl<
Type,
other_iterator_impl> &right) const noexcept {
return _impl != right._impl;
}
template <typename other_iterator_impl>
constexpr bool operator<(
const flat_multi_set_iterator_impl<
Type,
other_iterator_impl> &right) const noexcept {
return _impl < right._impl;
}
private:
iterator_impl _impl;
template <typename OtherType, typename OtherCompare>
friend class flat_multi_set;
template <typename OtherType, typename OtherCompare>
friend class flat_set;
template <
typename OtherType,
typename other_iterator_impl>
friend class flat_multi_set_iterator_impl;
constexpr Type &wrapped() noexcept {
return _impl->wrapped();
}
};
template <typename Type>
class flat_multi_set_const_wrap {
public:
constexpr flat_multi_set_const_wrap(const Type &value) noexcept
: _value(value) {
}
constexpr flat_multi_set_const_wrap(Type &&value) noexcept
: _value(std::move(value)) {
}
constexpr operator const Type&() const noexcept {
return _value;
}
constexpr Type &wrapped() noexcept {
return _value;
}
friend inline constexpr bool operator<(
const flat_multi_set_const_wrap &a,
const flat_multi_set_const_wrap &b) noexcept {
return a._value < b._value;
}
friend inline constexpr bool operator>(
const flat_multi_set_const_wrap &a,
const flat_multi_set_const_wrap &b) noexcept {
return a._value > b._value;
}
friend inline constexpr bool operator<=(
const flat_multi_set_const_wrap &a,
const flat_multi_set_const_wrap &b) noexcept {
return a._value <= b._value;
}
friend inline constexpr bool operator>=(
const flat_multi_set_const_wrap &a,
const flat_multi_set_const_wrap &b) noexcept {
return a._value >= b._value;
}
friend inline constexpr bool operator==(
const flat_multi_set_const_wrap &a,
const flat_multi_set_const_wrap &b) noexcept {
return a._value == b._value;
}
friend inline constexpr bool operator!=(
const flat_multi_set_const_wrap &a,
const flat_multi_set_const_wrap &b) noexcept {
return a._value != b._value;
}
private:
Type _value;
};
template <typename Type, typename Compare>
class flat_multi_set {
using const_wrap = flat_multi_set_const_wrap<Type>;
using impl_t = std::vector<const_wrap>;
public:
using value_type = Type;
using size_type = typename impl_t::size_type;
using difference_type = typename impl_t::difference_type;
using pointer = const Type*;
using reference = const Type&;
using iterator = flat_multi_set_iterator_impl<
Type,
typename impl_t::iterator>;
using const_iterator = flat_multi_set_iterator_impl<
Type,
typename impl_t::const_iterator>;
using reverse_iterator = flat_multi_set_iterator_impl<
Type,
typename impl_t::reverse_iterator>;
using const_reverse_iterator = flat_multi_set_iterator_impl<
Type,
typename impl_t::const_reverse_iterator>;
constexpr flat_multi_set() = default;
template <
typename Iterator,
typename = typename std::iterator_traits<Iterator>::iterator_category>
constexpr flat_multi_set(Iterator first, Iterator last) noexcept
: _data(first, last) {
std::sort(std::begin(impl()), std::end(impl()), compare());
}
constexpr flat_multi_set(std::initializer_list<Type> iter) noexcept
: flat_multi_set(iter.begin(), iter.end()) {
}
constexpr size_type size() const noexcept {
return impl().size();
}
constexpr bool empty() const noexcept {
return impl().empty();
}
constexpr void clear() noexcept {
impl().clear();
}
constexpr void reserve(size_type size) noexcept {
impl().reserve(size);
}
constexpr void shrink_to_fit() noexcept {
impl().shrink_to_fit();
}
constexpr iterator begin() noexcept {
return impl().begin();
}
constexpr iterator end() noexcept {
return impl().end();
}
constexpr const_iterator begin() const noexcept {
return impl().begin();
}
constexpr const_iterator end() const noexcept {
return impl().end();
}
constexpr const_iterator cbegin() const noexcept {
return impl().cbegin();
}
constexpr const_iterator cend() const noexcept {
return impl().cend();
}
constexpr reverse_iterator rbegin() noexcept {
return impl().rbegin();
}
constexpr reverse_iterator rend() noexcept {
return impl().rend();
}
constexpr const_reverse_iterator rbegin() const noexcept {
return impl().rbegin();
}
constexpr const_reverse_iterator rend() const noexcept {
return impl().rend();
}
constexpr const_reverse_iterator crbegin() const noexcept {
return impl().crbegin();
}
constexpr const_reverse_iterator crend() const noexcept {
return impl().crend();
}
constexpr reference front() const noexcept {
return *begin();
}
constexpr reference back() const noexcept {
return *(end() - 1);
}
constexpr iterator insert(const Type &value) noexcept {
if (empty() || !compare()(value, back())) {
impl().push_back(value);
return (end() - 1);
}
auto where = getUpperBound(value);
return impl().insert(where, value);
}
constexpr iterator insert(Type &&value) noexcept {
if (empty() || !compare()(value, back())) {
impl().push_back(std::move(value));
return (end() - 1);
}
auto where = getUpperBound(value);
return impl().insert(where, std::move(value));
}
template <typename... Args>
constexpr iterator emplace(Args&&... args) noexcept {
return insert(Type(std::forward<Args>(args)...));
}
template <typename OtherType>
constexpr bool removeOne(const OtherType &value) noexcept {
if (empty()
|| compare()(value, front())
|| compare()(back(), value)) {
return false;
}
auto where = getLowerBound(value);
if (compare()(value, *where)) {
return false;
}
impl().erase(where);
return true;
}
constexpr bool removeOne(const Type &value) noexcept {
return removeOne<Type>(value);
}
template <typename OtherType>
constexpr int removeAll(const OtherType &value) noexcept {
if (empty()
|| compare()(value, front())
|| compare()(back(), value)) {
return 0;
}
auto range = getEqualRange(value);
if (range.first == range.second) {
return 0;
}
const auto result = (range.second - range.first);
impl().erase(range.first, range.second);
return result;
}
constexpr int removeAll(const Type &value) noexcept {
return removeAll<Type>(value);
}
constexpr iterator erase(const_iterator where) noexcept {
return impl().erase(where._impl);
}
constexpr iterator erase(
const_iterator from,
const_iterator till) noexcept {
return impl().erase(from._impl, till._impl);
}
constexpr int erase(const Type &value) noexcept {
return removeAll(value);
}
template <typename OtherType>
constexpr iterator findFirst(const OtherType &value) noexcept {
if (empty()
|| compare()(value, front())
|| compare()(back(), value)) {
return end();
}
auto where = getLowerBound(value);
return compare()(value, *where) ? impl().end() : where;
}
constexpr iterator findFirst(const Type &value) noexcept {
return findFirst<Type>(value);
}
template <typename OtherType>
constexpr const_iterator findFirst(
const OtherType &value) const noexcept {
if (empty()
|| compare()(value, front())
|| compare()(back(), value)) {
return end();
}
auto where = getLowerBound(value);
return compare()(value, *where) ? impl().end() : where;
}
constexpr const_iterator findFirst(
const Type &value) const noexcept {
return findFirst<Type>(value);
}
template <typename OtherType>
constexpr bool contains(const OtherType &value) const noexcept {
return findFirst(value) != end();
}
constexpr bool contains(const Type &value) const noexcept {
return contains<Type>(value);
}
template <typename OtherType>
constexpr int count(const OtherType &value) const noexcept {
if (empty()
|| compare()(value, front())
|| compare()(back(), value)) {
return 0;
}
auto range = getEqualRange(value);
return (range.second - range.first);
}
constexpr int count(const Type &value) const noexcept {
return count<Type>(value);
}
template <typename OtherType>
constexpr iterator lower_bound(const OtherType &value) noexcept {
return getLowerBound(value);
}
template <typename OtherType>
constexpr const_iterator lower_bound(
const OtherType &value) const noexcept {
return getLowerBound(value);
}
template <typename OtherType>
constexpr iterator upper_bound(const OtherType &value) noexcept {
return getUpperBound(value);
}
template <typename OtherType>
constexpr const_iterator upper_bound(
const OtherType &value) const noexcept {
return getUpperBound(value);
}
template <typename OtherType>
constexpr std::pair<iterator, iterator> equal_range(
const OtherType &value) noexcept {
return getEqualRange(value);
}
template <typename OtherType>
constexpr std::pair<const_iterator, const_iterator> equal_range(
const OtherType &value) const noexcept {
return getEqualRange(value);
}
template <typename Action>
constexpr auto modify(iterator which, Action action) noexcept {
auto result = action(which.wrapped());
for (auto i = which + 1, e = end(); i != e; ++i) {
if (compare()(*i, *which)) {
std::swap(i.wrapped(), which.wrapped());
} else {
break;
}
}
for (auto i = which, b = begin(); i != b;) {
--i;
if (compare()(*which, *i)) {
std::swap(i.wrapped(), which.wrapped());
} else {
break;
}
}
return result;
}
template <
typename Iterator,
typename = typename std::iterator_traits<Iterator>::iterator_category>
constexpr void merge(Iterator first, Iterator last) noexcept {
impl().insert(impl().end(), first, last);
std::sort(std::begin(impl()), std::end(impl()), compare());
}
constexpr void merge(
const flat_multi_set<Type, Compare> &other) noexcept {
merge(other.begin(), other.end());
}
constexpr void merge(std::initializer_list<Type> list) noexcept {
merge(list.begin(), list.end());
}
friend inline constexpr bool operator<(
const flat_multi_set &a,
const flat_multi_set &b) noexcept {
return a.impl() < b.impl();
}
friend inline constexpr bool operator>(
const flat_multi_set &a,
const flat_multi_set &b) noexcept {
return a.impl() > b.impl();
}
friend inline constexpr bool operator<=(
const flat_multi_set &a,
const flat_multi_set &b) noexcept {
return a.impl() <= b.impl();
}
friend inline constexpr bool operator>=(
const flat_multi_set &a,
const flat_multi_set &b) noexcept {
return a.impl() >= b.impl();
}
friend inline constexpr bool operator==(
const flat_multi_set &a,
const flat_multi_set &b) noexcept {
return a.impl() == b.impl();
}
friend inline constexpr bool operator!=(
const flat_multi_set &a,
const flat_multi_set &b) noexcept {
return a.impl() != b.impl();
}
private:
friend class flat_set<Type, Compare>;
struct transparent_compare : Compare {
constexpr const Compare &initial() const noexcept {
return *this;
}
template <
typename OtherType1,
typename OtherType2,
typename = std::enable_if_t<
!std::is_same_v<std::decay_t<OtherType1>, const_wrap> &&
!std::is_same_v<std::decay_t<OtherType2>, const_wrap>>>
constexpr auto operator()(
OtherType1 &&a,
OtherType2 &&b) const noexcept {
return initial()(
std::forward<OtherType1>(a),
std::forward<OtherType2>(b));
}
template <
typename OtherType1,
typename OtherType2>
constexpr auto operator()(
OtherType1 &&a,
OtherType2 &&b) const noexcept -> std::enable_if_t<
std::is_same_v<std::decay_t<OtherType1>, const_wrap> &&
std::is_same_v<std::decay_t<OtherType2>, const_wrap>, bool> {
return initial()(
static_cast<const Type&>(a),
static_cast<const Type&>(b));
}
template <
typename OtherType,
typename = std::enable_if_t<
!std::is_same_v<std::decay_t<OtherType>, const_wrap>>>
constexpr auto operator()(
const const_wrap &a,
OtherType &&b) const noexcept {
return initial()(
static_cast<const Type&>(a),
std::forward<OtherType>(b));
}
template <
typename OtherType,
typename = std::enable_if_t<
!std::is_same_v<std::decay_t<OtherType>, const_wrap>>>
constexpr auto operator()(
OtherType &&a,
const const_wrap &b) const noexcept {
return initial()(
std::forward<OtherType>(a),
static_cast<const Type&>(b));
}
};
struct Data : transparent_compare {
template <typename ...Args>
constexpr Data(Args &&...args) noexcept
: elements(std::forward<Args>(args)...) {
}
impl_t elements;
};
Data _data;
constexpr const transparent_compare &compare() const noexcept {
return _data;
}
constexpr const impl_t &impl() const noexcept {
return _data.elements;
}
constexpr impl_t &impl() noexcept {
return _data.elements;
}
template <typename OtherType>
constexpr typename impl_t::iterator getLowerBound(
const OtherType &value) noexcept {
return std::lower_bound(
std::begin(impl()),
std::end(impl()),
value,
compare());
}
template <typename OtherType>
constexpr typename impl_t::const_iterator getLowerBound(
const OtherType &value) const noexcept {
return std::lower_bound(
std::begin(impl()),
std::end(impl()),
value,
compare());
}
template <typename OtherType>
constexpr typename impl_t::iterator getUpperBound(
const OtherType &value) noexcept {
return std::upper_bound(
std::begin(impl()),
std::end(impl()),
value,
compare());
}
template <typename OtherType>
constexpr typename impl_t::const_iterator getUpperBound(
const OtherType &value) const noexcept {
return std::upper_bound(
std::begin(impl()),
std::end(impl()),
value,
compare());
}
template <typename OtherType>
constexpr std::pair<
typename impl_t::iterator,
typename impl_t::iterator
> getEqualRange(const OtherType &value) noexcept {
return std::equal_range(
std::begin(impl()),
std::end(impl()),
value,
compare());
}
template <typename OtherType>
constexpr std::pair<
typename impl_t::const_iterator,
typename impl_t::const_iterator
> getEqualRange(const OtherType &value) const noexcept {
return std::equal_range(
std::begin(impl()),
std::end(impl()),
value,
compare());
}
};
template <typename Type, typename Compare>
class flat_set : private flat_multi_set<Type, Compare> {
using parent = flat_multi_set<Type, Compare>;
public:
using iterator = typename parent::iterator;
using const_iterator = typename parent::const_iterator;
using reverse_iterator = typename parent::reverse_iterator;
using const_reverse_iterator = typename parent::const_reverse_iterator;
using value_type = typename parent::value_type;
using size_type = typename parent::size_type;
using difference_type = typename parent::difference_type;
using pointer = typename parent::pointer;
using reference = typename parent::reference;
constexpr flat_set() = default;
template <
typename Iterator,
typename = typename std::iterator_traits<Iterator>::iterator_category
>
constexpr flat_set(Iterator first, Iterator last) noexcept
: parent(first, last) {
finalize();
}
constexpr flat_set(std::initializer_list<Type> iter) noexcept
: parent(iter.begin(), iter.end()) {
finalize();
}
using parent::parent;
using parent::size;
using parent::empty;
using parent::clear;
using parent::reserve;
using parent::shrink_to_fit;
using parent::begin;
using parent::end;
using parent::cbegin;
using parent::cend;
using parent::rbegin;
using parent::rend;
using parent::crbegin;
using parent::crend;
using parent::front;
using parent::back;
using parent::contains;
using parent::erase;
using parent::lower_bound;
using parent::upper_bound;
using parent::equal_range;
constexpr std::pair<iterator, bool> insert(const Type &value) noexcept {
if (this->empty() || this->compare()(this->back(), value)) {
this->impl().push_back(value);
return std::make_pair(this->end() - 1, true);
}
auto where = this->getLowerBound(value);
if (this->compare()(value, *where)) {
return std::make_pair(this->impl().insert(where, value), true);
}
return std::make_pair(where, false);
}
constexpr std::pair<iterator, bool> insert(Type &&value) noexcept {
if (this->empty() || this->compare()(this->back(), value)) {
this->impl().push_back(std::move(value));
return std::make_pair(this->end() - 1, true);
}
auto where = this->getLowerBound(value);
if (this->compare()(value, *where)) {
return std::make_pair(
this->impl().insert(where, std::move(value)),
true);
}
return std::make_pair(where, false);
}
template <typename... Args>
constexpr std::pair<iterator, bool> emplace(Args&&... args) noexcept {
return this->insert(Type(std::forward<Args>(args)...));
}
template <typename OtherType>
constexpr bool remove(const OtherType &value) noexcept {
return this->removeOne(value);
}
constexpr bool remove(const Type &value) noexcept {
return remove<Type>(value);
}
template <typename OtherType>
constexpr iterator find(const OtherType &value) noexcept {
return this->findFirst(value);
}
constexpr iterator find(const Type &value) noexcept {
return find<Type>(value);
}
template <typename OtherType>
constexpr const_iterator find(const OtherType &value) const noexcept {
return this->findFirst(value);
}
constexpr const_iterator find(const Type &value) const noexcept {
return find<Type>(value);
}
template <typename Action>
constexpr void modify(iterator which, Action action) noexcept {
action(which.wrapped());
for (auto i = iterator(which + 1), e = end(); i != e; ++i) {
if (this->compare()(*i, *which)) {
std::swap(i.wrapped(), which.wrapped());
} else if (!this->compare()(*which, *i)) {
erase(which);
return;
} else{
break;
}
}
for (auto i = which, b = begin(); i != b;) {
--i;
if (this->compare()(*which, *i)) {
std::swap(i.wrapped(), which.wrapped());
} else if (!this->compare()(*i, *which)) {
erase(which);
return;
} else {
break;
}
}
}
template <
typename Iterator,
typename = typename std::iterator_traits<Iterator>::iterator_category>
constexpr void merge(Iterator first, Iterator last) noexcept {
parent::merge(first, last);
finalize();
}
constexpr void merge(
const flat_multi_set<Type, Compare> &other) noexcept {
merge(other.begin(), other.end());
}
constexpr void merge(std::initializer_list<Type> list) noexcept {
merge(list.begin(), list.end());
}
friend inline constexpr bool operator<(
const flat_set &a,
const flat_set &b) noexcept {
return static_cast<const parent&>(a) < static_cast<const parent&>(b);
}
friend inline constexpr bool operator>(
const flat_set &a,
const flat_set &b) noexcept {
return static_cast<const parent&>(a) > static_cast<const parent&>(b);
}
friend inline constexpr bool operator<=(
const flat_set &a,
const flat_set &b) noexcept {
return static_cast<const parent&>(a) <= static_cast<const parent&>(b);
}
friend inline constexpr bool operator>=(
const flat_set &a,
const flat_set &b) noexcept {
return static_cast<const parent&>(a) >= static_cast<const parent&>(b);
}
friend inline constexpr bool operator==(
const flat_set &a,
const flat_set &b) noexcept {
return static_cast<const parent&>(a) == static_cast<const parent&>(b);
}
friend inline constexpr bool operator!=(
const flat_set &a,
const flat_set &b) noexcept {
return static_cast<const parent&>(a) != static_cast<const parent&>(b);
}
private:
constexpr void finalize() noexcept {
this->impl().erase(
std::unique(
std::begin(this->impl()),
std::end(this->impl()),
[&](auto &&a, auto &&b) {
return !this->compare()(a, b);
}
),
std::end(this->impl()));
}
};
} // namespace base

View File

@@ -0,0 +1,83 @@
// 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 <catch.hpp>
#include "base/flat_set.h"
struct int_wrap {
int value;
};
struct int_wrap_comparator {
using is_transparent = void;
inline bool operator()(const int &a, const int_wrap &b) const {
return a < b.value;
}
inline bool operator()(const int_wrap &a, const int_wrap &b) const {
return a.value < b.value;
}
inline bool operator()(const int_wrap &a, const int &b) const {
return a.value < b;
}
inline bool operator()(const int &a, const int &b) const {
return a < b;
}
};
TEST_CASE("flat_sets should keep items sorted", "[flat_set]") {
base::flat_set<int> v;
v.insert(0);
v.insert(5);
v.insert(4);
v.insert(2);
REQUIRE(v.contains(4));
auto checkSorted = [&] {
auto prev = v.begin();
REQUIRE(prev != v.end());
for (auto i = prev + 1; i != v.end(); prev = i, ++i) {
REQUIRE(*prev < *i);
}
};
REQUIRE(v.size() == 4);
checkSorted();
SECTION("adding item puts it in the right position") {
v.insert(3);
REQUIRE(v.size() == 5);
REQUIRE(v.find(3) != v.end());
checkSorted();
}
}
TEST_CASE("flat_sets with custom comparators", "[flat_set]") {
base::flat_set<int_wrap, int_wrap_comparator> v;
v.insert({ 0 });
v.insert({ 5 });
v.insert({ 4 });
v.insert({ 2 });
REQUIRE(v.find(4) != v.end());
auto checkSorted = [&] {
auto prev = v.begin();
REQUIRE(prev != v.end());
for (auto i = prev + 1; i != v.end(); prev = i, ++i) {
REQUIRE(prev->value < i->value);
}
};
REQUIRE(v.size() == 4);
checkSorted();
SECTION("adding item puts it in the right position") {
v.insert({ 3 });
REQUIRE(v.size() == 5);
REQUIRE(v.find(3) != v.end());
checkSorted();
}
}

View File

@@ -0,0 +1,40 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
namespace base {
namespace functors {
struct abs_helper {
template <
typename Type,
typename = decltype(0 < std::declval<Type>()),
typename = decltype(-std::declval<Type>())>
constexpr Type operator()(Type value) const {
return (0 < value) ? value : (-value);
}
};
constexpr auto abs = abs_helper{};
constexpr auto add = [](auto value) {
return [value](auto other) {
return value + other;
};
};
struct negate_helper {
template <
typename Type,
typename = decltype(-std::declval<Type>())>
constexpr Type operator()(Type value) const {
return -value;
}
};
constexpr auto negate = negate_helper{};
} // namespace functors
} // namespace base

View File

@@ -0,0 +1,43 @@
// 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 {
class GlobalShortcutValue {
public:
[[nodiscard]] virtual QString toDisplayString() = 0;
[[nodiscard]] virtual QByteArray serialize() = 0;
virtual ~GlobalShortcutValue() = default;
};
using GlobalShortcut = std::shared_ptr<GlobalShortcutValue>;
// Callbacks are called from unspecified thread.
class GlobalShortcutManager {
public:
virtual void startRecording(
Fn<void(GlobalShortcut)> progress,
Fn<void(GlobalShortcut)> done) = 0;
virtual void stopRecording() = 0;
virtual void startWatching(
GlobalShortcut shortcut,
Fn<void(bool pressed)> callback) = 0;
virtual void stopWatching(GlobalShortcut shortcut) = 0;
[[nodiscard]] virtual GlobalShortcut shortcutFromSerialized(
QByteArray serialized) = 0;
virtual ~GlobalShortcutManager() = default;
};
[[nodiscard]] bool GlobalShortcutsAvailable();
[[nodiscard]] bool GlobalShortcutsAllowed();
[[nodiscard]] std::unique_ptr<GlobalShortcutManager> CreateGlobalShortcutManager();
} // namespace base

View File

@@ -0,0 +1,302 @@
// 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/global_shortcuts_generic.h"
#include "base/platform/base_platform_global_shortcuts.h"
#include "base/invoke_queued.h"
namespace base {
namespace {
constexpr auto kShortcutLimit = 4;
std::mutex GlobalMutex;
std::vector<not_null<GlobalShortcutManagerGeneric*>> Managers;
[[nodiscard]] GlobalShortcut MakeShortcut(
std::vector<GlobalShortcutKeyGeneric> descriptors) {
return std::make_shared<GlobalShortcutValueGeneric>(
std::move(descriptors));
}
[[nodiscard]] bool Matches(
const std::vector<GlobalShortcutKeyGeneric> &sorted,
const flat_set<GlobalShortcutKeyGeneric> &down) {
if (sorted.size() > down.size()) {
return false;
}
auto j = begin(down);
for (const auto descriptor : sorted) {
while (true) {
if (*j > descriptor) {
return false;
} else if (*j < descriptor) {
++j;
if (j == end(down)) {
return false;
}
} else {
break;
}
}
}
return true;
}
void ScheduleForAll(GlobalShortcutKeyGeneric descriptor, bool down) {
std::unique_lock lock{ GlobalMutex };
for (const auto manager : Managers) {
manager->schedule(descriptor, down);
}
}
} // namespace
std::unique_ptr<GlobalShortcutManager> CreateGlobalShortcutManager() {
return std::make_unique<GlobalShortcutManagerGeneric>();
}
bool GlobalShortcutsAvailable() {
return Platform::GlobalShortcuts::Available();
}
bool GlobalShortcutsAllowed() {
return Platform::GlobalShortcuts::Allowed();
}
GlobalShortcutValueGeneric::GlobalShortcutValueGeneric(
std::vector<GlobalShortcutKeyGeneric> descriptors)
: _descriptors(std::move(descriptors)) {
Expects(!_descriptors.empty());
}
QString GlobalShortcutValueGeneric::toDisplayString() {
auto result = QStringList();
result.reserve(_descriptors.size());
for (const auto descriptor : _descriptors) {
result.push_back(Platform::GlobalShortcuts::KeyName(descriptor));
}
return result.join(" + ");
}
QByteArray GlobalShortcutValueGeneric::serialize() {
static_assert(sizeof(GlobalShortcutKeyGeneric) == sizeof(uint64));
const auto size = sizeof(GlobalShortcutKeyGeneric) * _descriptors.size();
auto result = QByteArray(size, Qt::Uninitialized);
memcpy(result.data(), _descriptors.data(), size);
return result;
}
GlobalShortcutManagerGeneric::GlobalShortcutManagerGeneric() {
std::unique_lock lock{ GlobalMutex };
const auto start = Managers.empty();
Managers.push_back(this);
lock.unlock();
if (start) {
Platform::GlobalShortcuts::Start(ScheduleForAll);
}
}
GlobalShortcutManagerGeneric::~GlobalShortcutManagerGeneric() {
std::unique_lock lock{ GlobalMutex };
Managers.erase(ranges::remove(Managers, not_null{ this }), end(Managers));
const auto stop = Managers.empty();
lock.unlock();
if (stop) {
Platform::GlobalShortcuts::Stop();
}
}
void GlobalShortcutManagerGeneric::startRecording(
Fn<void(GlobalShortcut)> progress,
Fn<void(GlobalShortcut)> done) {
Expects(done != nullptr);
_recordingDown.clear();
_recordingUp.clear();
_recording = true;
_recordingProgress = std::move(progress);
_recordingDone = std::move(done);
}
void GlobalShortcutManagerGeneric::stopRecording() {
_recordingDown.clear();
_recordingUp.clear();
_recording = false;
_recordingDone = nullptr;
_recordingProgress = nullptr;
}
void GlobalShortcutManagerGeneric::startWatching(
GlobalShortcut shortcut,
Fn<void(bool pressed)> callback) {
Expects(shortcut != nullptr);
Expects(callback != nullptr);
const auto i = ranges::find(_watchlist, shortcut, &Watch::shortcut);
if (i != end(_watchlist)) {
i->callback = std::move(callback);
} else {
auto sorted = static_cast<GlobalShortcutValueGeneric*>(
shortcut.get())->descriptors();
std::sort(begin(sorted), end(sorted));
_watchlist.push_back(Watch{
std::move(shortcut),
std::move(sorted),
std::move(callback)
});
}
}
void GlobalShortcutManagerGeneric::stopWatching(GlobalShortcut shortcut) {
const auto i = ranges::find(_watchlist, shortcut, &Watch::shortcut);
if (i != end(_watchlist)) {
_watchlist.erase(i);
}
_pressed.erase(ranges::find(_pressed, shortcut), end(_pressed));
}
GlobalShortcut GlobalShortcutManagerGeneric::shortcutFromSerialized(
QByteArray serialized) {
const auto single = sizeof(GlobalShortcutKeyGeneric);
if (serialized.isEmpty() || serialized.size() % single) {
return nullptr;
}
auto count = serialized.size() / single;
auto list = std::vector<GlobalShortcutKeyGeneric>(count);
memcpy(list.data(), serialized.constData(), serialized.size());
return MakeShortcut(std::move(list));
}
void GlobalShortcutManagerGeneric::schedule(
GlobalShortcutKeyGeneric descriptor,
bool down) {
InvokeQueued(this, [=] { process(descriptor, down); });
}
void GlobalShortcutManagerGeneric::process(
GlobalShortcutKeyGeneric descriptor,
bool down) {
if (!down) {
_down.remove(descriptor);
}
if (_recording) {
processRecording(descriptor, down);
return;
}
auto scheduled = std::vector<Fn<void(bool pressed)>>();
if (down) {
_down.emplace(descriptor);
for (const auto &watch : _watchlist) {
if (watch.sorted.size() > _down.size()
|| ranges::contains(_pressed, watch.shortcut)) {
continue;
} else if (Matches(watch.sorted, _down)) {
_pressed.push_back(watch.shortcut);
scheduled.push_back(watch.callback);
}
}
} else {
_down.remove(descriptor);
for (auto i = begin(_pressed); i != end(_pressed);) {
const auto generic = static_cast<GlobalShortcutValueGeneric*>(
i->get());
if (!ranges::contains(generic->descriptors(), descriptor)) {
++i;
} else {
const auto j = ranges::find(
_watchlist,
*i,
&Watch::shortcut);
Assert(j != end(_watchlist));
scheduled.push_back(j->callback);
i = _pressed.erase(i);
}
}
}
for (const auto &callback : scheduled) {
callback(down);
}
}
void GlobalShortcutManagerGeneric::processRecording(
GlobalShortcutKeyGeneric descriptor,
bool down) {
if (down) {
processRecordingPress(descriptor);
} else {
processRecordingRelease(descriptor);
}
}
void GlobalShortcutManagerGeneric::processRecordingPress(
GlobalShortcutKeyGeneric descriptor) {
auto changed = false;
_recordingUp.remove(descriptor);
for (const auto descriptor : _recordingUp) {
const auto i = ranges::remove(_recordingDown, descriptor);
if (i != end(_recordingDown)) {
_recordingDown.erase(i, end(_recordingDown));
changed = true;
}
}
_recordingUp.clear();
const auto i = std::find(
begin(_recordingDown),
end(_recordingDown),
descriptor);
if (i == _recordingDown.end()) {
_recordingDown.push_back(descriptor);
changed = true;
}
if (!changed) {
return;
} else if (_recordingDown.size() == kShortcutLimit) {
finishRecording();
} else if (const auto onstack = _recordingProgress) {
onstack(MakeShortcut(_recordingDown));
}
}
void GlobalShortcutManagerGeneric::processRecordingRelease(
GlobalShortcutKeyGeneric descriptor) {
const auto i = std::find(
begin(_recordingDown),
end(_recordingDown),
descriptor);
if (i == end(_recordingDown)) {
return;
}
_recordingUp.emplace(descriptor);
Assert(_recordingUp.size() <= _recordingDown.size());
if (_recordingUp.size() == _recordingDown.size()) {
// All keys are up, we got the shortcut.
// Some down keys are not up yet.
finishRecording();
}
}
void GlobalShortcutManagerGeneric::finishRecording() {
Expects(!_recordingDown.empty());
auto result = MakeShortcut(std::move(_recordingDown));
_recordingDown.clear();
_recordingUp.clear();
_recording = false;
const auto done = _recordingDone;
_recordingDone = nullptr;
_recordingProgress = nullptr;
done(std::move(result));
}
} // namespace base

View File

@@ -0,0 +1,77 @@
// 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"
namespace base {
using GlobalShortcutKeyGeneric = uint64;
class GlobalShortcutValueGeneric final : public GlobalShortcutValue {
public:
GlobalShortcutValueGeneric(
std::vector<GlobalShortcutKeyGeneric> descriptors);
QString toDisplayString() override;
QByteArray serialize() override;
const std::vector<GlobalShortcutKeyGeneric> &descriptors() const {
return _descriptors;
}
private:
std::vector<GlobalShortcutKeyGeneric> _descriptors;
};
class GlobalShortcutManagerGeneric final
: public GlobalShortcutManager
, public QObject {
public:
GlobalShortcutManagerGeneric();
~GlobalShortcutManagerGeneric();
void startRecording(
Fn<void(GlobalShortcut)> progress,
Fn<void(GlobalShortcut)> done) override;
void stopRecording() override;
void startWatching(
GlobalShortcut shortcut,
Fn<void(bool pressed)> callback) override;
void stopWatching(GlobalShortcut shortcut) override;
GlobalShortcut shortcutFromSerialized(QByteArray serialized) override;
// Thread-safe.
void schedule(GlobalShortcutKeyGeneric descriptor, bool down);
private:
struct Watch {
GlobalShortcut shortcut;
std::vector<GlobalShortcutKeyGeneric> sorted;
Fn<void(bool pressed)> callback;
};
void process(GlobalShortcutKeyGeneric descriptor, bool down);
void processRecording(GlobalShortcutKeyGeneric descriptor, bool down);
void processRecordingPress(GlobalShortcutKeyGeneric descriptor);
void processRecordingRelease(GlobalShortcutKeyGeneric descriptor);
void finishRecording();
Fn<void(GlobalShortcut)> _recordingProgress;
Fn<void(GlobalShortcut)> _recordingDone;
std::vector<GlobalShortcutKeyGeneric> _recordingDown;
flat_set<GlobalShortcutKeyGeneric> _recordingUp;
flat_set<GlobalShortcutKeyGeneric> _down;
std::vector<Watch> _watchlist;
std::vector<GlobalShortcut> _pressed;
bool _recording = false;
};
} // namespace base

View File

@@ -0,0 +1,129 @@
// 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/assertion.h"
namespace base {
template <typename Container>
class index_based_iterator {
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = typename Container::value_type;
using difference_type = typename Container::difference_type;
using pointer = std::conditional_t<
std::is_const_v<Container>,
typename Container::const_pointer,
typename Container::pointer>;
using reference = std::conditional_t<
std::is_const_v<Container>,
typename Container::const_reference,
typename Container::reference>;
using base_type = std::conditional_t<
std::is_const_v<Container>,
typename Container::const_iterator,
typename Container::iterator>;
index_based_iterator(Container *container, base_type impl)
: _container(container)
, _index(impl - _container->begin()) {
}
reference operator*() const {
return *(_container->begin() + _index);
}
pointer operator->() const {
return std::addressof(**this);
}
index_based_iterator &operator++() {
++_index;
return *this;
}
index_based_iterator operator++(int) {
auto copy = *this;
++*this;
return copy;
}
index_based_iterator &operator--() {
--_index;
return *this;
}
index_based_iterator operator--(int) {
auto copy = *this;
--*this;
return copy;
}
index_based_iterator &operator+=(difference_type offset) {
_index += offset;
return *this;
}
index_based_iterator operator+(difference_type offset) const {
auto copy = *this;
copy += offset;
return copy;
}
index_based_iterator &operator-=(difference_type offset) {
_index -= offset;
return *this;
}
index_based_iterator operator-(difference_type offset) const {
auto copy = *this;
copy -= offset;
return copy;
}
difference_type operator-(const index_based_iterator &other) const {
return _index - other._index;
}
reference operator[](difference_type offset) const {
return *(*this + offset);
}
bool operator==(const index_based_iterator &other) const {
Expects(_container == other._container);
return _index == other._index;
}
bool operator!=(const index_based_iterator &other) const {
Expects(_container == other._container);
return _index != other._index;
}
bool operator<(const index_based_iterator &other) const {
Expects(_container == other._container);
return _index < other._index;
}
bool operator>(const index_based_iterator &other) const {
return other < *this;
}
bool operator<=(const index_based_iterator &other) const {
return !(other < *this);
}
bool operator>=(const index_based_iterator &other) const {
return !(*this < other);
}
base_type base() const {
return _container->begin() + _index;
}
private:
Container *_container = nullptr;
difference_type _index = 0;
};
template <typename Container>
index_based_iterator<Container> index_based_begin(Container &container) {
return { &container, std::begin(container) };
}
template <typename Container>
index_based_iterator<Container> index_based_end(Container &container) {
return { &container, std::end(container) };
}
} // namespace base

View File

@@ -0,0 +1,73 @@
// 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/assertion.h"
#include "base/integration.h"
#include "base/platform/base_platform_file_utilities.h"
#include "base/debug_log.h"
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
namespace base {
namespace {
Integration *IntegrationInstance = nullptr;
} // namespace
void Integration::Set(not_null<Integration*> instance) {
IntegrationInstance = instance;
}
Integration &Integration::Instance() {
Expects(IntegrationInstance != nullptr);
return *IntegrationInstance;
}
bool Integration::Exists() {
return (IntegrationInstance != nullptr);
}
Integration::Integration(int argc, char *argv[]) {
const auto path = Platform::CurrentExecutablePath(argc, argv);
if (path.isEmpty()) {
return;
}
auto info = QFileInfo(path);
if (!info.exists()) {
return;
}
info = QFileInfo(info.canonicalFilePath());
const auto dir = info.path();
_executableDir = dir.endsWith('/') ? dir : (dir + '/');
_executableName = info.fileName();
}
void Integration::logAssertionViolation(const QString &info) {
logMessage("Assertion Failed! " + info);
}
void Integration::setCrashAnnotation(
const std::string &key,
const QString &value) {
}
QString Integration::executableDir() const {
return _executableDir;
}
QString Integration::executableName() const {
return _executableName;
}
QString Integration::executablePath() const {
return _executableDir + _executableName;
}
} // namespace base

View File

@@ -0,0 +1,43 @@
// 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"
namespace base {
class Integration {
public:
static void Set(not_null<Integration*> instance);
static Integration &Instance();
static bool Exists();
Integration(int argc, char *argv[]);
virtual void enterFromEventLoop(FnMut<void()> &&method) = 0;
virtual bool logSkipDebug() = 0;
virtual void logMessageDebug(const QString &message) = 0;
virtual void logMessage(const QString &message) = 0;
virtual void logAssertionViolation(const QString &info);
virtual void setCrashAnnotation(
const std::string &key,
const QString &value);
[[nodiscard]] QString executableDir() const;
[[nodiscard]] QString executableName() const;
[[nodiscard]] QString executablePath() const;
virtual ~Integration() = default;
private:
QString _executableDir;
QString _executableName;
};
} // namespace base

View File

@@ -0,0 +1,64 @@
// 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 <QtCore/QEvent>
#include <QtCore/QCoreApplication>
#include "base/basic_types.h"
namespace base {
class InvokeQueuedEvent : public QEvent {
public:
static auto Type() {
static const auto Result = QEvent::Type(QEvent::registerEventType());
return Result;
}
explicit InvokeQueuedEvent(FnMut<void()> &&method)
: QEvent(Type())
, _method(std::move(method)) {
}
void invoke() {
_method();
}
private:
FnMut<void()> _method;
};
} // namespace base
template <typename Lambda>
inline void InvokeQueued(const QObject *context, Lambda &&lambda) {
QCoreApplication::postEvent(
const_cast<QObject*>(context),
new base::InvokeQueuedEvent(std::forward<Lambda>(lambda)));
}
class SingleQueuedInvokation : public QObject {
public:
SingleQueuedInvokation(Fn<void()> callback) : _callback(callback) {
}
void call() {
if (_pending.testAndSetAcquire(0, 1)) {
InvokeQueued(this, [this] {
if (_pending.testAndSetRelease(1, 0)) {
_callback();
}
});
}
}
private:
Fn<void()> _callback;
QAtomicInt _pending = { 0 };
};

View 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
//
#pragma once
#include <list>
#include <unordered_map>
namespace base {
template <typename Entry>
class last_used_cache {
public:
void up(Entry entry);
void remove(Entry entry);
void clear();
Entry take_lowest();
private:
std::list<Entry> _queue;
std::unordered_map<Entry, typename std::list<Entry>::iterator> _map;
};
template <typename Entry>
void last_used_cache<Entry>::up(Entry entry) {
if (!_queue.empty() && _queue.back() == entry) {
return;
}
const auto i = _map.find(entry);
if (i != end(_map)) {
_queue.splice(end(_queue), _queue, i->second);
} else {
_map.emplace(entry, _queue.insert(end(_queue), entry));
}
}
template <typename Entry>
void last_used_cache<Entry>::remove(Entry entry) {
const auto i = _map.find(entry);
if (i != end(_map)) {
_queue.erase(i->second);
_map.erase(i);
}
}
template <typename Entry>
void last_used_cache<Entry>::clear() {
_queue.clear();
_map.clear();
}
template <typename Entry>
Entry last_used_cache<Entry>::take_lowest() {
if (_queue.empty()) {
return Entry();
}
auto result = std::move(_queue.front());
_queue.erase(begin(_queue));
_map.erase(result);
return result;
}
} // namespace base

View File

@@ -0,0 +1,49 @@
// 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/last_user_input.h"
#include "base/event_filter.h"
#include "base/platform/base_platform_last_input.h"
#include <QtCore/QCoreApplication>
namespace base {
crl::time LastUserInputTime() {
Expects(QCoreApplication::instance() != nullptr);
if (const auto specific = base::Platform::LastUserInputTime()) {
return *specific;
}
static auto result = crl::time(0);
static const auto isInputEvent = [](not_null<QEvent*> e) {
switch (e->type()) {
case QEvent::MouseMove:
case QEvent::KeyPress:
case QEvent::MouseButtonPress:
case QEvent::TouchBegin:
case QEvent::Wheel: return true;
}
return false;
};
static const auto updateResult = [](not_null<QEvent*> e) {
if (isInputEvent(e)) {
result = crl::now();
}
return base::EventFilterResult::Continue;
};
[[maybe_unused]] static const auto watcher = base::install_event_filter(
QCoreApplication::instance(),
updateResult);
return result;
}
crl::time SinceLastUserInput() {
return crl::now() - LastUserInputTime();
}
} // namespace base

View File

@@ -0,0 +1,14 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
namespace base {
[[nodiscard]] crl::time LastUserInputTime();
[[nodiscard]] crl::time SinceLastUserInput();
} // namespace base

View 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 <rpl/details/callable.h>
namespace base {
template <typename Data, typename Method, typename ...Methods>
inline decltype(auto) match_method(
Data &&data,
Method &&method,
Methods &&...methods) {
using namespace rpl::details;
if constexpr (is_callable_plain_v<Method, Data&&>) {
return std::forward<Method>(method)(std::forward<Data>(data));
} else {
return match_method(
std::forward<Data>(data),
std::forward<Methods>(methods)...);
}
}
template <
typename Data1,
typename Data2,
typename Method,
typename ...Methods>
inline decltype(auto) match_method2(
Data1 &&data1,
Data2 &&data2,
Method &&method,
Methods &&...methods) {
using namespace rpl::details;
if constexpr (is_callable_plain_v<Method, Data1&&, Data2&&>) {
return std::forward<Method>(method)(
std::forward<Data1>(data1),
std::forward<Data2>(data2));
} else {
return match_method2(
std::forward<Data1>(data1),
std::forward<Data2>(data2),
std::forward<Methods>(methods)...);
}
}
} // namespace base

View File

@@ -0,0 +1,112 @@
// 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/network_reachability.h"
#include "base/platform/base_platform_network_reachability.h"
#include "base/qt_signal_producer.h"
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
#include <QtNetwork/QNetworkInformation>
#elif QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // Qt >= 6.2.0
#include <QtNetwork/QNetworkConfigurationManager>
#endif // Qt >= 6.2.0 || Qt < 6.0.0
namespace base {
namespace {
std::weak_ptr<NetworkReachability> GlobalNetworkReachability;
} // namespace
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#elif defined __clang__ // __GNUC__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#elif defined _MSC_VER // __GNUC__ || __clang__
#pragma warning(push)
#pragma warning(disable:4996)
#endif // __GNUC__ || __clang__ || _MSC_VER
struct NetworkReachability::Private {
Private() : platformHelper(Platform::NetworkReachability::Create()) {
}
rpl::variable<bool> available = true;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QNetworkConfigurationManager configurationManager;
#endif // Qt < 6.0.0
std::unique_ptr<Platform::NetworkReachability> platformHelper;
rpl::lifetime lifetime;
};
NetworkReachability::NetworkReachability()
: _private(std::make_unique<Private>()) {
if (_private->platformHelper) {
_private->platformHelper->availableValue(
) | rpl::on_next([=](bool available) {
_private->available = available;
}, _private->lifetime);
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
} else if (QNetworkInformation::load(
QNetworkInformation::Feature::Reachability)) {
_private->available = QNetworkInformation::instance()->reachability()
== QNetworkInformation::Reachability::Online;
base::qt_signal_producer(
QNetworkInformation::instance(),
&QNetworkInformation::reachabilityChanged
) | rpl::on_next([=](
QNetworkInformation::Reachability newReachability) {
_private->available = newReachability
== QNetworkInformation::Reachability::Online;
}, _private->lifetime);
#elif QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // Qt >= 6.2.0
} else {
_private->available = _private->configurationManager.isOnline();
QObject::connect(
&_private->configurationManager,
&QNetworkConfigurationManager::onlineStateChanged,
[=](bool isOnline) {
_private->available = isOnline;
});
#endif // Qt >= 6.2.0 || Qt < 6.0.0
}
}
NetworkReachability::~NetworkReachability() {
}
std::shared_ptr<NetworkReachability> NetworkReachability::Instance() {
auto result = GlobalNetworkReachability.lock();
if (!result) {
GlobalNetworkReachability = result = std::make_shared<NetworkReachability>();
}
return result;
}
bool NetworkReachability::available() const {
return _private->available.current();
}
rpl::producer<bool> NetworkReachability::availableChanges() const {
return _private->available.changes();
}
rpl::producer<bool> NetworkReachability::availableValue() const {
return _private->available.value();
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#elif defined __clang__ // __GNUC__
#pragma clang diagnostic pop
#elif defined _MSC_VER // __GNUC__ || __clang__
#pragma warning(pop)
#endif // __GNUC__ || __clang__ || _MSC_VER
} // namespace base

View File

@@ -0,0 +1,27 @@
// 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 {
class NetworkReachability final {
public:
NetworkReachability();
~NetworkReachability();
[[nodiscard]] static std::shared_ptr<NetworkReachability> Instance();
[[nodiscard]] bool available() const;
[[nodiscard]] rpl::producer<bool> availableChanges() const;
[[nodiscard]] rpl::producer<bool> availableValue() const;
private:
struct Private;
const std::unique_ptr<Private> _private;
};
} // namespace base

View File

@@ -0,0 +1,62 @@
// 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/assertion.h"
namespace base {
// This pointer is used for global non-POD variables that are allocated
// on demand by createIfNull(lambda) and are never automatically freed.
template <typename T>
class NeverFreedPointer {
public:
NeverFreedPointer() = default;
NeverFreedPointer(const NeverFreedPointer<T> &other) = delete;
NeverFreedPointer &operator=(const NeverFreedPointer<T> &other) = delete;
template <typename... Args>
void createIfNull(Args&&... args) {
if (isNull()) {
reset(new T(std::forward<Args>(args)...));
}
};
T *data() const {
return _p;
}
T *release() {
return base::take(_p);
}
void reset(T *p = nullptr) {
delete _p;
_p = p;
}
bool isNull() const {
return data() == nullptr;
}
void clear() {
reset();
}
T *operator->() const {
return data();
}
T &operator*() const {
Assert(!isNull());
return *data();
}
explicit operator bool() const {
return !isNull();
}
private:
T *_p;
};
} // namespace base

View File

@@ -0,0 +1,124 @@
// 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 <QtCore/QPointer>
// Smart pointer for QObject*, has move semantics, destroys object if it doesn't have a parent.
template <typename Object>
class object_ptr {
public:
object_ptr(std::nullptr_t) noexcept {
}
// No default constructor, but constructors with at least
// one argument are simply make functions.
template <typename Parent, typename... Args>
explicit object_ptr(Parent &&parent, Args&&... args)
: _object(new Object(std::forward<Parent>(parent), std::forward<Args>(args)...)) {
}
static object_ptr<Object> fromRaw(Object *value) noexcept {
object_ptr<Object> result = { nullptr };
result._object = value;
return result;
}
Object *release() noexcept {
return static_cast<Object*>(base::take(_object).data());
}
object_ptr(const object_ptr &other) = delete;
object_ptr &operator=(const object_ptr &other) = delete;
object_ptr(object_ptr &&other) noexcept : _object(base::take(other._object)) {
}
object_ptr &operator=(object_ptr &&other) noexcept {
auto temp = std::move(other);
destroy();
std::swap(_object, temp._object);
return *this;
}
template <
typename OtherObject,
typename = std::enable_if_t<
std::is_base_of_v<Object, OtherObject>>>
object_ptr(object_ptr<OtherObject> &&other) noexcept
: _object(base::take(other._object)) {
}
template <
typename OtherObject,
typename = std::enable_if_t<
std::is_base_of_v<Object, OtherObject>>>
object_ptr &operator=(object_ptr<OtherObject> &&other) noexcept {
_object = base::take(other._object);
return *this;
}
object_ptr &operator=(std::nullptr_t) noexcept {
_object = nullptr;
return *this;
}
Object *get() const noexcept {
return static_cast<Object*>(_object.data());
}
// So we can pass this pointer to methods like connect().
Object *data() const noexcept {
return get();
}
operator Object*() const noexcept {
return get();
}
explicit operator bool() const noexcept {
return _object.data() != nullptr;
}
Object *operator->() const noexcept {
return get();
}
Object &operator*() const noexcept {
return *get();
}
// Use that instead "= new Object(parent, ...)"
template <typename Parent, typename... Args>
Object *create(Parent &&parent, Args&&... args) {
destroy();
_object = new Object(
std::forward<Parent>(parent),
std::forward<Args>(args)...);
return get();
}
void destroy() noexcept {
delete base::take(_object);
}
void destroyDelayed() {
if (_object) {
if (auto widget = base::up_cast<QWidget*>(get())) {
widget->hide();
}
base::take(_object)->deleteLater();
}
}
~object_ptr() noexcept {
if (auto pointer = _object) {
if (!pointer->parent()) {
destroy();
}
}
}
private:
template <typename OtherObject>
friend class object_ptr;
QPointer<QObject> _object;
};

View File

@@ -0,0 +1,629 @@
// 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/bytes.h"
#include "base/algorithm.h"
#include "base/assertion.h"
#include "base/basic_types.h"
extern "C" {
#include <openssl/bn.h>
#include <openssl/sha.h>
#include <openssl/aes.h>
#include <openssl/modes.h>
#include <openssl/crypto.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
} // extern "C"
#ifdef small
#undef small
#endif // small
namespace openssl {
class Context {
public:
Context() : _data(BN_CTX_new()) {
}
Context(const Context &other) = delete;
Context(Context &&other) : _data(base::take(other._data)) {
}
Context &operator=(const Context &other) = delete;
Context &operator=(Context &&other) {
_data = base::take(other._data);
return *this;
}
~Context() {
if (_data) {
BN_CTX_free(_data);
}
}
BN_CTX *raw() const {
return _data;
}
private:
BN_CTX *_data = nullptr;
};
class BigNum {
public:
BigNum() = default;
BigNum(const BigNum &other)
: _data((other.failed() || other.isZero())
? nullptr
: BN_dup(other.raw()))
, _failed(other._failed) {
}
BigNum(BigNum &&other)
: _data(std::exchange(other._data, nullptr))
, _failed(std::exchange(other._failed, false)) {
}
BigNum &operator=(const BigNum &other) {
if (other.failed()) {
_failed = true;
} else if (other.isZero()) {
clear();
_failed = false;
} else if (!_data) {
_data = BN_dup(other.raw());
_failed = false;
} else {
_failed = !BN_copy(raw(), other.raw());
}
return *this;
}
BigNum &operator=(BigNum &&other) {
std::swap(_data, other._data);
std::swap(_failed, other._failed);
return *this;
}
~BigNum() {
clear();
}
explicit BigNum(unsigned int word) : BigNum() {
setWord(word);
}
explicit BigNum(bytes::const_span bytes) : BigNum() {
setBytes(bytes);
}
BigNum &setWord(unsigned int word) {
if (!word) {
clear();
_failed = false;
} else {
_failed = !BN_set_word(raw(), word);
}
return *this;
}
BigNum &setBytes(bytes::const_span bytes) {
if (bytes.empty()) {
clear();
_failed = false;
} else {
_failed = !BN_bin2bn(
reinterpret_cast<const unsigned char*>(bytes.data()),
bytes.size(),
raw());
}
return *this;
}
BigNum &setAdd(const BigNum &a, const BigNum &b) {
if (a.failed() || b.failed()) {
_failed = true;
} else {
_failed = !BN_add(raw(), a.raw(), b.raw());
}
return *this;
}
BigNum &setSub(const BigNum &a, const BigNum &b) {
if (a.failed() || b.failed()) {
_failed = true;
} else {
_failed = !BN_sub(raw(), a.raw(), b.raw());
}
return *this;
}
BigNum &setMul(
const BigNum &a,
const BigNum &b,
const Context &context = Context()) {
if (a.failed() || b.failed()) {
_failed = true;
} else {
_failed = !BN_mul(raw(), a.raw(), b.raw(), context.raw());
}
return *this;
}
BigNum &setModAdd(
const BigNum &a,
const BigNum &b,
const BigNum &m,
const Context &context = Context()) {
if (a.failed() || b.failed() || m.failed()) {
_failed = true;
} else if (a.isNegative() || b.isNegative() || m.isNegative()) {
_failed = true;
} else if (!BN_mod_add(raw(), a.raw(), b.raw(), m.raw(), context.raw())) {
_failed = true;
} else if (isNegative()) {
_failed = true;
} else {
_failed = false;
}
return *this;
}
BigNum &setModSub(
const BigNum &a,
const BigNum &b,
const BigNum &m,
const Context &context = Context()) {
if (a.failed() || b.failed() || m.failed()) {
_failed = true;
} else if (a.isNegative() || b.isNegative() || m.isNegative()) {
_failed = true;
} else if (!BN_mod_sub(raw(), a.raw(), b.raw(), m.raw(), context.raw())) {
_failed = true;
} else if (isNegative()) {
_failed = true;
} else {
_failed = false;
}
return *this;
}
BigNum &setModMul(
const BigNum &a,
const BigNum &b,
const BigNum &m,
const Context &context = Context()) {
if (a.failed() || b.failed() || m.failed()) {
_failed = true;
} else if (a.isNegative() || b.isNegative() || m.isNegative()) {
_failed = true;
} else if (!BN_mod_mul(raw(), a.raw(), b.raw(), m.raw(), context.raw())) {
_failed = true;
} else if (isNegative()) {
_failed = true;
} else {
_failed = false;
}
return *this;
}
BigNum &setModInverse(
const BigNum &a,
const BigNum &m,
const Context &context = Context()) {
if (a.failed() || m.failed()) {
_failed = true;
} else if (a.isNegative() || m.isNegative()) {
_failed = true;
} else if (!BN_mod_inverse(raw(), a.raw(), m.raw(), context.raw())) {
_failed = true;
} else if (isNegative()) {
_failed = true;
} else {
_failed = false;
}
return *this;
}
BigNum &setModExp(
const BigNum &base,
const BigNum &power,
const BigNum &m,
const Context &context = Context()) {
if (base.failed() || power.failed() || m.failed()) {
_failed = true;
} else if (base.isNegative() || power.isNegative() || m.isNegative()) {
_failed = true;
} else if (!BN_mod_exp(raw(), base.raw(), power.raw(), m.raw(), context.raw())) {
_failed = true;
} else if (isNegative()) {
_failed = true;
} else {
_failed = false;
}
return *this;
}
BigNum &setGcd(
const BigNum &a,
const BigNum &b,
const Context &context = Context()) {
if (a.failed() || b.failed()) {
_failed = true;
} else if (a.isNegative() || b.isNegative()) {
_failed = true;
} else if (!BN_gcd(raw(), a.raw(), b.raw(), context.raw())) {
_failed = true;
} else if (isNegative()) {
_failed = true;
} else {
_failed = false;
}
return *this;
}
[[nodiscard]] bool isZero() const {
return !failed() && (!_data || BN_is_zero(raw()));
}
[[nodiscard]] bool isOne() const {
return !failed() && _data && BN_is_one(raw());
}
[[nodiscard]] bool isNegative() const {
return !failed() && _data && BN_is_negative(raw());
}
[[nodiscard]] bool isPrime(const Context &context = Context()) const {
if (failed() || !_data) {
return false;
}
constexpr auto kMillerRabinIterationCount = 64;
const auto result = BN_is_prime_ex(
raw(),
kMillerRabinIterationCount,
context.raw(),
nullptr);
if (result == 1) {
return true;
} else if (result != 0) {
_failed = true;
}
return false;
}
BigNum &subWord(unsigned int word) {
if (failed()) {
return *this;
} else if (!BN_sub_word(raw(), word)) {
_failed = true;
}
return *this;
}
BigNum &divWord(BN_ULONG word, BN_ULONG *mod = nullptr) {
Expects(word != 0);
const auto result = failed()
? (BN_ULONG)-1
: BN_div_word(raw(), word);
if (result == (BN_ULONG)-1) {
_failed = true;
}
if (mod) {
*mod = result;
}
return *this;
}
[[nodiscard]] BN_ULONG countModWord(BN_ULONG word) const {
Expects(word != 0);
return failed() ? (BN_ULONG)-1 : BN_mod_word(raw(), word);
}
[[nodiscard]] int bitsSize() const {
return failed() ? 0 : BN_num_bits(raw());
}
[[nodiscard]] int bytesSize() const {
return failed() ? 0 : BN_num_bytes(raw());
}
[[nodiscard]] bytes::vector getBytes() const {
if (failed()) {
return {};
}
auto length = BN_num_bytes(raw());
auto result = bytes::vector(length);
auto resultSize = BN_bn2bin(
raw(),
reinterpret_cast<unsigned char*>(result.data()));
Assert(resultSize == length);
return result;
}
[[nodiscard]] BIGNUM *raw() {
if (!_data) _data = BN_new();
return _data;
}
[[nodiscard]] const BIGNUM *raw() const {
if (!_data) _data = BN_new();
return _data;
}
[[nodiscard]] BIGNUM *takeRaw() {
return _failed
? nullptr
: _data
? std::exchange(_data, nullptr)
: BN_new();
}
[[nodiscard]] bool failed() const {
return _failed;
}
[[nodiscard]] static BigNum Add(const BigNum &a, const BigNum &b) {
return BigNum().setAdd(a, b);
}
[[nodiscard]] static BigNum Sub(const BigNum &a, const BigNum &b) {
return BigNum().setSub(a, b);
}
[[nodiscard]] static BigNum Mul(
const BigNum &a,
const BigNum &b,
const Context &context = Context()) {
return BigNum().setMul(a, b, context);
}
[[nodiscard]] static BigNum ModAdd(
const BigNum &a,
const BigNum &b,
const BigNum &mod,
const Context &context = Context()) {
return BigNum().setModAdd(a, b, mod, context);
}
[[nodiscard]] static BigNum ModSub(
const BigNum &a,
const BigNum &b,
const BigNum &mod,
const Context &context = Context()) {
return BigNum().setModSub(a, b, mod, context);
}
[[nodiscard]] static BigNum ModMul(
const BigNum &a,
const BigNum &b,
const BigNum &mod,
const Context &context = Context()) {
return BigNum().setModMul(a, b, mod, context);
}
[[nodiscard]] static BigNum ModInverse(
const BigNum &a,
const BigNum &mod,
const Context &context = Context()) {
return BigNum().setModInverse(a, mod, context);
}
[[nodiscard]] static BigNum ModExp(
const BigNum &base,
const BigNum &power,
const BigNum &mod,
const Context &context = Context()) {
return BigNum().setModExp(base, power, mod, context);
}
[[nodiscard]] static int Compare(const BigNum &a, const BigNum &b) {
return a.failed() ? -1 : b.failed() ? 1 : BN_cmp(a.raw(), b.raw());
}
static void Div(
BigNum *dv,
BigNum *rem,
const BigNum &a,
const BigNum &b,
const Context &context = Context()) {
if (!dv && !rem) {
return;
} else if (a.failed()
|| b.failed()
|| !BN_div(
dv ? dv->raw() : nullptr,
rem ? rem->raw() : nullptr,
a.raw(),
b.raw(),
context.raw())) {
if (dv) {
dv->_failed = true;
}
if (rem) {
rem->_failed = true;
}
} else {
if (dv) {
dv->_failed = false;
}
if (rem) {
rem->_failed = false;
}
}
}
[[nodiscard]] static BigNum Failed() {
auto result = BigNum();
result._failed = true;
return result;
}
private:
void clear() {
BN_clear_free(std::exchange(_data, nullptr));
}
mutable BIGNUM *_data = nullptr;
mutable bool _failed = false;
};
namespace details {
template <typename Context, typename Method, typename Arg>
inline void ShaUpdate(Context context, Method method, Arg &&arg) {
const auto span = bytes::make_span(arg);
method(context, span.data(), span.size());
}
template <typename Context, typename Method, typename Arg, typename ...Args>
inline void ShaUpdate(Context context, Method method, Arg &&arg, Args &&...args) {
const auto span = bytes::make_span(arg);
method(context, span.data(), span.size());
ShaUpdate(context, method, args...);
}
template <size_type Size, typename Method>
inline void Sha(
bytes::span dst,
Method method,
bytes::const_span data) {
Expects(dst.size() >= Size);
method(
reinterpret_cast<const unsigned char*>(data.data()),
data.size(),
reinterpret_cast<unsigned char*>(dst.data()));
}
template <size_type Size, typename Method>
[[nodiscard]] inline bytes::vector Sha(
Method method,
bytes::const_span data) {
auto bytes = bytes::vector(Size);
Sha<Size>(bytes, method, data);
return bytes;
}
template <
size_type Size,
typename Context,
typename Init,
typename Update,
typename Finalize,
typename ...Args,
typename = std::enable_if_t<(sizeof...(Args) > 1)>>
[[nodiscard]] bytes::vector Sha(
Context context,
Init init,
Update update,
Finalize finalize,
Args &&...args) {
auto bytes = bytes::vector(Size);
init(&context);
ShaUpdate(&context, update, args...);
finalize(reinterpret_cast<unsigned char*>(bytes.data()), &context);
return bytes;
}
template <
size_type Size,
typename Evp>
[[nodiscard]] bytes::vector Pbkdf2(
bytes::const_span password,
bytes::const_span salt,
int iterations,
Evp evp) {
auto result = bytes::vector(Size);
PKCS5_PBKDF2_HMAC(
reinterpret_cast<const char*>(password.data()),
password.size(),
reinterpret_cast<const unsigned char*>(salt.data()),
salt.size(),
iterations,
evp,
result.size(),
reinterpret_cast<unsigned char*>(result.data()));
return result;
}
} // namespace details
constexpr auto kSha1Size = size_type(SHA_DIGEST_LENGTH);
constexpr auto kSha256Size = size_type(SHA256_DIGEST_LENGTH);
constexpr auto kSha512Size = size_type(SHA512_DIGEST_LENGTH);
[[nodiscard]] inline bytes::vector Sha1(bytes::const_span data) {
return details::Sha<kSha1Size>(SHA1, data);
}
inline void Sha1To(bytes::span dst, bytes::const_span data) {
details::Sha<kSha1Size>(dst, SHA1, data);
}
template <
typename ...Args,
typename = std::enable_if_t<(sizeof...(Args) > 1)>>
[[nodiscard]] inline bytes::vector Sha1(Args &&...args) {
return details::Sha<kSha1Size>(
SHA_CTX(),
SHA1_Init,
SHA1_Update,
SHA1_Final,
args...);
}
[[nodiscard]] inline bytes::vector Sha256(bytes::const_span data) {
return details::Sha<kSha256Size>(SHA256, data);
}
inline void Sha256To(bytes::span dst, bytes::const_span data) {
details::Sha<kSha256Size>(dst, SHA256, data);
}
template <
typename ...Args,
typename = std::enable_if_t<(sizeof...(Args) > 1)>>
[[nodiscard]] inline bytes::vector Sha256(Args &&...args) {
return details::Sha<kSha256Size>(
SHA256_CTX(),
SHA256_Init,
SHA256_Update,
SHA256_Final,
args...);
}
[[nodiscard]] inline bytes::vector Sha512(bytes::const_span data) {
return details::Sha<kSha512Size>(SHA512, data);
}
inline void Sha512To(bytes::span dst, bytes::const_span data) {
details::Sha<kSha512Size>(dst, SHA512, data);
}
template <
typename ...Args,
typename = std::enable_if_t<(sizeof...(Args) > 1)>>
[[nodiscard]] inline bytes::vector Sha512(Args &&...args) {
return details::Sha<kSha512Size>(
SHA512_CTX(),
SHA512_Init,
SHA512_Update,
SHA512_Final,
args...);
}
inline bytes::vector Pbkdf2Sha512(
bytes::const_span password,
bytes::const_span salt,
int iterations) {
return details::Pbkdf2<kSha512Size>(
password,
salt,
iterations,
EVP_sha512());
}
inline bytes::vector HmacSha256(
bytes::const_span key,
bytes::const_span data) {
auto result = bytes::vector(kSha256Size);
auto length = (unsigned int)kSha256Size;
HMAC(
EVP_sha256(),
key.data(),
key.size(),
reinterpret_cast<const unsigned char*>(data.data()),
data.size(),
reinterpret_cast<unsigned char*>(result.data()),
&length);
return result;
}
} // namespace openssl

View File

@@ -0,0 +1,57 @@
// 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 <optional>
namespace base {
template <typename Type>
struct optional_wrap_once {
using type = std::optional<Type>;
};
template <typename Type>
struct optional_wrap_once<std::optional<Type>> {
using type = std::optional<Type>;
};
template <typename Type>
using optional_wrap_once_t = typename optional_wrap_once<std::decay_t<Type>>::type;
template <typename Type>
struct optional_chain_result {
using type = optional_wrap_once_t<Type>;
};
template <>
struct optional_chain_result<void> {
using type = bool;
};
template <typename Type>
using optional_chain_result_t = typename optional_chain_result<Type>::type;
template <typename Type>
optional_wrap_once_t<Type> make_optional(Type &&value) {
return optional_wrap_once_t<Type> { std::forward<Type>(value) };
}
} // namespace base
template <typename Type, typename Method>
inline auto operator|(const std::optional<Type> &value, Method method)
-> base::optional_chain_result_t<decltype(method(*value))> {
if constexpr (std::is_same_v<decltype(method(*value)), void>) {
return value ? (method(*value), true) : false;
} else {
return value
? base::optional_chain_result_t<decltype(method(*value))>(
method(*value))
: std::nullopt;
}
}

View File

@@ -0,0 +1,245 @@
// 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/options.h"
#include "base/call_delayed.h"
#include "base/variant.h"
#include "base/debug_log.h"
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonValue>
#include <QtCore/QFile>
namespace base::options {
namespace details {
namespace {
constexpr auto kSaveDelay = crl::time(1000);
bool WriteScheduled/* = false*/;
struct Compare {
bool operator()(const char *a, const char *b) const noexcept {
return strcmp(a, b) < 0;
}
};
using MapType = base::flat_map<const char*, not_null<BasicOption*>, Compare>;
[[nodiscard]] MapType &Map() {
static auto result = MapType();
return result;
}
[[nodiscard]] QString &LocalPath() {
static auto result = QString();
return result;
}
void Read(const QString &path) {
auto file = QFile(path);
if (!file.exists()) {
return;
} else if (!file.open(QIODevice::ReadOnly)) {
LOG(("Experimental: Error opening file from '%1'.").arg(path));
return;
}
auto error = QJsonParseError();
const auto parsed = QJsonDocument::fromJson(file.readAll(), &error);
if (error.error != QJsonParseError::NoError) {
LOG(("Experimental: Error parsing json from '%1': %2 (%3)"
).arg(path
).arg(error.error
).arg(error.errorString()));
return;
} else if (!parsed.isObject()) {
LOG(("Experimental: Non object in json from '%1'.").arg(path));
return;
}
auto &map = Map();
const auto values = parsed.object();
for (auto i = values.begin(); i != values.end(); ++i) {
const auto key = i.key().toLatin1() + char(0);
const auto j = map.find(key.data());
if (j == end(map)) {
LOG(("Experimental: Unknown option '%1'.").arg(i.key()));
continue;
}
const auto value = *i;
v::match(j->second->value(), [&](const auto &current) {
using T = std::remove_cvref_t<decltype(current)>;
if constexpr (std::is_same_v<T, bool>) {
if (value.isBool()) {
j->second->set(value.toBool());
return;
}
} else if constexpr (std::is_same_v<T, int>) {
if (value.isDouble()) {
j->second->set(value.toInt());
return;
}
} else if constexpr (std::is_same_v<T, QString>) {
if (value.isString()) {
j->second->set(value.toString());
return;
}
} else {
static_assert(unsupported_type(T()));
}
LOG(("Experimental: Wrong option value type for '%1'."
).arg(i.key()));
});
}
}
void Write() {
const auto &path = LocalPath();
if (!WriteScheduled || path.isEmpty()) {
return;
}
WriteScheduled = false;
auto map = QJsonObject();
for (const auto &[name, option] : Map()) {
const auto &value = option->value();
if (value != option->defaultValue()) {
map.insert(name, v::match(value, [](const auto &current) {
using T = std::remove_cvref_t<decltype(current)>;
if constexpr (std::is_same_v<T, bool>
|| std::is_same_v<T, int>
|| std::is_same_v<T, QString>) {
return QJsonValue(current);
} else {
static_assert(unsupported_type(T()));
}
}));
}
}
if (map.isEmpty()) {
QFile(path).remove();
} else if (auto file = QFile(path); file.open(QIODevice::WriteOnly)) {
file.write(QJsonDocument(map).toJson(QJsonDocument::Indented));
} else {
LOG(("Experimental: Could not write '%1'.").arg(path));
}
}
} // namespace
BasicOption::BasicOption(
const char id[],
const char name[],
const char description[],
ValueType defaultValue,
Scope scope,
bool restartRequired)
: _value(defaultValue)
, _defaultValue(std::move(defaultValue))
, _id(QString::fromUtf8(id))
, _name(QString::fromUtf8(name))
, _description(QString::fromUtf8(description))
, _scope(scope)
, _restartRequired(restartRequired) {
const auto [i, ok] = Map().emplace(id, this);
Ensures(ok);
}
void BasicOption::set(ValueType value) {
Expects(value.index() == _value.index());
_value = std::move(value);
if (!WriteScheduled && !LocalPath().isEmpty()) {
WriteScheduled = true;
call_delayed(kSaveDelay, [] { Write(); });
}
}
const ValueType &BasicOption::value() const {
return _value;
}
const ValueType &BasicOption::defaultValue() const {
return _defaultValue;
}
const QString &BasicOption::id() const {
return _id;
}
const QString &BasicOption::name() const {
return _name;
}
const QString &BasicOption::description() const {
return _description;
}
bool BasicOption::relevant() const {
const auto scopeFn = std::get_if<ScopeFn>(&_scope);
if (scopeFn) {
return (*scopeFn)();
}
const auto scopeFlags = v::get<ScopeFlags>(_scope);
#ifdef Q_OS_WIN
return scopeFlags & windows;
#elif defined Q_OS_MAC // Q_OS_WIN
return scopeFlags & macos;
#else // Q_OS_MAC || Q_OS_WIN
return scopeFlags & linux;
#endif // Q_OS_MAC || Q_OS_WIN
}
bool BasicOption::restartRequired() const {
return _restartRequired;
}
Scope BasicOption::scope() const {
return _scope;
}
BasicOption &Lookup(const char id[]) {
const auto i = Map().find(id);
Ensures(i != end(Map()));
return *i->second;
}
} // namespace details
bool changed() {
for (const auto &[name, option] : details::Map()) {
if (option->value() != option->defaultValue()) {
return true;
}
}
return false;
}
void reset() {
for (const auto &[name, option] : details::Map()) {
if (option->value() != option->defaultValue()) {
option->set(option->defaultValue());
}
}
}
void init(const QString &path) {
Expects(details::LocalPath().isEmpty());
if (!path.isEmpty()) {
details::Read(path);
details::LocalPath() = path;
static const auto guard = gsl::finally([] {
details::Write();
});
}
}
} // namespace base::options

View File

@@ -0,0 +1,140 @@
// 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/flags.h"
#include "base/variant.h"
#include "base/required.h"
#ifdef linux // GCC, cmon..
#undef linux
#endif // linux
namespace base::options {
namespace details {
using ValueType = std::variant<bool, int, QString>;
enum class ScopeFlag : uchar {
Windows = (1 << 1),
Mac = (1 << 2),
Linux = (1 << 3),
};
inline constexpr bool is_flag_type(ScopeFlag) { return true; }
using ScopeFlags = base::flags<ScopeFlag>;
using ScopeFn = Fn<bool()>;
using Scope = std::variant<ScopeFlags, ScopeFn>;
class BasicOption {
public:
BasicOption(
const char id[],
const char name[],
const char description[],
ValueType defaultValue,
Scope scope,
bool restartRequired);
BasicOption(const BasicOption&) = delete;
BasicOption operator=(const BasicOption&) = delete;
void set(ValueType value);
[[nodiscard]] const ValueType &value() const;
[[nodiscard]] const ValueType &defaultValue() const;
[[nodiscard]] const QString &id() const;
[[nodiscard]] const QString &name() const;
[[nodiscard]] const QString &description() const;
[[nodiscard]] bool relevant() const;
[[nodiscard]] Scope scope() const;
[[nodiscard]] bool restartRequired() const;
private:
ValueType _value;
ValueType _defaultValue;
QString _id;
QString _name;
QString _description;
Scope _scope;
bool _restartRequired = false;
};
[[nodiscard]] BasicOption &Lookup(const char name[]);
} // namespace details
inline constexpr auto windows = details::ScopeFlag::Windows;
inline constexpr auto macos = details::ScopeFlag::Mac;
inline constexpr auto linux = details::ScopeFlag::Linux;
template <typename Type>
struct descriptor {
required<const char*> id;
const char *name = "";
const char *description = "";
Type defaultValue = Type();
details::Scope scope = windows | macos | linux;
bool restartRequired = false;
};
template <typename Type>
class option final : details::BasicOption {
public:
option(descriptor<Type> &&fields)
: BasicOption(
fields.id,
fields.name,
fields.description,
std::move(fields.defaultValue),
fields.scope,
fields.restartRequired) {
}
using BasicOption::id;
using BasicOption::name;
using BasicOption::description;
using BasicOption::relevant;
using BasicOption::scope;
using BasicOption::restartRequired;
void set(Type value) {
BasicOption::set(std::move(value));
}
[[nodiscard]] Type value() const {
return v::get<Type>(BasicOption::value());
}
[[nodiscard]] Type defaultValue() const {
return v::get<Type>(BasicOption::defaultValue());
}
[[nodiscard]] static option &Wrap(BasicOption &that) {
Expects(v::is<Type>(that.value()));
return static_cast<option&>(that);
}
};
using toggle = option<bool>;
template <typename Type>
[[nodiscard]] inline Type value(const char id[]) {
return v::get<Type>(details::Lookup(id).value());
}
template <typename Type>
[[nodiscard]] inline option<Type> &lookup(const char id[]) {
return option<Type>::Wrap(details::Lookup(id));
}
[[nodiscard]] bool changed();
void reset();
void init(const QString &path);
} // namespace base::options

View File

@@ -0,0 +1,152 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <QtCore/QMap>
// ordered set template based on QMap
template <typename T>
class OrderedSet {
struct NullType {
};
using Self = OrderedSet<T>;
using Impl = QMap<T, NullType>;
using IteratorImpl = typename Impl::iterator;
using ConstIteratorImpl = typename Impl::const_iterator;
Impl impl_;
public:
OrderedSet() = default;
OrderedSet(const OrderedSet &other) = default;
OrderedSet(OrderedSet &&other) = default;
OrderedSet &operator=(const OrderedSet &other) = default;
OrderedSet &operator=(OrderedSet &&other) = default;
~OrderedSet() = default;
inline bool operator==(const Self &other) const { return impl_ == other.impl_; }
inline bool operator!=(const Self &other) const { return impl_ != other.impl_; }
inline int size() const { return impl_.size(); }
inline bool isEmpty() const { return impl_.isEmpty(); }
inline void detach() { return impl_.detach(); }
inline bool isDetached() const { return impl_.isDetached(); }
inline void clear() { return impl_.clear(); }
inline QList<T> values() const { return impl_.keys(); }
inline const T &first() const { return impl_.firstKey(); }
inline const T &last() const { return impl_.lastKey(); }
class const_iterator;
class iterator {
public:
typedef typename IteratorImpl::iterator_category iterator_category;
typedef typename IteratorImpl::difference_type difference_type;
typedef T value_type;
typedef T *pointer;
typedef T &reference;
iterator() = default;
iterator(const iterator &other) = default;
iterator &operator=(const iterator &other) = default;
inline const T &operator*() const { return impl_.key(); }
inline const T *operator->() const { return &impl_.key(); }
inline bool operator==(const iterator &other) const { return impl_ == other.impl_; }
inline bool operator!=(const iterator &other) const { return impl_ != other.impl_; }
inline iterator &operator++() { ++impl_; return *this; }
inline iterator operator++(int) { return iterator(impl_++); }
inline iterator &operator--() { --impl_; return *this; }
inline iterator operator--(int) { return iterator(impl_--); }
inline iterator operator+(int j) const { return iterator(impl_ + j); }
inline iterator operator-(int j) const { return iterator(impl_ - j); }
inline iterator &operator+=(int j) { impl_ += j; return *this; }
inline iterator &operator-=(int j) { impl_ -= j; return *this; }
friend class const_iterator;
inline bool operator==(const const_iterator &other) const { return impl_ == other.impl_; }
inline bool operator!=(const const_iterator &other) const { return impl_ != other.impl_; }
private:
explicit iterator(const IteratorImpl &impl) : impl_(impl) {
}
IteratorImpl impl_;
friend class OrderedSet<T>;
};
friend class iterator;
class const_iterator {
public:
typedef typename IteratorImpl::iterator_category iterator_category;
typedef typename IteratorImpl::difference_type difference_type;
typedef T value_type;
typedef T *pointer;
typedef T &reference;
const_iterator() = default;
const_iterator(const const_iterator &other) = default;
const_iterator &operator=(const const_iterator &other) = default;
const_iterator(const iterator &other) : impl_(other.impl_) {
}
const_iterator &operator=(const iterator &other) {
impl_ = other.impl_;
return *this;
}
inline const T &operator*() const { return impl_.key(); }
inline const T *operator->() const { return &impl_.key(); }
inline bool operator==(const const_iterator &other) const { return impl_ == other.impl_; }
inline bool operator!=(const const_iterator &other) const { return impl_ != other.impl_; }
inline const_iterator &operator++() { ++impl_; return *this; }
inline const_iterator operator++(int) { return const_iterator(impl_++); }
inline const_iterator &operator--() { --impl_; return *this; }
inline const_iterator operator--(int) { return const_iterator(impl_--); }
inline const_iterator operator+(int j) const { return const_iterator(impl_ + j); }
inline const_iterator operator-(int j) const { return const_iterator(impl_ - j); }
inline const_iterator &operator+=(int j) { impl_ += j; return *this; }
inline const_iterator &operator-=(int j) { impl_ -= j; return *this; }
friend class iterator;
inline bool operator==(const iterator &other) const { return impl_ == other.impl_; }
inline bool operator!=(const iterator &other) const { return impl_ != other.impl_; }
private:
explicit const_iterator(const ConstIteratorImpl &impl) : impl_(impl) {
}
ConstIteratorImpl impl_;
friend class OrderedSet<T>;
};
friend class const_iterator;
// STL style
inline iterator begin() { return iterator(impl_.begin()); }
inline const_iterator begin() const { return const_iterator(impl_.cbegin()); }
inline const_iterator constBegin() const { return const_iterator(impl_.cbegin()); }
inline const_iterator cbegin() const { return const_iterator(impl_.cbegin()); }
inline iterator end() { detach(); return iterator(impl_.end()); }
inline const_iterator end() const { return const_iterator(impl_.cend()); }
inline const_iterator constEnd() const { return const_iterator(impl_.cend()); }
inline const_iterator cend() const { return const_iterator(impl_.cend()); }
inline iterator erase(iterator it) { return iterator(impl_.erase(it.impl_)); }
inline iterator insert(const T &value) { return iterator(impl_.insert(value, NullType())); }
inline iterator insert(const_iterator pos, const T &value) { return iterator(impl_.insert(pos.impl_, value, NullType())); }
inline int remove(const T &value) { return impl_.remove(value); }
inline bool contains(const T &value) const { return impl_.contains(value); }
// more Qt
typedef iterator Iterator;
typedef const_iterator ConstIterator;
inline int count() const { return impl_.count(); }
inline iterator find(const T &value) { return iterator(impl_.find(value)); }
inline const_iterator find(const T &value) const { return const_iterator(impl_.constFind(value)); }
inline const_iterator constFind(const T &value) const { return const_iterator(impl_.constFind(value)); }
inline Self &unite(const Self &other) { impl_.unite(other.impl_); return *this; }
// STL compatibility
typedef typename Impl::difference_type difference_type;
typedef typename Impl::size_type size_type;
inline bool empty() const { return impl_.empty(); }
};

View File

@@ -0,0 +1,88 @@
// 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 {
namespace details {
// This implementation was taken from range-v3 library.
// It was modified so that more than one of passed function objects
// could be called with an argument list and the first one that
// matches gets used instead of a compile-time ambiguity error.
//
// This allows to write "default" visitor handlers with [](auto&&) syntax.
template <typename ...Args>
constexpr bool is_callable_v = rpl::details::is_callable_plain_v<Args...>;
template <typename ...Args>
struct overloaded;
template <>
struct overloaded<> {
};
template <typename First, typename ...Rest>
struct overloaded<First, Rest...>
: private First
, private overloaded<Rest...> {
private:
using Others = overloaded<Rest...>;
public:
overloaded() = default;
constexpr overloaded(First first, Rest... rest)
: First(std::move(first))
, Others(std::move(rest)...) {
}
template <typename... Args>
auto operator()(Args&&... args)
-> decltype(std::declval<First&>()(std::forward<Args>(args)...)) {
return static_cast<First&>(*this)(std::forward<Args>(args)...);
}
template <typename... Args>
auto operator()(Args&&... args) const
-> decltype(std::declval<const First&>()(std::forward<Args>(args)...)) {
return static_cast<const First&>(*this)(std::forward<Args>(args)...);
}
template <
typename... Args,
typename = std::enable_if_t<!is_callable_v<First&, Args&&...>>>
auto operator()(Args&&... args)
-> decltype(std::declval<Others&>()(std::forward<Args>(args)...)) {
return static_cast<Others&>(*this)(std::forward<Args>(args)...);
}
template <
typename... Args,
typename = std::enable_if_t<!is_callable_v<const First&, Args&&...>>>
auto operator()(Args&&... args) const
-> decltype(std::declval<const Others&>()(std::forward<Args>(args)...)) {
return static_cast<const Others&>(*this)(std::forward<Args>(args)...);
}
};
} // namespace details
template <typename Function>
Function overload(Function &&function) {
return std::forward<Function>(function);
}
template <typename ...Functions, typename = std::enable_if_t<(sizeof...(Functions) > 1)>>
auto overload(Functions ...functions) {
return details::overloaded<Functions...>(std::move(functions)...);
}
} // namespace base

View File

@@ -0,0 +1,100 @@
// 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/parse_helper.h"
namespace base {
namespace parse {
// inspired by https://github.com/sindresorhus/strip-json-comments
QByteArray stripComments(const QByteArray &content) {
enum class InsideComment {
None,
SingleLine,
MultiLine,
};
auto insideComment = InsideComment::None;
auto insideString = false;
QByteArray result;
auto begin = content.cbegin(), end = content.cend(), offset = begin;
auto feedContent = [&result, &offset, end](const char *ch) {
if (ch > offset) {
if (result.isEmpty()) result.reserve(end - offset - 2);
result.append(offset, ch - offset);
offset = ch;
}
};
auto feedComment = [&result, &offset, end](const char *ch) {
if (ch > offset) {
if (result.isEmpty()) result.reserve(end - offset - 2);
result.append(' ');
offset = ch;
}
};
for (auto ch = offset; ch != end;) {
auto currentChar = *ch;
auto nextChar = (ch + 1 == end) ? 0 : *(ch + 1);
if (insideComment == InsideComment::None && currentChar == '"') {
auto escaped = ((ch > begin) && *(ch - 1) == '\\') && ((ch - 1 < begin) || *(ch - 2) != '\\');
if (!escaped) {
insideString = !insideString;
}
}
if (insideString) {
++ch;
continue;
}
if (insideComment == InsideComment::None && currentChar == '/' && nextChar == '/') {
feedContent(ch);
insideComment = InsideComment::SingleLine;
ch += 2;
} else if (insideComment == InsideComment::SingleLine && currentChar == '\r' && nextChar == '\n') {
feedComment(ch);
ch += 2;
insideComment = InsideComment::None;
} else if (insideComment == InsideComment::SingleLine && currentChar == '\n') {
feedComment(ch);
++ch;
insideComment = InsideComment::None;
} else if (insideComment == InsideComment::None && currentChar == '/' && nextChar == '*') {
feedContent(ch);
ch += 2;
insideComment = InsideComment::MultiLine;
} else if (insideComment == InsideComment::MultiLine && currentChar == '*' && nextChar == '/') {
ch += 2;
feedComment(ch);
insideComment = InsideComment::None;
} else if (insideComment == InsideComment::MultiLine && currentChar == '\r' && nextChar == '\n') {
feedComment(ch);
ch += 2;
feedContent(ch);
} else if (insideComment == InsideComment::MultiLine && currentChar == '\n') {
feedComment(ch);
++ch;
feedContent(ch);
} else {
++ch;
}
}
if (insideComment == InsideComment::MultiLine) {
// unexpected end of content
}
if (insideComment == InsideComment::None && end > offset) {
if (result.isEmpty()) {
return content;
} else {
result.append(offset, end - offset);
}
}
return result;
}
} // namespace parse
} // namespace base

View File

@@ -0,0 +1,41 @@
// 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 {
namespace parse {
// Strip all C-style comments.
QByteArray stripComments(const QByteArray &content);
inline bool skipWhitespaces(const char *&from, const char *end) {
Assert(from <= end);
while (from != end && (
(*from == ' ') ||
(*from == '\n') ||
(*from == '\t') ||
(*from == '\r'))) {
++from;
}
return (from != end);
}
inline QLatin1String readName(const char *&from, const char *end) {
Assert(from <= end);
auto start = from;
while (from != end && (
(*from >= 'a' && *from <= 'z') ||
(*from >= 'A' && *from <= 'Z') ||
(*from >= '0' && *from <= '9') ||
(*from == '_'))) {
++from;
}
return QLatin1String(start, from - start);
}
} // namespace parse
} // namespace base

View File

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

View File

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

View 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
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

View File

@@ -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

View File

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

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More