Compare commits
9 Commits
Nebula
...
adefa1706e
| Author | SHA1 | Date | |
|---|---|---|---|
| adefa1706e | |||
| 8b87a07d1b | |||
| a101899d9c | |||
| 0b51d133a4 | |||
| 6b2a7c8404 | |||
| 618ea7d12d | |||
| 0137df60dd | |||
| b725d5a672 | |||
| 86f3b10e80 |
@@ -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 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 Steam’s 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 Steam’s 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 Nebula’s **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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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).
|
||||
@@ -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 **don’t 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.
|
||||
|
||||
---
|
||||
@@ -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 {}
|
||||
|
||||
try {
|
||||
if (typeof menuWin.showInactive === 'function') {
|
||||
menuWin.showInactive();
|
||||
} else {
|
||||
menuWin.show();
|
||||
menuWin.focus();
|
||||
}
|
||||
} catch {
|
||||
menuWin.show();
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
|
||||
|
||||
+22
-8
@@ -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
@@ -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
@@ -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');
|
||||
}
|
||||
|
||||
if (!dataPath) {
|
||||
// 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();
|
||||
dataPath = path.join(appRoot, 'user-data');
|
||||
let 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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 non‑Steam channels; the Electron runtime can be updated separately for security patches and performance improvements.</p>
|
||||
|
||||
<div class="stack">
|
||||
<!-- Update Status Banner -->
|
||||
|
||||
+15
-11
@@ -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
@@ -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
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
Reference in New Issue
Block a user