Compare commits

...
Author SHA1 Message Date
branderson e531228fa9 fix(streaming): keep Flash Player's window in place while it's open
Flash Player moves its window back to the top of the screen once the
movie has loaded, bringing the menu bar back. The streamer now checks
the window twice a second and puts it back, logging the first few
times.
2026-10-14 18:10:11 -07:00
branderson d1cebec142 fix(streaming): measure Flash Player's menu bar from the movie's offset
GTK draws the projector's menu bar in its window, not in a child window,
so the previous lookup found nothing and, under set -e and pipefail,
silently ended the block that moves the window. The movie is the
window's largest child, and its offset is the menu bar's height (56px
on flashbox). The block no longer stops on errors, logs what it measured
and where the window ended up, and GAME_WIDTH/GAME_HEIGHT, unused now,
are dropped.
2026-10-14 18:03:52 -07:00
branderson 3c4d3e7216 Merge pull request 'Streaming: hide Flash Player's menu bar without full screen' (#9) from flash-windowed into main
Images / app (push) Successful in 4m53s
Images / session-manager (push) Successful in 1m46s
Images / streamer (push) Successful in 3m31s
Reviewed-on: #9
Reviewed-by: branderson <brad@branderson.io>
2026-10-14 17:26:37 -07:00
branderson e697638827 Merge pull request 'Carry Flash saves between Ruffle and the original Flash Player' (#8) from flash-save-sync into main
Images / app (push) Successful in 4m55s
Images / session-manager (push) Successful in 1m41s
Images / streamer (push) Successful in 3m31s
Reviewed-on: #8
Reviewed-by: branderson <brad@branderson.io>
2026-10-14 17:26:19 -07:00
branderson 1306a88b0e fix(streaming): hide Flash Player's menu bar without full screen
Some games break in Flash Player's full-screen mode: in Bloons darts
make a sound but never fly. Instead of pressing Ctrl+F, the streamer
stretches the projector's window to the display and moves it up by the
menu bar's height, measured from the window's opening height and the
movie's. The session manager now passes the game's exact size as
GAME_WIDTH/GAME_HEIGHT and rounds only the display size to even.
2026-10-14 17:25:27 -07:00
6 changed files with 57 additions and 21 deletions
+3 -2
View File
@@ -46,8 +46,9 @@ export class DockerDriver implements Driver {
`PUID=${uid}`,
`PGID=${gid}`,
`SUBFOLDER=${streamPath(id)}`,
`SELKIES_MANUAL_WIDTH=${size.width}`,
`SELKIES_MANUAL_HEIGHT=${size.height}`,
// The game's size, rounded up to even for H.264
`SELKIES_MANUAL_WIDTH=${size.width + (size.width % 2)}`,
`SELKIES_MANUAL_HEIGHT=${size.height + (size.height % 2)}`,
'SELKIES_UI_SHOW_SIDEBAR=false',
'HARDEN_DESKTOP=true',
'RESTART_APP=true',
+6 -11
View File
@@ -59,7 +59,7 @@ export class GameCatalog {
return null;
}
const page = await this.readContent(gameId, row, row.cmd).catch(() => null);
return even((page && appletSize(page.toString('latin1'))) ?? DEFAULT_SIZE);
return ((page && appletSize(page.toString('latin1'))) ?? DEFAULT_SIZE);
}
const program = await this.launchProgram(row);
if (!program) {
@@ -68,19 +68,19 @@ export class GameCatalog {
const url = row.cmd.match(/https?:\/\/[^\s"']+/)?.[0];
if (!url) {
const exe = await readFile(program).catch(() => null);
return even((exe && projectorStageSize(exe)) ?? DEFAULT_SIZE);
return ((exe && projectorStageSize(exe)) ?? DEFAULT_SIZE);
}
const content = await this.readContent(gameId, row, url).catch(() => null);
const extension = path.extname(new URL(url).pathname).toLowerCase();
if (['.dcr', '.dir', '.dxr', '.cct', '.cst'].includes(extension)) {
return even((content && directorStageSize(content)) ?? DEFAULT_SIZE);
return ((content && directorStageSize(content)) ?? DEFAULT_SIZE);
}
if (extension === '.swf') {
// The exact stage: in full screen, games that set noScale keep their native size
return even((content && swfStageSize(content)) ?? DEFAULT_SIZE);
// The exact stage, so the movie fills the display without scaling
return ((content && swfStageSize(content)) ?? DEFAULT_SIZE);
}
const embed = content && embedSize(content.toString('latin1'));
return even(embed ? { width: embed.width + 2 * PAGE_MARGIN, height: embed.height + 2 * PAGE_MARGIN } : BROWSER_SIZE);
return (embed ? { width: embed.width + 2 * PAGE_MARGIN, height: embed.height + 2 * PAGE_MARGIN } : BROWSER_SIZE);
}
/**
@@ -151,11 +151,6 @@ async function resolveCaseInsensitive(root: string, relative: string): Promise<s
return (await stat(current).catch(() => null))?.isFile() ? current : null;
}
/** H.264 needs even dimensions. */
function even({ width, height }: Size): Size {
return { width: width + (width % 2), height: height + (height % 2) };
}
/** Reads the stage size from a SWF header: a rect of twips after the 8-byte header. */
export function swfStageSize(swf: Buffer): Size | null {
const signature = swf.toString('latin1', 0, 3);
+3 -3
View File
@@ -177,11 +177,11 @@ test('GameCatalog streams allowed platforms whose launcher resolves, sized from
legacy: { width: 300, height: 200 },
missing: { width: 800, height: 600 },
webstart: null,
spr: { width: 500, height: 426 },
oldcpu: { width: 500, height: 426 },
spr: { width: 500, height: 425 },
oldcpu: { width: 500, height: 425 },
secure: { width: 640, height: 480 },
navigator: { width: 528, height: 400 },
flash: { width: 500, height: 376 },
flash: { width: 500, height: 375 },
unity: null,
'no-program': null,
exe: { width: 800, height: 600 },
+1
View File
@@ -30,6 +30,7 @@ RUN apt-get update && \
libxtst6 \
libxxf86vm1 \
sqlite3 \
x11-utils \
xdotool && \
rm -rf /var/lib/apt/lists/*
+8 -3
View File
@@ -52,6 +52,11 @@ writable mirror of the install, since they write beside themselves; a
Director projector's `Prefs`, where it saves, is linked into `/config/saves`, as
is Flash Player's save directory, so the session manager syncs both to the
player's browser. OldCPUSimulator hangs under Wine, so its target runs under
`cpulimit` at the same share of the CPU. Flash projectors switch to full screen,
which hides their menu bar and scales the movie to the display, and Flashpoint
Navigator's toolbars are hidden.
`cpulimit` at the same share of the CPU. A Flash projector's window is
stretched to the display and moved up by its menu bar's height, which hides the
menu bar. GTK draws the menu bar in the window itself and the movie in a child
window below it, so the movie's offset, read with `xwininfo`, is the menu bar's
height. The window is made override-redirect first, since the window manager
keeps windows on screen, and is kept in place while it's open, since Flash
Player moves it back once the movie has loaded. Its full-screen mode isn't used, since some games, such
as Bloons, don't play right in it. Flashpoint Navigator's toolbars are hidden.
@@ -47,8 +47,42 @@ case ${app,,} in
mkdir -p "$HOME/saves/Flash_Player" "$HOME/.macromedia"
[[ -e $HOME/.macromedia/Flash_Player ]] || ln -s "$HOME/saves/Flash_Player" "$HOME/.macromedia/Flash_Player"
if [[ ${app,,} == 'fpsoftware\flash\'* ]]; then
# Full screen hides the projector's menu bar and scales the movie to the display
(xdotool search --sync --onlyvisible --name 'Flash Player' windowactivate --sync key ctrl+f) &
# Hide the projector's menu bar by moving it above the display, and fill
# the display with the movie. Not full screen, which some games, such as
# Bloons, don't play right in. The window manager keeps windows on
# screen, so the window is taken out of its hands first.
(
set +eo pipefail
wid=$(xdotool search --sync --onlyvisible --name 'Flash Player' | head -1)
read -r width height < <(xdotool getdisplaygeometry)
# GTK draws the menu bar in the window itself, and the movie in the
# largest child window, below it: the movie's offset is the menu bar's height
menu=$(xwininfo -children -id "$wid" | awk '
match($0, /[0-9]+x[0-9]+\+-?[0-9]+\+-?[0-9]+/) {
split(substr($0, RSTART, RLENGTH), g, /[x+]/)
if (g[1] * g[2] > area) { area = g[1] * g[2]; top = g[4] }
}
END { print top + 0 }')
((menu >= 0 && menu < height)) || menu=0
echo "Flash Player window $wid: menu bar ${menu}px, display ${width}x${height}"
xdotool windowunmap --sync "$wid"
xdotool set_window --overrideredirect 1 "$wid"
xdotool windowmap --sync "$wid"
place() { xdotool windowsize "$wid" "$width" $((height + menu)) windowmove "$wid" 0 $((-menu)); }
place
xdotool windowfocus "$wid"
# Flash Player moves its window back once the movie has loaded, so it's
# kept in place for as long as it's open
moves=0
while geometry=$(xdotool getwindowgeometry --shell "$wid" 2>/dev/null); do
eval "$geometry"
if ((X != 0 || Y != -menu || WIDTH != width || HEIGHT != height + menu)); then
((++moves <= 3)) && echo "Flash Player window moved to $X,$Y (${WIDTH}x${HEIGHT}); putting it back"
place
fi
sleep 0.5
done
) &
fi
cd "${argv[0]}"
exec "${argv[@]:1}" ;;