suckel
This commit is contained in:
parent
2825007345
commit
996ca1122d
10 changed files with 562 additions and 253 deletions
12
flake.lock
generated
12
flake.lock
generated
|
@ -2,11 +2,11 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1745998881,
|
"lastModified": 1746576598,
|
||||||
"narHash": "sha256-vonyYAKJSlsX4n9GCsS0pHxR6yCrfqBIuGvANlkwG6U=",
|
"narHash": "sha256-FshoQvr6Aor5SnORVvh/ZdJ1Sa2U4ZrIMwKBX5k2wu0=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "423d2df5b04b4ee7688c3d71396e872afa236a89",
|
"rev": "b3582c75c7f21ce0b429898980eddbbf05c68e55",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -59,11 +59,11 @@
|
||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1745929750,
|
"lastModified": 1746216483,
|
||||||
"narHash": "sha256-k5ELLpTwRP/OElcLpNaFWLNf8GRDq4/eHBmFy06gGko=",
|
"narHash": "sha256-4h3s1L/kKqt3gMDcVfN8/4v2jqHrgLIe4qok4ApH5x4=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "treefmt-nix",
|
"repo": "treefmt-nix",
|
||||||
"rev": "82bf32e541b30080d94e46af13d46da0708609ea",
|
"rev": "29ec5026372e0dec56f890e50dbe4f45930320fd",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
@ -84,6 +84,7 @@
|
||||||
xorg.libxcb
|
xorg.libxcb
|
||||||
|
|
||||||
(mkOverridden "cmake" ftxui)
|
(mkOverridden "cmake" ftxui)
|
||||||
|
(mkOverridden "cmake" pugixml)
|
||||||
(mkOverridden "cmake" sqlitecpp)
|
(mkOverridden "cmake" sqlitecpp)
|
||||||
(mkOverridden "meson" tomlplusplus)
|
(mkOverridden "meson" tomlplusplus)
|
||||||
];
|
];
|
||||||
|
@ -193,6 +194,7 @@
|
||||||
]
|
]
|
||||||
++ (with pkgsStatic; [
|
++ (with pkgsStatic; [
|
||||||
dbus
|
dbus
|
||||||
|
pugixml
|
||||||
xorg.libxcb
|
xorg.libxcb
|
||||||
wayland
|
wayland
|
||||||
]));
|
]));
|
||||||
|
|
|
@ -1454,12 +1454,11 @@ namespace argparse {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get a pointer to this argument if it has choices
|
* @brief Get a pointer to this argument if it has choices
|
||||||
* @return Result containing a pointer to this argument or an error
|
* @return Pointer to this argument or nullptr if no choices have been added
|
||||||
* @details Returns an error if no choices have been added
|
|
||||||
*/
|
*/
|
||||||
fn choices() -> Result<Argument*> {
|
fn choices() -> Argument* {
|
||||||
if (!m_choices.has_value() || m_choices.value().empty())
|
if (!m_choices.has_value() || m_choices.value().empty())
|
||||||
return Err(DracError(DracErrorCode::InvalidArgument, "Zero choices provided"));
|
return nullptr;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -1470,10 +1469,10 @@ namespace argparse {
|
||||||
* @tparam U Types of the remaining choices
|
* @tparam U Types of the remaining choices
|
||||||
* @param first The first choice value
|
* @param first The first choice value
|
||||||
* @param rest The remaining choice values
|
* @param rest The remaining choice values
|
||||||
* @return Result containing a pointer to this argument or an error
|
* @return Pointer to this argument or nullptr if no choices have been added
|
||||||
*/
|
*/
|
||||||
template <typename T, typename... U>
|
template <typename T, typename... U>
|
||||||
fn choices(T&& first, U&&... rest) -> Result<Argument*> {
|
fn choices(T&& first, U&&... rest) -> Argument* {
|
||||||
add_choice(std::forward<T>(first));
|
add_choice(std::forward<T>(first));
|
||||||
if constexpr (sizeof...(rest) == 0) {
|
if constexpr (sizeof...(rest) == 0) {
|
||||||
return choices();
|
return choices();
|
||||||
|
@ -1492,7 +1491,7 @@ namespace argparse {
|
||||||
const auto& choices = m_choices.value();
|
const auto& choices = m_choices.value();
|
||||||
|
|
||||||
if (m_default_value.has_value()) {
|
if (m_default_value.has_value()) {
|
||||||
if (choices.find(m_default_value_str.value_or("")) == choices.end()) {
|
if (!choices.contains(m_default_value_str.value_or(""))) {
|
||||||
const String choices_as_csv =
|
const String choices_as_csv =
|
||||||
std::accumulate(choices.begin(), choices.end(), String(), [](const String& a, const String& b) {
|
std::accumulate(choices.begin(), choices.end(), String(), [](const String& a, const String& b) {
|
||||||
return a + (a.empty() ? "" : ", ") + b;
|
return a + (a.empty() ? "" : ", ") + b;
|
||||||
|
@ -1696,41 +1695,28 @@ namespace argparse {
|
||||||
* - Validates default values against choices
|
* - Validates default values against choices
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] fn validate() const -> Result<> {
|
[[nodiscard]] fn validate() const -> Result<> {
|
||||||
if (m_num_args_range.get_min() > m_num_args_range.get_max()) {
|
if (m_num_args_range.get_min() > m_num_args_range.get_max())
|
||||||
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Invalid nargs range for argument '{}': min ({}) > max ({}). This indicates a configuration error when defining the argument.", m_names.empty() ? "UnnamedArgument" : m_names[0], m_num_args_range.get_min(), m_num_args_range.get_max())));
|
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Invalid nargs range for argument '{}': min ({}) > max ({}). This indicates a configuration error when defining the argument.", m_names.empty() ? "UnnamedArgument" : m_names[0], m_num_args_range.get_min(), m_num_args_range.get_max())));
|
||||||
}
|
|
||||||
|
|
||||||
if (m_is_optional) {
|
if (m_is_optional) {
|
||||||
if (!m_is_used && !m_default_value.has_value() && !m_implicit_value.has_value() && m_is_required)
|
if (!m_is_used && !m_default_value.has_value() && m_is_required)
|
||||||
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Required argument '{}' was not provided", m_names[0])));
|
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Required argument '{}' was not provided", m_names[0])));
|
||||||
|
|
||||||
if (m_is_used && m_is_required && m_values.empty())
|
if (m_is_used && m_is_required && m_values.empty())
|
||||||
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Required argument '{}' requires a value, but none was provided", m_names[0])));
|
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Required argument '{}' requires a value, but none was provided", m_names[0])));
|
||||||
|
|
||||||
if (m_is_used && m_num_args_range.get_min() > m_values.size())
|
|
||||||
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Too few arguments for optional argument '{}'. Expected at least {}, got {}.", m_names[0], m_num_args_range.get_min(), m_values.size())));
|
|
||||||
} else {
|
} else {
|
||||||
if (!m_num_args_range.contains(m_values.size()) && !m_default_value.has_value()) {
|
if (!m_num_args_range.contains(m_values.size()) && !m_default_value.has_value()) {
|
||||||
String expected_str;
|
String expected_str;
|
||||||
|
|
||||||
if (m_num_args_range.is_exact())
|
if (m_num_args_range.is_exact())
|
||||||
expected_str = std::to_string(m_num_args_range.get_min());
|
expected_str = std::to_string(m_num_args_range.get_min());
|
||||||
else if (!m_num_args_range.is_right_bounded())
|
else if (!m_num_args_range.is_right_bounded())
|
||||||
expected_str = std::format("at least {}", m_num_args_range.get_min());
|
expected_str = std::format("at least {}", m_num_args_range.get_min());
|
||||||
else
|
else
|
||||||
expected_str = std::format("{} to {}", m_num_args_range.get_min(), m_num_args_range.get_max());
|
expected_str = std::format("{} to {}", m_num_args_range.get_min(), m_num_args_range.get_max());
|
||||||
|
|
||||||
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Incorrect number of arguments for positional argument '{}'. Expected {}, got {}.", (m_metavar.empty() ? m_names[0] : m_metavar), expected_str, m_values.size())));
|
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Incorrect number of arguments for positional argument '{}'. Expected {}, got {}.", (m_metavar.empty() ? m_names[0] : m_metavar), expected_str, m_values.size())));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_num_args_range.get_min() > m_values.size())
|
if (m_num_args_range.get_max() < m_values.size())
|
||||||
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Too few arguments for positional argument '{}'. Expected at least {}, got {}.", (m_metavar.empty() ? m_names[0] : m_metavar), m_num_args_range.get_min(), m_values.size())));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_num_args_range.get_max() < m_values.size()) {
|
|
||||||
if (m_is_optional)
|
|
||||||
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Too many arguments for optional argument '{}'. Expected at most {}, got {}.", m_names[0], m_num_args_range.get_max(), m_values.size())));
|
|
||||||
|
|
||||||
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Too many arguments for positional argument '{}'. Expected at most {}, got {}.", (m_metavar.empty() ? m_names[0] : m_metavar), m_num_args_range.get_max(), m_values.size())));
|
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Too many arguments for positional argument '{}'. Expected at most {}, got {}.", (m_metavar.empty() ? m_names[0] : m_metavar), m_num_args_range.get_max(), m_values.size())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1738,25 +1724,12 @@ namespace argparse {
|
||||||
const auto& choices = m_choices.value();
|
const auto& choices = m_choices.value();
|
||||||
|
|
||||||
if (m_default_value.has_value())
|
if (m_default_value.has_value())
|
||||||
if (const String& default_val_str = m_default_value_str.value(); choices.find(default_val_str) == choices.end()) {
|
if (const String& default_val_str = m_default_value_str.value(); !choices.contains(default_val_str)) {
|
||||||
const String choices_as_csv = std::accumulate(
|
const String choices_as_csv = std::accumulate(
|
||||||
choices.begin(), choices.end(), String(), [](const String& option_a, const String& option_b) -> String { return option_a + (option_a.empty() ? "" : ", ") + option_b; }
|
choices.begin(), choices.end(), String(), [](const String& option_a, const String& option_b) -> String { return option_a + (option_a.empty() ? "" : ", ") + option_b; }
|
||||||
);
|
);
|
||||||
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Default value '{}' is not in the allowed choices: {{{}}}", default_val_str, choices_as_csv)));
|
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Default value '{}' is not in the allowed choices: {{{}}}", default_val_str, choices_as_csv)));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& value : m_values) {
|
|
||||||
if (value.index() != typeid(String).hash_code())
|
|
||||||
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Invalid argument type for choice validation - expected string, got '{}'", typeid(value).name())));
|
|
||||||
|
|
||||||
if (const String& value_str = std::get<String>(value); choices.find(value_str) == choices.end()) {
|
|
||||||
const String choices_as_csv = std::accumulate(
|
|
||||||
choices.begin(), choices.end(), String(), [](const String& option_a, const String& option_b) -> String { return std::format("{}{}{}", option_a, option_a.empty() ? "" : ", ", option_b); }
|
|
||||||
);
|
|
||||||
|
|
||||||
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Invalid argument '{}' - allowed options: {{{}}}", details::repr(value), choices_as_csv)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
@ -2144,7 +2117,7 @@ namespace argparse {
|
||||||
};
|
};
|
||||||
|
|
||||||
fn consume_digits = [=](StringView sd) -> StringView {
|
fn consume_digits = [=](StringView sd) -> StringView {
|
||||||
const auto it = std::ranges::find_if_not(sd, is_digit);
|
const char* const it = std::ranges::find_if_not(sd, is_digit);
|
||||||
|
|
||||||
return sd.substr(static_cast<usize>(it - std::begin(sd)));
|
return sd.substr(static_cast<usize>(it - std::begin(sd)));
|
||||||
};
|
};
|
||||||
|
@ -3447,13 +3420,22 @@ namespace argparse {
|
||||||
const String hypothetical_arg = { '-', compound_arg[j] };
|
const String hypothetical_arg = { '-', compound_arg[j] };
|
||||||
auto arg_map_it2 = m_argument_map.find(hypothetical_arg);
|
auto arg_map_it2 = m_argument_map.find(hypothetical_arg);
|
||||||
if (arg_map_it2 != m_argument_map.end()) {
|
if (arg_map_it2 != m_argument_map.end()) {
|
||||||
const auto argument_iter2 = arg_map_it2->second;
|
auto argument = arg_map_it2->second;
|
||||||
Result<decltype(it)> consume_result = argument_iter2->consume(it, end, arg_map_it2->first);
|
if (argument->m_num_args_range.get_max() == 0) {
|
||||||
|
// Flag: do not consume the next argument as a value
|
||||||
|
Result<decltype(it)> consume_result_flag = argument->consume(it, it, arg_map_it2->first);
|
||||||
|
if (!consume_result_flag)
|
||||||
|
return Err(consume_result_flag.error());
|
||||||
|
it = consume_result_flag.value();
|
||||||
|
} else {
|
||||||
|
// Option expects a value: consume as before
|
||||||
|
Result<decltype(it)> consume_result = argument->consume(it, end, arg_map_it2->first);
|
||||||
if (!consume_result)
|
if (!consume_result)
|
||||||
return Err(consume_result.error());
|
return Err(consume_result.error());
|
||||||
it = consume_result.value();
|
}
|
||||||
} else
|
} else {
|
||||||
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Unknown argument: {} in compound {}", hypothetical_arg, current_argument)));
|
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Unknown argument: {}", current_argument)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Unknown argument: {}", current_argument)));
|
return Err(DracError(DracErrorCode::InvalidArgument, std::format("Unknown argument: {}", current_argument)));
|
||||||
|
|
10
meson.build
10
meson.build
|
@ -42,8 +42,7 @@ common_cpp_flags = {
|
||||||
'-std=c++26',
|
'-std=c++26',
|
||||||
],
|
],
|
||||||
'msvc': [
|
'msvc': [
|
||||||
'-DNOMINMAX',
|
'-DNOMINMAX', '/MT',
|
||||||
'/MT',
|
|
||||||
'/Zc:__cplusplus',
|
'/Zc:__cplusplus',
|
||||||
'/Zc:preprocessor',
|
'/Zc:preprocessor',
|
||||||
'/external:W0',
|
'/external:W0',
|
||||||
|
@ -87,12 +86,12 @@ add_project_arguments(common_cpp_args, language : 'cpp')
|
||||||
# Files #
|
# Files #
|
||||||
# ------- #
|
# ------- #
|
||||||
base_sources = files(
|
base_sources = files(
|
||||||
'src/core/system_data.cpp',
|
|
||||||
'src/core/package.cpp',
|
|
||||||
'src/config/config.cpp',
|
'src/config/config.cpp',
|
||||||
'src/config/weather.cpp',
|
'src/config/weather.cpp',
|
||||||
'src/ui/ui.cpp',
|
'src/core/package.cpp',
|
||||||
|
'src/core/system_data.cpp',
|
||||||
'src/main.cpp',
|
'src/main.cpp',
|
||||||
|
'src/ui/ui.cpp',
|
||||||
)
|
)
|
||||||
|
|
||||||
platform_sources = {
|
platform_sources = {
|
||||||
|
@ -138,6 +137,7 @@ elif host_system == 'windows'
|
||||||
elif host_system != 'serenity'
|
elif host_system != 'serenity'
|
||||||
platform_deps += [
|
platform_deps += [
|
||||||
dependency('SQLiteCpp'),
|
dependency('SQLiteCpp'),
|
||||||
|
dependency('pugixml'),
|
||||||
dependency('xcb'),
|
dependency('xcb'),
|
||||||
dependency('xau'),
|
dependency('xau'),
|
||||||
dependency('xdmcp'),
|
dependency('xdmcp'),
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
#include <SQLiteCpp/Statement.h> // SQLite::Statement
|
#include <SQLiteCpp/Statement.h> // SQLite::Statement
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <pugixml.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <chrono> // std::chrono
|
#include <chrono> // std::chrono
|
||||||
#include <filesystem> // std::filesystem
|
#include <filesystem> // std::filesystem
|
||||||
#include <format> // std::format
|
#include <format> // std::format
|
||||||
|
@ -144,8 +148,9 @@ namespace {
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
return Err(DracError(DracErrorCode::NotFound, std::format("No packages found in {} directory", pmId)));
|
return Err(DracError(DracErrorCode::NotFound, std::format("No packages found in {} directory", pmId)));
|
||||||
|
|
||||||
const i64 nowEpochSeconds = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
const i64 timestampEpochSeconds = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||||
const PkgCountCacheData dataToCache = { .count = count, .timestampEpochSeconds = nowEpochSeconds };
|
|
||||||
|
const PkgCountCacheData dataToCache(count, timestampEpochSeconds);
|
||||||
|
|
||||||
if (Result writeResult = WriteCache(pmId, dataToCache); !writeResult)
|
if (Result writeResult = WriteCache(pmId, dataToCache); !writeResult)
|
||||||
debug_at(writeResult.error());
|
debug_at(writeResult.error());
|
||||||
|
@ -251,15 +256,99 @@ namespace package {
|
||||||
|
|
||||||
debug_log("Successfully fetched {} package count: {}.", pmId, count);
|
debug_log("Successfully fetched {} package count: {}.", pmId, count);
|
||||||
|
|
||||||
const i64 nowEpochSeconds = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
const i64 timestampEpochSeconds = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||||
const PkgCountCacheData dataToCache = { .count = count, .timestampEpochSeconds = nowEpochSeconds };
|
|
||||||
|
const PkgCountCacheData dataToCache(count, timestampEpochSeconds);
|
||||||
|
|
||||||
if (Result writeResult = WriteCache(cacheKey, dataToCache); !writeResult)
|
if (Result writeResult = WriteCache(cacheKey, dataToCache); !writeResult)
|
||||||
debug_at(writeResult.error());
|
debug_at(writeResult.error());
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
#endif // __serenity__
|
#endif // __serenity__ || _WIN32
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
fn GetCountFromPlist(const String& pmId, const std::filesystem::path& plistPath) -> Result<u64> {
|
||||||
|
using namespace pugi;
|
||||||
|
using util::types::StringView;
|
||||||
|
|
||||||
|
const String cacheKey = "pkg_count_" + pmId;
|
||||||
|
std::error_code fsErrCode;
|
||||||
|
|
||||||
|
// Cache check
|
||||||
|
if (Result<PkgCountCacheData> cachedDataResult = ReadCache<PkgCountCacheData>(cacheKey)) {
|
||||||
|
const auto& [cachedCount, timestamp] = *cachedDataResult;
|
||||||
|
if (fs::exists(plistPath, fsErrCode) && !fsErrCode) {
|
||||||
|
const fs::file_time_type plistModTime = fs::last_write_time(plistPath, fsErrCode);
|
||||||
|
if (!fsErrCode) {
|
||||||
|
if (const std::chrono::system_clock::time_point cacheTimePoint = std::chrono::system_clock::time_point(std::chrono::seconds(timestamp));
|
||||||
|
cacheTimePoint.time_since_epoch() >= plistModTime.time_since_epoch()) {
|
||||||
|
debug_log("Using valid {} plist count cache (file '{}' unchanged since {}). Count: {}", pmId, plistPath.string(), std::format("{:%F %T %Z}", std::chrono::floor<std::chrono::seconds>(cacheTimePoint)), cachedCount);
|
||||||
|
return cachedCount;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn_log("Could not get modification time for '{}': {}. Invalidating {} cache.", plistPath.string(), fsErrCode.message(), pmId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (cachedDataResult.error().code != DracErrorCode::NotFound) {
|
||||||
|
debug_at(cachedDataResult.error());
|
||||||
|
} else {
|
||||||
|
debug_log("{} plist count cache not found or unreadable", pmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse plist and count
|
||||||
|
xml_document doc;
|
||||||
|
xml_parse_result result = doc.load_file(plistPath.c_str());
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
return Err(util::error::DracError(util::error::DracErrorCode::ParseError, std::format("Failed to parse plist file '{}': {}", plistPath.string(), result.description())));
|
||||||
|
|
||||||
|
xml_node dict = doc.child("plist").child("dict");
|
||||||
|
|
||||||
|
if (!dict)
|
||||||
|
return Err(util::error::DracError(util::error::DracErrorCode::ParseError, std::format("No <dict> in plist file '{}'.", plistPath.string())));
|
||||||
|
|
||||||
|
u64 count = 0;
|
||||||
|
|
||||||
|
for (xml_node node = dict.first_child(); node; node = node.next_sibling()) {
|
||||||
|
if (StringView(node.name()) != "key")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const StringView keyName = node.child_value();
|
||||||
|
|
||||||
|
if (keyName == "_XBPS_ALTERNATIVES_")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
xml_node pkgDict = node.next_sibling("dict");
|
||||||
|
|
||||||
|
if (!pkgDict)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool isInstalled = false;
|
||||||
|
|
||||||
|
for (xml_node pkgNode = pkgDict.first_child(); pkgNode; pkgNode = pkgNode.next_sibling())
|
||||||
|
if (StringView(pkgNode.name()) == "key" && StringView(pkgNode.child_value()) == "state") {
|
||||||
|
xml_node stateValue = pkgNode.next_sibling("string");
|
||||||
|
if (stateValue && StringView(stateValue.child_value()) == "installed") {
|
||||||
|
isInstalled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInstalled)
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
return Err(util::error::DracError(util::error::DracErrorCode::NotFound, std::format("No installed packages found in plist file '{}'.", plistPath.string())));
|
||||||
|
|
||||||
|
const i64 timestampEpochSeconds = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||||
|
const PkgCountCacheData dataToCache(count, timestampEpochSeconds);
|
||||||
|
if (Result writeResult = WriteCache(cacheKey, dataToCache); !writeResult)
|
||||||
|
debug_at(writeResult.error());
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
#endif // __linux__
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
fn GetNixCount() -> Result<u64> {
|
fn GetNixCount() -> Result<u64> {
|
||||||
|
@ -302,13 +391,14 @@ namespace package {
|
||||||
Vec<Future<Result<u64>>> futures;
|
Vec<Future<Result<u64>>> futures;
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
futures.push_back(std::async(std::launch::async, GetDpkgCount));
|
|
||||||
futures.push_back(std::async(std::launch::async, GetPacmanCount));
|
|
||||||
// futures.push_back(std::async(std::launch::async, GetRpmCount));
|
|
||||||
// futures.push_back(std::async(std::launch::async, GetPortageCount));
|
|
||||||
// futures.push_back(std::async(std::launch::async, GetZypperCount));
|
|
||||||
// futures.push_back(std::async(std::launch::async, GetApkCount));
|
// futures.push_back(std::async(std::launch::async, GetApkCount));
|
||||||
|
futures.push_back(std::async(std::launch::async, GetDpkgCount));
|
||||||
futures.push_back(std::async(std::launch::async, GetMossCount));
|
futures.push_back(std::async(std::launch::async, GetMossCount));
|
||||||
|
futures.push_back(std::async(std::launch::async, GetPacmanCount));
|
||||||
|
// futures.push_back(std::async(std::launch::async, GetPortageCount));
|
||||||
|
futures.push_back(std::async(std::launch::async, GetRpmCount));
|
||||||
|
futures.push_back(std::async(std::launch::async, GetXbpsCount));
|
||||||
|
// futures.push_back(std::async(std::launch::async, GetZypperCount));
|
||||||
#elifdef __APPLE__
|
#elifdef __APPLE__
|
||||||
futures.push_back(std::async(std::launch::async, GetHomebrewCount));
|
futures.push_back(std::async(std::launch::async, GetHomebrewCount));
|
||||||
futures.push_back(std::async(std::launch::async, GetMacPortsCount));
|
futures.push_back(std::async(std::launch::async, GetMacPortsCount));
|
||||||
|
|
|
@ -11,11 +11,7 @@
|
||||||
namespace package {
|
namespace package {
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
using util::error::DracError;
|
using util::error::DracError;
|
||||||
using util::types::Future;
|
using util::types::Future, util::types::i64, util::types::Result, util::types::String, util::types::u64;
|
||||||
using util::types::i64;
|
|
||||||
using util::types::Result;
|
|
||||||
using util::types::String;
|
|
||||||
using util::types::u64;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @struct PkgCountCacheData
|
* @struct PkgCountCacheData
|
||||||
|
@ -25,6 +21,9 @@ namespace package {
|
||||||
u64 count {};
|
u64 count {};
|
||||||
i64 timestampEpochSeconds {};
|
i64 timestampEpochSeconds {};
|
||||||
|
|
||||||
|
PkgCountCacheData() = default;
|
||||||
|
PkgCountCacheData(u64 count, i64 timestampEpochSeconds) : count(count), timestampEpochSeconds(timestampEpochSeconds) {}
|
||||||
|
|
||||||
// NOLINTBEGIN(readability-identifier-naming)
|
// NOLINTBEGIN(readability-identifier-naming)
|
||||||
struct [[maybe_unused]] glaze {
|
struct [[maybe_unused]] glaze {
|
||||||
using T = PkgCountCacheData;
|
using T = PkgCountCacheData;
|
||||||
|
@ -102,13 +101,22 @@ namespace package {
|
||||||
fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath) -> Result<u64>;
|
fn GetCountFromDirectory(const String& pmId, const fs::path& dirPath) -> Result<u64>;
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
fn GetDpkgCount() -> Result<u64>;
|
|
||||||
fn GetPacmanCount() -> Result<u64>;
|
|
||||||
fn GetMossCount() -> Result<u64>;
|
|
||||||
fn GetRpmCount() -> Result<u64>;
|
|
||||||
fn GetZypperCount() -> Result<u64>;
|
|
||||||
fn GetPortageCount() -> Result<u64>;
|
|
||||||
fn GetApkCount() -> Result<u64>;
|
fn GetApkCount() -> Result<u64>;
|
||||||
|
fn GetDpkgCount() -> Result<u64>;
|
||||||
|
fn GetMossCount() -> Result<u64>;
|
||||||
|
fn GetPacmanCount() -> Result<u64>;
|
||||||
|
// fn GetPortageCount() -> Result<u64>;
|
||||||
|
fn GetRpmCount() -> Result<u64>;
|
||||||
|
fn GetXbpsCount() -> Result<u64>;
|
||||||
|
// fn GetZypperCount() -> Result<u64>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Counts installed packages in a plist file (used by xbps and potentially others).
|
||||||
|
* @param pmId Identifier for the package manager (for logging/cache).
|
||||||
|
* @param plistPath Path to the plist file.
|
||||||
|
* @return Result containing the count (u64) or a DracError.
|
||||||
|
*/
|
||||||
|
fn GetCountFromPlist(const String& pmId, const std::filesystem::path& plistPath) -> Result<u64>;
|
||||||
#elifdef __APPLE__
|
#elifdef __APPLE__
|
||||||
fn GetHomebrewCount() -> Result<u64>;
|
fn GetHomebrewCount() -> Result<u64>;
|
||||||
fn GetMacPortsCount() -> Result<u64>;
|
fn GetMacPortsCount() -> Result<u64>;
|
||||||
|
|
75
src/main.cpp
75
src/main.cpp
|
@ -1,4 +1,4 @@
|
||||||
#include <format> // std::format
|
#include <cstdlib>
|
||||||
#include <ftxui/dom/elements.hpp> // ftxui::{Element, hbox, vbox, text, separator, filler, etc.}
|
#include <ftxui/dom/elements.hpp> // ftxui::{Element, hbox, vbox, text, separator, filler, etc.}
|
||||||
#include <ftxui/dom/node.hpp> // ftxui::{Render}
|
#include <ftxui/dom/node.hpp> // ftxui::{Render}
|
||||||
#include <ftxui/screen/screen.hpp> // ftxui::{Screen, Dimension::Full}
|
#include <ftxui/screen/screen.hpp> // ftxui::{Screen, Dimension::Full}
|
||||||
|
@ -19,57 +19,84 @@
|
||||||
|
|
||||||
#include "include/argparse.hpp"
|
#include "include/argparse.hpp"
|
||||||
|
|
||||||
using util::types::i32;
|
using util::types::i32, util::types::Exception;
|
||||||
|
|
||||||
fn main(const i32 argc, char* argv[]) -> i32 {
|
fn main(const i32 argc, char* argv[]) -> i32 {
|
||||||
using namespace ftxui;
|
try {
|
||||||
using argparse::Argument, argparse::ArgumentParser;
|
|
||||||
using os::SystemData;
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
winrt::init_apartment();
|
winrt::init_apartment();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ArgumentParser parser("draconis", "0.1.0");
|
argparse::ArgumentParser parser("draconis", "0.1.0");
|
||||||
|
|
||||||
Argument& logLevel =
|
|
||||||
parser
|
parser
|
||||||
.add_argument("--log-level")
|
.add_argument("--log-level")
|
||||||
.help("Set the log level")
|
.help("Set the log level")
|
||||||
.default_value("info");
|
.default_value("info")
|
||||||
|
.choices("debug", "info", "warn", "error");
|
||||||
if (Result<Argument*> result = logLevel.choices("trace", "debug", "info", "warn", "error", "fatal"); !result) {
|
|
||||||
error_log("Error setting choices: {}", result.error().message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
parser
|
parser
|
||||||
.add_argument("-V", "--verbose")
|
.add_argument("-V", "--verbose")
|
||||||
.help("Enable verbose logging. Alias for --log-level=debug")
|
.help("Enable verbose logging. Overrides --log-level.")
|
||||||
.flag();
|
.flag();
|
||||||
|
|
||||||
if (Result<> result = parser.parse_args(argc, argv); !result) {
|
if (Result result = parser.parse_args(argc, argv); !result) {
|
||||||
error_log("Error parsing arguments: {}", result.error().message);
|
error_at(result.error());
|
||||||
return 1;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parser.get<bool>("--verbose").value_or(false) || parser.get<bool>("-v").value_or(false))
|
bool verbose = parser.get<bool>("-V").value_or(false) || parser.get<bool>("--verbose").value_or(false);
|
||||||
info_log("Verbose logging enabled");
|
Result logLevelStr = verbose ? "debug" : parser.get<String>("--log-level");
|
||||||
|
|
||||||
|
{
|
||||||
|
using matchit::match, matchit::is, matchit::_;
|
||||||
|
using util::logging::LogLevel;
|
||||||
|
using enum util::logging::LogLevel;
|
||||||
|
|
||||||
|
LogLevel minLevel = match(logLevelStr)(
|
||||||
|
is | "debug" = Debug,
|
||||||
|
is | "info" = Info,
|
||||||
|
is | "warn" = Warn,
|
||||||
|
is | "error" = Error,
|
||||||
|
#ifndef NDEBUG
|
||||||
|
is | _ = Debug
|
||||||
|
#else
|
||||||
|
is | _ = Info
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
|
||||||
|
SetRuntimeLogLevel(minLevel);
|
||||||
|
}
|
||||||
|
|
||||||
const Config& config = Config::getInstance();
|
const Config& config = Config::getInstance();
|
||||||
const SystemData data = SystemData(config);
|
|
||||||
|
const os::SystemData data = os::SystemData(config);
|
||||||
|
|
||||||
|
{
|
||||||
|
using ftxui::Element, ftxui::Screen, ftxui::Render;
|
||||||
|
using ftxui::Dimension::Full, ftxui::Dimension::Fit;
|
||||||
|
|
||||||
Element document = ui::CreateUI(config, data);
|
Element document = ui::CreateUI(config, data);
|
||||||
|
|
||||||
Screen screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
|
Screen screen = Screen::Create(Full(), Fit(document));
|
||||||
Render(screen, document);
|
Render(screen, document);
|
||||||
screen.Print();
|
screen.Print();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Running the program as part of the shell's startup will cut
|
||||||
|
// off the last line of output, so we need to add a newline here.
|
||||||
#ifdef __cpp_lib_print
|
#ifdef __cpp_lib_print
|
||||||
std::println();
|
std::println();
|
||||||
#else
|
#else
|
||||||
std::cout << '\n';
|
std::cout << '\n';
|
||||||
#endif
|
#endif
|
||||||
|
} catch (const DracError& e) {
|
||||||
return 0;
|
error_at(e);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
} catch (const Exception& e) {
|
||||||
|
error_at(e);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
142
src/os/linux.cpp
142
src/os/linux.cpp
|
@ -15,6 +15,7 @@
|
||||||
#include <glaze/beve/read.hpp> // glz::read_beve
|
#include <glaze/beve/read.hpp> // glz::read_beve
|
||||||
#include <glaze/beve/write.hpp> // glz::write_beve
|
#include <glaze/beve/write.hpp> // glz::write_beve
|
||||||
#include <limits> // std::numeric_limits
|
#include <limits> // std::numeric_limits
|
||||||
|
#include <pugixml.hpp> // pugi::xml_document
|
||||||
#include <string> // std::{getline, string (String)}
|
#include <string> // std::{getline, string (String)}
|
||||||
#include <string_view> // std::string_view (StringView)
|
#include <string_view> // std::string_view (StringView)
|
||||||
#include <sys/socket.h> // ucred, getsockopt, SOL_SOCKET, SO_PEERCRED
|
#include <sys/socket.h> // ucred, getsockopt, SOL_SOCKET, SO_PEERCRED
|
||||||
|
@ -34,6 +35,8 @@
|
||||||
#include "src/wrappers/wayland.hpp"
|
#include "src/wrappers/wayland.hpp"
|
||||||
#include "src/wrappers/xcb.hpp"
|
#include "src/wrappers/xcb.hpp"
|
||||||
|
|
||||||
|
#include "include/matchit.hpp"
|
||||||
|
|
||||||
#include "os.hpp"
|
#include "os.hpp"
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
@ -44,37 +47,34 @@ using util::helpers::GetEnv;
|
||||||
namespace {
|
namespace {
|
||||||
fn GetX11WindowManager() -> Result<String> {
|
fn GetX11WindowManager() -> Result<String> {
|
||||||
using namespace xcb;
|
using namespace xcb;
|
||||||
|
using namespace matchit;
|
||||||
|
using enum ConnError;
|
||||||
|
|
||||||
const DisplayGuard conn;
|
const DisplayGuard conn;
|
||||||
|
|
||||||
if (!conn)
|
if (!conn)
|
||||||
if (const i32 err = connection_has_error(conn.get()))
|
if (const i32 err = ConnectionHasError(conn.get()))
|
||||||
return Err(DracError(DracErrorCode::ApiUnavailable, [&] -> String {
|
return Err(
|
||||||
if (const Option<ConnError> connErr = getConnError(err)) {
|
DracError(
|
||||||
switch (*connErr) {
|
DracErrorCode::ApiUnavailable,
|
||||||
case Generic: return "Stream/Socket/Pipe Error";
|
match(err)(
|
||||||
case ExtNotSupported: return "Extension Not Supported";
|
is | Generic = "Stream/Socket/Pipe Error",
|
||||||
case MemInsufficient: return "Insufficient Memory";
|
is | ExtNotSupported = "Extension Not Supported",
|
||||||
case ReqLenExceed: return "Request Length Exceeded";
|
is | MemInsufficient = "Insufficient Memory",
|
||||||
case ParseErr: return "Display String Parse Error";
|
is | ReqLenExceed = "Request Length Exceeded",
|
||||||
case InvalidScreen: return "Invalid Screen";
|
is | ParseErr = "Display String Parse Error",
|
||||||
case FdPassingFailed: return "FD Passing Failed";
|
is | InvalidScreen = "Invalid Screen",
|
||||||
default: return std::format("Unknown Error Code ({})", err);
|
is | FdPassingFailed = "FD Passing Failed",
|
||||||
}
|
is | _ = std::format("Unknown Error Code ({})", err)
|
||||||
}
|
)
|
||||||
|
)
|
||||||
return std::format("Unknown Error Code ({})", err);
|
);
|
||||||
}()));
|
|
||||||
|
|
||||||
fn internAtom = [&conn](const StringView name) -> Result<atom_t> {
|
fn internAtom = [&conn](const StringView name) -> Result<atom_t> {
|
||||||
const ReplyGuard<intern_atom_reply_t> reply(
|
const ReplyGuard<intern_atom_reply_t> reply(InternAtomReply(conn.get(), InternAtom(conn.get(), 0, static_cast<u16>(name.size()), name.data()), nullptr));
|
||||||
intern_atom_reply(conn.get(), intern_atom(conn.get(), 0, static_cast<u16>(name.size()), name.data()), nullptr)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!reply)
|
if (!reply)
|
||||||
return Err(
|
return Err(DracError(DracErrorCode::PlatformSpecific, std::format("Failed to get X11 atom reply for '{}'", name)));
|
||||||
DracError(DracErrorCode::PlatformSpecific, std::format("Failed to get X11 atom reply for '{}'", name))
|
|
||||||
);
|
|
||||||
|
|
||||||
return reply->atom;
|
return reply->atom;
|
||||||
};
|
};
|
||||||
|
@ -96,27 +96,27 @@ namespace {
|
||||||
return Err(DracError(DracErrorCode::PlatformSpecific, "Failed to get X11 atoms"));
|
return Err(DracError(DracErrorCode::PlatformSpecific, "Failed to get X11 atoms"));
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReplyGuard<get_property_reply_t> wmWindowReply(get_property_reply(
|
const ReplyGuard<get_property_reply_t> wmWindowReply(GetPropertyReply(
|
||||||
conn.get(),
|
conn.get(),
|
||||||
get_property(conn.get(), 0, conn.rootScreen()->root, *supportingWmCheckAtom, ATOM_WINDOW, 0, 1),
|
GetProperty(conn.get(), 0, conn.rootScreen()->root, *supportingWmCheckAtom, ATOM_WINDOW, 0, 1),
|
||||||
nullptr
|
nullptr
|
||||||
));
|
));
|
||||||
|
|
||||||
if (!wmWindowReply || wmWindowReply->type != ATOM_WINDOW || wmWindowReply->format != 32 ||
|
if (!wmWindowReply || wmWindowReply->type != ATOM_WINDOW || wmWindowReply->format != 32 ||
|
||||||
get_property_value_length(wmWindowReply.get()) == 0)
|
GetPropertyValueLength(wmWindowReply.get()) == 0)
|
||||||
return Err(DracError(DracErrorCode::NotFound, "Failed to get _NET_SUPPORTING_WM_CHECK property"));
|
return Err(DracError(DracErrorCode::NotFound, "Failed to get _NET_SUPPORTING_WM_CHECK property"));
|
||||||
|
|
||||||
const window_t wmRootWindow = *static_cast<window_t*>(get_property_value(wmWindowReply.get()));
|
const window_t wmRootWindow = *static_cast<window_t*>(GetPropertyValue(wmWindowReply.get()));
|
||||||
|
|
||||||
const ReplyGuard<get_property_reply_t> wmNameReply(get_property_reply(
|
const ReplyGuard<get_property_reply_t> wmNameReply(GetPropertyReply(
|
||||||
conn.get(), get_property(conn.get(), 0, wmRootWindow, *wmNameAtom, *utf8StringAtom, 0, 1024), nullptr
|
conn.get(), GetProperty(conn.get(), 0, wmRootWindow, *wmNameAtom, *utf8StringAtom, 0, 1024), nullptr
|
||||||
));
|
));
|
||||||
|
|
||||||
if (!wmNameReply || wmNameReply->type != *utf8StringAtom || get_property_value_length(wmNameReply.get()) == 0)
|
if (!wmNameReply || wmNameReply->type != *utf8StringAtom || GetPropertyValueLength(wmNameReply.get()) == 0)
|
||||||
return Err(DracError(DracErrorCode::NotFound, "Failed to get _NET_WM_NAME property"));
|
return Err(DracError(DracErrorCode::NotFound, "Failed to get _NET_WM_NAME property"));
|
||||||
|
|
||||||
const char* nameData = static_cast<const char*>(get_property_value(wmNameReply.get()));
|
const char* nameData = static_cast<const char*>(GetPropertyValue(wmNameReply.get()));
|
||||||
const usize length = get_property_value_length(wmNameReply.get());
|
const usize length = GetPropertyValueLength(wmNameReply.get());
|
||||||
|
|
||||||
return String(nameData, length);
|
return String(nameData, length);
|
||||||
}
|
}
|
||||||
|
@ -255,20 +255,23 @@ namespace os {
|
||||||
Option<String> activePlayer = None;
|
Option<String> activePlayer = None;
|
||||||
|
|
||||||
{
|
{
|
||||||
Result<Message> listNamesResult =
|
Result<Message> listNamesResult = Message::newMethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames");
|
||||||
Message::newMethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames");
|
|
||||||
if (!listNamesResult)
|
if (!listNamesResult)
|
||||||
return Err(listNamesResult.error());
|
return Err(listNamesResult.error());
|
||||||
|
|
||||||
Result<Message> listNamesReplyResult = connection.sendWithReplyAndBlock(*listNamesResult, 100);
|
Result<Message> listNamesReplyResult = connection.sendWithReplyAndBlock(*listNamesResult, 100);
|
||||||
|
|
||||||
if (!listNamesReplyResult)
|
if (!listNamesReplyResult)
|
||||||
return Err(listNamesReplyResult.error());
|
return Err(listNamesReplyResult.error());
|
||||||
|
|
||||||
MessageIter iter = listNamesReplyResult->iterInit();
|
MessageIter iter = listNamesReplyResult->iterInit();
|
||||||
|
|
||||||
if (!iter.isValid() || iter.getArgType() != DBUS_TYPE_ARRAY)
|
if (!iter.isValid() || iter.getArgType() != DBUS_TYPE_ARRAY)
|
||||||
return Err(DracError(DracErrorCode::ParseError, "Invalid DBus ListNames reply format: Expected array"));
|
return Err(DracError(DracErrorCode::ParseError, "Invalid DBus ListNames reply format: Expected array"));
|
||||||
|
|
||||||
MessageIter subIter = iter.recurse();
|
MessageIter subIter = iter.recurse();
|
||||||
|
|
||||||
if (!subIter.isValid())
|
if (!subIter.isValid())
|
||||||
return Err(
|
return Err(
|
||||||
DracError(DracErrorCode::ParseError, "Invalid DBus ListNames reply format: Could not recurse into array")
|
DracError(DracErrorCode::ParseError, "Invalid DBus ListNames reply format: Could not recurse into array")
|
||||||
|
@ -288,9 +291,7 @@ namespace os {
|
||||||
if (!activePlayer)
|
if (!activePlayer)
|
||||||
return Err(DracError(DracErrorCode::NotFound, "No active MPRIS players found"));
|
return Err(DracError(DracErrorCode::NotFound, "No active MPRIS players found"));
|
||||||
|
|
||||||
Result<Message> msgResult = Message::newMethodCall(
|
Result<Message> msgResult = Message::newMethodCall(activePlayer->c_str(), "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get");
|
||||||
activePlayer->c_str(), "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!msgResult)
|
if (!msgResult)
|
||||||
return Err(msgResult.error());
|
return Err(msgResult.error());
|
||||||
|
@ -309,6 +310,7 @@ namespace os {
|
||||||
Option<String> artist = None;
|
Option<String> artist = None;
|
||||||
|
|
||||||
MessageIter propIter = replyResult->iterInit();
|
MessageIter propIter = replyResult->iterInit();
|
||||||
|
|
||||||
if (!propIter.isValid())
|
if (!propIter.isValid())
|
||||||
return Err(DracError(DracErrorCode::ParseError, "Properties.Get reply has no arguments or invalid iterator"));
|
return Err(DracError(DracErrorCode::ParseError, "Properties.Get reply has no arguments or invalid iterator"));
|
||||||
|
|
||||||
|
@ -316,6 +318,7 @@ namespace os {
|
||||||
return Err(DracError(DracErrorCode::ParseError, "Properties.Get reply argument is not a variant"));
|
return Err(DracError(DracErrorCode::ParseError, "Properties.Get reply argument is not a variant"));
|
||||||
|
|
||||||
MessageIter variantIter = propIter.recurse();
|
MessageIter variantIter = propIter.recurse();
|
||||||
|
|
||||||
if (!variantIter.isValid())
|
if (!variantIter.isValid())
|
||||||
return Err(DracError(DracErrorCode::ParseError, "Could not recurse into variant"));
|
return Err(DracError(DracErrorCode::ParseError, "Could not recurse into variant"));
|
||||||
|
|
||||||
|
@ -323,11 +326,13 @@ namespace os {
|
||||||
return Err(DracError(DracErrorCode::ParseError, "Metadata variant content is not a dictionary array (a{sv})"));
|
return Err(DracError(DracErrorCode::ParseError, "Metadata variant content is not a dictionary array (a{sv})"));
|
||||||
|
|
||||||
MessageIter dictIter = variantIter.recurse();
|
MessageIter dictIter = variantIter.recurse();
|
||||||
|
|
||||||
if (!dictIter.isValid())
|
if (!dictIter.isValid())
|
||||||
return Err(DracError(DracErrorCode::ParseError, "Could not recurse into metadata dictionary array"));
|
return Err(DracError(DracErrorCode::ParseError, "Could not recurse into metadata dictionary array"));
|
||||||
|
|
||||||
while (dictIter.getArgType() == DBUS_TYPE_DICT_ENTRY) {
|
while (dictIter.getArgType() == DBUS_TYPE_DICT_ENTRY) {
|
||||||
MessageIter entryIter = dictIter.recurse();
|
MessageIter entryIter = dictIter.recurse();
|
||||||
|
|
||||||
if (!entryIter.isValid()) {
|
if (!entryIter.isValid()) {
|
||||||
debug_log("Warning: Could not recurse into dict entry, skipping.");
|
debug_log("Warning: Could not recurse into dict entry, skipping.");
|
||||||
if (!dictIter.next())
|
if (!dictIter.next())
|
||||||
|
@ -336,6 +341,7 @@ namespace os {
|
||||||
}
|
}
|
||||||
|
|
||||||
Option<String> key = entryIter.getString();
|
Option<String> key = entryIter.getString();
|
||||||
|
|
||||||
if (!key) {
|
if (!key) {
|
||||||
debug_log("Warning: Could not get key string from dict entry, skipping.");
|
debug_log("Warning: Could not get key string from dict entry, skipping.");
|
||||||
if (!dictIter.next())
|
if (!dictIter.next())
|
||||||
|
@ -350,6 +356,7 @@ namespace os {
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageIter valueVariantIter = entryIter.recurse();
|
MessageIter valueVariantIter = entryIter.recurse();
|
||||||
|
|
||||||
if (!valueVariantIter.isValid()) {
|
if (!valueVariantIter.isValid()) {
|
||||||
if (!dictIter.next())
|
if (!dictIter.next())
|
||||||
break;
|
break;
|
||||||
|
@ -362,10 +369,9 @@ namespace os {
|
||||||
if (valueVariantIter.getArgType() == DBUS_TYPE_ARRAY && valueVariantIter.getElementType() == DBUS_TYPE_STRING) {
|
if (valueVariantIter.getArgType() == DBUS_TYPE_ARRAY && valueVariantIter.getElementType() == DBUS_TYPE_STRING) {
|
||||||
if (MessageIter artistArrayIter = valueVariantIter.recurse(); artistArrayIter.isValid())
|
if (MessageIter artistArrayIter = valueVariantIter.recurse(); artistArrayIter.isValid())
|
||||||
artist = artistArrayIter.getString();
|
artist = artistArrayIter.getString();
|
||||||
} else {
|
} else
|
||||||
debug_log("Warning: Artist value was not an array of strings as expected.");
|
debug_log("Warning: Artist value was not an array of strings as expected.");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!dictIter.next())
|
if (!dictIter.next())
|
||||||
break;
|
break;
|
||||||
|
@ -426,20 +432,28 @@ namespace os {
|
||||||
String line;
|
String line;
|
||||||
|
|
||||||
if (!file)
|
if (!file)
|
||||||
return Err(
|
return Err(DracError(DracErrorCode::NotFound, std::format("Failed to open DMI product identifier file '{}'", path)));
|
||||||
DracError(DracErrorCode::NotFound, std::format("Failed to open DMI product identifier file '{}'", path))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!std::getline(file, line) || line.empty())
|
if (!std::getline(file, line) || line.empty())
|
||||||
return Err(
|
return Err(DracError(DracErrorCode::ParseError, std::format("DMI product identifier file ('{}') is empty", path)));
|
||||||
DracError(DracErrorCode::ParseError, std::format("DMI product identifier file ('{}') is empty", path))
|
|
||||||
);
|
|
||||||
|
|
||||||
return line;
|
return line;
|
||||||
};
|
};
|
||||||
|
|
||||||
return readFirstLine(primaryPath).or_else([&](const DracError& primaryError) -> Result<String> {
|
Result<String> primaryResult = readFirstLine(primaryPath);
|
||||||
return readFirstLine(fallbackPath).or_else([&](const DracError& fallbackError) -> Result<String> {
|
|
||||||
|
if (primaryResult)
|
||||||
|
return primaryResult;
|
||||||
|
|
||||||
|
DracError primaryError = primaryResult.error();
|
||||||
|
|
||||||
|
Result<String> fallbackResult = readFirstLine(fallbackPath);
|
||||||
|
|
||||||
|
if (fallbackResult)
|
||||||
|
return fallbackResult;
|
||||||
|
|
||||||
|
DracError fallbackError = fallbackResult.error();
|
||||||
|
|
||||||
return Err(DracError(
|
return Err(DracError(
|
||||||
DracErrorCode::InternalError,
|
DracErrorCode::InternalError,
|
||||||
std::format(
|
std::format(
|
||||||
|
@ -450,8 +464,6 @@ namespace os {
|
||||||
fallbackError.message
|
fallbackError.message
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn GetKernelVersion() -> Result<String> {
|
fn GetKernelVersion() -> Result<String> {
|
||||||
|
@ -505,6 +517,38 @@ namespace package {
|
||||||
fn GetPacmanCount() -> Result<u64> {
|
fn GetPacmanCount() -> Result<u64> {
|
||||||
return GetCountFromDirectory("Pacman", fs::current_path().root_path() / "var" / "lib" / "pacman" / "local", true);
|
return GetCountFromDirectory("Pacman", fs::current_path().root_path() / "var" / "lib" / "pacman" / "local", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn GetRpmCount() -> Result<u64> {
|
||||||
|
const PackageManagerInfo rpmInfo = {
|
||||||
|
.id = "rpm",
|
||||||
|
.dbPath = "/var/lib/rpm/rpmdb.sqlite",
|
||||||
|
.countQuery = "SELECT COUNT(*) FROM Installtid",
|
||||||
|
};
|
||||||
|
|
||||||
|
return GetCountFromDb(rpmInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn GetXbpsCount() -> Result<u64> {
|
||||||
|
const StringView xbpsDbPath = "/var/db/xbps";
|
||||||
|
const String pmId = "xbps";
|
||||||
|
|
||||||
|
if (!fs::exists(xbpsDbPath))
|
||||||
|
return Err(DracError(DracErrorCode::NotFound, std::format("Xbps database path '{}' does not exist", xbpsDbPath)));
|
||||||
|
|
||||||
|
fs::path plistPath;
|
||||||
|
for (const fs::directory_entry& entry : fs::directory_iterator(xbpsDbPath)) {
|
||||||
|
const String filename = entry.path().filename().string();
|
||||||
|
if (filename.starts_with("pkgdb-") && filename.ends_with(".plist")) {
|
||||||
|
plistPath = entry.path();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plistPath.empty())
|
||||||
|
return Err(DracError(DracErrorCode::NotFound, "No Xbps database found"));
|
||||||
|
|
||||||
|
return GetCountFromPlist(pmId, plistPath);
|
||||||
|
}
|
||||||
} // namespace package
|
} // namespace package
|
||||||
|
|
||||||
#endif // __linux__
|
#endif // __linux__
|
||||||
|
|
|
@ -71,10 +71,21 @@ namespace util::logging {
|
||||||
* @enum LogLevel
|
* @enum LogLevel
|
||||||
* @brief Represents different log levels.
|
* @brief Represents different log levels.
|
||||||
*/
|
*/
|
||||||
enum class LogLevel : u8 { Debug,
|
enum class LogLevel : u8 {
|
||||||
|
Debug,
|
||||||
Info,
|
Info,
|
||||||
Warn,
|
Warn,
|
||||||
Error };
|
Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline fn GetRuntimeLogLevel() -> LogLevel& {
|
||||||
|
static LogLevel RuntimeLogLevel = LogLevel::Info;
|
||||||
|
return RuntimeLogLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn SetRuntimeLogLevel(const LogLevel level) {
|
||||||
|
GetRuntimeLogLevel() = level;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Directly applies ANSI color codes to text
|
* @brief Directly applies ANSI color codes to text
|
||||||
|
@ -177,6 +188,9 @@ namespace util::logging {
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
using std::filesystem::path;
|
using std::filesystem::path;
|
||||||
|
|
||||||
|
if (level < GetRuntimeLogLevel())
|
||||||
|
return;
|
||||||
|
|
||||||
const LockGuard lock(GetLogMutex());
|
const LockGuard lock(GetLogMutex());
|
||||||
|
|
||||||
const auto nowTp = system_clock::now();
|
const auto nowTp = system_clock::now();
|
||||||
|
|
|
@ -24,50 +24,110 @@ namespace xcb {
|
||||||
using get_property_cookie_t = xcb_get_property_cookie_t;
|
using get_property_cookie_t = xcb_get_property_cookie_t;
|
||||||
using get_property_reply_t = xcb_get_property_reply_t;
|
using get_property_reply_t = xcb_get_property_reply_t;
|
||||||
|
|
||||||
constexpr atom_t ATOM_WINDOW = XCB_ATOM_WINDOW;
|
constexpr atom_t ATOM_WINDOW = XCB_ATOM_WINDOW; ///< Window atom
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enum representing different types of connection errors
|
||||||
|
*
|
||||||
|
* This enum defines the possible types of errors that can occur when
|
||||||
|
* establishing or maintaining an XCB connection. Each error type
|
||||||
|
* corresponds to a specific error code defined in the XCB library.
|
||||||
|
*/
|
||||||
enum ConnError : u8 {
|
enum ConnError : u8 {
|
||||||
Generic = XCB_CONN_ERROR,
|
Generic = XCB_CONN_ERROR, ///< Generic connection error
|
||||||
ExtNotSupported = XCB_CONN_CLOSED_EXT_NOTSUPPORTED,
|
ExtNotSupported = XCB_CONN_CLOSED_EXT_NOTSUPPORTED, ///< Extension not supported
|
||||||
MemInsufficient = XCB_CONN_CLOSED_MEM_INSUFFICIENT,
|
MemInsufficient = XCB_CONN_CLOSED_MEM_INSUFFICIENT, ///< Memory insufficient
|
||||||
ReqLenExceed = XCB_CONN_CLOSED_REQ_LEN_EXCEED,
|
ReqLenExceed = XCB_CONN_CLOSED_REQ_LEN_EXCEED, ///< Request length exceed
|
||||||
ParseErr = XCB_CONN_CLOSED_PARSE_ERR,
|
ParseErr = XCB_CONN_CLOSED_PARSE_ERR, ///< Parse error
|
||||||
InvalidScreen = XCB_CONN_CLOSED_INVALID_SCREEN,
|
InvalidScreen = XCB_CONN_CLOSED_INVALID_SCREEN, ///< Invalid screen
|
||||||
FdPassingFailed = XCB_CONN_CLOSED_FDPASSING_FAILED,
|
FdPassingFailed = XCB_CONN_CLOSED_FDPASSING_FAILED, ///< FD passing failed
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOLINTBEGIN(readability-identifier-naming)
|
/**
|
||||||
inline fn getConnError(const util::types::i32 code) -> util::types::Option<ConnError> {
|
* @brief Connect to an XCB display
|
||||||
switch (code) {
|
*
|
||||||
case XCB_CONN_ERROR: return Generic;
|
* This function establishes a connection to an XCB display. It takes a
|
||||||
case XCB_CONN_CLOSED_EXT_NOTSUPPORTED: return ExtNotSupported;
|
* display name and a pointer to an integer that will store the screen
|
||||||
case XCB_CONN_CLOSED_MEM_INSUFFICIENT: return MemInsufficient;
|
* number.
|
||||||
case XCB_CONN_CLOSED_REQ_LEN_EXCEED: return ReqLenExceed;
|
*
|
||||||
case XCB_CONN_CLOSED_PARSE_ERR: return ParseErr;
|
* @param displayname The name of the display to connect to
|
||||||
case XCB_CONN_CLOSED_INVALID_SCREEN: return InvalidScreen;
|
* @param screenp Pointer to an integer that will store the screen number
|
||||||
case XCB_CONN_CLOSED_FDPASSING_FAILED: return FdPassingFailed;
|
* @return A pointer to the connection object
|
||||||
default: return None;
|
*/
|
||||||
}
|
inline fn Connect(const char* displayname, int* screenp) -> connection_t* {
|
||||||
}
|
|
||||||
|
|
||||||
inline fn connect(const char* displayname, int* screenp) -> connection_t* {
|
|
||||||
return xcb_connect(displayname, screenp);
|
return xcb_connect(displayname, screenp);
|
||||||
}
|
}
|
||||||
inline fn disconnect(connection_t* conn) -> void {
|
|
||||||
|
/**
|
||||||
|
* @brief Disconnect from an XCB display
|
||||||
|
*
|
||||||
|
* This function disconnects from an XCB display. It takes a pointer to
|
||||||
|
* the connection object.
|
||||||
|
*
|
||||||
|
* @param conn The connection object to disconnect from
|
||||||
|
*/
|
||||||
|
inline fn Disconnect(connection_t* conn) -> void {
|
||||||
xcb_disconnect(conn);
|
xcb_disconnect(conn);
|
||||||
}
|
}
|
||||||
inline fn connection_has_error(connection_t* conn) -> int {
|
|
||||||
|
/**
|
||||||
|
* @brief Check if a connection has an error
|
||||||
|
*
|
||||||
|
* This function checks if a connection has an error. It takes a pointer
|
||||||
|
* to the connection object.
|
||||||
|
*
|
||||||
|
* @param conn The connection object to check
|
||||||
|
* @return 1 if the connection has an error, 0 otherwise
|
||||||
|
*/
|
||||||
|
inline fn ConnectionHasError(connection_t* conn) -> int {
|
||||||
return xcb_connection_has_error(conn);
|
return xcb_connection_has_error(conn);
|
||||||
}
|
}
|
||||||
inline fn intern_atom(connection_t* conn, const uint8_t only_if_exists, const uint16_t name_len, const char* name)
|
|
||||||
|
/**
|
||||||
|
* @brief Intern an atom
|
||||||
|
*
|
||||||
|
* This function interns an atom. It takes a connection object, a flag
|
||||||
|
*
|
||||||
|
* @param conn The connection object to intern the atom on
|
||||||
|
* @param only_if_exists The flag to check if the atom exists
|
||||||
|
* @param name_len The length of the atom name
|
||||||
|
* @param name The name of the atom
|
||||||
|
* @return The cookie for the atom
|
||||||
|
*/
|
||||||
|
inline fn InternAtom(connection_t* conn, const uint8_t only_if_exists, const uint16_t name_len, const char* name)
|
||||||
-> intern_atom_cookie_t {
|
-> intern_atom_cookie_t {
|
||||||
return xcb_intern_atom(conn, only_if_exists, name_len, name);
|
return xcb_intern_atom(conn, only_if_exists, name_len, name);
|
||||||
}
|
}
|
||||||
inline fn intern_atom_reply(connection_t* conn, const intern_atom_cookie_t cookie, generic_error_t** err)
|
|
||||||
|
/**
|
||||||
|
* @brief Get the reply for an interned atom
|
||||||
|
*
|
||||||
|
* This function gets the reply for an interned atom. It takes a connection
|
||||||
|
* object, a cookie, and a pointer to a generic error.
|
||||||
|
*
|
||||||
|
* @param conn The connection object
|
||||||
|
* @param cookie The cookie for the atom
|
||||||
|
* @param err The pointer to the generic error
|
||||||
|
* @return The reply for the atom
|
||||||
|
*/
|
||||||
|
inline fn InternAtomReply(connection_t* conn, const intern_atom_cookie_t cookie, generic_error_t** err)
|
||||||
-> intern_atom_reply_t* {
|
-> intern_atom_reply_t* {
|
||||||
return xcb_intern_atom_reply(conn, cookie, err);
|
return xcb_intern_atom_reply(conn, cookie, err);
|
||||||
}
|
}
|
||||||
inline fn get_property(
|
|
||||||
|
/**
|
||||||
|
* @brief Get a property
|
||||||
|
*
|
||||||
|
* This function gets a property. It takes a connection object, a flag,
|
||||||
|
* a window, a property, a type, a long offset, and a long length.
|
||||||
|
*
|
||||||
|
* @param conn The connection object
|
||||||
|
* @param _delete The flag
|
||||||
|
* @param window The window
|
||||||
|
* @param property The property
|
||||||
|
* @param type The type
|
||||||
|
*/
|
||||||
|
inline fn GetProperty(
|
||||||
connection_t* conn,
|
connection_t* conn,
|
||||||
const uint8_t _delete,
|
const uint8_t _delete,
|
||||||
const window_t window,
|
const window_t window,
|
||||||
|
@ -78,24 +138,49 @@ namespace xcb {
|
||||||
) -> get_property_cookie_t {
|
) -> get_property_cookie_t {
|
||||||
return xcb_get_property(conn, _delete, window, property, type, long_offset, long_length);
|
return xcb_get_property(conn, _delete, window, property, type, long_offset, long_length);
|
||||||
}
|
}
|
||||||
inline fn get_property_reply(connection_t* conn, const get_property_cookie_t cookie, generic_error_t** err)
|
|
||||||
|
/**
|
||||||
|
* @brief Get the reply for a property
|
||||||
|
*
|
||||||
|
* This function gets the reply for a property. It takes a connection
|
||||||
|
* object, a cookie, and a pointer to a generic error.
|
||||||
|
*
|
||||||
|
* @param conn The connection object
|
||||||
|
* @param cookie The cookie for the property
|
||||||
|
* @param err The pointer to the generic error
|
||||||
|
* @return The reply for the property
|
||||||
|
*/
|
||||||
|
inline fn GetPropertyReply(connection_t* conn, const get_property_cookie_t cookie, generic_error_t** err)
|
||||||
-> get_property_reply_t* {
|
-> get_property_reply_t* {
|
||||||
return xcb_get_property_reply(conn, cookie, err);
|
return xcb_get_property_reply(conn, cookie, err);
|
||||||
}
|
}
|
||||||
inline fn get_property_value_length(const get_property_reply_t* reply) -> int {
|
|
||||||
|
/**
|
||||||
|
* @brief Get the value length for a property
|
||||||
|
*
|
||||||
|
* @param reply The reply for the property
|
||||||
|
* @return The value length for the property
|
||||||
|
*/
|
||||||
|
inline fn GetPropertyValueLength(const get_property_reply_t* reply) -> int {
|
||||||
return xcb_get_property_value_length(reply);
|
return xcb_get_property_value_length(reply);
|
||||||
}
|
}
|
||||||
inline fn get_property_value(const get_property_reply_t* reply) -> void* {
|
|
||||||
|
/**
|
||||||
|
* @brief Get the value for a property
|
||||||
|
*
|
||||||
|
* @param reply The reply for the property
|
||||||
|
* @return The value for the property
|
||||||
|
*/
|
||||||
|
inline fn GetPropertyValue(const get_property_reply_t* reply) -> void* {
|
||||||
return xcb_get_property_value(reply);
|
return xcb_get_property_value(reply);
|
||||||
}
|
}
|
||||||
// NOLINTEND(readability-identifier-naming)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RAII wrapper for X11 Display connections
|
* RAII wrapper for X11 Display connections
|
||||||
* Automatically handles resource acquisition and cleanup
|
* Automatically handles resource acquisition and cleanup
|
||||||
*/
|
*/
|
||||||
class DisplayGuard {
|
class DisplayGuard {
|
||||||
connection_t* m_connection = nullptr;
|
connection_t* m_connection = nullptr; ///< The connection to the display
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
|
@ -103,10 +188,10 @@ namespace xcb {
|
||||||
* @param name Display name (nullptr for default)
|
* @param name Display name (nullptr for default)
|
||||||
*/
|
*/
|
||||||
explicit DisplayGuard(const util::types::CStr name = nullptr)
|
explicit DisplayGuard(const util::types::CStr name = nullptr)
|
||||||
: m_connection(connect(name, nullptr)) {}
|
: m_connection(Connect(name, nullptr)) {}
|
||||||
~DisplayGuard() {
|
~DisplayGuard() {
|
||||||
if (m_connection)
|
if (m_connection)
|
||||||
disconnect(m_connection);
|
Disconnect(m_connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-copyable
|
// Non-copyable
|
||||||
|
@ -116,28 +201,50 @@ namespace xcb {
|
||||||
// Movable
|
// Movable
|
||||||
DisplayGuard(DisplayGuard&& other) noexcept
|
DisplayGuard(DisplayGuard&& other) noexcept
|
||||||
: m_connection(std::exchange(other.m_connection, nullptr)) {}
|
: m_connection(std::exchange(other.m_connection, nullptr)) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move assignment operator
|
||||||
|
* @param other The other display guard
|
||||||
|
* @return The moved display guard
|
||||||
|
*/
|
||||||
fn operator=(DisplayGuard&& other) noexcept -> DisplayGuard& {
|
fn operator=(DisplayGuard&& other) noexcept -> DisplayGuard& {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
if (m_connection)
|
if (m_connection)
|
||||||
disconnect(m_connection);
|
Disconnect(m_connection);
|
||||||
|
|
||||||
m_connection = std::exchange(other.m_connection, nullptr);
|
m_connection = std::exchange(other.m_connection, nullptr);
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if the display guard is valid
|
||||||
|
* @return True if the display guard is valid, false otherwise
|
||||||
|
*/
|
||||||
[[nodiscard]] explicit operator bool() const {
|
[[nodiscard]] explicit operator bool() const {
|
||||||
return m_connection && !connection_has_error(m_connection);
|
return m_connection && !ConnectionHasError(m_connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the connection to the display
|
||||||
|
* @return The connection to the display
|
||||||
|
*/
|
||||||
[[nodiscard]] fn get() const -> connection_t* {
|
[[nodiscard]] fn get() const -> connection_t* {
|
||||||
return m_connection;
|
return m_connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the setup for the display
|
||||||
|
* @return The setup for the display
|
||||||
|
*/
|
||||||
[[nodiscard]] fn setup() const -> const setup_t* {
|
[[nodiscard]] fn setup() const -> const setup_t* {
|
||||||
return m_connection ? xcb_get_setup(m_connection) : nullptr;
|
return m_connection ? xcb_get_setup(m_connection) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the root screen for the display
|
||||||
|
* @return The root screen for the display
|
||||||
|
*/
|
||||||
[[nodiscard]] fn rootScreen() const -> screen_t* {
|
[[nodiscard]] fn rootScreen() const -> screen_t* {
|
||||||
const setup_t* setup = this->setup();
|
const setup_t* setup = this->setup();
|
||||||
return setup ? xcb_setup_roots_iterator(setup).data : nullptr;
|
return setup ? xcb_setup_roots_iterator(setup).data : nullptr;
|
||||||
|
@ -150,13 +257,24 @@ namespace xcb {
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class ReplyGuard {
|
class ReplyGuard {
|
||||||
T* m_reply = nullptr;
|
T* m_reply = nullptr; ///< The reply to the XCB request
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Default constructor
|
||||||
|
*/
|
||||||
ReplyGuard() = default;
|
ReplyGuard() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructor with a reply
|
||||||
|
* @param reply The reply to the XCB request
|
||||||
|
*/
|
||||||
explicit ReplyGuard(T* reply)
|
explicit ReplyGuard(T* reply)
|
||||||
: m_reply(reply) {}
|
: m_reply(reply) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructor
|
||||||
|
*/
|
||||||
~ReplyGuard() {
|
~ReplyGuard() {
|
||||||
if (m_reply)
|
if (m_reply)
|
||||||
free(m_reply);
|
free(m_reply);
|
||||||
|
@ -169,6 +287,12 @@ namespace xcb {
|
||||||
// Movable
|
// Movable
|
||||||
ReplyGuard(ReplyGuard&& other) noexcept
|
ReplyGuard(ReplyGuard&& other) noexcept
|
||||||
: m_reply(std::exchange(other.m_reply, nullptr)) {}
|
: m_reply(std::exchange(other.m_reply, nullptr)) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move assignment operator
|
||||||
|
* @param other The other reply guard
|
||||||
|
* @return The moved reply guard
|
||||||
|
*/
|
||||||
fn operator=(ReplyGuard&& other) noexcept -> ReplyGuard& {
|
fn operator=(ReplyGuard&& other) noexcept -> ReplyGuard& {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
if (m_reply)
|
if (m_reply)
|
||||||
|
@ -179,16 +303,34 @@ namespace xcb {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if the reply guard is valid
|
||||||
|
* @return True if the reply guard is valid, false otherwise
|
||||||
|
*/
|
||||||
[[nodiscard]] explicit operator bool() const {
|
[[nodiscard]] explicit operator bool() const {
|
||||||
return m_reply != nullptr;
|
return m_reply != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the reply
|
||||||
|
* @return The reply
|
||||||
|
*/
|
||||||
[[nodiscard]] fn get() const -> T* {
|
[[nodiscard]] fn get() const -> T* {
|
||||||
return m_reply;
|
return m_reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the reply
|
||||||
|
* @return The reply
|
||||||
|
*/
|
||||||
[[nodiscard]] fn operator->() const->T* {
|
[[nodiscard]] fn operator->() const->T* {
|
||||||
return m_reply;
|
return m_reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Dereference the reply
|
||||||
|
* @return The reply
|
||||||
|
*/
|
||||||
[[nodiscard]] fn operator*() const->T& {
|
[[nodiscard]] fn operator*() const->T& {
|
||||||
return *m_reply;
|
return *m_reply;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue