config.service.ts 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. import Store from 'electron-store'
  2. import * as fs from 'fs'
  3. import * as path from 'path'
  4. import { app } from 'electron'
  5. import { WE_APP_ID } from '@shared/constants'
  6. import type { SteamIdentity } from '@shared/types'
  7. interface AppConfig {
  8. workshopPath: string | null
  9. defaultFps: number | null
  10. lweRepoUrl: string | null
  11. lweRepoBranch: string | null
  12. lweCmakeArgs: string | null
  13. backupPath: string | null
  14. autoUnsubscribeAfterBackup: boolean
  15. trayEnabled: boolean
  16. autostartEnabled: boolean
  17. autostartMinimized: boolean
  18. autostartPlaylistId: string | null
  19. killLweOnQuit: boolean
  20. recommendedFpsEnabled: boolean
  21. recommendedWebFpsEnabled: boolean
  22. audioScreen: string | null
  23. ambientVolume: number | null
  24. /** Default --audio-sensitivity multiplier for audio-reactive objects with no per-wallpaper override */
  25. defaultAudioSensitivity: number
  26. /** Passes --render-debug no-puppet-animation to LWE - freezes puppet (.mdl) meshes at their bind pose, for troubleshooting */
  27. disablePuppetAnimation: boolean
  28. /** Passes --disable-animations to LWE - freezes all scene time (scripts, particles, effects and puppet meshes) */
  29. disableAnimations: boolean
  30. steamIdentity: SteamIdentity
  31. /** steamId64 of creators whose workshop items are hidden from browsing by default */
  32. ignoredCreators: string[]
  33. }
  34. const store = new Store<AppConfig>({
  35. name: 'config',
  36. defaults: {
  37. workshopPath: null,
  38. defaultFps: null,
  39. recommendedFpsEnabled: false,
  40. recommendedWebFpsEnabled: false,
  41. lweRepoUrl: null,
  42. lweRepoBranch: null,
  43. lweCmakeArgs: null,
  44. backupPath: null,
  45. autoUnsubscribeAfterBackup: false,
  46. trayEnabled: false,
  47. autostartEnabled: false,
  48. autostartMinimized: false,
  49. autostartPlaylistId: null,
  50. killLweOnQuit: false,
  51. audioScreen: null,
  52. ambientVolume: null,
  53. defaultAudioSensitivity: 1,
  54. disablePuppetAnimation: false,
  55. disableAnimations: false,
  56. steamIdentity: 'wallpaper-engine',
  57. ignoredCreators: []
  58. }
  59. })
  60. export function getConfiguredWorkshopPath(): string | null {
  61. return store.get('workshopPath')
  62. }
  63. export function setConfiguredWorkshopPath(p: string): void {
  64. store.set('workshopPath', p)
  65. }
  66. export function isWorkshopPathConfigured(): boolean {
  67. return store.get('workshopPath') !== null
  68. }
  69. export function getConfiguredBackupPath(): string | null {
  70. return store.get('backupPath')
  71. }
  72. export function setConfiguredBackupPath(p: string): void {
  73. store.set('backupPath', p)
  74. }
  75. export function isBackupPathConfigured(): boolean {
  76. return store.get('backupPath') !== null
  77. }
  78. export function getAutoUnsubscribeAfterBackup(): boolean {
  79. return store.get('autoUnsubscribeAfterBackup')
  80. }
  81. export function setAutoUnsubscribeAfterBackup(enabled: boolean): void {
  82. store.set('autoUnsubscribeAfterBackup', enabled)
  83. }
  84. export function getTrayEnabled(): boolean {
  85. return store.get('trayEnabled')
  86. }
  87. export function setTrayEnabled(enabled: boolean): void {
  88. store.set('trayEnabled', enabled)
  89. }
  90. export function getAutostartEnabled(): boolean {
  91. return store.get('autostartEnabled')
  92. }
  93. export function getAutostartMinimized(): boolean {
  94. return store.get('autostartMinimized')
  95. }
  96. export function setAutostart(enabled: boolean, minimized: boolean): void {
  97. store.set('autostartEnabled', enabled)
  98. store.set('autostartMinimized', minimized)
  99. }
  100. export function getAutostartPlaylistId(): string | null {
  101. return store.get('autostartPlaylistId')
  102. }
  103. export function setAutostartPlaylistId(id: string | null): void {
  104. store.set('autostartPlaylistId', id)
  105. }
  106. export function getKillLweOnQuit(): boolean {
  107. return store.get('killLweOnQuit')
  108. }
  109. export function setKillLweOnQuit(enabled: boolean): void {
  110. store.set('killLweOnQuit', enabled)
  111. }
  112. /** How this process identifies itself to Steam - 'wallpaper-engine' registers under WE's own
  113. * app id (full workshop compatibility, but Steam treats this process as WE and closes it on
  114. * logout); 'standalone' registers under a neutral test app id so Steam leaves it running.
  115. * Only read once at startup (steam.service.ts initSteam()) - changing it takes effect on relaunch. */
  116. export function getSteamIdentity(): SteamIdentity {
  117. return store.get('steamIdentity')
  118. }
  119. export function setSteamIdentity(identity: SteamIdentity): void {
  120. store.set('steamIdentity', identity)
  121. }
  122. export function getIgnoredCreators(): string[] {
  123. return store.get('ignoredCreators')
  124. }
  125. export function ignoreCreator(steamId: string): void {
  126. const current = store.get('ignoredCreators')
  127. if (!current.includes(steamId)) store.set('ignoredCreators', [...current, steamId])
  128. }
  129. export function unignoreCreator(steamId: string): void {
  130. store.set('ignoredCreators', store.get('ignoredCreators').filter((id) => id !== steamId))
  131. }
  132. export function getDefaultFps(): number | null {
  133. return store.get('defaultFps')
  134. }
  135. export function setDefaultFps(fps: number | null): void {
  136. store.set('defaultFps', fps)
  137. }
  138. /** When enabled, video wallpapers without a manual FPS override are launched at their own native frame rate. */
  139. export function getRecommendedFpsEnabled(): boolean {
  140. return store.get('recommendedFpsEnabled')
  141. }
  142. export function setRecommendedFpsEnabled(enabled: boolean): void {
  143. store.set('recommendedFpsEnabled', enabled)
  144. }
  145. /** When enabled, web wallpapers without a manual FPS override launch at RECOMMENDED_WEB_FPS
  146. * instead of the default limit - CEF paints internally at that rate regardless, so a lower
  147. * default just throttles how often the engine displays what CEF already rendered. */
  148. export function getRecommendedWebFpsEnabled(): boolean {
  149. return store.get('recommendedWebFpsEnabled')
  150. }
  151. export function setRecommendedWebFpsEnabled(enabled: boolean): void {
  152. store.set('recommendedWebFpsEnabled', enabled)
  153. }
  154. /** Screen name (matches --screen-root/--audio-screen) that alone produces audio; null = every screen can. */
  155. export function getAudioScreen(): string | null {
  156. return store.get('audioScreen')
  157. }
  158. export function setAudioScreen(screen: string | null): void {
  159. store.set('audioScreen', screen)
  160. }
  161. /** 0-128, applied to non-video (scene sound + web) backgrounds instead of the per-wallpaper volume; null = same volume. */
  162. export function getAmbientVolume(): number | null {
  163. return store.get('ambientVolume')
  164. }
  165. export function setAmbientVolume(volume: number | null): void {
  166. store.set('ambientVolume', volume)
  167. }
  168. /** Freezes puppet (.mdl) meshes at their bind pose (--render-debug no-puppet-animation) - troubleshooting only. */
  169. export function getDisablePuppetAnimation(): boolean {
  170. return store.get('disablePuppetAnimation')
  171. }
  172. export function setDisablePuppetAnimation(disabled: boolean): void {
  173. store.set('disablePuppetAnimation', disabled)
  174. }
  175. export function getDisableAnimations(): boolean {
  176. return store.get('disableAnimations')
  177. }
  178. export function setDisableAnimations(disabled: boolean): void {
  179. store.set('disableAnimations', disabled)
  180. }
  181. /** Default --audio-sensitivity multiplier for audio-reactive objects with no per-wallpaper override. */
  182. export function getDefaultAudioSensitivity(): number {
  183. return store.get('defaultAudioSensitivity')
  184. }
  185. export function setDefaultAudioSensitivity(multiplier: number): void {
  186. store.set('defaultAudioSensitivity', multiplier)
  187. }
  188. /** Custom linux-wallpaperengine repo (git URL or local path); null = official repo. */
  189. export function getLweRepoUrl(): string | null {
  190. return store.get('lweRepoUrl')
  191. }
  192. /** Branch/tag to build from the custom repo; null = default branch. */
  193. export function getLweRepoBranch(): string | null {
  194. return store.get('lweRepoBranch')
  195. }
  196. export function setLweRepo(url: string | null, branch: string | null): void {
  197. store.set('lweRepoUrl', url?.trim() || null)
  198. store.set('lweRepoBranch', branch?.trim() || null)
  199. }
  200. /** Extra cmake arguments (e.g. "-DENABLE_KDE_EXPERIMENTAL_FEATURES=ON"), appended to the build. */
  201. export function getLweCmakeArgs(): string | null {
  202. return store.get('lweCmakeArgs')
  203. }
  204. export function setLweCmakeArgs(args: string | null): void {
  205. store.set('lweCmakeArgs', args?.trim() || null)
  206. }
  207. export function getWeConfigPath(): string {
  208. return path.join(app.getPath('userData'), 'we-config.json')
  209. }
  210. /**
  211. * Import a Wallpaper Engine config.json:
  212. * 1. Copies the file to our data dir
  213. * 2. Replaces all old workshop content paths with the configured workshop path
  214. * 3. Returns the path to the fixed copy
  215. */
  216. export function importWEConfigFile(sourcePath: string): string {
  217. const workshopPath = getConfiguredWorkshopPath()
  218. if (!workshopPath) throw new Error('Workshop path must be configured before importing')
  219. let content = fs.readFileSync(sourcePath, 'utf8')
  220. // The WE config uses paths like:
  221. // D:/Games/SteamLibrary/steamapps/workshop/content/431960/...
  222. // C:/Program Files (x86)/Steam/steamapps/workshop/content/431960/...
  223. // We need to replace everything up to and including "431960/" with the configured workshop path + "/"
  224. const appId = String(WE_APP_ID)
  225. const pattern = new RegExp(
  226. `[A-Za-z]:[^"]*?/steamapps/workshop/content/${appId}/|` +
  227. `/[^"]*?/steamapps/workshop/content/${appId}/`,
  228. 'g'
  229. )
  230. const normalizedTarget = workshopPath.endsWith('/') ? workshopPath : workshopPath + '/'
  231. content = content.replace(pattern, normalizedTarget)
  232. const destPath = getWeConfigPath()
  233. fs.writeFileSync(destPath, content, 'utf8')
  234. console.log(`[Config] Imported WE config from ${sourcePath} to ${destPath}`)
  235. return destPath
  236. }
  237. export function createFreshConfig(): string {
  238. const destPath = getWeConfigPath()
  239. const skeleton = {
  240. '~': {
  241. general: {
  242. browser: { folders: [] },
  243. playlists: []
  244. }
  245. }
  246. }
  247. fs.writeFileSync(destPath, JSON.stringify(skeleton, null, '\t'), 'utf8')
  248. console.log(`[Config] Created fresh config at ${destPath}`)
  249. return destPath
  250. }