Skip to content

Show the phone, live, and cut the settings down to two - #7

Merged
MAXAWER merged 3 commits into
mainfrom
rename/mthread-draw
Aug 26, 2026
Merged

MAXAWER merged 3 commits into
mainfrom
rename/mthread-draw

Conversation

@MAXAWER

@MAXAWER MAXAWER commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Three commits: the window now shows the phone, the project has a visual
identity, and the release carries the WinUI front end.

The window shows the phone

It had no idea what a phone looked like. A stroke preview on a grey panel
answers "what will be drawn" and not "where", and the second question is the one
people actually have.

mthread.mirror.ScreenMirror runs com.mthread.Mirror out of the same jar as
the touch injector: the phone captures, scales and JPEG-compresses, so the cable
carries 27 kB. Measured on a Pixel 8 Pro at 1344×2992, per frame:

adb exec-out screencap -p 2.1 s sixteen megabytes, PNG-encoded on the phone
adb exec-out screencap 2.9 s no encoding, but all of it over a 5 MB/s link
this 0.29 s scaled and compressed before it leaves

3.4 frames a second, and the ceiling is screencap itself — about 300 ms
whatever you do with the result. This is not scrcpy and does not try to be:
scrcpy is fast because it feeds the display to the hardware H.264 encoder, which
needs a decoder at the other end.

The frame is sized from the device's own resolution, so the proportions are the
phone's rather than a guess, and preview now also returns the strokes
positioned by the same fit_to_screen call the drawing itself makes, drawn over
the live view in red.

Two false starts are recorded in the code, because both cost real time. Plain
adb shell allocates a pty, a pty turns every line feed into a carriage return
and a line feed, and a JPEG is full of line feeds — so the first frame appears to
work and every frame after it is out of step by however many newlines the one
before it contained. adb exec-out has no pty and is the usual answer for binary
output, but it is output only: stdin never arrives and it hangs after READY. The
frame is now base64 on a single line, which no line-ending translation can touch.

Two sliders, not five

Edge sensitivity and detail were always turned together and nobody could say what
the difference was. Speed and hand simulation were one axis pretending to be two:
nobody wants a hand that draws instantly or a machine that trembles. The margin is
fixed at six per cent. The front end also connects by itself when there is exactly
one ready device, and opens an image named on its command line.

The device that was not there

adb had the phone the whole time; its daemon was wedged, as it is when started
before the USB driver settles or when another copy of adb claimed port 5037
first. adb devices then reports nothing for a phone that is plugged in and
working, and asking again never helps — kill-server does. find_devices now
does that once, and only when the list came back empty, since killing the daemon
disconnects anything else using it.

Identity

The icon is a white circle, on a near-black square rather than on nothing: a
white disc on transparency is invisible against a light taskbar, a light Start
Menu and half of GitHub, which is not a mark but a hole.

The title bar is ours. The window was carrying two copies of its own name, one in
the system caption and one in the toolbar below it, with a grey strip between
them doing nothing.

docs/banner.gif is a drawing assembling itself out of white touch points on a
dark ground, and the circles are not decoration: each one is a point sent to the
device, in send order, from a real run over examples/cat.jpg. The demo, gallery
and pipeline pictures move to the same dark page with white ink; the phone screen
stays white with black ink, because that is what a phone shows.

Release

The Windows job publishes the WinUI front end alongside the installer and zips
it — MThreadDraw-WinUI-1.1.0-x64.zip, 159 MB. The job pins .NET 8 rather than
trusting the runner, and its timeout goes from 30 minutes to 50.

For a reviewer

  • 166 tests pass.
  • The live view, the auto-connect and the overlay were all verified against a
    real Pixel 8 Pro; the published self-contained build was verified separately.
  • No application screenshot is included. The obvious one showed the phone's
    actual screen — an open chat — and that does not belong in a repository.
  • Still open: the WinUI build has no drag-to-place or gesture recording, and the
    self-contained publish is 407 MB, about half of it OpenCV for five functions.

🤖 Generated with Claude Code

MAXAWER and others added 3 commits August 26, 2026 21:33
The Windows job now publishes it alongside the installer and zips the result,
because a release asset has to be one file and a folder of four thousand is a
poor thing to ask anyone to download by hand. It lands as
MThreadDraw-WinUI-1.1.0-x64.zip, 159 MB, and the release notes table says what
it is and how it differs from the MSI.

