This commit is contained in:
Mars 2024-06-09 20:14:51 -04:00
parent 94d08a02d6
commit 4d1e69bbe5
Signed by: pupbrained
GPG key ID: 874E22DF2F9DFCB5
8 changed files with 167 additions and 133 deletions

View file

@ -5,11 +5,13 @@
#include <utility> #include <utility>
#include <variant> #include <variant>
#include "util/numtypes.h"
// Define an error type // Define an error type
class Error { class Error {
public: public:
explicit Error(std::string message) : m_Message(std::move(message)) {} explicit Error(std::string message) : m_Message(std::move(message)) {}
[[nodiscard]] const std::string& message() const { return m_Message; } [[nodiscard]] fn message() const -> const std::string& { return m_Message; }
private: private:
std::string m_Message; std::string m_Message;
@ -29,10 +31,10 @@ class Result<void> {
Result(Error&& error) : m_Result(std::move(error)) {} Result(Error&& error) : m_Result(std::move(error)) {}
// Check if the result is an error // Check if the result is an error
[[nodiscard]] bool isOk() const { [[nodiscard]] fn isOk() const -> bool {
return std::holds_alternative<std::monostate>(m_Result); return std::holds_alternative<std::monostate>(m_Result);
} }
[[nodiscard]] bool isErr() const { [[nodiscard]] fn isErr() const -> bool {
return std::holds_alternative<Error>(m_Result); return std::holds_alternative<Error>(m_Result);
} }
@ -44,7 +46,7 @@ class Result<void> {
} }
// Get the error or throw an exception if it is a value // Get the error or throw an exception if it is a value
[[nodiscard]] const Error& error() const { [[nodiscard]] fn error() const -> const Error& {
if (isOk()) { if (isOk()) {
throw std::logic_error("Attempted to access error of an ok Result"); throw std::logic_error("Attempted to access error of an ok Result");
} }
@ -66,15 +68,15 @@ class Result {
Result(Error&& error) : m_Result(std::move(error)) {} Result(Error&& error) : m_Result(std::move(error)) {}
// Check if the result is an error // Check if the result is an error
[[nodiscard]] bool isOk() const { [[nodiscard]] fn isOk() const -> bool {
return std::holds_alternative<T>(m_Result); return std::holds_alternative<T>(m_Result);
} }
[[nodiscard]] bool isErr() const { [[nodiscard]] fn isErr() const -> bool {
return std::holds_alternative<Error>(m_Result); return std::holds_alternative<Error>(m_Result);
} }
// Get the value or throw an exception if it is an error // Get the value or throw an exception if it is an error
const T& value() const { fn value() const -> const T& {
if (isErr()) { if (isErr()) {
throw std::logic_error("Attempted to access value of an error Result"); throw std::logic_error("Attempted to access value of an error Result");
} }
@ -82,7 +84,7 @@ class Result {
} }
// Get the error or throw an exception if it is a value // Get the error or throw an exception if it is a value
[[nodiscard]] const Error& error() const { [[nodiscard]] fn error() const -> const Error& {
if (isOk()) { if (isOk()) {
throw std::logic_error("Attempted to access error of an ok Result"); throw std::logic_error("Attempted to access error of an ok Result");
} }
@ -90,7 +92,7 @@ class Result {
} }
// Optional: Get the value or provide a default // Optional: Get the value or provide a default
T valueOr(const T& defaultValue) const { fn valueOr(const T& defaultValue) const -> T {
return isOk() ? std::get<T>(m_Result) : defaultValue; return isOk() ? std::get<T>(m_Result) : defaultValue;
} }
@ -99,8 +101,8 @@ class Result {
}; };
template <typename T> template <typename T>
auto Ok(T&& value) { fn Ok(T&& value) {
return Result<std::decay_t<T>>(std::forward<T>(value)); return Result<std::decay_t<T>>(std::forward<T>(value));
} }
inline auto Ok() { return Result<void>(); } inline fn Ok() -> Result<void> { return {}; }

View file

@ -1,7 +1,7 @@
#include "config.h" #include "config.h"
#define DEFINE_GETTER(class_name, type, name) \ #define DEFINE_GETTER(class_name, type, name) \
type class_name::get##name() const { return m_##name; } fn class_name::get##name() const->type { return m_##name; }
DEFINE_GETTER(Config, const General, General) DEFINE_GETTER(Config, const General, General)
DEFINE_GETTER(Config, const NowPlaying, NowPlaying) DEFINE_GETTER(Config, const NowPlaying, NowPlaying)
@ -12,7 +12,7 @@ DEFINE_GETTER(Weather, const Weather::Location, Location)
DEFINE_GETTER(Weather, const std::string, ApiKey) DEFINE_GETTER(Weather, const std::string, ApiKey)
DEFINE_GETTER(Weather, const std::string, Units) DEFINE_GETTER(Weather, const std::string, Units)
const Config& Config::getInstance() { fn Config::getInstance() -> const Config& {
static const auto* INSTANCE = static const auto* INSTANCE =
new Config(rfl::toml::load<Config>("./config.toml").value()); new Config(rfl::toml::load<Config>("./config.toml").value());
return *INSTANCE; return *INSTANCE;
@ -25,14 +25,14 @@ Config::Config(General general, NowPlaying now_playing, Weather weather)
General::General(std::string name) : m_Name(std::move(name)) {} General::General(std::string name) : m_Name(std::move(name)) {}
NowPlaying::NowPlaying(bool enable) : m_Enabled(enable) {} NowPlaying::NowPlaying(bool enabled) : m_Enabled(enabled) {}
Weather::Weather(Location location, std::string api_key, std::string units) Weather::Weather(Location location, std::string api_key, std::string units)
: m_Location(std::move(location)), : m_Location(std::move(location)),
m_ApiKey(std::move(api_key)), m_ApiKey(std::move(api_key)),
m_Units(std::move(units)) {} m_Units(std::move(units)) {}
WeatherImpl WeatherImpl::from_class(const Weather& weather) noexcept { fn WeatherImpl::from_class(const Weather& weather) noexcept -> WeatherImpl {
return { return {
.location = weather.getLocation(), .location = weather.getLocation(),
.api_key = weather.getApiKey(), .api_key = weather.getApiKey(),
@ -40,22 +40,27 @@ WeatherImpl WeatherImpl::from_class(const Weather& weather) noexcept {
}; };
} }
Weather WeatherImpl::to_class() const { return {location, api_key, units}; } fn WeatherImpl::to_class() const -> Weather {
return {location, api_key, units};
}
GeneralImpl GeneralImpl::from_class(const General& general) noexcept { fn GeneralImpl::from_class(const General& general) noexcept -> GeneralImpl {
return {general.getName()}; return {general.getName()};
} }
General GeneralImpl::to_class() const { return {name}; } fn GeneralImpl::to_class() const -> General { return {name}; }
NowPlayingImpl NowPlayingImpl::from_class( // clang-format off
const NowPlaying& now_playing) noexcept { fn NowPlayingImpl::from_class(
const NowPlaying& now_playing
) noexcept -> NowPlayingImpl {
return {.enabled = now_playing.getEnabled()}; return {.enabled = now_playing.getEnabled()};
} }
//clang-format on
NowPlaying NowPlayingImpl::to_class() const { return {enabled}; } fn NowPlayingImpl::to_class() const -> NowPlaying { return {enabled}; }
ConfigImpl ConfigImpl::from_class(const Config& config) noexcept { fn ConfigImpl::from_class(const Config& config) noexcept -> ConfigImpl {
return { return {
.general = config.getGeneral(), .general = config.getGeneral(),
.now_playing = config.getNowPlaying(), .now_playing = config.getNowPlaying(),
@ -63,4 +68,6 @@ ConfigImpl ConfigImpl::from_class(const Config& config) noexcept {
}; };
} }
Config ConfigImpl::to_class() const { return {general, now_playing, weather}; } fn ConfigImpl::to_class() const -> Config {
return {general, now_playing, weather};
}

View file

@ -10,30 +10,30 @@
class Weather { class Weather {
public: public:
using percentage = rfl::Validator<i8, rfl::Minimum<0>, rfl::Maximum<100>>;
using degrees = rfl::Validator<u16, rfl::Minimum<0>, rfl::Maximum<360>>; using degrees = rfl::Validator<u16, rfl::Minimum<0>, rfl::Maximum<360>>;
using percentage = rfl::Validator<i8, rfl::Minimum<0>, rfl::Maximum<100>>;
struct Condition { struct Condition {
usize id;
rfl::Rename<"main", std::string> group;
std::string description; std::string description;
rfl::Rename<"icon", std::string> icon_id; std::string icon;
std::string main;
usize id;
}; };
struct Main { struct Main {
f64 temp; f64 feels_like;
f64 temp_max; f64 temp;
f64 temp_min; f64 temp_max;
f64 feels_like; f64 temp_min;
isize pressure; isize pressure;
std::optional<isize> sea_level; percentage humidity;
std::optional<isize> grnd_level; std::optional<isize> grnd_level;
percentage humidity; std::optional<isize> sea_level;
}; };
struct Wind { struct Wind {
f64 speed; degrees deg;
degrees deg; f64 speed;
std::optional<f64> gust; std::optional<f64> gust;
}; };
@ -44,10 +44,10 @@ class Weather {
struct Sys { struct Sys {
std::string country; std::string country;
usize id; usize id;
usize sunrise; usize sunrise;
usize sunset; usize sunset;
usize type; usize type;
}; };
struct Clouds { struct Clouds {
@ -60,47 +60,47 @@ class Weather {
}; };
struct WeatherOutput { struct WeatherOutput {
isize timezone; Clouds clouds;
isize visibility; Main main;
Main main; Sys sys;
Clouds clouds; Wind wind;
isize timezone;
isize visibility;
rfl::Rename<"coord", Coords> coords; rfl::Rename<"coord", Coords> coords;
std::optional<Precipitation> rain; std::optional<Precipitation> rain;
std::optional<Precipitation> snow; std::optional<Precipitation> snow;
std::vector<Condition> weather; std::string base;
std::string base; std::string name;
std::string name; std::vector<Condition> weather;
Sys sys; usize cod;
usize cod; usize dt;
usize dt; usize id;
usize id;
Wind wind;
}; };
using Location = std::variant<std::string, Coords>; using Location = std::variant<std::string, Coords>;
private: private:
Location m_Location; Location m_Location;
std::string m_ApiKey; std::string m_ApiKey;
std::string m_Units; std::string m_Units;
public: public:
Weather(Location location, std::string api_key, std::string units); Weather(Location location, std::string api_key, std::string units);
[[nodiscard]] WeatherOutput getWeatherInfo() const; [[nodiscard]] fn getWeatherInfo() const -> WeatherOutput;
[[nodiscard]] const Location getLocation() const; [[nodiscard]] fn getLocation() const -> const Location;
[[nodiscard]] const std::string getApiKey() const; [[nodiscard]] fn getApiKey() const -> const std::string;
[[nodiscard]] const std::string getUnits() const; [[nodiscard]] fn getUnits() const -> const std::string;
}; };
struct WeatherImpl { struct WeatherImpl {
Weather::Location location; Weather::Location location;
std::string api_key; std::string api_key;
std::string units; std::string units;
static WeatherImpl from_class(const Weather& weather) noexcept; static fn from_class(const Weather& weather) noexcept -> WeatherImpl;
[[nodiscard]] Weather to_class() const; [[nodiscard]] fn to_class() const -> Weather;
}; };
class General { class General {
@ -110,15 +110,15 @@ class General {
public: public:
General(std::string name); General(std::string name);
[[nodiscard]] const std::string getName() const; [[nodiscard]] fn getName() const -> const std::string;
}; };
struct GeneralImpl { struct GeneralImpl {
std::string name; std::string name;
static GeneralImpl from_class(const General& general) noexcept; static fn from_class(const General& general) noexcept -> GeneralImpl;
[[nodiscard]] General to_class() const; [[nodiscard]] fn to_class() const -> General;
}; };
class NowPlaying { class NowPlaying {
@ -128,74 +128,79 @@ class NowPlaying {
public: public:
NowPlaying(bool enabled); NowPlaying(bool enabled);
[[nodiscard]] bool getEnabled() const; [[nodiscard]] fn getEnabled() const -> bool;
}; };
struct NowPlayingImpl { struct NowPlayingImpl {
bool enabled; bool enabled;
static NowPlayingImpl from_class(const NowPlaying& now_playing) noexcept; static fn from_class(const NowPlaying& now_playing
) noexcept -> NowPlayingImpl;
[[nodiscard]] NowPlaying to_class() const; [[nodiscard]] fn to_class() const -> NowPlaying;
}; };
class Config { class Config {
private: private:
General m_General; General m_General;
NowPlaying m_NowPlaying; NowPlaying m_NowPlaying;
Weather m_Weather; Weather m_Weather;
public: public:
Config(General general, NowPlaying now_playing, Weather weather); Config(General general, NowPlaying now_playing, Weather weather);
static const Config& getInstance(); static fn getInstance() -> const Config&;
[[nodiscard]] const Weather getWeather() const; [[nodiscard]] fn getWeather() const -> const Weather;
[[nodiscard]] const General getGeneral() const; [[nodiscard]] fn getGeneral() const -> const General;
[[nodiscard]] const NowPlaying getNowPlaying() const; [[nodiscard]] fn getNowPlaying() const -> const NowPlaying;
}; };
struct ConfigImpl { struct ConfigImpl {
General general; General general;
NowPlaying now_playing; NowPlaying now_playing;
Weather weather; Weather weather;
static ConfigImpl from_class(const Config& config) noexcept; static fn from_class(const Config& config) noexcept -> ConfigImpl;
[[nodiscard]] Config to_class() const; [[nodiscard]] fn to_class() const -> Config;
}; };
// Parsers for Config classes // Parsers for Config classes
namespace rfl::parsing { namespace rfl::parsing {
template <class ReaderType, class WriterType, class ProcessorsType> template <class ReaderType, class WriterType, class ProcessorsType>
struct Parser<ReaderType, WriterType, Weather, ProcessorsType> struct Parser<ReaderType, WriterType, Weather, ProcessorsType>
: public CustomParser<ReaderType, : public CustomParser<
WriterType, ReaderType,
ProcessorsType, WriterType,
Weather, ProcessorsType,
WeatherImpl> {}; Weather,
WeatherImpl> {};
template <class ReaderType, class WriterType, class ProcessorsType> template <class ReaderType, class WriterType, class ProcessorsType>
struct Parser<ReaderType, WriterType, General, ProcessorsType> struct Parser<ReaderType, WriterType, General, ProcessorsType>
: public CustomParser<ReaderType, : public CustomParser<
WriterType, ReaderType,
ProcessorsType, WriterType,
General, ProcessorsType,
GeneralImpl> {}; General,
GeneralImpl> {};
template <class ReaderType, class WriterType, class ProcessorsType> template <class ReaderType, class WriterType, class ProcessorsType>
struct Parser<ReaderType, WriterType, NowPlaying, ProcessorsType> struct Parser<ReaderType, WriterType, NowPlaying, ProcessorsType>
: public CustomParser<ReaderType, : public CustomParser<
WriterType, ReaderType,
ProcessorsType, WriterType,
NowPlaying, ProcessorsType,
NowPlayingImpl> {}; NowPlaying,
NowPlayingImpl> {};
template <class ReaderType, class WriterType, class ProcessorsType> template <class ReaderType, class WriterType, class ProcessorsType>
struct Parser<ReaderType, WriterType, Config, ProcessorsType> struct Parser<ReaderType, WriterType, Config, ProcessorsType>
: public CustomParser<ReaderType, : public CustomParser<
WriterType, ReaderType,
ProcessorsType, WriterType,
Config, ProcessorsType,
ConfigImpl> {}; Config,
ConfigImpl> {};
} // namespace rfl::parsing } // namespace rfl::parsing

View file

@ -8,11 +8,12 @@
using WeatherOutput = Weather::WeatherOutput; using WeatherOutput = Weather::WeatherOutput;
// Function to read cache from file // Function to read cache from file
Result<WeatherOutput> ReadCacheFromFile() { fn ReadCacheFromFile() -> Result<WeatherOutput> {
const std::string cacheFile = "/tmp/weather_cache.json"; const std::string cacheFile = "/tmp/weather_cache.json";
std::ifstream ifs(cacheFile); std::ifstream ifs(cacheFile);
if (!ifs.is_open()) return Error("Cache file not found."); if (!ifs.is_open())
return Error("Cache file not found.");
fmt::println("Reading from cache file..."); fmt::println("Reading from cache file...");
@ -32,12 +33,13 @@ Result<WeatherOutput> ReadCacheFromFile() {
} }
// Function to write cache to file // Function to write cache to file
Result<> WriteCacheToFile(const WeatherOutput& data) { fn WriteCacheToFile(const WeatherOutput& data) -> Result<> {
const std::string cacheFile = "/tmp/weather_cache.json"; const std::string cacheFile = "/tmp/weather_cache.json";
fmt::println("Writing to cache file..."); fmt::println("Writing to cache file...");
std::ofstream ofs(cacheFile); std::ofstream ofs(cacheFile);
if (!ofs.is_open()) return Error("Failed to open cache file for writing."); if (!ofs.is_open())
return Error("Failed to open cache file for writing.");
ofs << rfl::json::write(data); ofs << rfl::json::write(data);
@ -46,10 +48,8 @@ Result<> WriteCacheToFile(const WeatherOutput& data) {
return Ok(); return Ok();
} }
size_t WriteCallback(void* contents, fn WriteCallback(void* contents, size_t size, size_t nmemb, std::string* buffer)
size_t size, -> size_t {
size_t nmemb,
std::string* buffer) {
size_t realsize = size * nmemb; size_t realsize = size * nmemb;
buffer->append(static_cast<char*>(contents), realsize); buffer->append(static_cast<char*>(contents), realsize);
@ -58,7 +58,7 @@ size_t WriteCallback(void* contents,
} }
// Function to make API request // Function to make API request
Result<WeatherOutput> MakeApiRequest(const std::string& url) { fn MakeApiRequest(const std::string& url) -> Result<WeatherOutput> {
fmt::println("Making API request..."); fmt::println("Making API request...");
CURL* curl = curl_easy_init(); CURL* curl = curl_easy_init();
@ -73,8 +73,9 @@ Result<WeatherOutput> MakeApiRequest(const std::string& url) {
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
if (res != CURLE_OK) if (res != CURLE_OK)
return Error(fmt::format("Failed to perform cURL request: {}", return Error(fmt::format(
curl_easy_strerror(res))); "Failed to perform cURL request: {}", curl_easy_strerror(res)
));
fmt::println("Received response from API."); fmt::println("Received response from API.");
// Parse the JSON response // Parse the JSON response
@ -88,10 +89,10 @@ Result<WeatherOutput> MakeApiRequest(const std::string& url) {
} }
// Core function to get weather information // Core function to get weather information
WeatherOutput Weather::getWeatherInfo() const { fn Weather::getWeatherInfo() const -> WeatherOutput {
using namespace std::chrono; using namespace std::chrono;
const Location loc = m_Location; const Location loc = m_Location;
const std::string apiKey = m_ApiKey; const std::string apiKey = m_ApiKey;
const std::string units = m_Units; const std::string units = m_Units;
@ -116,15 +117,19 @@ WeatherOutput Weather::getWeatherInfo() const {
if (holds_alternative<std::string>(loc)) { if (holds_alternative<std::string>(loc)) {
const std::string city = get<std::string>(loc); const std::string city = get<std::string>(loc);
const char* location = curl_easy_escape(nullptr, city.c_str(), const char* location = curl_easy_escape(
static_cast<int>(city.length())); nullptr, city.c_str(), static_cast<int>(city.length())
);
fmt::println("City: {}", location); fmt::println("City: {}", location);
const std::string apiUrl = fmt::format( const std::string apiUrl = fmt::format(
"https://api.openweathermap.org/data/2.5/" "https://api.openweathermap.org/data/2.5/"
"weather?q={}&appid={}&units={}", "weather?q={}&appid={}&units={}",
location, apiKey, units); location,
apiKey,
units
);
result = MakeApiRequest(apiUrl).value(); result = MakeApiRequest(apiUrl).value();
} else { } else {
@ -135,7 +140,11 @@ WeatherOutput Weather::getWeatherInfo() const {
const std::string apiUrl = fmt::format( const std::string apiUrl = fmt::format(
"https://api.openweathermap.org/data/2.5/" "https://api.openweathermap.org/data/2.5/"
"weather?lat={:.3f}&lon={:.3f}&appid={}&units={}", "weather?lat={:.3f}&lon={:.3f}&appid={}&units={}",
lat, lon, apiKey, units); lat,
lon,
apiKey,
units
);
result = MakeApiRequest(apiUrl).value(); result = MakeApiRequest(apiUrl).value();
} }

View file

@ -11,13 +11,14 @@ struct BytesToGiB {
u64 value; u64 value;
}; };
constexpr u64 GIB = 1'073'741'824;
template <> template <>
struct fmt::formatter<BytesToGiB> : formatter<double> { struct fmt::formatter<BytesToGiB> : formatter<double> {
template <typename FmtCtx> template <typename FmtCtx>
fn format(const BytesToGiB BTG, FmtCtx& ctx) -> typename FmtCtx::iterator { fn format(const BytesToGiB BTG, FmtCtx& ctx) -> typename FmtCtx::iterator {
typename FmtCtx::iterator out = formatter<double>::format( typename FmtCtx::iterator out =
static_cast<double>(BTG.value) / pow(1024, 3), ctx formatter<double>::format(static_cast<double>(BTG.value) / GIB, ctx);
);
*out++ = 'G'; *out++ = 'G';
*out++ = 'i'; *out++ = 'i';
*out++ = 'B'; *out++ = 'B';

View file

@ -5,7 +5,7 @@
#include "macos/bridge.h" #include "macos/bridge.h"
#include "os.h" #include "os.h"
u64 GetMemInfo() { fn GetMemInfo() -> u64 {
u64 mem = 0; u64 mem = 0;
usize size = sizeof(mem); usize size = sizeof(mem);
@ -14,7 +14,7 @@ u64 GetMemInfo() {
return mem; return mem;
} }
std::string GetNowPlaying() { fn GetNowPlaying() -> std::string {
if (const char* title = GetCurrentPlayingTitle(); if (const char* title = GetCurrentPlayingTitle();
const char* artist = GetCurrentPlayingArtist()) const char* artist = GetCurrentPlayingArtist())
return "Now Playing: " + std::string(artist) + " - " + std::string(title); return "Now Playing: " + std::string(artist) + " - " + std::string(title);
@ -22,6 +22,6 @@ std::string GetNowPlaying() {
return "No song playing"; return "No song playing";
} }
const char* GetOSVersion() { return GetMacOSVersion(); }; fn GetOSVersion() -> const char* { return GetMacOSVersion(); };
#endif #endif

View file

@ -9,10 +9,12 @@
+ (NSString*)macOSVersion; + (NSString*)macOSVersion;
@end @end
#else #else
#include "util/numtypes.h"
extern "C" { extern "C" {
const char* GetCurrentPlayingTitle(); fn GetCurrentPlayingTitle() -> const char*;
const char* GetCurrentPlayingArtist(); fn GetCurrentPlayingArtist() -> const char*;
const char* GetMacOSVersion(); fn GetMacOSVersion() -> const char*;
} }
#endif #endif
#endif #endif

View file

@ -79,7 +79,8 @@ using MRMediaRemoteGetNowPlayingInfoFunction = void (*)(
NSNumber* majorVersionNumber = @(osVersion.majorVersion); NSNumber* majorVersionNumber = @(osVersion.majorVersion);
NSString* versionName = versionNames[majorVersionNumber]; NSString* versionName = versionNames[majorVersionNumber];
if (versionName == nil) versionName = @"Unknown"; if (versionName == nil)
versionName = @"Unknown";
NSString* fullVersion = NSString* fullVersion =
[NSString stringWithFormat:@"macOS %@ %@", version, versionName]; [NSString stringWithFormat:@"macOS %@ %@", version, versionName];
@ -88,37 +89,44 @@ using MRMediaRemoteGetNowPlayingInfoFunction = void (*)(
} }
@end @end
#include "util/numtypes.h"
extern "C" { extern "C" {
const char* GetCurrentPlayingTitle() { fn GetCurrentPlayingTitle() -> const char* {
NSDictionary* metadata = [Bridge currentPlayingMetadata]; NSDictionary* metadata = [Bridge currentPlayingMetadata];
if (metadata == nil) return nullptr; if (metadata == nil)
return nullptr;
NSString* title = NSString* title =
[metadata objectForKey:@"kMRMediaRemoteNowPlayingInfoTitle"]; [metadata objectForKey:@"kMRMediaRemoteNowPlayingInfoTitle"];
if (title) return strdup([title UTF8String]); if (title)
return strdup([title UTF8String]);
return nullptr; return nullptr;
} }
const char* GetCurrentPlayingArtist() { fn GetCurrentPlayingArtist() -> const char* {
NSDictionary* metadata = [Bridge currentPlayingMetadata]; NSDictionary* metadata = [Bridge currentPlayingMetadata];
if (metadata == nil) return nullptr; if (metadata == nil)
return nullptr;
NSString* artist = NSString* artist =
[metadata objectForKey:@"kMRMediaRemoteNowPlayingInfoArtist"]; [metadata objectForKey:@"kMRMediaRemoteNowPlayingInfoArtist"];
if (artist) return strdup([artist UTF8String]); if (artist)
return strdup([artist UTF8String]);
return nullptr; return nullptr;
} }
const char* GetMacOSVersion() { fn GetMacOSVersion() -> const char* {
NSString* version = [Bridge macOSVersion]; NSString* version = [Bridge macOSVersion];
if (version) return strdup([version UTF8String]); if (version)
return strdup([version UTF8String]);
return nullptr; return nullptr;
} }