Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mk/osiris.mk
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ OSIRIS_DIFF_JOBS ?= 1
OSIRIS_DIFF_INSTALL_DEPS ?= 0
OSIRIS_DIFF_LOCAL ?= 0
OSIRIS_DIFF_MAE_THRESHOLD ?= 0.16
OSIRIS_DIFF_CHANGED_THRESHOLD ?= 0.46
OSIRIS_DIFF_CHANGED_THRESHOLD ?= 0.55
OSIRIS_DIFF_GEOMETRY ?= 1280x1024x24
OSIRIS_DIFF_TOP ?= 12
OSIRIS_DIFF_COMPARE_LOCATION ?= $(if $(filter 1 yes true,$(OSIRIS_DIFF_LOCAL)),local,remote)
Expand Down
57 changes: 53 additions & 4 deletions scripts/run-osiris-differential-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,13 @@ def remote_script(args, remote_repo):
input_backend=$9
replay_out="$remote_root/replay-$name"
rm -rf "$replay_out" "$remote_root/home-$name"
mkdir -p "$log_dir" "$screen_dir" "$remote_root/home-$name"
mkdir -p "$replay_out" "$log_dir" "$screen_dir" "$remote_root/home-$name"
replay_path="$repo/tests/ui/replays/$replay"
if [ "$input_backend" = xdotool ]; then
replay_path="$replay_out/$replay"
awk '/^wait-window / {{ sub(/[0-9]+[ \t]*$/, "15000") }} {{ print }}' \
"$repo/tests/ui/replays/$replay" > "$replay_path"
fi
# Read display from the current env so the parallel capture
# subshells can each target their own Xvfb. Strip the leading
# colon and any trailing .screen suffix to recover the numeric
Expand All @@ -228,12 +234,13 @@ def remote_script(args, remote_repo):
--app "$app" \\
--app-arg=-geometry --app-arg="$geometry_arg" \\
--workdir "$workdir" \\
--replay "$repo/tests/ui/replays/$replay" \\
--replay "$replay_path" \\
--out-root "$replay_out" \\
--display "$display_num" \\
--geometry {q(args.geometry)} \\
--input-backend "$input_backend" \\
--screenshot-command import \\
$([ "$input_backend" = internal ] && printf %s --in-process-snapshots) \\
--screenshot-region {q(args.screenshot_region)} \\
--env DISPLAY="$DISPLAY" \\
--env HOME="$remote_root/home-$name" \\
Expand Down Expand Up @@ -362,10 +369,34 @@ def remote_script(args, remote_repo):
xvfb_pid=$!
Xvfb "$compat_display" -screen 0 {q(args.geometry)} >"$remote_root/xvfb-compat.log" 2>&1 &
compat_xvfb_pid=$!
trap 'exit' INT TERM HUP
trap 'kill "$xvfb_pid" "$compat_xvfb_pid" >/dev/null 2>&1 || true' EXIT
sleep 1

# Osiris has 4 demos per side and each capture runs a replay so the
wait_for_display() {{
target=$1
server_pid=$2
waited=0
while [ "$waited" -lt 100 ]; do
if ! kill -0 "$server_pid" 2>/dev/null; then
echo "Xvfb for $target exited before accepting connections" >&2
return 1
fi
if DISPLAY="$target" xdotool getdisplaygeometry >/dev/null 2>&1; then
return 0
fi
# ponytail: sub-second poll assumes GNU coreutils sleep (the CI
# runner); switch to integer sleep if a POSIX-only sleep is ever
# targeted.
sleep 0.1
waited=$((waited + 1))
done
echo "Xvfb for $target did not become ready within 10s" >&2
return 1
}}
wait_for_display "$display" "$xvfb_pid"
wait_for_display "$compat_display" "$compat_xvfb_pid"

# Osiris has 5 demos per side and each capture runs a replay so the
# capture phase dominates the job. Run system-side and compat-side
# captures concurrently on separate Xvfb instances.
system_cap_log="$remote_root/logs/system-capture.log"
Expand Down Expand Up @@ -412,6 +443,15 @@ def remote_script(args, remote_repo):
"$system_logs" \\
"$system_screens" \\
xdotool
capture_osiris system-designer-menu \\
"$system_designer" \\
"$osiris_src/tools/designer/designer" \\
osiris-designer-menu.replay \\
940x740+0+0 \\
"$system_build" \\
"$system_logs" \\
"$system_screens" \\
xdotool
) >"$system_cap_log" 2>&1 &
system_cap_pid=$!

Expand Down Expand Up @@ -454,6 +494,15 @@ def remote_script(args, remote_repo):
"$compat_logs" \\
"$compat_screens" \\
internal
capture_osiris compat-designer-menu \\
"$compat_designer" \\
"$osiris_src/tools/designer/designer" \\
osiris-designer-menu.replay \\
940x740+0+0 \\
"$repo/build/osiris/build:$repo/build" \\
"$compat_logs" \\
"$compat_screens" \\
internal
) >"$compat_cap_log" 2>&1 &
compat_cap_pid=$!

