Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,226 @@ import Testing
import WaylandClient

extension WaylandDisplayPublicIntegrationTests {
@Test
func asyncSoftwarePresentationSupersedesStalePreparationAndCommitsReplacement() async throws {
try await withPublicConnection { display in
try await exerciseStaleSoftwarePresentation(on: display)
}
}

@Test
func canceledAsyncSoftwarePreparationIsSupersededAndRetryable() async throws {
try await withPublicConnection { display in
try await exerciseCanceledSoftwarePresentation(on: display)
}
}

@Test
func closingWindowDuringAsyncSoftwarePreparationReturnsClosedWithoutDrawing() async throws {
try await withPublicConnection { display in
try await exerciseWindowCloseDuringSoftwarePreparation(on: display)
}
}

@Test
func closingDisplayDuringAsyncSoftwarePreparationReturnsClosedWithoutDrawing() async throws {
try await withPublicConnection { display in
try await exerciseDisplayCloseDuringSoftwarePreparation(on: display)
}
}

private func exerciseStaleSoftwarePresentation(on display: WaylandDisplay) async throws {
let displayEvents = display.events
let window = try await display.createTopLevelWindow(
configuration: testWindowConfiguration()
)

let initialOutcome = try await window.show(
timeoutMilliseconds: publicIntegrationTimeoutMilliseconds,
preparing: { $0.id },
{ preparedID, frame in
#expect(preparedID == frame.id)
fill(frame, color: 0x0014_2434)
}
)
#expect(initialOutcome == .presented)
#expect(
try await window.redraw(
preparing: { $0.id },
{ _, _ in throw UnexpectedStaleSoftwareDraw() }
) == .deferred
)
try await waitForRedrawRequest(for: window, in: displayEvents)

let gate = AsyncSoftwarePreparationGate()
async let staleOutcome = window.redraw(
requestPresentationFeedback: true,
preparing: { reservation in
await gate.suspendPreparation()
return reservation.id
},
{ _, _ in throw UnexpectedStaleSoftwareDraw() }
)
await gate.waitUntilSuspended()

async let replacementEvent = displayEvent(
in: displayEvents,
matching: { $0 == .redrawRequested(window.id) },
after: {
try await window.requestRedraw()
await gate.resumePreparation()
}
)

#expect(try await staleOutcome == .superseded)
#expect(try await replacementEvent == .redrawRequested(window.id))
#expect(try await window.needsRedraw)
let replacementOutcome = try await window.redraw(
preparing: { $0.id },
{ preparedID, frame in
#expect(preparedID == frame.id)
fill(frame, color: 0x0044_2414)
}
)
#expect(replacementOutcome == .presented)
await window.close()
}

private func exerciseCanceledSoftwarePresentation(on display: WaylandDisplay) async throws {
let displayEvents = display.events
let window = try await display.createTopLevelWindow(
configuration: testWindowConfiguration()
)
try await show(window, color: 0x0024_1424)
try await waitForRedrawRequest(for: window, in: displayEvents)

let gate = AsyncSoftwarePreparationGate()
let outcome = try await withThrowingTaskGroup(
of: SoftwarePresentationOutcome.self
) { group in
group.addTask {
try await window.redraw(
preparing: { reservation in
await gate.suspendPreparation()
return reservation.id
},
{ _, _ in throw UnexpectedStaleSoftwareDraw() }
)
}
await gate.waitUntilSuspended()
group.cancelAll()

async let replacementEvent = displayEvent(
in: displayEvents,
matching: { $0 == .redrawRequested(window.id) },
after: { await gate.resumePreparation() }
)
let nextOutcome = try await group.next()
#expect(try await replacementEvent == .redrawRequested(window.id))
return try #require(nextOutcome)
}
#expect(outcome == .superseded)
#expect(
try await window.redraw(
preparing: { $0.id },
{ _, frame in fill(frame, color: 0x0034_1434) }
) == .presented
)
await window.close()
}

private func exerciseWindowCloseDuringSoftwarePreparation(
on display: WaylandDisplay
) async throws {
let displayEvents = display.events
let window = try await display.createTopLevelWindow(
configuration: testWindowConfiguration()
)
try await show(window, color: 0x0011_2233)
try await waitForRedrawRequest(for: window, in: displayEvents)

let gate = AsyncSoftwarePreparationGate()
async let outcome = window.redraw(
preparing: { reservation in
await gate.suspendPreparation()
return reservation.id
},
{ _, _ in throw UnexpectedStaleSoftwareDraw() }
)
await gate.waitUntilSuspended()
await window.close()
await gate.resumePreparation()

#expect(try await outcome == .closed)
#expect(
try await window.redraw(
preparing: { $0.id },
{ _, _ in throw UnexpectedStaleSoftwareDraw() }
) == .closed
)
}

private func exerciseDisplayCloseDuringSoftwarePreparation(
on display: WaylandDisplay
) async throws {
let displayEvents = display.events
let window = try await display.createTopLevelWindow(
configuration: testWindowConfiguration()
)
try await show(window, color: 0x0011_3344)
try await waitForRedrawRequest(for: window, in: displayEvents)

let gate = AsyncSoftwarePreparationGate()
async let outcome = window.redraw(
preparing: { reservation in
await gate.suspendPreparation()
return reservation.id
},
{ _, _ in throw UnexpectedStaleSoftwareDraw() }
)
await gate.waitUntilSuspended()
await display.close()
await gate.resumePreparation()

#expect(try await outcome == .closed)
}

