#ifdef __HAIKU__ // clang-format off #include // For BFile #include // For BAppFileInfo and version_info #include // B_OK, strerror, status_t #include // get_system_info, system_info #include // PATH_MAX #include // std::strlen #include // DBUS_TYPE_* #include // DBUS_BUS_SESSION #include // BPackageKit::BPackageInfoSet #include // BPackageKit::BPackageInfo #include // BPackageKit::BPackageRoster #include // ucred, getsockopt, SOL_SOCKET, SO_PEERCRED #include // statvfs #include // std::move #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 "os.hpp" // clang-format on using namespace util::types; using util::error::DracError, util::error::DracErrorCode; using util::helpers::GetEnv; namespace os { fn GetOSVersion() -> Result { BFile file; status_t status = file.SetTo("/boot/system/lib/libbe.so", B_READ_ONLY); if (status != B_OK) return Err(DracError(DracErrorCode::InternalError, "Error opening /boot/system/lib/libbe.so")); BAppFileInfo appInfo; status = appInfo.SetTo(&file); if (status != B_OK) return Err(DracError(DracErrorCode::InternalError, "Error initializing BAppFileInfo")); version_info versionInfo; status = appInfo.GetVersionInfo(&versionInfo, B_APP_VERSION_KIND); if (status != B_OK) return Err(DracError(DracErrorCode::InternalError, "Error reading version info attribute")); String versionShortString = versionInfo.short_info; if (versionShortString.empty()) return Err(DracError(DracErrorCode::InternalError, "Version info short_info is empty")); return std::format("Haiku {}", versionShortString); } fn GetMemInfo() -> Result { system_info sysinfo; const status_t status = get_system_info(&sysinfo); if (status != B_OK) return Err(DracError(DracErrorCode::InternalError, std::format("get_system_info failed: {}", strerror(status)))); return static_cast(sysinfo.max_pages) * B_PAGE_SIZE; } 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 { return "app_server"; } fn GetDesktopEnvironment() -> Result { return "Haiku Desktop Environment"; } 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 hostnameBuffer {}; if (gethostname(hostnameBuffer.data(), hostnameBuffer.size()) != 0) return Err(DracError( DracErrorCode::ApiUnavailable, std::format("gethostname() failed: {} (errno {})", strerror(errno), errno) )); hostnameBuffer.at(HOST_NAME_MAX) = '\0'; return String(hostnameBuffer.data(), hostnameBuffer.size()); } fn GetKernelVersion() -> Result { system_info sysinfo; const status_t status = get_system_info(&sysinfo); if (status != B_OK) return Err(DracError(DracErrorCode::InternalError, std::format("get_system_info failed: {}", strerror(status)))); return std::to_string(sysinfo.kernel_version); } fn GetDiskUsage() -> Result { struct statvfs stat; if (statvfs("/boot", &stat) == -1) return Err(DracError::withErrno(std::format("Failed to get filesystem stats for '/boot' (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, }; } fn GetPackageCount() -> Result { u64 count = 0; BPackageKit::BPackageRoster roster; BPackageKit::BPackageInfoSet packageList; const status_t status = roster.GetActivePackages(BPackageKit::B_PACKAGE_INSTALLATION_LOCATION_SYSTEM, packageList); if (status != B_OK) return Err(DracError(DracErrorCode::ApiUnavailable, "Failed to get active package list")); count += static_cast(packageList.CountInfos()); if (Result sharedCount = shared::GetPackageCount()) count += *sharedCount; else debug_at(sharedCount.error()); return count; } } // namespace os #endif // __HAIKU__