Expand Down
76 changes: 74 additions & 2 deletions src/snapshot.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "events.h"
#include "replay-target.h"
#include "snapshot.h"
#include "window.h"
#include "window-internal.h"
#include "util.h"

Expand Down Expand Up @@ -181,6 +182,68 @@ static int waitSnapshotResult(int *waitRcOut)
return rc;
}

/* In-process snapshots read a single SDL window surface (the replay
* target). Qt popup menus and other override-redirect shells live in
* their own borderless SDL windows, so they are absent from that read
* even though they render correctly. Composite every mapped
* override-redirect top-level onto a copy of the target surface, in
* stacking order, so the snapshot matches what a whole-screen capture
* (the system side's import) would see.
*
* Returns a newly allocated surface the caller must free, or NULL when
* no popup is present, in which case the caller saves the target
* surface unchanged.
*/
static SDL_Surface *composeOverlayPopups(SDL_Surface *target, Window targetWin)
{
if (SCREEN_WINDOW == None || targetWin == None)
return NULL;
WindowStruct *targetStruct = GET_WINDOW_STRUCT(targetWin);
Window *children = GET_CHILDREN(SCREEN_WINDOW);
size_t count = GET_WINDOW_STRUCT(SCREEN_WINDOW)->children.length;
SDL_Surface *composed = NULL;
for (size_t i = 0; i < count; i++) {
Window child = children[i];
if (child == targetWin)
continue;
WindowStruct *childStruct = GET_WINDOW_STRUCT(child);
if (!childStruct->overrideRedirect || childStruct->mapState != Mapped ||
!childStruct->sdlWindow)
continue;
SDL_Surface *childSurface =
SDL_GetWindowSurface(childStruct->sdlWindow);
if (!childSurface)
continue;
if (!composed) {
/* SDL_DuplicateSurface is absent from the older SDL2 on the
* differential runners, so build the copy the way drawing.c
* does: a matching-format surface plus a base blit.
*/
composed = SDL_CreateRGBSurfaceWithFormat(
0, target->w, target->h, 32, XC_SURFACE_FMT_ENUM(target));
if (!composed)
return NULL;
if (SDL_BlitSurface(target, NULL, composed, NULL) != 0) {
SDL_FreeSurface(composed);
return NULL;
}
}
/* ponytail: 1:1 X-logical to surface pixel mapping, correct on the
* Xvfb CI path. A HiDPI host would need the surface-to-logical
* scale folded into the offset; blit clipping handles popups that
* fall partly or wholly outside the target rect.
*/
SDL_Rect dst = {
.x = childStruct->x - targetStruct->x,
.y = childStruct->y - targetStruct->y,
.w = childSurface->w,
.h = childSurface->h,
};
SDL_BlitSurface(childSurface, NULL, composed, &dst);
}
return composed;
}

int snapshotHandleEvent(const SDL_Event *event)
{
SnapshotEnvelope *env = (SnapshotEnvelope *) event->user.data1;
Expand All @@ -195,6 +258,7 @@ int snapshotHandleEvent(const SDL_Event *event)
char *path = env->path;
int rc = 0;
SDL_Surface *ownedSurface = NULL;
SDL_Surface *composed = NULL;
Uint32 winId = replayTargetWindowId();
SDL_Window *win = (winId != 0) ? SDL_GetWindowFromID(winId) : NULL;
if (!win) {
Expand Down Expand Up @@ -228,6 +292,12 @@ int snapshotHandleEvent(const SDL_Event *event)
}
}

/* Fold any mapped popup windows (Qt menus etc.) into the saved image so
* the single-window read matches the system side's whole-screen capture.
*/
composed = composeOverlayPopups(surface, getWindowFromId(winId));
SDL_Surface *saveSurface = composed ? composed : surface;

/* SDL_SaveBMP writes incrementally to the open file, so a runner that polls
* for this path can observe a non-empty but truncated BMP and fail to
* decode it (PIL "image file is truncated"). Write to a temp file and
Expand All @@ -248,7 +318,7 @@ int snapshotHandleEvent(const SDL_Event *event)
}
memcpy(tmpPath, path, pathLen);
memcpy(tmpPath + pathLen, ".tmp", sizeof(".tmp"));
if (SDL_SaveBMP(surface, tmpPath) != 0) {
if (SDL_SaveBMP(saveSurface, tmpPath) != 0) {
LOG("snapshot: SDL_SaveBMP(%s) failed: %s\n", tmpPath, SDL_GetError());
free(tmpPath);
rc = -3;
Expand All @@ -262,8 +332,10 @@ int snapshotHandleEvent(const SDL_Event *event)
goto signal;
}
free(tmpPath);
LOG("snapshot: wrote %s (%dx%d)\n", path, surface->w, surface->h);
LOG("snapshot: wrote %s (%dx%d)\n", path, saveSurface->w, saveSurface->h);
signal:
if (composed)
SDL_FreeSurface(composed);
if (ownedSurface)
SDL_FreeSurface(ownedSurface);
signalSnapshotResult(env->generation, rc);
Expand Down
Loading