@Test
func throwingAsyncSoftwarePreparationReleasesReservationAndRepublishesRedraw() async throws {
try await withPublicConnection { display in
let displayEvents = display.events
let window = try await display.createTopLevelWindow(
configuration: testWindowConfiguration()
)
try await show(window, color: 0x0033_2211)
try await waitForRedrawRequest(for: window, in: displayEvents)

let replacementEvent = try await displayEvent(
in: displayEvents,
matching: { $0 == .redrawRequested(window.id) },
after: {
do {
_ = try await window.redraw(
preparing: { _ in throw InjectedSoftwarePreparationFailure() },
{ _, _ in throw UnexpectedStaleSoftwareDraw() }
)
Issue.record("expected software preparation failure")
} catch is InjectedSoftwarePreparationFailure {
// The original preparation failure remains observable.
}
}
)
#expect(replacementEvent == .redrawRequested(window.id))
#expect(
try await window.redraw(
preparing: { $0.id },
{ _, frame in fill(frame, color: 0x0022_3344) }
) == .presented
)
await window.close()
}
}

@Test
func presentationFeedbackReportsUnavailableOrPublishesResult() async throws {
try await withPublicConnection { display in
Expand Down Expand Up @@ -53,10 +273,10 @@ private func expectPresentationFeedback(
try await window.requestRedraw()
}
)
try await window.requestPresentationFeedback()
try await window.redraw { frame in
let outcome = try await window.redraw(requestPresentationFeedback: true) { frame in
fill(frame, color: 0x0044_2414)
}
#expect(outcome == .presented)

let feedback: SurfacePresentationFeedback?
do {
Expand Down Expand Up @@ -98,3 +318,48 @@ private func noteOptionalProtocolRuntimeSkip(test: String, interfaceName: String
"Compositor advertised \(interfaceName) for \(test) but did not deliver a terminal event."
)
}

private func waitForRedrawRequest(
for window: Window,
in displayEvents: DisplayEvents
) async throws {
_ = try await displayEvent(
in: displayEvents,
matching: { $0 == .redrawRequested(window.id) },
after: { try await window.requestRedraw() }
)
}

private actor AsyncSoftwarePreparationGate {
private var isSuspended = false
private var suspensionWaiters: [CheckedContinuation<Void, Never>] = []
private var resumeContinuation: CheckedContinuation<Void, Never>?

func suspendPreparation() async {
isSuspended = true
let waiters = suspensionWaiters
suspensionWaiters.removeAll()
for waiter in waiters {
waiter.resume()
}

await withCheckedContinuation { continuation in
resumeContinuation = continuation
}
}

func waitUntilSuspended() async {
guard !isSuspended else { return }
await withCheckedContinuation { continuation in
suspensionWaiters.append(continuation)
}
}

func resumePreparation() {
resumeContinuation?.resume()
resumeContinuation = nil
}
}

private struct UnexpectedStaleSoftwareDraw: Error {}
private struct InjectedSoftwarePreparationFailure: Error {}
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,68 @@ struct WaylandPresentationAPISurfaceTests {

_ = usePresentationFeedbackAPI
}

@Test
func softwarePresentationOutcomesCompileForExternalClients() {
let outcomes: [SoftwarePresentationOutcome] = [
.presented,
.superseded,
.deferred,
.closed,
]

#expect(Set(outcomes).count == 4)
_ = useAtomicSoftwarePresentationAPI
}

private func useAtomicSoftwarePresentationAPI(
_ window: Window
) async throws -> [SoftwarePresentationOutcome] {
let simpleShow = try await window.show(requestPresentationFeedback: true) { _ in () }
let simpleRedraw = try await window.redraw(requestPresentationFeedback: true) { _ in () }
let simpleDamagedShow = try await window.show(
damage: nil,
requestPresentationFeedback: true,
timeoutMilliseconds: 1_000
) { _ in () }
let simpleDamagedRedraw = try await window.redraw(
damage: nil,
requestPresentationFeedback: true
) { _ in () }
let shown = try await window.show(
requestPresentationFeedback: true,
preparing: { reservation in reservation.id },
{ _, _ in () }
)
let redrawn = try await window.redraw(
requestPresentationFeedback: true,
preparing: { reservation in reservation.id },
{ _, _ in () }
)
let damagedShow = try await window.show(
damage: nil,
timeoutMilliseconds: 1_000,
requestPresentationFeedback: true,
preparing: { reservation in reservation.id },
{ _, _ in () }
)
let damagedRedraw = try await window.redraw(
damage: nil,
requestPresentationFeedback: true,
preparing: { reservation in reservation.id },
{ _, _ in () }
)
return [
simpleShow,
simpleRedraw,
simpleDamagedShow,
simpleDamagedRedraw,
shown,
redrawn,
damagedShow,
damagedRedraw,
]
}
}

@Suite("WaylandDisplay data transfer public API surface")
Expand Down
6 changes: 0 additions & 6 deletions Sources/CWaylandProtocols/generated/shims/request-bridges.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,6 @@ void swl_seat_release(
wl_seat_release(object);
}

struct wl_callback *swl_surface_frame(
struct wl_surface *object)
{
return wl_surface_frame(object);
}

void swl_surface_set_buffer_scale(
struct wl_surface *object,
int32_t request_scale)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include <stdint.h>
#include <wayland-client.h>

struct wl_callback;
struct wl_compositor;
struct wl_data_device;
struct wl_data_device_manager;
Expand Down Expand Up @@ -97,9 +96,6 @@ struct wl_touch *swl_seat_get_touch(
void swl_seat_release(
struct wl_seat *object);

struct wl_callback *swl_surface_frame(
struct wl_surface *object);

void swl_surface_set_buffer_scale(
struct wl_surface *object,
int32_t request_scale);
Expand Down
Loading
Loading