diff --git a/meson.build b/meson.build index 9baa0f3..7c00637 100644 --- a/meson.build +++ b/meson.build @@ -155,10 +155,11 @@ elif host_system != 'serenity' and host_system != 'haiku' xdmcp_dep = dependency('xdmcp', required: false) wayland_dep = dependency('wayland-client', required: false) - platform_deps += [ - dependency('SQLiteCpp'), - dependency('pugixml'), - ] + platform_deps += dependency('SQLiteCpp') + + if host_system == 'linux' + platform_deps += dependency('pugixml') + endif if dbus_dep.found() platform_deps += dbus_dep diff --git a/src/OS/Linux.cpp b/src/OS/Linux.cpp index fe61dda..28b11d0 100644 --- a/src/OS/Linux.cpp +++ b/src/OS/Linux.cpp @@ -1,9 +1,6 @@ #ifdef __linux__ // clang-format off -#include // SQLite::{Database, OPEN_READONLY} -#include // SQLite::Exception -#include // SQLite::Statement #include // PATH_MAX #include // std::strlen #include // std::{unexpected, expected} @@ -14,7 +11,6 @@ #include // glz::write_beve #include // std::numeric_limits #include // matchit::{is, is_not, is_any, etc.} -#include // pugi::xml_document #include // std::{getline, string (String)} #include // std::string_view (StringView) #include // ucred, getsockopt, SOL_SOCKET, SO_PEERCRED diff --git a/src/OS/bsd.cpp b/src/OS/bsd.cpp deleted file mode 100644 index 248b403..0000000 --- a/src/OS/bsd.cpp +++ /dev/null @@ -1,505 +0,0 @@ -#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) - -// clang-format off -#include // DBUS_TYPE_* -#include // DBUS_BUS_SESSION -#include // ifstream -#include // ucred, getsockopt, SOL_SOCKET, SO_PEERCRED -#include // statvfs -#include // sysctlbyname -#include // LOCAL_PEERCRED -#include // uname, utsname - -#ifndef __NetBSD__ - #include // kenv - #include // xucred -#endif - -#include "src/core/package.hpp" -#include "src/util/defs.hpp" -#include "src/util/error.hpp" -#include "src/util/helpers.hpp" -#include "src/util/logging.hpp" -#include "src/util/types.hpp" -#include "src/wrappers/dbus.hpp" -#include "src/wrappers/wayland.hpp" -#include "src/wrappers/xcb.hpp" - -#include "os.hpp" -// clang-format on - -using namespace util::types; -using util::error::DracError, util::error::DracErrorCode; - -namespace { - #ifdef __FreeBSD__ - fn GetPathByPid(pid_t pid) -> Result { - Array exePathBuf; - usize size = exePathBuf.size(); - Array mib; - - mib.at(0) = CTL_KERN; - mib.at(1) = KERN_PROC_ARGS; - mib.at(2) = pid; - mib.at(3) = KERN_PROC_PATHNAME; - - if (sysctl(mib.data(), 4, exePathBuf.data(), &size, nullptr, 0) == -1) - return Err(DracError::withErrno(std::format("sysctl KERN_PROC_PATHNAME failed for pid {}", pid))); - - if (size == 0 || exePathBuf[0] == '\0') - return Err( - DracError(DracErrorCode::NotFound, std::format("sysctl KERN_PROC_PATHNAME returned empty path for pid {}", pid)) - ); - - exePathBuf.at(std::min(size, exePathBuf.size() - 1)) = '\0'; - - return String(exePathBuf.data()); - } - #endif - - fn GetX11WindowManager() -> Result { - using namespace xcb; - - const DisplayGuard conn; - - if (!conn) - if (const i32 err = connection_has_error(conn.get())) - return Err(DracError(DracErrorCode::ApiUnavailable, [&] -> String { - if (const Option connErr = getConnError(err)) { - switch (*connErr) { - case Generic: return "Stream/Socket/Pipe Error"; - case ExtNotSupported: return "Extension Not Supported"; - case MemInsufficient: return "Insufficient Memory"; - case ReqLenExceed: return "Request Length Exceeded"; - case ParseErr: return "Display String Parse Error"; - case InvalidScreen: return "Invalid Screen"; - case FdPassingFailed: return "FD Passing Failed"; - default: return std::format("Unknown Error Code ({})", err); - } - } - - return std::format("Unknown Error Code ({})", err); - }())); - - fn internAtom = [&conn](const StringView name) -> Result { - const ReplyGuard reply( - intern_atom_reply(conn.get(), intern_atom(conn.get(), 0, static_cast(name.size()), name.data()), nullptr) - ); - - if (!reply) - return Err( - DracError(DracErrorCode::PlatformSpecific, std::format("Failed to get X11 atom reply for '{}'", name)) - ); - - return reply->atom; - }; - - const Result supportingWmCheckAtom = internAtom("_NET_SUPPORTING_WM_CHECK"); - const Result wmNameAtom = internAtom("_NET_WM_NAME"); - const Result utf8StringAtom = internAtom("UTF8_STRING"); - - if (!supportingWmCheckAtom || !wmNameAtom || !utf8StringAtom) { - if (!supportingWmCheckAtom) - error_log("Failed to get _NET_SUPPORTING_WM_CHECK atom"); - - if (!wmNameAtom) - error_log("Failed to get _NET_WM_NAME atom"); - - if (!utf8StringAtom) - error_log("Failed to get UTF8_STRING atom"); - - return Err(DracError(DracErrorCode::PlatformSpecific, "Failed to get X11 atoms")); - } - - const ReplyGuard wmWindowReply(get_property_reply( - conn.get(), - get_property(conn.get(), 0, conn.rootScreen()->root, *supportingWmCheckAtom, ATOM_WINDOW, 0, 1), - nullptr - )); - - if (!wmWindowReply || wmWindowReply->type != ATOM_WINDOW || wmWindowReply->format != 32 || - get_property_value_length(wmWindowReply.get()) == 0) - return Err(DracError(DracErrorCode::NotFound, "Failed to get _NET_SUPPORTING_WM_CHECK property")); - - const window_t wmRootWindow = *static_cast(get_property_value(wmWindowReply.get())); - - const ReplyGuard wmNameReply(get_property_reply( - conn.get(), get_property(conn.get(), 0, wmRootWindow, *wmNameAtom, *utf8StringAtom, 0, 1024), nullptr - )); - - if (!wmNameReply || wmNameReply->type != *utf8StringAtom || get_property_value_length(wmNameReply.get()) == 0) - return Err(DracError(DracErrorCode::NotFound, "Failed to get _NET_WM_NAME property")); - - const char* nameData = static_cast(get_property_value(wmNameReply.get())); - const usize length = get_property_value_length(wmNameReply.get()); - - return String(nameData, length); - } - - fn GetWaylandCompositor() -> Result { - #ifndef __FreeBSD__ - return "Wayland Compositor"; - #else - const wl::DisplayGuard display; - - if (!display) - return Err(DracError(DracErrorCode::NotFound, "Failed to connect to display (is Wayland running?)")); - - const i32 fileDescriptor = display.fd(); - if (fileDescriptor < 0) - return Err(DracError(DracErrorCode::ApiUnavailable, "Failed to get Wayland file descriptor")); - - pid_t peerPid = -1; // Initialize PID - - struct xucred cred; - - socklen_t len = sizeof(cred); - - if (getsockopt(fileDescriptor, SOL_SOCKET, LOCAL_PEERCRED, &cred, &len) == -1) - return Err(DracError::withErrno("Failed to get socket credentials (LOCAL_PEERCRED)")); - - peerPid = cred.cr_pid; - - if (peerPid <= 0) - return Err(DracError(DracErrorCode::PlatformSpecific, "Failed to obtain a valid peer PID")); - - Result exePathResult = GetPathByPid(peerPid); - - if (!exePathResult) - return Err(std::move(exePathResult).error()); - - const String& exeRealPath = *exePathResult; - - StringView compositorNameView; - - if (const usize lastSlash = exeRealPath.rfind('/'); lastSlash != String::npos) - compositorNameView = StringView(exeRealPath).substr(lastSlash + 1); - else - compositorNameView = exeRealPath; - - if (compositorNameView.empty() || compositorNameView == "." || compositorNameView == "/") - return Err(DracError(DracErrorCode::NotFound, "Failed to get compositor name from path")); - - if (constexpr StringView wrappedSuffix = "-wrapped"; compositorNameView.length() > 1 + wrappedSuffix.length() && - compositorNameView[0] == '.' && compositorNameView.ends_with(wrappedSuffix)) { - const StringView cleanedView = - compositorNameView.substr(1, compositorNameView.length() - 1 - wrappedSuffix.length()); - - if (cleanedView.empty()) - return Err(DracError(DracErrorCode::NotFound, "Compositor name invalid after heuristic")); - - return String(cleanedView); - } - - return String(compositorNameView); - #endif - } -} // namespace - -namespace os { - using util::helpers::GetEnv; - - fn GetOSVersion() -> Result { - constexpr CStr path = "/etc/os-release"; - - std::ifstream file(path); - - if (file) { - String line; - constexpr StringView prefix = "NAME="; - - while (std::getline(file, line)) { - if (StringView(line).starts_with(prefix)) { - String value = line.substr(prefix.size()); - - if ((value.length() >= 2 && value.front() == '"' && value.back() == '"') || - (value.length() >= 2 && value.front() == '\'' && value.back() == '\'')) - value = value.substr(1, value.length() - 2); - - return value; - } - } - } - - utsname uts; - - if (uname(&uts) == -1) - return Err(DracError::withErrno(std::format("Failed to open {} and uname() call also failed", path))); - - String osName = uts.sysname; - - if (osName.empty()) - return Err(DracError(DracErrorCode::ParseError, "uname() returned empty sysname or release")); - - return osName; - } - - fn GetMemInfo() -> Result { - u64 mem = 0; - usize size = sizeof(mem); - - #ifdef __NetBSD__ - sysctlbyname("hw.physmem64", &mem, &size, nullptr, 0); - #else - sysctlbyname("hw.physmem", &mem, &size, nullptr, 0); - #endif - - return mem; - } - - fn GetNowPlaying() -> Result { - using namespace dbus; - - Result connectionResult = Connection::busGet(DBUS_BUS_SESSION); - if (!connectionResult) - return Err(connectionResult.error()); - - const Connection& connection = *connectionResult; - - Option activePlayer = None; - - { - Result listNamesResult = - Message::newMethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames"); - if (!listNamesResult) - return Err(listNamesResult.error()); - - Result listNamesReplyResult = connection.sendWithReplyAndBlock(*listNamesResult, 100); - if (!listNamesReplyResult) - return Err(listNamesReplyResult.error()); - - MessageIter iter = listNamesReplyResult->iterInit(); - if (!iter.isValid() || iter.getArgType() != DBUS_TYPE_ARRAY) - return Err(DracError(DracErrorCode::ParseError, "Invalid DBus ListNames reply format: Expected array")); - - MessageIter subIter = iter.recurse(); - if (!subIter.isValid()) - return Err( - DracError(DracErrorCode::ParseError, "Invalid DBus ListNames reply format: Could not recurse into array") - ); - - while (subIter.getArgType() != DBUS_TYPE_INVALID) { - if (Option name = subIter.getString()) - if (name->starts_with("org.mpris.MediaPlayer2.")) { - activePlayer = std::move(*name); - break; - } - if (!subIter.next()) - break; - } - } - - if (!activePlayer) - return Err(DracError(DracErrorCode::NotFound, "No active MPRIS players found")); - - Result msgResult = Message::newMethodCall( - activePlayer->c_str(), "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get" - ); - - if (!msgResult) - return Err(msgResult.error()); - - Message& msg = *msgResult; - - if (!msg.appendArgs("org.mpris.MediaPlayer2.Player", "Metadata")) - return Err(DracError(DracErrorCode::InternalError, "Failed to append arguments to Properties.Get message")); - - Result replyResult = connection.sendWithReplyAndBlock(msg, 100); - - if (!replyResult) - return Err(replyResult.error()); - - Option title = None; - Option artist = None; - - MessageIter propIter = replyResult->iterInit(); - if (!propIter.isValid()) - return Err(DracError(DracErrorCode::ParseError, "Properties.Get reply has no arguments or invalid iterator")); - - if (propIter.getArgType() != DBUS_TYPE_VARIANT) - return Err(DracError(DracErrorCode::ParseError, "Properties.Get reply argument is not a variant")); - - MessageIter variantIter = propIter.recurse(); - if (!variantIter.isValid()) - return Err(DracError(DracErrorCode::ParseError, "Could not recurse into variant")); - - if (variantIter.getArgType() != DBUS_TYPE_ARRAY || variantIter.getElementType() != DBUS_TYPE_DICT_ENTRY) - return Err(DracError(DracErrorCode::ParseError, "Metadata variant content is not a dictionary array (a{sv})")); - - MessageIter dictIter = variantIter.recurse(); - if (!dictIter.isValid()) - return Err(DracError(DracErrorCode::ParseError, "Could not recurse into metadata dictionary array")); - - while (dictIter.getArgType() == DBUS_TYPE_DICT_ENTRY) { - MessageIter entryIter = dictIter.recurse(); - if (!entryIter.isValid()) { - debug_log("Warning: Could not recurse into dict entry, skipping."); - if (!dictIter.next()) - break; - continue; - } - - Option key = entryIter.getString(); - if (!key) { - debug_log("Warning: Could not get key string from dict entry, skipping."); - if (!dictIter.next()) - break; - continue; - } - - if (!entryIter.next() || entryIter.getArgType() != DBUS_TYPE_VARIANT) { - if (!dictIter.next()) - break; - continue; - } - - MessageIter valueVariantIter = entryIter.recurse(); - if (!valueVariantIter.isValid()) { - if (!dictIter.next()) - break; - continue; - } - - if (*key == "xesam:title") { - title = valueVariantIter.getString(); - } else if (*key == "xesam:artist") { - if (valueVariantIter.getArgType() == DBUS_TYPE_ARRAY && valueVariantIter.getElementType() == DBUS_TYPE_STRING) { - if (MessageIter artistArrayIter = valueVariantIter.recurse(); artistArrayIter.isValid()) - artist = artistArrayIter.getString(); - } else { - debug_log("Warning: Artist value was not an array of strings as expected."); - } - } - - if (!dictIter.next()) - break; - } - - return MediaInfo(std::move(title), std::move(artist)); - } - - fn GetWindowManager() -> Result { - if (!GetEnv("DISPLAY") && !GetEnv("WAYLAND_DISPLAY") && !GetEnv("XDG_SESSION_TYPE")) - return Err(DracError(DracErrorCode::NotFound, "Could not find a graphical session")); - - if (Result waylandResult = GetWaylandCompositor()) - return *waylandResult; - - if (Result x11Result = GetX11WindowManager()) - return *x11Result; - - return Err(DracError(DracErrorCode::NotFound, "Could not detect window manager (Wayland/X11) or both failed")); - } - - fn GetDesktopEnvironment() -> Result { - if (!GetEnv("DISPLAY") && !GetEnv("WAYLAND_DISPLAY") && !GetEnv("XDG_SESSION_TYPE")) - return Err(DracError(DracErrorCode::NotFound, "Could not find a graphical session")); - - return GetEnv("XDG_CURRENT_DESKTOP") - .transform([](String xdgDesktop) -> String { - if (const usize colon = xdgDesktop.find(':'); colon != String::npos) - xdgDesktop.resize(colon); - - return xdgDesktop; - }) - .or_else([](const DracError&) -> Result { return GetEnv("DESKTOP_SESSION"); }); - } - - fn GetShell() -> Result { - if (const Result shellPath = GetEnv("SHELL")) { - // clang-format off - constexpr Array, 5> shellMap {{ - { "bash", "Bash" }, - { "zsh", "Zsh" }, - { "fish", "Fish" }, - { "nu", "Nushell" }, - { "sh", "SH" }, // sh last because other shells contain "sh" - }}; - // clang-format on - - for (const auto& [exe, name] : shellMap) - if (shellPath->contains(exe)) - return String(name); - - return *shellPath; // fallback to the raw shell path - } - - return Err(DracError(DracErrorCode::NotFound, "Could not find SHELL environment variable")); - } - - fn GetHost() -> Result { - Array buffer {}; - usize size = buffer.size(); - - #if defined(__FreeBSD__) || defined(__DragonFly__) - int result = kenv(KENV_GET, "smbios.system.product", buffer.data(), buffer.size() - 1); // Ensure space for null - - if (result == -1) { - if (sysctlbyname("hw.model", buffer.data(), &size, nullptr, 0) == -1) - return Err(DracError::withErrno("kenv smbios.system.product failed and sysctl hw.model also failed")); - - buffer.at(std::min(size, buffer.size() - 1)) = '\0'; - return String(buffer.data()); - } - - if (result > 0) - buffer.at(result) = '\0'; - else - buffer.at(0) = '\0'; - - #elifdef __NetBSD__ - if (sysctlbyname("machdep.dmi.system-product", buffer.data(), &size, nullptr, 0) == -1) - return Err(DracError::withErrno(std::format("sysctlbyname failed for"))); - - buffer[std::min(size, buffer.size() - 1)] = '\0'; - #endif - if (buffer[0] == '\0') - return Err(DracError(DracErrorCode::NotFound, "Failed to get host product information (empty result)")); - - return String(buffer.data()); - } - - fn GetKernelVersion() -> Result { - utsname uts; - - if (uname(&uts) == -1) - return Err(DracError::withErrno("uname call failed")); - - if (std::strlen(uts.release) == 0) - return Err(DracError(DracErrorCode::ParseError, "uname returned null kernel release")); - - return uts.release; - } - - fn GetDiskUsage() -> Result { - struct statvfs stat; - - if (statvfs("/", &stat) == -1) - return Err(DracError::withErrno(std::format("Failed to get filesystem stats for '/' (statvfs call failed)"))); - - return DiskSpace { - .used_bytes = (stat.f_blocks * stat.f_frsize) - (stat.f_bfree * stat.f_frsize), - .total_bytes = stat.f_blocks * stat.f_frsize, - }; - } -} // namespace os - -namespace package { - #ifdef __NetBSD__ - fn GetPkgSrcCount() -> Result { - return GetCountFromDirectory("pkgsrc", fs::current_path().root_path() / "usr" / "pkg" / "pkgdb", true); - } - #else - fn GetPkgNgCount() -> Result { - const PackageManagerInfo pkgInfo = { - .id = "pkgng", - .dbPath = "/var/db/pkg/local.sqlite", - .countQuery = "SELECT COUNT(*) FROM packages", - }; - - return GetCountFromDb(pkgInfo); - } - #endif -} // namespace package - -#endif // __FreeBSD__ || __DragonFly__ || __NetBSD__ diff --git a/src/Services/Weather/MetNoService.cpp b/src/Services/Weather/MetNoService.cpp index 3a8084a..b4f0d38 100644 --- a/src/Services/Weather/MetNoService.cpp +++ b/src/Services/Weather/MetNoService.cpp @@ -1,7 +1,7 @@ #define NOMINMAX #ifdef __HAIKU__ - #define _DEFAULT_SOURCE + #define _DEFAULT_SOURCE // exposes timegm #endif #include "MetNoService.hpp" diff --git a/src/Services/Weather/OpenMeteoService.cpp b/src/Services/Weather/OpenMeteoService.cpp index 9026372..69bdba9 100644 --- a/src/Services/Weather/OpenMeteoService.cpp +++ b/src/Services/Weather/OpenMeteoService.cpp @@ -1,7 +1,7 @@ #define NOMINMAX #ifdef __HAIKU__ - #define _DEFAULT_SOURCE + #define _DEFAULT_SOURCE // exposes timegm #endif #include "OpenMeteoService.hpp"