# LX Music Desktop Administrator & Developer Guide

This guide covers the architecture, configuration, build/distribution, security model, and internals of **LX Music Desktop** (洛雪音乐助手 桌面版) — a cross-platform Electron + Vue 3 music search-and-audition application. It is written for power users, packagers, integrators building on the Open API or scheme URLs, custom-source authors, and contributors. For installation and day-to-day usage, see the companion User Guide.

> [!NOTE]
> LX Music is a desktop client with no server-side component of its own (aside from optional, locally-hosted services it starts: the Open API and the LAN sync server). It is a single-user application whose "deployment" is desktop packaging, not service operation.

---

## 1. Architecture Overview

### 1.1 What the system is

LX Music is an Electron application split across **two renderer surfaces** (the main UI and the desktop-lyric window), a **main process** that owns native integration and background services, and a **shared common layer**. The renderer is a Vue 3 single-page app built with webpack; the main process is TypeScript compiled separately. Music-platform adapters and user-supplied source scripts run **inside the renderer with full Node.js access**, which is central to both the feature set and the security posture (Section 7).

The application aggregates public catalog data from several music platforms and, critically, **does not resolve audio URLs itself** — it delegates that to a configurable *source* (a built-in interface source or a user "custom source" script). This separation is a deliberate design and legal boundary: LX Music passes track metadata to a source and treats whatever URL the source returns as the audio.

### 1.2 Process and component diagram

```text
                         Electron Main Process  (src/main)
   ┌───────────────────────────────────────────────────────────────────┐
   │  app lifecycle (app.ts): single-instance, deeplink lxmusic://,      │
   │     env/param parsing, userData path, security listeners           │
   │  modules/ (registerModules):                                       │
   │     winMain ── BrowserWindow (frameless, transparent)              │
   │     winLyric ── desktop lyric BrowserWindow                        │
   │     tray, appMenu, hotKey (global + local)                         │
   │     userApi ── loads/runs custom-source scripts                    │
   │     openApi ── local HTTP control server (:23330)                  │
   │     sync ── LAN sync server/client over WebSocket (:23332)         │
   │     commonRenderers ── shared renderer-process logic               │
   │  worker/ ── better-sqlite3 dbService (lists, cache) in a thread    │
   │  utils/ ── Store (JSON config), settings, theme, migrate, logInit  │
   └───────────────┬──────────────────────────────────┬────────────────┘
        IPC (mainIpc / rendererIpc, message2call)      │
   ┌───────────────▼──────────────┐      ┌─────────────▼────────────────┐
   │  Renderer: main UI           │      │  Renderer: desktop lyric      │
   │  (src/renderer, Vue 3)       │      │  (src/renderer-lyric, Vue 3)  │
   │   views/ Search, List,       │      │   LyricHorizontal /           │
   │     Leaderboard, songList,   │      │   LyricVertical               │
   │     Download, Setting        │      └───────────────────────────────┘
   │   core/ music, player        │
   │   store/ (Vue-reactive)      │      ┌───────────────────────────────┐
   │   utils/musicSdk/            │      │  src/common (shared)          │
   │     bd kg kw mg tx wy + user │      │   config, constants, ipc,     │
   │   worker/ download, main     │      │   defaultSetting, theme, lang │
   └──────────────────────────────┘      └───────────────────────────────┘
```

### 1.3 Component inventory

| Component | Path | Role |
|-----------|------|------|
| Main entry | `src/main/index.ts`, `src/main/app.ts` | Boot sequence, single-instance, lifecycle, security listeners |
| Module registry | `src/main/modules/index.ts` | Registers userApi, commonRenderers, winMain, hotKey, tray, appMenu, winLyric |
| Main window | `src/main/modules/winMain/` | Frameless BrowserWindow, taskbar thumb buttons, dialogs, auto-update |
| Lyric window | `src/main/modules/winLyric/` | Desktop lyrics BrowserWindow |
| Tray | `src/main/modules/tray.ts` | Tray icon, context menu, status-bar lyric (macOS) |
| Hotkeys | `src/main/modules/hotKey/` | Global + local accelerator registration |
| Custom sources | `src/main/modules/userApi/` | Loads and sandboxes user source scripts |
| Open API | `src/main/modules/openApi/index.ts` | Local HTTP control/status server |
| Sync | `src/main/modules/sync/` | LAN sync server and client (WebSocket) |
| DB service | `src/main/worker/dbService/` | better-sqlite3 store for lists, lyric cache, URL cache, downloads |
| Config store | `src/main/utils/store.ts` | Atomic JSON persistence (`Store` class) |
| Renderer UI | `src/renderer/` | Vue 3 SPA: views, core player, stores, plugins |
| Lyric renderer | `src/renderer-lyric/` | Vue 3 SPA for the lyric window |
| Source adapters | `src/renderer/utils/musicSdk/{bd,kg,kw,mg,tx,wy}` | Per-platform catalog/search/URL logic |
| Shared layer | `src/common/` | Config defaults, constants, IPC names, i18n (`lang`), themes |
| Build configs | `build-config/` | webpack configs (main, renderer, renderer-lyric, renderer-scripts) + electron-builder packaging |

### 1.4 Technology stack

| Layer | Technology | Version |
|-------|------------|---------|
| App version | LX Music | 2.12.2 |
| Shell | Electron | 40.9.2 (Chromium target `Electron 22.3.0` for browserslist) |
| UI framework | Vue | ~3.3.13 |
| Routing | vue-router | ~4.5.1 |
| Language | TypeScript | 5.9.3 |
| Build | webpack 5 + ts-loader + vue-loader + pug + less | — |
| Packaging | electron-builder + electron-updater | ^26.9.0 / 6.8.4 |
| Database | better-sqlite3 | ^12.9.0 |
| HTTP | needle (fork), undici | — |
| WebSocket (sync) | ws | ^8.20.0 |
| Worker RPC | comlink, message2call | — |
| Audio metadata | music-metadata, node-id3 | — |
| Crypto | crypto-js | ^4.2.0 |
| Misc | iconv-lite, jschardet, image-size, font-list, sortablejs, @simonwep/pickr | — |
| Runtime floor | Node | `>= 22` (build), npm `>= 8.5.2` |

> [!NOTE]
> The desktop and mobile apps share design but not code; the mobile project (`lyswhut/lx-music-mobile`) is the sync peer for the LAN sync feature.

### 1.5 Boot sequence

`src/main/index.ts` runs, in order:

1. `initGlobalData()` — builds `global.lx` (settings, events, worker, hotkey/theme/player state) and `global.envParams` from parsed CLI/deeplink params.
2. `initSingleInstanceHandle()` — `app.requestSingleInstanceLock()`; a second instance forwards its deeplink/args to the running one.
3. `applyElectronEnvParams()` — applies `-dha`, `-dhmkh`, Linux GL workaround, `--disable-gpu-sandbox`, and `-proxy-server`/`-proxy-bypass-list`.
4. `setUserDataPath()` — selects the `portable` folder on Windows if present, then ensures `userData/LxDatas`.
5. `registerDeeplink()` — registers `lxmusic://` and an `open-url` handler.
6. `listenerAppEvent()` — installs `web-contents-created` security listeners and lifecycle handlers.
7. On `app.whenReady()` → `initAppSetting()` (init hotkeys, DB, settings, theme, migrations) → `registerModules()` → `app_inited` event.

---

## 2. System Requirements

| Requirement | Detail |
|-------------|--------|
| OS | Windows 7+, macOS, Linux |
| Arch | x64, x86 (32-bit), ARM64 (build-dependent) |
| Windows 7 | Aero/DWM for transparent windows; `.NET Framework 4.7.1`+ for display issues; PowerShell 5.1+ for desktop-lyric font enumeration |
| Build toolchain | Node `>= 22`, npm `>= 8.5.2`, Python + native build tools for `better-sqlite3` (`electron-builder install-app-deps` runs `postinstall`) |
| Disk | Install footprint plus user data, downloads, and lyric cache |

---

## 3. Configuration Reference

### 3.1 Where configuration lives

LX Music has **no system-wide config file and no environment-variable configuration for the desktop client itself**. All user configuration is stored in JSON and SQLite files inside the per-user data directory.

| OS | Data directory |
|----|----------------|
| Windows | `%APPDATA%/lx-music-desktop` (or a `portable/userData` folder beside the exe) |
| Linux | `$XDG_CONFIG_HOME/lx-music-desktop` or `~/.config/lx-music-desktop` |
| macOS | `~/Library/Application Support/lx-music-desktop` |

Persistent stores (managed by the `Store` class in `src/main/utils/store.ts`, with the SQLite DB managed by the worker `dbService`):

| Store name | Holds |
|------------|-------|
| `config_v2` | The application settings map (Section 3.3) |
| `data` | View/session state (last view, play info, search history, scroll positions, etc.) |
| `sync` | Sync server/client state |
| `hot_key` | Hotkey configuration |
| `user_api` | Imported custom-source scripts and metadata |
| `lyrics` / `lyrics_edited` | Raw and user-edited lyric cache |
| `theme` | Theme data |
| `sound_effect` | Equalizer / audio-effect state |
| `LxDatas/lx.data.db` | better-sqlite3 (WAL) database: lists, list items, download records, music-URL cache, other-source mappings |

> [!IMPORTANT]
> The list database `lx.data.db` (with its `-wal`/`-shm` sidecars) is the irreplaceable user data. Back it up before sync experiments or major upgrades. On a failed schema verification at startup, the app backs up the bad DB to `lx.data.db.<timestamp>.bak` and reinitializes — data may be lost, so keep your own backups.

### 3.2 Settings groups

The settings map is namespaced by prefix. The complete default map is in `src/common/defaultSetting.ts` (config schema version `2.1.0`). Groups:

| Prefix | Concern |
|--------|---------|
| `common.*` | Window size, font, language, interface source, animations, control-button position, auto-update, transparency |
| `player.*` | Quality, loop mode, volume, mute, playback rate, lyric display, audio-output device, audio visualization |
| `player.soundEffect.*` | 10-band equalizer, convolution reverb, stereo panner, pitch shifter |
| `playDetail.*` | Full-window lyric view styling |
| `desktopLyric.*` | Desktop lyric window geometry, behavior, and styling |
| `list.*` | List behavior (click-to-play, source display, scroll memory, add-location) |
| `download.*` | Download enablement, path, filename, concurrency, lyric/cover embedding |
| `search.*` | Hot/history search, search-box focus |
| `network.proxy.*` | In-app interface proxy |
| `tray.*` | Tray enablement and icon theme |
| `sync.*` | LAN sync mode, port, snapshots, client host |
| `openAPI.*` | Open API enablement, port, LAN binding |
| `theme.*` | Active theme and light/dark theme IDs |
| `odc.*` | "Online detail capture" search-input behaviors |

### 3.3 Notable defaults

These are exact values from `defaultSetting.ts` (omitting purely cosmetic keys for brevity; the file is authoritative):

| Key | Default | Notes |
|-----|---------|-------|
| `common.apiSource` | `temp` | The interface source resolving playback URLs |
| `common.windowSizeId` | `3` | Window-size preset |
| `common.fontSize` | `16` | — |
| `common.langId` | `null` | Falls back to system locale |
| `common.transparentWindow` | `true` (false on macOS) | Drives the `-dt` default |
| `common.tryAutoUpdate` | `true` | Auto-update on launch (Windows Setup builds) |
| `player.playQuality` | `128k` | Default playback quality |
| `player.togglePlayMethod` | `listLoop` | Loop mode |
| `player.volume` | `1` | 0–1 range |
| `player.preservesPitch` | `true` | Pitch preserved on rate change |
| `download.enable` | `false` | Downloads off until enabled |
| `download.savePath` | `~/Desktop` | Default download folder |
| `download.fileName` | `歌名 - 歌手` | Filename template |
| `download.maxDownloadNum` | `3` | Concurrent downloads |
| `network.proxy.enable` | `false` | In-app proxy off |
| `tray.enable` | `false` | Tray off by default |
| `sync.mode` | `server` | `server` or `client` |
| `sync.server.port` | `23332` | LAN sync port |
| `sync.server.maxSsnapshotNum` | `5` | Retained list snapshots |
| `openAPI.enable` | `false` | Open API off |
| `openAPI.port` | `23330` | Open API port |
| `openAPI.bindLan` | `false` | `false` = bind `127.0.0.1`; `true` = bind `0.0.0.0` |
| `theme.id` | `green` | Active theme (`happy_new_year` in Jan–Feb) |

> [!IMPORTANT]
> `openAPI.bindLan = true` exposes the Open API on all interfaces with no authentication and `Access-Control-Allow-Origin: *`. Treat it as a deliberate, network-facing decision (Section 7.4).

### 3.4 Audio effects

`player.soundEffect.*` exposes a full DSP chain, all defaulting to neutral/off:

