Commit Graph

13 Commits

Author SHA1 Message Date
Trey T
c1bed4f53b FramebufferUIView: disable CALayer implicit animations on frame updates
Every time Mac repainted a region we set imageLayer.contents to a new
CGImage. CALayer's default action for .contents is a 0.25s crossfade, so
big repaints (like after a click — cursor + button + window-focus) looked
like a pulse. Tap seemed "flickery"; actually the whole view was doing
quarter-second crossfades constantly, most just weren't big enough to
notice until a chunky repaint hit.

Override imageLayer.actions with NSNull for contents/contentsRect/frame/
transform so blits are instantaneous, and wrap apply() in a
CATransaction.setDisableActions(true) for safety.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:21:33 -05:00
Trey T
4ff3e4b030 Session: add a persistent chrome-toggle handle at top center
Three-finger tap still works as a power-user shortcut, but now there's a
small glass chevron pill at the top center that flips between chevron.up
(hide toolbar) and chevron.down (show toolbar) based on chrome state.
Discoverable and reachable from one hand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:14:51 -05:00
Trey T
8177be94a5 FramebufferUIView: give it a full UITextInputTraits implementation
Class-level UIKeyInput conformance without UITextInputTraits means iOS
falls back to default traits — autocorrect on, predictive on, smart
quotes/dashes on. The suggestion engine was swallowing most keystrokes
before insertText() could forward them (the "1 in 6 chars" symptom).

Declaring all the traits as @objc stored properties with permissive
(autocorrect=.no, etc.) values turns every suggestion layer off so each
key tap produces exactly one insertText() and hits the remote.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:05:13 -05:00
Trey T
a21946ba2c SessionView: surface a "View only" badge in the chrome
When a connection is configured with viewOnly=true, the controller silently
drops every key, pointer, and scroll event. Without a visible indicator the
behavior looks like the keyboard is broken. Yellow capsule next to the
connection label makes it obvious.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:17:10 -05:00
Trey T
0e25dbeba4 Fix the keyboard typing pipeline + add a test that catches the regression
The earlier UIKeyInput conformance was declared in a separate extension. ObjC
protocol conformance via Swift extension is fragile when the protocol
inherits another @objc protocol (UIKeyInput inherits UITextInputTraits) — the
runtime didn't always pick up insertText:, so the on-screen keyboard came
up but characters never reached controller.type(_:).

Fix: declare UIKeyInput conformance directly on FramebufferUIView's class
declaration, with insertText / deleteBackward / hasText as native members.

Also caught and fixed by the new UI test:
- The toolbar's keyboard-icon button had a 20×13 hit region (SF Symbol size)
  even though the visual frame was 34×34 — XCUI taps couldn't land on it
  reliably. .contentShape(Rectangle()) widens the hit area to the frame.
- accessibilityValue is reserved by iOS for UIKeyInput-classed views (treats
  them as TextView), so a separate hidden "fb-diag" accessibility probe
  records keyboard plumbing events for the test to verify.

Tests:
- KeyboardInputTests (5): pure mapping from String → X11 keysym down/up pairs
- ScreensUITests.testSoftwareKeyboardSendsCharactersToFramebuffer:
  opens a session, taps the keyboard toggle, types "hi" via the system
  keyboard, and asserts the framebuffer's diagnostic probe records
  [ins:h] and [ins:i] — proving the chars reach controller.type(_:)
- A SwiftUI state probe (sessionview-state) verifies the binding flips,
  which guards against future tap-routing regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:04:03 -05:00
Trey T
da882531d1 Make the keyboard button actually present a keyboard
The toolbar's keyboard icon used to toggle a custom function-key bar — it
never opened the iOS system keyboard, so users couldn't type into the remote.

Fix: FramebufferUIView now conforms to UIKeyInput + UITextInputTraits, so
becoming first responder presents the iOS keyboard. Tapping the keyboard
button toggles software-keyboard visibility on the framebuffer view via a
SwiftUI binding. While the keyboard is up, an inputAccessoryView toolbar
(esc / tab / ctrl / ⌘ / ⌥ / ←↓↑→ / dismiss) sits directly above it, with
each button forwarded to the existing controller.send… APIs.

The standalone SoftKeyboardBar overlay is no longer used.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 22:34:28 -05:00
Trey T
689e30d59a Support Apple Remote Desktop auth via account username+password
RoyalVNCKit prioritizes .diffieHellman (ARD) over .vnc during handshake when
both are offered. My delegate adapter was passing an empty username to
VNCUsernamePasswordCredential, so any Mac with user-account screen sharing
enabled rejected the credential before .vnc fallback could happen.

Fix: persist a username on SavedConnection and pipe it through to the
credential callback. Leave blank to use the VNC-only password path.

AddConnection footer now explains the two Mac paths:
  • User account (ARD) — macOS short name + full account password
  • VNC-only password — blank username + ≤8 char password

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 22:25:27 -05:00
Trey T
6b50184bcc Kill the NavigationStack on the list — that was the floating-chrome bug
iOS 26's NavigationStack reserves space for its floating Liquid Glass nav bar
even with `.toolbar(.hidden, for: .navigationBar)` — that's why "Screens"
landed ~180pt down from the dynamic island with a wedge of black above it.

