mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 10:06:15 -08:00
fix(embedded-mpv): sanitize Linux helper environment
This commit is contained in:
1 parent
08e92d4f27
commit
4b2e60a0ff
6 files changed
+368
-18
No files matched your search
@@ -17,6 +17,7 @@ import {
|
||||
import { tmpdir } from 'os';
|
||||
import path from 'path';
|
||||
import {
|
||||
createLinuxFrameCopyHelperEnvironment,
|
||||
createEmbeddedMpvFrameCopyRuntimeProbe,
|
||||
EmbeddedMpvFrameCopyRuntimeDependencies,
|
||||
} from './embedded-mpv-frame-copy-runtime';
|
||||
@@ -477,6 +478,95 @@ function cloneManifest<T>(manifest: T): T {
|
||||
return JSON.parse(JSON.stringify(manifest)) as T;
|
||||
}
|
||||
|
||||
describe('createLinuxFrameCopyHelperEnvironment', () => {
|
||||
it('removes ambient loader overrides for system packages', () => {
|
||||
expect(
|
||||
createLinuxFrameCopyHelperEnvironment(
|
||||
{
|
||||
PATH: '/usr/bin',
|
||||
HOME: '/home/user',
|
||||
LD_LIBRARY_PATH: '/tmp/hostile-libs',
|
||||
LD_PRELOAD: '/tmp/inject.so',
|
||||
},
|
||||
'/opt/iptvnator/native',
|
||||
'system'
|
||||
)
|
||||
).toEqual({
|
||||
PATH: '/usr/bin',
|
||||
HOME: '/home/user',
|
||||
});
|
||||
});
|
||||
|
||||
it('reconstructs only trusted Snap loader roots for a bundled package inside the Snap mount', () => {
|
||||
const snapRoot = '/snap/iptvnator/42';
|
||||
const nativeDir = path.join(
|
||||
snapRoot,
|
||||
'resources',
|
||||
'app.asar.unpacked',
|
||||
'electron-backend',
|
||||
'native'
|
||||
);
|
||||
|
||||
expect(
|
||||
createLinuxFrameCopyHelperEnvironment(
|
||||
{
|
||||
PATH: '/snap/bin:/usr/bin',
|
||||
SNAP: snapRoot,
|
||||
SNAP_LIBRARY_PATH: [
|
||||
'/var/lib/snapd/lib/gl',
|
||||
'/tmp/hostile-gl',
|
||||
'/var/lib/snapd/lib/gl/nvidia',
|
||||
'/var/lib/snapd/lib/gl-evil',
|
||||
].join(':'),
|
||||
LD_LIBRARY_PATH: '/tmp/hostile-libs',
|
||||
LD_PRELOAD: '/tmp/inject.so',
|
||||
},
|
||||
nativeDir,
|
||||
'bundled'
|
||||
)
|
||||
).toEqual({
|
||||
PATH: '/snap/bin:/usr/bin',
|
||||
SNAP: snapRoot,
|
||||
SNAP_LIBRARY_PATH: [
|
||||
'/var/lib/snapd/lib/gl',
|
||||
'/tmp/hostile-gl',
|
||||
'/var/lib/snapd/lib/gl/nvidia',
|
||||
'/var/lib/snapd/lib/gl-evil',
|
||||
].join(':'),
|
||||
LD_LIBRARY_PATH: [
|
||||
path.join(nativeDir, 'lib'),
|
||||
path.join(snapRoot, 'lib'),
|
||||
path.join(snapRoot, 'usr', 'lib'),
|
||||
path.join(snapRoot, 'lib', 'x86_64-linux-gnu'),
|
||||
path.join(snapRoot, 'usr', 'lib', 'x86_64-linux-gnu'),
|
||||
'/var/lib/snapd/lib/gl',
|
||||
'/var/lib/snapd/lib/gl/nvidia',
|
||||
].join(':'),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not trust Snap loader paths when nativeDir is outside the declared mount', () => {
|
||||
expect(
|
||||
createLinuxFrameCopyHelperEnvironment(
|
||||
{
|
||||
PATH: '/usr/bin',
|
||||
SNAP: '/snap/iptvnator/42',
|
||||
SNAP_LIBRARY_PATH: '/var/lib/snapd/lib/gl:/tmp/hostile-gl',
|
||||
LD_LIBRARY_PATH: '/tmp/hostile-libs',
|
||||
LD_PRELOAD: '/tmp/inject.so',
|
||||
},
|
||||
'/opt/iptvnator/native',
|
||||
'bundled'
|
||||
)
|
||||
).toEqual({
|
||||
PATH: '/usr/bin',
|
||||
SNAP: '/snap/iptvnator/42',
|
||||
SNAP_LIBRARY_PATH: '/var/lib/snapd/lib/gl:/tmp/hostile-gl',
|
||||
LD_LIBRARY_PATH: '/opt/iptvnator/native/lib',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('embedded-mpv-frame-copy-runtime', () => {
|
||||
let rootDir: string;
|
||||
let spawnRuntimeProbe: jest.Mock;
|
||||
@@ -540,6 +630,7 @@ describe('embedded-mpv-frame-copy-runtime', () => {
|
||||
{
|
||||
encoding: 'utf8',
|
||||
timeout: 3000,
|
||||
killSignal: 'SIGKILL',
|
||||
windowsHide: true,
|
||||
env: {
|
||||
PATH: '/usr/bin',
|
||||
|
||||
@@ -326,6 +326,7 @@ export type EmbeddedMpvFrameCopyRuntimeFailureReason =
|
||||
| 'runtime-probe-internal-error';
|
||||
|
||||
export type EmbeddedMpvFrameCopyManifestContract = 'packaged' | 'development';
|
||||
export type EmbeddedMpvFrameCopyRuntimeMode = 'system' | 'bundled';
|
||||
|
||||
type RuntimeProfile = keyof typeof PROFILE_CONTRACTS;
|
||||
type RuntimeProbeProfile = RuntimeProfile | 'development';
|
||||
@@ -366,7 +367,7 @@ interface RuntimeFile {
|
||||
|
||||
interface ValidManifest {
|
||||
profile: RuntimeProbeProfile;
|
||||
runtimeMode: 'system' | 'bundled';
|
||||
runtimeMode: EmbeddedMpvFrameCopyRuntimeMode;
|
||||
runtimeFiles: RuntimeFile[];
|
||||
}
|
||||
|
||||
@@ -1337,19 +1338,118 @@ function parseSuccessfulProbe(
|
||||
};
|
||||
}
|
||||
|
||||
const TRUSTED_SNAP_MOUNT_ROOTS = ['/snap', '/var/lib/snapd/snap'] as const;
|
||||
const TRUSTED_SNAP_GL_ROOT = '/var/lib/snapd/lib/gl';
|
||||
|
||||
function isPathInside(
|
||||
parentPath: string,
|
||||
candidatePath: string,
|
||||
allowEqual: boolean
|
||||
): boolean {
|
||||
const relativePath = path.relative(parentPath, candidatePath);
|
||||
if (relativePath === '') {
|
||||
return allowEqual;
|
||||
}
|
||||
return (
|
||||
relativePath !== '..' &&
|
||||
!relativePath.startsWith(`..${path.sep}`) &&
|
||||
!path.isAbsolute(relativePath)
|
||||
);
|
||||
}
|
||||
|
||||
function resolveTrustedSnapRoot(
|
||||
environment: NodeJS.ProcessEnv,
|
||||
nativeDir: string
|
||||
): string | null {
|
||||
const declaredSnapRoot = environment.SNAP;
|
||||
if (
|
||||
!declaredSnapRoot ||
|
||||
!path.isAbsolute(declaredSnapRoot) ||
|
||||
!path.isAbsolute(nativeDir)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedSnapRoot = path.resolve(declaredSnapRoot);
|
||||
const resemblesReadOnlySnapMount = TRUSTED_SNAP_MOUNT_ROOTS.some(
|
||||
(mountRoot) => {
|
||||
const relativePath = path.relative(mountRoot, normalizedSnapRoot);
|
||||
return (
|
||||
isPathInside(mountRoot, normalizedSnapRoot, false) &&
|
||||
relativePath.split(path.sep).filter(Boolean).length >= 2
|
||||
);
|
||||
}
|
||||
);
|
||||
if (
|
||||
!resemblesReadOnlySnapMount ||
|
||||
!isPathInside(normalizedSnapRoot, path.resolve(nativeDir), false)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return normalizedSnapRoot;
|
||||
}
|
||||
|
||||
function getTrustedSnapLibraryPaths(
|
||||
environment: NodeJS.ProcessEnv,
|
||||
snapRoot: string
|
||||
): string[] {
|
||||
const snapLibraryPaths = (environment.SNAP_LIBRARY_PATH ?? '')
|
||||
.split(':')
|
||||
.filter(Boolean)
|
||||
.filter((libraryPath) => path.isAbsolute(libraryPath))
|
||||
.map((libraryPath) => path.resolve(libraryPath))
|
||||
.filter((libraryPath) =>
|
||||
isPathInside(TRUSTED_SNAP_GL_ROOT, libraryPath, true)
|
||||
);
|
||||
|
||||
return [
|
||||
path.join(snapRoot, 'lib'),
|
||||
path.join(snapRoot, 'usr', 'lib'),
|
||||
path.join(snapRoot, 'lib', 'x86_64-linux-gnu'),
|
||||
path.join(snapRoot, 'usr', 'lib', 'x86_64-linux-gnu'),
|
||||
...snapLibraryPaths,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the loader environment shared by the bounded startup probe and each
|
||||
* real Linux helper session. The validated package profile is authoritative:
|
||||
* system packages use the system loader, while bundled packages start at
|
||||
* native/lib and may add only immutable-looking Snap runtime/GL roots.
|
||||
*/
|
||||
export function createLinuxFrameCopyHelperEnvironment(
|
||||
environment: NodeJS.ProcessEnv,
|
||||
nativeDir: string,
|
||||
runtimeMode: EmbeddedMpvFrameCopyRuntimeMode
|
||||
): NodeJS.ProcessEnv {
|
||||
const helperEnvironment = { ...environment };
|
||||
delete helperEnvironment.LD_LIBRARY_PATH;
|
||||
delete helperEnvironment.LD_PRELOAD;
|
||||
|
||||
if (runtimeMode === 'system') {
|
||||
return helperEnvironment;
|
||||
}
|
||||
|
||||
const libraryPaths = [path.join(nativeDir, 'lib')];
|
||||
const trustedSnapRoot = resolveTrustedSnapRoot(environment, nativeDir);
|
||||
if (trustedSnapRoot) {
|
||||
libraryPaths.push(
|
||||
...getTrustedSnapLibraryPaths(environment, trustedSnapRoot)
|
||||
);
|
||||
}
|
||||
helperEnvironment.LD_LIBRARY_PATH = [...new Set(libraryPaths)].join(':');
|
||||
return helperEnvironment;
|
||||
}
|
||||
|
||||
function runHelperProbe(
|
||||
runtimePackage: ValidatedPackage,
|
||||
dependencies: EmbeddedMpvFrameCopyRuntimeDependencies
|
||||
): EmbeddedMpvFrameCopyRuntimeResult {
|
||||
const probeEnvironment = { ...dependencies.env };
|
||||
// The manifest/OS package is the loader contract. Ambient overrides could
|
||||
// replace libmpv or inject code into the helper before its bounded probe.
|
||||
delete probeEnvironment.LD_LIBRARY_PATH;
|
||||
delete probeEnvironment.LD_PRELOAD;
|
||||
if (runtimePackage.manifest.runtimeMode === 'bundled') {
|
||||
const bundledLibDir = path.join(runtimePackage.nativeDir, 'lib');
|
||||
probeEnvironment.LD_LIBRARY_PATH = bundledLibDir;
|
||||
}
|
||||
const probeEnvironment = createLinuxFrameCopyHelperEnvironment(
|
||||
dependencies.env,
|
||||
runtimePackage.nativeDir,
|
||||
runtimePackage.manifest.runtimeMode
|
||||
);
|
||||
|
||||
let result: ReturnType<typeof nodeSpawnSync>;
|
||||
try {
|
||||
@@ -1359,6 +1459,7 @@ function runHelperProbe(
|
||||
{
|
||||
encoding: 'utf8',
|
||||
timeout: RUNTIME_PROBE_TIMEOUT_MS,
|
||||
killSignal: 'SIGKILL',
|
||||
windowsHide: true,
|
||||
env: probeEnvironment,
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ jest.mock('child_process', () => ({
|
||||
}));
|
||||
|
||||
import { EmbeddedMpvFrameCopyAdapter } from './embedded-mpv-frame-copy.adapter';
|
||||
import type { EmbeddedMpvFrameCopyRuntimeMode } from './embedded-mpv-frame-copy-runtime';
|
||||
|
||||
class FakeHelperProcess extends EventEmitter {
|
||||
exitCode: number | null = null;
|
||||
@@ -36,10 +37,21 @@ describe('EmbeddedMpvFrameCopyAdapter', () => {
|
||||
let frameSourceChanges: Array<{ sessionId: string; shmName: string }>;
|
||||
let adapter: EmbeddedMpvFrameCopyAdapter;
|
||||
|
||||
const createAdapter = (helperPath: string | null = '/native/helper') => {
|
||||
const createAdapter = (
|
||||
helperPath: string | null = '/native/helper',
|
||||
{
|
||||
runtimeMode = 'system',
|
||||
environment,
|
||||
}: {
|
||||
runtimeMode?: EmbeddedMpvFrameCopyRuntimeMode | null;
|
||||
environment?: NodeJS.ProcessEnv;
|
||||
} = {}
|
||||
) => {
|
||||
frameSourceChanges = [];
|
||||
return new EmbeddedMpvFrameCopyAdapter({
|
||||
resolveHelperPath: () => helperPath,
|
||||
resolveRuntimeMode: () => runtimeMode,
|
||||
environment,
|
||||
getScaleFactor: () => 2,
|
||||
onFrameSourceChanged: (sessionId, source) =>
|
||||
frameSourceChanges.push({ sessionId, shmName: source.shmName }),
|
||||
@@ -83,6 +95,94 @@ describe('EmbeddedMpvFrameCopyAdapter', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
describe('Linux loader environment', () => {
|
||||
const originalPlatform = process.platform;
|
||||
const originalArch = process.arch;
|
||||
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(process, 'platform', { value: 'linux' });
|
||||
Object.defineProperty(process, 'arch', { value: 'x64' });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: originalPlatform,
|
||||
});
|
||||
Object.defineProperty(process, 'arch', { value: originalArch });
|
||||
});
|
||||
|
||||
it('uses a sanitized system environment for the real helper session', () => {
|
||||
adapter = createAdapter('/opt/iptvnator/native/helper', {
|
||||
runtimeMode: 'system',
|
||||
environment: {
|
||||
PATH: '/usr/bin',
|
||||
HOME: '/home/user',
|
||||
LD_LIBRARY_PATH: '/tmp/hostile-libs',
|
||||
LD_PRELOAD: '/tmp/inject.so',
|
||||
},
|
||||
});
|
||||
|
||||
createSession();
|
||||
|
||||
expect(spawnMock.mock.calls[0][2]).toEqual({
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: {
|
||||
PATH: '/usr/bin',
|
||||
HOME: '/home/user',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('uses only the bundled runtime and trusted Snap GL roots for the real helper session', () => {
|
||||
const snapRoot = '/snap/iptvnator/42';
|
||||
const nativeDir = path.join(
|
||||
snapRoot,
|
||||
'resources',
|
||||
'app.asar.unpacked',
|
||||
'electron-backend',
|
||||
'native'
|
||||
);
|
||||
adapter = createAdapter(path.join(nativeDir, 'helper'), {
|
||||
runtimeMode: 'bundled',
|
||||
environment: {
|
||||
PATH: '/snap/bin:/usr/bin',
|
||||
SNAP: snapRoot,
|
||||
SNAP_LIBRARY_PATH: '/var/lib/snapd/lib/gl:/tmp/hostile-gl',
|
||||
LD_LIBRARY_PATH: '/tmp/hostile-libs',
|
||||
LD_PRELOAD: '/tmp/inject.so',
|
||||
},
|
||||
});
|
||||
|
||||
createSession();
|
||||
|
||||
expect(spawnMock.mock.calls[0][2]).toEqual({
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: {
|
||||
PATH: '/snap/bin:/usr/bin',
|
||||
SNAP: snapRoot,
|
||||
SNAP_LIBRARY_PATH: '/var/lib/snapd/lib/gl:/tmp/hostile-gl',
|
||||
LD_LIBRARY_PATH: [
|
||||
path.join(nativeDir, 'lib'),
|
||||
path.join(snapRoot, 'lib'),
|
||||
path.join(snapRoot, 'usr', 'lib'),
|
||||
path.join(snapRoot, 'lib', 'x86_64-linux-gnu'),
|
||||
path.join(snapRoot, 'usr', 'lib', 'x86_64-linux-gnu'),
|
||||
'/var/lib/snapd/lib/gl',
|
||||
].join(':'),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses a Linux session without a validated runtime mode', () => {
|
||||
adapter = createAdapter('/native/helper', { runtimeMode: null });
|
||||
|
||||
expect(() => createSession()).toThrow(
|
||||
'validated Linux frame-copy runtime'
|
||||
);
|
||||
expect(spawnMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('caches helper snapshot events for getSessionSnapshot', () => {
|
||||
const sessionId = createSession();
|
||||
child.emitStdout({
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
ResolvedPortalPlayback,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
import { isFrameCopyPlatformSupported } from './embedded-mpv-frame-copy-platform.util';
|
||||
import { createLinuxFrameCopyHelperEnvironment } from './embedded-mpv-frame-copy-runtime';
|
||||
import type { EmbeddedMpvFrameCopyRuntimeMode } from './embedded-mpv-frame-copy-runtime';
|
||||
import type {
|
||||
NativeEmbeddedMpvAddon,
|
||||
NativeEmbeddedMpvSessionSnapshot,
|
||||
@@ -28,6 +30,8 @@ import type {
|
||||
|
||||
export interface EmbeddedMpvFrameCopyAdapterOptions {
|
||||
resolveHelperPath: () => string | null;
|
||||
resolveRuntimeMode: () => EmbeddedMpvFrameCopyRuntimeMode | null;
|
||||
environment?: NodeJS.ProcessEnv;
|
||||
getScaleFactor: () => number;
|
||||
onFrameSourceChanged: (
|
||||
sessionId: string,
|
||||
@@ -76,14 +80,18 @@ function createInitialSnapshot(): NativeEmbeddedMpvSessionSnapshot {
|
||||
export class EmbeddedMpvFrameCopyAdapter implements NativeEmbeddedMpvAddon {
|
||||
private readonly sessions = new Map<string, FrameCopyRuntimeSession>();
|
||||
|
||||
constructor(
|
||||
private readonly options: EmbeddedMpvFrameCopyAdapterOptions
|
||||
) {}
|
||||
constructor(private readonly options: EmbeddedMpvFrameCopyAdapterOptions) {}
|
||||
|
||||
isSupported(): boolean {
|
||||
if (
|
||||
!isFrameCopyPlatformSupported() ||
|
||||
this.options.resolveHelperPath() === null
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
isFrameCopyPlatformSupported() &&
|
||||
this.options.resolveHelperPath() !== null
|
||||
process.platform !== 'linux' ||
|
||||
this.options.resolveRuntimeMode() !== null
|
||||
);
|
||||
}
|
||||
|
||||
@@ -104,6 +112,20 @@ export class EmbeddedMpvFrameCopyAdapter implements NativeEmbeddedMpvAddon {
|
||||
const scale = this.options.getScaleFactor();
|
||||
const width = Math.max(16, Math.round(bounds.width * scale));
|
||||
const height = Math.max(16, Math.round(bounds.height * scale));
|
||||
let helperEnvironment: NodeJS.ProcessEnv | undefined;
|
||||
if (process.platform === 'linux') {
|
||||
const runtimeMode = this.options.resolveRuntimeMode();
|
||||
if (!runtimeMode) {
|
||||
throw new Error(
|
||||
'A validated Linux frame-copy runtime is not available.'
|
||||
);
|
||||
}
|
||||
helperEnvironment = createLinuxFrameCopyHelperEnvironment(
|
||||
this.options.environment ?? process.env,
|
||||
path.dirname(helperPath),
|
||||
runtimeMode
|
||||
);
|
||||
}
|
||||
|
||||
const child = spawn(
|
||||
helperPath,
|
||||
@@ -126,7 +148,10 @@ export class EmbeddedMpvFrameCopyAdapter implements NativeEmbeddedMpvAddon {
|
||||
]
|
||||
: []),
|
||||
],
|
||||
{ stdio: ['pipe', 'pipe', 'pipe'] }
|
||||
{
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
...(helperEnvironment ? { env: helperEnvironment } : {}),
|
||||
}
|
||||
);
|
||||
|
||||
console.log(
|
||||
@@ -177,7 +202,9 @@ export class EmbeddedMpvFrameCopyAdapter implements NativeEmbeddedMpvAddon {
|
||||
}
|
||||
|
||||
loadPlayback(sessionId: string, playback: ResolvedPortalPlayback): void {
|
||||
const fields: string[] = [`url=${encodeProtocolValue(playback.streamUrl)}`];
|
||||
const fields: string[] = [
|
||||
`url=${encodeProtocolValue(playback.streamUrl)}`,
|
||||
];
|
||||
if (playback.title) {
|
||||
fields.push(
|
||||
`opt.force-media-title=${encodeProtocolValue(playback.title)}`
|
||||
|
||||
@@ -381,6 +381,25 @@ describe('EmbeddedMpvNativeService power blocker', () => {
|
||||
expect(service.isFrameCopyAvailable()).toBe(true);
|
||||
});
|
||||
|
||||
it('supplies the adapter with the runtime mode from the validated Linux capability', () => {
|
||||
Object.defineProperty(process, 'platform', { value: 'linux' });
|
||||
mockGetFrameCopyRuntimeAvailability.mockReturnValue({
|
||||
usable: true,
|
||||
profile: 'portable',
|
||||
runtimeMode: 'bundled',
|
||||
libmpv: '2.3',
|
||||
renderApi: 'egl',
|
||||
});
|
||||
|
||||
expect(
|
||||
(
|
||||
service as unknown as {
|
||||
resolveFrameCopyRuntimeMode(): string | null;
|
||||
}
|
||||
).resolveFrameCopyRuntimeMode()
|
||||
).toBe('bundled');
|
||||
});
|
||||
|
||||
it('keeps the frame-copy engine Apple-Silicon-only on macOS', () => {
|
||||
Object.defineProperty(process, 'arch', { value: 'x64' });
|
||||
process.env.IPTVNATOR_ENABLE_EMBEDDED_MPV_FRAME_COPY = '1';
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
isFrameCopyRuntimeUsable,
|
||||
resolveFrameCopyHelperPath,
|
||||
} from './embedded-mpv-frame-copy-platform.util';
|
||||
import type { EmbeddedMpvFrameCopyRuntimeMode } from './embedded-mpv-frame-copy-runtime';
|
||||
import {
|
||||
EMBEDDED_MPV_EXPERIMENT_ENV,
|
||||
isEmbeddedMpvFeatureEnabled,
|
||||
@@ -154,6 +155,16 @@ export class EmbeddedMpvNativeService {
|
||||
};
|
||||
}
|
||||
|
||||
private resolveFrameCopyRuntimeMode(): EmbeddedMpvFrameCopyRuntimeMode | null {
|
||||
if (process.platform !== 'linux') {
|
||||
return null;
|
||||
}
|
||||
const availability = getFrameCopyRuntimeAvailability();
|
||||
return availability.usable && 'runtimeMode' in availability
|
||||
? availability.runtimeMode
|
||||
: null;
|
||||
}
|
||||
|
||||
getFrameSource(sessionId: string): EmbeddedMpvFrameSource | null {
|
||||
return this.frameCopyAdapter?.getFrameSource(sessionId) ?? null;
|
||||
}
|
||||
@@ -162,6 +173,7 @@ export class EmbeddedMpvNativeService {
|
||||
if (!this.frameCopyAdapter) {
|
||||
this.frameCopyAdapter = new EmbeddedMpvFrameCopyAdapter({
|
||||
resolveHelperPath: resolveFrameCopyHelperPath,
|
||||
resolveRuntimeMode: () => this.resolveFrameCopyRuntimeMode(),
|
||||
getScaleFactor: () => this.getMainWindowScaleFactor(),
|
||||
onFrameSourceChanged: (sessionId, source) => {
|
||||
if (!App.mainWindow || App.mainWindow.isDestroyed()) {
|
||||
|
||||
Reference in new issue
Block a user