- **Equalizer:** 10 biquad bands — `hz31`, `hz62`, `hz125`, `hz250`, `hz500`, `hz1000`, `hz2000`, `hz4000`, `hz8000`, `hz16000` (gain, default `0`).
- **Convolution reverb:** `convolution.fileName`, `convolution.mainGain` (default `10`), `convolution.sendGain`.
- **Stereo panner:** `panner.enable`, `panner.soundR`, `panner.speed`.
- **Pitch shifter:** `pitchShifter.playbackRate`.

---

## 4. Build & Distribution

### 4.1 Building from source

```bash
git clone https://github.com/lyswhut/lx-music-desktop.git
cd lx-music-desktop
npm install          # postinstall runs `electron-builder install-app-deps`
npm run dev          # development with HMR
```

Production builds compile each surface independently:

```bash
npm run build:main             # main process
npm run build:renderer         # main UI renderer
npm run build:renderer-lyric   # desktop-lyric renderer
npm run build:renderer-scripts # injected renderer scripts
npm run build                  # full pack via build-config/pack.js
```

> [!NOTE]
> Development uses webpack-dev-server on `http://localhost:9080` for the renderer; the main process loads that URL in dev and `file://…/index.html` in production. Contributions target the `dev` branch.

### 4.2 Packaging matrix

`electron-builder` produces a large artifact matrix via `build-config/build-pack.js` (driven by `target`, `arch`, `type`). Representative `npm` scripts:

| Platform | Types | Architectures |
|----------|-------|---------------|
| Windows | `setup` (NSIS, auto-update), `portable`, `green` (7z), `win7_setup`, `win7_green` | x64, x86, arm64, x86_64 (combined) |
| Linux | `deb`, `appImage`, `rpm`, `pacman` | x64, arm64, armv7l (deb) |
| macOS | `dmg` | x64, arm64 |

```bash
npm run pack:win        # default: win setup x64
npm run pack:linux      # deb + appImage + rpm + pacman
npm run pack:mac        # dmg x64 + arm64
```

Publishing variants append `publish=always` (e.g. `publish:win:setup:x64`) and are wired through `node publish` / GitHub Releases.

> [!TIP]
> The combined `x86_64` Windows build ships both 32- and 64-bit payloads and selects the correct one at install time. Use it when you don't know the target architecture.

### 4.3 Auto-update behavior

`electron-updater` (configured in `src/main/modules/winMain/autoUpdate.ts`) checks GitHub on launch for new releases and prompts after download. Important constraints:

- Auto-update applies to **Windows Setup** builds only.
- **Windows green/portable, macOS, and Linux** builds do **not** auto-update — distribute and update them manually.
- `common.tryAutoUpdate` gates the check; `common.showChangeLog` controls the changelog prompt.

### 4.4 Code signing

The published binaries are **unsigned**. Expect Gatekeeper quarantine on macOS (`xattr -rd com.apple.quarantine`) and antivirus heuristics on Windows. Packagers redistributing builds should sign them with their own certificates and document that fact.

---

## 5. Operations

This is a desktop client; "operations" means the local maintenance and the optional local services it can host.

### 5.1 Routine maintenance checklist

- [ ] Back up `LxDatas/lx.data.db` (+ `-wal`/`-shm`) periodically and before upgrades
- [ ] Keep the app updated (Setup builds auto-update; others manual)
- [ ] Clear the renderer cache from Settings if disk grows (the app exposes cache size/clear)
- [ ] Review imported custom sources; remove ones you no longer trust
- [ ] If using sync, keep snapshots within `sync.server.maxSsnapshotNum`

### 5.2 Running the LAN sync server

When `sync.mode = server` and sync is enabled, the app starts a WebSocket sync server on `sync.server.port` (default `23332`). A health check responds at `GET /hello` with `Hello~::^-^::~v4~`.

- Both peers must be on the same router/LAN with AP isolation off.
- The PC firewall must allow the sync port.
- Sync state, per-user device records, and list/dislike snapshots are stored under `sync/server` in the data directory (`serverInfo.json`, `users/`, `list/snapshot/`, etc., per `constants_sync.ts`).

> [!WARNING]
> Sync traffic is plaintext. Use only on trusted networks, and back up lists before the first sync. First-time sync between two non-empty devices prompts for a merge mode.

> [!NOTE]
> For always-on, internet-reachable sync, the maintainer publishes a separate **standalone sync server** project (`lx-music-sync-server`) you can deploy to your own server; the desktop client then runs in `client` mode pointing at it.

### 5.3 Running the Open API

When enabled, a Node `http` server starts on `openAPI.port` (default `23330`), bound to `127.0.0.1` (or `0.0.0.0` when `openAPI.bindLan` is true). The status panel shows the reachable address(es). See Section 8.2 for the endpoint reference.

---

## 6. Performance

- **Native SQLite via better-sqlite3** runs in a dedicated worker thread (`src/main/worker/dbService`), keeping list operations off the UI thread. WAL mode is used.
- **Renderer workers** (`src/renderer/worker/{download,main}`) and `comlink`/`message2call` offload download and processing work from the main renderer thread.
- **`player.powerSaveBlocker`** (default on) prevents the OS from sleeping during playback.
- **`player.isMaxOutputChannelCount`** and `player.mediaDeviceId` tune audio output; `player.audioVisualization` adds CPU cost when on.
- **Transparency** (`common.transparentWindow`) is GPU-dependent; disabling it (`-dt`) reduces compositor load on weak GPUs / Windows 7.
- **Hardware acceleration** can be disabled (`-dha`) to work around driver issues at some CPU cost.

> [!TIP]
> On low-spec machines or VMs, launching with `-dt -dha` trades visual polish for stability and lower resource use.

---

## 7. Security

LX Music makes a deliberate, atypical Electron security trade-off. Document and operate it with that in mind — it is not an oversight.

### 7.1 The renderer trust model (intentional inversion)

The main window is created with:

```javascript
webPreferences: {
  nodeIntegration: true,
  nodeIntegrationInWorker: true,
  contextIsolation: false,
  sandbox: false,
  webSecurity: false,
  enableWebSQL: false,
  webgl: false,
  spellcheck: false,
}
```

This is the **opposite** of the hardened `contextBridge` playbook. It is intentional: the music-platform adapters and user **custom-source scripts run in the renderer and require Node APIs** — cross-origin HTTP (`window.lx.request` bypasses CORS, which is why `webSecurity:false`), `Buffer`, and `crypto`. The honest framing is that **renderer code (including any imported source) is trusted code running with local privileges**, not sandboxed web content.

> [!CAUTION]
> Because imported custom sources execute with full Node access, a malicious source can read/write local files and make arbitrary network requests. Only import sources you can read and trust. In managed environments, treat the `user_api` store as a code allowlist.

### 7.2 What contains that trust

The renderer never loads remote web content in production. Several listeners in `src/main/app.ts` enforce this:

- **`will-navigate`** is blocked unless the target matches `navigationUrlWhiteList`.
- **`setWindowOpenHandler`** denies all `window.open`; `http(s)` URLs are instead opened in the external browser via `shell.openExternal`.
- **`will-attach-webview`** strips any preload, forces `nodeIntegration = false` on webviews, and blocks non-whitelisted `src`.
- The production window loads only local `file://…/index.html`.
- **Single-instance lock** prevents multiple copies; a second launch forwards its deeplink to the first.

> [!WARNING]
> The protective boundary is "only local, whitelisted content loads in the privileged renderer." Anything that could load untrusted content there (a bad webview src, a malicious custom source, a navigation past the whitelist) breaks the model. Changes to these listeners or the whitelist are security-sensitive.

### 7.3 Scheme URL / deeplink handling

`lxmusic://` deeplinks are validated against `URL_SCHEME_RXP` (`/^lxmusic:\/\//`) before processing, and second-instance args are re-parsed through `parseEnvParams`. Deeplinks drive search/playback (Section 8.3) and cannot, by themselves, execute arbitrary code — but treat them as untrusted input.

### 7.4 Local network services

| Service | Default bind | Auth | Risk |
|---------|--------------|------|------|
| Open API | `127.0.0.1:23330` | **None** | Full playback control; `bindLan` exposes it to the LAN with `Access-Control-Allow-Origin: *` |
| LAN sync | `0.0.0.0:23332` (when enabled) | Pairing code / auth handshake | **Plaintext** list data on the LAN |

> [!CAUTION]
> Keep the Open API bound to localhost unless you need remote control. Use LAN sync only on trusted networks; the payload is unencrypted.

### 7.5 Privacy notes

- The audio-output-switch feature calls `MediaDevices.enumerateDevices()`, which some security tools misreport as camera access. The camera is not used; the indicator light does not turn on.
- Spell-check dictionary downloads are disabled (the dictionary URL is pointed at `0.0.0.0`).
- The maintainer states the first-party code contains no malicious or telemetry behavior; binaries are built by public CI from open source. Supply-chain risk from third-party dependencies is acknowledged and mitigated by favoring widely-used packages.

---

## 8. Developer Internals

### 8.1 Repository structure

```text
src/
  common/        Shared: config.ts, constants.ts, constants_sync.ts,
                 defaultSetting.ts, defaultHotKey.ts, hotKey.ts,
                 ipcNames.ts, mainIpc.ts, rendererIpc.ts, theme/, lang/, utils/
  main/          Main process
    app.ts       Lifecycle, security, data path, deeplink
    index.ts     Boot entry (index-dev.ts for dev tooling)
    modules/     winMain, winLyric, tray, appMenu, hotKey, userApi,
                 openApi, sync, commonRenderers
    worker/      dbService (better-sqlite3) + modules per table
    utils/       Store, settings, theme, migrate, logInit
  renderer/      Main UI (Vue 3)
    views/       Search, List (MyList), Leaderboard, songList, Download, Setting
    core/        music, player, useApp (composition)
    store/       Vue-reactive state per domain
    plugins/     Dialog, Tips, SvgIcon, player
    utils/musicSdk/  bd, kg, kw, mg, tx, wy source adapters
    worker/      download, main renderer workers
  renderer-lyric/  Desktop-lyric renderer (LyricHorizontal/Vertical)
  lang/          i18n message catalogs
build-config/    webpack + electron-builder packaging
publish/         release/changelog tooling
```

### 8.2 Open API reference

Base URL: `http://<host>:<openAPI.port>` (default `http://127.0.0.1:23330`). All endpoints are HTTP `GET`, return `Access-Control-Allow-Origin: *`, and are unauthenticated. Source: `src/main/modules/openApi/index.ts`.

| Endpoint | Query | Returns |
|----------|-------|---------|
| `/status` | `filter=<csv of status keys>` | JSON subset of player status (default keys: `status,name,singer,albumName,lyricLineText,duration,progress,playbackRate`) |
| `/lyric` | — | Raw current lyric text |
| `/lyric-all` | — | JSON `{lyric, tlyric, rlyric, lxlyric}` |
| `/play` | — | Start playback |
| `/pause` | — | Pause |
| `/skip-next` | — | Next track |
| `/skip-prev` | — | Previous track |
| `/seek` | `offset=<seconds>` | Seek (validated `0 ≤ offset ≤ duration`, else `400 Invalid offset`) |
| `/collect` | — | Favorite current track |
| `/uncollect` | — | Unfavorite |
| `/volume` | `volume=<0–100>` | Set volume (else `400 Invalid volume`) |
| `/mute` | `mute=true\|false` | Mute/unmute (else `400 Invalid mute value`) |
| `/subscribe-player-status` | `filter=<csv>` | **SSE** stream; each status field is an event (`event: <key>\ndata: <json>`) |

Unknown paths return `401 Forbidden`. The SSE stream pushes an initial snapshot of the filtered keys, then live updates as player status changes.

```bash
# Live status stream, only name + lyric line
curl -N "http://127.0.0.1:23330/subscribe-player-status?filter=name,lyricLineText"
```

```text
event: name
data: "突然的自我"

event: lyricLineText
data: "让我掉下眼泪的"
```

> [!NOTE]
> The status keys correspond to `LX.Player.Status` (e.g. `status`, `name`, `singer`, `albumName`, `picUrl`, `progress`, `duration`, `playbackRate`, `lyricLineText`, `lyric`, `tlyric`, `rlyric`, `lxlyric`, `collect`, `volume`, `mute`). `filter` selects a subset; an empty/invalid filter falls back to the default key set.

### 8.3 Scheme URL reference

URLs begin with `lxmusic://`. Two parameter styles exist:

**`data` style** — URL-encoded JSON payload, for complex calls: `lxmusic://<path>?data=<urlencoded-json>`

| Action | Path | Key parameters |
|--------|------|----------------|
| Open song list | `songlist/open` | `source` (req), one of `id`/`url` (req) |
| Play song list | `songlist/play` | `source` (req), one of `id`/`url` (req), `index` (opt, 0-based) |
| Search | `music/search` | `keywords` (req), `source` (opt) |
| Play song | `music/play` | `name`, `singer`, `source`, `songmid` (all req), plus `types` array and platform-specific keys (`hash` for kg, `strMediaMid` for tx, `copyrightId` for mg, …) |

