diff --git a/src/os/macos.cpp b/src/os/macos.cpp index e125f65..3f74159 100644 --- a/src/os/macos.cpp +++ b/src/os/macos.cpp @@ -9,37 +9,40 @@ #include "os.h" #include "src/util/types.h" -fn os::GetMemInfo() -> Result { +fn os::GetMemInfo() -> Result { u64 mem = 0; usize size = sizeof(mem); if (sysctlbyname("hw.memsize", &mem, &size, nullptr, 0) == -1) - return Err(std::format("sysctlbyname failed: {}", strerror(errno))); + return Err(OsError::withErrno("Failed to get memory info")); return mem; } -fn os::GetNowPlaying() -> Result { return GetCurrentPlayingInfo(); } +fn os::GetNowPlaying() -> Result { return GetCurrentPlayingInfo(); } -fn os::GetOSVersion() -> Result { return GetMacOSVersion(); } +fn os::GetOSVersion() -> Result { return GetMacOSVersion(); } fn os::GetDesktopEnvironment() -> Option { return None; } -fn os::GetWindowManager() -> String { return "Yabai"; } +fn os::GetWindowManager() -> Option { return None; } -fn os::GetKernelVersion() -> String { +fn os::GetKernelVersion() -> Result { std::array kernelVersion {}; usize kernelVersionLen = sizeof(kernelVersion); - sysctlbyname("kern.osrelease", std::span(kernelVersion).data(), &kernelVersionLen, nullptr, 0); + if (sysctlbyname("kern.osrelease", kernelVersion.data(), &kernelVersionLen, nullptr, 0) == -1) + return Err(OsError::withErrno("Failed to get kernel version")); + return kernelVersion.data(); } -fn os::GetHost() -> String { +fn os::GetHost() -> Result { std::array hwModel {}; size_t hwModelLen = sizeof(hwModel); - sysctlbyname("hw.model", hwModel.data(), &hwModelLen, nullptr, 0); + if (sysctlbyname("hw.model", hwModel.data(), &hwModelLen, nullptr, 0) == -1) + return Err(OsError::withErrno("Failed to get host info")); // taken from https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/host/host_mac.c // shortened a lot of the entries to remove unnecessary info @@ -192,18 +195,22 @@ fn os::GetHost() -> String { { "iMac9,1", "iMac (24/20-inch, 2009)" }, }; - return String(modelNameByHwModel[hwModel.data()]); + const auto it = modelNameByHwModel.find(hwModel.data()); + if (it == modelNameByHwModel.end()) + return Err(OsError::withErrno("Failed to get host info")); + + return String(it->second); } -fn os::GetDiskUsage() -> std::pair { +fn os::GetDiskUsage() -> Result { struct statvfs vfs; if (statvfs("/", &vfs) != 0) - return { 0, 0 }; + return Err(OsError::withErrno("Failed to get disk usage")); - return { (vfs.f_blocks - vfs.f_bfree) * vfs.f_frsize, vfs.f_blocks * vfs.f_frsize }; + return DiskSpace { .used_bytes=(vfs.f_blocks - vfs.f_bfree) * vfs.f_frsize, .total_bytes=vfs.f_blocks * vfs.f_frsize }; } -fn os::GetShell() -> String { return ""; } +fn os::GetShell() -> Option { return None; } #endif diff --git a/src/os/macos/bridge.h b/src/os/macos/bridge.h index 094b441..ffb4bfe 100644 --- a/src/os/macos/bridge.h +++ b/src/os/macos/bridge.h @@ -11,15 +11,15 @@ #import @interface Bridge : NSObject -+ (void)fetchCurrentPlayingMetadata:(void (^)(std::expected))completion; -+ (std::expected)macOSVersion; ++ (void)fetchCurrentPlayingMetadata:(void (^)(Result))completion; ++ (Result)macOSVersion; @end #else extern "C++" { - fn GetCurrentPlayingInfo() -> std::expected; - fn GetMacOSVersion() -> std::expected; + fn GetCurrentPlayingInfo() -> Result; + fn GetMacOSVersion() -> Result; } #endif diff --git a/src/os/macos/bridge.mm b/src/os/macos/bridge.mm index 8e6e368..f3bbd4b 100644 --- a/src/os/macos/bridge.mm +++ b/src/os/macos/bridge.mm @@ -59,7 +59,7 @@ using MRMediaRemoteGetNowPlayingInfoFunction = ); } -+ (std::expected)macOSVersion { ++ (Result)macOSVersion { NSProcessInfo* processInfo = [NSProcessInfo processInfo]; NSOperatingSystemVersion osVersion = [processInfo operatingSystemVersion]; @@ -84,20 +84,21 @@ using MRMediaRemoteGetNowPlayingInfoFunction = extern "C++" { // NOLINTBEGIN(misc-use-internal-linkage) - fn GetCurrentPlayingInfo() -> std::expected { - __block std::expected result; - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + fn GetCurrentPlayingInfo() -> Result { + __block Result result; + + dispatch_semaphore_t const semaphore = dispatch_semaphore_create(0); [Bridge fetchCurrentPlayingMetadata:^(std::expected metadataResult) { if (!metadataResult) { - result = std::unexpected(NowPlayingError { metadataResult.error() }); + result = Err(OsError { OsErrorCode::InternalError, metadataResult.error() }); dispatch_semaphore_signal(semaphore); return; } const NSDictionary* const metadata = *metadataResult; if (!metadata) { - result = std::unexpected(NowPlayingError { NowPlayingCode::NoPlayers }); + result = Err(OsError { OsErrorCode::InternalError, "No metadata" }); dispatch_semaphore_signal(semaphore); return; } @@ -105,14 +106,10 @@ extern "C++" { const NSString* const title = metadata[@"kMRMediaRemoteNowPlayingInfoTitle"]; const NSString* const artist = metadata[@"kMRMediaRemoteNowPlayingInfoArtist"]; - if (!title && !artist) - result = std::unexpected(NowPlayingError { "No metadata" }); - else if (!title) - result = String([artist UTF8String]); - else if (!artist) - result = String([title UTF8String]); - else - result = String([[NSString stringWithFormat:@"%@ - %@", title, artist] UTF8String]); + result = MediaInfo( + title ? Option(String([title UTF8String])) : None, + artist ? Option(String([artist UTF8String])) : None + ); dispatch_semaphore_signal(semaphore); }]; @@ -121,7 +118,7 @@ extern "C++" { return result; } - fn GetMacOSVersion() -> std::expected { return [Bridge macOSVersion]; } + fn GetMacOSVersion() -> Result { return [Bridge macOSVersion]; } // NOLINTEND(misc-use-internal-linkage) } diff --git a/src/util/types.h b/src/util/types.h index a94b1b8..970a500 100644 --- a/src/util/types.h +++ b/src/util/types.h @@ -1,15 +1,17 @@ #pragma once -#include // std::array alias (Array) -#include // std::getenv, std::free -#include // std::expected alias (Result) -#include // std::map alias (Map) -#include // std::shared_ptr and std::unique_ptr aliases (SharedPointer, UniquePointer) -#include // std::optional alias (Option) -#include // std::string and std::string_view aliases (String, StringView) -#include // std::pair alias (Pair) -#include // std::variant alias (NowPlayingError) -#include // std::vector alias (Vec) +#include // std::array alias (Array) +#include // std::getenv, std::free +#include // std::expected alias (Result) +#include // std::format +#include // std::map alias (Map) +#include // std::shared_ptr and std::unique_ptr aliases (SharedPointer, UniquePointer) +#include // std::optional alias (Option) +#include // std::string and std::string_view aliases (String, StringView) +#include // std::error_code and std::system_error +#include // std::pair alias (Pair) +#include // std::variant alias (NowPlayingError) +#include // std::vector alias (Vec) #ifdef _WIN32 #include // winrt::hresult_error (WindowsError) @@ -230,7 +232,43 @@ struct OsError { default: code = PlatformSpecific; break; } } -#endif + + static auto withErrno(const String& context) -> OsError { + const i32 errNo = errno; + const String msg = std::system_category().message(errNo); + const String fullMsg = std::format("{}: {}", context, msg); + + switch (errNo) { + case EACCES: + case EPERM: return OsError { OsErrorCode::PermissionDenied, fullMsg }; + case ENOENT: return OsError { OsErrorCode::NotFound, fullMsg }; + case ETIMEDOUT: return OsError { OsErrorCode::Timeout, fullMsg }; + case ENOTSUP: return OsError { OsErrorCode::NotSupported, fullMsg }; + case EIO: return OsError { OsErrorCode::IoError, fullMsg }; + case ECONNREFUSED: + case ENETDOWN: + case ENETUNREACH: return OsError { OsErrorCode::NetworkError, fullMsg }; + default: return OsError { OsErrorCode::PlatformSpecific, fullMsg }; + } + } + +#ifdef __linux__ + static auto fromDBus(const DBus::Error& err) -> OsError { + String name = err.name(); + + if (name == "org.freedesktop.DBus.Error.ServiceUnknown" || name == "org.freedesktop.DBus.Error.NameHasNoOwner") + return OsError { OsErrorCode::NotFound, std::format("DBus service/name not found: {}", err.message()) }; + + if (name == "org.freedesktop.DBus.Error.NoReply" || name == "org.freedesktop.DBus.Error.Timeout") + return OsError { OsErrorCode::Timeout, std::format("DBus timeout/no reply: {}", err.message()) }; + + if (name == "org.freedesktop.DBus.Error.AccessDenied") + return OsError { OsErrorCode::PermissionDenied, std::format("DBus access denied: {}", err.message()) }; + + return OsError { OsErrorCode::PlatformSpecific, std::format("DBus error: {} - {}", name, err.message()) }; + } +#endif // __linux__ +#endif // _WIN32 }; /** @@ -252,17 +290,15 @@ struct DiskSpace { * Using Option<> for fields that might not always be available. */ struct MediaInfo { - /** - * @enum PlaybackStatus - * @brief Represents the playback status of the media player. - */ - enum class PlaybackStatus : u8 { Playing, Paused, Stopped, Unknown }; - Option title; ///< Track title. Option artist; ///< Track artist(s). Option album; ///< Album name. Option app_name; ///< Name of the media player application (e.g., "Spotify", "Firefox"). - PlaybackStatus status = PlaybackStatus::Unknown; ///< Current playback status. + + MediaInfo() = default; + + MediaInfo(Option title, Option artist) + : title(std::move(title)), artist(std::move(artist)) {} MediaInfo(Option title, Option artist, Option album, Option app) : title(std::move(title)), artist(std::move(artist)), album(std::move(album)), app_name(std::move(app)) {}