v0.26.0
Linux DRM/KMS backend — feature = "linux-drm" runs mirui directly against /dev/dri/cardN with a connector + CRTC + N dumb buffers, page-flipping via legacy drmModePageFlip on every frame_end. Companion N-buffer dirty mirror in core: Surface exposes multiple framebuffer slots and mirui copies each frame's dirty rects + scroll shifts into the inactive slots so the next rotation finds them ready. A handful of fixes ride along — hit_test no longer routes taps through scrolled-off rows, the cursor leaves no smear on a scrolled overlay, and SimAction::drag emits at least one PointerMove on low-tick-rate hosts.
Added
feature = "linux-drm"— opt-in DRM/KMS backend ondrm0.14,drm-ffi0.10,evdev0.13,libc0.2, andsignal-hook0.3. Mutually exclusive withlinux-fbat compile time. Drives the full legacy modeset (acquire_master_lock→ connector + mode + CRTC selection → dumb buffer × N →add_framebuffer→set_crtc), mmaps each dumb buffer for the surface's lifetime, shares the evdev pump and DPI scale heuristic withlinux-fb, and rejectssimpledrm/simple-framebuffer/efifbupfront so callers fall through to the fbdev path.- Page-flip + vblank wait —
advance()queues a non-blockingdrmModePageFlipwithPageFlipFlags::EVENT;frame_end()polls the drm fd to drain the event before the next paint. An open-time probe gates the path so old simpledrm-style drivers fall back toset_crtc+wait_vblank. - Paravirtual scale + dirty notification —
virtio_gpu/vmwgfx/qxlreport bogus mm panel sizes;LinuxDrmConfig::scale = AutoDpi { .. }is downgraded toFixed(1.0)for those drivers (callerFixed(_)honoured regardless).flush()issuesMODE_DIRTYFBso paravirtual cards sync the dumb buffer to the host (EOPNOTSUPPon real hardware is a non-error). LinuxDrmConfig—card_path,connector_filter(matchesxrandr/kmsprint-style names likeHDMI-A-1),mode(Some((w, h))matches by exact dimensions),input_path,overscan_inset_percent,scale,buffer_count(default 2).
- Page-flip + vblank wait —
- N-buffer dirty mirror core —
Surface::buffer_count()(default 1, skips the mirror loop).FramebufferAccess::all_buffers()returns every slot with idx 0 = active;FramebufferAccess::advance()rotates to the next slot.RendererFactory::mirror_and_advance(backend, plan, transform)is default no-op;SwRendererFactoryoverrides it to copy each frame's dirty rects + scroll shifts into every inactive slot before rotating. The shared mirror primitives live insrc/surface/mirror.rs. Surface::frame_end()— per-tick hook afterrender/render_dirty, called even on empty frames so vblank-bound backends can wait. Default empty.ColorFormat::BGRA8888— byte 0 = B, byte 3 = A.LinuxFbSurface::format_from_varpicks it whenvar.red.offset > var.blue.offset(the BGRX layout signal);LinuxDrmSurfacepins it forDRM_FORMAT_XRGB8888. Cross-backend conversions wired throughsdl_gpu(PixelFormatEnum::BGRA32) and the wgpu / web-canvas byte swap. Fast paths insrc/draw/sw/{rect_fill, blit_fast, blend}/aren't mirrored yet, so BGRA frames go through the slow path; RGB565 panels are unaffected.gallery::demos::widgetsviewport scaling —DemoSize::for_viewport(view_w, view_h)readsbackend.display_info()so the demo fills any aspect ratio.scale = min(w, h) / 128keys row + tabbar heights to the shorter axis.gallery/examples/linux_drm_demo.rs— runs the sharedhelloscene on the DRM backend.- Env var overrides —
MIRUI_OVERSCAN_INSET=<n>,MIRUI_DRM_CARD=/dev/dri/cardN,MIRUI_DRM_CONNECTOR=HDMI-A-1,MIRUI_DRM_BUFFERS=<n>,MIRUI_DRM_MODE=WxH,MIRUI_SIM_OFF=1(skip widgets demo's auto-cycle).
Fixed
hit_testno longer routes taps to scrolled-off rows. A row scrolled past the top of its container had its post-shift rect drift outside the container's layout rect, and last-hit-wins traversal then let it steal taps from siblings outside the scroll — most visible as aTabBarabove aLazyListwhere the list's first screen kept eating taps that should have hit the tab.hit_testnow walks the parent chain and rejects any candidate whose probe falls outside an ancestor scroll container's visible viewport.- Cursor leaves no smear on a scrolled overlay. When the cursor moved between frames and sat above a scrolling container, the in-place self-blit on the scroll area carried the previous-frame cursor pixels by
(dx, dy), leaving a strip the existing overlay-rect repaint didn't cover. The dirty walker now records each out-of-scrollPrevRectand a post-walk pass foldsprev ∩ shift.areatranslated by the shift back into the dirty bounds. SimAction::dragemits at least onePointerMoveon low-tick-rate hosts. A 100ms drag on a 16-30ms tick wentaction_started=false → true → elapsed ≥ durationin two iterations and skipped the move branch entirely; the gesture recogniser then classified the result as aTap.SimTimeline::drag_move_emittednow gates the finalPointerUp: if no move ever fired, the catch-up tick clampstto 1.0 and emits the move first.- N=2 buffer-role rotation in
LinuxDrmSurface::advance.advance()was page-flipping the slot mirui was about to paint instead of the one it just finished painting; the bug was masked on virtio_gpu by paravirtual scanout latency but surfaced as per-frame tearing on real hardware. The rotation now readsfront_idxbefore incrementing. LinuxDrmSurface::openpage_flip probe could hang. The probe called blockingreceive_events()without gating on POLLIN, so a driver that returnedOkfrompage_flipbut never delivered the event parkedopen()forever. Probe now gates on POLLIN and treats poll-timeout aspage_flip_supported = false.
Changed
linux-fbandlinux-drmare mutually exclusive at compile time. Both backends drive the same panel — fbdev via mmap on/dev/fb0, DRM via master on/dev/dri/card0.compile_error!fires at lib compile time on Linux when both features are set.
Internal
mirui::surface::linux::inputandmirui::surface::linux::scaleextracted fromsurface.rs(943 → 227 lines). Both gate onany(linux-fb, linux-drm), so the DRM backend reuses the evdev pump and DPI heuristic verbatim.mirror::blit_regionformat / stride / dimensions checks promoted fromdebug_assert_eq!toassert_eq!so any backend with mismatched per-slot dimensions panics loudly instead of silently corrupting buffers.SwRendererFactory::mirror_and_advanceruns shift-on-inactives before blit-from-active, matchingrender_dirty's active-target order; the reverse smears the just-blitted dirty pixels.LinuxDrmSurface::flushis a no-op whenbuffer_count > 1;set_crtcinadvanceis what re-points scanout, sodirty_framebufferon the new active wakes the host on a slot it isn't displaying.App::needs_full_first_frame— first persistent-path tick on a multi-buffer backend renders the full screen so every inactive slot starts from a known-complete image; cleared afterward.drm-ffi0.10 added to thelinux-drmfeature forwait_vblank.