diff --git a/Assets/Scripts/AppProcessor.cs b/Assets/Scripts/AppProcessor.cs index 10e88c8..5ba3111 100644 --- a/Assets/Scripts/AppProcessor.cs +++ b/Assets/Scripts/AppProcessor.cs @@ -40,7 +40,6 @@ namespace QuestAppLauncher } // File name of app name overrides - const string AppNameOverrideTxtFileSearch = "appnames*.txt"; const string AppNameOverrideJsonFileSearch = "appnames*.json"; // Rename file names @@ -214,19 +213,11 @@ namespace QuestAppLauncher private static void ProcessAppNameOverrideFiles(bool isRenameMode, Dictionary apps, string path) { - // Process appname*.json files, sorted by name foreach (var filePath in Directory.GetFiles( path, AppNameOverrideJsonFileSearch).OrderBy(f => f)) { ProcessAppNameOverrideJsonFile(isRenameMode, apps, filePath); } - - // Process appname*.txt files, sorted by name - foreach (var filePath in Directory.GetFiles( - path, AppNameOverrideTxtFileSearch).OrderBy(f => f)) - { - ProcessAppNameOverrideTxtFile(isRenameMode, apps, filePath); - } } private static void ProcessAppNameOverrideJsonFile(bool isRenameMode, Dictionary apps, string appNameOverrideFilePath) @@ -320,98 +311,6 @@ namespace QuestAppLauncher } } - private static void ProcessAppNameOverrideTxtFile(bool isRenameMode, Dictionary apps, string appNameOverrideFilePath) - { - // Override app names, if any - // This is just a file with comma-separated packageName,appName[,category1[, category2]] - // Category1 and category2 are optional categories (tabs). - Debug.Log("Found file: " + appNameOverrideFilePath); - - string[] lines = File.ReadAllLines(appNameOverrideFilePath, Encoding.UTF8); - foreach (string line in lines) - { - line.Trim(); - - if (line.StartsWith("#")) - { - // Skip comments - continue; - } - - // Parse line - var entry = line.Split(','); - if (entry.Length < 2) - { - // We expect at least two entries - continue; - } - - var packageName = entry[0]; - var appName = entry[1]; - - if (!apps.ContainsKey(packageName)) - { - // App is not installed - if (isRenameMode) - { - // If rename mode, so just add it - apps[packageName] = new ProcessedApp - { - PackageName = packageName, - AppName = appName, - }; - } - - continue; - } - - // Get the custom tab names, if any - string autoTabName = null; - var tab1 = entry.Length > 2 ? entry[2] : null; - var tab2 = entry.Length > 3 ? entry[3] : null; - - if (tab1 != null && tab1.Length == 0) - { - tab1 = null; - } - - if (tab2 != null && tab2.Length == 0) - { - tab2 = null; - } - - if (tab1 != null && tab2 != null && tab1.Equals(tab2, StringComparison.OrdinalIgnoreCase)) - { - tab2 = null; - } - - // Override auto tab name if custom name matches built-in tab name - if (tab1 != null && Auto_Tabs.Contains(tab1, StringComparer.OrdinalIgnoreCase)) - { - autoTabName = tab1; - tab1 = null; - } - - if (tab2 != null && Auto_Tabs.Contains(tab2, StringComparer.OrdinalIgnoreCase)) - { - autoTabName = tab2; - tab2 = null; - } - - // Update entry - apps[packageName] = new ProcessedApp - { - PackageName = apps[entry[0]].PackageName, - Index = apps[entry[0]].Index, - AppName = appName, - AutoTabName = autoTabName ?? apps[entry[0]].AutoTabName, - Tab1Name = tab1 ?? apps[entry[0]].Tab1Name, - Tab2Name = tab2 ?? apps[entry[0]].Tab2Name, - LastTimeUsed = apps[entry[0]].LastTimeUsed - }; - } - } - private static void ExtractIconPacks(AndroidJavaObject currentActivity, string iconPacksPath) { if (!Directory.Exists(iconPacksPath)) diff --git a/README.md b/README.md index 6bc1b3e..1d3965e 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,28 @@ [![Build](https://github.com/HooverHigh/QuestAppLauncher/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/HooverHigh/QuestAppLauncher/actions/workflows/build.yml) -An app launcher for Hoover High School's Meta Quest 2 headsets, built in Unity. Displays all installed apps in a configurable grid with tabs, supports custom icons, app name overrides, and automatic asset updates from GitHub. +A VR home-screen replacement for Meta Quest 2, built in Unity by [oxmc](https://github.com/oxmc) for Hoover High School. Replaces the default Quest shell with a fully configurable app grid — custom icons, renamed apps, user-defined tabs, and automatic asset updates from any HTTP or GitHub source. + +## Features + +- Scrollable grid of all installed apps, configurable rows × columns +- Auto-categorized tabs (Quest / 2D) and user-defined custom tabs +- App name overrides and custom icons via JSON or zip icon packs +- Asset auto-update from GitHub releases or any plain HTTP endpoint — not just one source, any number of repos can be configured +- Sort by alphabetical or most recently used +- Configurable background skybox +- Managed policy support for IT/MDM deployments (hidden apps, name overrides, wallpaper, disable settings) +- Memory-efficient lazy icon loading — textures are loaded/unloaded based on scroll position ## Requirements - Meta Quest 2 -- Unity (see project settings for version) +- Unity (see `ProjectSettings/ProjectVersion.txt` for version) - Meta XR SDK (included via Package Manager) -- Git LFS (run `git lfs pull` after cloning) ## Building -Builds are handled by GitHub Actions — there is no local build command. +Builds are handled entirely by GitHub Actions — there is no local build command. - **`build.yml`** — runs tests (on master), builds the Android APK, uploads to GitHub Releases as `HooverHighQuestAppLauncher.apk` - **`activation.yml`** — manual trigger to request a Unity license activation file @@ -30,19 +40,30 @@ Builds are handled by GitHub Actions — there is no local build command. ## Configuration -User settings are stored in `config.json` in the app's persistent data path on-device (not in the repo). Key settings: +Settings are stored in `config.json` in the app's persistent data path on-device. The file is created with defaults on first run and can be pushed via `adb`: -- **Grid size** — rows × columns -- **Sort mode** — alphabetical or most recently used -- **Auto category** — places Quest / 2D apps into tabs automatically -- **Custom category** — user-defined tabs via `appnames.json` -- **Background** — skybox selection -- **Auto update** — downloads icon packs and app name files from configured GitHub repos +```bash +adb push config.json /sdcard/Android/data/dev.oxmc.QuestAppLauncher/files/ +``` -See [docs.md](docs.md) for full configuration and customization details. +Key settings: + +- **Grid size** — rows × columns (1–10 each) +- **Sort mode** — `az` (alphabetical) or `mostRecent` +- **Show 2D apps** — toggle non-VR apps on or off +- **Auto category** — places Quest / 2D apps into auto tabs; position: `top`, `left`, `right`, or `off` +- **Custom category** — user-defined tabs from `appnames.json`; same position options +- **Background** — path to a skybox image, or `"default"` +- **Auto update** — checks configured repos for new icon packs and app name files on startup +- **Download repos** — list of asset sources; supports GitHub releases (`type: "github"`) and any plain HTTP manifest endpoint (`type: "http"`) — multiple sources can be combined + +### Custom app names and icons + +Place `appnames.json` or `appnames*.txt` in the persistent data path to rename apps and assign them to tabs. Place `iconpack*.zip` files (containing `.jpg` entries) or individual `.jpg` files to override icons. + +See [docs.md](docs.md) for the full format reference, all config fields, managed policy details, and useful `adb` commands. ## Credits - [tverona1](https://github.com/tverona1) — original creator -- [fecheva](https://github.com/fecheva) — app names & icon packs - noxx — original app icon diff --git a/docs.md b/docs.md index f79d7e5..d0b7a0b 100644 --- a/docs.md +++ b/docs.md @@ -25,7 +25,7 @@ Quest App Launcher is a VR home-screen replacement for Meta Quest 2. It lists al ### Data Flow 1. **AppInfo.java** (Android plugin) — queries the Android package manager for installed apps, decodes icons as bitmaps, and reads usage statistics via `UsageStatsManager`. -2. **AppProcessor.cs** — calls the Java plugin over JNI, applies name overrides from `appnames*.json/txt`, filters excluded packages, assigns apps to auto tabs (Quest / 2D), and returns a `ProcessedApp` dictionary. +2. **AppProcessor.cs** — calls the Java plugin over JNI, applies name overrides from `appnames*.json`, filters excluded packages, assigns apps to auto tabs (Quest / 2D), and returns a `ProcessedApp` dictionary. 3. **GridPopulation.cs** — takes the processed app list, sorts it (A–Z or most-recently-used), builds tab containers, and instantiates `AppEntry` prefab tiles in the grid. 4. **AppEntry.cs** — represents a single icon tile. Lazy-loads and unloads its texture based on scroll position to keep memory use bounded. @@ -89,9 +89,9 @@ adb push config.json /sdcard/Android/data/dev.oxmc.QuestAppLauncher/files/ ## App Name Overrides -Place `appnames.json` (or `appnames*.json` / `appnames*.txt`) in the persistent data path to rename apps and assign them to custom tabs. +Place `appnames.json` (or any `appnames*.json` file) in the persistent data path to rename apps and assign them to custom tabs. -### JSON format (`appnames.json`) +### Format (`appnames.json`) ```json { @@ -106,16 +106,7 @@ Place `appnames.json` (or `appnames*.json` / `appnames*.txt`) in the persistent - **Name** — display name override (leave blank to keep the original name) - **Category** / **Category2** — optional tab names (up to two per app) -### Text format (`appnames.txt`) - -``` -# Lines starting with # are comments -com.example.myapp,My App,Games,Favorites -``` - -Format: `packageName,displayName[,category1[,category2]]` - -### Pushing files to the device +### Pushing to the device ``` adb push appnames.json /sdcard/Android/data/dev.oxmc.QuestAppLauncher/files/ @@ -168,7 +159,7 @@ Auto tabs can be placed at the top, left, or right of the panel, or disabled ent ### Custom tabs -Assign apps to custom tabs using the `Category` / `Category2` fields in `appnames.json` (or columns 3–4 in `appnames.txt`). Custom tabs appear sorted alphabetically. Their position is controlled by `customCategory` in `config.json`. +Assign apps to custom tabs using the `Category` / `Category2` fields in `appnames.json`. Custom tabs appear sorted alphabetically. Their position is controlled by `customCategory` in `config.json`. --- @@ -223,7 +214,7 @@ All user-editable files live in the app's persistent data path: | File / Folder | Purpose | |---------------|---------| | `config.json` | App settings | -| `appnames*.json` / `appnames*.txt` | App name and category overrides | +| `appnames*.json` | App name and category overrides | | `iconpack*.zip` | Custom icon packs | | `*.jpg` | Individual icon overrides (`.jpg`) | | `excludedpackages.txt` | One package name per line — these apps are hidden | diff --git a/examples/appnames.json b/examples/appnames.json new file mode 100644 index 0000000..949a505 --- /dev/null +++ b/examples/appnames.json @@ -0,0 +1,19 @@ +{ + "com.beatgames.beatsaber": { + "Name": "Beat Saber", + "Category": "Games" + }, + "com.facebook.bigbox": { + "Name": "Meta Quest Browser", + "Category": "Apps" + }, + "com.oculus.socialplatform": { + "Name": "Horizon Worlds", + "Category": "Social", + "Category2": "Apps" + }, + "com.example.hiddenname": { + "Name": "", + "Category": "Games" + } +} diff --git a/examples/config.json b/examples/config.json new file mode 100644 index 0000000..64d9797 --- /dev/null +++ b/examples/config.json @@ -0,0 +1,19 @@ +{ + "gridSize": { + "rows": 3, + "cols": 4 + }, + "sortMode": "az", + "show2D": true, + "autoCategory": "top", + "customCategory": "right", + "autoUpdate": true, + "background": "default", + "downloadRepos": [ + { + "repoUri": "hooverhigh/QuestAppLauncher_Assets/releases/latest", + "type": "github" + } + ], + "managedPolicyEndpoint": "" +} diff --git a/examples/config_http_repo.json b/examples/config_http_repo.json new file mode 100644 index 0000000..eb79739 --- /dev/null +++ b/examples/config_http_repo.json @@ -0,0 +1,19 @@ +{ + "gridSize": { + "rows": 3, + "cols": 3 + }, + "sortMode": "az", + "show2D": true, + "autoCategory": "top", + "customCategory": "right", + "autoUpdate": true, + "background": "default", + "downloadRepos": [ + { + "repoUri": "https://yourserver.com/quest/manifest.json", + "type": "http" + } + ], + "managedPolicyEndpoint": "" +} diff --git a/examples/config_managed_policy.json b/examples/config_managed_policy.json new file mode 100644 index 0000000..891ee5d --- /dev/null +++ b/examples/config_managed_policy.json @@ -0,0 +1,19 @@ +{ + "gridSize": { + "rows": 3, + "cols": 3 + }, + "sortMode": "az", + "show2D": true, + "autoCategory": "top", + "customCategory": "right", + "autoUpdate": true, + "background": "default", + "downloadRepos": [ + { + "repoUri": "hooverhigh/QuestAppLauncher_Assets/releases/latest", + "type": "github" + } + ], + "managedPolicyEndpoint": "https://yourserver.com/quest/policy.json" +} diff --git a/examples/config_mostrecent_noupdate.json b/examples/config_mostrecent_noupdate.json new file mode 100644 index 0000000..60b1ac6 --- /dev/null +++ b/examples/config_mostrecent_noupdate.json @@ -0,0 +1,14 @@ +{ + "gridSize": { + "rows": 4, + "cols": 4 + }, + "sortMode": "mostRecent", + "show2D": false, + "autoCategory": "top", + "customCategory": "off", + "autoUpdate": false, + "background": "default", + "downloadRepos": [], + "managedPolicyEndpoint": "" +} diff --git a/examples/excludedpackages.txt b/examples/excludedpackages.txt new file mode 100644 index 0000000..8d00907 --- /dev/null +++ b/examples/excludedpackages.txt @@ -0,0 +1,6 @@ +# excludedpackages.txt +# One package name per line. Apps listed here will be hidden from the launcher. +# Lines starting with # are ignored. + +com.example.unwantedapp +com.oculus.someapp diff --git a/examples/http_manifest.json b/examples/http_manifest.json new file mode 100644 index 0000000..ee3dbc5 --- /dev/null +++ b/examples/http_manifest.json @@ -0,0 +1,14 @@ +{ + "files": [ + { + "name": "iconpack_school.zip", + "url": "https://yourserver.com/quest/iconpack_school.zip", + "updatedAt": "2025-01-01T00:00:00Z" + }, + { + "name": "appnames.json", + "url": "https://yourserver.com/quest/appnames.json", + "updatedAt": "2025-01-01T00:00:00Z" + } + ] +} diff --git a/examples/managed_policy.json b/examples/managed_policy.json new file mode 100644 index 0000000..76879cd --- /dev/null +++ b/examples/managed_policy.json @@ -0,0 +1,18 @@ +{ + "hiddenApps": [ + "com.example.blockedapp", + "com.oculus.socialplatform" + ], + "appNames": { + "com.beatgames.beatsaber": { + "Name": "Beat Saber", + "Category": "Games" + }, + "com.facebook.bigbox": { + "Name": "Browser", + "Category": "Apps" + } + }, + "disableSettings": false, + "wallpaper": "https://yourserver.com/quest/background.jpg" +}