up
This commit is contained in:
@@ -26,6 +26,8 @@ Commit и push выполняет только пользователь. Вло
|
||||
## Запуск стенда SCREEN-001
|
||||
|
||||
Нужны Node.js 24.15+ и ffmpeg с libx264 в PATH.
|
||||
В Linux приложение использует X11; для сессии Wayland нужен XWayland, чтобы
|
||||
размещать показ на выбранном мониторе.
|
||||
|
||||
```sh
|
||||
npm ci
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user