**`URL` style** — simple positional calls (since v1.18.0), still URL-encoded:

| Action | Path |
|--------|------|
| Search | `music/search/{source}/{keywords}` (source optional) |
| Open song list | `songlist/open/{source}/{id|url}` |

Conventions: source values are `kw/kg/tx/wy/mg`; quality values are `128k/320k/flac/flac24bit`. A companion userscript (`lx-music-script`) integrates these into music websites.

### 8.4 Custom-source scripting API (`window.lx`)

Custom sources are UTF-8 JavaScript (ES6+) files whose job is to resolve playback URLs. They communicate with the app through an event-style API exposed as `window.lx`. A source must begin with a metadata header comment:

```javascript
/**
 * @name   Example Source
 * @description Demo source
 * @version 1.0.0
 * @author you
 * @homepage http://example.com
 */
```

`@name` is required (≤24 chars recommended); `@description` must remain present even if empty; the others are optional.

**API surface:**

| Member | Purpose |
|--------|---------|
| `window.lx.version` | Custom-source API version (changes on API breaks) |
| `window.lx.EVENT_NAMES` | Event-name constants: `inited`, `request`, `updateAlert` |
| `window.lx.on(event, handler)` | Register a handler — **must return a `Promise`** |
| `window.lx.send(event, data)` | Send an event to the app |
| `window.lx.request(url, options, cb)` | HTTP request **not subject to CORS**; `options`: `method/headers/body/form/formData/timeout`; returns a cancel function |
| `window.lx.utils.buffer.from` / `bufToString` | Node `Buffer.from` / buffer→string |
| `window.lx.utils.crypto.{aesEncrypt,md5,randomBytes,rsaEncrypt}` | Crypto helpers |

**Lifecycle:**

1. Register a handler for `EVENT_NAMES.request`. The handler receives `{ source, action, info }`; for `action === 'musicUrl'`, `info = { type, musicInfo }` where `type` is the quality (`128k`/`320k`/`flac`/`flac24bit`) and `musicInfo` carries track identifiers. It must return a `Promise` resolving to the audio URL.
2. After setup, send `EVENT_NAMES.inited` with `{ status, openDevTools, sources }`, declaring which platforms (`kw/kg/tx/wy/mg`) the script supports and each one's `actions` (currently `['musicUrl']`) and `qualitys`.
3. Optionally send `EVENT_NAMES.updateAlert` (once per run) with `{ log, updateUrl }` to prompt users to update the source.

```javascript
const { EVENT_NAMES, request, on, send } = window.lx

on(EVENT_NAMES.request, ({ source, action, info }) => {
  if (action === 'musicUrl') {
    return resolveUrl(source, info.musicInfo, info.type) // returns a Promise<string>
  }
})

send(EVENT_NAMES.inited, {
  status: true,
  openDevTools: false,
  sources: {
    kw: { name: '酷我音乐', type: 'music', actions: ['musicUrl'], qualitys: ['128k', '320k', 'flac', 'flac24bit'] },
  },
})
```

> [!CAUTION]
> This API gives sources cross-origin HTTP and crypto/Buffer access by design. The trust boundary in Section 7.1 applies directly — a source is fully-trusted local code.

### 8.5 IPC and events

Main↔renderer messaging uses helpers in `src/common/mainIpc.ts` / `rendererIpc.ts` with names declared in `ipcNames.ts`, layered on `message2call`. Within the main process, an `EventEmitter`-based app event bus (`global.lx.event_app`, plus `event_list` and `event_dislike` in `src/main/event/`) coordinates modules — e.g. `updated_config` notifies tray, theme, and proxy logic of setting changes; `player_status` fans out to the tray, Open API SSE subscribers, and taskbar.

> [!NOTE]
> There are **no decorator-based HTTP routes, no REST controllers, and no AST-discoverable command registry** in this codebase. The "API surfaces" are (a) the Open API HTTP endpoints, (b) the `lxmusic://` scheme, (c) the `window.lx` source API, and (d) the internal IPC/event buses. Don't look for an OpenAPI spec or a CLI — there is none.

### 8.6 Data model (better-sqlite3)

The `dbService` worker manages tables for lists and list items, downloads, lyric cache, music-URL cache, "other source" mappings, and dislike lists (`src/main/worker/dbService/modules/*`). Built-in list IDs (`constants.ts`): `default`, `love`, `temp`, `download`, and a null-keyed play-later queue. Download status values: `run`, `waiting`, `pause`, `error`, `completed`.

### 8.7 Internationalization & theming

- **i18n:** message catalogs in `src/lang/` (and a small inline catalog in `tray.ts` for `en-us`/`zh-cn`/`zh-tw`). `common.langId` selects the language; `null` follows the system.
- **Themes:** built-in themes are generated by `src/common/theme/createThemes.js` (`npm run build:theme`); the live theme can follow the system via the `auto` theme, with separate `theme.lightId`/`theme.darkId`. The color picker uses `@simonwep/pickr`.

### 8.8 Things that do NOT exist (negative knowledge)

To prevent false assumptions:

- No command-line interface for end-user operations (only Electron startup flags / scheme URLs).
- No environment-variable configuration of the desktop client; no `/etc/lx-music/`, no registry config tree.
- No user accounts, no cloud login in the desktop app itself (NetEase "我喜欢" uses a manually-injected cookie token, not an app account).
- No first-party telemetry or analytics.
- No HTTP REST API beyond the optional, local, unauthenticated Open API.
- No auto-update for macOS, Linux, or Windows green/portable builds.
- No audio hosting — the app never owns audio; sources resolve URLs.
- The codebase is not a monorepo of multiple products; it is one desktop app with two renderer surfaces.

### 8.9 Testing & quality

The project uses ESLint with `eslint-config-standard-with-typescript`, `eslint-plugin-vue`, and pug linting (`npm run lint` / `lint:fix`). There is no separate unit-test suite in the build scripts; verification is via building each surface and manual QA. Contributions go to the `dev` branch and should reference an issue for new features.

### 8.10 Potential improvements

- The renderer's `webSecurity:false` / `nodeIntegration:true` posture is necessary for the current source model but is the single largest attack surface. A longer-term path is moving source execution into an isolated process or a `contextBridge`-mediated sandbox with a capability-limited `window.lx`.
- The Open API has no auth or rate limiting; an optional token (even a shared secret in the URL/header) would make `bindLan` safer.
- Sync transmits in plaintext; optional TLS or symmetric encryption would let it cross untrusted networks.
- macOS/Linux lack auto-update; integrating platform updaters would close the manual-update gap.

### 8.11 Contributing

1. Set up the dev environment per the project's "use source code" docs and `npm install`.
2. Branch from and target `dev`.
3. For features, open an issue first to confirm scope; for fixes, include before/after behavior and reproduction steps.
4. Run `npm run lint:fix` before submitting.

---

## 9. Quick Reference

### 9.1 Local services

| Service | Port (default) | Bind | Auth | Setting |
|---------|----------------|------|------|---------|
| Open API | `23330` | localhost / LAN | none | `openAPI.enable`, `openAPI.bindLan` |
| LAN sync | `23332` | LAN | pairing/auth | `sync.enable`, `sync.mode`, `sync.server.port` |

### 9.2 Default hotkeys

| Action | In-app | Global (off by default) |
|--------|--------|--------------------------|
| Play/pause | `Ctrl+F5` | `Ctrl+Alt+F5` |
| Previous | `Ctrl+←` | `Ctrl+Alt+←` |
| Next | `Ctrl+→` | `Ctrl+Alt+→` |
| Volume up/down | — | `Ctrl+Alt+↑` / `Ctrl+Alt+↓` |
| Focus search | `F1` | — |
| Toggle/lock/top desktop lyrics | — | `Ctrl+Alt+0` / `Ctrl+Alt+-` / `Ctrl+Alt+=` |

(macOS: `Ctrl` → `Command`.)

### 9.3 Key paths

| Item | Path |
|------|------|
| Data dir (Win) | `%APPDATA%/lx-music-desktop` (or `portable/userData`) |
| Data dir (Linux) | `~/.config/lx-music-desktop` |
| Data dir (macOS) | `~/Library/Application Support/lx-music-desktop` |
| List database | `<data>/LxDatas/lx.data.db` (+ `-wal`, `-shm`) |
| Sync server data | `<data>/sync/server/` |

### 9.4 Source codes

`kw` Kuwo · `kg` Kugou · `tx` QQ Music (Tencent) · `wy` NetEase · `mg` Migu · `bd` Baidu (adapter present)

---

_Generated June 2026 from lyswhut/lx-music-desktop at 9c364b4. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
