feat: Rename to Toju and add translation
Some checks failed
Deploy Web Apps / deploy (push) Successful in 5m52s
Build Android APK / build-android-apk (push) Failing after 23m15s
Queue Release Build / prepare (push) Successful in 1m42s
Queue Release Build / build-linux (push) Failing after 9m33s
Queue Release Build / build-windows (push) Successful in 26m5s
Queue Release Build / finalize (push) Has been skipped
Some checks failed
Deploy Web Apps / deploy (push) Successful in 5m52s
Build Android APK / build-android-apk (push) Failing after 23m15s
Queue Release Build / prepare (push) Successful in 1m42s
Queue Release Build / build-linux (push) Failing after 9m33s
Queue Release Build / build-windows (push) Successful in 26m5s
Queue Release Build / finalize (push) Has been skipped
This commit is contained in:
@@ -80,7 +80,7 @@ export function getDocsHtml(specUrl: string): string {
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="${contentSecurityPolicy}"
|
||||
/>
|
||||
<title>MetoYou Local API</title>
|
||||
<title>Toju Local API</title>
|
||||
<style>
|
||||
:root { color-scheme: light dark; }
|
||||
body {
|
||||
|
||||
@@ -18,10 +18,10 @@ export function buildOpenApiDocument(options: OpenApiBuildOptions): unknown {
|
||||
return {
|
||||
openapi: '3.1.0',
|
||||
info: {
|
||||
title: 'MetoYou Local Desktop API',
|
||||
title: 'Toju Local Desktop API',
|
||||
version: appVersion,
|
||||
description:
|
||||
'Authenticated local HTTP API exposed by the MetoYou desktop app. '
|
||||
'Authenticated local HTTP API exposed by the Toju desktop app. '
|
||||
+ 'Authentication is performed against a configured signaling server. '
|
||||
+ 'Bearer tokens issued here are scoped to this device only.'
|
||||
},
|
||||
|
||||
@@ -3,21 +3,14 @@ import AutoLaunch from 'auto-launch';
|
||||
import * as fsp from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import { readDesktopSettings } from '../desktop-settings';
|
||||
import { DESKTOP_APP_DISPLAY_NAME, patchLinuxAutostartDesktopEntryNameField } from './desktop-branding.rules';
|
||||
import { resolveLaunchPath } from './launch-path';
|
||||
|
||||
let autoLauncher: AutoLaunch | null = null;
|
||||
let autoLaunchPath = '';
|
||||
|
||||
const LINUX_AUTO_START_ARGUMENTS = ['--no-sandbox', '%U'];
|
||||
|
||||
function resolveLaunchPath(): string {
|
||||
// AppImage runs from a temporary mount; APPIMAGE points to the real file path.
|
||||
const appImagePath = process.platform === 'linux'
|
||||
? String(process.env['APPIMAGE'] || '').trim()
|
||||
: '';
|
||||
|
||||
return appImagePath || process.execPath;
|
||||
}
|
||||
|
||||
function escapeDesktopEntryExecArgument(argument: string): string {
|
||||
const escapedArgument = argument.replace(/(["\\$`])/g, '\\$1');
|
||||
|
||||
@@ -35,14 +28,12 @@ function buildLinuxAutoStartExecLine(launchPath: string): string {
|
||||
}
|
||||
|
||||
function buildLinuxAutoStartDesktopEntry(launchPath: string): string {
|
||||
const appName = path.basename(launchPath);
|
||||
|
||||
return [
|
||||
'[Desktop Entry]',
|
||||
'Type=Application',
|
||||
'Version=1.0',
|
||||
`Name=${appName}`,
|
||||
`Comment=${appName}startup script`,
|
||||
`Name=${DESKTOP_APP_DISPLAY_NAME}`,
|
||||
`Comment=${DESKTOP_APP_DISPLAY_NAME} startup script`,
|
||||
buildLinuxAutoStartExecLine(launchPath),
|
||||
'StartupNotify=false',
|
||||
'Terminal=false'
|
||||
@@ -65,11 +56,13 @@ async function synchronizeLinuxAutoStartDesktopEntry(launchPath: string): Promis
|
||||
// Create the desktop entry if auto-launch did not leave one behind.
|
||||
}
|
||||
|
||||
const nextDesktopEntry = currentDesktopEntry
|
||||
? /^Exec=.*$/m.test(currentDesktopEntry)
|
||||
? currentDesktopEntry.replace(/^Exec=.*$/m, execLine)
|
||||
: `${currentDesktopEntry.trimEnd()}\n${execLine}\n`
|
||||
: buildLinuxAutoStartDesktopEntry(launchPath);
|
||||
const nextDesktopEntry = patchLinuxAutostartDesktopEntryNameField(
|
||||
currentDesktopEntry
|
||||
? /^Exec=.*$/m.test(currentDesktopEntry)
|
||||
? currentDesktopEntry.replace(/^Exec=.*$/m, execLine)
|
||||
: `${currentDesktopEntry.trimEnd()}\n${execLine}\n`
|
||||
: buildLinuxAutoStartDesktopEntry(launchPath)
|
||||
);
|
||||
|
||||
if (nextDesktopEntry === currentDesktopEntry) {
|
||||
return;
|
||||
@@ -87,7 +80,7 @@ function getAutoLauncher(): AutoLaunch | null {
|
||||
if (!autoLauncher) {
|
||||
autoLaunchPath = resolveLaunchPath();
|
||||
autoLauncher = new AutoLaunch({
|
||||
name: app.getName(),
|
||||
name: DESKTOP_APP_DISPLAY_NAME,
|
||||
path: autoLaunchPath
|
||||
});
|
||||
}
|
||||
|
||||
88
electron/app/desktop-branding-migration.ts
Normal file
88
electron/app/desktop-branding-migration.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { app } from 'electron';
|
||||
import AutoLaunch from 'auto-launch';
|
||||
import * as fsp from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
|
||||
import {
|
||||
DESKTOP_APP_DISPLAY_NAME,
|
||||
isLegacyLinuxAutostartEntry,
|
||||
LEGACY_APP_REGISTRY_NAMES
|
||||
} from './desktop-branding.rules';
|
||||
import { resolveLaunchPath } from './launch-path';
|
||||
|
||||
function getLinuxAutoStartDirectory(): string {
|
||||
return path.join(app.getPath('home'), '.config', 'autostart');
|
||||
}
|
||||
|
||||
export function configureDesktopBranding(): void {
|
||||
if (!app.isPackaged) {
|
||||
return;
|
||||
}
|
||||
|
||||
app.setName(DESKTOP_APP_DISPLAY_NAME);
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
process.title = DESKTOP_APP_DISPLAY_NAME;
|
||||
}
|
||||
}
|
||||
|
||||
async function removeLegacyLinuxAutostartEntries(launchPath: string): Promise<void> {
|
||||
if (process.platform !== 'linux') {
|
||||
return;
|
||||
}
|
||||
|
||||
const autostartDirectory = getLinuxAutoStartDirectory();
|
||||
const currentLaunchBaseName = path.basename(launchPath);
|
||||
|
||||
let fileNames: string[] = [];
|
||||
|
||||
try {
|
||||
fileNames = await fsp.readdir(autostartDirectory);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(fileNames.map(async (fileName) => {
|
||||
if (!fileName.endsWith('.desktop')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isLegacyLinuxAutostartEntry(fileName, currentLaunchBaseName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await fsp.unlink(path.join(autostartDirectory, fileName)).catch(() => {});
|
||||
}));
|
||||
}
|
||||
|
||||
async function disableLegacyWindowsAutoLaunchEntries(launchPath: string): Promise<void> {
|
||||
if (process.platform !== 'win32') {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(LEGACY_APP_REGISTRY_NAMES.map(async (legacyName) => {
|
||||
const launcher = new AutoLaunch({
|
||||
name: legacyName,
|
||||
path: launchPath
|
||||
});
|
||||
|
||||
try {
|
||||
if (await launcher.isEnabled()) {
|
||||
await launcher.disable();
|
||||
}
|
||||
} catch {
|
||||
// Best-effort cleanup for renamed desktop binaries.
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
export async function migrateLegacyDesktopBranding(): Promise<void> {
|
||||
if (!app.isPackaged) {
|
||||
return;
|
||||
}
|
||||
|
||||
const launchPath = resolveLaunchPath();
|
||||
|
||||
await removeLegacyLinuxAutostartEntries(launchPath);
|
||||
await disableLegacyWindowsAutoLaunchEntries(launchPath);
|
||||
}
|
||||
45
electron/app/desktop-branding.rules.spec.ts
Normal file
45
electron/app/desktop-branding.rules.spec.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
describe,
|
||||
expect,
|
||||
it
|
||||
} from 'vitest';
|
||||
|
||||
import {
|
||||
DESKTOP_APP_DISPLAY_NAME,
|
||||
DESKTOP_EXECUTABLE_NAME,
|
||||
LEGACY_APP_REGISTRY_NAMES,
|
||||
isLegacyLinuxAutostartEntry,
|
||||
patchLinuxAutostartDesktopEntryNameField
|
||||
} from './desktop-branding.rules';
|
||||
|
||||
describe('desktop-branding.rules', () => {
|
||||
it('exposes the Toju desktop branding constants', () => {
|
||||
expect(DESKTOP_APP_DISPLAY_NAME).toBe('Toju');
|
||||
expect(DESKTOP_EXECUTABLE_NAME).toBe('toju');
|
||||
expect(LEGACY_APP_REGISTRY_NAMES).toContain('MetoYou');
|
||||
expect(LEGACY_APP_REGISTRY_NAMES).toContain('metoyou');
|
||||
});
|
||||
|
||||
it('treats legacy linux autostart entries as removable', () => {
|
||||
expect(isLegacyLinuxAutostartEntry('metoyou.desktop', 'toju')).toBe(true);
|
||||
expect(isLegacyLinuxAutostartEntry('MetoYou.desktop', 'toju')).toBe(true);
|
||||
expect(isLegacyLinuxAutostartEntry('MetoYou-1.0.0-x86_64.AppImage.desktop', 'Toju-1.1.0-x86_64.AppImage')).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps the current launch entry when it already uses the Toju binary', () => {
|
||||
expect(isLegacyLinuxAutostartEntry('toju.desktop', 'toju')).toBe(false);
|
||||
expect(isLegacyLinuxAutostartEntry('Toju-1.1.0-x86_64.AppImage.desktop', 'Toju-1.1.0-x86_64.AppImage')).toBe(false);
|
||||
});
|
||||
|
||||
it('rewrites the desktop entry display name to Toju', () => {
|
||||
const patched = patchLinuxAutostartDesktopEntryNameField([
|
||||
'[Desktop Entry]',
|
||||
'Type=Application',
|
||||
'Name=metoyou',
|
||||
'Exec=/opt/Toju/toju --no-sandbox %U'
|
||||
].join('\n'));
|
||||
|
||||
expect(patched).toContain('Name=Toju');
|
||||
expect(patched).not.toContain('Name=metoyou');
|
||||
});
|
||||
});
|
||||
43
electron/app/desktop-branding.rules.ts
Normal file
43
electron/app/desktop-branding.rules.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
export const DESKTOP_APP_DISPLAY_NAME = 'Toju';
|
||||
export const DESKTOP_EXECUTABLE_NAME = 'toju';
|
||||
|
||||
export const LEGACY_APP_REGISTRY_NAMES = [
|
||||
'MetoYou',
|
||||
'MeToYou',
|
||||
'metoyou'
|
||||
] as const;
|
||||
|
||||
function normalizeAutostartBaseName(fileName: string): string {
|
||||
return fileName.replace(/\.desktop$/iu, '').replace(/\.exe$/iu, '');
|
||||
}
|
||||
|
||||
export function isLegacyLinuxAutostartEntry(
|
||||
fileName: string,
|
||||
currentLaunchBaseName: string
|
||||
): boolean {
|
||||
const entryBaseName = normalizeAutostartBaseName(fileName);
|
||||
const currentBaseName = normalizeAutostartBaseName(currentLaunchBaseName);
|
||||
|
||||
if (entryBaseName === currentBaseName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const normalizedEntry = entryBaseName.toLowerCase();
|
||||
|
||||
if (LEGACY_APP_REGISTRY_NAMES.some((legacyName) => normalizedEntry === legacyName.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return /^metoyou[-.]/iu.test(entryBaseName);
|
||||
}
|
||||
|
||||
export function patchLinuxAutostartDesktopEntryNameField(
|
||||
desktopEntry: string,
|
||||
displayName: string = DESKTOP_APP_DISPLAY_NAME
|
||||
): string {
|
||||
if (/^Name=.*$/m.test(desktopEntry)) {
|
||||
return desktopEntry.replace(/^Name=.*$/m, `Name=${displayName}`);
|
||||
}
|
||||
|
||||
return desktopEntry;
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { app } from 'electron';
|
||||
import { configureDesktopBranding } from './desktop-branding-migration';
|
||||
import { readDesktopSettings } from '../desktop-settings';
|
||||
|
||||
export function configureAppFlags(): void {
|
||||
configureDesktopBranding();
|
||||
linuxSpecificFlags();
|
||||
networkFlags();
|
||||
setupGpuEncodingFlags();
|
||||
|
||||
8
electron/app/launch-path.ts
Normal file
8
electron/app/launch-path.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/** Resolves the packaged binary path used for auto-start and updater migration. */
|
||||
export function resolveLaunchPath(): string {
|
||||
const appImagePath = process.platform === 'linux'
|
||||
? String(process.env['APPIMAGE'] || '').trim()
|
||||
: '';
|
||||
|
||||
return appImagePath || process.execPath;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { app, BrowserWindow } from 'electron';
|
||||
import { cleanupLinuxScreenShareAudioRouting } from '../audio/linux-screen-share-routing';
|
||||
import { initializeDesktopUpdater, shutdownDesktopUpdater } from '../update/desktop-updater';
|
||||
import { synchronizeAutoStartSetting } from './auto-start';
|
||||
import { migrateLegacyDesktopBranding } from './desktop-branding-migration';
|
||||
import { applyLocalApiSettings, stopLocalApiServer } from '../api';
|
||||
import {
|
||||
initializeDatabase,
|
||||
@@ -41,6 +42,7 @@ export function registerAppLifecycle(): void {
|
||||
setupCqrsHandlers();
|
||||
setupWindowControlHandlers();
|
||||
setupSystemHandlers();
|
||||
await migrateLegacyDesktopBranding();
|
||||
await synchronizeAutoStartSetting();
|
||||
initializeDesktopUpdater();
|
||||
await createWindow();
|
||||
|
||||
@@ -47,8 +47,8 @@ export async function exportUserData(): Promise<ExportUserDataResult> {
|
||||
.slice(0, 10)}.dat`;
|
||||
const { canceled, filePath } = await dialog.showSaveDialog({
|
||||
defaultPath: path.join(app.getPath('documents'), defaultFileName),
|
||||
filters: [{ extensions: ['dat'], name: 'MetoYou data archive' }],
|
||||
title: 'Export MetoYou data'
|
||||
filters: [{ extensions: ['dat'], name: 'Toju data archive' }],
|
||||
title: 'Export Toju data'
|
||||
});
|
||||
|
||||
if (canceled || !filePath) {
|
||||
@@ -87,9 +87,9 @@ export async function exportUserData(): Promise<ExportUserDataResult> {
|
||||
|
||||
export async function importUserData(): Promise<ImportUserDataResult> {
|
||||
const { canceled, filePaths } = await dialog.showOpenDialog({
|
||||
filters: [{ extensions: ['dat', 'zip'], name: 'MetoYou data archive' }],
|
||||
filters: [{ extensions: ['dat', 'zip'], name: 'Toju data archive' }],
|
||||
properties: ['openFile'],
|
||||
title: 'Import MetoYou data'
|
||||
title: 'Import Toju data'
|
||||
});
|
||||
|
||||
if (canceled || filePaths.length === 0) {
|
||||
@@ -235,7 +235,7 @@ function validateArchiveManifest(entries: ZipArchiveEntry[]): void {
|
||||
const manifest = entries.find((entry) => entry.path === ARCHIVE_MANIFEST_PATH);
|
||||
|
||||
if (!manifest) {
|
||||
throw new Error('The selected file is missing a MetoYou data manifest.');
|
||||
throw new Error('The selected file is missing a Toju data manifest.');
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(manifest.data.toString('utf8')) as { format?: string; version?: number };
|
||||
|
||||
@@ -105,6 +105,7 @@ export const HARDCODED_IGNORED_PROCESSES: ReadonlySet<string> = new Set([
|
||||
'logitechg',
|
||||
'login',
|
||||
'metoyou',
|
||||
'toju',
|
||||
'msedge',
|
||||
'msedgewebview2',
|
||||
'msteams',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { app, net } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import { migrateLegacyDesktopBranding } from '../app/desktop-branding-migration';
|
||||
import { readDesktopSettings, type AutoUpdateMode } from '../desktop-settings';
|
||||
import { getMainWindow } from '../window/create-window';
|
||||
import {
|
||||
@@ -500,7 +501,7 @@ async function performUpdateCheck(
|
||||
setDesktopUpdateState({
|
||||
lastCheckedAt: Date.now(),
|
||||
status: 'checking',
|
||||
statusMessage: `Checking for MetoYou ${targetRelease.version}...`,
|
||||
statusMessage: `Checking for Toju ${targetRelease.version}...`,
|
||||
targetVersion: targetRelease.version
|
||||
});
|
||||
|
||||
@@ -641,7 +642,7 @@ async function refreshDesktopUpdater(
|
||||
|
||||
setDesktopUpdateState({
|
||||
status: 'target-older-than-installed',
|
||||
statusMessage: `MetoYou ${app.getVersion()} is newer than ${selectedRelease.version}. Downgrades are not applied automatically.`,
|
||||
statusMessage: `Toju ${app.getVersion()} is newer than ${selectedRelease.version}. Downgrades are not applied automatically.`,
|
||||
targetVersion: selectedRelease.version
|
||||
});
|
||||
|
||||
@@ -698,7 +699,7 @@ export function initializeDesktopUpdater(): void {
|
||||
setDesktopUpdateState({
|
||||
lastCheckedAt: Date.now(),
|
||||
status: 'downloading',
|
||||
statusMessage: `Downloading MetoYou ${nextVersion ?? 'update'}...`,
|
||||
statusMessage: `Downloading Toju ${nextVersion ?? 'update'}...`,
|
||||
targetVersion: nextVersion
|
||||
});
|
||||
});
|
||||
@@ -715,8 +716,8 @@ export function initializeDesktopUpdater(): void {
|
||||
lastCheckedAt: Date.now(),
|
||||
status: 'up-to-date',
|
||||
statusMessage: isPinnedVersion
|
||||
? `MetoYou ${desktopUpdateState.targetVersion} is already installed.`
|
||||
: 'MetoYou is up to date.'
|
||||
? `Toju ${desktopUpdateState.targetVersion} is already installed.`
|
||||
: 'Toju is up to date.'
|
||||
});
|
||||
});
|
||||
|
||||
@@ -726,11 +727,13 @@ export function initializeDesktopUpdater(): void {
|
||||
const nextVersion = normalizeSemanticVersion(updateInfo.version)
|
||||
?? desktopUpdateState.targetVersion;
|
||||
|
||||
void migrateLegacyDesktopBranding();
|
||||
|
||||
setDesktopUpdateState({
|
||||
lastCheckedAt: Date.now(),
|
||||
restartRequired: true,
|
||||
status: 'restart-required',
|
||||
statusMessage: `MetoYou ${nextVersion ?? 'update'} is ready. Restart the app to finish installing it.`,
|
||||
statusMessage: `Toju ${nextVersion ?? 'update'} is ready. Restart the app to finish installing it.`,
|
||||
targetVersion: nextVersion
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { DESKTOP_APP_DISPLAY_NAME } from '../app/desktop-branding.rules';
|
||||
import { readDesktopSettings } from '../desktop-settings';
|
||||
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
@@ -114,11 +115,11 @@ function ensureTray(): void {
|
||||
}
|
||||
|
||||
tray = new Tray(trayIconPath);
|
||||
tray.setToolTip('MetoYou');
|
||||
tray.setToolTip('Toju');
|
||||
tray.setContextMenu(
|
||||
Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Open MetoYou',
|
||||
label: 'Open Toju',
|
||||
click: () => {
|
||||
void showMainWindow();
|
||||
}
|
||||
@@ -127,7 +128,7 @@ function ensureTray(): void {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Close MetoYou',
|
||||
label: 'Close Toju',
|
||||
click: () => {
|
||||
requestAppQuit();
|
||||
}
|
||||
@@ -200,6 +201,7 @@ export async function createWindow(): Promise<void> {
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
frame: false,
|
||||
title: DESKTOP_APP_DISPLAY_NAME,
|
||||
titleBarStyle: 'hidden',
|
||||
backgroundColor: '#0a0a0f',
|
||||
...(windowIconPath ? { icon: windowIconPath } : {}),
|
||||
|
||||
Reference in New Issue
Block a user