9 Commits

Author SHA1 Message Date
andrew adefa1706e Added OS context menu for linux 2026-02-25 11:57:31 +13:00
andrew 8b87a07d1b Merge remote-tracking branch 'origin/main' 2026-02-25 11:21:04 +13:00
andrew a101899d9c Add RGB CSS variables for theme colors
Introduce --primary-rgb, --accent-rgb, --success-rgb and --warning-rgb and replace hardcoded rgba(...) usages in CSS with rgba(var(--*-rgb), alpha) to allow dynamic alpha blending from theme colors. Add a hexToRgb helper in setup.js and populate these RGB variables in applyThemeToSetupPage (handles 3- and 6-digit hex and validates input), so runtime theme changes can drive translucent shadows, backgrounds and highlights.
2026-02-24 21:00:38 +13:00
andrew 0b51d133a4 Archive Steam docs; add itch.io upload guide
Move legacy Steam-related docs into documentation/archived and add a new UPLOAD-ITCH.md describing how to publish builds to itch.io with Butler. Update top-level README to state official releases will be on itch.io. Remove Steam-specific UI/features: drop steamCloudOptIn from first-run preferences, remove the Steam Cloud teaser and summary from the setup flow, and adjust settings/setup copy to reference handheld devices and non‑Steam distribution. Also make a small wording tweak in the plugins doc about rendererPreload.
2026-02-24 20:50:00 +13:00
andrew 6b2a7c8404 Hide default linux task buttons
The electron app now uses the built in menu bar on linux
2026-02-24 15:19:41 +13:00
andrew 618ea7d12d Prefer usr/user-data for portable data
Rename and consolidate portable user-data to usr/user-data and update tooling and runtime to match. Updated appdir-example/run-nebula.sh to point at usr/user-data; make-appdir.sh and update-appdir.sh now patch/copy the launcher, create the usr/user-data directory and set secure permissions (mkdir -p, chmod 700), and remove sed backups. portable-data.js now defaults to app-local user-data and prefers <AppDir>/usr/user-data on Linux AppDir builds (with safe fs checks). Also minor UI change in renderer/setup.css to make the footer background transparent and disable the backdrop blur.
2026-02-19 17:33:26 +13:00
andrew 0137df60dd Updated create and update appdir files to include Nebula-Desktop and Nebula-Controller files 2026-02-01 21:29:13 +13:00
Andrew Zambazos b725d5a672 Revise project description and status in README
Updated project status and focus, changing from Steam-centric to Linux-first. Clarified maintenance mode and community engagement.
2026-02-01 21:12:20 +13:00
Andrew Zambazos 86f3b10e80 Update README to reflect dormant project status
Updated project status and description in README.md to reflect that development is paused and the project is in a dormant state. Added a section clarifying the implications of this status and maintained the licensing information.
2026-01-31 19:28:27 +13:00
21 changed files with 477 additions and 107 deletions
View File
+53 -18
View File
@@ -1,13 +1,13 @@
# NEBULA BROWSER
*A controller-first browser originally designed for SteamOS*
*A controller-first browser, now re-focused as a Linux-first project*
---
### ⚠️ Final Release • Project Archived ⚠️
### Limited Development • Project in Maintenance Mode
Nebula Browser has reached end of support and is no longer under active development.
This repository represents the final archived state of the project.
Nebula Browser is no longer under active, full-time development.
The project is maintained on an occasional basis, with updates made when time and interest allow.
---
@@ -15,34 +15,71 @@ This repository represents the final archived state of the project.
![Nebula Logo](assets/images/Logos/Nebula-Logo.svg)
Nebula is a customizable and privacy-focused web browser built with Electron. It was designed to be a lightweight, secure, and user-friendly browser with a strong emphasis on controller-first interaction, performance, and privacy.
Nebula is a customizable and privacy-focused web browser built with Electron. It is designed to be lightweight, secure, and user-friendly, with a strong emphasis on **controller-first and keyboard-driven interaction**, performance, and privacy.
While originally conceived for SteamOS and the Steam Deck, Nebula has since evolved into a **Linux-first experimental browser**, aimed at alternative input setups, handheld PCs, accessibility use cases, and living room environments.
---
## Project Status
**Status:** Archived
**Maintenance:** Ended
**Future Updates:** None planned
**Status:** Maintenance Mode
**Maintenance:** Occasional updates
**Development:** No active roadmap
**Focus:** Linux-first (desktop, handhelds, and alternative input setups)
Nebula is no longer actively maintained. The source code remains available for reference, learning, and archival purposes.
Nebula is not a primary or actively scheduled project. Updates may occur sporadically and are not guaranteed. The repository remains open for use, modification, and experimentation under the MIT license.
This repository reflects a stable snapshot of the project, with incremental improvements added when appropriate.
---
## Why Nebula Was Archived
## Why the Project Changed Direction
Nebula was created with a very specific goal in mind: to be a **controller-first browser that lived inside the Steam ecosystem**, particularly for Steam Deck and SteamOS users who wanted a web experience without relying on desktop mode, keyboards, or external workarounds.
Nebula was originally created with a very specific goal: to be a **controller-first browser designed to live inside the Steam ecosystem**, especially for Steam Deck and SteamOS users who wanted a seamless web experience without relying on desktop mode, keyboards, or external workarounds.
During the Steam review process, Valve determined that Nebula does not fit within Steams allowed categories for non-game software. As a result, the application was permanently retired from Steam and cannot be distributed on the platform.
During the Steam review process, Valve determined that Nebula does not fit within Steams allowed categories for non-game software. As a result, the browser could not be distributed on the Steam Store.
While Nebula can function as a desktop browser, distributing it outside of Steam fundamentally changes the experience it was designed to provide. Requiring third-party installation methods or desktop mode defeats the core problem Nebula was built to solve.
At the time, Steam distribution was considered a core pillar of the project, and development was paused as the original vision could no longer be fulfilled in its intended form.
Rather than ship a compromised version that no longer aligns with its original purpose, the decision was made to formally conclude development and archive the project.
This repository preserves Nebula in its final state as a complete exploration of controller-first browser design.
Since then, community feedback has highlighted broader interest in Nebulas **input model and interaction design**, beyond Steam or SteamOS alone. Because of this, Nebula has been re-contextualized as a Linux-first project rather than a Steam-native application.
---
## What This Means Now
* Nebula is **not abandoned**
* It is **no longer a main or priority project**
* Development happens **occasionally and without a fixed schedule**
* The project is no longer tied to Steam or SteamOS
* Linux desktop users, handhelds, and alternative input setups are the primary audience
Nebula exists as an ongoing experiment in controller- and keyboard-driven web navigation. It may evolve further, remain stable, or inspire forks and derivative projects.
---
## Distribution
Nebula may be distributed outside of Steam through platforms such as:
* GitHub (source and releases)
* itch.io
* Flatpak / Flathub
Availability and packaging may change over time and are not guaranteed.
Official releases for Nebula will be published on itch.io; Steam distribution
is not available due to the Steam review outcome described above.
---
## Licensing
Nebula Browser is licensed under the MIT License.
You are free to use, modify, and build upon the project.
## Features
* **Privacy Control:** Easily clear your browsing data (history, cookies, cache, local storage, and more).
@@ -131,9 +168,7 @@ Contributions are welcome! Please read our [contributing guidelines](documentati
* [Electron](https://www.electronjs.org/)
* HTML, CSS, JavaScript
## License
This project is licensed under the MIT License. [Read More](documentation/MIT.md)
## Documentation
+2 -2
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# Run Nebula with portable data storage
# User data (cookies, history, bookmarks) is stored in usr/data/ alongside the app.
# User data (cookies, history, bookmarks) is stored in usr/user-data/ alongside the app.
set -e
HERE="$(cd "$(dirname "$0")" && pwd)"
@@ -11,7 +11,7 @@ export LD_LIBRARY_PATH="$HERE/usr/lib:$HERE/usr/lib64:$LD_LIBRARY_PATH"
# --- PORTABLE DATA CONFIGURATION ---
# Store user data in a local folder for portable operation
PORTABLE_DATA_DIR="$HERE/usr/data"
PORTABLE_DATA_DIR="$HERE/usr/user-data"
export NEBULA_PORTABLE=1
export NEBULA_PORTABLE_PATH="$PORTABLE_DATA_DIR"
+113
View File
@@ -0,0 +1,113 @@
# Uploading releases to itch.io using Butler
This document explains how to prepare and upload Nebula releases to itch.io using Butler, covering macOS, Windows, and Linux.
## Overview
Butler (itch.io) is the recommended channel for distributing Nebula releases. The process is:
1. Build your platform-specific artifact.
2. Install and authenticate `butler`.
3. Push the build to your `username/game:channel` with `butler push`.
## Install Butler
- macOS / Linux / Windows: Download the appropriate Butler binary from the official itch.io Butler releases (follow the official docs). Unpack, make executable, and place it on your `PATH`.
Example (Linux/macOS):
```bash
# after downloading 'butler' binary
chmod +x butler
sudo mv butler /usr/local/bin/
```
On Windows, place `butler.exe` in a folder on `PATH` or use it directly from the build folder.
## Authenticate
Run:
```bash
butler login
```
This opens a browser-based authentication flow. Verify with:
```bash
butler whoami
```
If automation is required, consult the official Butler docs for API-key/token login options.
## Prepare platform artifacts
macOS
- Create a zip of your `.app` bundle (keep the `.app` as a top-level item inside the zip):
```bash
ditto -c -k --sequesterRsrc --keepParent MyApp.app MyApp-mac.zip
```
Windows
- Zip the folder containing your `.exe` and runtime files, or create an installer and zip the installer.
```powershell
Compress-Archive -Path .\build\MyApp\* -DestinationPath MyApp-windows.zip
```
Linux
- Create a tarball (or zip) of the Linux runtime files:
```bash
tar -czf MyApp-linux.tar.gz -C build/linux .
```
Note: ensure the main binary has executable permissions before archiving.
## Push to itch.io
Basic command:
```bash
butler push <path> <username>/<game>:<channel>
```
Examples:
```bash
# macOS build
butler push MyApp-mac.zip myuser/nebulabrowser:mac
# Windows build
butler push MyApp-windows.zip myuser/nebulabrowser:windows
# Linux build
butler push MyApp-linux.tar.gz myuser/nebulabrowser:linux
```
Set a release version for itch.io using `--userversion`:
```bash
butler push MyApp-mac.zip myuser/nebulabrowser:mac --userversion 1.2.3
```
## Recommended channel strategy
- `stable` or `default` — production releases
- `beta` — pre-release testing
- Use platform-specific channels (e.g., `mac`, `windows`, `linux`) if you want separate channels per OS
## Tips
- Keep artifacts small and platform-specific to reduce download size.
- Verify the upload with `butler whoami` and by visiting your game page on itch.io.
- When testing on macOS, notarization and Gatekeeper may affect distribution; provide clear install instructions on your itch page.
## Rollback
Butler supports pushing to a channel multiple times; the latest pushed build becomes the current for that channel. To revert, push a previous artifact or use the itch.io web UI to select a previous build.
## References
Consult the official Butler documentation for advanced usage (credentials, automated CI uploads, delta uploads, and platform-specific packaging recommendations).
+3
View File
@@ -0,0 +1,3 @@
# GPU-FIX-README (archived)
This file was present at repository root and has been archived here for reference. The canonical GPU fix documentation lives under `documentation/`.
@@ -31,7 +31,7 @@ Example:
Fields:
- id: Unique id. Defaults to folder name if omitted.
- main: Optional entry for main process integration.
- rendererPreload: Optional file injected into the preload. Use it to expose limited APIs.
- rendererPreload: Optional file injected into the preload. Use it to expose a safe surface to the page.
- categories: Optional string or array of strings used for organizing/filtering plugins in UI and APIs. Example: ["AI", "Utilities"].
- authors: Optional string or array of strings/objects describing authors. Objects support { name, email, url }. In APIs/UI, names are displayed.
- enabled: Defaults to true.
@@ -1,5 +1,10 @@
Converting extracted AppImage (`squashfs-root`) into a distributable AppDir for Steam
> Note: Nebula will not be distributed on the Steam Store. This document is
> kept for reference and for users who run Nebula via Steam as a non-Steam
> shortcut. Official distribution will be handled via itch.io and other
> non-Steam channels.
If your environment lacks `rsync`, use `cp -a` to copy the extracted AppImage into a clean AppDir and prepare it for upload to Steam.
1) Copy the extracted AppImage to an AppDir folder
@@ -77,7 +82,7 @@ Nebula solves this by:
### Gamepad API (for Developers)
The gamepad handler exposes an API via `window.gamepadAPI`:
The gamepad handler exposes an API via `window.gamepadAPI`.
```javascript
// Check if gamepad handler is initialized
@@ -142,7 +147,6 @@ If Steam is still applying mouse emulation:
- **Steam Deck / SteamOS Gaming Mode**:
- Open Nebula → press the Steam button → **Controller Settings** (or the controller icon)
- Set the layout to a **Gamepad** template (not “Keyboard/Mouse”), or disable Steam Input if the toggle is available
- This stops Steam from translating controller input into keyboard/mouse events (“Desktop Layout” behavior).
If you **dont see a Controller tab** (common when the Steam entry is treated as an “application/tool”):
- Use **Big Picture / Gaming Mode** and edit the **Controller Layout** for that specific entry.
@@ -1,5 +1,9 @@
# Linux / SteamOS Build Upload Guide (SteamCMD)
> Note: Nebula will not be distributed on the Steam Store. This Steam upload
> guide is retained for historical/reference purposes only. Official releases
> will be published on itch.io and other non-Steam channels.
This guide explains how to upload the **Linux / SteamOS** build of Nebula Browser to Steam using **SteamCMD**. It is tailored to the current project layout on Steam Deck / Linux.
---
+101 -17
View File
@@ -293,7 +293,6 @@ function getDesktopViewState(win) {
function createMenuPopupWindow(parentWin) {
const menuWin = new BrowserWindow({
parent: parentWin,
modal: false,
frame: false,
transparent: true,
@@ -301,7 +300,8 @@ function createMenuPopupWindow(parentWin) {
show: false,
alwaysOnTop: true,
skipTaskbar: true,
focusable: true,
focusable: false,
fullscreenable: false,
hasShadow: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
@@ -314,6 +314,7 @@ function createMenuPopupWindow(parentWin) {
menuWin.setMenu(null);
try { menuWin.setAlwaysOnTop(true, 'pop-up-menu'); } catch {}
try { menuWin.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); } catch {}
const hideMenu = () => {
if (!menuWin.isDestroyed()) menuWin.hide();
@@ -334,20 +335,45 @@ function createMenuPopupWindow(parentWin) {
}
function positionMenuPopup(parentWin, menuWin, anchorRect) {
if (!parentWin || !menuWin || !anchorRect) return;
const contentBounds = parentWin.getContentBounds();
const display = screen.getDisplayMatching(contentBounds);
const workArea = display?.workArea || contentBounds;
if (!parentWin || !menuWin) return;
const width = MENU_POPUP_SIZE.width;
const height = MENU_POPUP_SIZE.height;
let x = Math.round(contentBounds.x + anchorRect.x + anchorRect.width - width);
let y = Math.round(contentBounds.y + anchorRect.y + anchorRect.height + 6);
if (x < workArea.x) x = workArea.x;
if (y < workArea.y) y = workArea.y;
if (x + width > workArea.x + workArea.width) x = workArea.x + workArea.width - width;
if (y + height > workArea.y + workArea.height) y = workArea.y + workArea.height - height;
const parentBounds = parentWin.getBounds();
const contentBounds = parentWin.getContentBounds();
const rect = anchorRect && Number.isFinite(anchorRect.x) && Number.isFinite(anchorRect.y)
? anchorRect
: null;
let x;
let y;
if (rect) {
x = Math.round(contentBounds.x + rect.x + rect.width - width);
y = Math.round(contentBounds.y + rect.y + rect.height + 6);
} else {
x = Math.round(parentBounds.x + parentBounds.width - width - 12);
y = Math.round(parentBounds.y + 52);
}
const display = screen.getDisplayNearestPoint({ x, y });
const workArea = display?.workArea || { x: 0, y: 0, width: 1920, height: 1080 };
if (x < workArea.x) x = workArea.x + 6;
if (x + width > workArea.x + workArea.width) x = workArea.x + workArea.width - width - 6;
if (y < workArea.y) y = workArea.y + 6;
if (y + height > workArea.y + workArea.height) {
const aboveY = rect
? Math.round(contentBounds.y + rect.y - height - 6)
: Math.round(parentBounds.y + parentBounds.height - height - 12);
if (aboveY >= workArea.y) {
y = aboveY;
} else {
y = workArea.y + workArea.height - height - 6;
}
}
menuWin.setBounds({ x, y, width, height }, false);
}
@@ -612,7 +638,6 @@ async function completeFirstRun(preferences = {}) {
selectedThemeId: preferences.selectedTheme || 'default',
defaultBrowserAttempted: preferences.defaultBrowserSet || false,
defaultBrowserSet: preferences.defaultBrowserSet || false,
steamCloudOptIn: preferences.steamCloudOptIn || false,
completedAt: new Date().toISOString()
};
@@ -1115,6 +1140,13 @@ function createWindow(startUrl, bigPictureMode = false) {
frame: false,
backgroundColor: '#0b0d10',
});
} else if (process.platform === 'linux') {
// On Linux, use a frameless window so only the in-app controls are shown.
Object.assign(windowOptions, {
frame: false,
backgroundColor: '#0b0d10',
skipTaskbar: true
});
} else {
windowOptions.frame = true;
}
@@ -2111,7 +2143,35 @@ ipcMain.handle('browserview-set-bounds', (event, bounds) => {
}
});
// Overlay menu (to sit above BrowserView)
// Native popup menu for the burger button — renders above BrowserView on all platforms
ipcMain.handle('show-app-menu', (event, payload = {}) => {
try {
const win = BrowserWindow.fromWebContents(event.sender);
if (!win) return;
const zoomFactor = win.webContents.getZoomFactor?.() || 1;
const zoomLabel = `${Math.round(zoomFactor * 100)}%`;
const template = [
{ label: 'Settings', click: () => win.webContents.send('menu-command', { cmd: 'open-settings' }) },
{ type: 'separator' },
{ label: '\u{1F3AE} Big Picture Mode', click: () => win.webContents.send('menu-command', { cmd: 'big-picture' }) },
{ type: 'separator' },
{ label: 'Toggle Developer Tools', click: () => win.webContents.send('menu-command', { cmd: 'toggle-devtools' }) },
{ type: 'separator' },
{ label: `Zoom: ${zoomLabel}`, enabled: false },
{ label: 'Zoom In', accelerator: 'CmdOrCtrl+=', click: () => win.webContents.send('menu-command', { cmd: 'zoom-in' }) },
{ label: 'Zoom Out', accelerator: 'CmdOrCtrl+-', click: () => win.webContents.send('menu-command', { cmd: 'zoom-out' }) },
{ type: 'separator' },
{ label: 'Hard Reload (Ignore Cache)', click: () => win.webContents.send('menu-command', { cmd: 'hard-reload' }) },
{ label: 'Reload Fresh (Add Cache-Buster)', click: () => win.webContents.send('menu-command', { cmd: 'fresh-reload' }) },
];
const menu = Menu.buildFromTemplate(template);
menu.popup({ window: win, x: payload.x, y: payload.y });
} catch (err) {
console.error('[show-app-menu] Error:', err);
}
});
// Overlay menu (to sit above BrowserView) — kept for backwards compat but no longer primary
ipcMain.on('menu-popup-toggle', (event, payload = {}) => {
try {
const parentWin = BrowserWindow.fromWebContents(event.sender);
@@ -2128,7 +2188,24 @@ ipcMain.on('menu-popup-toggle', (event, payload = {}) => {
return;
}
positionMenuPopup(parentWin, menuWin, payload.anchorRect);
const anchorRect = payload?.anchorRect;
const anchorScreenPoint = payload?.anchorScreenPoint;
if (anchorScreenPoint && Number.isFinite(anchorScreenPoint.x) && Number.isFinite(anchorScreenPoint.y)) {
const width = MENU_POPUP_SIZE.width;
const height = MENU_POPUP_SIZE.height;
let x = Math.round(anchorScreenPoint.x - width);
let y = Math.round(anchorScreenPoint.y + 6);
const display = screen.getDisplayNearestPoint({ x, y });
const workArea = display?.workArea || { x: 0, y: 0, width: 1920, height: 1080 };
if (x < workArea.x) x = workArea.x + 6;
if (x + width > workArea.x + workArea.width) x = workArea.x + workArea.width - width - 6;
if (y < workArea.y) y = workArea.y + 6;
if (y + height > workArea.y + workArea.height) y = workArea.y + workArea.height - height - 6;
menuWin.setBounds({ x, y, width, height }, false);
} else {
positionMenuPopup(parentWin, menuWin, anchorRect);
}
const initPayload = { theme: payload.theme || null };
const sendInit = () => {
@@ -2142,8 +2219,15 @@ ipcMain.on('menu-popup-toggle', (event, payload = {}) => {
}
} catch {}
menuWin.show();
menuWin.focus();
try {
if (typeof menuWin.showInactive === 'function') {
menuWin.showInactive();
} else {
menuWin.show();
}
} catch {
menuWin.show();
}
} catch {}
});
+22 -8
View File
@@ -14,8 +14,8 @@ fi
mkdir -p "$DEST"
cp -a "$SRC/." "$DEST/"
# Ensure launcher/binary exist
if [ -f "$DEST/run-nebula.sh" ]; then
# Ensure launcher/binary exist (prefer extracted run-nebula.sh as fallback)
if [ -f "$DEST/run-nebula.sh" ] && [ ! -f "$DEST/Nebula" ]; then
mv "$DEST/run-nebula.sh" "$DEST/Nebula" 2>/dev/null || true
fi
chmod +x "$DEST/Nebula" || true
@@ -67,6 +67,7 @@ fi
# Copy Linux launch wrappers if present in appdir-example
if [ -f "$SCRIPT_DIR/appdir-example/run-nebula.sh" ]; then
cp "$SCRIPT_DIR/appdir-example/run-nebula.sh" "$DEST/run-nebula.sh"
sed -i.bak 's|usr/data|usr/user-data|g' "$DEST/run-nebula.sh" && rm -f "$DEST/run-nebula.sh.bak"
chmod +x "$DEST/run-nebula.sh" || true
fi
if [ -f "$SCRIPT_DIR/appdir-example/steam_appid.txt" ]; then
@@ -76,14 +77,27 @@ if [ -f "$SCRIPT_DIR/appdir-example/nebula.desktop" ]; then
cp "$SCRIPT_DIR/appdir-example/nebula.desktop" "$DEST/nebula.desktop"
cp "$SCRIPT_DIR/appdir-example/nebula.desktop" "$DEST/usr/share/applications/nebula.desktop"
fi
# Ensure root launcher exists (from example if needed)
if [ -f "$SCRIPT_DIR/appdir-example/Nebula" ]; then
cp "$SCRIPT_DIR/appdir-example/Nebula" "$DEST/Nebula"
chmod +x "$DEST/Nebula" || true
# Ensure root launchers exist (from example if needed)
if [ -f "$SCRIPT_DIR/appdir-example/Nebula-Desktop" ]; then
cp "$SCRIPT_DIR/appdir-example/Nebula-Desktop" "$DEST/Nebula-Desktop"
chmod +x "$DEST/Nebula-Desktop" || true
fi
if [ -f "$SCRIPT_DIR/appdir-example/Nebula-Controller" ]; then
cp "$SCRIPT_DIR/appdir-example/Nebula-Controller" "$DEST/Nebula-Controller"
chmod +x "$DEST/Nebula-Controller" || true
fi
# Fallback: create Nebula as symlink to Nebula-Desktop
if [ ! -f "$DEST/Nebula" ] && [ -f "$DEST/Nebula-Desktop" ]; then
ln -sf "Nebula-Desktop" "$DEST/Nebula"
fi
# Fix permissions
chmod -R a+r "$DEST/usr/share/icons/hicolor/256x256/apps" || true
chmod +x "$DEST/Nebula" || true
chmod +x "$DEST/Nebula" "$DEST/Nebula-Desktop" "$DEST/Nebula-Controller" || true
mkdir -p "$DEST/usr/user-data"
chmod 700 "$DEST/usr/user-data" || true
echo "AppDir assembled at $DEST. Run with: $DEST/run-nebula.sh"
echo "AppDir assembled at $DEST."
echo " Desktop mode: $DEST/Nebula-Desktop"
echo " Controller mode: $DEST/Nebula-Controller"
echo " Default: $DEST/Nebula (symlink to Nebula-Desktop)"
+4 -1
View File
@@ -58,7 +58,10 @@
"icon": "assets/images/Logos/Nebula-Favicon.ico"
},
"linux": {
"icon": "assets/images/Logos/Nebula-Favicon.png"
"icon": "assets/images/Logos/Nebula-Favicon.png",
"target": [
"AppImage"
]
}
}
}
+18 -14
View File
@@ -85,8 +85,8 @@ class PortableDataManager {
/**
* Get the portable data directory path
* Uses NEBULA_PORTABLE_PATH if set, otherwise creates 'user-data' in Documents/My Games/<AppName>
* with a safe fallback to the app directory.
* Uses NEBULA_PORTABLE_PATH if set, otherwise creates app-local 'user-data'.
* Linux AppDir builds prefer 'usr/user-data' to keep writable data inside AppDir.
*/
getPortableDataPath() {
if (this._portableDataPath !== null) {
@@ -111,19 +111,23 @@ class PortableDataManager {
}
}
// Default: prefer Documents/My Games/<AppName>/user-data
let dataPath = '';
try {
const docsDir = app.getPath('documents');
const appName = app.getName() || 'NebulaBrowser';
dataPath = path.join(docsDir, 'My Games', appName, 'user-data');
} catch (err) {
console.warn('[Portable] Failed to resolve Documents path, using app directory');
}
// Default: app-local user data
// - Windows: beside the executable
// - macOS: inside .app Contents (portable bundle)
// - Linux AppDir: <AppDir>/usr/user-data
// - Other Linux/dev: beside the app root
const appRoot = this._getAppRootDir();
let dataPath = path.join(appRoot, 'user-data');
if (!dataPath) {
const appRoot = this._getAppRootDir();
dataPath = path.join(appRoot, 'user-data');
if (process.platform === 'linux') {
const appDirUsr = path.join(appRoot, 'usr');
try {
if (fs.existsSync(appDirUsr) && fs.statSync(appDirUsr).isDirectory()) {
dataPath = path.join(appDirUsr, 'user-data');
}
} catch (err) {
console.warn('[Portable] Could not inspect Linux AppDir usr path, using app root user-data');
}
}
// Validate the path
+1 -1
View File
@@ -88,7 +88,7 @@
<div class="menu-wrapper">
<button id="menu-btn"></button>
<div id="menu-popup" class="hidden">
<button onclick="openSettings()">Settings</button>
<button id="open-settings-btn">Settings</button>
<!-- You can add more options here -->
<button id="bigpicture-btn" title="Launch Big Picture Mode (Controller/Steam Deck UI)">🎮 Big Picture Mode</button>
<button id="devtools-btn" title="Open / Close Developer Tools">Toggle Developer Tools</button>
+65 -14
View File
@@ -477,8 +477,7 @@ ipcRenderer.on('browserview-host-message', (payload) => {
});
// Commands from the overlay menu window
ipcRenderer.on('menu-command', (payload) => {
const cmd = payload?.cmd;
function runMenuCommand(cmd) {
if (!cmd) return;
switch (cmd) {
case 'open-settings':
@@ -508,6 +507,45 @@ ipcRenderer.on('menu-command', (payload) => {
default:
break;
}
}
const isLinux = document.body.classList.contains('platform-linux');
function hideMainMenuPopup() {
if (!isLinux) {
// Windows/macOS: hide the popup window
ipcRenderer.send('menu-popup-hide');
}
}
function showNativeAppMenu() {
if (!menuBtn) return;
const rect = menuBtn.getBoundingClientRect();
if (isLinux) {
// Linux: use native OS menu (renders above BrowserView reliably)
ipcRenderer.invoke('show-app-menu', {
x: Math.round(rect.right - 200),
y: Math.round(rect.bottom + 4)
}).catch(() => {});
} else {
// Windows/macOS: use the custom popup window
const theme = currentThemeColors ? { colors: currentThemeColors } : null;
ipcRenderer.send('menu-popup-toggle', {
anchorRect: { x: rect.left, y: rect.top, width: rect.width, height: rect.height },
anchorScreenPoint: {
x: Math.round(window.screenX + rect.right),
y: Math.round(window.screenY + rect.bottom)
},
theme
});
}
}
ipcRenderer.on('menu-command', (payload) => {
const cmd = payload?.cmd;
runMenuCommand(cmd);
hideMainMenuPopup();
});
// Auto-open on download start is disabled by design now.
@@ -1145,18 +1183,12 @@ let ringSvgEl = null;
// Open/close on button click; stop propagation so outside-click handler doesn't immediately close it
menuBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (!menuBtn) return;
const rect = menuBtn.getBoundingClientRect();
const theme = currentThemeColors ? { colors: currentThemeColors } : null;
ipcRenderer.send('menu-popup-toggle', {
anchorRect: { x: rect.left, y: rect.top, width: rect.width, height: rect.height },
theme
});
showNativeAppMenu();
});
// Close on Escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') ipcRenderer.send('menu-popup-hide');
if (e.key === 'Escape') hideMainMenuPopup();
if (e.key === 'Escape' && downloadsPopupEl && !downloadsPopupEl.classList.contains('hidden')) {
hideDownloadsPopup();
}
@@ -1167,7 +1199,7 @@ ipcRenderer.on('browserview-event', (payload) => {
if (!payload || !payload.type) return;
const { tabId, type } = payload;
if (type === 'focus') {
ipcRenderer.send('menu-popup-hide');
hideMainMenuPopup();
if (downloadsPopupEl && !downloadsPopupEl.classList.contains('hidden')) hideDownloadsPopup();
return;
}
@@ -1276,14 +1308,33 @@ window.addEventListener('DOMContentLoaded', () => {
bigPictureBtn.addEventListener('click', async () => {
try {
await window.bigPictureAPI.launch();
// Close the overlay menu
ipcRenderer.send('menu-popup-hide');
hideMainMenuPopup();
} catch (e) {
console.error('Failed to launch Big Picture Mode:', e);
}
});
}
if (menuPopup) {
menuPopup.addEventListener('click', (e) => {
const button = e.target instanceof HTMLElement ? e.target.closest('button') : null;
if (!button) return;
const id = button.id;
if (!id) return;
if (id !== 'zoom-in-btn' && id !== 'zoom-out-btn') {
hideMainMenuPopup();
}
e.stopPropagation();
});
}
document.addEventListener('click', (e) => {
if (!menuPopup || menuPopup.classList.contains('hidden')) return;
if (menuWrapper && !menuWrapper.contains(e.target)) {
hideMainMenuPopup();
}
});
// Cache back/forward buttons for faster updates (no need to add listeners - already in HTML)
backBtnCached = document.querySelector('.nav-left button:nth-child(1)');
fwdBtnCached = document.querySelector('.nav-left button:nth-child(2)');
@@ -1462,7 +1513,7 @@ try {
function attachCloseMenuOnInteract(el) {
if (!el) return;
const closeIfOpen = () => {
ipcRenderer.send('menu-popup-hide');
hideMainMenuPopup();
if (downloadsPopupEl && !downloadsPopupEl.classList.contains('hidden')) {
hideDownloadsPopup();
}
+2 -2
View File
@@ -36,7 +36,7 @@
<!-- Big Picture Mode -->
<div class="setting-group">
<h3>Big Picture Mode</h3>
<p class="note">A controller-friendly UI designed for Steam Deck and handheld devices.</p>
<p class="note">A controller-friendly UI designed for handheld devices (e.g., Steam Deck).</p>
<div class="setting-row">
<button id="launch-bigpicture-btn" class="primary-btn">
<span style="font-size: 18px;"></span> Launch Big Picture Mode
@@ -287,7 +287,7 @@
<div class="customization-group">
<h3>Security Updates</h3>
<p class="note" style="margin-bottom: 12px;">Nebula Browser is distributed via Steam, but the Electron runtime can be updated separately for security patches and performance improvements.</p>
<p class="note" style="margin-bottom: 12px;">Nebula Browser is distributed via itch.io and other nonSteam channels; the Electron runtime can be updated separately for security patches and performance improvements.</p>
<div class="stack">
<!-- Update Status Banner -->
+15 -11
View File
@@ -12,14 +12,18 @@
--dark-blue: #0B1C2B;
--dark-purple: #1B1035;
--primary: #7B2EFF;
--primary-rgb: 123, 46, 255;
--accent: #00C6FF;
--accent-rgb: 0, 198, 255;
--text: #E0E0E0;
--text-secondary: #A0A0A0;
--card-bg: rgba(255, 255, 255, 0.05);
--card-hover: rgba(255, 255, 255, 0.08);
--border: rgba(255, 255, 255, 0.1);
--success: #4CAF50;
--success-rgb: 76, 175, 80;
--warning: #FF9800;
--warning-rgb: 255, 152, 0;
--gradient-primary: linear-gradient(135deg, var(--accent), var(--primary));
--gradient-bg: linear-gradient(145deg, var(--bg) 0%, var(--dark-purple) 100%);
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.2);
@@ -98,7 +102,7 @@ body, html {
.progress-step.active .step-circle {
background: var(--primary);
border-color: transparent;
box-shadow: 0 4px 12px rgba(123, 46, 255, 0.4);
box-shadow: 0 4px 12px rgba(var(--primary-rgb), 0.4);
transform: scale(1.1);
}
@@ -190,7 +194,7 @@ body, html {
.setup-logo {
width: 120px;
height: 120px;
filter: drop-shadow(0 8px 24px rgba(123, 46, 255, 0.3));
filter: drop-shadow(0 8px 24px rgba(var(--primary-rgb), 0.3));
animation: float 3s ease-in-out infinite;
}
@@ -273,7 +277,7 @@ body, html {
.theme-card.selected {
border-color: var(--primary);
background: var(--card-hover);
box-shadow: 0 0 0 3px rgba(123, 46, 255, 0.2);
box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.2);
}
.theme-card.selected::after {
@@ -383,7 +387,7 @@ body, html {
.default-browser-status.is-default {
border-color: var(--success);
background: rgba(76, 175, 80, 0.1);
background: rgba(var(--success-rgb), 0.1);
}
.default-browser-status.is-default .status-icon {
@@ -396,7 +400,7 @@ body, html {
.default-browser-status.not-default {
border-color: var(--warning);
background: rgba(255, 152, 0, 0.1);
background: rgba(var(--warning-rgb), 0.1);
}
@keyframes spin {
@@ -430,7 +434,7 @@ body, html {
justify-content: center;
font-size: 4rem;
font-weight: bold;
box-shadow: 0 8px 32px rgba(123, 46, 255, 0.4);
box-shadow: 0 8px 32px rgba(var(--primary-rgb), 0.4);
animation: scaleIn 0.5s ease;
}
@@ -492,7 +496,7 @@ body, html {
/* Future Feature Teaser */
.future-feature-teaser {
background: linear-gradient(135deg, rgba(123, 46, 255, 0.1), rgba(0, 198, 255, 0.1));
background: linear-gradient(135deg, rgba(var(--primary-rgb), 0.1), rgba(var(--accent-rgb), 0.1));
border: 1px solid var(--border);
border-radius: 16px;
padding: 2rem;
@@ -531,8 +535,8 @@ body, html {
padding-bottom: 1rem;
position: sticky;
bottom: 0;
background: linear-gradient(180deg, rgba(18, 20, 24, 0), rgba(18, 20, 24, 0.85) 45%, rgba(18, 20, 24, 0.95));
backdrop-filter: blur(6px);
background: transparent;
backdrop-filter: none;
}
.btn {
@@ -554,12 +558,12 @@ body, html {
.btn-primary {
background: var(--primary);
color: white;
box-shadow: 0 4px 12px rgba(123, 46, 255, 0.3);
box-shadow: 0 4px 12px rgba(var(--primary-rgb), 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(123, 46, 255, 0.4);
box-shadow: 0 6px 20px rgba(var(--primary-rgb), 0.4);
}
.btn-primary:active {
+3 -8
View File
@@ -111,7 +111,7 @@
</div>
</div>
<!-- Step 4: Complete (Future: Steam Cloud) -->
<!-- Step 4: Complete -->
<div class="setup-step" data-step="4">
<div class="step-content">
<div class="success-icon"></div>
@@ -120,13 +120,8 @@
<div class="completion-summary" id="completion-summary">
<!-- Summary will be populated dynamically -->
</div>
<div class="future-feature-teaser">
<h3>Coming Soon: Steam Cloud Sync</h3>
<p class="teaser-text">
<span class="teaser-icon">☁️</span>
In Phase 2, you'll be able to sync your bookmarks, settings, and themes across devices using Steam Cloud.
</p>
</div>
</div>
<div class="step-actions">
<button class="btn btn-primary btn-large" id="btn-finish">Start Browsing</button>
+35 -7
View File
@@ -121,6 +121,22 @@ function getThemeById(themeId) {
return null;
}
function hexToRgb(hex) {
if (!hex || typeof hex !== 'string') return null;
let normalized = hex.trim().replace(/^#/, '');
if (normalized.length === 3) {
normalized = normalized.split('').map(char => char + char).join('');
}
if (!/^[a-fA-F\d]{6}$/.test(normalized)) return null;
const intValue = parseInt(normalized, 16);
return {
r: (intValue >> 16) & 255,
g: (intValue >> 8) & 255,
b: intValue & 255
};
}
/**
* Apply theme to the setup page UI and persist selection
*/
@@ -140,6 +156,25 @@ function applyThemeToSetupPage(theme, themeId = null) {
setVar('--primary', colors.primary, '#7B2EFF');
setVar('--accent', colors.accent, '#00C6FF');
setVar('--text', colors.text, '#E0E0E0');
setVar('--success', colors.accent, '#4CAF50');
setVar('--warning', colors.primary, '#FF9800');
const primaryRgb = hexToRgb(colors.primary || '#7B2EFF');
const accentRgb = hexToRgb(colors.accent || '#00C6FF');
const successRgb = hexToRgb(colors.accent || '#4CAF50');
const warningRgb = hexToRgb(colors.primary || '#FF9800');
if (primaryRgb) {
setVar('--primary-rgb', `${primaryRgb.r}, ${primaryRgb.g}, ${primaryRgb.b}`);
}
if (accentRgb) {
setVar('--accent-rgb', `${accentRgb.r}, ${accentRgb.g}, ${accentRgb.b}`);
}
if (successRgb) {
setVar('--success-rgb', `${successRgb.r}, ${successRgb.g}, ${successRgb.b}`);
}
if (warningRgb) {
setVar('--warning-rgb', `${warningRgb.r}, ${warningRgb.g}, ${warningRgb.b}`);
}
if (theme.gradient) {
document.body.style.background = theme.gradient;
@@ -416,13 +451,6 @@ function renderCompletionSummary() {
<div class="summary-value">${setupState.defaultBrowserSet ? 'Set as Default' : 'Not Set'}</div>
</div>
</div>
<div class="summary-item">
<div class="summary-icon"></div>
<div class="summary-content">
<div class="summary-label">Steam Cloud Sync</div>
<div class="summary-value">Coming in Phase 2</div>
</div>
</div>
`;
}
+28
View File
@@ -81,6 +81,29 @@ for file in "${FILES[@]}"; do
fi
done
# Update main launcher scripts if present
echo ""
echo "🔄 Syncing launcher scripts..."
if [ -f "$SCRIPT_DIR/appdir-example/run-nebula.sh" ]; then
cp "$SCRIPT_DIR/appdir-example/run-nebula.sh" "$APPDIR_ROOT/run-nebula.sh"
sed -i.bak 's|usr/data|usr/user-data|g' "$APPDIR_ROOT/run-nebula.sh" && rm -f "$APPDIR_ROOT/run-nebula.sh.bak"
chmod +x "$APPDIR_ROOT/run-nebula.sh" || true
echo " ✓ run-nebula.sh"
fi
for launcher in "Nebula-Desktop" "Nebula-Controller"; do
if [ -f "$SCRIPT_DIR/nebula-appdir/$launcher" ]; then
cp "$SCRIPT_DIR/nebula-appdir/$launcher" "$APPDIR_ROOT/$launcher"
chmod +x "$APPDIR_ROOT/$launcher" || true
echo "$launcher"
fi
done
# Sync Nebula symlink if it exists
if [ -L "$SCRIPT_DIR/nebula-appdir/Nebula" ]; then
rm -f "$APPDIR_ROOT/Nebula"
ln -sf "Nebula-Desktop" "$APPDIR_ROOT/Nebula"
echo " ✓ Nebula (symlink)"
fi
# Sync directories
echo ""
echo "📁 Syncing directories..."
@@ -99,6 +122,11 @@ for dir in "${DIRS[@]}"; do
fi
done
# Ensure portable user-data directory exists for Linux AppDir builds
mkdir -p "$APPDIR_ROOT/usr/user-data"
chmod 700 "$APPDIR_ROOT/usr/user-data" || true
echo " ✓ usr/user-data/"
echo ""
echo "✅ AppDir updated successfully!"
echo ""