Skip to content

feat(screen): share the container's screen in a browser, driving nothing - #7

Merged
magicabdel merged 5 commits into
magicabdel:masterfrom
theophile-wallez:feat/screen-share
Aug 23, 2026
Merged

magicabdel merged 5 commits into
magicabdel:masterfrom
theophile-wallez:feat/screen-share

Conversation

@theophile-wallez

Copy link
Copy Markdown
Contributor

Stacked on #6 — its commit is the first of the five here and disappears from this
diff when #6 merges.

The report

login --web showed a black page with everything apparently running.

Why it was black

  1. A login session from an earlier hour was still alive and owned intune-portal
    on its own display, :77.
  2. The new --web run made :78 and asked the container to start the portal.
  3. intune-portal is a single instance. The second launch handed its request to
    the process on :77 and exited, so :78 never had a window on it.
  4. The viewer streamed an empty display: a black 1280×800 rectangle.
  5. portal_is_running greps the process name, so it found the old portal and
    reported the portal as up. Nothing in the page said otherwise.

What is here

screen, a new command. It streams; it does not sign in. It starts the portal so
the first Intune screen is there without a second command, and from that point it
touches nothing: the portal, the broker's Authentication dialog and an Edge window
all appear as they are, and the reader drives them. login is unchanged.

It looks for a display before it makes one — a display in this tool's range that
already has a window is the one it shares — so case 3 above streams the portal that
exists instead of an empty screen. A share that finds a display leaves it running;
only a share that opened one closes it. A portal running with no display is
closed first, because it is unreachable and would otherwise absorb the next launch.

The viewer sends the windows, not the display. A 478×628 portal on a 1280×800
screen meant two thirds of the page was undrawn desktop. It now sends the union of
the mapped windows, read from the X geometry rather than from a brightness scan, so a
dark-themed window is not cropped to its light part. The picture therefore changes
size mid-session: the canvas takes its size from each answer, and a click is put back
on the display by the box's origin before XTEST sees it.

A screen with nothing on it says so on the picture. The status line already said
it, but it is one line under a large black rectangle.

Two fixes underneath.

  • portal_stop now waits for the exit. It sent TERM and returned, so the detach that
    follows saw a GUI app running and skipped — which is why a machine was found
    reporting Display fwd: on (GUI session active) for a session that had ended. It
    is also what makes the close-then-start above reach a container with no instance.
  • Xvfb::start takes the next display number when another process wins the race
    between finding a free one and starting a server on it. Two screen shares started in
    the same second race exactly like that; two X11 tests run together already did, and
    failed about half the time. Nothing is matched against Xvfb's text — the number
    being taken now, having been free a moment ago, is the signal. The failure message
    also stopped quoting the xkbcomp keysym warnings Xvfb opens with and now carries the
    error line.
  • Placing and focusing a window no longer refuses a keystroke when it fails: there is
    no window manager, so the call races the window it names.

Tested

  • cargo test --lib: 89 passed. cargo fmt --check and
    cargo clippy --all-targets -- -W clippy::all: clean.
  • The two X11 end-to-end tests pass together, five runs in a row — they could not be
    run together at all before the Xvfb fix.
  • Every one of the five commits builds on its own.
  • Against the real container on a headless host: screen streams the Intune Agent
    first screen at 478×628; a click on Sign in sent from the browser reaches the
    portal and starts the identity broker; a second screen shares the first one's
    display instead of showing black, and leaves it running when it closes; a stale
    portal with a dead display is recovered; Finish closes the portal, drops the
    display and returns the container to headless.

`--web` mints a token for every session and puts it in the link, so the
browser keeps the old link in its history and offers it back in the
address bar. The next session refuses it with `wrong token`, two words in
plain text: true, and no help at all. The reader cannot tell it from a
broken viewer, and the one thing they have to do — take the link this run
printed — is not in it.

A refused page load now answers with a page that says the link belongs to
an older session, that the terminal printed the current one, and that the
address bar completes the stale one. The paths the script calls keep the
plain body, because it reads the status and never the text.

The printed instructions say the same thing, at the point where the
reader is about to copy the link.
`portal_stop` sent TERM and returned. Two things then happened on a
timing that nobody controls.

The teardown that follows it asks `auto_detach_after_gui` to return the
container to headless, and that skips while a GUI app is running — so a
detach that raced the exit left the container forwarding a display with
nothing on it. `status` then reports "Display fwd: on (GUI session
active)" for a session that ended minutes ago, and neither the reader nor
the next run can tell that from a sign-in in progress. That is the state a
machine was found in after a `--web` session that had already finished.

The second is worse and is coming: `intune-portal` is a single instance, so
a command that closes one and starts another hands its request to the
process on the way out, and the display it just made stays empty.

So the close waits for the exit, bounded at ten seconds and with a warning
when the portal outlasts it. TERM is still what it sends: the portal writes
the broker's account state on the way out, and waiting is what lets it
finish.
… race

Finding a free number and starting a server on it are two steps, and
another process can take the number in between. The loser reported "Xvfb
exited immediately" and the command failed, although twenty-one other
numbers were free.

It was reachable only from the test suite until now — two X11 tests each
take the first free display, so running them together failed about half the
time — but a screen share makes it a user's problem: two of them started in
the same second race exactly like that.

A failure whose number is taken NOW, having been free a moment ago, was
taken by somebody else, so the search moves on. A failure that leaves the
number free — Xvfb missing, a geometry it refuses — is reported as it is
rather than tried twenty-one more times. Nothing is matched against Xvfb's
text, so nothing depends on how it words itself.

The message it does print is worth having: Xvfb opens with pages of xkbcomp
keysym warnings, so the first line said "The XKEYBOARD keymap compiler
(xkbcomp) reports:" and sent the reader after a keyboard problem they did
not have. The reason is on the error line after them, and that is what the
message now carries.

The search also stops at :98 rather than :99, which is the container's own
compliance agent display: it was excluded in practice, because its socket
is in the bound-in /tmp/.X11-unix, and it is excluded on purpose now.
Two ways the browser viewer showed a reader black pixels and left them to
guess.

A portal window is 478×628 on the 1280×800 display it is given, and the
viewer sent the display. Two thirds of the page was therefore desktop that
nobody had drawn on, with the window in a corner — and a reader who opens
that reasonably concludes the viewer is broken. It now sends the box around
the windows: the union of every mapped window big enough to be real, read
from the X geometry rather than from the pixels (the terminal viewer's
brightness scan would crop a dark-themed window to the light part of it).
The union, not the topmost window, because the identity broker opens its
Authentication dialog beside the portal rather than over it.

The picture therefore changes size while the session runs, so the page takes
the canvas size from each answer instead of from the page load, and a click
is put back on the display by the box's origin before XTEST sees it. The
whole-frame repaint that a size change needs was already there: a diff is
only taken against a frame of the same size.

The other way is the display that genuinely has nothing on it, for the half
minute the portal takes to draw. The status line said "waiting for the
sign-in window", but it is one line under a large black rectangle, and the
rectangle is what the reader is looking at. The reason is now on the picture,
and it goes when a window arrives.

Placing and focusing a window no longer refuses a keystroke when it fails.
There is no window manager on this display, so the call races the window it
names: a dialog that closes between the capture and the keystroke makes the
request name a window the server has forgotten. Dropping the keystroke for
that is the wrong trade, since XTEST sends it to whatever holds the focus.

The end-to-end test types its line more than once if it has to. The window
being up is what it can see; a shell inside it being ready to read is not,
and on a loaded machine the first line can arrive before the prompt.
`login` signs in for you: it presses Sign in, types the address and the
password, and closes what it opened. A reader who wants to do the steps
themselves — read what Intune says, answer a prompt it did not expect,
follow a conditional-access page — has no command for that. Using
`login --web` for it means an automation is pressing buttons under them.

`screen` streams instead. It starts the Intune portal, so the first screen
is there without a second command, and from that point it touches nothing:
the portal, the identity broker's Authentication dialog and an Edge window
all appear as they are, and the reader's own keyboard and mouse go to them.

It looks for a display before it makes one, which is the difference between
working and a black page. `intune-portal` is a single instance: a second
launch hands its request to the running one and exits, so a command that
insists on its own display gets a display with nothing on it while the
portal draws on the one some earlier session made. That is exactly how a
reader ends up watching black pixels with everything apparently running. So
a display in this tool's range that already has a window on it is the one
shared, and a share that finds a display leaves it alone when it closes —
only a share that opened one closes it.

A portal running with no display at all is the third case, and it is
unreachable rather than merely invisible: its display went with the session
that made it. That one is closed before a new one starts, because the
launch would otherwise reach the single instance on the way out.
@magicabdel
magicabdel merged commit 9c9a466 into magicabdel:master Aug 23, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants