IT WORKS (its scuffed as hell but IT WORKS)
This commit is contained in:
parent
2be2613e12
commit
f6a7bee047
9 changed files with 635 additions and 416 deletions
154
flake.nix
154
flake.nix
|
@ -15,7 +15,142 @@
|
|||
...
|
||||
}:
|
||||
utils.lib.eachDefaultSystem (
|
||||
system: let
|
||||
system:
|
||||
if system == "x86_64-linux"
|
||||
then let
|
||||
hostPkgs = import nixpkgs {inherit system;};
|
||||
muslPkgs = import nixpkgs {
|
||||
system = "x86_64-linux-musl";
|
||||
overlays = [
|
||||
(self: super: {
|
||||
mimalloc = super.mimalloc.overrideAttrs (oldAttrs: {
|
||||
cmakeFlags =
|
||||
(oldAttrs.cmakeFlags or [])
|
||||
++ [(self.lib.cmakeBool "MI_LIBC_MUSL" true)];
|
||||
|
||||
postPatch = ''
|
||||
sed -i '\|<linux/prctl.h>|s|^|// |' src/prim/unix/prim.c
|
||||
'';
|
||||
});
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
llvmPackages = muslPkgs.llvmPackages_20;
|
||||
|
||||
stdenv =
|
||||
muslPkgs.stdenvAdapters.useMoldLinker
|
||||
llvmPackages.libcxxStdenv;
|
||||
|
||||
glaze = (muslPkgs.glaze.override {inherit stdenv;}).overrideAttrs (oldAttrs: {
|
||||
cmakeFlags =
|
||||
(oldAttrs.cmakeFlags or [])
|
||||
++ [
|
||||
"-Dglaze_DEVELOPER_MODE=OFF"
|
||||
"-Dglaze_BUILD_EXAMPLES=OFF"
|
||||
];
|
||||
|
||||
doCheck = false;
|
||||
|
||||
enableAvx2 = stdenv.hostPlatform.isx86;
|
||||
});
|
||||
|
||||
mkOverridden = buildSystem: pkg: ((pkg.override {inherit stdenv;}).overrideAttrs (oldAttrs: {
|
||||
"${buildSystem}Flags" =
|
||||
(oldAttrs."${buildSystem}Flags" or [])
|
||||
++ (
|
||||
if buildSystem == "meson"
|
||||
then ["-Ddefault_library=static"]
|
||||
else if buildSystem == "cmake"
|
||||
then [
|
||||
"-D${hostPkgs.lib.toUpper pkg.pname}_BUILD_EXAMPLES=OFF"
|
||||
"-D${hostPkgs.lib.toUpper pkg.pname}_BUILD_TESTS=OFF"
|
||||
"-DBUILD_SHARED_LIBS=OFF"
|
||||
]
|
||||
else throw "Invalid build system: ${buildSystem}"
|
||||
);
|
||||
}));
|
||||
|
||||
deps = with hostPkgs.pkgsStatic; [
|
||||
curlMinimal
|
||||
dbus
|
||||
glaze
|
||||
llvmPackages.libcxx
|
||||
openssl
|
||||
sqlite
|
||||
wayland
|
||||
xorg.libXau
|
||||
xorg.libXdmcp
|
||||
xorg.libxcb
|
||||
|
||||
(mkOverridden "cmake" ftxui)
|
||||
(mkOverridden "cmake" sqlitecpp)
|
||||
(mkOverridden "meson" libsigcxx30)
|
||||
(mkOverridden "meson" tomlplusplus)
|
||||
];
|
||||
in {
|
||||
packages = rec {
|
||||
draconisplusplus = stdenv.mkDerivation {
|
||||
name = "draconis++";
|
||||
version = "0.1.0";
|
||||
src = self;
|
||||
NIX_ENFORCE_NO_NATIVE = 0;
|
||||
|
||||
nativeBuildInputs = with muslPkgs; [
|
||||
cmake
|
||||
meson
|
||||
ninja
|
||||
pkg-config
|
||||
];
|
||||
|
||||
buildInputs = deps;
|
||||
|
||||
configurePhase = ''
|
||||
meson setup build
|
||||
'';
|
||||
buildPhase = ''
|
||||
meson compile -C build
|
||||
'';
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
mv build/draconis++ $out/bin/draconis++
|
||||
'';
|
||||
|
||||
meta.staticExecutable = true;
|
||||
};
|
||||
default = draconisplusplus;
|
||||
};
|
||||
|
||||
devShell = muslPkgs.mkShell.override {inherit stdenv;} {
|
||||
packages =
|
||||
(with hostPkgs; [bear cmake])
|
||||
++ (with muslPkgs; [
|
||||
llvmPackages_20.clang-tools
|
||||
meson
|
||||
ninja
|
||||
pkg-config
|
||||
(hostPkgs.writeScriptBin "build" "meson compile -C build")
|
||||
(hostPkgs.writeScriptBin "clean" "meson setup build --wipe")
|
||||
(hostPkgs.writeScriptBin "run" "meson compile -C build && build/draconis++")
|
||||
])
|
||||
++ deps;
|
||||
|
||||
NIX_ENFORCE_NO_NATIVE = 0;
|
||||
};
|
||||
|
||||
formatter = treefmt-nix.lib.mkWrapper hostPkgs {
|
||||
projectRootFile = "flake.nix";
|
||||
programs = {
|
||||
alejandra.enable = true;
|
||||
deadnix.enable = true;
|
||||
clang-format = {
|
||||
enable = true;
|
||||
package = hostPkgs.llvmPackages.clang-tools;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
else let
|
||||
pkgs = import nixpkgs {inherit system;};
|
||||
|
||||
llvmPackages = pkgs.llvmPackages_20;
|
||||
|
@ -28,21 +163,6 @@
|
|||
)
|
||||
llvmPackages.stdenv;
|
||||
|
||||
sources = import ./_sources/generated.nix {
|
||||
inherit (pkgs) fetchFromGitHub fetchgit fetchurl dockerTools;
|
||||
};
|
||||
|
||||
dbus-cxx = stdenv.mkDerivation {
|
||||
inherit (sources.dbus-cxx) pname version src;
|
||||
nativeBuildInputs = with pkgs; [cmake pkg-config];
|
||||
|
||||
buildInputs = with pkgs.pkgsStatic; [libsigcxx30];
|
||||
|
||||
prePatch = ''
|
||||
substituteInPlace CMakeLists.txt --replace "add_library( dbus-cxx SHARED" "add_library( dbus-cxx STATIC"
|
||||
'';
|
||||
};
|
||||
|
||||
deps = with pkgs;
|
||||
[
|
||||
(glaze.override {enableAvx2 = hostPlatform.isx86;})
|
||||
|
@ -64,7 +184,7 @@
|
|||
valgrind
|
||||
]
|
||||
++ (with pkgsStatic; [
|
||||
dbus-cxx
|
||||
dbus
|
||||
libsigcxx30
|
||||
sqlitecpp
|
||||
xorg.libxcb
|
||||
|
|
21
meson.build
21
meson.build
|
@ -30,6 +30,7 @@ common_warning_flags = [
|
|||
'-Wno-missing-prototypes',
|
||||
'-Wno-padded',
|
||||
'-Wno-pre-c++20-compat-pedantic',
|
||||
'-Wno-unused-command-line-argument',
|
||||
'-Wunused-function',
|
||||
]
|
||||
|
||||
|
@ -49,10 +50,7 @@ common_cpp_flags = {
|
|||
'/external:anglebrackets',
|
||||
'/std:c++latest',
|
||||
],
|
||||
'unix_extra' : [
|
||||
'-march=native',
|
||||
'-nostdlib++',
|
||||
],
|
||||
'unix_extra' : '-march=native',
|
||||
'windows_extra' : '-DCURL_STATICLIB',
|
||||
}
|
||||
|
||||
|
@ -80,7 +78,6 @@ else
|
|||
if host_system == 'windows'
|
||||
common_cpp_args += common_cpp_flags['windows_extra']
|
||||
endif
|
||||
common_cpp_args = cpp.get_supported_arguments(common_cpp_args)
|
||||
endif
|
||||
|
||||
add_project_arguments(common_cpp_args, language : 'cpp')
|
||||
|
@ -91,7 +88,7 @@ add_project_arguments(common_cpp_args, language : 'cpp')
|
|||
base_sources = files('src/core/system_data.cpp', 'src/config/config.cpp', 'src/config/weather.cpp', 'src/main.cpp')
|
||||
|
||||
platform_sources = {
|
||||
'linux' : ['src/os/linux.cpp', 'src/os/linux/issetugid_stub.cpp', 'src/os/linux/pkg_count.cpp'],
|
||||
'linux' : ['src/os/linux.cpp', 'src/os/linux/pkg_count.cpp'],
|
||||
'darwin' : ['src/os/macos.cpp', 'src/os/macos/bridge.mm'],
|
||||
'windows' : ['src/os/windows.cpp'],
|
||||
}
|
||||
|
@ -125,21 +122,13 @@ elif host_system == 'windows'
|
|||
cpp.find_library('windowsapp'),
|
||||
]
|
||||
elif host_system == 'linux'
|
||||
dbus_cxx_dep = dependency('dbus-cxx', include_type : 'system', required : false)
|
||||
|
||||
if not dbus_cxx_dep.found()
|
||||
cmake = import('cmake')
|
||||
dbus_cxx_proj = cmake.subproject('dbus_cxx')
|
||||
dbus_cxx_dep = dbus_cxx_proj.dependency('dbus_cxx', include_type : 'system')
|
||||
endif
|
||||
platform_deps += [
|
||||
dependency('SQLiteCpp'),
|
||||
dependency('xcb'),
|
||||
dependency('xau'),
|
||||
dependency('xdmcp'),
|
||||
dependency('wayland-client'),
|
||||
dependency('sigc++-3.0', include_type : 'system'),
|
||||
dbus_cxx_dep,
|
||||
dependency('dbus-1', include_type: 'system'),
|
||||
]
|
||||
endif
|
||||
|
||||
|
@ -183,7 +172,7 @@ objc_args = []
|
|||
if host_system == 'darwin'
|
||||
objc_args += ['-fobjc-arc']
|
||||
elif cpp.get_id() == 'clang'
|
||||
link_args += ['-static-libgcc', '-static-libstdc++']
|
||||
link_args += ['-static']
|
||||
endif
|
||||
|
||||
# ------------------- #
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
#include <filesystem> // std::filesystem::{path, remove, rename}
|
||||
#include <format> // std::format
|
||||
#include <fstream> // std::{ifstream, ofstream}
|
||||
#include <glaze/beve/read.hpp> // glz::read_beve
|
||||
#include <glaze/beve/write.hpp> // glz::write_beve
|
||||
#include <glaze/core/context.hpp> // glz::{error_ctx, error_code}
|
||||
#include <glaze/core/opts.hpp> // glz::check_partial_read
|
||||
#include <glaze/core/read.hpp> // glz::read
|
||||
#include <glaze/core/reflect.hpp> // glz::format_error
|
||||
#include <glaze/json/write.hpp> // glz::write_json
|
||||
#include <glaze/json/read.hpp> // glz::write_json
|
||||
#include <iterator> // std::istreambuf_iterator
|
||||
#include <system_error> // std::error_code
|
||||
#include <utility> // std::move
|
||||
|
@ -18,17 +18,18 @@
|
|||
|
||||
#include "src/core/util/defs.hpp"
|
||||
#include "src/core/util/logging.hpp"
|
||||
#include "src/core/util/types.hpp"
|
||||
|
||||
#include "config.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
using namespace weather;
|
||||
|
||||
using util::types::i32, util::types::Err, util::types::Exception;
|
||||
using weather::Output;
|
||||
|
||||
namespace {
|
||||
using glz::opts, glz::error_ctx, glz::error_code, glz::write_json, glz::read, glz::format_error;
|
||||
using glz::opts, glz::error_ctx, glz::error_code, glz::read, glz::read_beve, glz::write_beve, glz::format_error;
|
||||
using util::types::usize, util::types::Err, util::types::Exception;
|
||||
using weather::Coords;
|
||||
|
||||
constexpr opts glaze_opts = { .error_on_unknown_keys = false };
|
||||
|
||||
|
@ -39,7 +40,7 @@ namespace {
|
|||
if (errc)
|
||||
return Err("Failed to get temp directory: " + errc.message());
|
||||
|
||||
cachePath /= "weather_cache.json";
|
||||
cachePath /= "weather_cache.beve";
|
||||
return cachePath;
|
||||
}
|
||||
|
||||
|
@ -58,10 +59,19 @@ namespace {
|
|||
|
||||
try {
|
||||
const String content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
||||
ifs.close();
|
||||
|
||||
if (content.empty())
|
||||
return Err(std::format("BEVE cache file is empty: {}", cachePath->string()));
|
||||
|
||||
Output result;
|
||||
|
||||
if (const error_ctx errc = read<glaze_opts>(result, content); errc.ec != error_code::none)
|
||||
return Err(std::format("JSON parse error: {}", format_error(errc, content)));
|
||||
if (const error_ctx glazeErr = read_beve(result, content); glazeErr.ec != error_code::none)
|
||||
return Err(
|
||||
std::format(
|
||||
"BEVE parse error reading cache (code {}): {}", static_cast<int>(glazeErr.ec), cachePath->string()
|
||||
)
|
||||
);
|
||||
|
||||
debug_log("Successfully read from cache file.");
|
||||
return result;
|
||||
|
@ -69,6 +79,8 @@ namespace {
|
|||
}
|
||||
|
||||
fn WriteCacheToFile(const Output& data) -> Result<void, String> {
|
||||
using util::types::isize;
|
||||
|
||||
Result<fs::path, String> cachePath = GetCachePath();
|
||||
|
||||
if (!cachePath)
|
||||
|
@ -79,19 +91,22 @@ namespace {
|
|||
tempPath += ".tmp";
|
||||
|
||||
try {
|
||||
String binaryBuffer;
|
||||
|
||||
if (const error_ctx glazeErr = write_beve(data, binaryBuffer); glazeErr)
|
||||
return Err(std::format("BEVE serialization error writing cache (code {})", static_cast<int>(glazeErr.ec)));
|
||||
|
||||
{
|
||||
std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc);
|
||||
if (!ofs.is_open())
|
||||
return Err("Failed to open temp file: " + tempPath.string());
|
||||
|
||||
String jsonStr;
|
||||
|
||||
if (const error_ctx errc = write_json(data, jsonStr); errc.ec != error_code::none)
|
||||
return Err("JSON serialization error: " + format_error(errc, jsonStr));
|
||||
|
||||
ofs << jsonStr;
|
||||
if (!ofs)
|
||||
return Err("Failed to write to temp file");
|
||||
ofs.write(binaryBuffer.data(), static_cast<isize>(binaryBuffer.size()));
|
||||
if (!ofs) {
|
||||
std::error_code removeEc;
|
||||
fs::remove(tempPath, removeEc);
|
||||
return Err("Failed to write to temp BEVE cache file");
|
||||
}
|
||||
}
|
||||
|
||||
std::error_code errc;
|
||||
|
@ -105,7 +120,19 @@ namespace {
|
|||
|
||||
debug_log("Successfully wrote to cache file.");
|
||||
return {};
|
||||
} catch (const Exception& e) { return Err(std::format("File operation error: {}", e.what())); }
|
||||
} catch (const std::ios_base::failure& e) {
|
||||
std::error_code removeEc;
|
||||
fs::remove(tempPath, removeEc);
|
||||
return Err(std::format("Filesystem error writing BEVE cache file {}: {}", tempPath.string(), e.what()));
|
||||
} catch (const Exception& e) {
|
||||
std::error_code removeEc;
|
||||
fs::remove(tempPath, removeEc);
|
||||
return Err(std::format("File operation error during BEVE cache write: {}", e.what()));
|
||||
} catch (...) {
|
||||
std::error_code removeEc;
|
||||
fs::remove(tempPath, removeEc);
|
||||
return Err(std::format("Unknown error writing BEVE cache file: {}", tempPath.string()));
|
||||
}
|
||||
}
|
||||
|
||||
fn WriteCallback(void* contents, const usize size, const usize nmemb, String* str) -> usize {
|
||||
|
@ -136,7 +163,7 @@ namespace {
|
|||
|
||||
Output output;
|
||||
|
||||
if (const error_ctx errc = glz::read<glaze_opts>(output, responseBuffer); errc.ec != error_code::none)
|
||||
if (const error_ctx errc = read<glaze_opts>(output, responseBuffer); errc.ec != error_code::none)
|
||||
return Err("API response parse error: " + format_error(errc, responseBuffer));
|
||||
|
||||
return std::move(output);
|
||||
|
@ -145,6 +172,7 @@ namespace {
|
|||
|
||||
fn Weather::getWeatherInfo() const -> Output {
|
||||
using namespace std::chrono;
|
||||
using util::types::i32;
|
||||
|
||||
if (Result<Output, String> data = ReadCacheFromFile()) {
|
||||
const Output& dataVal = *data;
|
||||
|
@ -174,7 +202,9 @@ fn Weather::getWeatherInfo() const -> Output {
|
|||
|
||||
if (std::holds_alternative<String>(location)) {
|
||||
const auto& city = std::get<String>(location);
|
||||
|
||||
char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast<i32>(city.length()));
|
||||
|
||||
debug_log("Requesting city: {}", escaped);
|
||||
|
||||
const String apiUrl =
|
||||
|
@ -184,12 +214,22 @@ fn Weather::getWeatherInfo() const -> Output {
|
|||
return handleApiResult(MakeApiRequest(apiUrl));
|
||||
}
|
||||
|
||||
if (std::holds_alternative<Coords>(location)) {
|
||||
const auto& [lat, lon] = std::get<Coords>(location);
|
||||
debug_log("Requesting coordinates: lat={:.3f}, lon={:.3f}", lat, lon);
|
||||
|
||||
const String apiUrl = std::format(
|
||||
"https://api.openweathermap.org/data/2.5/weather?lat={:.3f}&lon={:.3f}&appid={}&units={}", lat, lon, api_key, units
|
||||
"https://api.openweathermap.org/data/2.5/weather?lat={:.3f}&lon={:.3f}&appid={}&units={}",
|
||||
lat,
|
||||
lon,
|
||||
api_key,
|
||||
units
|
||||
);
|
||||
|
||||
return handleApiResult(MakeApiRequest(apiUrl));
|
||||
}
|
||||
#ifdef __GLIBCXX__
|
||||
printf("Invalid location type in configuration. Expected String or Coords.\n");
|
||||
#endif
|
||||
|
||||
error_log("Invalid location type in configuration. Expected String or Coords.");
|
||||
return Output {};
|
||||
}
|
||||
|
|
|
@ -15,12 +15,7 @@ namespace {
|
|||
|
||||
const year_month_day ymd = year_month_day { floor<days>(system_clock::now()) };
|
||||
|
||||
try {
|
||||
return std::format(std::locale(""), "{:%B %d}", ymd);
|
||||
} catch (const std::runtime_error& e) {
|
||||
warn_log("Could not retrieve or use system locale ({}). Falling back to default C locale.", e.what());
|
||||
return std::format(std::locale::classic(), "{:%B %d}", ymd);
|
||||
}
|
||||
return std::format("{:%B %d}", ymd);
|
||||
}
|
||||
|
||||
fn log_timing(const std::string& name, const std::chrono::steady_clock::duration& duration) -> void {
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <format>
|
||||
#include <source_location> // std::source_location
|
||||
#include <system_error> // std::error_code
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winerror.h> // error values
|
||||
#include <winrt/base.h> // winrt::hresult_error
|
||||
#elifdef __linux__
|
||||
#include <dbus-cxx/error.h> // DBus::Error
|
||||
#endif
|
||||
|
||||
#include "src/core/util/types.hpp"
|
||||
|
@ -126,33 +125,6 @@ namespace util::error {
|
|||
|
||||
return DraconisError { code, fullMsg, loc };
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
static auto fromDBus(const DBus::Error& err, const std::source_location& loc = std::source_location::current())
|
||||
-> DraconisError {
|
||||
String name = err.name();
|
||||
DraconisErrorCode codeHint = DraconisErrorCode::PlatformSpecific;
|
||||
String message;
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
if (name == "org.freedesktop.DBus.Error.ServiceUnknown"sv ||
|
||||
name == "org.freedesktop.DBus.Error.NameHasNoOwner"sv) {
|
||||
codeHint = DraconisErrorCode::NotFound;
|
||||
message = std::format("DBus service/name not found: {}", err.message());
|
||||
} else if (name == "org.freedesktop.DBus.Error.NoReply"sv || name == "org.freedesktop.DBus.Error.Timeout"sv) {
|
||||
codeHint = DraconisErrorCode::Timeout;
|
||||
message = std::format("DBus timeout/no reply: {}", err.message());
|
||||
} else if (name == "org.freedesktop.DBus.Error.AccessDenied"sv) {
|
||||
codeHint = DraconisErrorCode::PermissionDenied;
|
||||
message = std::format("DBus access denied: {}", err.message());
|
||||
} else {
|
||||
message = std::format("DBus error: {} - {}", name, err.message());
|
||||
}
|
||||
|
||||
return DraconisError { codeHint, message, loc };
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
} // namespace util::error
|
||||
|
|
11
src/main.cpp
11
src/main.cpp
|
@ -1,13 +1,10 @@
|
|||
#include <cmath> // std::lround
|
||||
#include <format> // std::format
|
||||
#include <ftxui/dom/elements.hpp> // ftxui::{hbox, vbox, text, separator, filler}
|
||||
#include <ftxui/dom/node.hpp> // ftxui::{Element, Render}
|
||||
#include <ftxui/dom/elements.hpp> // ftxui::{Element, hbox, vbox, text, separator, filler, etc.}
|
||||
#include <ftxui/dom/node.hpp> // ftxui::{Render}
|
||||
#include <ftxui/screen/color.hpp> // ftxui::Color
|
||||
#include <ftxui/screen/screen.hpp> // ftxui::{Screen, Dimension::Full}
|
||||
#include <optional> // std::optional (operator!=)
|
||||
#include <ranges> // std::ranges::{to, views}
|
||||
#include <string> // std::string (String)
|
||||
#include <string_view> // std::string_view (StringView)
|
||||
#include <ranges> // std::ranges::{iota, to, transform}
|
||||
|
||||
#include "src/config/weather.hpp"
|
||||
|
||||
|
@ -274,7 +271,7 @@ fn main() -> i32 {
|
|||
else
|
||||
error_at(packageCount.error());
|
||||
|
||||
Element document = vbox({ hbox({ SystemInfoBox(config, data), filler() }), text("") });
|
||||
Element document = vbox({ hbox({ SystemInfoBox(config, data), filler() }) });
|
||||
|
||||
Screen screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
|
||||
Render(screen, document);
|
||||
|
|
375
src/os/linux.cpp
375
src/os/linux.cpp
|
@ -2,22 +2,12 @@
|
|||
|
||||
// clang-format off
|
||||
#include <cstring> // std::strlen
|
||||
#include <dbus-cxx/callmessage.h> // DBus::CallMessage
|
||||
#include <dbus-cxx/connection.h> // DBus::Connection
|
||||
#include <dbus-cxx/dispatcher.h> // DBus::Dispatcher
|
||||
#include <dbus-cxx/enums.h> // DBus::{DataType, BusType}
|
||||
#include <dbus-cxx/error.h> // DBus::Error
|
||||
#include <dbus-cxx/messageappenditerator.h> // DBus::MessageAppendIterator
|
||||
#include <dbus-cxx/signature.h> // DBus::Signature
|
||||
#include <dbus-cxx/standalonedispatcher.h> // DBus::StandaloneDispatcher
|
||||
#include <dbus-cxx/variant.h> // DBus::Variant
|
||||
#include <dbus/dbus.h>
|
||||
#include <expected> // std::{unexpected, expected}
|
||||
#include <format> // std::{format, format_to_n}
|
||||
#include <fstream> // std::ifstream
|
||||
#include <climits> // PATH_MAX
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <map> // std::map (Map)
|
||||
#include <memory> // std::shared_ptr (SharedPointer)
|
||||
#include <string> // std::{getline, string (String)}
|
||||
#include <string_view> // std::string_view (StringView)
|
||||
#include <sys/socket.h> // ucred, getsockopt, SOL_SOCKET, SO_PEERCRED
|
||||
|
@ -25,10 +15,9 @@
|
|||
#include <sys/sysinfo.h> // sysinfo
|
||||
#include <sys/utsname.h> // utsname, uname
|
||||
#include <unistd.h> // readlink
|
||||
#include <utility> // std::move
|
||||
|
||||
#include "src/core/util/logging.hpp"
|
||||
#include "src/core/util/helpers.hpp"
|
||||
#include "src/core/util/logging.hpp"
|
||||
|
||||
#include "src/wrappers/wayland.hpp"
|
||||
#include "src/wrappers/xcb.hpp"
|
||||
|
@ -191,91 +180,84 @@ namespace {
|
|||
return String(compositorNameView);
|
||||
}
|
||||
|
||||
fn GetMprisPlayers(const SharedPointer<DBus::Connection>& connection) -> Result<String, DraconisError> {
|
||||
using namespace std::string_view_literals;
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
fn GetMprisPlayers(DBusConnection* connection) -> Vec<String> {
|
||||
Vec<String> mprisPlayers;
|
||||
DBusError err;
|
||||
dbus_error_init(&err);
|
||||
|
||||
try {
|
||||
const SharedPointer<DBus::CallMessage> call =
|
||||
DBus::CallMessage::create("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames");
|
||||
|
||||
const SharedPointer<DBus::Message> reply = connection->send_with_reply_blocking(call, 5);
|
||||
|
||||
if (!reply || !reply->is_valid())
|
||||
return Err(DraconisError(DraconisErrorCode::Timeout, "Failed to get reply from ListNames"));
|
||||
|
||||
Vec<String> allNamesStd;
|
||||
DBus::MessageIterator reader(*reply);
|
||||
reader >> allNamesStd;
|
||||
|
||||
for (const String& name : allNamesStd)
|
||||
if (StringView(name).contains("org.mpris.MediaPlayer2"sv))
|
||||
return name;
|
||||
|
||||
return Err(DraconisError(DraconisErrorCode::NotFound, "No MPRIS players found"));
|
||||
} catch (const DBus::Error& e) { return Err(DraconisError::fromDBus(e)); } catch (const Exception& e) {
|
||||
return Err(DraconisError(DraconisErrorCode::InternalError, e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
fn GetMediaPlayerMetadata(const SharedPointer<DBus::Connection>& connection, const String& playerBusName)
|
||||
-> Result<MediaInfo, DraconisError> {
|
||||
try {
|
||||
const SharedPointer<DBus::CallMessage> metadataCall =
|
||||
DBus::CallMessage::create(playerBusName, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get");
|
||||
|
||||
*metadataCall << "org.mpris.MediaPlayer2.Player" << "Metadata";
|
||||
|
||||
const SharedPointer<DBus::Message> metadataReply = connection->send_with_reply_blocking(metadataCall, 1000);
|
||||
|
||||
if (!metadataReply || !metadataReply->is_valid()) {
|
||||
return Err(
|
||||
DraconisError(DraconisErrorCode::Timeout, "DBus Get Metadata call timed out or received invalid reply")
|
||||
// Create a method call to org.freedesktop.DBus.ListNames
|
||||
DBusMessage* msg = dbus_message_new_method_call(
|
||||
"org.freedesktop.DBus", // target service
|
||||
"/org/freedesktop/DBus", // object path
|
||||
"org.freedesktop.DBus", // interface name
|
||||
"ListNames" // method name
|
||||
);
|
||||
|
||||
if (!msg) {
|
||||
debug_log("Failed to create message for ListNames.");
|
||||
return mprisPlayers;
|
||||
}
|
||||
|
||||
DBus::MessageIterator iter(*metadataReply);
|
||||
DBus::Variant metadataVariant;
|
||||
iter >> metadataVariant; // Can throw
|
||||
// Send the message and block until we get a reply.
|
||||
DBusMessage* reply = dbus_connection_send_with_reply_and_block(connection, msg, -1, &err);
|
||||
dbus_message_unref(msg);
|
||||
|
||||
// MPRIS metadata is variant containing a dict a{sv}
|
||||
if (metadataVariant.type() != DBus::DataType::DICT_ENTRY && metadataVariant.type() != DBus::DataType::ARRAY) {
|
||||
return Err(DraconisError(
|
||||
DraconisErrorCode::ParseError,
|
||||
std::format(
|
||||
"Inner metadata variant is not the expected type, expected dict/a{{sv}} but got '{}'",
|
||||
metadataVariant.signature().str()
|
||||
)
|
||||
));
|
||||
if (dbus_error_is_set(&err)) {
|
||||
debug_log("DBus error in ListNames: {}", err.message);
|
||||
dbus_error_free(&err);
|
||||
return mprisPlayers;
|
||||
}
|
||||
|
||||
Map<String, DBus::Variant> metadata = metadataVariant.to_map<String, DBus::Variant>(); // Can throw
|
||||
|
||||
Option<String> title = None;
|
||||
Option<String> artist = None;
|
||||
|
||||
if (const auto titleIter = metadata.find("xesam:title");
|
||||
titleIter != metadata.end() && titleIter->second.type() == DBus::DataType::STRING)
|
||||
title = titleIter->second.to_string();
|
||||
|
||||
if (const auto artistIter = metadata.find("xesam:artist"); artistIter != metadata.end()) {
|
||||
if (artistIter->second.type() == DBus::DataType::ARRAY) {
|
||||
if (Vec<String> artists = artistIter->second.to_vector<String>(); !artists.empty())
|
||||
artist = artists[0];
|
||||
} else if (artistIter->second.type() == DBus::DataType::STRING) {
|
||||
artist = artistIter->second.to_string();
|
||||
}
|
||||
if (!reply) {
|
||||
debug_log("No reply received for ListNames.");
|
||||
return mprisPlayers;
|
||||
}
|
||||
|
||||
return MediaInfo(std::move(title), std::move(artist));
|
||||
} catch (const DBus::Error& e) { return Err(DraconisError::fromDBus(e)); } catch (const Exception& e) {
|
||||
return Err(DraconisError(
|
||||
DraconisErrorCode::InternalError, std::format("Standard exception processing metadata: {}", e.what())
|
||||
));
|
||||
// The expected reply signature is "as" (an array of strings)
|
||||
DBusMessageIter iter;
|
||||
|
||||
if (!dbus_message_iter_init(reply, &iter)) {
|
||||
debug_log("Reply has no arguments.");
|
||||
dbus_message_unref(reply);
|
||||
return mprisPlayers;
|
||||
}
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
|
||||
debug_log("Reply argument is not an array.");
|
||||
dbus_message_unref(reply);
|
||||
return mprisPlayers;
|
||||
}
|
||||
|
||||
// Iterate over the array of strings
|
||||
DBusMessageIter subIter;
|
||||
dbus_message_iter_recurse(&iter, &subIter);
|
||||
|
||||
while (dbus_message_iter_get_arg_type(&subIter) != DBUS_TYPE_INVALID) {
|
||||
if (dbus_message_iter_get_arg_type(&subIter) == DBUS_TYPE_STRING) {
|
||||
const char* name = nullptr;
|
||||
dbus_message_iter_get_basic(&subIter, static_cast<void*>(&name));
|
||||
if (name && std::string_view(name).contains("org.mpris.MediaPlayer2"))
|
||||
mprisPlayers.emplace_back(name);
|
||||
}
|
||||
dbus_message_iter_next(&subIter);
|
||||
}
|
||||
|
||||
dbus_message_unref(reply);
|
||||
return mprisPlayers;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
fn GetActivePlayer(const Vec<String>& mprisPlayers) -> Option<String> {
|
||||
if (!mprisPlayers.empty())
|
||||
return mprisPlayers.front();
|
||||
return None;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
fn os::GetOSVersion() -> Result<String, DraconisError> {
|
||||
namespace os {
|
||||
fn GetOSVersion() -> Result<String, DraconisError> {
|
||||
constexpr CStr path = "/etc/os-release";
|
||||
|
||||
std::ifstream file(path);
|
||||
|
@ -304,13 +286,13 @@ fn os::GetOSVersion() -> Result<String, DraconisError> {
|
|||
}
|
||||
|
||||
return Err(DraconisError(DraconisErrorCode::NotFound, std::format("PRETTY_NAME line not found in {}", path)));
|
||||
}
|
||||
}
|
||||
|
||||
fn os::GetMemInfo() -> Result<u64, DraconisError> {
|
||||
fn GetMemInfo() -> Result<u64, DraconisError> {
|
||||
struct sysinfo info;
|
||||
|
||||
if (sysinfo(&info) != 0)
|
||||
return Err(DraconisError::fromDBus("sysinfo call failed"));
|
||||
return Err(DraconisError::withErrno("sysinfo call failed"));
|
||||
|
||||
const u64 totalRam = info.totalram;
|
||||
const u64 memUnit = info.mem_unit;
|
||||
|
@ -322,42 +304,170 @@ fn os::GetMemInfo() -> Result<u64, DraconisError> {
|
|||
return Err(DraconisError(DraconisErrorCode::InternalError, "Potential overflow calculating total RAM"));
|
||||
|
||||
return info.totalram * info.mem_unit;
|
||||
}
|
||||
|
||||
fn os::GetNowPlaying() -> Result<MediaInfo, DraconisError> {
|
||||
// Dispatcher must outlive the try-block because 'connection' depends on it later.
|
||||
// ReSharper disable once CppTooWideScope, CppJoinDeclarationAndAssignment
|
||||
SharedPointer<DBus::Dispatcher> dispatcher;
|
||||
SharedPointer<DBus::Connection> connection;
|
||||
|
||||
try {
|
||||
dispatcher = DBus::StandaloneDispatcher::create();
|
||||
|
||||
if (!dispatcher)
|
||||
return Err(DraconisError(DraconisErrorCode::ApiUnavailable, "Failed to create DBus dispatcher"));
|
||||
|
||||
connection = dispatcher->create_connection(DBus::BusType::SESSION);
|
||||
|
||||
if (!connection)
|
||||
return Err(DraconisError(DraconisErrorCode::ApiUnavailable, "Failed to connect to DBus session bus"));
|
||||
} catch (const DBus::Error& e) { return Err(DraconisError::fromDBus(e)); } catch (const Exception& e) {
|
||||
return Err(DraconisError(DraconisErrorCode::InternalError, e.what()));
|
||||
}
|
||||
|
||||
Result<String, DraconisError> playerBusName = GetMprisPlayers(connection);
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
fn GetNowPlaying() -> Result<MediaInfo, DraconisError> {
|
||||
DBusError err;
|
||||
dbus_error_init(&err);
|
||||
|
||||
if (!playerBusName)
|
||||
return Err(playerBusName.error());
|
||||
// Connect to the session bus
|
||||
DBusConnection* connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
|
||||
|
||||
Result<MediaInfo, DraconisError> metadataResult = GetMediaPlayerMetadata(connection, *playerBusName);
|
||||
if (!connection)
|
||||
if (dbus_error_is_set(&err)) {
|
||||
error_log("DBus connection error: {}", err.message);
|
||||
|
||||
if (!metadataResult)
|
||||
return Err(metadataResult.error());
|
||||
DraconisError error = DraconisError(DraconisErrorCode::ApiUnavailable, err.message);
|
||||
dbus_error_free(&err);
|
||||
|
||||
return std::move(*metadataResult);
|
||||
}
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
fn os::GetWindowManager() -> Option<String> {
|
||||
Vec<String> mprisPlayers = GetMprisPlayers(connection);
|
||||
|
||||
if (mprisPlayers.empty()) {
|
||||
dbus_connection_unref(connection);
|
||||
return Err(DraconisError(DraconisErrorCode::NotFound, "No MPRIS players found"));
|
||||
}
|
||||
|
||||
Option<String> activePlayer = GetActivePlayer(mprisPlayers);
|
||||
|
||||
if (!activePlayer.has_value()) {
|
||||
dbus_connection_unref(connection);
|
||||
return Err(DraconisError(DraconisErrorCode::NotFound, "No active MPRIS player found"));
|
||||
}
|
||||
|
||||
// Prepare a call to the Properties.Get method to fetch "Metadata"
|
||||
DBusMessage* msg = dbus_message_new_method_call(
|
||||
activePlayer->c_str(), // target service (active player)
|
||||
"/org/mpris/MediaPlayer2", // object path
|
||||
"org.freedesktop.DBus.Properties", // interface
|
||||
"Get" // method name
|
||||
);
|
||||
|
||||
if (!msg) {
|
||||
dbus_connection_unref(connection);
|
||||
return Err(DraconisError(DraconisErrorCode::InternalError, "Failed to create DBus message"));
|
||||
}
|
||||
|
||||
const char* interfaceName = "org.mpris.MediaPlayer2.Player";
|
||||
const char* propertyName = "Metadata";
|
||||
|
||||
if (!dbus_message_append_args(
|
||||
msg, DBUS_TYPE_STRING, &interfaceName, DBUS_TYPE_STRING, &propertyName, DBUS_TYPE_INVALID
|
||||
)) {
|
||||
dbus_message_unref(msg);
|
||||
dbus_connection_unref(connection);
|
||||
return Err(DraconisError(DraconisErrorCode::InternalError, "Failed to append arguments to DBus message"));
|
||||
}
|
||||
|
||||
// Call the method and block until reply is received.
|
||||
DBusMessage* reply = dbus_connection_send_with_reply_and_block(connection, msg, -1, &err);
|
||||
dbus_message_unref(msg);
|
||||
|
||||
if (dbus_error_is_set(&err)) {
|
||||
error_log("DBus error in Properties.Get: {}", err.message);
|
||||
|
||||
DraconisError error = DraconisError(DraconisErrorCode::ApiUnavailable, err.message);
|
||||
dbus_error_free(&err);
|
||||
dbus_connection_unref(connection);
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
if (!reply) {
|
||||
dbus_connection_unref(connection);
|
||||
return Err(DraconisError(DraconisErrorCode::ApiUnavailable, "No reply received for Properties.Get"));
|
||||
}
|
||||
|
||||
// The reply should contain a variant holding a dictionary ("a{sv}")
|
||||
DBusMessageIter iter;
|
||||
|
||||
if (!dbus_message_iter_init(reply, &iter)) {
|
||||
dbus_message_unref(reply);
|
||||
dbus_connection_unref(connection);
|
||||
return Err(DraconisError(DraconisErrorCode::InternalError, "Reply has no arguments"));
|
||||
}
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
|
||||
dbus_message_unref(reply);
|
||||
dbus_connection_unref(connection);
|
||||
return Err(DraconisError(DraconisErrorCode::InternalError, "Reply argument is not a variant"));
|
||||
}
|
||||
|
||||
// Recurse into the variant to get the dictionary
|
||||
DBusMessageIter variantIter;
|
||||
dbus_message_iter_recurse(&iter, &variantIter);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&variantIter) != DBUS_TYPE_ARRAY) {
|
||||
dbus_message_unref(reply);
|
||||
dbus_connection_unref(connection);
|
||||
return Err(DraconisError(DraconisErrorCode::InternalError, "Variant argument is not an array"));
|
||||
}
|
||||
|
||||
String title;
|
||||
String artist;
|
||||
DBusMessageIter arrayIter;
|
||||
dbus_message_iter_recurse(&variantIter, &arrayIter);
|
||||
|
||||
// Iterate over each dictionary entry (each entry is of type dict entry)
|
||||
while (dbus_message_iter_get_arg_type(&arrayIter) != DBUS_TYPE_INVALID) {
|
||||
if (dbus_message_iter_get_arg_type(&arrayIter) == DBUS_TYPE_DICT_ENTRY) {
|
||||
DBusMessageIter dictEntry;
|
||||
dbus_message_iter_recurse(&arrayIter, &dictEntry);
|
||||
|
||||
// Get the key (a string)
|
||||
const char* key = nullptr;
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&dictEntry) == DBUS_TYPE_STRING)
|
||||
dbus_message_iter_get_basic(&dictEntry, static_cast<void*>(&key));
|
||||
|
||||
// Move to the value (a variant)
|
||||
dbus_message_iter_next(&dictEntry);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&dictEntry) == DBUS_TYPE_VARIANT) {
|
||||
DBusMessageIter valueIter;
|
||||
dbus_message_iter_recurse(&dictEntry, &valueIter);
|
||||
|
||||
if (key && std::string_view(key) == "xesam:title") {
|
||||
if (dbus_message_iter_get_arg_type(&valueIter) == DBUS_TYPE_STRING) {
|
||||
const char* val = nullptr;
|
||||
dbus_message_iter_get_basic(&valueIter, static_cast<void*>(&val));
|
||||
|
||||
if (val)
|
||||
title = val;
|
||||
}
|
||||
} else if (key && std::string_view(key) == "xesam:artist") {
|
||||
// Expect an array of strings
|
||||
if (dbus_message_iter_get_arg_type(&valueIter) == DBUS_TYPE_ARRAY) {
|
||||
DBusMessageIter subIter;
|
||||
dbus_message_iter_recurse(&valueIter, &subIter);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&subIter) == DBUS_TYPE_STRING) {
|
||||
const char* val = nullptr;
|
||||
dbus_message_iter_get_basic(&subIter, static_cast<void*>(&val));
|
||||
|
||||
if (val)
|
||||
artist = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dbus_message_iter_next(&arrayIter);
|
||||
}
|
||||
|
||||
dbus_message_unref(reply);
|
||||
dbus_connection_unref(connection);
|
||||
|
||||
return MediaInfo(artist, title);
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
fn GetWindowManager() -> Option<String> {
|
||||
if (Result<String, DraconisError> waylandResult = GetWaylandCompositor())
|
||||
return *waylandResult;
|
||||
else
|
||||
|
@ -369,9 +479,9 @@ fn os::GetWindowManager() -> Option<String> {
|
|||
debug_log("Could not detect X11 window manager: {}", x11Result.error().message);
|
||||
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
fn os::GetDesktopEnvironment() -> Option<String> {
|
||||
fn GetDesktopEnvironment() -> Option<String> {
|
||||
return util::helpers::GetEnv("XDG_CURRENT_DESKTOP")
|
||||
.transform([](const String& xdgDesktop) -> String {
|
||||
if (const usize colon = xdgDesktop.find(':'); colon != String::npos)
|
||||
|
@ -387,9 +497,9 @@ fn os::GetDesktopEnvironment() -> Option<String> {
|
|||
return finalValue;
|
||||
})
|
||||
.value_or(None);
|
||||
}
|
||||
}
|
||||
|
||||
fn os::GetShell() -> Option<String> {
|
||||
fn GetShell() -> Option<String> {
|
||||
if (const Result<String, DraconisError> shellPath = util::helpers::GetEnv("SHELL")) {
|
||||
// clang-format off
|
||||
constexpr Array<Pair<StringView, StringView>, 5> shellMap {{
|
||||
|
@ -409,9 +519,9 @@ fn os::GetShell() -> Option<String> {
|
|||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
fn os::GetHost() -> Result<String, DraconisError> {
|
||||
fn GetHost() -> Result<String, DraconisError> {
|
||||
constexpr CStr primaryPath = "/sys/class/dmi/id/product_family";
|
||||
constexpr CStr fallbackPath = "/sys/class/dmi/id/product_name";
|
||||
|
||||
|
@ -420,9 +530,9 @@ fn os::GetHost() -> Result<String, DraconisError> {
|
|||
String line;
|
||||
|
||||
if (!file)
|
||||
return Err(
|
||||
DraconisError(DraconisErrorCode::NotFound, std::format("Failed to open DMI product identifier file '{}'", path))
|
||||
);
|
||||
return Err(DraconisError(
|
||||
DraconisErrorCode::NotFound, std::format("Failed to open DMI product identifier file '{}'", path)
|
||||
));
|
||||
|
||||
if (!std::getline(file, line))
|
||||
return Err(
|
||||
|
@ -447,9 +557,9 @@ fn os::GetHost() -> Result<String, DraconisError> {
|
|||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn os::GetKernelVersion() -> Result<String, DraconisError> {
|
||||
fn GetKernelVersion() -> Result<String, DraconisError> {
|
||||
utsname uts;
|
||||
|
||||
if (uname(&uts) == -1)
|
||||
|
@ -459,9 +569,9 @@ fn os::GetKernelVersion() -> Result<String, DraconisError> {
|
|||
return Err(DraconisError(DraconisErrorCode::ParseError, "uname returned null kernel release"));
|
||||
|
||||
return uts.release;
|
||||
}
|
||||
}
|
||||
|
||||
fn os::GetDiskUsage() -> Result<DiskSpace, DraconisError> {
|
||||
fn GetDiskUsage() -> Result<DiskSpace, DraconisError> {
|
||||
struct statvfs stat;
|
||||
|
||||
if (statvfs("/", &stat) == -1)
|
||||
|
@ -471,8 +581,9 @@ fn os::GetDiskUsage() -> Result<DiskSpace, DraconisError> {
|
|||
.used_bytes = (stat.f_blocks * stat.f_frsize) - (stat.f_bfree * stat.f_frsize),
|
||||
.total_bytes = stat.f_blocks * stat.f_frsize,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn os::GetPackageCount() -> Result<u64, DraconisError> { return linux::GetTotalPackageCount(); }
|
||||
fn GetPackageCount() -> Result<u64, DraconisError> { return linux::GetTotalPackageCount(); }
|
||||
} // namespace os
|
||||
|
||||
#endif // __linux__
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
#include "src/core/util/defs.hpp"
|
||||
#include "src/core/util/types.hpp"
|
||||
|
||||
extern "C" fn issetugid() -> util::types::usize { return 0; } // NOLINT
|
|
@ -6,7 +6,6 @@
|
|||
#include <glaze/beve/write.hpp>
|
||||
#include <glaze/core/common.hpp>
|
||||
#include <glaze/core/read.hpp>
|
||||
#include <glaze/core/reflect.hpp>
|
||||
|
||||
#include "src/core/util/logging.hpp"
|
||||
#include "src/core/util/types.hpp"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue