This commit is contained in:
2026-10-17 18:54:57 +07:00
parent d3683ac6cf
commit 8b29b4f24e
5 changed files with 74 additions and 10 deletions
+2
View File
@@ -26,6 +26,8 @@ Commit и push выполняет только пользователь. Вло
## Запуск стенда SCREEN-001
Нужны Node.js 24.15+ и ffmpeg с libx264 в PATH.
В Linux приложение использует X11; для сессии Wayland нужен XWayland, чтобы
размещать показ на выбранном мониторе.
```sh
npm ci
+9
View File
@@ -39,6 +39,15 @@ SCREEN-009 (LAN-пульт) отменена владельцем; ID не пе
PASS. В smoke проверено отсутствие LAN-панели и методов lan/approve в preload,
локальные play/pause/stop/seek, Range и открытие окна без автоповтора.
Frontend-стандарт прочитан; ручное ревью diff выполнено, нарушений не найдено.
- Исправление выбора дисплея: для Linux явно включён X11/XWayland, поскольку native
Wayland игнорирует глобальные координаты окон. Перед fullscreen повторно заданы
bounds выбранного дисплея. GUI smoke проверяет размещение и повторное открытие;
при наличии реального второго дисплея проверяет фактический монитор окна.
Type-check, lint, lint:css, тесты и GUI smoke — PASS. В текущей среде физически
подключён только eDP-1; второй дисплей в этом прогоне синтетический, аппаратная
проверка переноса остаётся открытой. Нужен полный перезапуск приложения.
Четыре Linux/Windows-архива пересобраны с исправлением; содержимое app.asar
сверено с текущим кодом, SHA256SUMS обновлён. Нарушений стандарта в diff не найдено.
- Сборки: Linux/Windows x64/ARM64 пересобраны без LAN; содержимое всех четырёх
app.asar сверено с проверенным кодом. LAN-модули, ws и тесты отсутствуют в поставке;
release/SHA256SUMS обновлён. macOS нужно собирать на Mac или ручным workflow.
+10 -2
View File
@@ -19,6 +19,11 @@ Electron загружается отдельной командой install-elec
| Linux x64 | npm run dist -- --linux --x64 | release/*.tar.gz |
| Linux ARM64 | npm run dist -- --linux --arm64 | release/*.tar.gz |
В Linux приложение явно использует X11, в сессии Wayland — через XWayland.
Нативный Wayland не позволяет надёжно задавать глобальные координаты окна:
[ограничение Electron](https://www.electronjs.org/docs/latest/api/browser-window#platform-notices).
Перед fullscreen окно явно размещается в границах выбранного дисплея.
Сборки macOS выполняйте на Mac. На Windows ARM используйте ARM-версию Node.js.
Linux tar.gz распаковать с сохранением прав файлов; запуск — исполняемый файл
в распакованном каталоге. Архивы Windows распаковать целиком, macOS .app перенести в Applications.
@@ -52,8 +57,11 @@ build, 4 теста и GUI smoke — PASS, включая запуск кноп
## Автоматический smoke
После генерации медиа выполните npm run smoke в графической сессии. Проверка
использует синтетический второй дисплей: IPC, Range, запуск/пауза/стоп/seek,
запрет IPC из окна показа и отсутствие автоповтора при открытии окна. Дополнительно
использует реальный второй дисплей при наличии, иначе синтетический: IPC, Range,
запуск/пауза/стоп/seek, запрет IPC из окна показа и отсутствие автоповтора.
Проверяются координаты выбранного дисплея перед fullscreen и при повторном открытии;
в Linux — режим X11. При реальном втором дисплее дополнительно проверяется,
на каком мониторе фактически оказалось полноэкранное окно. Дополнительно
проверяется отсутствие LAN-методов в preload и сетевых параметров в данных пульта.
Это не проверка реального проектора. Серверное управление проверяется в SCREEN-006.
+3
View File
@@ -7,6 +7,8 @@ import { Controller } from './controller.js'
import { parseRange } from './range.js'
import type { HostInfo } from '../src/types/screen.js'
// Native Wayland запрещает размещение окна на выбранном дисплее по координатам.
if (process.platform === 'linux') app.commandLine.appendSwitch('ozone-platform', 'x11')
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required')
protocol.registerSchemesAsPrivileged([
{
@@ -79,6 +81,7 @@ function show(displayId: number) {
output.once('ready-to-show', () => {
if (!window.isDestroyed()) {
window.show()
window.setBounds(display.bounds)
window.setFullScreen(true)
}
})
+50 -8
View File
@@ -9,18 +9,39 @@ app.setPath(
),
)
const delay = (ms) => new Promise((r) => setTimeout(r, ms))
let targetDisplay
let isSyntheticDisplay = false
const boundsBeforeFullscreen = new Map()
const fullscreenEntered = new Set()
app.on('browser-window-created', (_event, window) => {
window.on('enter-full-screen', () => fullscreenEntered.add(window.id))
let requestedBounds
const setBounds = window.setBounds.bind(window)
window.setBounds = (bounds, ...args) => {
requestedBounds = { ...bounds }
return setBounds(bounds, ...args)
}
const setFullScreen = window.setFullScreen.bind(window)
window.setFullScreen = (flag) => {
if (flag) boundsBeforeFullscreen.set(window.id, requestedBounds)
return setFullScreen(flag)
}
})
app.whenReady().then(() => {
const original = screen.getAllDisplays.bind(screen)
screen.getAllDisplays = () => [
...original(),
{
...original()[0],
const displays = original()
targetDisplay = displays.find((display) => display.id !== screen.getPrimaryDisplay().id)
if (!targetDisplay) {
isSyntheticDisplay = true
targetDisplay = {
...displays[0],
id: 424242,
label: 'Synthetic test display',
bounds: { x: 1280, y: 0, width: 1280, height: 720 },
size: { width: 1280, height: 720 },
},
]
}
screen.getAllDisplays = () => [...original(), targetDisplay]
}
})
require(root + '/dist-electron/electron/main.js')
async function poll(check, label) {
@@ -47,7 +68,7 @@ app.whenReady().then(async () => {
)
const evaluate = (s) => control.webContents.executeJavaScript(s)
assert.equal(await evaluate('typeof window.require'), 'undefined')
await evaluate('window.screenBridge.show(424242)')
await evaluate(`window.screenBridge.show(${targetDisplay.id})`)
const output = await poll(
() => BrowserWindow.getAllWindows().find((w) => w !== control),
'output',
@@ -63,6 +84,15 @@ app.whenReady().then(async () => {
)),
'metadata',
)
if (process.platform === 'linux')
assert.equal(app.commandLine.getSwitchValue('ozone-platform'), 'x11')
await poll(() => boundsBeforeFullscreen.has(output.id), 'output fullscreen requested')
assert.deepEqual(boundsBeforeFullscreen.get(output.id), targetDisplay.bounds)
if (!isSyntheticDisplay) {
await poll(() => fullscreenEntered.has(output.id), 'output entered fullscreen')
assert.ok(output.isFullScreen())
assert.equal(screen.getDisplayMatching(output.getBounds()).id, targetDisplay.id)
}
const range = await output.webContents.executeJavaScript(
`fetch('screen-app://app/media/video', {headers: {Range: 'bytes=100-199'}}).then(async r => ({status:r.status, size:(await r.arrayBuffer()).byteLength, range:r.headers.get('content-range')}))`,
)
@@ -117,7 +147,7 @@ app.whenReady().then(async () => {
assert.equal(await evaluate('typeof window.screenBridge.lan'), 'undefined')
assert.equal(await evaluate('typeof window.screenBridge.approve'), 'undefined')
output.close()
await evaluate('window.screenBridge.show(424242)')
await evaluate(`window.screenBridge.show(${targetDisplay.id})`)
const reopened = await poll(
() => BrowserWindow.getAllWindows().find((w) => w !== control),
'reopened output',
@@ -128,6 +158,13 @@ app.whenReady().then(async () => {
(await reopened.webContents.executeJavaScript('!!document.querySelector("video")')),
'reopened ready',
)
await poll(() => boundsBeforeFullscreen.has(reopened.id), 'reopened fullscreen requested')
assert.deepEqual(boundsBeforeFullscreen.get(reopened.id), targetDisplay.bounds)
if (!isSyntheticDisplay) {
await poll(() => fullscreenEntered.has(reopened.id), 'reopened entered fullscreen')
assert.ok(reopened.isFullScreen())
assert.equal(screen.getDisplayMatching(reopened.getBounds()).id, targetDisplay.id)
}
assert.equal(
await reopened.webContents.executeJavaScript('document.querySelector("video").paused'),
true,
@@ -136,6 +173,11 @@ app.whenReady().then(async () => {
'SCREEN_SMOKE_PASS',
JSON.stringify({
range,
display: {
id: targetDisplay.id,
bounds: targetDisplay.bounds,
synthetic: isSyntheticDisplay,
},
seek: position,
samples: (await evaluate('window.screenBridge.state()')).samples,
electron: process.versions.electron,