Removed the NavigationStack from the list entirely. Layout is now a plain
top-anchored VStack: top chrome (gear + Screens + plus + search) flush with
the safe-area top, then a ScrollView with LazyVStack of cards filling the
rest of the screen. Sessions present via fullScreenCover instead of
NavigationLink, so we don't need NavigationStack here at all.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 21:33:50 -05:00
Trey T
fcdd19ceb9 List screen: ditch nav bar, build flush top chrome
The system nav bar floated low on iOS 26 and left a wedge of black above the
title. Replaced it with a custom top row pinned to the safe-area top: gear ⟶
big "Screens" wordmark ⟶ +. Search bar lives directly beneath, sections start
right after — no centered-in-the-void layout. Background gets a subtle blue
radial bloom so the floating glass buttons have something to anchor to.

Saved-empty state is now a glass card with an icon and a gradient CTA button.
Connection rows are full-width glass cards with rounded corners; long-press
gives Edit / Open in New Window / Delete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 21:19:41 -05:00
Trey T
333c08724f Redesign UI for iOS 26 Liquid Glass
- Fix port-formatting bug: Int interpolation was adding a locale grouping
  separator ("5,900"); now renders "5900" via portLabel helper.
- LiquidGlass helpers: glassSurface/interactiveGlassSurface/glassButton wrap
  iOS 26's .glassEffect / .buttonStyle(.glass) / scrollEdgeEffectStyle with
  iOS 18 fallbacks (ultraThinMaterial + stroke) gated by #available.
- List: searchable, labeled Bonjour section with "looking for computers"
  state, empty-state CTA, hover-ready rounded discovery buttons, subtle
  dark gradient background, connection cards with color swatch +
  monospaced host:port and chevron.
- Session: floating glass back-pill + connection-status pill + toolbar
  capsule; three-finger tap toggles chrome; disconnect dialog upgraded to
  a 28pt glass card with role-based glyphs/tints.
- Soft keyboard bar redesigned as a rounded glass panel with pill keys.
- Add/Edit form: horizontal color-tag picker, show/hide password eye,
  helpful footers (Tailscale hint, 8-char VNC-password reminder,
  View-only explainer).
- Settings: app-icon-style hero, grouped sections with footers, links to
  privacy policy and RoyalVNCKit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 21:04:17 -05:00
Trey T
8e01068ad3 Add edit-connection flow
Edit reuses AddConnectionView with an `editing:` parameter that prefills the
form and updates in-place; password field becomes optional ("leave blank to
keep current"). Surfaced via context menu and a leading-edge swipe action on
each saved row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 20:21:14 -05:00
Trey T
1c01b3573f Phases 1-4: full VNC client implementation
- SessionController wraps RoyalVNCKit.VNCConnection via nonisolated delegate
  adapter that bridges callbacks to @MainActor; Keychain-resolved passwords;
  reconnect with jittered exponential backoff; NWPathMonitor adaptive-quality
  hook; framebuffer rendered to CALayer.contents from didUpdateFramebuffer.
- Touch + trackpad input modes with floating soft cursor overlay; hardware
  keyboard via pressesBegan/Ended → X11 keysyms; UIPointerInteraction with
  hidden cursor for indirect pointers; pinch-to-zoom; Apple Pencil as direct
  touch; two-finger pan / indirect scroll wheel events.
- Bidirectional clipboard sync (per-connection opt-in); multi-monitor screen
  picker with input remapping; screenshot capture → share sheet; on-disconnect
  reconnect/close prompt; view-only and curtain-mode persisted.
- iPad multi-window via WindowGroup(for: UUID.self) + context-menu open;
  CloudKit-backed ModelContainer with local fallback; PrivacyInfo.xcprivacy.

10 VNCCore tests + 4 VNCUI tests pass; iPhone and iPad simulator builds clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 20:07:54 -05:00
Claude
2cff17fa0d Phase 0: scaffold
Two SPM packages (VNCCore, VNCUI) + thin iOS app target wired via
xcodegen. Builds for iPhone 17 simulator, unit tests pass.

- VNCCore: SessionState, SessionController stub, Transport protocol
  with DirectTransport (NWConnection), DiscoveryService (Bonjour on
  _rfb._tcp and _workstation._tcp), SavedConnection @Model,
  ConnectionStore, KeychainService, ClipboardBridge
- VNCUI: ConnectionListView, AddConnectionView, SessionView,
  FramebufferView/FramebufferUIView (UIKit CALayer), InputMapper,
  SettingsView; UIKit bits guarded with #if canImport(UIKit) so
  swift test runs on macOS
- App: @main VNCApp, AppStateController state machine, RootView
- RoyalVNCKit dependency pinned to main (transitive CryptoSwift
  constraint blocks tagged releases)
- xcodegen Project.yml + README + .gitignore

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:29:47 -05:00