From b7596674ab7e8c941fc21bd3c0b58ed98efd7819 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 22 May 2026 10:55:16 +1200 Subject: [PATCH] Add host platform detection and macOS styles Expose host platform to the UI and apply platform-specific styling. Backend: add CurrentPlatformName() and include "platform" in the JSON sent from NebulaController so the frontend can know the host OS. Frontend: detectHostPlatform() initializes state.platform, applyPlatform() sets a platform-* body class, and applyState() applies it. CSS: add .platform-macos rules to adjust title padding and hide window controls on macOS. Also fix FilePathToUrl to avoid producing an extra slash when the encoded path already starts with '/'. --- src/app/nebula_controller.cpp | 13 +++++++++++++ src/ui/paths.cpp | 2 +- ui/css/chrome.css | 8 ++++++++ ui/js/chrome.js | 16 ++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/app/nebula_controller.cpp b/src/app/nebula_controller.cpp index 60e3ed0..2733785 100644 --- a/src/app/nebula_controller.cpp +++ b/src/app/nebula_controller.cpp @@ -165,6 +165,18 @@ std::string GetChromeDisplayUrl(const std::string& url) { return nebula::ui::IsInternalHomeUrl(url) ? std::string{} : url; } +const char* CurrentPlatformName() { +#if defined(_WIN32) + return "windows"; +#elif defined(__APPLE__) + return "macos"; +#elif defined(__linux__) + return "linux"; +#else + return "unknown"; +#endif +} + void SetBrowserVisible(CefRefPtr browser, bool visible) { if (!browser) { return; @@ -1127,6 +1139,7 @@ void NebulaController::SendChromeState(const nebula::browser::NebulaTab& tab) { ",\"canGoForward\":" + std::string(tab.CanGoForward() ? "true" : "false") + ",\"favicon\":\"" + nebula::browser::JsonEscape(tab.favicon_url) + "\"" + ",\"zoomLevel\":" + std::to_string(zoom_level) + + ",\"platform\":\"" + CurrentPlatformName() + "\"" + ",\"tabs\":" + tabs_json + "});"; diff --git a/src/ui/paths.cpp b/src/ui/paths.cpp index 7afa8d3..ae4f3d6 100644 --- a/src/ui/paths.cpp +++ b/src/ui/paths.cpp @@ -174,7 +174,7 @@ std::string FilePathToUrl(std::filesystem::path path) { for (char ch : value) { encoded += ch == ' ' ? "%20" : std::string(1, ch); } - return "file:///" + encoded; + return encoded.starts_with('/') ? "file://" + encoded : "file:///" + encoded; } std::string GetChromeUrl() { diff --git a/ui/css/chrome.css b/ui/css/chrome.css index 361b6bb..eb732eb 100644 --- a/ui/css/chrome.css +++ b/ui/css/chrome.css @@ -89,6 +89,10 @@ button:disabled { background: var(--bg); } +.platform-macos .title-row { + padding: 0 12px 0 78px; +} + /* ── Tabs ───────────────────────────────────────────────────── */ .tabs { @@ -257,6 +261,10 @@ button:disabled { color: white; } +.platform-macos .window-controls { + display: none; +} + /* ── Toolbar ────────────────────────────────────────────────── */ .toolbar { diff --git a/ui/js/chrome.js b/ui/js/chrome.js index 474f3d5..99b5c3d 100644 --- a/ui/js/chrome.js +++ b/ui/js/chrome.js @@ -28,9 +28,24 @@ const state = { canGoBack: false, canGoForward: false, favicon: '', + platform: detectHostPlatform(), tabs: [] }; +function detectHostPlatform() { + const platform = `${navigator.userAgentData?.platform || ''} ${navigator.platform || ''} ${navigator.userAgent || ''}`.toLowerCase(); + if (platform.includes('mac')) return 'macos'; + if (platform.includes('win')) return 'windows'; + if (platform.includes('linux')) return 'linux'; + return 'unknown'; +} + +function applyPlatform(platform) { + const normalized = ['macos', 'windows', 'linux'].includes(platform) ? platform : 'unknown'; + document.body.classList.remove('platform-macos', 'platform-windows', 'platform-linux', 'platform-unknown'); + document.body.classList.add(`platform-${normalized}`); +} + function hexToRgb(hex) { if (!hex || typeof hex !== 'string') return null; let normalized = hex.trim().replace(/^#/, ''); @@ -191,6 +206,7 @@ function renderTabs() { function applyState(nextState) { Object.assign(state, nextState || {}); + applyPlatform(state.platform); const title = state.title || 'New Tab'; const url = state.url || '';