The job also pins .NET 8 rather than trusting whatever the runner ships, and
its timeout goes from thirty minutes to fifty: it was sized for two PyInstaller
builds and a WiX build, and now carries a self-contained WinUI publish and a
400 MB zip as well.

Verified by running the whole thing locally under the new names. The packaged
self-test passes, the compiled XAML lands in the publish directory - that copy
target keys off AssemblyName, which the rename changed - and the published
application starts, shows "MThread Draw" and gets an answer from its bundled
engine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…revived

**The device that was not there.** adb had a device the whole time; its daemon
was wedged, as it is after being started before the USB driver settles or when
another copy of adb claimed port 5037 first. `adb devices` then reports nothing
for a phone that is plugged in and working, and asking again never helps -
`kill-server` does. `find_devices` now does that once, and only when the list
came back empty, since killing the daemon disconnects anything else using it.
The window says so too, instead of implying the cable is at fault.

**The icon is a white circle.** On a near-black square rather than on nothing: a
white disc on transparency is invisible against a light taskbar, a light Start
Menu and half of GitHub, which is not a mark but a hole.

**The title bar is ours.** The window was carrying two copies of its own name -
one in the system caption, one in the toolbar below it - with a grey strip in
between doing nothing. Content now extends into the caption, the name is said
once, and the same circle mark sits beside it.

**The repository takes the same identity.** docs/banner.gif is a drawing
assembling itself out of white touch points on a dark ground, and the circles
are not decoration: each one is a point sent to the device, in send order, from
a real run over examples/cat.jpg. The demo, gallery and pipeline pictures move
to the same dark page with white ink; the phone screen stays white with black
ink, because that is what a phone shows. The phone body needed a colour of its
own once the page went dark - it had been drawn in the text colour, which on a
dark page made a pale body around a pale screen.

Verified by running the published build: it finds 37311FDJG00337 by itself at
startup, and the new caption renders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The window had no idea what a phone looked like. It showed a stroke preview on
a grey panel, which answers "what will be drawn" and not "where", and the
second question is the one people actually have.

**The live view.** mthread.mirror.ScreenMirror runs com.mthread.Mirror out of
the same jar as the injector: the phone captures, scales and JPEG-compresses,
and the cable carries 27 kB. Measured on a Pixel 8 Pro at 1344x2992, per frame:

    adb exec-out screencap -p    2.1 s   sixteen megabytes, PNG-encoded on the phone
    adb exec-out screencap       2.9 s   no encoding, but all of it over a 5 MB/s link
    this                         0.29 s  scaled and compressed before it leaves

That is 3.4 frames a second, and the ceiling is screencap itself, which costs
about 300 ms whatever you do with the result. It is not scrcpy and does not try
to be: scrcpy is fast because it feeds the display to the hardware H.264
encoder, which needs a decoder at the other end.

Two false starts are recorded in the code because both cost real time. Plain
"adb shell" allocates a pty, a pty turns every line feed into a carriage return
and a line feed, and a JPEG is full of line feeds - so the first frame appears
to work and every frame after it is out of step by however many newlines the
one before it contained. "adb exec-out" has no pty and is the usual answer, but
it is output only: stdin never arrives and it hangs after READY. The frame is
now base64 on a single line, which no line-ending translation can touch, and
that is worth a third more bytes on a 27 kB frame.

**The phone's shape.** The frame is sized from the device's own resolution and
scaled by a Viewbox, so the proportions are the phone's rather than a guess.

**The placement overlay.** preview now also returns the strokes positioned by
the same fit_to_screen call the drawing itself makes, on a transparent ground,
drawn over the live view in red.

**Two sliders, not five.** Edge sensitivity and detail were always turned
together and nobody could say what the difference was. Speed and hand
simulation were one axis pretending to be two: nobody wants a hand that draws
instantly or a machine that trembles. The margin is fixed at six per cent, it
being the least interesting control in the window and in the way of the two
that matter.

The front end also connects by itself when there is exactly one ready device,
and opens an image named on its command line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@MAXAWER
MAXAWER merged commit bf16971 into main Aug 26, 2026
2 of 13 checks passed
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.

1 participant