This commit is contained in:
@@ -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<string, ProcessedApp> 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<string, ProcessedApp> apps, string appNameOverrideFilePath)
|
||||
@@ -320,98 +311,6 @@ namespace QuestAppLauncher
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessAppNameOverrideTxtFile(bool isRenameMode, Dictionary<string, ProcessedApp> 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))
|
||||
|
||||
@@ -2,18 +2,28 @@
|
||||
|
||||
[](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 `<packageName>.jpg` entries) or individual `<packageName>.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
|
||||
|
||||
@@ -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 (`<packageName>.jpg`) |
|
||||
| `excludedpackages.txt` | One package name per line — these apps are hidden |
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"gridSize": {
|
||||
"rows": 4,
|
||||
"cols": 4
|
||||
},
|
||||
"sortMode": "mostRecent",
|
||||
"show2D": false,
|
||||
"autoCategory": "top",
|
||||
"customCategory": "off",
|
||||
"autoUpdate": false,
|
||||
"background": "default",
|
||||
"downloadRepos": [],
|
||||
"managedPolicyEndpoint": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user