From 2606013b64d5a277f43ca4598b891ba403c492bd Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Mon, 1 Jun 2026 15:16:37 +0800 Subject: [PATCH] make openExternal use main api and improve offline mode --- docs/cli.md | 2 +- src/main/ffmpeg.ts | 3 +++ src/main/index.ts | 19 ++++++++++----- src/main/menu.ts | 24 +++++++++++-------- src/main/networking.ts | 13 ++++++++++ src/renderer/src/NoFileLoaded.tsx | 5 ++-- src/renderer/src/components/ExportConfirm.tsx | 5 ++-- .../src/components/FileNameTemplateEditor.tsx | 5 ++-- src/renderer/src/components/TagEditor.tsx | 4 +--- src/renderer/src/components/WhatsNew.tsx | 7 +++--- src/renderer/src/hooks/useSegments.tsx | 8 +++---- src/renderer/src/hooks/useStreamsMeta.tsx | 5 ++-- src/renderer/src/reporting.tsx | 8 +++---- 13 files changed, 64 insertions(+), 44 deletions(-) create mode 100644 src/main/networking.ts diff --git a/docs/cli.md b/docs/cli.md index 20eabb24..a31e2c21 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -45,7 +45,7 @@ LosslessCut --settings-json '{customFfPath:"/path/to/folder/containing/ffmpeg_an ## Other options - `--locales-path` Customise path to locales (useful for [translators](translation.md)). -- `--disable-networking` Turn off all network requests (see [#1418](https://github.com/mifi/lossless-cut/issues/1418)). +- `--disable-networking` Make app completely offline (see [#1418](https://github.com/mifi/lossless-cut/issues/1418)). - `--http-api` Start the [HTTP server with an API](api.md) to control LosslessCut, optionally specifying a port (default `8080`). - `--keyboard-action` Run a keyboard action (see below.) - `--config-dir` Path to a directory where the `config.json` file will be stored and loaded from. Note: don't include `config.json` in the path (only the directory containing it). diff --git a/src/main/ffmpeg.ts b/src/main/ffmpeg.ts index f4203f80..86154c47 100644 --- a/src/main/ffmpeg.ts +++ b/src/main/ffmpeg.ts @@ -16,6 +16,7 @@ import logger from './logger.js'; import { parseFfmpegProgressLine } from './progress.js'; import { formatFfmpegNumber, getHwaccelArgs, parseFfprobeDuration } from '../common/util.js'; import { getFfmpegJpegQuality } from './ffmpegUtil.js'; +import { throwIfDisabledNetworking } from './networking.js'; // cannot use process.kill: https://github.com/sindresorhus/execa/issues/1177 @@ -735,6 +736,8 @@ export function createMediaSourceProcess({ path, videoStreamIndex, audioStreamIn } export async function downloadMediaUrl(url: string, outPath: string) { + throwIfDisabledNetworking(); + // User agent taken from https://techblog.willshouse.com/2012/01/03/most-common-user-agents/ const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'; const args = [ diff --git a/src/main/index.ts b/src/main/index.ts index 5b370855..81caf23d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -38,6 +38,7 @@ import type { ApiActionRequest } from '../common/types.js'; import * as ffmpeg from './ffmpeg.js'; import * as compatPlayer from './compatPlayer.js'; import { downloadMediaUrl } from './ffmpeg.js'; +import { hasDisabledNetworking, setDisableNetworking } from './networking.js'; electronUnhandled({ showDialog: true, logger: (err) => logger.error('electron-unhandled', err) }); @@ -68,7 +69,6 @@ let mainWindow: BrowserWindow | null; let askBeforeClose = false; let rendererReady = false; let newVersion: string | undefined; -let disableNetworking: boolean; const openFiles = (paths: string[]) => mainWindow!.webContents.send('openFiles', paths); @@ -218,9 +218,17 @@ function createWindow() { mainWindow.on('move', debouncedSaveWindowState); } +async function openExternal(url: string) { + if (hasDisabledNetworking()) { + logger.warn('openExternal blocked because networking is disabled', url); + return; + } + await shell.openExternal(url); +} + function updateMenu() { assert(mainWindow); - menu({ app, mainWindow, newVersion, isStoreBuild }); + menu({ app, mainWindow, newVersion, isStoreBuild, openExternal }); } async function changeLanguage(language: string | null) { @@ -370,7 +378,7 @@ async function init() { if (filesToOpen.length === 0) filesToOpen = argv._.map(String); const { settingsJson } = argv; - ({ disableNetworking } = argv); + setDisableNetworking(argv['disableNetworking']); if (settingsJson != null) { logger.info('initializing settings', settingsJson); @@ -403,7 +411,7 @@ async function init() { const enableUpdateCheck = configStore.get('enableUpdateCheck'); - if (!disableNetworking && enableUpdateCheck && !isStoreBuild) { + if (!hasDisabledNetworking() && enableUpdateCheck && !isStoreBuild) { newVersion = await checkNewVersion(); // newVersion = '1.2.3'; if (newVersion) updateMenu(); @@ -426,8 +434,6 @@ function quitApp() { timers.setTimeout(1000).then(() => electron.app.quit()); } -const hasDisabledNetworking = () => !!disableNetworking; - const setProgressBar = (v: number) => mainWindow?.setProgressBar(v); function sendOsNotification(options: NotificationConstructorOptions) { @@ -447,6 +453,7 @@ const remoteApi = { sendOsNotification, writeClipboardText: (text: string) => electron.clipboard.writeText(text), readClipboardText: () => electron.clipboard.readText(), + openExternal, }; export type RemoteApi = typeof remoteApi; diff --git a/src/main/menu.ts b/src/main/menu.ts index 5e18428a..2bdda8cb 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -14,8 +14,12 @@ const esc = (val: string) => val.replaceAll('&', '&&'); const { Menu } = electron; -export default ({ app, mainWindow, newVersion, isStoreBuild }: { - app: Electron.App, mainWindow: BrowserWindow, newVersion?: string | undefined, isStoreBuild: boolean, +export default ({ app, mainWindow, newVersion, isStoreBuild, openExternal }: { + app: Electron.App, + mainWindow: BrowserWindow, + newVersion?: string | undefined, + isStoreBuild: boolean, + openExternal: (url: string) => void, }) => { // todo TS mainWindow.webContents.send const menu: (MenuItemConstructorOptions | MenuItem)[] = [ @@ -444,15 +448,15 @@ export default ({ app, mainWindow, newVersion, isStoreBuild }: { submenu: [ { label: esc(t('How to use')), - click() { electron.shell.openExternal(usageUrl); }, + click() { openExternal(usageUrl); }, }, { label: esc(t('FAQ')), - click() { electron.shell.openExternal(faqUrl); }, + click() { openExternal(faqUrl); }, }, { label: esc(t('Troubleshooting')), - click() { electron.shell.openExternal(troubleshootingUrl); }, + click() { openExternal(troubleshootingUrl); }, }, { label: esc(t('Keyboard & mouse shortcuts')), @@ -462,7 +466,7 @@ export default ({ app, mainWindow, newVersion, isStoreBuild }: { }, { label: esc(t('Learn More')), - click() { electron.shell.openExternal(homepageUrl); }, + click() { openExternal(homepageUrl); }, }, { type: 'separator' }, { @@ -471,11 +475,11 @@ export default ({ app, mainWindow, newVersion, isStoreBuild }: { }, { label: esc(t('Feature request')), - click() { electron.shell.openExternal(featureRequestUrl); }, + click() { openExternal(featureRequestUrl); }, }, ...(!isStoreBuild ? [{ label: esc(`${t('Donate')} ❤️`), - click() { electron.shell.openExternal(thanksUrl); }, + click() { openExternal(thanksUrl); }, }] : []), { type: 'separator' }, { @@ -489,7 +493,7 @@ export default ({ app, mainWindow, newVersion, isStoreBuild }: { { type: 'separator' }, { label: esc(t('Licenses')), - click() { electron.shell.openExternal(licensesUrl); }, + click() { openExternal(licensesUrl); }, }, ...(process.platform !== 'darwin' ? [{ role: 'about' as const, label: esc(t('About LosslessCut')) }] : []), ], @@ -502,7 +506,7 @@ export default ({ app, mainWindow, newVersion, isStoreBuild }: { submenu: [ { label: esc(t('Download {{version}}', { version: newVersion })), - click() { electron.shell.openExternal(getReleaseUrl(newVersion)); }, + click() { openExternal(getReleaseUrl(newVersion)); }, }, ], }); diff --git a/src/main/networking.ts b/src/main/networking.ts new file mode 100644 index 00000000..6636527d --- /dev/null +++ b/src/main/networking.ts @@ -0,0 +1,13 @@ +let disableNetworking: boolean; + +export const hasDisabledNetworking = () => !!disableNetworking; + +export function throwIfDisabledNetworking() { + if (hasDisabledNetworking()) { + throw new Error('Networking is disabled'); + } +} + +export function setDisableNetworking(value: boolean) { + disableNetworking = value; +} diff --git a/src/renderer/src/NoFileLoaded.tsx b/src/renderer/src/NoFileLoaded.tsx index 3494a4f1..361552dc 100644 --- a/src/renderer/src/NoFileLoaded.tsx +++ b/src/renderer/src/NoFileLoaded.tsx @@ -12,8 +12,7 @@ import type { KeyBinding } from '../../common/types'; import { splitKeyboardKeys } from './util'; import { getModifier } from './hooks/useTimelineScroll'; import Kbd from './components/Kbd'; - -const { shell } = window.require('@electron/remote'); +import mainApi from './mainApi'; function Keys({ keys }: { keys: string | undefined }) { @@ -89,7 +88,7 @@ function NoFileLoaded({ mifiLink, currentCutSeg, onClick, darkMode, keyBindingBy