cmux-tui: render inline Kitty images through libghostty (#8811)

* test: cover kitty placement frame reuse

* fix: cache kitty placement frames

* test: cover pixel-accurate kitty clipping

* fix: clip kitty placements in pixel space

* test: cover pixel-accurate kitty replay clipping

* fix: clip kitty replay in pixel space

* test: cover number-only kitty image attach

* test: cover both numbered kitty image aliases

* fix: preserve kitty number aliases across attach

* test: cover inflight kitty replay across resize

* fix: preserve inflight kitty replay

* test: cover anonymous kitty replay collisions

* fix: preserve anonymous kitty placements in replay

* test: cover kitty object count limits

* fix: bound kitty graphics object counts

* test: cover Kitty graphics in web render mode

* test: cover host kitty scene invalidation

* fix: restore kitty graphics after host resize

* feat: render Kitty graphics in web terminal

* test: cover graphics writer shutdown quiescence

* fix: layer Kitty graphics above cell backgrounds

* fix: draw web graphics from callback ref

* fix: quiesce graphics before terminal restore

* test: cover kitty replay allocation order

* fix: preserve kitty replay allocation order

* test: cover incremental render graphics deltas

* test(tui): cover linear graphics state maintenance

* fix: send incremental render graphics deltas

* test: cover bounded kitty replay semantics

* test(tui): cover late Kitty image ordering

* test: cover render transport size boundaries

* fix(tui): maintain Kitty graphic IDs linearly

* test: cover atomic cell geometry updates

* test(tui): cover Kitty PNG compatibility

* test: cover full render metadata budget

* test: preserve measured cell pixels across resize

* test: bound kitty pixel cache lookups

* fix: make cell geometry updates atomic

* test: bound kitty placement grouping

* fix: align render transport size budgets

* test: bound render taps and resize replay

* fix: preserve kitty images in bounded vt replay

* fix: bound render taps and skip unused replay

* docs(tui): document inline Kitty image support

* style(tui): format merged changes

* test: cover UTF-8 before kitty replay

* test: cover large kitty resize replay

* fix: distinguish UTF-8 from C1 kitty APC

* fix: preserve kitty upload across resize

* test(tui-sdk): cover retained render metadata overflow

* fix(tui-sdk): bound retained render events

* test(tui-web): expose placement canvas memory blowup

* fix(tui-web): bound graphic canvas backing

* test: preserve hosted Kitty image aliases

* fix: preserve hosted Kitty image aliases

* test(tui): cover stale Kitty write after resize clear

* fix(tui): discard stale Kitty writes after resize clear

* test(browser): expose terminal host alias protocol gap

* fix(browser): support terminal host Kitty aliases

* test(cmux-tui): expect resize alias sidecars

* test(tui): preserve sparse viewport across replay

* fix(tui): preserve sparse rows in terminal replay

* fix(tui): align replayed scrollback rows

* test(tui): await terminal host process exit

* test(tui): cover Kitty alias history and sparse replay

* fix(tui): preserve Kitty alias and sparse row history

* fix(tui): address Kitty graphics review findings

* fix(tui): harden Kitty graphics integration

* fix(tui): resolve final Kitty autoreview findings

* fix(tui): close Kitty autoreview findings

* test(tui): cover final Kitty review regressions

* fix(tui): close final Kitty autoreview findings

* test(tui): reject overflowing PTY pixel geometry

* fix(tui): reject invalid PTY pixel geometry

* test(tui): cover remaining Kitty review regressions

* fix(tui): close remaining Kitty review findings

* fix(cmux-tui): close graphics review gaps

* fix(cmux-tui): preserve attach and startup progress

* fix(tui): reconcile image render geometry

* fix(tui): align attach wire progress

* fix(tui): bound inline image rendering resources

* fix(tui): close inline image review gaps

* fix(tui): bound inline graphics hot paths

* test(tui): cover graphics attachment memory regressions

* fix(tui): bound graphics attachment allocations

* test(tui): budget retained render capacity

* test(tui): cover graphics review regressions

* fix(tui): close graphics autoreview gaps

* test(tui): cover second graphics review regressions

* fix(tui): close remaining graphics review gaps

* test(tui): cover remaining graphics review regressions

* fix(tui): close graphics review findings

* test(tui): cover final graphics review regressions

* fix(tui): close final graphics review findings

* test(tui): cover graphics admission regressions

* fix(tui): make graphics admission lazy and refillable

* test(tui): cover final host lifecycle findings

* fix(tui): bound host lifecycle work

* test(tui): cover final protocol review findings

* fix(tui): close final protocol review gaps

* test(tui): cover bounded graphics writer failure

* fix(tui): bound graphics output failure lifecycle

* test(tui): cover final remote graphics review gaps

* fix(tui): validate and localize remote attach data

* test(tui): cover final graphics ownership findings

* fix(tui): scope graphics output ownership

* test(tui): cover graphics resource safety gaps

* fix(tui): bound graphics resource lifecycles

* test(tui): cover graphics budget scan fanout

* fix(tui): make graphics admission single-pass

* test(tui): cover review resource safety gaps

* fix(tui): bound graphics attachment resources

* test(tui): cover enhanced input adapter compatibility

* fix(tui): reconcile shortcut merge with graphics input

* test(tui): reconcile merged attach fixtures

* test(tui): bound inline surface state

* fix(tui): keep libghostty state out of line

* test(tui): cover cell pixel fanout retry gap

* fix(tui): reconcile skipped cell pixel fanout

* test(tui): cover aggregate graphics ownership gaps

* fix(tui): bound aggregate graphics ownership

* test(tui): cover graphics teardown ownership

* fix(tui): rebalance graphics ownership on teardown

* test(tui): cover aggregate graphics recovery

* fix(tui): recover aggregate graphics capacity

* test(tui): isolate graphics counters per thread

* test(tui): cover graphics resource ownership gaps

* fix(tui): close graphics resource ownership gaps

* test(tui): cover Kitty replay state divergence

* fix(tui): preserve Kitty replay state across mirrors

* test(tui): cover terminal resource lifecycle stalls

* fix(tui): decouple terminal resource lifecycle work

* test(tui): cover review lifecycle regressions

* fix(tui): close review lifecycle gaps

* test(tui): cover graphics review regressions

* fix(tui): reconcile graphics lifecycle under load

* test(tui): cover graphics baseline and quota exhaustion

* fix(tui): reconcile graphics baselines and quota overflow

* test(tui): make graphics backpressure deterministic

* test(tui): cover exited quota and reset replay ordering

* fix(tui): preserve graphics state across resets and exits

* test(tui): cover scrolled Kitty placement alignment

* fix(tui): align Kitty graphics with scrolled viewports

* test(tui): bound stalled renderer output

* fix(tui): preserve renderer output backpressure

* test(tui): cover relabel and retry bounds

* fix(tui): bound graphics recovery work

* fix(ci): isolate fork-agent singleton default

* test(tui): bound persistent graphics recovery

* fix(tui): bound persistent graphics recovery

* test(tui): drain stalled quota worker

* test(tui): cover panic and fanout lifecycles

* fix(tui): bound graphics worker lifecycles

* test(web): cover exhausted graphics decode queue

* fix(web): retire exhausted graphics decode jobs

* test(tui): cover final Kitty review findings

* fix(tui): close final Kitty replay gaps

* test(tui): cover encoded Kitty quota

* fix(tui): budget encoded Kitty uploads

* test(browser): sync Kitty replay ceilings

* fix(browser): match Kitty replay ceilings

* test(tui): cover unsupported Kitty grayscale

* fix(tui): bound Kitty snapshot formats

* test(tui): cover Kitty quota recovery

* fix(tui): reconcile Kitty quota recovery

* test(tui): cover reconnect completion retry

* fix(tui): retry failed host reconnect completion

* test(tui): cover attach priority and replay cursor state

* fix(tui): preserve attach priority and replay state

* test(tui): cover superseded attach resize failure

* fix(tui): settle the latest promoted resize

* chore(tui): satisfy strict attach lifecycle lint

* test(tui): cover numeric Kitty final chunks

* fix(tui): parse Kitty chunk flags numerically

* test(tui): cover Kitty images in web scrollback

* fix(tui): render Kitty images in web scrollback

* test(tui): admit terminals after graphics quota failure

* fix(tui): degrade graphics after quota failure

* test(tui): finish graphics probe at DA1 marker

* fix(tui): end graphics probe at DA1 marker

* test(tui): reject stale scrollback image epochs

* fix(tui): version scrollback image anchors

* test(tui): refresh active scrollback epochs

* fix(tui): refresh active scrollback epochs

* test(tui): ignore screen-only history epochs

* fix(tui): scope history epochs to retained rows

* test(tui): keep image frames out of history epochs

* fix(tui): keep image frames out of history epochs

* test(tui): assert stable screen-only history epochs

* test(tui): bound deferred work and reserve attaches

* fix(tui): bound deferred graphics coordination

* test(tui): exhaust saturated Kitty quota retries

* fix(tui): exhaust saturated Kitty quota retries

* test(tui): retain overlapping Kitty replay placements

* fix(tui): clip Kitty placements at replay boundaries

* fix(tui): preserve merged attach invariants

* test(ci): cover Ghostty path metadata

* fix(ci): inspect executable Ghostty consumers

* test(ios): replace wall-clock synchronization

* chore(xcode): normalize project ordering

* fix(ssh): simplify retry script assembly

* test(app): update detached transfer fixture

* test: update remote PTY lifecycle fake

* test: require explicit app-host test mode

* fix: declare app-host test launch mode

* fix: clear Xcode 26.3 warning gate

* test: detect embedded app-host test bundle

* fix: detect app-host tests from embedded bundle

* chore: drop unreliable app-host scheme marker

* test: avoid async ARC lifetime assertion

* test: require app-host build identity

* fix: stamp app-host test builds before launch

* test: require test-runner app-host marker

* fix: forward app-host test identity through xcodebuild

* fix: keep completed iroh dial single-flight through install

* test: re-report lifecycle after status clear

* Fix cmux-tui merge integration

* test(web): cover render attach WebSocket budget

* fix(web): admit full render attach frames

---------

Co-authored-by: austinpower1258 <[email protected]>
This commit is contained in:
Lawrence Chen
2026-08-01 07:54:34 -07:00
committed by GitHub
co-authored by austinpower1258
parent b813ab9a25
commit 80f40831da
200 changed files with 36984 additions and 3321 deletions
@@ -100,11 +100,10 @@ import Testing
await completion.finish()
}
#expect(await transport.waitUntilCloseCount(1))
try await Task.sleep(nanoseconds: 10_000_000)
await drain.value
#expect(await completion.isFinished)
await transport.releaseConnects()
await drain.value
#expect(await transport.waitUntilCloseCount(2))
request.cancel()
_ = await request.result
@@ -1,5 +1,6 @@
#if os(iOS)
@testable import CmuxMobileShellUI
import Foundation
import Testing
@MainActor
@@ -68,7 +69,6 @@ struct OnboardingMacDiscoveryKeepAliveTests {
}
#expect(await eventually { !keepAlive.isRunning })
try? await ContinuousClock().sleep(for: .milliseconds(20))
#expect(attemptCount == 1)
}
@@ -104,7 +104,6 @@ struct OnboardingMacDiscoveryKeepAliveTests {
gate.resume(returning: false)
#expect(await eventually { !keepAlive.isRunning })
try? await ContinuousClock().sleep(for: .milliseconds(20))
#expect(!gate.cancellationObserved)
#expect(attemptCount == 1)
}
@@ -183,7 +182,11 @@ struct OnboardingMacDiscoveryKeepAliveTests {
func waitsUntilCoordinatorOwnershipIsReleased() async throws {
let coordinator = MobileStartupConnectionCoordinator()
let externalClaim = try #require(coordinator.claimStoredReconnect())
let keepAlive = makeKeepAlive()
let clock = OnboardingDiscoveryManualClock()
let keepAlive = makeKeepAlive(
clock: clock,
claimRetryDelay: .seconds(1)
)
var attemptCount = 0
keepAlive.update(
@@ -197,10 +200,11 @@ struct OnboardingMacDiscoveryKeepAliveTests {
return true
}
try? await ContinuousClock().sleep(for: .milliseconds(20))
#expect(await eventually { clock.sleeperCount == 1 })
#expect(attemptCount == 0)
coordinator.finishStoredReconnect(externalClaim)
clock.advance(by: .seconds(1))
#expect(await eventually { attemptCount == 1 && !keepAlive.isRunning })
}
@@ -262,14 +266,17 @@ struct OnboardingMacDiscoveryKeepAliveTests {
}
#expect(await eventually { !keepAlive.isRunning })
try? await ContinuousClock().sleep(for: .milliseconds(20))
#expect(attemptCount == 1)
}
private func makeKeepAlive() -> OnboardingMacDiscoveryKeepAlive {
private func makeKeepAlive(
clock: any Clock<Duration> = ContinuousClock(),
claimRetryDelay: Duration = .milliseconds(1)
) -> OnboardingMacDiscoveryKeepAlive {
OnboardingMacDiscoveryKeepAlive(
clock: clock,
retryDelay: .milliseconds(1),
claimRetryDelay: .milliseconds(1)
claimRetryDelay: claimRetryDelay
)
}
@@ -289,6 +296,100 @@ struct OnboardingMacDiscoveryKeepAliveTests {
}
}
private final class OnboardingDiscoveryManualClock: Clock, @unchecked Sendable {
struct Instant: InstantProtocol {
var offset: Duration
func advanced(by duration: Duration) -> Instant {
Instant(offset: offset + duration)
}
func duration(to other: Instant) -> Duration {
other.offset - offset
}
static func < (lhs: Instant, rhs: Instant) -> Bool {
lhs.offset < rhs.offset
}
}
private struct Sleeper {
let id: UUID
let deadline: Instant
let continuation: UnsafeContinuation<Void, any Error>
}
private let lock = NSLock()
private var current = Instant(offset: .zero)
private var sleepers: [Sleeper] = []
private var preCancelledIDs: Set<UUID> = []
var now: Instant {
lock.lock()
defer { lock.unlock() }
return current
}
var minimumResolution: Duration { .zero }
var sleeperCount: Int {
lock.lock()
defer { lock.unlock() }
return sleepers.count
}
func sleep(until deadline: Instant, tolerance _: Duration?) async throws {
let id = UUID()
try await withTaskCancellationHandler {
try await withUnsafeThrowingContinuation {
(continuation: UnsafeContinuation<Void, any Error>) in
lock.lock()
if preCancelledIDs.remove(id) != nil {
lock.unlock()
continuation.resume(throwing: CancellationError())
} else if deadline <= current {
lock.unlock()
continuation.resume()
} else {
sleepers.append(Sleeper(
id: id,
deadline: deadline,
continuation: continuation
))
lock.unlock()
}
}
} onCancel: {
cancelSleeper(id: id)
}
}
func advance(by duration: Duration) {
lock.lock()
current = current.advanced(by: duration)
let due = sleepers
.filter { $0.deadline <= current }
.sorted { $0.deadline < $1.deadline }
sleepers.removeAll { $0.deadline <= current }
lock.unlock()
for sleeper in due {
sleeper.continuation.resume()
}
}
private func cancelSleeper(id: UUID) {
lock.lock()
guard let index = sleepers.firstIndex(where: { $0.id == id }) else {
preCancelledIDs.insert(id)
lock.unlock()
return
}
let sleeper = sleepers.remove(at: index)
lock.unlock()
sleeper.continuation.resume(throwing: CancellationError())
}
}
@MainActor
private final class AttemptParkingGate {
private var continuation: CheckedContinuation<Bool, Never>?
@@ -10,10 +10,8 @@ struct CommandRunnerDescriptorLifecycleTests {
@Test("Capture pipes are close-on-exec and close before success returns")
func capturePipesAreCloseOnExecAndCloseAfterSuccess() async throws {
weak var releasedProcess: Process?
var execution: CommandExecution? = try makeExecution(executable: "/usr/bin/true")
releasedProcess = execution?.process
let descriptors = try snapshotDescriptors(of: #require(execution))
let execution = try makeExecution(executable: "/usr/bin/true")
let descriptors = try snapshotDescriptors(of: execution)
#expect(descriptors.count == 8)
for descriptor in descriptors {
let flags = fcntl(descriptor.fileDescriptor, F_GETFD)
@@ -24,15 +22,10 @@ struct CommandRunnerDescriptorLifecycleTests {
)
}
let result: CommandResult
do {
let activeExecution = try #require(execution)
result = await activeExecution.run(timeout: 5)
}
let result = await execution.run(timeout: 5)
#expect(result.exitStatus == 0)
expectDescriptorsClosed(descriptors)
execution = nil
#expect(releasedProcess == nil)
#expect(execution.process.terminationHandler == nil)
}
@Test("Launch failure closes capture pipes")
+5
View File
@@ -1,3 +1,5 @@
import Foundation
struct MacSentryStartupPolicy: Sendable {
let telemetryEnabled: Bool
let isRunningUnderXCTest: Bool
@@ -29,6 +31,9 @@ struct MacSentryStartupPolicy: Sendable {
}
static func isRunningUnderXCTest(environment: [String: String]) -> Bool {
// xcodebuild strips TEST_RUNNER_ from variables forwarded to the test
// host, so the CI wrapper makes this available before XCTest connects.
if environment["CMUX_TEST_PROCESS"] == "1" { return true }
if environment["XCTestConfigurationFilePath"] != nil { return true }
if environment["XCTestBundlePath"] != nil { return true }
if environment["XCTestSessionIdentifier"] != nil { return true }
+3 -4
View File
@@ -568,9 +568,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
ConnectivityInvalidationSubscriberCoordinator()
private func isRunningUnderXCTest(_ env: [String: String]) -> Bool {
// On some macOS/Xcode setups, the app-under-test process doesn't get
// `XCTestConfigurationFilePath`. Use a broader set of signals so UI tests
// can reliably skip heavyweight startup work and bring up a window.
// The CI wrapper uses xcodebuild's TEST_RUNNER_ forwarding so its marker
// exists before XCTest connects. Standard XCTest keys cover other paths.
MacSentryStartupPolicy.isRunningUnderXCTest(environment: env)
}
@@ -16709,7 +16708,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
private func handleMainTerminalWindowShouldClose() -> Bool {
// XCTest has no UI for the warn-before-quit dialog and would either block
// on runModal or have NSApp.terminate kill the test process.
if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil { return true }
if isRunningUnderXCTest(ProcessInfo.processInfo.environment) { return true }
guard !isTerminatingApp, mainWindowContexts.count <= 1 else { return true }
_ = handleQuitShortcutWarning()
return false
+3
View File
@@ -125,6 +125,9 @@ enum SessionRestorePolicy {
static func isRunningUnderAutomatedTests(
environment: [String: String] = ProcessInfo.processInfo.environment
) -> Bool {
if environment["CMUX_TEST_PROCESS"] == "1" {
return true
}
if environment["CMUX_UI_TEST_MODE"] == "1" {
return true
}
+1
View File
@@ -5230,6 +5230,7 @@ enum AppIconSettings {
private static var liveEnvironmentProvider: () -> Environment = { .live() }
private static func isRunningUnderXCTest(_ env: [String: String] = ProcessInfo.processInfo.environment) -> Bool {
if env["CMUX_TEST_PROCESS"] == "1" { return true }
if env["XCTestConfigurationFilePath"] != nil { return true }
if env["XCTestBundlePath"] != nil { return true }
if env["XCTestSessionIdentifier"] != nil { return true }
@@ -6,6 +6,7 @@
#include <algorithm>
#include <iterator>
#include <limits>
#include <unordered_set>
#include <utility>
namespace cmux {
@@ -13,6 +14,10 @@ namespace {
constexpr size_t kClientHelloLength = 60;
constexpr size_t kHostHelloLength = 40;
constexpr uint64_t kMaxKittyImageBytes = 10'000'000;
constexpr uint64_t kMaxKittyInflightBytes = 13'595'480;
constexpr uint64_t kMaxKittyImages = 4'096;
constexpr uint64_t kMaxKittyPlacements = 16'384;
void AppendU16(std::vector<uint8_t>* output, uint16_t value) {
output->push_back(static_cast<uint8_t>(value));
@@ -88,6 +93,12 @@ std::optional<TerminalHostMessageKind> ParseMessageKind(uint16_t value) {
return TerminalHostMessageKind::kCapability;
case 16:
return TerminalHostMessageKind::kResizeAck;
case 17:
return TerminalHostMessageKind::kClearHistoryAck;
case 18:
return TerminalHostMessageKind::kCellPixelSizeAck;
case 19:
return TerminalHostMessageKind::kKittyGraphicsLimitsAck;
case 100:
return TerminalHostMessageKind::kInput;
case 101:
@@ -100,6 +111,14 @@ std::optional<TerminalHostMessageKind> ParseMessageKind(uint16_t value) {
return TerminalHostMessageKind::kTerminate;
case 105:
return TerminalHostMessageKind::kMintCapability;
case 106:
return TerminalHostMessageKind::kSetDefaults;
case 107:
return TerminalHostMessageKind::kClearHistory;
case 108:
return TerminalHostMessageKind::kSetCellPixelSize;
case 109:
return TerminalHostMessageKind::kSetKittyGraphicsLimits;
default:
return std::nullopt;
}
@@ -225,6 +244,15 @@ class PayloadReader {
return true;
}
bool U64(uint64_t* value) {
std::string_view bytes;
if (!value || !Take(8, &bytes)) {
return false;
}
*value = ReadU64(bytes, 0);
return true;
}
bool Bytes(size_t limit, std::string_view* bytes) {
uint32_t length = 0;
return U32(&length) && length <= limit && Take(length, bytes);
@@ -267,6 +295,109 @@ class PayloadReader {
size_t offset_ = 0;
};
TerminalHostProtocolError ValidateKittyImageAliases(
const std::vector<TerminalHostKittyImageAlias>& aliases) {
if (aliases.size() > kTerminalHostMaxKittyImageAliases) {
return TerminalHostProtocolError::kPayloadTooLarge;
}
// Repeated image numbers preserve Kitty's assignment history. Image IDs
// remain unique identities within a snapshot.
std::unordered_set<uint32_t> image_ids;
image_ids.reserve(aliases.size());
for (const TerminalHostKittyImageAlias& alias : aliases) {
if (alias.image_id == 0 || alias.image_number == 0 ||
!image_ids.insert(alias.image_id).second) {
return TerminalHostProtocolError::kMalformedPayload;
}
}
return TerminalHostProtocolError::kNone;
}
void AppendKittyImageAliases(
std::vector<uint8_t>* output,
const std::vector<TerminalHostKittyImageAlias>& aliases) {
AppendU16(output, static_cast<uint16_t>(aliases.size()));
for (const TerminalHostKittyImageAlias& alias : aliases) {
AppendU32(output, alias.image_id);
AppendU32(output, alias.image_number);
}
}
TerminalHostProtocolError ReadKittyImageAliases(
PayloadReader* reader,
std::vector<TerminalHostKittyImageAlias>* aliases) {
uint16_t count = 0;
if (!reader || !aliases || !reader->U16(&count) ||
count > kTerminalHostMaxKittyImageAliases) {
return TerminalHostProtocolError::kMalformedPayload;
}
std::vector<TerminalHostKittyImageAlias> decoded;
decoded.reserve(count);
for (size_t index = 0; index < count; ++index) {
TerminalHostKittyImageAlias alias;
if (!reader->U32(&alias.image_id) ||
!reader->U32(&alias.image_number)) {
return TerminalHostProtocolError::kMalformedPayload;
}
decoded.push_back(alias);
}
if (ValidateKittyImageAliases(decoded) !=
TerminalHostProtocolError::kNone) {
return TerminalHostProtocolError::kMalformedPayload;
}
*aliases = std::move(decoded);
return TerminalHostProtocolError::kNone;
}
TerminalHostProtocolError ValidateKittyReplayState(
const TerminalHostKittyReplayState& state) {
if (state.limits.image_bytes > kMaxKittyImageBytes ||
state.limits.inflight_bytes > kMaxKittyInflightBytes ||
state.limits.images > kMaxKittyImages ||
state.limits.placements > kMaxKittyPlacements ||
state.replay_next_image_ids.primary == 0 ||
state.next_image_ids.primary == 0 ||
state.replay_next_image_ids.alternate == 0 ||
state.next_image_ids.alternate == 0) {
return TerminalHostProtocolError::kMalformedPayload;
}
return TerminalHostProtocolError::kNone;
}
void AppendKittyReplayState(std::vector<uint8_t>* output,
const TerminalHostKittyReplayState& state) {
AppendU64(output, state.limits.image_bytes);
AppendU64(output, state.limits.inflight_bytes);
AppendU64(output, state.limits.images);
AppendU64(output, state.limits.placements);
AppendU32(output, state.replay_cursor_offset);
AppendU32(output, state.replay_next_image_ids.primary);
AppendU32(output, state.next_image_ids.primary);
AppendU32(output, state.replay_next_image_ids.alternate);
AppendU32(output, state.next_image_ids.alternate);
}
TerminalHostProtocolError ReadKittyReplayState(
PayloadReader* reader,
TerminalHostKittyReplayState* state) {
TerminalHostKittyReplayState decoded;
if (!reader || !state ||
!reader->U64(&decoded.limits.image_bytes) ||
!reader->U64(&decoded.limits.inflight_bytes) ||
!reader->U64(&decoded.limits.images) ||
!reader->U64(&decoded.limits.placements) ||
!reader->U32(&decoded.replay_cursor_offset) ||
!reader->U32(&decoded.replay_next_image_ids.primary) ||
!reader->U32(&decoded.next_image_ids.primary) ||
!reader->U32(&decoded.replay_next_image_ids.alternate) ||
!reader->U32(&decoded.next_image_ids.alternate) ||
ValidateKittyReplayState(decoded) != TerminalHostProtocolError::kNone) {
return TerminalHostProtocolError::kMalformedPayload;
}
*state = decoded;
return TerminalHostProtocolError::kNone;
}
bool AppendBytes(std::vector<uint8_t>* output,
std::string_view bytes,
size_t limit) {
@@ -786,6 +917,7 @@ TerminalHostRendererGrantError ValidateTerminalHostRendererGrant(
std::string_view incarnation,
std::string_view token,
uint64_t rights,
uint64_t protocol_version,
uint64_t ttl_ms,
uint64_t expected_ttl_ms,
TerminalHostRendererGrant* grant) {
@@ -811,6 +943,10 @@ TerminalHostRendererGrantError ValidateTerminalHostRendererGrant(
if (rights != kRendererRights) {
return TerminalHostRendererGrantError::kInvalidRights;
}
if (protocol_version < kTerminalHostProtocolVersionV1 ||
protocol_version > kTerminalHostProtocolVersion) {
return TerminalHostRendererGrantError::kInvalidProtocolVersion;
}
if (ttl_ms == 0 || ttl_ms > kTerminalHostMaxRendererCapabilityTtlMs ||
expected_ttl_ms == 0 ||
expected_ttl_ms > kTerminalHostMaxRendererCapabilityTtlMs ||
@@ -824,11 +960,24 @@ TerminalHostRendererGrantError ValidateTerminalHostRendererGrant(
}
decoded.endpoint = std::string(endpoint);
decoded.rights = TerminalHostCapabilityRights::kRenderer;
decoded.protocol_version = static_cast<uint16_t>(protocol_version);
decoded.ttl_ms = static_cast<uint32_t>(ttl_ms);
*grant = std::move(decoded);
return TerminalHostRendererGrantError::kNone;
}
TerminalHostClientHello TerminalHostClientHelloForRendererGrant(
const TerminalHostRendererGrant& grant) {
TerminalHostClientHello hello;
hello.min_version = grant.protocol_version;
hello.max_version = grant.protocol_version;
hello.role = TerminalHostClientRole::kRenderer;
hello.requested_rights = grant.rights;
hello.terminal_id = grant.terminal_id;
hello.token = grant.token;
return hello;
}
const char* TerminalHostRendererGrantErrorMessage(
TerminalHostRendererGrantError error) {
switch (error) {
@@ -844,6 +993,8 @@ const char* TerminalHostRendererGrantErrorMessage(
return "invalid terminal-host one-use token";
case TerminalHostRendererGrantError::kInvalidRights:
return "invalid terminal-host renderer rights";
case TerminalHostRendererGrantError::kInvalidProtocolVersion:
return "invalid terminal-host protocol version";
case TerminalHostRendererGrantError::kInvalidTtl:
return "invalid terminal-host renderer TTL";
}
@@ -854,6 +1005,26 @@ std::string EncodeTerminalHostId(const TerminalHostId& id) {
return EncodeLowerHex(id);
}
bool TerminalHostKittyGraphicsLimits::operator==(
const TerminalHostKittyGraphicsLimits& other) const {
return image_bytes == other.image_bytes &&
inflight_bytes == other.inflight_bytes && images == other.images &&
placements == other.placements;
}
bool TerminalHostKittyImageIdCursors::operator==(
const TerminalHostKittyImageIdCursors& other) const {
return primary == other.primary && alternate == other.alternate;
}
bool TerminalHostKittyReplayState::operator==(
const TerminalHostKittyReplayState& other) const {
return limits == other.limits &&
replay_cursor_offset == other.replay_cursor_offset &&
replay_next_image_ids == other.replay_next_image_ids &&
next_image_ids == other.next_image_ids;
}
TerminalHostSnapshot::TerminalHostSnapshot() = default;
TerminalHostSnapshot::TerminalHostSnapshot(const TerminalHostSnapshot&) =
default;
@@ -865,8 +1036,12 @@ TerminalHostSnapshot& TerminalHostSnapshot::operator=(TerminalHostSnapshot&&) =
TerminalHostSnapshot::~TerminalHostSnapshot() = default;
bool TerminalHostSnapshot::operator==(const TerminalHostSnapshot& other) const {
return cols == other.cols && rows == other.rows && pid == other.pid &&
replay == other.replay && cwd == other.cwd && command == other.command;
return cols == other.cols && rows == other.rows &&
cell_width == other.cell_width && cell_height == other.cell_height &&
pid == other.pid && replay == other.replay && cwd == other.cwd &&
command == other.command &&
kitty_image_aliases == other.kitty_image_aliases &&
kitty_state == other.kitty_state;
}
TerminalHostProtocolError EncodeTerminalHostSnapshot(
@@ -879,6 +1054,19 @@ TerminalHostProtocolError EncodeTerminalHostSnapshot(
snapshot.command.size() > kTerminalHostMaxCommandArguments) {
return TerminalHostProtocolError::kPayloadTooLarge;
}
const TerminalHostProtocolError aliases_error =
ValidateKittyImageAliases(snapshot.kitty_image_aliases);
if (aliases_error != TerminalHostProtocolError::kNone) {
return aliases_error;
}
const TerminalHostProtocolError state_error =
ValidateKittyReplayState(snapshot.kitty_state);
if (state_error != TerminalHostProtocolError::kNone) {
return state_error;
}
if (snapshot.kitty_state.replay_cursor_offset > snapshot.replay.size()) {
return TerminalHostProtocolError::kMalformedPayload;
}
std::vector<uint8_t> encoded;
AppendU16(&encoded, snapshot.cols);
AppendU16(&encoded, snapshot.rows);
@@ -897,6 +1085,13 @@ TerminalHostProtocolError EncodeTerminalHostSnapshot(
return TerminalHostProtocolError::kMalformedPayload;
}
}
AppendKittyImageAliases(&encoded, snapshot.kitty_image_aliases);
AppendU16(&encoded, std::max<uint16_t>(snapshot.cell_width, 1));
AppendU16(&encoded, std::max<uint16_t>(snapshot.cell_height, 1));
AppendKittyReplayState(&encoded, snapshot.kitty_state);
if (encoded.size() > kTerminalHostMaxFramePayload) {
return TerminalHostProtocolError::kPayloadTooLarge;
}
*payload = std::move(encoded);
return TerminalHostProtocolError::kNone;
}
@@ -904,9 +1099,24 @@ TerminalHostProtocolError EncodeTerminalHostSnapshot(
TerminalHostProtocolError DecodeTerminalHostSnapshot(
std::string_view payload,
TerminalHostSnapshot* snapshot) {
return DecodeTerminalHostSnapshotForVersion(
payload, kTerminalHostProtocolVersion, snapshot);
}
TerminalHostProtocolError DecodeTerminalHostSnapshotForVersion(
std::string_view payload,
uint16_t protocol_version,
TerminalHostSnapshot* snapshot) {
if (!snapshot) {
return TerminalHostProtocolError::kInvalidArgument;
}
if (protocol_version < kTerminalHostProtocolVersionV1 ||
protocol_version > kTerminalHostProtocolVersion) {
return TerminalHostProtocolError::kInvalidVersion;
}
if (payload.size() > kTerminalHostMaxFramePayload) {
return TerminalHostProtocolError::kPayloadTooLarge;
}
PayloadReader reader(payload);
TerminalHostSnapshot decoded;
uint32_t pid = 0;
@@ -938,6 +1148,27 @@ TerminalHostProtocolError DecodeTerminalHostSnapshot(
}
decoded.command.push_back(std::move(argument));
}
if (protocol_version >= kTerminalHostProtocolVersionV2 &&
ReadKittyImageAliases(&reader, &decoded.kitty_image_aliases) !=
TerminalHostProtocolError::kNone) {
return TerminalHostProtocolError::kMalformedPayload;
}
if (protocol_version >= kTerminalHostProtocolVersionV2 &&
(!reader.U16(&decoded.cell_width) ||
!reader.U16(&decoded.cell_height))) {
return TerminalHostProtocolError::kMalformedPayload;
}
if (protocol_version >= kTerminalHostProtocolVersion &&
ReadKittyReplayState(&reader, &decoded.kitty_state) !=
TerminalHostProtocolError::kNone) {
return TerminalHostProtocolError::kMalformedPayload;
}
if (protocol_version >= kTerminalHostProtocolVersion &&
decoded.kitty_state.replay_cursor_offset > decoded.replay.size()) {
return TerminalHostProtocolError::kMalformedPayload;
}
decoded.cell_width = std::max<uint16_t>(decoded.cell_width, 1);
decoded.cell_height = std::max<uint16_t>(decoded.cell_height, 1);
if (!reader.finished()) {
return TerminalHostProtocolError::kMalformedPayload;
}
@@ -955,7 +1186,11 @@ TerminalHostResize& TerminalHostResize::operator=(TerminalHostResize&&) =
TerminalHostResize::~TerminalHostResize() = default;
bool TerminalHostResize::operator==(const TerminalHostResize& other) const {
return cols == other.cols && rows == other.rows && replay == other.replay;
return cols == other.cols && rows == other.rows &&
cell_width == other.cell_width && cell_height == other.cell_height &&
replay == other.replay &&
kitty_image_aliases == other.kitty_image_aliases &&
kitty_state == other.kitty_state;
}
TerminalHostProtocolError EncodeTerminalHostResize(
@@ -967,32 +1202,91 @@ TerminalHostProtocolError EncodeTerminalHostResize(
if (resize.replay.size() > kTerminalHostMaxSnapshotReplay) {
return TerminalHostProtocolError::kPayloadTooLarge;
}
const TerminalHostProtocolError aliases_error =
ValidateKittyImageAliases(resize.kitty_image_aliases);
if (aliases_error != TerminalHostProtocolError::kNone) {
return aliases_error;
}
const TerminalHostProtocolError state_error =
ValidateKittyReplayState(resize.kitty_state);
if (state_error != TerminalHostProtocolError::kNone) {
return state_error;
}
if (resize.kitty_state.replay_cursor_offset > resize.replay.size()) {
return TerminalHostProtocolError::kMalformedPayload;
}
std::vector<uint8_t> encoded;
encoded.reserve(8 + resize.replay.size());
encoded.reserve(14 + resize.replay.size() +
resize.kitty_image_aliases.size() * 8);
AppendU16(&encoded, resize.cols);
AppendU16(&encoded, resize.rows);
AppendU32(&encoded, static_cast<uint32_t>(resize.replay.size()));
encoded.insert(encoded.end(), resize.replay.begin(), resize.replay.end());
AppendKittyImageAliases(&encoded, resize.kitty_image_aliases);
AppendU16(&encoded, std::max<uint16_t>(resize.cell_width, 1));
AppendU16(&encoded, std::max<uint16_t>(resize.cell_height, 1));
AppendKittyReplayState(&encoded, resize.kitty_state);
if (encoded.size() > kTerminalHostMaxFramePayload) {
return TerminalHostProtocolError::kPayloadTooLarge;
}
*payload = std::move(encoded);
return TerminalHostProtocolError::kNone;
}
TerminalHostProtocolError DecodeTerminalHostResize(std::string_view payload,
TerminalHostResize* resize) {
return DecodeTerminalHostResizeForVersion(
payload, kTerminalHostProtocolVersion, resize);
}
TerminalHostProtocolError DecodeTerminalHostResizeForVersion(
std::string_view payload,
uint16_t protocol_version,
TerminalHostResize* resize) {
if (!resize) {
return TerminalHostProtocolError::kInvalidArgument;
}
if (protocol_version < kTerminalHostProtocolVersionV1 ||
protocol_version > kTerminalHostProtocolVersion) {
return TerminalHostProtocolError::kInvalidVersion;
}
if (payload.size() > kTerminalHostMaxFramePayload) {
return TerminalHostProtocolError::kPayloadTooLarge;
}
PayloadReader reader(payload);
TerminalHostResize decoded;
std::string_view replay;
if (!reader.U16(&decoded.cols) || !reader.U16(&decoded.rows) ||
!reader.Bytes(kTerminalHostMaxSnapshotReplay, &replay) ||
!reader.finished()) {
!reader.Bytes(kTerminalHostMaxSnapshotReplay, &replay)) {
return TerminalHostProtocolError::kMalformedPayload;
}
decoded.replay.assign(replay.begin(), replay.end());
if (protocol_version >= kTerminalHostProtocolVersionV2 &&
ReadKittyImageAliases(&reader, &decoded.kitty_image_aliases) !=
TerminalHostProtocolError::kNone) {
return TerminalHostProtocolError::kMalformedPayload;
}
if (protocol_version >= kTerminalHostProtocolVersionV2 &&
(!reader.U16(&decoded.cell_width) ||
!reader.U16(&decoded.cell_height))) {
return TerminalHostProtocolError::kMalformedPayload;
}
if (protocol_version >= kTerminalHostProtocolVersion &&
ReadKittyReplayState(&reader, &decoded.kitty_state) !=
TerminalHostProtocolError::kNone) {
return TerminalHostProtocolError::kMalformedPayload;
}
if (protocol_version >= kTerminalHostProtocolVersion &&
decoded.kitty_state.replay_cursor_offset > decoded.replay.size()) {
return TerminalHostProtocolError::kMalformedPayload;
}
if (!reader.finished()) {
return TerminalHostProtocolError::kMalformedPayload;
}
decoded.cols = std::max<uint16_t>(decoded.cols, 1);
decoded.rows = std::max<uint16_t>(decoded.rows, 1);
decoded.replay.assign(replay.begin(), replay.end());
decoded.cell_width = std::max<uint16_t>(decoded.cell_width, 1);
decoded.cell_height = std::max<uint16_t>(decoded.cell_height, 1);
*resize = std::move(decoded);
return TerminalHostProtocolError::kNone;
}
@@ -17,12 +17,15 @@ namespace cmux {
inline constexpr std::array<uint8_t, 4> kTerminalHostMagic = {'C', 'M', 'T',
'H'};
inline constexpr size_t kTerminalHostHeaderLength = 32;
inline constexpr uint16_t kTerminalHostProtocolVersion = 1;
inline constexpr uint16_t kTerminalHostProtocolVersionV1 = 1;
inline constexpr uint16_t kTerminalHostProtocolVersionV2 = 2;
inline constexpr uint16_t kTerminalHostProtocolVersion = 3;
inline constexpr uint32_t kTerminalHostFlagColorsFollow = 1u << 0;
inline constexpr uint32_t kTerminalHostFlagViewerSizeAcks = 1u << 1;
inline constexpr uint32_t kTerminalHostResizeAckCanonicalChanged = 1u << 0;
inline constexpr size_t kTerminalHostMaxFramePayload = 16 * 1024 * 1024;
inline constexpr size_t kTerminalHostMaxSnapshotReplay = 8 * 1024 * 1024;
inline constexpr size_t kTerminalHostMaxSnapshotReplay = 15'692'632;
inline constexpr size_t kTerminalHostMaxKittyImageAliases = 4'096;
inline constexpr size_t kTerminalHostMaxString = 256 * 1024;
inline constexpr size_t kTerminalHostMaxCommandArguments = 256;
inline constexpr uint16_t kTerminalHostColorsVersionV1 = 1;
@@ -48,12 +51,19 @@ enum class TerminalHostMessageKind : uint16_t {
kLaunch = 14,
kCapability = 15,
kResizeAck = 16,
kClearHistoryAck = 17,
kCellPixelSizeAck = 18,
kKittyGraphicsLimitsAck = 19,
kInput = 100,
kPaste = 101,
kViewerSize = 102,
kReleaseViewer = 103,
kTerminate = 104,
kMintCapability = 105,
kSetDefaults = 106,
kClearHistory = 107,
kSetCellPixelSize = 108,
kSetKittyGraphicsLimits = 109,
};
enum class TerminalHostProtocolError {
@@ -240,6 +250,7 @@ enum class TerminalHostRendererGrantError {
kInvalidIncarnation,
kInvalidToken,
kInvalidRights,
kInvalidProtocolVersion,
kInvalidTtl,
};
@@ -249,26 +260,65 @@ struct TerminalHostRendererGrant {
TerminalHostIncarnation incarnation{};
TerminalHostCapabilityToken token{};
TerminalHostCapabilityRights rights = TerminalHostCapabilityRights::kNone;
uint16_t protocol_version = kTerminalHostProtocolVersion;
uint32_t ttl_ms = 0;
};
// Parses the canonical JSON grant fields without depending on a JSON library.
// IDs are lowercase, unhyphenated UUIDv4 hex; the capability is 32 nonzero
// bytes of lowercase hex; rights must be exactly the renderer set; and the
// response TTL must equal the bounded TTL sent in the request.
// bytes of lowercase hex; rights must be exactly the renderer set; the host
// protocol version must be supported; and the response TTL must equal the
// bounded TTL sent in the request.
TerminalHostRendererGrantError ValidateTerminalHostRendererGrant(
std::string_view endpoint,
std::string_view terminal_id,
std::string_view incarnation,
std::string_view token,
uint64_t rights,
uint64_t protocol_version,
uint64_t ttl_ms,
uint64_t expected_ttl_ms,
TerminalHostRendererGrant* grant);
TerminalHostClientHello TerminalHostClientHelloForRendererGrant(
const TerminalHostRendererGrant& grant);
const char* TerminalHostRendererGrantErrorMessage(
TerminalHostRendererGrantError error);
std::string EncodeTerminalHostId(const TerminalHostId& id);
struct TerminalHostKittyImageAlias {
uint32_t image_id = 0;
uint32_t image_number = 0;
bool operator==(const TerminalHostKittyImageAlias& other) const {
return image_id == other.image_id && image_number == other.image_number;
}
};
struct TerminalHostKittyGraphicsLimits {
uint64_t image_bytes = 0;
uint64_t inflight_bytes = 0;
uint64_t images = 0;
uint64_t placements = 0;
bool operator==(const TerminalHostKittyGraphicsLimits& other) const;
};
struct TerminalHostKittyImageIdCursors {
uint32_t primary = 2'147'483'647;
uint32_t alternate = 2'147'483'647;
bool operator==(const TerminalHostKittyImageIdCursors& other) const;
};
struct TerminalHostKittyReplayState {
TerminalHostKittyGraphicsLimits limits;
uint32_t replay_cursor_offset = 0;
TerminalHostKittyImageIdCursors replay_next_image_ids;
TerminalHostKittyImageIdCursors next_image_ids;
bool operator==(const TerminalHostKittyReplayState& other) const;
};
struct TerminalHostSnapshot {
TerminalHostSnapshot();
TerminalHostSnapshot(const TerminalHostSnapshot&);
@@ -279,10 +329,14 @@ struct TerminalHostSnapshot {
uint16_t cols = 80;
uint16_t rows = 24;
uint16_t cell_width = 8;
uint16_t cell_height = 16;
std::optional<uint32_t> pid;
std::vector<uint8_t> replay;
std::optional<std::string> cwd;
std::vector<std::string> command;
std::vector<TerminalHostKittyImageAlias> kitty_image_aliases;
TerminalHostKittyReplayState kitty_state;
bool operator==(const TerminalHostSnapshot& other) const;
};
@@ -293,6 +347,10 @@ TerminalHostProtocolError EncodeTerminalHostSnapshot(
TerminalHostProtocolError DecodeTerminalHostSnapshot(
std::string_view payload,
TerminalHostSnapshot* snapshot);
TerminalHostProtocolError DecodeTerminalHostSnapshotForVersion(
std::string_view payload,
uint16_t protocol_version,
TerminalHostSnapshot* snapshot);
struct TerminalHostResize {
TerminalHostResize();
@@ -304,7 +362,11 @@ struct TerminalHostResize {
uint16_t cols = 80;
uint16_t rows = 24;
uint16_t cell_width = 8;
uint16_t cell_height = 16;
std::vector<uint8_t> replay;
std::vector<TerminalHostKittyImageAlias> kitty_image_aliases;
TerminalHostKittyReplayState kitty_state;
bool operator==(const TerminalHostResize& other) const;
};
@@ -314,6 +376,10 @@ TerminalHostProtocolError EncodeTerminalHostResize(
std::vector<uint8_t>* payload);
TerminalHostProtocolError DecodeTerminalHostResize(std::string_view payload,
TerminalHostResize* resize);
TerminalHostProtocolError DecodeTerminalHostResizeForVersion(
std::string_view payload,
uint16_t protocol_version,
TerminalHostResize* resize);
struct TerminalHostResizeAck {
uint16_t cols = 80;
@@ -10,6 +10,7 @@
#include <cstdio>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
@@ -18,6 +19,11 @@ namespace {
int checks = 0;
int failures = 0;
constexpr uint16_t kRustTerminalHostProtocolVersion = 3;
constexpr size_t kRustTerminalHostMaxReplay = 15'692'632;
constexpr size_t kRustTerminalHostMaxKittyImageAliases = 4'096;
constexpr size_t kRustTerminalHostKittyReplayStateLength = 52;
void Check(bool condition, const char* message) {
++checks;
if (!condition) {
@@ -38,6 +44,246 @@ std::array<uint8_t, N> Filled(uint8_t value) {
return result;
}
template <typename Payload, typename = void>
struct HasKittyImageAliases : std::false_type {};
template <typename Payload>
struct HasKittyImageAliases<
Payload,
std::void_t<
decltype(std::declval<Payload&>().kitty_image_aliases)>>
: std::true_type {};
template <typename Payload, typename = void>
struct HasCellPixels : std::false_type {};
template <typename Payload>
struct HasCellPixels<
Payload,
std::void_t<decltype(std::declval<Payload&>().cell_width),
decltype(std::declval<Payload&>().cell_height)>>
: std::true_type {};
template <typename Payload, typename = void>
struct HasKittyReplayState : std::false_type {};
template <typename Payload>
struct HasKittyReplayState<
Payload,
std::void_t<decltype(std::declval<Payload&>().kitty_state)>>
: std::true_type {};
template <typename Grant, typename = void>
struct HasProtocolVersion : std::false_type {};
template <typename Grant>
struct HasProtocolVersion<
Grant,
std::void_t<decltype(std::declval<Grant&>().protocol_version)>>
: std::true_type {};
template <typename Grant>
bool GrantProtocolVersionIs(const Grant& grant, uint16_t expected) {
if constexpr (!HasProtocolVersion<Grant>::value) {
return false;
} else {
return grant.protocol_version == expected;
}
}
template <typename Payload>
constexpr size_t ReplayMetadataSuffixLength() {
return (HasCellPixels<Payload>::value ? 4 : 0) +
(HasKittyReplayState<Payload>::value
? kRustTerminalHostKittyReplayStateLength
: 0);
}
template <typename Payload>
bool CellPixelsAre(const Payload& payload,
uint16_t expected_width,
uint16_t expected_height) {
if constexpr (!HasCellPixels<Payload>::value) {
return false;
} else {
return payload.cell_width == expected_width &&
payload.cell_height == expected_height;
}
}
template <typename Payload>
using PayloadEncoder = cmux::TerminalHostProtocolError (*)(
const Payload&,
std::vector<uint8_t>*);
template <typename Payload>
using PayloadDecoder = cmux::TerminalHostProtocolError (*)(
std::string_view,
Payload*);
template <typename Payload>
bool KittyAliasesRoundTrip(PayloadEncoder<Payload> encode,
PayloadDecoder<Payload> decode) {
if constexpr (!HasKittyImageAliases<Payload>::value) {
return false;
} else {
using AliasVector =
std::decay_t<decltype(std::declval<Payload&>().kitty_image_aliases)>;
using Alias = typename AliasVector::value_type;
Payload value;
value.kitty_image_aliases = {Alias{41, 77}, Alias{42, 77}};
std::vector<uint8_t> payload;
Payload decoded;
if (encode(value, &payload) != cmux::TerminalHostProtocolError::kNone ||
decode(Bytes(payload), &decoded) !=
cmux::TerminalHostProtocolError::kNone ||
!(decoded == value)) {
return false;
}
constexpr std::array<uint8_t, 18> kAliasSuffix = {
2, 0, 41, 0, 0, 0, 77, 0, 0, 0, 42, 0, 0, 0, 77, 0, 0, 0,
};
const size_t trailing = ReplayMetadataSuffixLength<Payload>();
return payload.size() >= kAliasSuffix.size() + trailing &&
std::equal(kAliasSuffix.begin(), kAliasSuffix.end(),
payload.end() - trailing - kAliasSuffix.size());
}
}
enum class InvalidKittyAlias {
kZeroId,
kZeroNumber,
kDuplicateId,
};
template <typename Payload>
bool KittyAliasEncoderRejects(PayloadEncoder<Payload> encode,
InvalidKittyAlias invalid) {
if constexpr (!HasKittyImageAliases<Payload>::value) {
return false;
} else {
using AliasVector =
std::decay_t<decltype(std::declval<Payload&>().kitty_image_aliases)>;
using Alias = typename AliasVector::value_type;
Payload value;
switch (invalid) {
case InvalidKittyAlias::kZeroId:
value.kitty_image_aliases = {Alias{0, 77}};
break;
case InvalidKittyAlias::kZeroNumber:
value.kitty_image_aliases = {Alias{41, 0}};
break;
case InvalidKittyAlias::kDuplicateId:
value.kitty_image_aliases = {Alias{41, 77}, Alias{41, 78}};
break;
}
std::vector<uint8_t> untouched = {0xa5};
return encode(value, &untouched) ==
cmux::TerminalHostProtocolError::kMalformedPayload &&
untouched == std::vector<uint8_t>({0xa5});
}
}
template <typename Payload>
bool KittyAliasDecoderRejects(PayloadEncoder<Payload> encode,
PayloadDecoder<Payload> decode,
InvalidKittyAlias invalid) {
if constexpr (!HasKittyImageAliases<Payload>::value) {
return false;
} else {
using AliasVector =
std::decay_t<decltype(std::declval<Payload&>().kitty_image_aliases)>;
using Alias = typename AliasVector::value_type;
Payload value;
value.kitty_image_aliases = {Alias{41, 77}, Alias{42, 78}};
std::vector<uint8_t> payload;
if (encode(value, &payload) != cmux::TerminalHostProtocolError::kNone ||
payload.size() < 18) {
return false;
}
const size_t aliases =
payload.size() - 18 - ReplayMetadataSuffixLength<Payload>();
switch (invalid) {
case InvalidKittyAlias::kZeroId:
std::fill(payload.begin() + aliases + 2,
payload.begin() + aliases + 6, 0);
break;
case InvalidKittyAlias::kZeroNumber:
std::fill(payload.begin() + aliases + 6,
payload.begin() + aliases + 10, 0);
break;
case InvalidKittyAlias::kDuplicateId:
std::copy(payload.begin() + aliases + 2,
payload.begin() + aliases + 6,
payload.begin() + aliases + 10);
break;
}
Payload decoded;
return decode(Bytes(payload), &decoded) ==
cmux::TerminalHostProtocolError::kMalformedPayload;
}
}
template <typename Payload>
bool KittyAliasDecoderRejectsBadFraming(PayloadEncoder<Payload> encode,
PayloadDecoder<Payload> decode) {
if constexpr (!HasKittyImageAliases<Payload>::value) {
return false;
} else {
using AliasVector =
std::decay_t<decltype(std::declval<Payload&>().kitty_image_aliases)>;
using Alias = typename AliasVector::value_type;
Payload value;
value.kitty_image_aliases = {Alias{41, 77}};
std::vector<uint8_t> payload;
if (encode(value, &payload) != cmux::TerminalHostProtocolError::kNone) {
return false;
}
Payload decoded;
std::vector<uint8_t> truncated = payload;
truncated.pop_back();
std::vector<uint8_t> trailing = payload;
trailing.push_back(0);
return decode(Bytes(truncated), &decoded) ==
cmux::TerminalHostProtocolError::kMalformedPayload &&
decode(Bytes(trailing), &decoded) ==
cmux::TerminalHostProtocolError::kMalformedPayload;
}
}
template <typename Payload>
bool KittyAliasCountIsBounded(PayloadEncoder<Payload> encode) {
if constexpr (!HasKittyImageAliases<Payload>::value) {
return false;
} else {
using AliasVector =
std::decay_t<decltype(std::declval<Payload&>().kitty_image_aliases)>;
using Alias = typename AliasVector::value_type;
Payload value;
value.kitty_image_aliases.reserve(
kRustTerminalHostMaxKittyImageAliases + 1);
for (size_t index = 0;
index < kRustTerminalHostMaxKittyImageAliases; ++index) {
value.kitty_image_aliases.push_back(
Alias{static_cast<uint32_t>(index + 1),
static_cast<uint32_t>(index + 10'001)});
}
std::vector<uint8_t> payload;
if (encode(value, &payload) != cmux::TerminalHostProtocolError::kNone) {
return false;
}
value.kitty_image_aliases.push_back(
Alias{static_cast<uint32_t>(
kRustTerminalHostMaxKittyImageAliases + 1),
static_cast<uint32_t>(
kRustTerminalHostMaxKittyImageAliases + 10'001)});
payload = {0xa5};
return encode(value, &payload) ==
cmux::TerminalHostProtocolError::kPayloadTooLarge &&
payload == std::vector<uint8_t>({0xa5});
}
}
cmux::TerminalHostFrame SampleFrame() {
cmux::TerminalHostFrame frame;
frame.kind = cmux::TerminalHostMessageKind::kOutput;
@@ -50,6 +296,9 @@ cmux::TerminalHostFrame SampleFrame() {
void TestFrameGoldenAndKinds() {
using Error = cmux::TerminalHostProtocolError;
Check(cmux::kTerminalHostProtocolVersion ==
kRustTerminalHostProtocolVersion,
"terminal-host default protocol version matches Rust v3");
Check(cmux::kTerminalHostFlagColorsFollow == 1u,
"COLORS_FOLLOW is exactly header flag bit zero");
Check(cmux::kTerminalHostFlagViewerSizeAcks == 2u,
@@ -58,7 +307,7 @@ void TestFrameGoldenAndKinds() {
Check(cmux::EncodeTerminalHostFrame(SampleFrame(), &encoded) == Error::kNone,
"sample frame encodes");
Check(encoded == std::vector<uint8_t>({
'C', 'M', 'T', 'H', 0x01, 0x00, 0x06, 0x00, 0x44,
'C', 'M', 'T', 'H', 0x03, 0x00, 0x06, 0x00, 0x44,
0x33, 0x22, 0x11, 0x03, 0x00, 0x00, 0x00, 0x08, 0x07,
0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x18, 0x17, 0x16,
0x15, 0x14, 0x13, 0x12, 0x11, 0xaa, 0xbb, 0xcc,
@@ -77,7 +326,7 @@ void TestFrameGoldenAndKinds() {
Check(cmux::DecodeTerminalHostFrameHeader(
Bytes(encoded).substr(0, cmux::kTerminalHostHeaderLength), 3,
&header) == Error::kNone &&
header.version == 1 &&
header.version == 3 &&
header.kind == cmux::TerminalHostMessageKind::kOutput &&
header.flags == 0x11223344 && header.payload_length == 3 &&
header.request_id == 0x0102030405060708ULL &&
@@ -106,12 +355,19 @@ void TestFrameGoldenAndKinds() {
{cmux::TerminalHostMessageKind::kLaunch, 14},
{cmux::TerminalHostMessageKind::kCapability, 15},
{cmux::TerminalHostMessageKind::kResizeAck, 16},
{cmux::TerminalHostMessageKind::kClearHistoryAck, 17},
{cmux::TerminalHostMessageKind::kCellPixelSizeAck, 18},
{cmux::TerminalHostMessageKind::kKittyGraphicsLimitsAck, 19},
{cmux::TerminalHostMessageKind::kInput, 100},
{cmux::TerminalHostMessageKind::kPaste, 101},
{cmux::TerminalHostMessageKind::kViewerSize, 102},
{cmux::TerminalHostMessageKind::kReleaseViewer, 103},
{cmux::TerminalHostMessageKind::kTerminate, 104},
{cmux::TerminalHostMessageKind::kMintCapability, 105},
{cmux::TerminalHostMessageKind::kSetDefaults, 106},
{cmux::TerminalHostMessageKind::kClearHistory, 107},
{cmux::TerminalHostMessageKind::kSetCellPixelSize, 108},
{cmux::TerminalHostMessageKind::kSetKittyGraphicsLimits, 109},
};
std::vector<uint8_t> stream;
for (const auto& [kind, wire_number] : kinds) {
@@ -178,14 +434,39 @@ void TestRendererGrantValidation() {
cmux::TerminalHostRendererGrant grant;
Check(cmux::ValidateTerminalHostRendererGrant(
endpoint, terminal_hex, incarnation_hex, token_hex,
static_cast<uint32_t>(Rights::kRenderer), 30000, 30000,
static_cast<uint32_t>(Rights::kRenderer),
cmux::kTerminalHostProtocolVersionV1, 30000, 30000,
&grant) == Error::kNone,
"canonical renderer grant validates");
Check(grant.endpoint == endpoint && grant.terminal_id == terminal &&
grant.incarnation == incarnation &&
grant.token == Filled<32>(0xa5) &&
grant.rights == Rights::kRenderer && grant.ttl_ms == 30000,
grant.rights == Rights::kRenderer &&
grant.protocol_version == cmux::kTerminalHostProtocolVersionV1 &&
grant.ttl_ms == 30000,
"validated renderer grant contains typed binary identities and token");
Check(HasProtocolVersion<cmux::TerminalHostRendererGrant>::value &&
GrantProtocolVersionIs(grant,
cmux::kTerminalHostProtocolVersionV1),
"validated renderer grant carries the selected host protocol version");
using Validator = decltype(&cmux::ValidateTerminalHostRendererGrant);
Check(
(std::is_invocable_r_v<
Error, Validator, std::string_view, std::string_view,
std::string_view, std::string_view, uint64_t, uint64_t, uint64_t,
uint64_t, cmux::TerminalHostRendererGrant*>),
"renderer grant validation requires the selected protocol version");
const cmux::TerminalHostClientHello renderer_hello =
cmux::TerminalHostClientHelloForRendererGrant(grant);
Check(renderer_hello.min_version ==
cmux::kTerminalHostProtocolVersionV1 &&
renderer_hello.max_version ==
cmux::kTerminalHostProtocolVersionV1 &&
renderer_hello.role == cmux::TerminalHostClientRole::kRenderer &&
renderer_hello.requested_rights == Rights::kRenderer &&
renderer_hello.terminal_id == terminal &&
renderer_hello.token == Filled<32>(0xa5),
"validated grant pins renderer Hello to the selected host version");
Check(std::string(cmux::TerminalHostRendererGrantErrorMessage(Error::kNone))
.empty(),
"successful grant validation has no error text");
@@ -200,7 +481,8 @@ void TestRendererGrantValidation() {
output.endpoint = "untouched";
const Error error = cmux::ValidateTerminalHostRendererGrant(
candidate_endpoint, candidate_terminal, candidate_incarnation,
candidate_token, candidate_rights, candidate_ttl, expected_ttl,
candidate_token, candidate_rights,
cmux::kTerminalHostProtocolVersion, candidate_ttl, expected_ttl,
&output);
Check(error == Error::kNone || output.endpoint == "untouched",
"invalid grant leaves typed destination untouched");
@@ -292,8 +574,19 @@ void TestRendererGrantValidation() {
Check(Validate(endpoint, terminal_hex, incarnation_hex, token_hex, 7, 30000,
29999) == Error::kInvalidTtl,
"renderer grant TTL must echo the exact request");
for (const uint64_t invalid_version : {0ULL, 4ULL, 65537ULL}) {
cmux::TerminalHostRendererGrant output;
output.endpoint = "untouched";
Check(cmux::ValidateTerminalHostRendererGrant(
endpoint, terminal_hex, incarnation_hex, token_hex, 7,
invalid_version, 30000, 30000,
&output) == Error::kInvalidProtocolVersion &&
output.endpoint == "untouched",
"renderer grant rejects an unsupported host protocol version");
}
Check(cmux::ValidateTerminalHostRendererGrant(
endpoint, terminal_hex, incarnation_hex, token_hex, 7, 30000, 30000,
endpoint, terminal_hex, incarnation_hex, token_hex, 7,
cmux::kTerminalHostProtocolVersion, 30000, 30000,
nullptr) == Error::kInvalidEndpoint,
"renderer grant requires an output destination");
}
@@ -354,11 +647,11 @@ void TestMalformedFrames() {
Check(DecodeError(bad) == Error::kInvalidVersion,
"zero header version is rejected");
bad = encoded;
bad[4] = 2;
bad[4] = 3;
cmux::TerminalHostFrameDecoder future_version;
std::vector<cmux::TerminalHostFrame> frames;
Check(future_version.Push(Bytes(bad), &frames) == Error::kNone &&
frames.size() == 1 && frames[0].version == 2,
frames.size() == 1 && frames[0].version == 3,
"nonzero frame versions remain parseable before hello negotiation");
bad = encoded;
bad[6] = 0xe7;
@@ -507,6 +800,8 @@ void TestSnapshotPayload() {
cmux::TerminalHostSnapshot snapshot;
snapshot.cols = 120;
snapshot.rows = 41;
snapshot.cell_width = 9;
snapshot.cell_height = 18;
snapshot.pid = 0x01020304;
snapshot.replay = {0, 0xff, 'A'};
snapshot.cwd = "/tmp/\xe2\x98\x83";
@@ -526,18 +821,47 @@ void TestSnapshotPayload() {
Check(cmux::DecodeTerminalHostSnapshot(Bytes(payload), &decoded) ==
Error::kNone &&
decoded == snapshot,
"snapshot round trips replay, metadata, and argv");
"snapshot round trips replay, metadata, cell pixels, and Kitty state");
Check(payload.size() >= 4 + kRustTerminalHostKittyReplayStateLength &&
std::equal(
payload.end() - kRustTerminalHostKittyReplayStateLength - 4,
payload.end() - kRustTerminalHostKittyReplayStateLength,
std::array<uint8_t, 4>{9, 0, 18, 0}.begin()),
"snapshot cell pixels match the Rust v2 suffix");
cmux::TerminalHostSnapshot golden;
golden.cols = 1;
golden.rows = 2;
golden.cell_width = 9;
golden.cell_height = 18;
golden.kitty_state.limits = {1, 2, 3, 4};
golden.kitty_state.replay_next_image_ids = {5, 7};
golden.kitty_state.next_image_ids = {6, 8};
Check(cmux::EncodeTerminalHostSnapshot(golden, &payload) == Error::kNone &&
payload == std::vector<uint8_t>({
1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 9, 0, 18, 0, 1, 0,
0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0,
0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0,
0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8, 0,
0, 0,
}),
"snapshot exact bytes match the Rust v3 golden payload");
cmux::TerminalHostSnapshot empty;
empty.cols = 0;
empty.rows = 0;
empty.cell_width = 0;
empty.cell_height = 0;
Check(cmux::EncodeTerminalHostSnapshot(empty, &payload) == Error::kNone,
"empty snapshot encodes");
Check(cmux::DecodeTerminalHostSnapshot(Bytes(payload), &decoded) ==
Error::kNone &&
decoded.cols == 1 && decoded.rows == 1 && !decoded.pid &&
decoded.cell_width == 1 && decoded.cell_height == 1 &&
!decoded.cwd && decoded.command.empty(),
"snapshot decode matches Rust zero-size clamp and zero-pid sentinel");
"snapshot decode matches Rust geometry clamps and zero-pid sentinel");
std::vector<uint8_t> malformed = payload;
malformed.push_back(0);
@@ -557,12 +881,19 @@ void TestSnapshotPayload() {
Error::kMalformedPayload,
"snapshot rejects truncated replay");
std::vector<uint8_t> oversized_blob = {
80, 0, 24, 0, 0, 0, 0, 0, 1, 0x00, 0x80, 0x00,
};
std::vector<uint8_t> oversized_blob(12);
oversized_blob[0] = 80;
oversized_blob[2] = 24;
const uint32_t oversized_replay =
static_cast<uint32_t>(cmux::kTerminalHostMaxSnapshotReplay + 1);
for (size_t index = 0; index < sizeof(oversized_replay); ++index) {
oversized_blob[8 + index] =
static_cast<uint8_t>(oversized_replay >> (index * 8));
}
Check(cmux::DecodeTerminalHostSnapshot(Bytes(oversized_blob), &decoded) ==
Error::kMalformedPayload,
"snapshot rejects replay length above 8 MiB before allocation");
"snapshot rejects replay length above the exact Rust bound before "
"allocation");
cmux::TerminalHostSnapshot invalid_utf8;
invalid_utf8.command = {std::string("\xc0\x80", 2)};
@@ -580,6 +911,211 @@ void TestSnapshotPayload() {
"snapshot encoder bounds argv count");
}
void TestProtocolV1SnapshotAndResizeCompatibility() {
using Error = cmux::TerminalHostProtocolError;
cmux::TerminalHostSnapshot snapshot;
snapshot.cols = 91;
snapshot.rows = 27;
snapshot.replay = {'v', '1'};
snapshot.command = {"/bin/sh"};
std::vector<uint8_t> snapshot_payload;
Check(cmux::EncodeTerminalHostSnapshot(snapshot, &snapshot_payload) ==
Error::kNone &&
snapshot_payload.size() >= 2,
"v3 snapshot fixture encodes");
cmux::TerminalHostSnapshot decoded_snapshot;
std::vector<uint8_t> snapshot_v2 = snapshot_payload;
snapshot_v2.resize(snapshot_v2.size() -
kRustTerminalHostKittyReplayStateLength);
Check(cmux::DecodeTerminalHostSnapshotForVersion(
Bytes(snapshot_v2), cmux::kTerminalHostProtocolVersionV2,
&decoded_snapshot) == Error::kNone &&
decoded_snapshot == snapshot,
"protocol-v2 snapshot decodes aliases and cell pixels without Kitty "
"state");
snapshot_payload.resize(
snapshot_payload.size() - kRustTerminalHostKittyReplayStateLength - 2 -
4);
Check(cmux::DecodeTerminalHostSnapshotForVersion(
Bytes(snapshot_payload), cmux::kTerminalHostProtocolVersionV1,
&decoded_snapshot) == Error::kNone &&
decoded_snapshot == snapshot,
"protocol-v1 snapshot decodes without Kitty aliases or cell pixels");
Check(cmux::DecodeTerminalHostSnapshotForVersion(
Bytes(snapshot_payload), cmux::kTerminalHostProtocolVersion,
&decoded_snapshot) == Error::kMalformedPayload,
"protocol-v3 snapshot requires Kitty aliases, cell pixels, and state");
cmux::TerminalHostResize resize;
resize.cols = 101;
resize.rows = 33;
resize.replay = {'o', 'l', 'd'};
std::vector<uint8_t> resize_payload;
Check(cmux::EncodeTerminalHostResize(resize, &resize_payload) ==
Error::kNone &&
resize_payload.size() >= 2,
"v3 resize fixture encodes");
cmux::TerminalHostResize decoded_resize;
std::vector<uint8_t> resize_v2 = resize_payload;
resize_v2.resize(resize_v2.size() -
kRustTerminalHostKittyReplayStateLength);
Check(cmux::DecodeTerminalHostResizeForVersion(
Bytes(resize_v2), cmux::kTerminalHostProtocolVersionV2,
&decoded_resize) == Error::kNone &&
decoded_resize == resize,
"protocol-v2 resize decodes aliases and cell pixels without Kitty "
"state");
resize_payload.resize(
resize_payload.size() - kRustTerminalHostKittyReplayStateLength - 2 -
4);
Check(cmux::DecodeTerminalHostResizeForVersion(
Bytes(resize_payload), cmux::kTerminalHostProtocolVersionV1,
&decoded_resize) == Error::kNone &&
decoded_resize == resize,
"protocol-v1 resize decodes without a Kitty alias suffix");
Check(cmux::DecodeTerminalHostResizeForVersion(
Bytes(resize_payload), cmux::kTerminalHostProtocolVersion,
&decoded_resize) == Error::kMalformedPayload,
"protocol-v3 resize requires aliases, cell pixels, and Kitty state");
}
void TestProtocolV2KittyAliasesAndBounds() {
using Error = cmux::TerminalHostProtocolError;
Check(cmux::kTerminalHostMaxSnapshotReplay ==
kRustTerminalHostMaxReplay,
"snapshot and resized replay limit matches Rust exactly");
Check(cmux::kTerminalHostMaxFramePayload == 16 * 1024 * 1024,
"terminal-host frame payload limit remains exactly 16 MiB");
Check(KittyAliasesRoundTrip<cmux::TerminalHostSnapshot>(
cmux::EncodeTerminalHostSnapshot,
cmux::DecodeTerminalHostSnapshot),
"snapshot Kitty aliases round trip after existing metadata");
Check(KittyAliasesRoundTrip<cmux::TerminalHostResize>(
cmux::EncodeTerminalHostResize, cmux::DecodeTerminalHostResize),
"resized Kitty aliases round trip after replay");
Check(HasCellPixels<cmux::TerminalHostResize>::value,
"resized payload exposes the v2 cell-pixel suffix");
const std::vector<uint8_t> rust_v2_resize = {
101, 0, 33, 0, 3, 0, 0, 0, 'r', 's', 'z',
0, 0, 13, 0, 29, 0,
};
cmux::TerminalHostResize decoded_rust_v2_resize;
Check(cmux::DecodeTerminalHostResizeForVersion(
Bytes(rust_v2_resize), cmux::kTerminalHostProtocolVersionV2,
&decoded_rust_v2_resize) == Error::kNone &&
decoded_rust_v2_resize.cols == 101 &&
decoded_rust_v2_resize.rows == 33 &&
decoded_rust_v2_resize.replay ==
std::vector<uint8_t>({'r', 's', 'z'}) &&
CellPixelsAre(decoded_rust_v2_resize, 13, 29),
"C++ decodes the exact Rust v2 resized payload including cell pixels");
for (InvalidKittyAlias invalid :
{InvalidKittyAlias::kZeroId, InvalidKittyAlias::kZeroNumber,
InvalidKittyAlias::kDuplicateId}) {
Check(KittyAliasEncoderRejects<cmux::TerminalHostSnapshot>(
cmux::EncodeTerminalHostSnapshot, invalid),
"snapshot encoder rejects invalid Kitty alias identity");
Check(KittyAliasDecoderRejects<cmux::TerminalHostSnapshot>(
cmux::EncodeTerminalHostSnapshot,
cmux::DecodeTerminalHostSnapshot, invalid),
"snapshot decoder rejects invalid Kitty alias identity");
Check(KittyAliasEncoderRejects<cmux::TerminalHostResize>(
cmux::EncodeTerminalHostResize, invalid),
"resized encoder rejects invalid Kitty alias identity");
Check(KittyAliasDecoderRejects<cmux::TerminalHostResize>(
cmux::EncodeTerminalHostResize,
cmux::DecodeTerminalHostResize, invalid),
"resized decoder rejects invalid Kitty alias identity");
}
Check(KittyAliasDecoderRejectsBadFraming<cmux::TerminalHostSnapshot>(
cmux::EncodeTerminalHostSnapshot,
cmux::DecodeTerminalHostSnapshot),
"snapshot rejects truncated and trailing alias bytes");
Check(KittyAliasDecoderRejectsBadFraming<cmux::TerminalHostResize>(
cmux::EncodeTerminalHostResize, cmux::DecodeTerminalHostResize),
"resized rejects truncated and trailing alias bytes");
Check(KittyAliasCountIsBounded<cmux::TerminalHostSnapshot>(
cmux::EncodeTerminalHostSnapshot),
"snapshot admits 4096 aliases and rejects 4097");
Check(KittyAliasCountIsBounded<cmux::TerminalHostResize>(
cmux::EncodeTerminalHostResize),
"resized admits 4096 aliases and rejects 4097");
cmux::TerminalHostResize resize;
resize.replay.resize(kRustTerminalHostMaxReplay);
std::vector<uint8_t> payload;
Check(cmux::EncodeTerminalHostResize(resize, &payload) == Error::kNone &&
payload.size() == 8 + kRustTerminalHostMaxReplay + 2 + 4 +
kRustTerminalHostKittyReplayStateLength,
"resized admits the exact Rust replay ceiling plus v3 suffix");
resize.replay.push_back(0);
payload = {0xa5};
Check(cmux::EncodeTerminalHostResize(resize, &payload) ==
Error::kPayloadTooLarge &&
payload == std::vector<uint8_t>({0xa5}),
"resized rejects one byte beyond the exact Rust replay ceiling");
resize.replay.pop_back();
cmux::TerminalHostSnapshot snapshot;
snapshot.replay = std::move(resize.replay);
Check(cmux::EncodeTerminalHostSnapshot(snapshot, &payload) == Error::kNone,
"snapshot admits the exact Rust replay ceiling");
snapshot.replay.push_back(0);
payload = {0xa5};
Check(cmux::EncodeTerminalHostSnapshot(snapshot, &payload) ==
Error::kPayloadTooLarge &&
payload == std::vector<uint8_t>({0xa5}),
"snapshot rejects one byte beyond the exact Rust replay ceiling");
cmux::TerminalHostSnapshot oversized_frame;
oversized_frame.replay.resize(8 * 1024 * 1024);
oversized_frame.command.assign(
32, std::string(cmux::kTerminalHostMaxString, 'x'));
payload = {0xa5};
Check(cmux::EncodeTerminalHostSnapshot(oversized_frame, &payload) ==
Error::kPayloadTooLarge &&
payload == std::vector<uint8_t>({0xa5}),
"snapshot total payload cannot exceed the exact frame cap");
}
void TestProtocolV3KittyReplayState() {
using Error = cmux::TerminalHostProtocolError;
cmux::TerminalHostSnapshot snapshot;
snapshot.replay = {'x'};
snapshot.kitty_state.limits = {10'000'000, 13'595'480, 4'096, 16'384};
snapshot.kitty_state.replay_cursor_offset = 1;
snapshot.kitty_state.replay_next_image_ids = {41, 43};
snapshot.kitty_state.next_image_ids = {42, 44};
std::vector<uint8_t> payload;
cmux::TerminalHostSnapshot decoded;
Check(cmux::EncodeTerminalHostSnapshot(snapshot, &payload) == Error::kNone &&
cmux::DecodeTerminalHostSnapshot(Bytes(payload), &decoded) ==
Error::kNone &&
decoded == snapshot,
"protocol-v3 snapshot preserves bounded per-screen Kitty cursors");
snapshot.kitty_state.limits.image_bytes += 1;
payload = {0xa5};
Check(cmux::EncodeTerminalHostSnapshot(snapshot, &payload) ==
Error::kMalformedPayload &&
payload == std::vector<uint8_t>({0xa5}),
"Kitty replay state rejects a resource limit above Rust's bound");
snapshot.kitty_state.limits.image_bytes = 10'000'000;
snapshot.kitty_state.replay_next_image_ids.alternate = 0;
Check(cmux::EncodeTerminalHostSnapshot(snapshot, &payload) ==
Error::kMalformedPayload,
"Kitty replay state rejects a zero automatic image-ID cursor");
snapshot.kitty_state.replay_next_image_ids.alternate = 43;
snapshot.kitty_state.replay_cursor_offset = 2;
Check(cmux::EncodeTerminalHostSnapshot(snapshot, &payload) ==
Error::kMalformedPayload,
"Kitty replay state rejects a cursor offset beyond replay bytes");
}
void TestCapabilityPayloads() {
using Error = cmux::TerminalHostProtocolError;
using Rights = cmux::TerminalHostCapabilityRights;
@@ -648,12 +1184,21 @@ void TestResizeAndViewerSizePayloads() {
resize.cols = 0x0123;
resize.rows = 0x4567;
resize.replay = {0xaa, 0xbb, 0xcc};
resize.kitty_state.limits = {1, 2, 3, 4};
resize.kitty_state.replay_next_image_ids = {5, 7};
resize.kitty_state.next_image_ids = {6, 8};
std::vector<uint8_t> payload;
Check(cmux::EncodeTerminalHostResize(resize, &payload) == Error::kNone,
"resized payload encodes");
Check(payload == std::vector<uint8_t>(
{0x23, 0x01, 0x67, 0x45, 3, 0, 0, 0, 0xaa, 0xbb, 0xcc}),
"resized payload matches Rust u16/u16/u32/blob layout");
Check(payload == std::vector<uint8_t>({
0x23, 0x01, 0x67, 0x45, 3, 0, 0, 0, 0xaa, 0xbb,
0xcc, 0, 0, 8, 0, 16, 0, 1, 0, 0, 0, 0, 0, 0,
0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0,
0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8, 0,
0, 0,
}),
"resized payload matches Rust v3 replay and Kitty-state layout");
cmux::TerminalHostResize decoded;
Check(cmux::DecodeTerminalHostResize(Bytes(payload), &decoded) ==
Error::kNone &&
@@ -846,6 +1391,9 @@ int main() {
TestHelloPayloads();
TestCapabilityPayloads();
TestSnapshotPayload();
TestProtocolV1SnapshotAndResizeCompatibility();
TestProtocolV2KittyAliasesAndBounds();
TestProtocolV3KittyReplayState();
TestResizeAndViewerSizePayloads();
TestColorMetadataSetThenReset();
TestAuthoritativeCursorMetadataFollowsReplay();
+171 -86
View File
@@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aead"
version = "0.5.2"
@@ -63,9 +69,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.103"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]]
name = "approx"
@@ -109,7 +115,7 @@ dependencies = [
"nom",
"num-traits",
"rusticata-macros",
"thiserror 2.0.18",
"thiserror 2.0.19",
"time",
]
@@ -395,9 +401,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
[[package]]
name = "bytemuck"
version = "1.25.1"
version = "1.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424"
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
[[package]]
name = "bytes"
@@ -416,9 +422,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.67"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
dependencies = [
"find-msvc-tools",
"shlex 2.0.1",
@@ -517,9 +523,9 @@ dependencies = [
[[package]]
name = "clang-sys"
version = "1.8.1"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
dependencies = [
"glob",
"libc",
@@ -655,6 +661,8 @@ dependencies = [
"serde_json",
"smallvec",
"tempfile",
"terminput",
"terminput-crossterm",
"tokio",
"toml",
"unicode-segmentation",
@@ -681,6 +689,7 @@ dependencies = [
"anyhow",
"base64",
"cmux-pty",
"cmux-remote-protocol",
"cmux-tui-cdp",
"fs4",
"getrandom 0.3.4",
@@ -725,7 +734,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
"thiserror 2.0.18",
"thiserror 2.0.19",
]
[[package]]
@@ -827,6 +836,15 @@ dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "critical-section"
version = "1.2.0"
@@ -1208,9 +1226,9 @@ dependencies = [
[[package]]
name = "either"
version = "1.16.0"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
[[package]]
name = "embedded-io"
@@ -1290,9 +1308,18 @@ checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
[[package]]
name = "fastrand"
version = "2.4.1"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]]
name = "fiat-crypto"
@@ -1335,6 +1362,16 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flate2"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
@@ -1368,9 +1405,9 @@ dependencies = [
[[package]]
name = "futures"
version = "0.3.32"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218"
dependencies = [
"futures-channel",
"futures-core",
@@ -1396,9 +1433,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.32"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
dependencies = [
"futures-core",
"futures-sink",
@@ -1406,15 +1443,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.32"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
[[package]]
name = "futures-executor"
version = "0.3.32"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458"
dependencies = [
"futures-core",
"futures-task",
@@ -1442,9 +1479,9 @@ dependencies = [
[[package]]
name = "futures-macro"
version = "0.3.32"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
dependencies = [
"proc-macro2",
"quote",
@@ -1459,15 +1496,15 @@ checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
[[package]]
name = "futures-task"
version = "0.3.32"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
[[package]]
name = "futures-util"
version = "0.3.32"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
dependencies = [
"futures-channel",
"futures-core",
@@ -1558,7 +1595,9 @@ dependencies = [
name = "ghostty-vt"
version = "0.1.0"
dependencies = [
"base64",
"ghostty-vt-sys",
"png",
]
[[package]]
@@ -1570,9 +1609,9 @@ dependencies = [
[[package]]
name = "glob"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
[[package]]
name = "gloo-timers"
@@ -1660,7 +1699,7 @@ dependencies = [
"jni 0.22.4",
"rand 0.10.2",
"rustls",
"thiserror 2.0.18",
"thiserror 2.0.19",
"tinyvec",
"tokio",
"tokio-rustls",
@@ -1682,7 +1721,7 @@ dependencies = [
"prefix-trie",
"rand 0.10.2",
"ring",
"thiserror 2.0.18",
"thiserror 2.0.19",
"tinyvec",
"tracing",
"url",
@@ -1710,7 +1749,7 @@ dependencies = [
"rustls",
"smallvec",
"system-configuration",
"thiserror 2.0.18",
"thiserror 2.0.19",
"tokio",
"tokio-rustls",
"tracing",
@@ -1727,9 +1766,9 @@ dependencies = [
[[package]]
name = "http"
version = "1.4.2"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
dependencies = [
"bytes",
"itoa",
@@ -2279,7 +2318,7 @@ dependencies = [
"jni-sys 0.4.1",
"log",
"simd_cesu8",
"thiserror 2.0.18",
"thiserror 2.0.19",
"walkdir",
"windows-link",
]
@@ -2344,7 +2383,7 @@ checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899"
dependencies = [
"hashbrown 0.16.1",
"portable-atomic",
"thiserror 2.0.18",
"thiserror 2.0.19",
]
[[package]]
@@ -2361,9 +2400,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.186"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libloading"
@@ -2526,6 +2565,16 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "mio"
version = "1.2.2"
@@ -2674,7 +2723,7 @@ dependencies = [
"log",
"netlink-packet-core",
"netlink-sys",
"thiserror 2.0.18",
"thiserror 2.0.19",
]
[[package]]
@@ -2777,7 +2826,7 @@ dependencies = [
"rustc-hash",
"rustls",
"socket2 0.6.5",
"thiserror 2.0.18",
"thiserror 2.0.19",
"tokio",
"tokio-stream",
"tracing",
@@ -2805,7 +2854,7 @@ dependencies = [
"rustls-pki-types",
"slab",
"sorted-index-buffer",
"thiserror 2.0.18",
"thiserror 2.0.19",
"tinyvec",
"tracing",
"web-time",
@@ -3135,9 +3184,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pest"
version = "2.8.7"
version = "2.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47627dd7305c6a2d6c8c6bcd24c5a4c17dbbf425f4f9c5313e724b38fc9782e9"
checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2"
dependencies = [
"memchr",
"ucd-trie",
@@ -3145,9 +3194,9 @@ dependencies = [
[[package]]
name = "pest_derive"
version = "2.8.7"
version = "2.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b4254325ecad416ab689e27ba51da03ba01a9632bc6e108f5fe7c3c4ad29d58"
checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd"
dependencies = [
"pest",
"pest_generator",
@@ -3155,9 +3204,9 @@ dependencies = [
[[package]]
name = "pest_generator"
version = "2.8.7"
version = "2.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c4c0e91ead7a8f7acecbca6f003fc2e8282b1dbe2dd9c9d2f16aba42995e0a7"
checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6"
dependencies = [
"pest",
"pest_meta",
@@ -3168,9 +3217,9 @@ dependencies = [
[[package]]
name = "pest_meta"
version = "2.8.7"
version = "2.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9744bc48116fee06334924bb5f2bad41eed5e89bd26e29b0b799f9a3f82c210"
checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c"
dependencies = [
"pest",
]
@@ -3292,6 +3341,19 @@ dependencies = [
"time",
]
[[package]]
name = "png"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "poly1305"
version = "0.8.0"
@@ -3317,9 +3379,9 @@ dependencies = [
[[package]]
name = "portable-atomic"
version = "1.13.1"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3"
dependencies = [
"serde",
]
@@ -3454,9 +3516,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.106"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
@@ -3472,9 +3534,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.46"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
@@ -3593,7 +3655,7 @@ dependencies = [
"palette",
"serde",
"strum",
"thiserror 2.0.18",
"thiserror 2.0.19",
"unicode-segmentation",
"unicode-truncate",
"unicode-width",
@@ -3985,9 +4047,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
[[package]]
name = "serde"
version = "1.0.228"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
"serde_derive",
@@ -4005,29 +4067,29 @@ dependencies = [
[[package]]
name = "serde_core"
version = "1.0.228"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
"syn 3.0.3",
]
[[package]]
name = "serde_json"
version = "1.0.150"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"itoa",
"memchr",
@@ -4080,9 +4142,9 @@ dependencies = [
[[package]]
name = "serial2"
version = "0.2.37"
version = "0.2.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eb6ea5562eeaed6936b8b54e086aa0f88b9e5b1bef45beb038e2519fa1185b1"
checksum = "b16809bc35793b19ce4e0c53924bc0dce3937f15487997cfdaed936004180730"
dependencies = [
"cfg-if",
"libc",
@@ -4205,6 +4267,12 @@ dependencies = [
"rand_core 0.10.1",
]
[[package]]
name = "simd-adler32"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
[[package]]
name = "simd_cesu8"
version = "1.2.0"
@@ -4481,6 +4549,23 @@ dependencies = [
"phf_codegen",
]
[[package]]
name = "terminput"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86a8a2c741a146c297baff16b315401ab7ec4b4817eff783606e89734242408a"
dependencies = [
"bitflags 2.13.1",
]
[[package]]
name = "terminput-crossterm"
version = "0.4.7"
dependencies = [
"crossterm",
"terminput",
]
[[package]]
name = "termios"
version = "0.3.3"
@@ -4543,11 +4628,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.18"
version = "2.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
dependencies = [
"thiserror-impl 2.0.18",
"thiserror-impl 2.0.19",
]
[[package]]
@@ -4563,13 +4648,13 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.18"
version = "2.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
"syn 3.0.3",
]
[[package]]
@@ -4583,9 +4668,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.53"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50"
checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
dependencies = [
"deranged",
"js-sys",
@@ -4606,9 +4691,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
[[package]]
name = "time-macros"
version = "0.2.31"
version = "0.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f"
checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
dependencies = [
"num-conv",
"time-core",
@@ -4942,7 +5027,7 @@ dependencies = [
"rustls",
"rustls-pki-types",
"sha1",
"thiserror 2.0.18",
"thiserror 2.0.19",
]
[[package]]
@@ -5645,7 +5730,7 @@ dependencies = [
"futures",
"log",
"serde",
"thiserror 2.0.18",
"thiserror 2.0.19",
"windows",
"windows-core",
]
@@ -5669,7 +5754,7 @@ dependencies = [
"pharos",
"rustc_version",
"send_wrapper",
"thiserror 2.0.18",
"thiserror 2.0.19",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
@@ -5701,7 +5786,7 @@ dependencies = [
"oid-registry",
"ring",
"rusticata-macros",
"thiserror 2.0.18",
"thiserror 2.0.19",
"time",
]
@@ -5755,18 +5840,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.54"
version = "0.8.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19"
checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.54"
version = "0.8.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb"
dependencies = [
"proc-macro2",
"quote",
+6
View File
@@ -55,6 +55,7 @@ ratatui = "0.30"
unicode-width = "0.2"
unicode-segmentation = "1"
base64 = "0.22"
png = "0.17"
libc = "0.2"
tungstenite = { version = "0.29", default-features = false, features = ["handshake"] }
uds_windows = "1.2"
@@ -65,6 +66,8 @@ getrandom = "0.3"
bytes = "1"
diffy = "0.4"
fs4 = "1.1.0"
terminput = "=0.5.11"
terminput-crossterm = { version = "=0.4.7", default-features = false, features = ["crossterm_0_29"] }
futures-util = "0.3"
hmac = "0.12"
http = "1"
@@ -87,6 +90,9 @@ zeroize = { version = "1", features = ["derive"] }
# text while parsing CSI-u input. cmux needs those fields to preserve keyboard
# layouts across its shortcut layer and nested terminals.
crossterm = { path = "vendor/crossterm" }
# The cmux Crossterm patch adds `Event::EnhancedKey`. Keep the adapter's
# exhaustive event mapping in the same source-controlled compatibility layer.
terminput-crossterm = { path = "vendor/terminput-crossterm" }
[profile.release]
lto = "thin"
+2 -2
View File
@@ -2,7 +2,7 @@
The seven public SDKs expose handwritten resource handles over the reviewed
112-operation `cmux.protocol/1` catalog. The raw protocol inventory is a
separate compatibility surface with 91 commands and 44 events. Deterministic
separate compatibility surface with 92 commands and 45 events. Deterministic
generation is limited to those private protocol-10 models under each package's
explicit `raw` namespace. Consumers do not run a generator or install a
generator runtime.
@@ -71,7 +71,7 @@ and secret redaction.
The exact-binary live matrix adds one isolated create, run, exit, restart, and
cleanup flow per language. TypeScript repeats it over authenticated WebSocket,
for eight live transport runs. The separate raw protocol-10 suite runs 266
compatibility checks over its 91 commands and 44 events.
compatibility checks over its 92 commands and 45 events.
Each package suite also opens a stream with a short request deadline, leaves it
idle past that deadline, then delivers and cancels normally. This separates
+1 -1
View File
@@ -23,7 +23,7 @@ from app release tags such as `vX.Y.Z`. Current SDK package versions are
| C++ | C++20 and CMake 3.20 | standard library and platform socket APIs | installable CMake package |
| Zig | Zig 0.15.2 | standard library only | source package |
All packages target mux protocol 10 and expose the same 91 commands and 44
All packages target mux protocol 10 and expose the same 92 commands and 45
events. The shared conformance suite verifies their common wire behavior.
## One-time registry setup
@@ -2,27 +2,27 @@
"files": [
{
"path": "include/cmux/raw/generated/commands.hpp",
"sha256": "2f2639ee4f2176926c80d791531dd3278f6c3d2684d3299d5cbaa2fe5ab73cfb",
"size": 13229
"sha256": "bb02d6e2390d48b3d7008f9be8183d04090ab8b4efa30a0332f715760c27bfc4",
"size": 13363
},
{
"path": "include/cmux/raw/generated/events.hpp",
"sha256": "e3a6276f574e14171250cd6df38e8f56eabd438e9ed07e6556ecff14f622b689",
"size": 1965
"sha256": "11f825023a6b0faa094c53a852007e660210329642b51480240c492523a866aa",
"size": 1986
},
{
"path": "include/cmux/raw/generated/models.hpp",
"sha256": "cec984d4dd226e37e607722bf063be0c89f58d29f4e889326af19023e5483c8a",
"size": 101285
"sha256": "35687a732640759594c62150c3826e04d791271cd8e3b0f64b90ad28d760b827",
"size": 108123
},
{
"path": "src/raw/generated/protocol.cpp",
"sha256": "828a55826e6a0538dd97e607236be78e75720df580648cd9623f09165c3b470a",
"size": 667725
"sha256": "18efa59646ad007a8f2fc5fd7ec4e3c8cb06559793254b4f78fbd98e56733483",
"size": 716210
}
],
"format": 1,
"ir_sha256": "4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e",
"ir_sha256": "17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350",
"language": "cpp",
"mux_protocol": 10,
"schema_version": 2
@@ -71,6 +71,7 @@ public:
[[nodiscard]] Result<ExportLayoutResult> export_layout(const ExportLayoutRequest& request = {}, RequestOptions options = {});
[[nodiscard]] Result<FocusDirectionResult> focus_direction(const FocusDirectionRequest& request, RequestOptions options = {});
[[nodiscard]] Result<EmptyResult> focus_pane(const FocusPaneRequest& request, RequestOptions options = {});
[[nodiscard]] Result<GetCellPixelsResult> get_cell_pixels(const GetCellPixelsRequest& request = {}, RequestOptions options = {});
[[nodiscard]] Result<FrontendProjection> get_frontend_projection(const GetFrontendProjectionRequest& request, RequestOptions options = {});
[[nodiscard]] Result<IdentifyResult> identify(const IdentifyRequest& request = {}, RequestOptions options = {});
[[nodiscard]] Result<IdsResult> ids(const IdsRequest& request = {}, RequestOptions options = {});
@@ -17,7 +17,7 @@ struct UnknownEvent {
};
struct Event {
using Variant = std::variant<BellEvent, BrowserStateEvent, ClientAttachedEvent, ClientChangedEvent, ClientDetachedEvent, ClientListInvalidatedEvent, ColorsChangedEvent, ConfigReloadRequestedEvent, DetachedEvent, EmptyEvent, FrameEvent, FrontendProjectionChangedEvent, LayoutChangedEvent, NotificationEvent, OutputEvent, OverflowEvent, PairingRequestedEvent, PairingResolvedEvent, PaneAddedEvent, PaneClosedEvent, RenderDeltaEvent, RenderStateEvent, ResizedEvent, ScreenAddedEvent, ScreenClosedEvent, ScreenRenamedEvent, ScrollChangedEvent, StatusEvent, SurfaceExitedEvent, SurfaceOutputEvent, SurfaceResizeFailedEvent, SurfaceResizedEvent, TabAddedEvent, TabClosedEvent, TabRenamedEvent, TerminalRegistryChangedEvent, TitleChangedEvent, TreeChangedEvent, VtStateEvent, WindowTitleRequestedEvent, WorkspaceAddedEvent, WorkspaceClosedEvent, WorkspaceMovedEvent, WorkspaceRenamedEvent, UnknownEvent>;
using Variant = std::variant<BellEvent, BrowserStateEvent, ClientAttachedEvent, ClientChangedEvent, ClientDetachedEvent, ClientListInvalidatedEvent, ColorsChangedEvent, ConfigReloadRequestedEvent, DetachedEvent, EmptyEvent, FrameEvent, FrontendProjectionChangedEvent, GraphicsStatusEvent, LayoutChangedEvent, NotificationEvent, OutputEvent, OverflowEvent, PairingRequestedEvent, PairingResolvedEvent, PaneAddedEvent, PaneClosedEvent, RenderDeltaEvent, RenderStateEvent, ResizedEvent, ScreenAddedEvent, ScreenClosedEvent, ScreenRenamedEvent, ScrollChangedEvent, StatusEvent, SurfaceExitedEvent, SurfaceOutputEvent, SurfaceResizeFailedEvent, SurfaceResizedEvent, TabAddedEvent, TabClosedEvent, TabRenamedEvent, TerminalRegistryChangedEvent, TitleChangedEvent, TreeChangedEvent, VtStateEvent, WindowTitleRequestedEvent, WorkspaceAddedEvent, WorkspaceClosedEvent, WorkspaceMovedEvent, WorkspaceRenamedEvent, UnknownEvent>;
Variant value;
Json raw;
[[nodiscard]] std::string_view name() const noexcept;
@@ -14,7 +14,7 @@
namespace cmux::raw {
inline constexpr std::uint32_t kMuxProtocolVersion = 10U;
inline constexpr std::string_view kProtocolIrSha256 = "4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e";
inline constexpr std::string_view kProtocolIrSha256 = "17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350";
struct AgentRecord;
enum class AgentReportSource;
@@ -26,6 +26,7 @@ struct Base64;
struct BrowserFrame;
struct CellPixelFailure;
struct CellPixelResize;
struct CellPixelSurface;
struct ClientInfo;
struct ClientSize;
enum class ClientTransport;
@@ -40,11 +41,14 @@ struct ExportLayoutResult;
struct ExportedPane;
struct FocusDirectionResult;
struct FrontendProjection;
struct GetCellPixelsResult;
struct Id;
struct IdMapping;
struct IdentifyResult;
struct IdsResult;
struct JsonValue;
struct KittyGraphicsState;
struct KittyImageAlias;
struct Layout;
struct LayoutUndoConfirmationRequired;
struct LayoutUndoResult;
@@ -66,6 +70,11 @@ struct ProviderWorkspaceMutationResult;
struct ReadScreenResult;
struct ReadScrollbackResult;
struct RenderCursor;
enum class RenderGraphicFormat;
struct RenderGraphicImage;
struct RenderGraphicPlacement;
struct RenderGraphics;
struct RenderGraphicsDelta;
struct RenderRow;
struct RenderRun;
enum class RenderUnderline;
@@ -127,6 +136,7 @@ struct DetachClientRequest;
struct ExportLayoutRequest;
struct FocusDirectionRequest;
struct FocusPaneRequest;
struct GetCellPixelsRequest;
struct GetFrontendProjectionRequest;
struct IdentifyRequest;
struct IdsRequest;
@@ -202,6 +212,7 @@ struct DetachedEvent;
struct EmptyEvent;
struct FrameEvent;
struct FrontendProjectionChangedEvent;
struct GraphicsStatusEvent;
struct LayoutChangedEvent;
struct NotificationEvent;
struct OutputEvent;
@@ -255,6 +266,7 @@ enum class SubscribeRequestTreeEvents;
enum class ZoomPaneRequestMode;
enum class BrowserStateEventStatus;
enum class ClientAttachedEventTransport;
enum class GraphicsStatusEventKind;
enum class AgentSource {
detected,
@@ -521,6 +533,13 @@ struct CellPixelResize {
friend bool operator==(const CellPixelResize&, const CellPixelResize&) = default;
};
struct CellPixelSurface {
std::uint16_t height_px{};
Id surface{};
std::uint16_t width_px{};
friend bool operator==(const CellPixelSurface&, const CellPixelSurface&) = default;
};
enum class TerminalKey {
unidentified,
backquote,
@@ -988,6 +1007,17 @@ struct FrontendProjectionChangedEvent {
friend bool operator==(const FrontendProjectionChangedEvent&, const FrontendProjectionChangedEvent&) = default;
};
struct GetCellPixelsRequest {
friend bool operator==(const GetCellPixelsRequest&, const GetCellPixelsRequest&) = default;
};
struct GetCellPixelsResult {
std::uint16_t height_px{};
std::vector<CellPixelSurface> surfaces{};
std::uint16_t width_px{};
friend bool operator==(const GetCellPixelsResult&, const GetCellPixelsResult&) = default;
};
struct GetFrontendProjectionRequest {
std::string frontend{};
std::string scope{};
@@ -995,6 +1025,24 @@ struct GetFrontendProjectionRequest {
friend bool operator==(const GetFrontendProjectionRequest&, const GetFrontendProjectionRequest&) = default;
};
enum class GraphicsStatusEventKind {
kitty_image_budget_worker_start_failed,
kitty_image_budget_update_failed,
cell_pixel_update_retries_exhausted,
};
struct GraphicsStatusEvent {
std::optional<std::uint16_t> attempts{};
std::optional<std::uint16_t> cell_height{};
std::optional<std::uint16_t> cell_width{};
std::optional<std::string> error{};
GraphicsStatusEventKind kind{};
std::optional<std::uint64_t> remaining{};
std::optional<bool> retry_exhausted{};
std::optional<std::string> summary{};
friend bool operator==(const GraphicsStatusEvent&, const GraphicsStatusEvent&) = default;
};
enum class IdMappingKind {
workspace,
screen,
@@ -1045,6 +1093,25 @@ struct IdsResult {
friend bool operator==(const IdsResult&, const IdsResult&) = default;
};
struct KittyGraphicsState {
std::uint32_t alternate_next_image_id{};
std::uint32_t alternate_replay_next_image_id{};
std::uint64_t image_bytes{};
std::uint64_t images{};
std::uint64_t inflight_bytes{};
std::uint64_t placements{};
std::uint32_t primary_next_image_id{};
std::uint32_t primary_replay_next_image_id{};
std::uint32_t replay_cursor_offset{};
friend bool operator==(const KittyGraphicsState&, const KittyGraphicsState&) = default;
};
struct KittyImageAlias {
std::uint32_t image_id{};
std::uint32_t image_number{};
friend bool operator==(const KittyImageAlias&, const KittyImageAlias&) = default;
};
struct LayoutChangedEvent {
Id screen{};
friend bool operator==(const LayoutChangedEvent&, const LayoutChangedEvent&) = default;
@@ -1490,6 +1557,7 @@ struct RenderRow {
};
struct ReadScrollbackResult {
std::uint64_t epoch{};
std::vector<RenderRow> rows{};
std::uint32_t start{};
std::uint32_t total{};
@@ -1557,11 +1625,61 @@ struct RenderCursor {
friend bool operator==(const RenderCursor&, const RenderCursor&) = default;
};
enum class RenderGraphicFormat {
rgb,
rgba,
};
struct RenderGraphicImage {
Base64 data{};
RenderGraphicFormat format{};
std::uint64_t generation{};
std::uint32_t height{};
std::uint32_t id{};
std::uint32_t width{};
friend bool operator==(const RenderGraphicImage&, const RenderGraphicImage&) = default;
};
struct RenderGraphicPlacement {
std::optional<std::uint16_t> anchor_col{};
std::optional<std::uint32_t> anchor_row{};
std::uint32_t columns{};
std::uint32_t grid_cols{};
std::uint32_t grid_rows{};
std::uint32_t image_id{};
std::uint32_t ordinal{};
std::uint32_t pixel_height{};
std::uint32_t pixel_width{};
std::uint32_t placement_id{};
std::uint32_t rows{};
std::uint32_t source_height{};
std::uint32_t source_width{};
std::uint32_t source_x{};
std::uint32_t source_y{};
std::int32_t viewport_col{};
std::int32_t viewport_row{};
bool viewport_visible{};
std::uint32_t x_offset{};
std::uint32_t y_offset{};
std::int32_t z{};
friend bool operator==(const RenderGraphicPlacement&, const RenderGraphicPlacement&) = default;
};
struct RenderGraphicsDelta {
std::uint64_t generation{};
std::optional<std::vector<RenderGraphicImage>> images{};
std::optional<std::vector<RenderGraphicPlacement>> placements{};
std::optional<std::vector<std::uint32_t>> removed_image_ids{};
friend bool operator==(const RenderGraphicsDelta&, const RenderGraphicsDelta&) = default;
};
struct RenderDeltaEvent {
RenderCursor cursor{};
std::optional<ColorHex> default_bg{};
std::optional<ColorHex> default_fg{};
bool full{};
std::optional<RenderGraphicsDelta> graphics{};
std::optional<std::uint64_t> history_epoch{};
std::vector<RenderRow> rows{};
std::optional<std::uint32_t> scrollback_rows{};
std::optional<Size> size{};
@@ -1569,10 +1687,20 @@ struct RenderDeltaEvent {
friend bool operator==(const RenderDeltaEvent&, const RenderDeltaEvent&) = default;
};
struct RenderGraphics {
std::uint64_t generation{};
std::optional<std::vector<RenderGraphicImage>> images{};
std::vector<RenderGraphicPlacement> placements{};
std::optional<std::vector<std::uint32_t>> removed_image_ids{};
friend bool operator==(const RenderGraphics&, const RenderGraphics&) = default;
};
struct RenderStateEvent {
RenderCursor cursor{};
ColorHex default_bg{};
ColorHex default_fg{};
std::optional<RenderGraphics> graphics{};
std::uint64_t history_epoch{};
std::vector<RenderRow> rows{};
std::uint32_t scrollback_rows{};
Size size{};
@@ -1613,6 +1741,8 @@ struct ResizedEvent {
std::optional<TerminalColors> colors{};
std::uint16_t cols{};
std::optional<Base64> data{};
std::optional<KittyGraphicsState> kitty_graphics_state{};
std::optional<std::vector<KittyImageAlias>> kitty_image_aliases{};
std::optional<Base64> replay{};
std::uint16_t rows{};
Id surface{};
@@ -2018,6 +2148,8 @@ struct VtStateEvent {
std::optional<TerminalColors> colors{};
std::uint16_t cols{};
Base64 data{};
std::optional<KittyGraphicsState> kitty_graphics_state{};
std::optional<std::vector<KittyImageAlias>> kitty_image_aliases{};
std::uint16_t rows{};
Id surface{};
friend bool operator==(const VtStateEvent&, const VtStateEvent&) = default;
@@ -2031,6 +2163,8 @@ struct VtStateRequest {
struct VtStateResult {
std::uint16_t cols{};
Base64 data{};
std::optional<KittyGraphicsState> kitty_graphics_state{};
std::optional<std::vector<KittyImageAlias>> kitty_image_aliases{};
std::uint16_t rows{};
friend bool operator==(const VtStateResult&, const VtStateResult&) = default;
};
@@ -2191,6 +2325,12 @@ struct Codec<CellPixelResize> {
static Result<CellPixelResize> decode(const Json& value);
};
template <>
struct Codec<CellPixelSurface> {
static Result<Json> encode(const CellPixelSurface& value);
static Result<CellPixelSurface> decode(const Json& value);
};
template <>
struct Codec<ClientInfo> {
static Result<Json> encode(const ClientInfo& value);
@@ -2275,6 +2415,12 @@ struct Codec<FrontendProjection> {
static Result<FrontendProjection> decode(const Json& value);
};
template <>
struct Codec<GetCellPixelsResult> {
static Result<Json> encode(const GetCellPixelsResult& value);
static Result<GetCellPixelsResult> decode(const Json& value);
};
template <>
struct Codec<Id> {
static Result<Json> encode(const Id& value);
@@ -2305,6 +2451,18 @@ struct Codec<JsonValue> {
static Result<JsonValue> decode(const Json& value);
};
template <>
struct Codec<KittyGraphicsState> {
static Result<Json> encode(const KittyGraphicsState& value);
static Result<KittyGraphicsState> decode(const Json& value);
};
template <>
struct Codec<KittyImageAlias> {
static Result<Json> encode(const KittyImageAlias& value);
static Result<KittyImageAlias> decode(const Json& value);
};
template <>
struct Codec<Layout> {
static Result<Json> encode(const Layout& value);
@@ -2431,6 +2589,36 @@ struct Codec<RenderCursor> {
static Result<RenderCursor> decode(const Json& value);
};
template <>
struct Codec<RenderGraphicFormat> {
static Result<Json> encode(const RenderGraphicFormat& value);
static Result<RenderGraphicFormat> decode(const Json& value);
};
template <>
struct Codec<RenderGraphicImage> {
static Result<Json> encode(const RenderGraphicImage& value);
static Result<RenderGraphicImage> decode(const Json& value);
};
template <>
struct Codec<RenderGraphicPlacement> {
static Result<Json> encode(const RenderGraphicPlacement& value);
static Result<RenderGraphicPlacement> decode(const Json& value);
};
template <>
struct Codec<RenderGraphics> {
static Result<Json> encode(const RenderGraphics& value);
static Result<RenderGraphics> decode(const Json& value);
};
template <>
struct Codec<RenderGraphicsDelta> {
static Result<Json> encode(const RenderGraphicsDelta& value);
static Result<RenderGraphicsDelta> decode(const Json& value);
};
template <>
struct Codec<RenderRow> {
static Result<Json> encode(const RenderRow& value);
@@ -2797,6 +2985,12 @@ struct Codec<FocusPaneRequest> {
static Result<FocusPaneRequest> decode(const Json& value);
};
template <>
struct Codec<GetCellPixelsRequest> {
static Result<Json> encode(const GetCellPixelsRequest& value);
static Result<GetCellPixelsRequest> decode(const Json& value);
};
template <>
struct Codec<GetFrontendProjectionRequest> {
static Result<Json> encode(const GetFrontendProjectionRequest& value);
@@ -3247,6 +3441,12 @@ struct Codec<FrontendProjectionChangedEvent> {
static Result<FrontendProjectionChangedEvent> decode(const Json& value);
};
template <>
struct Codec<GraphicsStatusEvent> {
static Result<Json> encode(const GraphicsStatusEvent& value);
static Result<GraphicsStatusEvent> decode(const Json& value);
};
template <>
struct Codec<LayoutChangedEvent> {
static Result<Json> encode(const LayoutChangedEvent& value);
@@ -3565,4 +3765,10 @@ struct Codec<ClientAttachedEventTransport> {
static Result<ClientAttachedEventTransport> decode(const Json& value);
};
template <>
struct Codec<GraphicsStatusEventKind> {
static Result<Json> encode(const GraphicsStatusEventKind& value);
static Result<GraphicsStatusEventKind> decode(const Json& value);
};
} // namespace cmux::raw
File diff suppressed because it is too large Load Diff
@@ -84,7 +84,7 @@ static_assert(std::is_same_v<
static_assert(!std::is_copy_constructible_v<cmux::raw::EventStream>);
static_assert(std::is_move_constructible_v<cmux::raw::EventStream>);
constexpr std::size_t kExpectedRawCommandCount = 91U;
constexpr std::size_t kExpectedRawCommandCount = 92U;
constexpr std::array<std::string_view, 4> kViewportHistoryCommandNames{
"clear-history",
"new-pane-right",
@@ -102,7 +102,7 @@ TEST("generated command and event metadata is exhaustive and unique") {
const auto commands = cmux::raw::command_metadata();
const auto events = cmux::raw::event_metadata();
CHECK_EQ(commands.size(), kExpectedRawCommandCount);
CHECK_EQ(events.size(), 44U);
CHECK_EQ(events.size(), 45U);
std::set<std::string_view> command_names;
bool checked_attach_fields = false;
@@ -2,32 +2,32 @@
"files": [
{
"path": "generated_commands.go",
"sha256": "87dc6478f63716719e55c00ddc4891f4b44afdae1c583fd8ba6f32005e01fff4",
"size": 319070
"sha256": "a1efaacea3cbf1af5ce1e69b293d0fdc67b4e91512a7e4b5303219d2374c9542",
"size": 319901
},
{
"path": "generated_events.go",
"sha256": "109a07a79fee909753d6fc0664051ef4b4f07176ea1a6276821f398be10a5c28",
"size": 96693
"sha256": "2a013f474ca4d2d51bf7c93cae58a11fc94fc01614152e7fba00f3f2c776de5e",
"size": 105515
},
{
"path": "generated_metadata.go",
"sha256": "231d209bbf07d0a2b147b9027e1a85aec6b19fc08874402e7db98a020a11fdcf",
"size": 43354
"sha256": "2c0f23f4da7d2e3dff327b15223dc307018b848836166dcb35eba249ef7f9400",
"size": 43920
},
{
"path": "generated_presence_test.go",
"sha256": "deb7759851ed8eee77a3ef08a726bb077cd20b1e2632d87dedfff5679030e726",
"size": 802758
"sha256": "aceab1f1c81b4cdf17ffe3eed9bfc87f8e66c7a3e2718a658ff5a9dd30761093",
"size": 871216
},
{
"path": "generated_types.go",
"sha256": "85e098909cf5311e446862571a77d85ceb3831e864a5b75bb13cfa6210406b83",
"size": 196327
"sha256": "f104bda0cfb9d766f287d15bb9eb0ee510f2a9136b4a54d046628cc6a84fbbd5",
"size": 216980
}
],
"format": 1,
"ir_sha256": "4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e",
"ir_sha256": "17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350",
"language": "go",
"mux_protocol": 10,
"schema_version": 2
+1 -1
View File
@@ -1,6 +1,6 @@
# cmux Go SDK
The Go SDK covers all 91 protocol-10 commands and 44 event shapes using only
The Go SDK covers all 92 protocol-10 commands and 45 event shapes using only
the Go standard library.
```go
+4 -4
View File
@@ -20,8 +20,8 @@ import (
func TestGeneratedInventoryHasTypedMethodForEveryCommand(t *testing.T) {
commands := AllCommandMetadata()
if len(commands) != 91 {
t.Fatalf("generated commands = %d, want 91", len(commands))
if len(commands) != 92 {
t.Fatalf("generated commands = %d, want 92", len(commands))
}
clientType := reflect.TypeOf((*Client)(nil))
commandNames := make(map[string]struct{}, len(commands))
@@ -51,8 +51,8 @@ func TestGeneratedInventoryHasTypedMethodForEveryCommand(t *testing.T) {
t.Errorf("generated command inventory is missing %s", name)
}
}
if events := AllEventMetadata(); len(events) != 44 {
t.Fatalf("generated events = %d, want 44", len(events))
if events := AllEventMetadata(); len(events) != 45 {
t.Fatalf("generated events = %d, want 45", len(events))
}
}
@@ -88,6 +88,7 @@ func TestPublicReadScrollbackTailUsesTwoBoundedSnapshots(t *testing.T) {
return
}
if err := externalWriteSuccess(conn, probe["id"], map[string]any{
"epoch": 1,
"rows": []any{},
"start": 0,
"total": 10,
@@ -111,6 +112,7 @@ func TestPublicReadScrollbackTailUsesTwoBoundedSnapshots(t *testing.T) {
return
}
serverDone <- externalWriteSuccess(conn, page["id"], map[string]any{
"epoch": 1,
"rows": []any{
externalRenderRow(7, "eight"),
externalRenderRow(8, "nine"),
+27 -1
View File
@@ -1,5 +1,5 @@
// Code generated by cmux-tui SDK codegen. DO NOT EDIT.
// Mux protocol 10; IR SHA-256 4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e.
// Mux protocol 10; IR SHA-256 17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350.
package raw
@@ -3168,6 +3168,25 @@ func (value *FocusPaneRequest) UnmarshalJSON(data []byte) error {
type FocusPaneResult = EmptyResult
// GetCellPixelsRequest is the exact get-cell-pixels wire payload.
type GetCellPixelsRequest struct {
}
func (value *GetCellPixelsRequest) UnmarshalJSON(data []byte) error {
if !isJSONObject(data) {
return fmt.Errorf("decode GetCellPixelsRequest: expected object")
}
var fields struct {
}
if err := json.Unmarshal(data, &fields); err != nil {
return fmt.Errorf("decode GetCellPixelsRequest: %w", err)
}
type wire GetCellPixelsRequest
var decoded wire
*value = GetCellPixelsRequest(decoded)
return nil
}
// GetFrontendProjectionRequest is the exact get-frontend-projection wire payload.
type GetFrontendProjectionRequest struct {
Frontend string `json:"frontend"`
@@ -9374,6 +9393,13 @@ func (c *Client) FocusPane(ctx context.Context, pane ID) error {
return err
}
// GetCellPixels sends get-cell-pixels. Protocol v6; authority frontend.
func (c *Client) GetCellPixels(ctx context.Context) (GetCellPixelsResult, error) {
var result GetCellPixelsResult
err := c.requestGenerated(ctx, commandMetadata["get-cell-pixels"], "get-cell-pixels", nil, &result)
return result, err
}
// GetFrontendProjection sends get-frontend-projection. Protocol v7; authority control.
func (c *Client) GetFrontendProjection(ctx context.Context, frontend string, scope string, subjectKey string) (GetFrontendProjectionResult, error) {
var result GetFrontendProjectionResult
+255 -51
View File
@@ -1,5 +1,5 @@
// Code generated by cmux-tui SDK codegen. DO NOT EDIT.
// Mux protocol 10; IR SHA-256 4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e.
// Mux protocol 10; IR SHA-256 17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350.
package raw
@@ -853,6 +853,143 @@ func (FrontendProjectionChangedEvent) EventName() string { return "frontend-proj
func (FrontendProjectionChangedEvent) isDeltaEvent() {}
func (FrontendProjectionChangedEvent) isSubscribeEvent() {}
// GraphicsStatusEvent is emitted by protocol v10.
type GraphicsStatusEventKind string
const (
GraphicsStatusEventKindKittyImageBudgetWorkerStartFailed GraphicsStatusEventKind = "kitty-image-budget-worker-start-failed"
GraphicsStatusEventKindKittyImageBudgetUpdateFailed GraphicsStatusEventKind = "kitty-image-budget-update-failed"
GraphicsStatusEventKindCellPixelUpdateRetriesExhausted GraphicsStatusEventKind = "cell-pixel-update-retries-exhausted"
)
func (value GraphicsStatusEventKind) valid() bool {
switch value {
case GraphicsStatusEventKindKittyImageBudgetWorkerStartFailed, GraphicsStatusEventKindKittyImageBudgetUpdateFailed, GraphicsStatusEventKindCellPixelUpdateRetriesExhausted:
return true
default:
return false
}
}
func (value GraphicsStatusEventKind) MarshalJSON() ([]byte, error) {
if !value.valid() {
return nil, fmt.Errorf("%s has invalid value %v", "GraphicsStatusEventKind", value)
}
return json.Marshal(string(value))
}
func (value *GraphicsStatusEventKind) UnmarshalJSON(data []byte) error {
var decoded string
if err := json.Unmarshal(data, &decoded); err != nil {
return err
}
candidate := GraphicsStatusEventKind(decoded)
if !candidate.valid() {
return fmt.Errorf("%s has invalid value %v", "GraphicsStatusEventKind", decoded)
}
*value = candidate
return nil
}
type GraphicsStatusEvent struct {
Attempts *uint16 `json:"attempts,omitempty"`
CellHeight *uint16 `json:"cell_height,omitempty"`
CellWidth *uint16 `json:"cell_width,omitempty"`
Error *string `json:"error,omitempty"`
Kind GraphicsStatusEventKind `json:"kind"`
Remaining *uint64 `json:"remaining,omitempty"`
RetryExhausted *bool `json:"retry_exhausted,omitempty"`
Summary *string `json:"summary,omitempty"`
}
func (value GraphicsStatusEvent) MarshalJSON() ([]byte, error) {
switch value.Kind {
case "kitty-image-budget-worker-start-failed", "kitty-image-budget-update-failed", "cell-pixel-update-retries-exhausted":
default:
return nil, fmt.Errorf("encode GraphicsStatusEvent.Kind: invalid value %v", value.Kind)
}
type wire GraphicsStatusEvent
return json.Marshal(wire(value))
}
func (value *GraphicsStatusEvent) UnmarshalJSON(data []byte) error {
if !isJSONObject(data) {
return fmt.Errorf("decode GraphicsStatusEvent: expected object")
}
var fields struct {
Attempts optionalNonNullJSON[uint16] `json:"attempts"`
CellHeight optionalNonNullJSON[uint16] `json:"cell_height"`
CellWidth optionalNonNullJSON[uint16] `json:"cell_width"`
Error optionalNonNullJSON[string] `json:"error"`
Kind *GraphicsStatusEventKind `json:"kind"`
Remaining optionalNonNullJSON[uint64] `json:"remaining"`
RetryExhausted optionalNonNullJSON[bool] `json:"retry_exhausted"`
Summary optionalNonNullJSON[string] `json:"summary"`
}
if err := json.Unmarshal(data, &fields); err != nil {
return fmt.Errorf("decode GraphicsStatusEvent: %w", err)
}
type wire GraphicsStatusEvent
var decoded wire
if fields.Attempts.null {
return fmt.Errorf("decode GraphicsStatusEvent: non-nullable field attempts is null")
}
if fields.Attempts.set {
decoded.Attempts = &fields.Attempts.value
}
if fields.CellHeight.null {
return fmt.Errorf("decode GraphicsStatusEvent: non-nullable field cell_height is null")
}
if fields.CellHeight.set {
decoded.CellHeight = &fields.CellHeight.value
}
if fields.CellWidth.null {
return fmt.Errorf("decode GraphicsStatusEvent: non-nullable field cell_width is null")
}
if fields.CellWidth.set {
decoded.CellWidth = &fields.CellWidth.value
}
if fields.Error.null {
return fmt.Errorf("decode GraphicsStatusEvent: non-nullable field error is null")
}
if fields.Error.set {
decoded.Error = &fields.Error.value
}
if fields.Kind == nil {
return fmt.Errorf("decode GraphicsStatusEvent: required field kind is missing or null")
}
decoded.Kind = *fields.Kind
switch decoded.Kind {
case "kitty-image-budget-worker-start-failed", "kitty-image-budget-update-failed", "cell-pixel-update-retries-exhausted":
default:
return fmt.Errorf("decode GraphicsStatusEvent.Kind: invalid value %v", decoded.Kind)
}
if fields.Remaining.null {
return fmt.Errorf("decode GraphicsStatusEvent: non-nullable field remaining is null")
}
if fields.Remaining.set {
decoded.Remaining = &fields.Remaining.value
}
if fields.RetryExhausted.null {
return fmt.Errorf("decode GraphicsStatusEvent: non-nullable field retry_exhausted is null")
}
if fields.RetryExhausted.set {
decoded.RetryExhausted = &fields.RetryExhausted.value
}
if fields.Summary.null {
return fmt.Errorf("decode GraphicsStatusEvent: non-nullable field summary is null")
}
if fields.Summary.set {
decoded.Summary = &fields.Summary.value
}
*value = GraphicsStatusEvent(decoded)
return nil
}
func (GraphicsStatusEvent) EventName() string { return "graphics-status" }
func (GraphicsStatusEvent) isDeltaEvent() {}
func (GraphicsStatusEvent) isSubscribeEvent() {}
// LayoutChangedEvent is emitted by protocol v6.
type LayoutChangedEvent struct {
Screen ID `json:"screen"`
@@ -1292,14 +1429,16 @@ func (PaneClosedEvent) isSubscribeEvent() {}
// RenderDeltaEvent is emitted by protocol v7.
type RenderDeltaEvent struct {
Cursor RenderCursor `json:"cursor"`
DefaultBg *ColorHex `json:"default_bg,omitempty"`
DefaultFg *ColorHex `json:"default_fg,omitempty"`
Full bool `json:"full"`
Rows []RenderRow `json:"rows"`
ScrollbackRows *uint32 `json:"scrollback_rows,omitempty"`
Size *Size `json:"size,omitempty"`
Surface ID `json:"surface"`
Cursor RenderCursor `json:"cursor"`
DefaultBg *ColorHex `json:"default_bg,omitempty"`
DefaultFg *ColorHex `json:"default_fg,omitempty"`
Full bool `json:"full"`
Graphics *RenderGraphicsDelta `json:"graphics,omitempty"`
HistoryEpoch *uint64 `json:"history_epoch,omitempty"`
Rows []RenderRow `json:"rows"`
ScrollbackRows *uint32 `json:"scrollback_rows,omitempty"`
Size *Size `json:"size,omitempty"`
Surface ID `json:"surface"`
}
func (value *RenderDeltaEvent) UnmarshalJSON(data []byte) error {
@@ -1307,14 +1446,16 @@ func (value *RenderDeltaEvent) UnmarshalJSON(data []byte) error {
return fmt.Errorf("decode RenderDeltaEvent: expected object")
}
var fields struct {
Cursor *RenderCursor `json:"cursor"`
DefaultBg optionalNonNullJSON[ColorHex] `json:"default_bg"`
DefaultFg optionalNonNullJSON[ColorHex] `json:"default_fg"`
Full *bool `json:"full"`
Rows *[]RenderRow `json:"rows"`
ScrollbackRows optionalNonNullJSON[uint32] `json:"scrollback_rows"`
Size optionalNonNullJSON[Size] `json:"size"`
Surface *ID `json:"surface"`
Cursor *RenderCursor `json:"cursor"`
DefaultBg optionalNonNullJSON[ColorHex] `json:"default_bg"`
DefaultFg optionalNonNullJSON[ColorHex] `json:"default_fg"`
Full *bool `json:"full"`
Graphics optionalNonNullJSON[RenderGraphicsDelta] `json:"graphics"`
HistoryEpoch optionalNonNullJSON[uint64] `json:"history_epoch"`
Rows *[]RenderRow `json:"rows"`
ScrollbackRows optionalNonNullJSON[uint32] `json:"scrollback_rows"`
Size optionalNonNullJSON[Size] `json:"size"`
Surface *ID `json:"surface"`
}
if err := json.Unmarshal(data, &fields); err != nil {
return fmt.Errorf("decode RenderDeltaEvent: %w", err)
@@ -1341,6 +1482,18 @@ func (value *RenderDeltaEvent) UnmarshalJSON(data []byte) error {
return fmt.Errorf("decode RenderDeltaEvent: required field full is missing or null")
}
decoded.Full = *fields.Full
if fields.Graphics.null {
return fmt.Errorf("decode RenderDeltaEvent: non-nullable field graphics is null")
}
if fields.Graphics.set {
decoded.Graphics = &fields.Graphics.value
}
if fields.HistoryEpoch.null {
return fmt.Errorf("decode RenderDeltaEvent: non-nullable field history_epoch is null")
}
if fields.HistoryEpoch.set {
decoded.HistoryEpoch = &fields.HistoryEpoch.value
}
if fields.Rows == nil {
return fmt.Errorf("decode RenderDeltaEvent: required field rows is missing or null")
}
@@ -1371,13 +1524,15 @@ func (RenderDeltaEvent) isRenderAttachEvent() {}
// RenderStateEvent is emitted by protocol v7.
type RenderStateEvent struct {
Cursor RenderCursor `json:"cursor"`
DefaultBg ColorHex `json:"default_bg"`
DefaultFg ColorHex `json:"default_fg"`
Rows []RenderRow `json:"rows"`
ScrollbackRows uint32 `json:"scrollback_rows"`
Size Size `json:"size"`
Surface ID `json:"surface"`
Cursor RenderCursor `json:"cursor"`
DefaultBg ColorHex `json:"default_bg"`
DefaultFg ColorHex `json:"default_fg"`
Graphics *RenderGraphics `json:"graphics,omitempty"`
HistoryEpoch uint64 `json:"history_epoch"`
Rows []RenderRow `json:"rows"`
ScrollbackRows uint32 `json:"scrollback_rows"`
Size Size `json:"size"`
Surface ID `json:"surface"`
}
func (value *RenderStateEvent) UnmarshalJSON(data []byte) error {
@@ -1385,13 +1540,15 @@ func (value *RenderStateEvent) UnmarshalJSON(data []byte) error {
return fmt.Errorf("decode RenderStateEvent: expected object")
}
var fields struct {
Cursor *RenderCursor `json:"cursor"`
DefaultBg *ColorHex `json:"default_bg"`
DefaultFg *ColorHex `json:"default_fg"`
Rows *[]RenderRow `json:"rows"`
ScrollbackRows *uint32 `json:"scrollback_rows"`
Size *Size `json:"size"`
Surface *ID `json:"surface"`
Cursor *RenderCursor `json:"cursor"`
DefaultBg *ColorHex `json:"default_bg"`
DefaultFg *ColorHex `json:"default_fg"`
Graphics optionalNonNullJSON[RenderGraphics] `json:"graphics"`
HistoryEpoch *uint64 `json:"history_epoch"`
Rows *[]RenderRow `json:"rows"`
ScrollbackRows *uint32 `json:"scrollback_rows"`
Size *Size `json:"size"`
Surface *ID `json:"surface"`
}
if err := json.Unmarshal(data, &fields); err != nil {
return fmt.Errorf("decode RenderStateEvent: %w", err)
@@ -1410,6 +1567,16 @@ func (value *RenderStateEvent) UnmarshalJSON(data []byte) error {
return fmt.Errorf("decode RenderStateEvent: required field default_fg is missing or null")
}
decoded.DefaultFg = *fields.DefaultFg
if fields.Graphics.null {
return fmt.Errorf("decode RenderStateEvent: non-nullable field graphics is null")
}
if fields.Graphics.set {
decoded.Graphics = &fields.Graphics.value
}
if fields.HistoryEpoch == nil {
return fmt.Errorf("decode RenderStateEvent: required field history_epoch is missing or null")
}
decoded.HistoryEpoch = *fields.HistoryEpoch
if fields.Rows == nil {
return fmt.Errorf("decode RenderStateEvent: required field rows is missing or null")
}
@@ -1439,10 +1606,12 @@ type ResizedEvent struct {
Colors *TerminalColors `json:"colors,omitempty"`
Cols uint16 `json:"cols"`
// Protocol 6 compatibility field.
Data *Base64 `json:"data,omitempty"`
Replay *Base64 `json:"replay,omitempty"`
Rows uint16 `json:"rows"`
Surface ID `json:"surface"`
Data *Base64 `json:"data,omitempty"`
KittyGraphicsState *KittyGraphicsState `json:"kitty_graphics_state,omitempty"`
KittyImageAliases *[]KittyImageAlias `json:"kitty_image_aliases,omitempty"`
Replay *Base64 `json:"replay,omitempty"`
Rows uint16 `json:"rows"`
Surface ID `json:"surface"`
}
func (value *ResizedEvent) UnmarshalJSON(data []byte) error {
@@ -1450,12 +1619,14 @@ func (value *ResizedEvent) UnmarshalJSON(data []byte) error {
return fmt.Errorf("decode ResizedEvent: expected object")
}
var fields struct {
Colors optionalNonNullJSON[TerminalColors] `json:"colors"`
Cols *uint16 `json:"cols"`
Data optionalNonNullJSON[Base64] `json:"data"`
Replay optionalNonNullJSON[Base64] `json:"replay"`
Rows *uint16 `json:"rows"`
Surface *ID `json:"surface"`
Colors optionalNonNullJSON[TerminalColors] `json:"colors"`
Cols *uint16 `json:"cols"`
Data optionalNonNullJSON[Base64] `json:"data"`
KittyGraphicsState optionalNonNullJSON[KittyGraphicsState] `json:"kitty_graphics_state"`
KittyImageAliases optionalNonNullJSON[[]KittyImageAlias] `json:"kitty_image_aliases"`
Replay optionalNonNullJSON[Base64] `json:"replay"`
Rows *uint16 `json:"rows"`
Surface *ID `json:"surface"`
}
if err := json.Unmarshal(data, &fields); err != nil {
return fmt.Errorf("decode ResizedEvent: %w", err)
@@ -1478,6 +1649,18 @@ func (value *ResizedEvent) UnmarshalJSON(data []byte) error {
if fields.Data.set {
decoded.Data = &fields.Data.value
}
if fields.KittyGraphicsState.null {
return fmt.Errorf("decode ResizedEvent: non-nullable field kitty_graphics_state is null")
}
if fields.KittyGraphicsState.set {
decoded.KittyGraphicsState = &fields.KittyGraphicsState.value
}
if fields.KittyImageAliases.null {
return fmt.Errorf("decode ResizedEvent: non-nullable field kitty_image_aliases is null")
}
if fields.KittyImageAliases.set {
decoded.KittyImageAliases = &fields.KittyImageAliases.value
}
if fields.Replay.null {
return fmt.Errorf("decode ResizedEvent: non-nullable field replay is null")
}
@@ -2268,11 +2451,13 @@ func (TreeChangedEvent) isSubscribeEvent() {}
// VTStateEvent is emitted by protocol v5.
type VTStateEvent struct {
Colors *TerminalColors `json:"colors,omitempty"`
Cols uint16 `json:"cols"`
Data Base64 `json:"data"`
Rows uint16 `json:"rows"`
Surface ID `json:"surface"`
Colors *TerminalColors `json:"colors,omitempty"`
Cols uint16 `json:"cols"`
Data Base64 `json:"data"`
KittyGraphicsState *KittyGraphicsState `json:"kitty_graphics_state,omitempty"`
KittyImageAliases *[]KittyImageAlias `json:"kitty_image_aliases,omitempty"`
Rows uint16 `json:"rows"`
Surface ID `json:"surface"`
}
func (value *VTStateEvent) UnmarshalJSON(data []byte) error {
@@ -2280,11 +2465,13 @@ func (value *VTStateEvent) UnmarshalJSON(data []byte) error {
return fmt.Errorf("decode VTStateEvent: expected object")
}
var fields struct {
Colors optionalNonNullJSON[TerminalColors] `json:"colors"`
Cols *uint16 `json:"cols"`
Data *Base64 `json:"data"`
Rows *uint16 `json:"rows"`
Surface *ID `json:"surface"`
Colors optionalNonNullJSON[TerminalColors] `json:"colors"`
Cols *uint16 `json:"cols"`
Data *Base64 `json:"data"`
KittyGraphicsState optionalNonNullJSON[KittyGraphicsState] `json:"kitty_graphics_state"`
KittyImageAliases optionalNonNullJSON[[]KittyImageAlias] `json:"kitty_image_aliases"`
Rows *uint16 `json:"rows"`
Surface *ID `json:"surface"`
}
if err := json.Unmarshal(data, &fields); err != nil {
return fmt.Errorf("decode VTStateEvent: %w", err)
@@ -2305,6 +2492,18 @@ func (value *VTStateEvent) UnmarshalJSON(data []byte) error {
return fmt.Errorf("decode VTStateEvent: required field data is missing or null")
}
decoded.Data = *fields.Data
if fields.KittyGraphicsState.null {
return fmt.Errorf("decode VTStateEvent: non-nullable field kitty_graphics_state is null")
}
if fields.KittyGraphicsState.set {
decoded.KittyGraphicsState = &fields.KittyGraphicsState.value
}
if fields.KittyImageAliases.null {
return fmt.Errorf("decode VTStateEvent: non-nullable field kitty_image_aliases is null")
}
if fields.KittyImageAliases.set {
decoded.KittyImageAliases = &fields.KittyImageAliases.value
}
if fields.Rows == nil {
return fmt.Errorf("decode VTStateEvent: required field rows is missing or null")
}
@@ -2720,6 +2919,11 @@ func parseEvent(raw map[string]any) Event {
if decodeEvent(raw, &event) {
return event
}
case "graphics-status":
var event GraphicsStatusEvent
if decodeEvent(raw, &event) {
return event
}
case "layout-changed":
var event LayoutChangedEvent
if decodeEvent(raw, &event) {
+10 -4
View File
@@ -1,12 +1,12 @@
// Code generated by cmux-tui SDK codegen. DO NOT EDIT.
// Mux protocol 10; IR SHA-256 4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e.
// Mux protocol 10; IR SHA-256 17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350.
package raw
const (
SDKSchemaVersion = 2
MuxProtocolVersion = 10
SDKIRSHA256 = "4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e"
SDKIRSHA256 = "17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350"
)
type Authority string
@@ -92,6 +92,7 @@ var commandMetadata = map[string]CommandMetadata{
"export-layout": {Name: "export-layout", GoMethod: "ExportLayout", Authority: AuthorityControl, Since: 6, Capability: "", Stream: "", FieldSince: nil, FieldCapabilities: nil},
"focus-direction": {Name: "focus-direction", GoMethod: "FocusDirection", Authority: AuthorityControl, Since: 6, Capability: "", Stream: "", FieldSince: nil, FieldCapabilities: nil},
"focus-pane": {Name: "focus-pane", GoMethod: "FocusPane", Authority: AuthorityControl, Since: 5, Capability: "", Stream: "", FieldSince: nil, FieldCapabilities: nil},
"get-cell-pixels": {Name: "get-cell-pixels", GoMethod: "GetCellPixels", Authority: AuthorityFrontend, Since: 6, Capability: "", Stream: "", FieldSince: nil, FieldCapabilities: nil},
"get-frontend-projection": {Name: "get-frontend-projection", GoMethod: "GetFrontendProjection", Authority: AuthorityControl, Since: 7, Capability: "", Stream: "", FieldSince: nil, FieldCapabilities: nil},
"identify": {Name: "identify", GoMethod: "Identify", Authority: AuthorityControl, Since: 5, Capability: "", Stream: "", FieldSince: nil, FieldCapabilities: nil},
"ids": {Name: "ids", GoMethod: "IDs", Authority: AuthorityControl, Since: 6, Capability: "", Stream: "", FieldSince: nil, FieldCapabilities: nil},
@@ -168,6 +169,7 @@ var eventMetadata = map[string]EventMetadata{
"empty": {Name: "empty", Since: 5, Capability: "", Streams: []string{"subscribe"}, Emission: "emitted"},
"frame": {Name: "frame", Since: 6, Capability: "", Streams: []string{"attach-browser"}, Emission: "emitted"},
"frontend-projection-changed": {Name: "frontend-projection-changed", Since: 7, Capability: "", Streams: []string{"subscribe"}, Emission: "emitted"},
"graphics-status": {Name: "graphics-status", Since: 10, Capability: "", Streams: []string{"subscribe"}, Emission: "emitted"},
"layout-changed": {Name: "layout-changed", Since: 6, Capability: "", Streams: []string{"subscribe"}, Emission: "emitted"},
"notification": {Name: "notification", Since: 6, Capability: "", Streams: []string{"subscribe", "attach-byte", "attach-browser"}, Emission: "emitted"},
"output": {Name: "output", Since: 5, Capability: "", Streams: []string{"attach-byte"}, Emission: "emitted"},
@@ -245,7 +247,7 @@ func ProfileInfo(name Profile) (ProfileMetadata, bool) {
}
func AllCommandMetadata() []CommandMetadata {
result := make([]CommandMetadata, 0, 91)
result := make([]CommandMetadata, 0, 92)
result = append(result, cloneCommandMetadata(commandMetadata["apply-layout"]))
result = append(result, cloneCommandMetadata(commandMetadata["attach-surface"]))
result = append(result, cloneCommandMetadata(commandMetadata["browser-activate"]))
@@ -276,6 +278,7 @@ func AllCommandMetadata() []CommandMetadata {
result = append(result, cloneCommandMetadata(commandMetadata["export-layout"]))
result = append(result, cloneCommandMetadata(commandMetadata["focus-direction"]))
result = append(result, cloneCommandMetadata(commandMetadata["focus-pane"]))
result = append(result, cloneCommandMetadata(commandMetadata["get-cell-pixels"]))
result = append(result, cloneCommandMetadata(commandMetadata["get-frontend-projection"]))
result = append(result, cloneCommandMetadata(commandMetadata["identify"]))
result = append(result, cloneCommandMetadata(commandMetadata["ids"]))
@@ -341,7 +344,7 @@ func AllCommandMetadata() []CommandMetadata {
}
func AllEventMetadata() []EventMetadata {
result := make([]EventMetadata, 0, 44)
result := make([]EventMetadata, 0, 45)
var metadata EventMetadata
metadata = eventMetadata["bell"]
metadata.Streams = append([]string(nil), metadata.Streams...)
@@ -379,6 +382,9 @@ func AllEventMetadata() []EventMetadata {
metadata = eventMetadata["frontend-projection-changed"]
metadata.Streams = append([]string(nil), metadata.Streams...)
result = append(result, metadata)
metadata = eventMetadata["graphics-status"]
metadata.Streams = append([]string(nil), metadata.Streams...)
result = append(result, metadata)
metadata = eventMetadata["layout-changed"]
metadata.Streams = append([]string(nil), metadata.Streams...)
result = append(result, metadata)
File diff suppressed because it is too large Load Diff
+535 -7
View File
@@ -1,5 +1,5 @@
// Code generated by cmux-tui SDK codegen. DO NOT EDIT.
// Mux protocol 10; IR SHA-256 4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e.
// Mux protocol 10; IR SHA-256 17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350.
package raw
@@ -497,6 +497,42 @@ func (value *CellPixelResize) UnmarshalJSON(data []byte) error {
return nil
}
type CellPixelSurface struct {
HeightPx uint16 `json:"height_px"`
Surface ID `json:"surface"`
WidthPx uint16 `json:"width_px"`
}
func (value *CellPixelSurface) UnmarshalJSON(data []byte) error {
if !isJSONObject(data) {
return fmt.Errorf("decode CellPixelSurface: expected object")
}
var fields struct {
HeightPx *uint16 `json:"height_px"`
Surface *ID `json:"surface"`
WidthPx *uint16 `json:"width_px"`
}
if err := json.Unmarshal(data, &fields); err != nil {
return fmt.Errorf("decode CellPixelSurface: %w", err)
}
type wire CellPixelSurface
var decoded wire
if fields.HeightPx == nil {
return fmt.Errorf("decode CellPixelSurface: required field height_px is missing or null")
}
decoded.HeightPx = *fields.HeightPx
if fields.Surface == nil {
return fmt.Errorf("decode CellPixelSurface: required field surface is missing or null")
}
decoded.Surface = *fields.Surface
if fields.WidthPx == nil {
return fmt.Errorf("decode CellPixelSurface: required field width_px is missing or null")
}
decoded.WidthPx = *fields.WidthPx
*value = CellPixelSurface(decoded)
return nil
}
type ClientInfo struct {
Attached []ID `json:"attached"`
Client uint64 `json:"client"`
@@ -1589,6 +1625,42 @@ func (value *FrontendProjection) UnmarshalJSON(data []byte) error {
return nil
}
type GetCellPixelsResult struct {
HeightPx uint16 `json:"height_px"`
Surfaces []CellPixelSurface `json:"surfaces"`
WidthPx uint16 `json:"width_px"`
}
func (value *GetCellPixelsResult) UnmarshalJSON(data []byte) error {
if !isJSONObject(data) {
return fmt.Errorf("decode GetCellPixelsResult: expected object")
}
var fields struct {
HeightPx *uint16 `json:"height_px"`
Surfaces *[]CellPixelSurface `json:"surfaces"`
WidthPx *uint16 `json:"width_px"`
}
if err := json.Unmarshal(data, &fields); err != nil {
return fmt.Errorf("decode GetCellPixelsResult: %w", err)
}
type wire GetCellPixelsResult
var decoded wire
if fields.HeightPx == nil {
return fmt.Errorf("decode GetCellPixelsResult: required field height_px is missing or null")
}
decoded.HeightPx = *fields.HeightPx
if fields.Surfaces == nil {
return fmt.Errorf("decode GetCellPixelsResult: required field surfaces is missing or null")
}
decoded.Surfaces = *fields.Surfaces
if fields.WidthPx == nil {
return fmt.Errorf("decode GetCellPixelsResult: required field width_px is missing or null")
}
decoded.WidthPx = *fields.WidthPx
*value = GetCellPixelsResult(decoded)
return nil
}
type ID = uint64
type IDMappingKind string
@@ -1925,6 +1997,108 @@ func (value *IDsResult) UnmarshalJSON(data []byte) error {
type JSONValue = json.RawMessage
type KittyGraphicsState struct {
AlternateNextImageID uint32 `json:"alternate_next_image_id"`
AlternateReplayNextImageID uint32 `json:"alternate_replay_next_image_id"`
ImageBytes uint64 `json:"image_bytes"`
Images uint64 `json:"images"`
InflightBytes uint64 `json:"inflight_bytes"`
Placements uint64 `json:"placements"`
PrimaryNextImageID uint32 `json:"primary_next_image_id"`
PrimaryReplayNextImageID uint32 `json:"primary_replay_next_image_id"`
ReplayCursorOffset uint32 `json:"replay_cursor_offset"`
}
func (value *KittyGraphicsState) UnmarshalJSON(data []byte) error {
if !isJSONObject(data) {
return fmt.Errorf("decode KittyGraphicsState: expected object")
}
var fields struct {
AlternateNextImageID *uint32 `json:"alternate_next_image_id"`
AlternateReplayNextImageID *uint32 `json:"alternate_replay_next_image_id"`
ImageBytes *uint64 `json:"image_bytes"`
Images *uint64 `json:"images"`
InflightBytes *uint64 `json:"inflight_bytes"`
Placements *uint64 `json:"placements"`
PrimaryNextImageID *uint32 `json:"primary_next_image_id"`
PrimaryReplayNextImageID *uint32 `json:"primary_replay_next_image_id"`
ReplayCursorOffset *uint32 `json:"replay_cursor_offset"`
}
if err := json.Unmarshal(data, &fields); err != nil {
return fmt.Errorf("decode KittyGraphicsState: %w", err)
}
type wire KittyGraphicsState
var decoded wire
if fields.AlternateNextImageID == nil {
return fmt.Errorf("decode KittyGraphicsState: required field alternate_next_image_id is missing or null")
}
decoded.AlternateNextImageID = *fields.AlternateNextImageID
if fields.AlternateReplayNextImageID == nil {
return fmt.Errorf("decode KittyGraphicsState: required field alternate_replay_next_image_id is missing or null")
}
decoded.AlternateReplayNextImageID = *fields.AlternateReplayNextImageID
if fields.ImageBytes == nil {
return fmt.Errorf("decode KittyGraphicsState: required field image_bytes is missing or null")
}
decoded.ImageBytes = *fields.ImageBytes
if fields.Images == nil {
return fmt.Errorf("decode KittyGraphicsState: required field images is missing or null")
}
decoded.Images = *fields.Images
if fields.InflightBytes == nil {
return fmt.Errorf("decode KittyGraphicsState: required field inflight_bytes is missing or null")
}
decoded.InflightBytes = *fields.InflightBytes
if fields.Placements == nil {
return fmt.Errorf("decode KittyGraphicsState: required field placements is missing or null")
}
decoded.Placements = *fields.Placements
if fields.PrimaryNextImageID == nil {
return fmt.Errorf("decode KittyGraphicsState: required field primary_next_image_id is missing or null")
}
decoded.PrimaryNextImageID = *fields.PrimaryNextImageID
if fields.PrimaryReplayNextImageID == nil {
return fmt.Errorf("decode KittyGraphicsState: required field primary_replay_next_image_id is missing or null")
}
decoded.PrimaryReplayNextImageID = *fields.PrimaryReplayNextImageID
if fields.ReplayCursorOffset == nil {
return fmt.Errorf("decode KittyGraphicsState: required field replay_cursor_offset is missing or null")
}
decoded.ReplayCursorOffset = *fields.ReplayCursorOffset
*value = KittyGraphicsState(decoded)
return nil
}
type KittyImageAlias struct {
ImageID uint32 `json:"image_id"`
ImageNumber uint32 `json:"image_number"`
}
func (value *KittyImageAlias) UnmarshalJSON(data []byte) error {
if !isJSONObject(data) {
return fmt.Errorf("decode KittyImageAlias: expected object")
}
var fields struct {
ImageID *uint32 `json:"image_id"`
ImageNumber *uint32 `json:"image_number"`
}
if err := json.Unmarshal(data, &fields); err != nil {
return fmt.Errorf("decode KittyImageAlias: %w", err)
}
type wire KittyImageAlias
var decoded wire
if fields.ImageID == nil {
return fmt.Errorf("decode KittyImageAlias: required field image_id is missing or null")
}
decoded.ImageID = *fields.ImageID
if fields.ImageNumber == nil {
return fmt.Errorf("decode KittyImageAlias: required field image_number is missing or null")
}
decoded.ImageNumber = *fields.ImageNumber
*value = KittyImageAlias(decoded)
return nil
}
type LayoutLeafType string
const (
@@ -3343,6 +3517,7 @@ func (value *ReadScreenResult) UnmarshalJSON(data []byte) error {
}
type ReadScrollbackResult struct {
Epoch uint64 `json:"epoch"`
Rows []RenderRow `json:"rows"`
Start uint32 `json:"start"`
Total uint32 `json:"total"`
@@ -3353,6 +3528,7 @@ func (value *ReadScrollbackResult) UnmarshalJSON(data []byte) error {
return fmt.Errorf("decode ReadScrollbackResult: expected object")
}
var fields struct {
Epoch *uint64 `json:"epoch"`
Rows *[]RenderRow `json:"rows"`
Start *uint32 `json:"start"`
Total *uint32 `json:"total"`
@@ -3362,6 +3538,10 @@ func (value *ReadScrollbackResult) UnmarshalJSON(data []byte) error {
}
type wire ReadScrollbackResult
var decoded wire
if fields.Epoch == nil {
return fmt.Errorf("decode ReadScrollbackResult: required field epoch is missing or null")
}
decoded.Epoch = *fields.Epoch
if fields.Rows == nil {
return fmt.Errorf("decode ReadScrollbackResult: required field rows is missing or null")
}
@@ -3458,6 +3638,338 @@ func (value *RenderCursor) UnmarshalJSON(data []byte) error {
return nil
}
type RenderGraphicFormat string
const (
RenderGraphicFormatRGB RenderGraphicFormat = "rgb"
RenderGraphicFormatRgba RenderGraphicFormat = "rgba"
)
func (value RenderGraphicFormat) valid() bool {
switch value {
case RenderGraphicFormatRGB, RenderGraphicFormatRgba:
return true
default:
return false
}
}
func (value RenderGraphicFormat) MarshalJSON() ([]byte, error) {
if !value.valid() {
return nil, fmt.Errorf("%s has invalid value %v", "RenderGraphicFormat", value)
}
return json.Marshal(string(value))
}
func (value *RenderGraphicFormat) UnmarshalJSON(data []byte) error {
var decoded string
if err := json.Unmarshal(data, &decoded); err != nil {
return err
}
candidate := RenderGraphicFormat(decoded)
if !candidate.valid() {
return fmt.Errorf("%s has invalid value %v", "RenderGraphicFormat", decoded)
}
*value = candidate
return nil
}
type RenderGraphicImage struct {
Data Base64 `json:"data"`
Format RenderGraphicFormat `json:"format"`
Generation uint64 `json:"generation"`
Height uint32 `json:"height"`
ID uint32 `json:"id"`
Width uint32 `json:"width"`
}
func (value *RenderGraphicImage) UnmarshalJSON(data []byte) error {
if !isJSONObject(data) {
return fmt.Errorf("decode RenderGraphicImage: expected object")
}
var fields struct {
Data *Base64 `json:"data"`
Format *RenderGraphicFormat `json:"format"`
Generation *uint64 `json:"generation"`
Height *uint32 `json:"height"`
ID *uint32 `json:"id"`
Width *uint32 `json:"width"`
}
if err := json.Unmarshal(data, &fields); err != nil {
return fmt.Errorf("decode RenderGraphicImage: %w", err)
}
type wire RenderGraphicImage
var decoded wire
if fields.Data == nil {
return fmt.Errorf("decode RenderGraphicImage: required field data is missing or null")
}
decoded.Data = *fields.Data
if fields.Format == nil {
return fmt.Errorf("decode RenderGraphicImage: required field format is missing or null")
}
decoded.Format = *fields.Format
if fields.Generation == nil {
return fmt.Errorf("decode RenderGraphicImage: required field generation is missing or null")
}
decoded.Generation = *fields.Generation
if fields.Height == nil {
return fmt.Errorf("decode RenderGraphicImage: required field height is missing or null")
}
decoded.Height = *fields.Height
if fields.ID == nil {
return fmt.Errorf("decode RenderGraphicImage: required field id is missing or null")
}
decoded.ID = *fields.ID
if fields.Width == nil {
return fmt.Errorf("decode RenderGraphicImage: required field width is missing or null")
}
decoded.Width = *fields.Width
*value = RenderGraphicImage(decoded)
return nil
}
type RenderGraphicPlacement struct {
AnchorCol *uint16 `json:"anchor_col,omitempty"`
AnchorRow *uint32 `json:"anchor_row,omitempty"`
Columns uint32 `json:"columns"`
GridCols uint32 `json:"grid_cols"`
GridRows uint32 `json:"grid_rows"`
ImageID uint32 `json:"image_id"`
Ordinal uint32 `json:"ordinal"`
PixelHeight uint32 `json:"pixel_height"`
PixelWidth uint32 `json:"pixel_width"`
PlacementID uint32 `json:"placement_id"`
Rows uint32 `json:"rows"`
SourceHeight uint32 `json:"source_height"`
SourceWidth uint32 `json:"source_width"`
SourceX uint32 `json:"source_x"`
SourceY uint32 `json:"source_y"`
ViewportCol int32 `json:"viewport_col"`
ViewportRow int32 `json:"viewport_row"`
ViewportVisible bool `json:"viewport_visible"`
XOffset uint32 `json:"x_offset"`
YOffset uint32 `json:"y_offset"`
Z int32 `json:"z"`
}
func (value *RenderGraphicPlacement) UnmarshalJSON(data []byte) error {
if !isJSONObject(data) {
return fmt.Errorf("decode RenderGraphicPlacement: expected object")
}
var fields struct {
AnchorCol optionalNonNullJSON[uint16] `json:"anchor_col"`
AnchorRow optionalNonNullJSON[uint32] `json:"anchor_row"`
Columns *uint32 `json:"columns"`
GridCols *uint32 `json:"grid_cols"`
GridRows *uint32 `json:"grid_rows"`
ImageID *uint32 `json:"image_id"`
Ordinal *uint32 `json:"ordinal"`
PixelHeight *uint32 `json:"pixel_height"`
PixelWidth *uint32 `json:"pixel_width"`
PlacementID *uint32 `json:"placement_id"`
Rows *uint32 `json:"rows"`
SourceHeight *uint32 `json:"source_height"`
SourceWidth *uint32 `json:"source_width"`
SourceX *uint32 `json:"source_x"`
SourceY *uint32 `json:"source_y"`
ViewportCol *int32 `json:"viewport_col"`
ViewportRow *int32 `json:"viewport_row"`
ViewportVisible *bool `json:"viewport_visible"`
XOffset *uint32 `json:"x_offset"`
YOffset *uint32 `json:"y_offset"`
Z *int32 `json:"z"`
}
if err := json.Unmarshal(data, &fields); err != nil {
return fmt.Errorf("decode RenderGraphicPlacement: %w", err)
}
type wire RenderGraphicPlacement
var decoded wire
if fields.AnchorCol.null {
return fmt.Errorf("decode RenderGraphicPlacement: non-nullable field anchor_col is null")
}
if fields.AnchorCol.set {
decoded.AnchorCol = &fields.AnchorCol.value
}
if fields.AnchorRow.null {
return fmt.Errorf("decode RenderGraphicPlacement: non-nullable field anchor_row is null")
}
if fields.AnchorRow.set {
decoded.AnchorRow = &fields.AnchorRow.value
}
if fields.Columns == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field columns is missing or null")
}
decoded.Columns = *fields.Columns
if fields.GridCols == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field grid_cols is missing or null")
}
decoded.GridCols = *fields.GridCols
if fields.GridRows == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field grid_rows is missing or null")
}
decoded.GridRows = *fields.GridRows
if fields.ImageID == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field image_id is missing or null")
}
decoded.ImageID = *fields.ImageID
if fields.Ordinal == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field ordinal is missing or null")
}
decoded.Ordinal = *fields.Ordinal
if fields.PixelHeight == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field pixel_height is missing or null")
}
decoded.PixelHeight = *fields.PixelHeight
if fields.PixelWidth == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field pixel_width is missing or null")
}
decoded.PixelWidth = *fields.PixelWidth
if fields.PlacementID == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field placement_id is missing or null")
}
decoded.PlacementID = *fields.PlacementID
if fields.Rows == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field rows is missing or null")
}
decoded.Rows = *fields.Rows
if fields.SourceHeight == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field source_height is missing or null")
}
decoded.SourceHeight = *fields.SourceHeight
if fields.SourceWidth == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field source_width is missing or null")
}
decoded.SourceWidth = *fields.SourceWidth
if fields.SourceX == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field source_x is missing or null")
}
decoded.SourceX = *fields.SourceX
if fields.SourceY == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field source_y is missing or null")
}
decoded.SourceY = *fields.SourceY
if fields.ViewportCol == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field viewport_col is missing or null")
}
decoded.ViewportCol = *fields.ViewportCol
if fields.ViewportRow == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field viewport_row is missing or null")
}
decoded.ViewportRow = *fields.ViewportRow
if fields.ViewportVisible == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field viewport_visible is missing or null")
}
decoded.ViewportVisible = *fields.ViewportVisible
if fields.XOffset == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field x_offset is missing or null")
}
decoded.XOffset = *fields.XOffset
if fields.YOffset == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field y_offset is missing or null")
}
decoded.YOffset = *fields.YOffset
if fields.Z == nil {
return fmt.Errorf("decode RenderGraphicPlacement: required field z is missing or null")
}
decoded.Z = *fields.Z
*value = RenderGraphicPlacement(decoded)
return nil
}
type RenderGraphics struct {
Generation uint64 `json:"generation"`
Images *[]RenderGraphicImage `json:"images,omitempty"`
Placements []RenderGraphicPlacement `json:"placements"`
RemovedImageIDs *[]uint32 `json:"removed_image_ids,omitempty"`
}
func (value *RenderGraphics) UnmarshalJSON(data []byte) error {
if !isJSONObject(data) {
return fmt.Errorf("decode RenderGraphics: expected object")
}
var fields struct {
Generation *uint64 `json:"generation"`
Images optionalNonNullJSON[[]RenderGraphicImage] `json:"images"`
Placements *[]RenderGraphicPlacement `json:"placements"`
RemovedImageIDs optionalNonNullJSON[[]uint32] `json:"removed_image_ids"`
}
if err := json.Unmarshal(data, &fields); err != nil {
return fmt.Errorf("decode RenderGraphics: %w", err)
}
type wire RenderGraphics
var decoded wire
if fields.Generation == nil {
return fmt.Errorf("decode RenderGraphics: required field generation is missing or null")
}
decoded.Generation = *fields.Generation
if fields.Images.null {
return fmt.Errorf("decode RenderGraphics: non-nullable field images is null")
}
if fields.Images.set {
decoded.Images = &fields.Images.value
}
if fields.Placements == nil {
return fmt.Errorf("decode RenderGraphics: required field placements is missing or null")
}
decoded.Placements = *fields.Placements
if fields.RemovedImageIDs.null {
return fmt.Errorf("decode RenderGraphics: non-nullable field removed_image_ids is null")
}
if fields.RemovedImageIDs.set {
decoded.RemovedImageIDs = &fields.RemovedImageIDs.value
}
*value = RenderGraphics(decoded)
return nil
}
type RenderGraphicsDelta struct {
Generation uint64 `json:"generation"`
Images *[]RenderGraphicImage `json:"images,omitempty"`
Placements *[]RenderGraphicPlacement `json:"placements,omitempty"`
RemovedImageIDs *[]uint32 `json:"removed_image_ids,omitempty"`
}
func (value *RenderGraphicsDelta) UnmarshalJSON(data []byte) error {
if !isJSONObject(data) {
return fmt.Errorf("decode RenderGraphicsDelta: expected object")
}
var fields struct {
Generation *uint64 `json:"generation"`
Images optionalNonNullJSON[[]RenderGraphicImage] `json:"images"`
Placements optionalNonNullJSON[[]RenderGraphicPlacement] `json:"placements"`
RemovedImageIDs optionalNonNullJSON[[]uint32] `json:"removed_image_ids"`
}
if err := json.Unmarshal(data, &fields); err != nil {
return fmt.Errorf("decode RenderGraphicsDelta: %w", err)
}
type wire RenderGraphicsDelta
var decoded wire
if fields.Generation == nil {
return fmt.Errorf("decode RenderGraphicsDelta: required field generation is missing or null")
}
decoded.Generation = *fields.Generation
if fields.Images.null {
return fmt.Errorf("decode RenderGraphicsDelta: non-nullable field images is null")
}
if fields.Images.set {
decoded.Images = &fields.Images.value
}
if fields.Placements.null {
return fmt.Errorf("decode RenderGraphicsDelta: non-nullable field placements is null")
}
if fields.Placements.set {
decoded.Placements = &fields.Placements.value
}
if fields.RemovedImageIDs.null {
return fmt.Errorf("decode RenderGraphicsDelta: non-nullable field removed_image_ids is null")
}
if fields.RemovedImageIDs.set {
decoded.RemovedImageIDs = &fields.RemovedImageIDs.value
}
*value = RenderGraphicsDelta(decoded)
return nil
}
type RenderRow struct {
Row uint32 `json:"row"`
Runs []RenderRun `json:"runs"`
@@ -5756,9 +6268,11 @@ func (value *Tree) UnmarshalJSON(data []byte) error {
}
type VTStateResult struct {
Cols uint16 `json:"cols"`
Data Base64 `json:"data"`
Rows uint16 `json:"rows"`
Cols uint16 `json:"cols"`
Data Base64 `json:"data"`
KittyGraphicsState *KittyGraphicsState `json:"kitty_graphics_state,omitempty"`
KittyImageAliases *[]KittyImageAlias `json:"kitty_image_aliases,omitempty"`
Rows uint16 `json:"rows"`
}
func (value *VTStateResult) UnmarshalJSON(data []byte) error {
@@ -5766,9 +6280,11 @@ func (value *VTStateResult) UnmarshalJSON(data []byte) error {
return fmt.Errorf("decode VTStateResult: expected object")
}
var fields struct {
Cols *uint16 `json:"cols"`
Data *Base64 `json:"data"`
Rows *uint16 `json:"rows"`
Cols *uint16 `json:"cols"`
Data *Base64 `json:"data"`
KittyGraphicsState optionalNonNullJSON[KittyGraphicsState] `json:"kitty_graphics_state"`
KittyImageAliases optionalNonNullJSON[[]KittyImageAlias] `json:"kitty_image_aliases"`
Rows *uint16 `json:"rows"`
}
if err := json.Unmarshal(data, &fields); err != nil {
return fmt.Errorf("decode VTStateResult: %w", err)
@@ -5783,6 +6299,18 @@ func (value *VTStateResult) UnmarshalJSON(data []byte) error {
return fmt.Errorf("decode VTStateResult: required field data is missing or null")
}
decoded.Data = *fields.Data
if fields.KittyGraphicsState.null {
return fmt.Errorf("decode VTStateResult: non-nullable field kitty_graphics_state is null")
}
if fields.KittyGraphicsState.set {
decoded.KittyGraphicsState = &fields.KittyGraphicsState.value
}
if fields.KittyImageAliases.null {
return fmt.Errorf("decode VTStateResult: non-nullable field kitty_image_aliases is null")
}
if fields.KittyImageAliases.set {
decoded.KittyImageAliases = &fields.KittyImageAliases.value
}
if fields.Rows == nil {
return fmt.Errorf("decode VTStateResult: required field rows is missing or null")
}
@@ -170,6 +170,11 @@
"sha256": "154b12d0dfa1f306a9bcaf0e40d98ca2a9986ad4091e1ef5b86779919f43df21",
"size": 3964
},
{
"path": "CellPixelSurface.java",
"sha256": "1c8c5bfa39d949770a5a6ceafd4e95b3bc383ea59b6d05319019e742c044b828",
"size": 3300
},
{
"path": "ClearHistoryRequest.java",
"sha256": "ffc80cfcdf28e7c70a8caf1416776000f2dcb237635c9d163101d65868dd9c8f",
@@ -267,8 +272,8 @@
},
{
"path": "Commands.java",
"sha256": "668e659ba7dc3e14d484019a6b09853ecfafc10834e622f554b49f47064c2899",
"size": 21774
"sha256": "de0a2a553db5edba17b4b28c472059e9403dd407b4ea0d93ca3e2d59114f5ce2",
"size": 21994
},
{
"path": "ConfigReloadRequestedEvent.java",
@@ -367,8 +372,8 @@
},
{
"path": "Events.java",
"sha256": "2882c85325083df5043f7421143c35ef6f749d6b2ba108cbdbe104dcea8ea649",
"size": 8671
"sha256": "ea4d3a5388d173e2a5a132e6e44deda398f091f867c4aec4481c8719cdde80af",
"size": 8859
},
{
"path": "ExportLayoutRequest.java",
@@ -417,14 +422,34 @@
},
{
"path": "GeneratedCmuxClient.java",
"sha256": "ccdfa230076d713f61870a015464ac4612019cd70b71ca679d05f3bf74814393",
"size": 21121
"sha256": "7fca834f19533a0fb525d2bbac09930f5aad1a018035d60035b8ce170a1d9664",
"size": 21326
},
{
"path": "GetCellPixelsRequest.java",
"sha256": "bdd5e6d52e1c900f523cc688ff381e91f0566b5fc6a822ce824b84787257dc0c",
"size": 1369
},
{
"path": "GetCellPixelsResult.java",
"sha256": "d29e71cb2e5f6ada5e49e14496576397f67f5a202cb042fadd48555f19333fa1",
"size": 3473
},
{
"path": "GetFrontendProjectionRequest.java",
"sha256": "f5ed502bdb2612a465c16ff64056d8f9b4f17bc4b093c02718fdc605d7a1b8b9",
"size": 3602
},
{
"path": "GraphicsStatusEvent.java",
"sha256": "a53bd8d72dd6c0b4b618d1c9bc43aa15840ee6b18daa65de41221b8e7422a506",
"size": 6890
},
{
"path": "GraphicsStatusEventKind.java",
"sha256": "47503857ebc1b24813505e9f47dad13f23477bac6a38c90887be3bf3dd70f52c",
"size": 1135
},
{
"path": "IdMapping.java",
"sha256": "e35ddb332efca399d079c362172f47ba1053f917077d86c3ef62652c254b2403",
@@ -460,6 +485,16 @@
"sha256": "e7d564deb5069396421c91febce6d1ac41475c2440efc3f21e58926241f6f625",
"size": 1873
},
{
"path": "KittyGraphicsState.java",
"sha256": "0e848438699ec59e5e350eb366300d0013ebc245d8d68adfb57f5b36fe092e80",
"size": 9264
},
{
"path": "KittyImageAlias.java",
"sha256": "24f3178aefaad9b9bc8bb7c0116c5bda9b8e52fc9a873060f9e8a1b1f0167d4f",
"size": 2661
},
{
"path": "Layout.java",
"sha256": "648d2528387849a64a3827238cabdaa776471db4bd346d8d1ef54fd80b56be26",
@@ -702,8 +737,8 @@
},
{
"path": "Protocol.java",
"sha256": "f9b6177545a0721b7c47ca110ba9a42d79c787472c26bffa6486e8c3f24ad9ad",
"size": 3892
"sha256": "fa738484567eb60411b02de02f126c89473a8a9ab7d1bce1a10b9b8d4a1b7423",
"size": 3967
},
{
"path": "ProtocolEvent.java",
@@ -742,8 +777,8 @@
},
{
"path": "ReadScrollbackResult.java",
"sha256": "0d14e6942c5086a0c42367870cde40fe6a1dbd9941f79b361907729c63f127be",
"size": 3239
"sha256": "a0cb7c51adc7de08e1e8ad0fd4f42be2ecc8e6afbe738205af9253a190a6c5b5",
"size": 3892
},
{
"path": "ReleaseSurfaceSizeRequest.java",
@@ -797,8 +832,33 @@
},
{
"path": "RenderDeltaEvent.java",
"sha256": "da32ee758213d771b1dff5499ba5d2705d23745b46050271628daaa95983a607",
"size": 6832
"sha256": "891813611f010ed7701f8121921010732d71be3b6ddc41e17f7468cc01e34960",
"size": 8213
},
{
"path": "RenderGraphicFormat.java",
"sha256": "2a9287077f3ac37fb0eeea11551193c560976d79e2511f43871fef4cbe4018db",
"size": 909
},
{
"path": "RenderGraphicImage.java",
"sha256": "d2d3211bfdd32b677e9f73aa9c6b9efdcbf72389a85b6da6e35a714395e356a5",
"size": 5186
},
{
"path": "RenderGraphicPlacement.java",
"sha256": "1d5f8f7f12e9929a8c4918aa5b45ead129aa74e7caca707b51038ae417d64b81",
"size": 16010
},
{
"path": "RenderGraphics.java",
"sha256": "1abf863d205ff1a359a7f65b83c51e9cb711067d98f445765d4de774d01e3be2",
"size": 4528
},
{
"path": "RenderGraphicsDelta.java",
"sha256": "f4b51dce1b732e072e901a0ed0559f2be276eb17b0ebdf6b747871b6aeec7f93",
"size": 4518
},
{
"path": "RenderRow.java",
@@ -812,8 +872,8 @@
},
{
"path": "RenderStateEvent.java",
"sha256": "b53dea205aea0d62cfae1853c87c8ab7cfd98f8b94d7401789cdefb51e5570f5",
"size": 6563
"sha256": "01c4a4e5fc08242bef40fd80de2ae2fcd77d14d8c9bd994c7584bb936294f4d6",
"size": 8024
},
{
"path": "RenderUnderline.java",
@@ -842,8 +902,8 @@
},
{
"path": "ResizedEvent.java",
"sha256": "718ad6b4b0c97ae4a2f9e32bd36d1716c185e7844a7b269b31ab0412a0942c1a",
"size": 5172
"sha256": "c47b105665396450dcc1372287890933d9d8d087cbca29028a79214b5b8c34ba",
"size": 6958
},
{
"path": "ResolveTerminalRequest.java",
@@ -1177,8 +1237,8 @@
},
{
"path": "VtStateEvent.java",
"sha256": "2870f38755bb8bc0222f7960e260513130b926161379acb2f1a43c5c49b5d380",
"size": 4620
"sha256": "32984fa6573b5d89371f1c5de476773ad2258c265156b69ecaabf8946bd78d54",
"size": 6406
},
{
"path": "VtStateRequest.java",
@@ -1187,8 +1247,8 @@
},
{
"path": "VtStateResult.java",
"sha256": "cc15f7f7c05155a2b424994690b33feca62024e3613e5006a7b0b179cd4fcf05",
"size": 3031
"sha256": "27507e2d0f1e9273bcdc7264a7f6ab529204ee04ab7df1aa0e3255d62c4a5951",
"size": 4818
},
{
"path": "WaitForRequest.java",
@@ -1252,7 +1312,7 @@
}
],
"format": 1,
"ir_sha256": "4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e",
"ir_sha256": "17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350",
"language": "java",
"mux_protocol": 10,
"schema_version": 2
@@ -0,0 +1,91 @@
// Generated from cmux-tui/spec/sdk-schema.json. DO NOT EDIT.
package com.cmux.raw;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public final class CellPixelSurface implements WireValue {
private final int heightPx;
private final UInt64 surface;
private final int widthPx;
private CellPixelSurface(Builder builder) {
if (!builder.heightPxSet) throw new IllegalArgumentException("height_px is required");
this.heightPx = builder.heightPx;
if (!builder.surfaceSet) throw new IllegalArgumentException("surface is required");
this.surface = Wire.nonNull(builder.surface, "surface");
if (!builder.widthPxSet) throw new IllegalArgumentException("width_px is required");
this.widthPx = builder.widthPx;
}
public static Builder builder() { return new Builder(); }
public int heightPx() { return heightPx; }
public UInt64 surface() { return surface; }
public int widthPx() { return widthPx; }
public static CellPixelSurface fromWire(Object value) {
Map<String, Object> object = Wire.object(value, "CellPixelSurface");
Builder builder = builder();
Object rawHeightPx = Wire.required(object, "height_px");
builder.heightPx(Wire.uint16(rawHeightPx, "CellPixelSurface.height_px"));
Object rawSurface = Wire.required(object, "surface");
builder.surface(Wire.uint64(rawSurface, "CellPixelSurface.surface"));
Object rawWidthPx = Wire.required(object, "width_px");
builder.widthPx(Wire.uint16(rawWidthPx, "CellPixelSurface.width_px"));
return builder.build();
}
@Override
public Map<String, Object> toWire() {
LinkedHashMap<String, Object> object = new LinkedHashMap<>();
Wire.put(object, "height_px", heightPx);
Wire.put(object, "surface", surface);
Wire.put(object, "width_px", widthPx);
return Collections.unmodifiableMap(object);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof CellPixelSurface that)) return false;
return Objects.equals(heightPx, that.heightPx) && Objects.equals(surface, that.surface) && Objects.equals(widthPx, that.widthPx);
}
@Override
public int hashCode() { return Objects.hash(heightPx, surface, widthPx); }
@Override
public String toString() { return "CellPixelSurface" + toWire(); }
public static final class Builder {
private Integer heightPx;
private boolean heightPxSet;
private UInt64 surface;
private boolean surfaceSet;
private Integer widthPx;
private boolean widthPxSet;
public Builder heightPx(int value) {
this.heightPx = value;
this.heightPxSet = true;
return this;
}
public Builder surface(UInt64 value) {
this.surface = value;
this.surfaceSet = true;
return this;
}
public Builder widthPx(int value) {
this.widthPx = value;
this.widthPxSet = true;
return this;
}
public CellPixelSurface build() { return new CellPixelSurface(this); }
}
}
@@ -40,6 +40,7 @@ public final class Commands {
public static final CommandMetadata EXPORT_LAYOUT = new CommandMetadata("export-layout", Authority.CONTROL, 6, null, StreamKind.NONE, Map.of(), Map.of());
public static final CommandMetadata FOCUS_DIRECTION = new CommandMetadata("focus-direction", Authority.CONTROL, 6, null, StreamKind.NONE, Map.of(), Map.of());
public static final CommandMetadata FOCUS_PANE = new CommandMetadata("focus-pane", Authority.CONTROL, 5, null, StreamKind.NONE, Map.of(), Map.of());
public static final CommandMetadata GET_CELL_PIXELS = new CommandMetadata("get-cell-pixels", Authority.FRONTEND, 6, null, StreamKind.NONE, Map.of(), Map.of());
public static final CommandMetadata GET_FRONTEND_PROJECTION = new CommandMetadata("get-frontend-projection", Authority.CONTROL, 7, null, StreamKind.NONE, Map.of(), Map.of());
public static final CommandMetadata IDENTIFY = new CommandMetadata("identify", Authority.CONTROL, 5, null, StreamKind.NONE, Map.of(), Map.of());
public static final CommandMetadata IDS = new CommandMetadata("ids", Authority.CONTROL, 6, null, StreamKind.NONE, Map.of(), Map.of());
@@ -135,6 +136,7 @@ public final class Commands {
values.put("export-layout", EXPORT_LAYOUT);
values.put("focus-direction", FOCUS_DIRECTION);
values.put("focus-pane", FOCUS_PANE);
values.put("get-cell-pixels", GET_CELL_PIXELS);
values.put("get-frontend-projection", GET_FRONTEND_PROJECTION);
values.put("identify", IDENTIFY);
values.put("ids", IDS);
@@ -23,6 +23,7 @@ public final class Events {
public static final EventMetadata EMPTY = new EventMetadata("empty", 5, null, List.of("subscribe"), true);
public static final EventMetadata FRAME = new EventMetadata("frame", 6, null, List.of("attach-browser"), true);
public static final EventMetadata FRONTEND_PROJECTION_CHANGED = new EventMetadata("frontend-projection-changed", 7, null, List.of("subscribe"), true);
public static final EventMetadata GRAPHICS_STATUS = new EventMetadata("graphics-status", 10, null, List.of("subscribe"), true);
public static final EventMetadata LAYOUT_CHANGED = new EventMetadata("layout-changed", 6, null, List.of("subscribe"), true);
public static final EventMetadata NOTIFICATION = new EventMetadata("notification", 6, null, List.of("subscribe", "attach-byte", "attach-browser"), true);
public static final EventMetadata OUTPUT = new EventMetadata("output", 5, null, List.of("attach-byte"), true);
@@ -71,6 +72,7 @@ public final class Events {
values.put("empty", EMPTY);
values.put("frame", FRAME);
values.put("frontend-projection-changed", FRONTEND_PROJECTION_CHANGED);
values.put("graphics-status", GRAPHICS_STATUS);
values.put("layout-changed", LAYOUT_CHANGED);
values.put("notification", NOTIFICATION);
values.put("output", OUTPUT);
@@ -163,6 +163,11 @@ public abstract class GeneratedCmuxClient {
return EmptyResult.fromWire(result);
}
public final GetCellPixelsResult getCellPixels() throws CmuxException {
Object result = execute(Commands.GET_CELL_PIXELS, Map.of());
return GetCellPixelsResult.fromWire(result);
}
public final FrontendProjection getFrontendProjection(GetFrontendProjectionRequest request) throws CmuxException {
Object result = execute(Commands.GET_FRONTEND_PROJECTION, request.toWire());
return FrontendProjection.fromWire(result);
@@ -0,0 +1,50 @@
// Generated from cmux-tui/spec/sdk-schema.json. DO NOT EDIT.
package com.cmux.raw;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/** Immutable get-cell-pixels request. Protocol v6; authority: frontend. */
public final class GetCellPixelsRequest implements WireValue {
private GetCellPixelsRequest(Builder builder) {
}
public static Builder builder() { return new Builder(); }
public static GetCellPixelsRequest fromWire(Object value) {
Map<String, Object> object = Wire.object(value, "GetCellPixelsRequest");
Builder builder = builder();
return builder.build();
}
@Override
public Map<String, Object> toWire() {
LinkedHashMap<String, Object> object = new LinkedHashMap<>();
return Collections.unmodifiableMap(object);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof GetCellPixelsRequest that)) return false;
return true;
}
@Override
public int hashCode() { return Objects.hash(); }
@Override
public String toString() { return "GetCellPixelsRequest" + toWire(); }
public static final class Builder {
public GetCellPixelsRequest build() { return new GetCellPixelsRequest(this); }
}
}
@@ -0,0 +1,91 @@
// Generated from cmux-tui/spec/sdk-schema.json. DO NOT EDIT.
package com.cmux.raw;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public final class GetCellPixelsResult implements WireValue {
private final int heightPx;
private final List<CellPixelSurface> surfaces;
private final int widthPx;
private GetCellPixelsResult(Builder builder) {
if (!builder.heightPxSet) throw new IllegalArgumentException("height_px is required");
this.heightPx = builder.heightPx;
if (!builder.surfacesSet) throw new IllegalArgumentException("surfaces is required");
this.surfaces = List.copyOf(Wire.nonNull(builder.surfaces, "surfaces"));
if (!builder.widthPxSet) throw new IllegalArgumentException("width_px is required");
this.widthPx = builder.widthPx;
}
public static Builder builder() { return new Builder(); }
public int heightPx() { return heightPx; }
public List<CellPixelSurface> surfaces() { return surfaces; }
public int widthPx() { return widthPx; }
public static GetCellPixelsResult fromWire(Object value) {
Map<String, Object> object = Wire.object(value, "GetCellPixelsResult");
Builder builder = builder();
Object rawHeightPx = Wire.required(object, "height_px");
builder.heightPx(Wire.uint16(rawHeightPx, "GetCellPixelsResult.height_px"));
Object rawSurfaces = Wire.required(object, "surfaces");
builder.surfaces(Wire.array(rawSurfaces, "GetCellPixelsResult.surfaces", item -> CellPixelSurface.fromWire(item)));
Object rawWidthPx = Wire.required(object, "width_px");
builder.widthPx(Wire.uint16(rawWidthPx, "GetCellPixelsResult.width_px"));
return builder.build();
}
@Override
public Map<String, Object> toWire() {
LinkedHashMap<String, Object> object = new LinkedHashMap<>();
Wire.put(object, "height_px", heightPx);
Wire.put(object, "surfaces", surfaces);
Wire.put(object, "width_px", widthPx);
return Collections.unmodifiableMap(object);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof GetCellPixelsResult that)) return false;
return Objects.equals(heightPx, that.heightPx) && Objects.equals(surfaces, that.surfaces) && Objects.equals(widthPx, that.widthPx);
}
@Override
public int hashCode() { return Objects.hash(heightPx, surfaces, widthPx); }
@Override
public String toString() { return "GetCellPixelsResult" + toWire(); }
public static final class Builder {
private Integer heightPx;
private boolean heightPxSet;
private List<CellPixelSurface> surfaces;
private boolean surfacesSet;
private Integer widthPx;
private boolean widthPxSet;
public Builder heightPx(int value) {
this.heightPx = value;
this.heightPxSet = true;
return this;
}
public Builder surfaces(List<CellPixelSurface> value) {
this.surfaces = value;
this.surfacesSet = true;
return this;
}
public Builder widthPx(int value) {
this.widthPx = value;
this.widthPxSet = true;
return this;
}
public GetCellPixelsResult build() { return new GetCellPixelsResult(this); }
}
}
@@ -0,0 +1,158 @@
// Generated from cmux-tui/spec/sdk-schema.json. DO NOT EDIT.
package com.cmux.raw;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/** Immutable graphics-status event. Protocol v10; streams: subscribe. */
public final class GraphicsStatusEvent implements WireValue, DeltaStreamEvent, ProtocolEvent, SubscribeEvent {
private final Field<Integer> attempts;
private final Field<Integer> cellHeight;
private final Field<Integer> cellWidth;
private final Field<String> error;
private final GraphicsStatusEventKind kind;
private final Field<UInt64> remaining;
private final Field<Boolean> retryExhausted;
private final Field<String> summary;
private GraphicsStatusEvent(Builder builder) {
this.attempts = builder.attempts;
this.cellHeight = builder.cellHeight;
this.cellWidth = builder.cellWidth;
this.error = builder.error;
if (!builder.kindSet) throw new IllegalArgumentException("kind is required");
this.kind = Wire.nonNull(builder.kind, "kind");
this.remaining = builder.remaining;
this.retryExhausted = builder.retryExhausted;
this.summary = builder.summary;
}
public static Builder builder() { return new Builder(); }
public Field<Integer> attempts() { return attempts; }
public Field<Integer> cellHeight() { return cellHeight; }
public Field<Integer> cellWidth() { return cellWidth; }
public Field<String> error() { return error; }
public GraphicsStatusEventKind kind() { return kind; }
public Field<UInt64> remaining() { return remaining; }
public Field<Boolean> retryExhausted() { return retryExhausted; }
public Field<String> summary() { return summary; }
@Override public String event() { return "graphics-status"; }
public static GraphicsStatusEvent fromWire(Object value) {
Map<String, Object> object = Wire.object(value, "GraphicsStatusEvent");
Builder builder = builder();
ProtocolSupport.literal(Wire.required(object, "event"), "graphics-status", "GraphicsStatusEvent.event");
Object rawAttempts = Wire.optional(object, "attempts");
if (!Wire.isMissing(rawAttempts)) {
builder.attempts(Wire.uint16(rawAttempts, "GraphicsStatusEvent.attempts"));
}
Object rawCellHeight = Wire.optional(object, "cell_height");
if (!Wire.isMissing(rawCellHeight)) {
builder.cellHeight(Wire.uint16(rawCellHeight, "GraphicsStatusEvent.cell_height"));
}
Object rawCellWidth = Wire.optional(object, "cell_width");
if (!Wire.isMissing(rawCellWidth)) {
builder.cellWidth(Wire.uint16(rawCellWidth, "GraphicsStatusEvent.cell_width"));
}
Object rawError = Wire.optional(object, "error");
if (!Wire.isMissing(rawError)) {
builder.error(Wire.string(rawError, "GraphicsStatusEvent.error"));
}
Object rawKind = Wire.required(object, "kind");
builder.kind(GraphicsStatusEventKind.fromWire(rawKind));
Object rawRemaining = Wire.optional(object, "remaining");
if (!Wire.isMissing(rawRemaining)) {
builder.remaining(Wire.uint64(rawRemaining, "GraphicsStatusEvent.remaining"));
}
Object rawRetryExhausted = Wire.optional(object, "retry_exhausted");
if (!Wire.isMissing(rawRetryExhausted)) {
builder.retryExhausted(Wire.bool(rawRetryExhausted, "GraphicsStatusEvent.retry_exhausted"));
}
Object rawSummary = Wire.optional(object, "summary");
if (!Wire.isMissing(rawSummary)) {
builder.summary(Wire.string(rawSummary, "GraphicsStatusEvent.summary"));
}
return builder.build();
}
@Override
public Map<String, Object> toWire() {
LinkedHashMap<String, Object> object = new LinkedHashMap<>();
object.put("event", "graphics-status");
Wire.put(object, "attempts", attempts);
Wire.put(object, "cell_height", cellHeight);
Wire.put(object, "cell_width", cellWidth);
Wire.put(object, "error", error);
Wire.put(object, "kind", kind);
Wire.put(object, "remaining", remaining);
Wire.put(object, "retry_exhausted", retryExhausted);
Wire.put(object, "summary", summary);
return Collections.unmodifiableMap(object);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof GraphicsStatusEvent that)) return false;
return Objects.equals(attempts, that.attempts) && Objects.equals(cellHeight, that.cellHeight) && Objects.equals(cellWidth, that.cellWidth) && Objects.equals(error, that.error) && Objects.equals(kind, that.kind) && Objects.equals(remaining, that.remaining) && Objects.equals(retryExhausted, that.retryExhausted) && Objects.equals(summary, that.summary);
}
@Override
public int hashCode() { return Objects.hash(attempts, cellHeight, cellWidth, error, kind, remaining, retryExhausted, summary); }
@Override
public String toString() { return "GraphicsStatusEvent" + toWire(); }
public static final class Builder {
private Field<Integer> attempts = Field.omitted();
private Field<Integer> cellHeight = Field.omitted();
private Field<Integer> cellWidth = Field.omitted();
private Field<String> error = Field.omitted();
private GraphicsStatusEventKind kind;
private boolean kindSet;
private Field<UInt64> remaining = Field.omitted();
private Field<Boolean> retryExhausted = Field.omitted();
private Field<String> summary = Field.omitted();
public Builder attempts(Integer value) {
this.attempts = Field.of(value);
return this;
}
public Builder cellHeight(Integer value) {
this.cellHeight = Field.of(value);
return this;
}
public Builder cellWidth(Integer value) {
this.cellWidth = Field.of(value);
return this;
}
public Builder error(String value) {
this.error = Field.of(value);
return this;
}
public Builder kind(GraphicsStatusEventKind value) {
this.kind = value;
this.kindSet = true;
return this;
}
public Builder remaining(UInt64 value) {
this.remaining = Field.of(value);
return this;
}
public Builder retryExhausted(Boolean value) {
this.retryExhausted = Field.of(value);
return this;
}
public Builder summary(String value) {
this.summary = Field.of(value);
return this;
}
public GraphicsStatusEvent build() { return new GraphicsStatusEvent(this); }
}
}
@@ -0,0 +1,35 @@
// Generated from cmux-tui/spec/sdk-schema.json. DO NOT EDIT.
package com.cmux.raw;
import java.util.Objects;
public enum GraphicsStatusEventKind implements WireEnum {
KITTY_IMAGE_BUDGET_WORKER_START_FAILED("kitty-image-budget-worker-start-failed"),
KITTY_IMAGE_BUDGET_UPDATE_FAILED("kitty-image-budget-update-failed"),
CELL_PIXEL_UPDATE_RETRIES_EXHAUSTED("cell-pixel-update-retries-exhausted");
private final Object wireValue;
GraphicsStatusEventKind(Object wireValue) {
this.wireValue = wireValue;
}
@Override
public String wireValue() {
return String.valueOf(wireValue);
}
public Object rawWireValue() {
return wireValue;
}
public static GraphicsStatusEventKind fromWire(Object value) {
for (GraphicsStatusEventKind candidate : values()) {
if (Objects.equals(candidate.wireValue, value)
|| Objects.equals(String.valueOf(candidate.wireValue), value)) {
return candidate;
}
}
throw new CmuxDecodeException("unknown GraphicsStatusEventKind value " + value, null);
}
}
@@ -0,0 +1,175 @@
// Generated from cmux-tui/spec/sdk-schema.json. DO NOT EDIT.
package com.cmux.raw;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public final class KittyGraphicsState implements WireValue {
private final long alternateNextImageId;
private final long alternateReplayNextImageId;
private final UInt64 imageBytes;
private final UInt64 images;
private final UInt64 inflightBytes;
private final UInt64 placements;
private final long primaryNextImageId;
private final long primaryReplayNextImageId;
private final long replayCursorOffset;
private KittyGraphicsState(Builder builder) {
if (!builder.alternateNextImageIdSet) throw new IllegalArgumentException("alternate_next_image_id is required");
this.alternateNextImageId = builder.alternateNextImageId;
if (!builder.alternateReplayNextImageIdSet) throw new IllegalArgumentException("alternate_replay_next_image_id is required");
this.alternateReplayNextImageId = builder.alternateReplayNextImageId;
if (!builder.imageBytesSet) throw new IllegalArgumentException("image_bytes is required");
this.imageBytes = Wire.nonNull(builder.imageBytes, "image_bytes");
if (!builder.imagesSet) throw new IllegalArgumentException("images is required");
this.images = Wire.nonNull(builder.images, "images");
if (!builder.inflightBytesSet) throw new IllegalArgumentException("inflight_bytes is required");
this.inflightBytes = Wire.nonNull(builder.inflightBytes, "inflight_bytes");
if (!builder.placementsSet) throw new IllegalArgumentException("placements is required");
this.placements = Wire.nonNull(builder.placements, "placements");
if (!builder.primaryNextImageIdSet) throw new IllegalArgumentException("primary_next_image_id is required");
this.primaryNextImageId = builder.primaryNextImageId;
if (!builder.primaryReplayNextImageIdSet) throw new IllegalArgumentException("primary_replay_next_image_id is required");
this.primaryReplayNextImageId = builder.primaryReplayNextImageId;
if (!builder.replayCursorOffsetSet) throw new IllegalArgumentException("replay_cursor_offset is required");
this.replayCursorOffset = builder.replayCursorOffset;
}
public static Builder builder() { return new Builder(); }
public long alternateNextImageId() { return alternateNextImageId; }
public long alternateReplayNextImageId() { return alternateReplayNextImageId; }
public UInt64 imageBytes() { return imageBytes; }
public UInt64 images() { return images; }
public UInt64 inflightBytes() { return inflightBytes; }
public UInt64 placements() { return placements; }
public long primaryNextImageId() { return primaryNextImageId; }
public long primaryReplayNextImageId() { return primaryReplayNextImageId; }
public long replayCursorOffset() { return replayCursorOffset; }
public static KittyGraphicsState fromWire(Object value) {
Map<String, Object> object = Wire.object(value, "KittyGraphicsState");
Builder builder = builder();
Object rawAlternateNextImageId = Wire.required(object, "alternate_next_image_id");
builder.alternateNextImageId(Wire.uint32(rawAlternateNextImageId, "KittyGraphicsState.alternate_next_image_id"));
Object rawAlternateReplayNextImageId = Wire.required(object, "alternate_replay_next_image_id");
builder.alternateReplayNextImageId(Wire.uint32(rawAlternateReplayNextImageId, "KittyGraphicsState.alternate_replay_next_image_id"));
Object rawImageBytes = Wire.required(object, "image_bytes");
builder.imageBytes(Wire.uint64(rawImageBytes, "KittyGraphicsState.image_bytes"));
Object rawImages = Wire.required(object, "images");
builder.images(Wire.uint64(rawImages, "KittyGraphicsState.images"));
Object rawInflightBytes = Wire.required(object, "inflight_bytes");
builder.inflightBytes(Wire.uint64(rawInflightBytes, "KittyGraphicsState.inflight_bytes"));
Object rawPlacements = Wire.required(object, "placements");
builder.placements(Wire.uint64(rawPlacements, "KittyGraphicsState.placements"));
Object rawPrimaryNextImageId = Wire.required(object, "primary_next_image_id");
builder.primaryNextImageId(Wire.uint32(rawPrimaryNextImageId, "KittyGraphicsState.primary_next_image_id"));
Object rawPrimaryReplayNextImageId = Wire.required(object, "primary_replay_next_image_id");
builder.primaryReplayNextImageId(Wire.uint32(rawPrimaryReplayNextImageId, "KittyGraphicsState.primary_replay_next_image_id"));
Object rawReplayCursorOffset = Wire.required(object, "replay_cursor_offset");
builder.replayCursorOffset(Wire.uint32(rawReplayCursorOffset, "KittyGraphicsState.replay_cursor_offset"));
return builder.build();
}
@Override
public Map<String, Object> toWire() {
LinkedHashMap<String, Object> object = new LinkedHashMap<>();
Wire.put(object, "alternate_next_image_id", alternateNextImageId);
Wire.put(object, "alternate_replay_next_image_id", alternateReplayNextImageId);
Wire.put(object, "image_bytes", imageBytes);
Wire.put(object, "images", images);
Wire.put(object, "inflight_bytes", inflightBytes);
Wire.put(object, "placements", placements);
Wire.put(object, "primary_next_image_id", primaryNextImageId);
Wire.put(object, "primary_replay_next_image_id", primaryReplayNextImageId);
Wire.put(object, "replay_cursor_offset", replayCursorOffset);
return Collections.unmodifiableMap(object);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof KittyGraphicsState that)) return false;
return Objects.equals(alternateNextImageId, that.alternateNextImageId) && Objects.equals(alternateReplayNextImageId, that.alternateReplayNextImageId) && Objects.equals(imageBytes, that.imageBytes) && Objects.equals(images, that.images) && Objects.equals(inflightBytes, that.inflightBytes) && Objects.equals(placements, that.placements) && Objects.equals(primaryNextImageId, that.primaryNextImageId) && Objects.equals(primaryReplayNextImageId, that.primaryReplayNextImageId) && Objects.equals(replayCursorOffset, that.replayCursorOffset);
}
@Override
public int hashCode() { return Objects.hash(alternateNextImageId, alternateReplayNextImageId, imageBytes, images, inflightBytes, placements, primaryNextImageId, primaryReplayNextImageId, replayCursorOffset); }
@Override
public String toString() { return "KittyGraphicsState" + toWire(); }
public static final class Builder {
private Long alternateNextImageId;
private boolean alternateNextImageIdSet;
private Long alternateReplayNextImageId;
private boolean alternateReplayNextImageIdSet;
private UInt64 imageBytes;
private boolean imageBytesSet;
private UInt64 images;
private boolean imagesSet;
private UInt64 inflightBytes;
private boolean inflightBytesSet;
private UInt64 placements;
private boolean placementsSet;
private Long primaryNextImageId;
private boolean primaryNextImageIdSet;
private Long primaryReplayNextImageId;
private boolean primaryReplayNextImageIdSet;
private Long replayCursorOffset;
private boolean replayCursorOffsetSet;
public Builder alternateNextImageId(long value) {
this.alternateNextImageId = value;
this.alternateNextImageIdSet = true;
return this;
}
public Builder alternateReplayNextImageId(long value) {
this.alternateReplayNextImageId = value;
this.alternateReplayNextImageIdSet = true;
return this;
}
public Builder imageBytes(UInt64 value) {
this.imageBytes = value;
this.imageBytesSet = true;
return this;
}
public Builder images(UInt64 value) {
this.images = value;
this.imagesSet = true;
return this;
}
public Builder inflightBytes(UInt64 value) {
this.inflightBytes = value;
this.inflightBytesSet = true;
return this;
}
public Builder placements(UInt64 value) {
this.placements = value;
this.placementsSet = true;
return this;
}
public Builder primaryNextImageId(long value) {
this.primaryNextImageId = value;
this.primaryNextImageIdSet = true;
return this;
}
public Builder primaryReplayNextImageId(long value) {
this.primaryReplayNextImageId = value;
this.primaryReplayNextImageIdSet = true;
return this;
}
public Builder replayCursorOffset(long value) {
this.replayCursorOffset = value;
this.replayCursorOffsetSet = true;
return this;
}
public KittyGraphicsState build() { return new KittyGraphicsState(this); }
}
}
@@ -0,0 +1,77 @@
// Generated from cmux-tui/spec/sdk-schema.json. DO NOT EDIT.
package com.cmux.raw;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public final class KittyImageAlias implements WireValue {
private final long imageId;
private final long imageNumber;
private KittyImageAlias(Builder builder) {
if (!builder.imageIdSet) throw new IllegalArgumentException("image_id is required");
this.imageId = builder.imageId;
if (!builder.imageNumberSet) throw new IllegalArgumentException("image_number is required");
this.imageNumber = builder.imageNumber;
}
public static Builder builder() { return new Builder(); }
public long imageId() { return imageId; }
public long imageNumber() { return imageNumber; }
public static KittyImageAlias fromWire(Object value) {
Map<String, Object> object = Wire.object(value, "KittyImageAlias");
Builder builder = builder();
Object rawImageId = Wire.required(object, "image_id");
builder.imageId(Wire.uint32(rawImageId, "KittyImageAlias.image_id"));
Object rawImageNumber = Wire.required(object, "image_number");
builder.imageNumber(Wire.uint32(rawImageNumber, "KittyImageAlias.image_number"));
return builder.build();
}
@Override
public Map<String, Object> toWire() {
LinkedHashMap<String, Object> object = new LinkedHashMap<>();
Wire.put(object, "image_id", imageId);
Wire.put(object, "image_number", imageNumber);
return Collections.unmodifiableMap(object);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof KittyImageAlias that)) return false;
return Objects.equals(imageId, that.imageId) && Objects.equals(imageNumber, that.imageNumber);
}
@Override
public int hashCode() { return Objects.hash(imageId, imageNumber); }
@Override
public String toString() { return "KittyImageAlias" + toWire(); }
public static final class Builder {
private Long imageId;
private boolean imageIdSet;
private Long imageNumber;
private boolean imageNumberSet;
public Builder imageId(long value) {
this.imageId = value;
this.imageIdSet = true;
return this;
}
public Builder imageNumber(long value) {
this.imageNumber = value;
this.imageNumberSet = true;
return this;
}
public KittyImageAlias build() { return new KittyImageAlias(this); }
}
}
@@ -9,7 +9,7 @@ public final class Protocol {
public static final String SDK_VERSION = "1.0.0";
public static final int VERSION = 10;
public static final int SCHEMA_VERSION = 2;
public static final String IR_SHA256 = "4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e";
public static final String IR_SHA256 = "17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350";
private Protocol() {}
public static ProtocolEvent decodeEvent(Object value) {
@@ -28,6 +28,7 @@ public final class Protocol {
case "empty" -> EmptyEvent.fromWire(value);
case "frame" -> FrameEvent.fromWire(value);
case "frontend-projection-changed" -> FrontendProjectionChangedEvent.fromWire(value);
case "graphics-status" -> GraphicsStatusEvent.fromWire(value);
case "layout-changed" -> LayoutChangedEvent.fromWire(value);
case "notification" -> NotificationEvent.fromWire(value);
case "output" -> OutputEvent.fromWire(value);
@@ -11,11 +11,14 @@ import java.util.Objects;
public final class ReadScrollbackResult implements WireValue {
private final UInt64 epoch;
private final List<RenderRow> rows;
private final long start;
private final long total;
private ReadScrollbackResult(Builder builder) {
if (!builder.epochSet) throw new IllegalArgumentException("epoch is required");
this.epoch = Wire.nonNull(builder.epoch, "epoch");
if (!builder.rowsSet) throw new IllegalArgumentException("rows is required");
this.rows = List.copyOf(Wire.nonNull(builder.rows, "rows"));
if (!builder.startSet) throw new IllegalArgumentException("start is required");
@@ -26,6 +29,7 @@ public final class ReadScrollbackResult implements WireValue {
public static Builder builder() { return new Builder(); }
public UInt64 epoch() { return epoch; }
public List<RenderRow> rows() { return rows; }
public long start() { return start; }
public long total() { return total; }
@@ -33,6 +37,8 @@ public final class ReadScrollbackResult implements WireValue {
public static ReadScrollbackResult fromWire(Object value) {
Map<String, Object> object = Wire.object(value, "ReadScrollbackResult");
Builder builder = builder();
Object rawEpoch = Wire.required(object, "epoch");
builder.epoch(Wire.uint64(rawEpoch, "ReadScrollbackResult.epoch"));
Object rawRows = Wire.required(object, "rows");
builder.rows(Wire.array(rawRows, "ReadScrollbackResult.rows", item -> RenderRow.fromWire(item)));
Object rawStart = Wire.required(object, "start");
@@ -45,6 +51,7 @@ public final class ReadScrollbackResult implements WireValue {
@Override
public Map<String, Object> toWire() {
LinkedHashMap<String, Object> object = new LinkedHashMap<>();
Wire.put(object, "epoch", epoch);
Wire.put(object, "rows", rows);
Wire.put(object, "start", start);
Wire.put(object, "total", total);
@@ -54,16 +61,18 @@ public final class ReadScrollbackResult implements WireValue {
@Override
public boolean equals(Object other) {
if (!(other instanceof ReadScrollbackResult that)) return false;
return Objects.equals(rows, that.rows) && Objects.equals(start, that.start) && Objects.equals(total, that.total);
return Objects.equals(epoch, that.epoch) && Objects.equals(rows, that.rows) && Objects.equals(start, that.start) && Objects.equals(total, that.total);
}
@Override
public int hashCode() { return Objects.hash(rows, start, total); }
public int hashCode() { return Objects.hash(epoch, rows, start, total); }
@Override
public String toString() { return "ReadScrollbackResult" + toWire(); }
public static final class Builder {
private UInt64 epoch;
private boolean epochSet;
private List<RenderRow> rows;
private boolean rowsSet;
private Long start;
@@ -71,6 +80,11 @@ public final class ReadScrollbackResult implements WireValue {
private Long total;
private boolean totalSet;
public Builder epoch(UInt64 value) {
this.epoch = value;
this.epochSet = true;
return this;
}
public Builder rows(List<RenderRow> value) {
this.rows = value;
this.rowsSet = true;
@@ -16,6 +16,8 @@ public final class RenderDeltaEvent implements WireValue, ProtocolEvent, RenderA
private final Field<String> defaultBg;
private final Field<String> defaultFg;
private final boolean full;
private final Field<RenderGraphicsDelta> graphics;
private final Field<UInt64> historyEpoch;
private final List<RenderRow> rows;
private final Field<Long> scrollbackRows;
private final Field<Size> size;
@@ -28,6 +30,8 @@ public final class RenderDeltaEvent implements WireValue, ProtocolEvent, RenderA
this.defaultFg = builder.defaultFg;
if (!builder.fullSet) throw new IllegalArgumentException("full is required");
this.full = builder.full;
this.graphics = builder.graphics;
this.historyEpoch = builder.historyEpoch;
if (!builder.rowsSet) throw new IllegalArgumentException("rows is required");
this.rows = List.copyOf(Wire.nonNull(builder.rows, "rows"));
this.scrollbackRows = builder.scrollbackRows;
@@ -42,6 +46,8 @@ public final class RenderDeltaEvent implements WireValue, ProtocolEvent, RenderA
public Field<String> defaultBg() { return defaultBg; }
public Field<String> defaultFg() { return defaultFg; }
public boolean full() { return full; }
public Field<RenderGraphicsDelta> graphics() { return graphics; }
public Field<UInt64> historyEpoch() { return historyEpoch; }
public List<RenderRow> rows() { return rows; }
public Field<Long> scrollbackRows() { return scrollbackRows; }
public Field<Size> size() { return size; }
@@ -64,6 +70,14 @@ public final class RenderDeltaEvent implements WireValue, ProtocolEvent, RenderA
}
Object rawFull = Wire.required(object, "full");
builder.full(Wire.bool(rawFull, "RenderDeltaEvent.full"));
Object rawGraphics = Wire.optional(object, "graphics");
if (!Wire.isMissing(rawGraphics)) {
builder.graphics(RenderGraphicsDelta.fromWire(rawGraphics));
}
Object rawHistoryEpoch = Wire.optional(object, "history_epoch");
if (!Wire.isMissing(rawHistoryEpoch)) {
builder.historyEpoch(Wire.uint64(rawHistoryEpoch, "RenderDeltaEvent.history_epoch"));
}
Object rawRows = Wire.required(object, "rows");
builder.rows(Wire.array(rawRows, "RenderDeltaEvent.rows", item -> RenderRow.fromWire(item)));
Object rawScrollbackRows = Wire.optional(object, "scrollback_rows");
@@ -87,6 +101,8 @@ public final class RenderDeltaEvent implements WireValue, ProtocolEvent, RenderA
Wire.put(object, "default_bg", defaultBg);
Wire.put(object, "default_fg", defaultFg);
Wire.put(object, "full", full);
Wire.put(object, "graphics", graphics);
Wire.put(object, "history_epoch", historyEpoch);
Wire.put(object, "rows", rows);
Wire.put(object, "scrollback_rows", scrollbackRows);
Wire.put(object, "size", size);
@@ -97,11 +113,11 @@ public final class RenderDeltaEvent implements WireValue, ProtocolEvent, RenderA
@Override
public boolean equals(Object other) {
if (!(other instanceof RenderDeltaEvent that)) return false;
return Objects.equals(cursor, that.cursor) && Objects.equals(defaultBg, that.defaultBg) && Objects.equals(defaultFg, that.defaultFg) && Objects.equals(full, that.full) && Objects.equals(rows, that.rows) && Objects.equals(scrollbackRows, that.scrollbackRows) && Objects.equals(size, that.size) && Objects.equals(surface, that.surface);
return Objects.equals(cursor, that.cursor) && Objects.equals(defaultBg, that.defaultBg) && Objects.equals(defaultFg, that.defaultFg) && Objects.equals(full, that.full) && Objects.equals(graphics, that.graphics) && Objects.equals(historyEpoch, that.historyEpoch) && Objects.equals(rows, that.rows) && Objects.equals(scrollbackRows, that.scrollbackRows) && Objects.equals(size, that.size) && Objects.equals(surface, that.surface);
}
@Override
public int hashCode() { return Objects.hash(cursor, defaultBg, defaultFg, full, rows, scrollbackRows, size, surface); }
public int hashCode() { return Objects.hash(cursor, defaultBg, defaultFg, full, graphics, historyEpoch, rows, scrollbackRows, size, surface); }
@Override
public String toString() { return "RenderDeltaEvent" + toWire(); }
@@ -113,6 +129,8 @@ public final class RenderDeltaEvent implements WireValue, ProtocolEvent, RenderA
private Field<String> defaultFg = Field.omitted();
private Boolean full;
private boolean fullSet;
private Field<RenderGraphicsDelta> graphics = Field.omitted();
private Field<UInt64> historyEpoch = Field.omitted();
private List<RenderRow> rows;
private boolean rowsSet;
private Field<Long> scrollbackRows = Field.omitted();
@@ -138,6 +156,14 @@ public final class RenderDeltaEvent implements WireValue, ProtocolEvent, RenderA
this.fullSet = true;
return this;
}
public Builder graphics(RenderGraphicsDelta value) {
this.graphics = Field.of(value);
return this;
}
public Builder historyEpoch(UInt64 value) {
this.historyEpoch = Field.of(value);
return this;
}
public Builder rows(List<RenderRow> value) {
this.rows = value;
this.rowsSet = true;
@@ -0,0 +1,34 @@
// Generated from cmux-tui/spec/sdk-schema.json. DO NOT EDIT.
package com.cmux.raw;
import java.util.Objects;
public enum RenderGraphicFormat implements WireEnum {
RGB("rgb"),
RGBA("rgba");
private final Object wireValue;
RenderGraphicFormat(Object wireValue) {
this.wireValue = wireValue;
}
@Override
public String wireValue() {
return String.valueOf(wireValue);
}
public Object rawWireValue() {
return wireValue;
}
public static RenderGraphicFormat fromWire(Object value) {
for (RenderGraphicFormat candidate : values()) {
if (Objects.equals(candidate.wireValue, value)
|| Objects.equals(String.valueOf(candidate.wireValue), value)) {
return candidate;
}
}
throw new CmuxDecodeException("unknown RenderGraphicFormat value " + value, null);
}
}
@@ -0,0 +1,133 @@
// Generated from cmux-tui/spec/sdk-schema.json. DO NOT EDIT.
package com.cmux.raw;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public final class RenderGraphicImage implements WireValue {
private final Bytes data;
private final RenderGraphicFormat format;
private final UInt64 generation;
private final long height;
private final long id;
private final long width;
private RenderGraphicImage(Builder builder) {
if (!builder.dataSet) throw new IllegalArgumentException("data is required");
this.data = Wire.nonNull(builder.data, "data");
if (!builder.formatSet) throw new IllegalArgumentException("format is required");
this.format = Wire.nonNull(builder.format, "format");
if (!builder.generationSet) throw new IllegalArgumentException("generation is required");
this.generation = Wire.nonNull(builder.generation, "generation");
if (!builder.heightSet) throw new IllegalArgumentException("height is required");
this.height = builder.height;
if (!builder.idSet) throw new IllegalArgumentException("id is required");
this.id = builder.id;
if (!builder.widthSet) throw new IllegalArgumentException("width is required");
this.width = builder.width;
}
public static Builder builder() { return new Builder(); }
public Bytes data() { return data; }
public RenderGraphicFormat format() { return format; }
public UInt64 generation() { return generation; }
public long height() { return height; }
public long id() { return id; }
public long width() { return width; }
public static RenderGraphicImage fromWire(Object value) {
Map<String, Object> object = Wire.object(value, "RenderGraphicImage");
Builder builder = builder();
Object rawData = Wire.required(object, "data");
builder.data(Wire.bytes(rawData, "RenderGraphicImage.data"));
Object rawFormat = Wire.required(object, "format");
builder.format(RenderGraphicFormat.fromWire(rawFormat));
Object rawGeneration = Wire.required(object, "generation");
builder.generation(Wire.uint64(rawGeneration, "RenderGraphicImage.generation"));
Object rawHeight = Wire.required(object, "height");
builder.height(Wire.uint32(rawHeight, "RenderGraphicImage.height"));
Object rawId = Wire.required(object, "id");
builder.id(Wire.uint32(rawId, "RenderGraphicImage.id"));
Object rawWidth = Wire.required(object, "width");
builder.width(Wire.uint32(rawWidth, "RenderGraphicImage.width"));
return builder.build();
}
@Override
public Map<String, Object> toWire() {
LinkedHashMap<String, Object> object = new LinkedHashMap<>();
Wire.put(object, "data", data);
Wire.put(object, "format", format);
Wire.put(object, "generation", generation);
Wire.put(object, "height", height);
Wire.put(object, "id", id);
Wire.put(object, "width", width);
return Collections.unmodifiableMap(object);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof RenderGraphicImage that)) return false;
return Objects.equals(data, that.data) && Objects.equals(format, that.format) && Objects.equals(generation, that.generation) && Objects.equals(height, that.height) && Objects.equals(id, that.id) && Objects.equals(width, that.width);
}
@Override
public int hashCode() { return Objects.hash(data, format, generation, height, id, width); }
@Override
public String toString() { return "RenderGraphicImage" + toWire(); }
public static final class Builder {
private Bytes data;
private boolean dataSet;
private RenderGraphicFormat format;
private boolean formatSet;
private UInt64 generation;
private boolean generationSet;
private Long height;
private boolean heightSet;
private Long id;
private boolean idSet;
private Long width;
private boolean widthSet;
public Builder data(Bytes value) {
this.data = value;
this.dataSet = true;
return this;
}
public Builder format(RenderGraphicFormat value) {
this.format = value;
this.formatSet = true;
return this;
}
public Builder generation(UInt64 value) {
this.generation = value;
this.generationSet = true;
return this;
}
public Builder height(long value) {
this.height = value;
this.heightSet = true;
return this;
}
public Builder id(long value) {
this.id = value;
this.idSet = true;
return this;
}
public Builder width(long value) {
this.width = value;
this.widthSet = true;
return this;
}
public RenderGraphicImage build() { return new RenderGraphicImage(this); }
}
}
@@ -0,0 +1,341 @@
// Generated from cmux-tui/spec/sdk-schema.json. DO NOT EDIT.
package com.cmux.raw;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public final class RenderGraphicPlacement implements WireValue {
private final Field<Integer> anchorCol;
private final Field<Long> anchorRow;
private final long columns;
private final long gridCols;
private final long gridRows;
private final long imageId;
private final long ordinal;
private final long pixelHeight;
private final long pixelWidth;
private final long placementId;
private final long rows;
private final long sourceHeight;
private final long sourceWidth;
private final long sourceX;
private final long sourceY;
private final int viewportCol;
private final int viewportRow;
private final boolean viewportVisible;
private final long xOffset;
private final long yOffset;
private final int z;
private RenderGraphicPlacement(Builder builder) {
this.anchorCol = builder.anchorCol;
this.anchorRow = builder.anchorRow;
if (!builder.columnsSet) throw new IllegalArgumentException("columns is required");
this.columns = builder.columns;
if (!builder.gridColsSet) throw new IllegalArgumentException("grid_cols is required");
this.gridCols = builder.gridCols;
if (!builder.gridRowsSet) throw new IllegalArgumentException("grid_rows is required");
this.gridRows = builder.gridRows;
if (!builder.imageIdSet) throw new IllegalArgumentException("image_id is required");
this.imageId = builder.imageId;
if (!builder.ordinalSet) throw new IllegalArgumentException("ordinal is required");
this.ordinal = builder.ordinal;
if (!builder.pixelHeightSet) throw new IllegalArgumentException("pixel_height is required");
this.pixelHeight = builder.pixelHeight;
if (!builder.pixelWidthSet) throw new IllegalArgumentException("pixel_width is required");
this.pixelWidth = builder.pixelWidth;
if (!builder.placementIdSet) throw new IllegalArgumentException("placement_id is required");
this.placementId = builder.placementId;
if (!builder.rowsSet) throw new IllegalArgumentException("rows is required");
this.rows = builder.rows;
if (!builder.sourceHeightSet) throw new IllegalArgumentException("source_height is required");
this.sourceHeight = builder.sourceHeight;
if (!builder.sourceWidthSet) throw new IllegalArgumentException("source_width is required");
this.sourceWidth = builder.sourceWidth;
if (!builder.sourceXSet) throw new IllegalArgumentException("source_x is required");
this.sourceX = builder.sourceX;
if (!builder.sourceYSet) throw new IllegalArgumentException("source_y is required");
this.sourceY = builder.sourceY;
if (!builder.viewportColSet) throw new IllegalArgumentException("viewport_col is required");
this.viewportCol = builder.viewportCol;
if (!builder.viewportRowSet) throw new IllegalArgumentException("viewport_row is required");
this.viewportRow = builder.viewportRow;
if (!builder.viewportVisibleSet) throw new IllegalArgumentException("viewport_visible is required");
this.viewportVisible = builder.viewportVisible;
if (!builder.xOffsetSet) throw new IllegalArgumentException("x_offset is required");
this.xOffset = builder.xOffset;
if (!builder.yOffsetSet) throw new IllegalArgumentException("y_offset is required");
this.yOffset = builder.yOffset;
if (!builder.zSet) throw new IllegalArgumentException("z is required");
this.z = builder.z;
}
public static Builder builder() { return new Builder(); }
public Field<Integer> anchorCol() { return anchorCol; }
public Field<Long> anchorRow() { return anchorRow; }
public long columns() { return columns; }
public long gridCols() { return gridCols; }
public long gridRows() { return gridRows; }
public long imageId() { return imageId; }
public long ordinal() { return ordinal; }
public long pixelHeight() { return pixelHeight; }
public long pixelWidth() { return pixelWidth; }
public long placementId() { return placementId; }
public long rows() { return rows; }
public long sourceHeight() { return sourceHeight; }
public long sourceWidth() { return sourceWidth; }
public long sourceX() { return sourceX; }
public long sourceY() { return sourceY; }
public int viewportCol() { return viewportCol; }
public int viewportRow() { return viewportRow; }
public boolean viewportVisible() { return viewportVisible; }
public long xOffset() { return xOffset; }
public long yOffset() { return yOffset; }
public int z() { return z; }
public static RenderGraphicPlacement fromWire(Object value) {
Map<String, Object> object = Wire.object(value, "RenderGraphicPlacement");
Builder builder = builder();
Object rawAnchorCol = Wire.optional(object, "anchor_col");
if (!Wire.isMissing(rawAnchorCol)) {
builder.anchorCol(Wire.uint16(rawAnchorCol, "RenderGraphicPlacement.anchor_col"));
}
Object rawAnchorRow = Wire.optional(object, "anchor_row");
if (!Wire.isMissing(rawAnchorRow)) {
builder.anchorRow(Wire.uint32(rawAnchorRow, "RenderGraphicPlacement.anchor_row"));
}
Object rawColumns = Wire.required(object, "columns");
builder.columns(Wire.uint32(rawColumns, "RenderGraphicPlacement.columns"));
Object rawGridCols = Wire.required(object, "grid_cols");
builder.gridCols(Wire.uint32(rawGridCols, "RenderGraphicPlacement.grid_cols"));
Object rawGridRows = Wire.required(object, "grid_rows");
builder.gridRows(Wire.uint32(rawGridRows, "RenderGraphicPlacement.grid_rows"));
Object rawImageId = Wire.required(object, "image_id");
builder.imageId(Wire.uint32(rawImageId, "RenderGraphicPlacement.image_id"));
Object rawOrdinal = Wire.required(object, "ordinal");
builder.ordinal(Wire.uint32(rawOrdinal, "RenderGraphicPlacement.ordinal"));
Object rawPixelHeight = Wire.required(object, "pixel_height");
builder.pixelHeight(Wire.uint32(rawPixelHeight, "RenderGraphicPlacement.pixel_height"));
Object rawPixelWidth = Wire.required(object, "pixel_width");
builder.pixelWidth(Wire.uint32(rawPixelWidth, "RenderGraphicPlacement.pixel_width"));
Object rawPlacementId = Wire.required(object, "placement_id");
builder.placementId(Wire.uint32(rawPlacementId, "RenderGraphicPlacement.placement_id"));
Object rawRows = Wire.required(object, "rows");
builder.rows(Wire.uint32(rawRows, "RenderGraphicPlacement.rows"));
Object rawSourceHeight = Wire.required(object, "source_height");
builder.sourceHeight(Wire.uint32(rawSourceHeight, "RenderGraphicPlacement.source_height"));
Object rawSourceWidth = Wire.required(object, "source_width");
builder.sourceWidth(Wire.uint32(rawSourceWidth, "RenderGraphicPlacement.source_width"));
Object rawSourceX = Wire.required(object, "source_x");
builder.sourceX(Wire.uint32(rawSourceX, "RenderGraphicPlacement.source_x"));
Object rawSourceY = Wire.required(object, "source_y");
builder.sourceY(Wire.uint32(rawSourceY, "RenderGraphicPlacement.source_y"));
Object rawViewportCol = Wire.required(object, "viewport_col");
builder.viewportCol(Wire.int32(rawViewportCol, "RenderGraphicPlacement.viewport_col"));
Object rawViewportRow = Wire.required(object, "viewport_row");
builder.viewportRow(Wire.int32(rawViewportRow, "RenderGraphicPlacement.viewport_row"));
Object rawViewportVisible = Wire.required(object, "viewport_visible");
builder.viewportVisible(Wire.bool(rawViewportVisible, "RenderGraphicPlacement.viewport_visible"));
Object rawXOffset = Wire.required(object, "x_offset");
builder.xOffset(Wire.uint32(rawXOffset, "RenderGraphicPlacement.x_offset"));
Object rawYOffset = Wire.required(object, "y_offset");
builder.yOffset(Wire.uint32(rawYOffset, "RenderGraphicPlacement.y_offset"));
Object rawZ = Wire.required(object, "z");
builder.z(Wire.int32(rawZ, "RenderGraphicPlacement.z"));
return builder.build();
}
@Override
public Map<String, Object> toWire() {
LinkedHashMap<String, Object> object = new LinkedHashMap<>();
Wire.put(object, "anchor_col", anchorCol);
Wire.put(object, "anchor_row", anchorRow);
Wire.put(object, "columns", columns);
Wire.put(object, "grid_cols", gridCols);
Wire.put(object, "grid_rows", gridRows);
Wire.put(object, "image_id", imageId);
Wire.put(object, "ordinal", ordinal);
Wire.put(object, "pixel_height", pixelHeight);
Wire.put(object, "pixel_width", pixelWidth);
Wire.put(object, "placement_id", placementId);
Wire.put(object, "rows", rows);
Wire.put(object, "source_height", sourceHeight);
Wire.put(object, "source_width", sourceWidth);
Wire.put(object, "source_x", sourceX);
Wire.put(object, "source_y", sourceY);
Wire.put(object, "viewport_col", viewportCol);
Wire.put(object, "viewport_row", viewportRow);
Wire.put(object, "viewport_visible", viewportVisible);
Wire.put(object, "x_offset", xOffset);
Wire.put(object, "y_offset", yOffset);
Wire.put(object, "z", z);
return Collections.unmodifiableMap(object);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof RenderGraphicPlacement that)) return false;
return Objects.equals(anchorCol, that.anchorCol) && Objects.equals(anchorRow, that.anchorRow) && Objects.equals(columns, that.columns) && Objects.equals(gridCols, that.gridCols) && Objects.equals(gridRows, that.gridRows) && Objects.equals(imageId, that.imageId) && Objects.equals(ordinal, that.ordinal) && Objects.equals(pixelHeight, that.pixelHeight) && Objects.equals(pixelWidth, that.pixelWidth) && Objects.equals(placementId, that.placementId) && Objects.equals(rows, that.rows) && Objects.equals(sourceHeight, that.sourceHeight) && Objects.equals(sourceWidth, that.sourceWidth) && Objects.equals(sourceX, that.sourceX) && Objects.equals(sourceY, that.sourceY) && Objects.equals(viewportCol, that.viewportCol) && Objects.equals(viewportRow, that.viewportRow) && Objects.equals(viewportVisible, that.viewportVisible) && Objects.equals(xOffset, that.xOffset) && Objects.equals(yOffset, that.yOffset) && Objects.equals(z, that.z);
}
@Override
public int hashCode() { return Objects.hash(anchorCol, anchorRow, columns, gridCols, gridRows, imageId, ordinal, pixelHeight, pixelWidth, placementId, rows, sourceHeight, sourceWidth, sourceX, sourceY, viewportCol, viewportRow, viewportVisible, xOffset, yOffset, z); }
@Override
public String toString() { return "RenderGraphicPlacement" + toWire(); }
public static final class Builder {
private Field<Integer> anchorCol = Field.omitted();
private Field<Long> anchorRow = Field.omitted();
private Long columns;
private boolean columnsSet;
private Long gridCols;
private boolean gridColsSet;
private Long gridRows;
private boolean gridRowsSet;
private Long imageId;
private boolean imageIdSet;
private Long ordinal;
private boolean ordinalSet;
private Long pixelHeight;
private boolean pixelHeightSet;
private Long pixelWidth;
private boolean pixelWidthSet;
private Long placementId;
private boolean placementIdSet;
private Long rows;
private boolean rowsSet;
private Long sourceHeight;
private boolean sourceHeightSet;
private Long sourceWidth;
private boolean sourceWidthSet;
private Long sourceX;
private boolean sourceXSet;
private Long sourceY;
private boolean sourceYSet;
private Integer viewportCol;
private boolean viewportColSet;
private Integer viewportRow;
private boolean viewportRowSet;
private Boolean viewportVisible;
private boolean viewportVisibleSet;
private Long xOffset;
private boolean xOffsetSet;
private Long yOffset;
private boolean yOffsetSet;
private Integer z;
private boolean zSet;
public Builder anchorCol(Integer value) {
this.anchorCol = Field.of(value);
return this;
}
public Builder anchorRow(Long value) {
this.anchorRow = Field.of(value);
return this;
}
public Builder columns(long value) {
this.columns = value;
this.columnsSet = true;
return this;
}
public Builder gridCols(long value) {
this.gridCols = value;
this.gridColsSet = true;
return this;
}
public Builder gridRows(long value) {
this.gridRows = value;
this.gridRowsSet = true;
return this;
}
public Builder imageId(long value) {
this.imageId = value;
this.imageIdSet = true;
return this;
}
public Builder ordinal(long value) {
this.ordinal = value;
this.ordinalSet = true;
return this;
}
public Builder pixelHeight(long value) {
this.pixelHeight = value;
this.pixelHeightSet = true;
return this;
}
public Builder pixelWidth(long value) {
this.pixelWidth = value;
this.pixelWidthSet = true;
return this;
}
public Builder placementId(long value) {
this.placementId = value;
this.placementIdSet = true;
return this;
}
public Builder rows(long value) {
this.rows = value;
this.rowsSet = true;
return this;
}
public Builder sourceHeight(long value) {
this.sourceHeight = value;
this.sourceHeightSet = true;
return this;
}
public Builder sourceWidth(long value) {
this.sourceWidth = value;
this.sourceWidthSet = true;
return this;
}
public Builder sourceX(long value) {
this.sourceX = value;
this.sourceXSet = true;
return this;
}
public Builder sourceY(long value) {
this.sourceY = value;
this.sourceYSet = true;
return this;
}
public Builder viewportCol(int value) {
this.viewportCol = value;
this.viewportColSet = true;
return this;
}
public Builder viewportRow(int value) {
this.viewportRow = value;
this.viewportRowSet = true;
return this;
}
public Builder viewportVisible(boolean value) {
this.viewportVisible = value;
this.viewportVisibleSet = true;
return this;
}
public Builder xOffset(long value) {
this.xOffset = value;
this.xOffsetSet = true;
return this;
}
public Builder yOffset(long value) {
this.yOffset = value;
this.yOffsetSet = true;
return this;
}
public Builder z(int value) {
this.z = value;
this.zSet = true;
return this;
}
public RenderGraphicPlacement build() { return new RenderGraphicPlacement(this); }
}
}
@@ -0,0 +1,103 @@
// Generated from cmux-tui/spec/sdk-schema.json. DO NOT EDIT.
package com.cmux.raw;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public final class RenderGraphics implements WireValue {
private final UInt64 generation;
private final Field<List<RenderGraphicImage>> images;
private final List<RenderGraphicPlacement> placements;
private final Field<List<Long>> removedImageIds;
private RenderGraphics(Builder builder) {
if (!builder.generationSet) throw new IllegalArgumentException("generation is required");
this.generation = Wire.nonNull(builder.generation, "generation");
this.images = builder.images.map(value -> List.copyOf(value));
if (!builder.placementsSet) throw new IllegalArgumentException("placements is required");
this.placements = List.copyOf(Wire.nonNull(builder.placements, "placements"));
this.removedImageIds = builder.removedImageIds.map(value -> List.copyOf(value));
}
public static Builder builder() { return new Builder(); }
public UInt64 generation() { return generation; }
public Field<List<RenderGraphicImage>> images() { return images; }
public List<RenderGraphicPlacement> placements() { return placements; }
public Field<List<Long>> removedImageIds() { return removedImageIds; }
public static RenderGraphics fromWire(Object value) {
Map<String, Object> object = Wire.object(value, "RenderGraphics");
Builder builder = builder();
Object rawGeneration = Wire.required(object, "generation");
builder.generation(Wire.uint64(rawGeneration, "RenderGraphics.generation"));
Object rawImages = Wire.optional(object, "images");
if (!Wire.isMissing(rawImages)) {
builder.images(Wire.array(rawImages, "RenderGraphics.images", item -> RenderGraphicImage.fromWire(item)));
}
Object rawPlacements = Wire.required(object, "placements");
builder.placements(Wire.array(rawPlacements, "RenderGraphics.placements", item -> RenderGraphicPlacement.fromWire(item)));
Object rawRemovedImageIds = Wire.optional(object, "removed_image_ids");
if (!Wire.isMissing(rawRemovedImageIds)) {
builder.removedImageIds(Wire.array(rawRemovedImageIds, "RenderGraphics.removed_image_ids", item -> Wire.uint32(item, "RenderGraphics.removed_image_ids item")));
}
return builder.build();
}
@Override
public Map<String, Object> toWire() {
LinkedHashMap<String, Object> object = new LinkedHashMap<>();
Wire.put(object, "generation", generation);
Wire.put(object, "images", images);
Wire.put(object, "placements", placements);
Wire.put(object, "removed_image_ids", removedImageIds);
return Collections.unmodifiableMap(object);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof RenderGraphics that)) return false;
return Objects.equals(generation, that.generation) && Objects.equals(images, that.images) && Objects.equals(placements, that.placements) && Objects.equals(removedImageIds, that.removedImageIds);
}
@Override
public int hashCode() { return Objects.hash(generation, images, placements, removedImageIds); }
@Override
public String toString() { return "RenderGraphics" + toWire(); }
public static final class Builder {
private UInt64 generation;
private boolean generationSet;
private Field<List<RenderGraphicImage>> images = Field.omitted();
private List<RenderGraphicPlacement> placements;
private boolean placementsSet;
private Field<List<Long>> removedImageIds = Field.omitted();
public Builder generation(UInt64 value) {
this.generation = value;
this.generationSet = true;
return this;
}
public Builder images(List<RenderGraphicImage> value) {
this.images = Field.of(value);
return this;
}
public Builder placements(List<RenderGraphicPlacement> value) {
this.placements = value;
this.placementsSet = true;
return this;
}
public Builder removedImageIds(List<Long> value) {
this.removedImageIds = Field.of(value);
return this;
}
public RenderGraphics build() { return new RenderGraphics(this); }
}
}
@@ -0,0 +1,102 @@
// Generated from cmux-tui/spec/sdk-schema.json. DO NOT EDIT.
package com.cmux.raw;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public final class RenderGraphicsDelta implements WireValue {
private final UInt64 generation;
private final Field<List<RenderGraphicImage>> images;
private final Field<List<RenderGraphicPlacement>> placements;
private final Field<List<Long>> removedImageIds;
private RenderGraphicsDelta(Builder builder) {
if (!builder.generationSet) throw new IllegalArgumentException("generation is required");
this.generation = Wire.nonNull(builder.generation, "generation");
this.images = builder.images.map(value -> List.copyOf(value));
this.placements = builder.placements.map(value -> List.copyOf(value));
this.removedImageIds = builder.removedImageIds.map(value -> List.copyOf(value));
}
public static Builder builder() { return new Builder(); }
public UInt64 generation() { return generation; }
public Field<List<RenderGraphicImage>> images() { return images; }
public Field<List<RenderGraphicPlacement>> placements() { return placements; }
public Field<List<Long>> removedImageIds() { return removedImageIds; }
public static RenderGraphicsDelta fromWire(Object value) {
Map<String, Object> object = Wire.object(value, "RenderGraphicsDelta");
Builder builder = builder();
Object rawGeneration = Wire.required(object, "generation");
builder.generation(Wire.uint64(rawGeneration, "RenderGraphicsDelta.generation"));
Object rawImages = Wire.optional(object, "images");
if (!Wire.isMissing(rawImages)) {
builder.images(Wire.array(rawImages, "RenderGraphicsDelta.images", item -> RenderGraphicImage.fromWire(item)));
}
Object rawPlacements = Wire.optional(object, "placements");
if (!Wire.isMissing(rawPlacements)) {
builder.placements(Wire.array(rawPlacements, "RenderGraphicsDelta.placements", item -> RenderGraphicPlacement.fromWire(item)));
}
Object rawRemovedImageIds = Wire.optional(object, "removed_image_ids");
if (!Wire.isMissing(rawRemovedImageIds)) {
builder.removedImageIds(Wire.array(rawRemovedImageIds, "RenderGraphicsDelta.removed_image_ids", item -> Wire.uint32(item, "RenderGraphicsDelta.removed_image_ids item")));
}
return builder.build();
}
@Override
public Map<String, Object> toWire() {
LinkedHashMap<String, Object> object = new LinkedHashMap<>();
Wire.put(object, "generation", generation);
Wire.put(object, "images", images);
Wire.put(object, "placements", placements);
Wire.put(object, "removed_image_ids", removedImageIds);
return Collections.unmodifiableMap(object);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof RenderGraphicsDelta that)) return false;
return Objects.equals(generation, that.generation) && Objects.equals(images, that.images) && Objects.equals(placements, that.placements) && Objects.equals(removedImageIds, that.removedImageIds);
}
@Override
public int hashCode() { return Objects.hash(generation, images, placements, removedImageIds); }
@Override
public String toString() { return "RenderGraphicsDelta" + toWire(); }
public static final class Builder {
private UInt64 generation;
private boolean generationSet;
private Field<List<RenderGraphicImage>> images = Field.omitted();
private Field<List<RenderGraphicPlacement>> placements = Field.omitted();
private Field<List<Long>> removedImageIds = Field.omitted();
public Builder generation(UInt64 value) {
this.generation = value;
this.generationSet = true;
return this;
}
public Builder images(List<RenderGraphicImage> value) {
this.images = Field.of(value);
return this;
}
public Builder placements(List<RenderGraphicPlacement> value) {
this.placements = Field.of(value);
return this;
}
public Builder removedImageIds(List<Long> value) {
this.removedImageIds = Field.of(value);
return this;
}
public RenderGraphicsDelta build() { return new RenderGraphicsDelta(this); }
}
}
@@ -15,6 +15,8 @@ public final class RenderStateEvent implements WireValue, ProtocolEvent, RenderA
private final RenderCursor cursor;
private final String defaultBg;
private final String defaultFg;
private final Field<RenderGraphics> graphics;
private final UInt64 historyEpoch;
private final List<RenderRow> rows;
private final long scrollbackRows;
private final Size size;
@@ -27,6 +29,9 @@ public final class RenderStateEvent implements WireValue, ProtocolEvent, RenderA
this.defaultBg = Wire.nonNull(builder.defaultBg, "default_bg");
if (!builder.defaultFgSet) throw new IllegalArgumentException("default_fg is required");
this.defaultFg = Wire.nonNull(builder.defaultFg, "default_fg");
this.graphics = builder.graphics;
if (!builder.historyEpochSet) throw new IllegalArgumentException("history_epoch is required");
this.historyEpoch = Wire.nonNull(builder.historyEpoch, "history_epoch");
if (!builder.rowsSet) throw new IllegalArgumentException("rows is required");
this.rows = List.copyOf(Wire.nonNull(builder.rows, "rows"));
if (!builder.scrollbackRowsSet) throw new IllegalArgumentException("scrollback_rows is required");
@@ -42,6 +47,8 @@ public final class RenderStateEvent implements WireValue, ProtocolEvent, RenderA
public RenderCursor cursor() { return cursor; }
public String defaultBg() { return defaultBg; }
public String defaultFg() { return defaultFg; }
public Field<RenderGraphics> graphics() { return graphics; }
public UInt64 historyEpoch() { return historyEpoch; }
public List<RenderRow> rows() { return rows; }
public long scrollbackRows() { return scrollbackRows; }
public Size size() { return size; }
@@ -58,6 +65,12 @@ public final class RenderStateEvent implements WireValue, ProtocolEvent, RenderA
builder.defaultBg(Wire.string(rawDefaultBg, "RenderStateEvent.default_bg"));
Object rawDefaultFg = Wire.required(object, "default_fg");
builder.defaultFg(Wire.string(rawDefaultFg, "RenderStateEvent.default_fg"));
Object rawGraphics = Wire.optional(object, "graphics");
if (!Wire.isMissing(rawGraphics)) {
builder.graphics(RenderGraphics.fromWire(rawGraphics));
}
Object rawHistoryEpoch = Wire.required(object, "history_epoch");
builder.historyEpoch(Wire.uint64(rawHistoryEpoch, "RenderStateEvent.history_epoch"));
Object rawRows = Wire.required(object, "rows");
builder.rows(Wire.array(rawRows, "RenderStateEvent.rows", item -> RenderRow.fromWire(item)));
Object rawScrollbackRows = Wire.required(object, "scrollback_rows");
@@ -76,6 +89,8 @@ public final class RenderStateEvent implements WireValue, ProtocolEvent, RenderA
Wire.put(object, "cursor", cursor);
Wire.put(object, "default_bg", defaultBg);
Wire.put(object, "default_fg", defaultFg);
Wire.put(object, "graphics", graphics);
Wire.put(object, "history_epoch", historyEpoch);
Wire.put(object, "rows", rows);
Wire.put(object, "scrollback_rows", scrollbackRows);
Wire.put(object, "size", size);
@@ -86,11 +101,11 @@ public final class RenderStateEvent implements WireValue, ProtocolEvent, RenderA
@Override
public boolean equals(Object other) {
if (!(other instanceof RenderStateEvent that)) return false;
return Objects.equals(cursor, that.cursor) && Objects.equals(defaultBg, that.defaultBg) && Objects.equals(defaultFg, that.defaultFg) && Objects.equals(rows, that.rows) && Objects.equals(scrollbackRows, that.scrollbackRows) && Objects.equals(size, that.size) && Objects.equals(surface, that.surface);
return Objects.equals(cursor, that.cursor) && Objects.equals(defaultBg, that.defaultBg) && Objects.equals(defaultFg, that.defaultFg) && Objects.equals(graphics, that.graphics) && Objects.equals(historyEpoch, that.historyEpoch) && Objects.equals(rows, that.rows) && Objects.equals(scrollbackRows, that.scrollbackRows) && Objects.equals(size, that.size) && Objects.equals(surface, that.surface);
}
@Override
public int hashCode() { return Objects.hash(cursor, defaultBg, defaultFg, rows, scrollbackRows, size, surface); }
public int hashCode() { return Objects.hash(cursor, defaultBg, defaultFg, graphics, historyEpoch, rows, scrollbackRows, size, surface); }
@Override
public String toString() { return "RenderStateEvent" + toWire(); }
@@ -102,6 +117,9 @@ public final class RenderStateEvent implements WireValue, ProtocolEvent, RenderA
private boolean defaultBgSet;
private String defaultFg;
private boolean defaultFgSet;
private Field<RenderGraphics> graphics = Field.omitted();
private UInt64 historyEpoch;
private boolean historyEpochSet;
private List<RenderRow> rows;
private boolean rowsSet;
private Long scrollbackRows;
@@ -126,6 +144,15 @@ public final class RenderStateEvent implements WireValue, ProtocolEvent, RenderA
this.defaultFgSet = true;
return this;
}
public Builder graphics(RenderGraphics value) {
this.graphics = Field.of(value);
return this;
}
public Builder historyEpoch(UInt64 value) {
this.historyEpoch = value;
this.historyEpochSet = true;
return this;
}
public Builder rows(List<RenderRow> value) {
this.rows = value;
this.rowsSet = true;
@@ -16,6 +16,8 @@ public final class ResizedEvent implements WireValue, ByteAttachEvent, ProtocolE
private final int cols;
/** Protocol 6 compatibility field. */
private final Field<Bytes> data;
private final Field<KittyGraphicsState> kittyGraphicsState;
private final Field<List<KittyImageAlias>> kittyImageAliases;
private final Field<Bytes> replay;
private final int rows;
private final UInt64 surface;
@@ -25,6 +27,8 @@ public final class ResizedEvent implements WireValue, ByteAttachEvent, ProtocolE
if (!builder.colsSet) throw new IllegalArgumentException("cols is required");
this.cols = builder.cols;
this.data = builder.data;
this.kittyGraphicsState = builder.kittyGraphicsState;
this.kittyImageAliases = builder.kittyImageAliases.map(value -> List.copyOf(value));
this.replay = builder.replay;
if (!builder.rowsSet) throw new IllegalArgumentException("rows is required");
this.rows = builder.rows;
@@ -37,6 +41,8 @@ public final class ResizedEvent implements WireValue, ByteAttachEvent, ProtocolE
public Field<TerminalColors> colors() { return colors; }
public int cols() { return cols; }
public Field<Bytes> data() { return data; }
public Field<KittyGraphicsState> kittyGraphicsState() { return kittyGraphicsState; }
public Field<List<KittyImageAlias>> kittyImageAliases() { return kittyImageAliases; }
public Field<Bytes> replay() { return replay; }
public int rows() { return rows; }
public UInt64 surface() { return surface; }
@@ -56,6 +62,14 @@ public final class ResizedEvent implements WireValue, ByteAttachEvent, ProtocolE
if (!Wire.isMissing(rawData)) {
builder.data(Wire.bytes(rawData, "ResizedEvent.data"));
}
Object rawKittyGraphicsState = Wire.optional(object, "kitty_graphics_state");
if (!Wire.isMissing(rawKittyGraphicsState)) {
builder.kittyGraphicsState(KittyGraphicsState.fromWire(rawKittyGraphicsState));
}
Object rawKittyImageAliases = Wire.optional(object, "kitty_image_aliases");
if (!Wire.isMissing(rawKittyImageAliases)) {
builder.kittyImageAliases(Wire.array(rawKittyImageAliases, "ResizedEvent.kitty_image_aliases", item -> KittyImageAlias.fromWire(item)));
}
Object rawReplay = Wire.optional(object, "replay");
if (!Wire.isMissing(rawReplay)) {
builder.replay(Wire.bytes(rawReplay, "ResizedEvent.replay"));
@@ -74,6 +88,8 @@ public final class ResizedEvent implements WireValue, ByteAttachEvent, ProtocolE
Wire.put(object, "colors", colors);
Wire.put(object, "cols", cols);
Wire.put(object, "data", data);
Wire.put(object, "kitty_graphics_state", kittyGraphicsState);
Wire.put(object, "kitty_image_aliases", kittyImageAliases);
Wire.put(object, "replay", replay);
Wire.put(object, "rows", rows);
Wire.put(object, "surface", surface);
@@ -83,11 +99,11 @@ public final class ResizedEvent implements WireValue, ByteAttachEvent, ProtocolE
@Override
public boolean equals(Object other) {
if (!(other instanceof ResizedEvent that)) return false;
return Objects.equals(colors, that.colors) && Objects.equals(cols, that.cols) && Objects.equals(data, that.data) && Objects.equals(replay, that.replay) && Objects.equals(rows, that.rows) && Objects.equals(surface, that.surface);
return Objects.equals(colors, that.colors) && Objects.equals(cols, that.cols) && Objects.equals(data, that.data) && Objects.equals(kittyGraphicsState, that.kittyGraphicsState) && Objects.equals(kittyImageAliases, that.kittyImageAliases) && Objects.equals(replay, that.replay) && Objects.equals(rows, that.rows) && Objects.equals(surface, that.surface);
}
@Override
public int hashCode() { return Objects.hash(colors, cols, data, replay, rows, surface); }
public int hashCode() { return Objects.hash(colors, cols, data, kittyGraphicsState, kittyImageAliases, replay, rows, surface); }
@Override
public String toString() { return "ResizedEvent" + toWire(); }
@@ -97,6 +113,8 @@ public final class ResizedEvent implements WireValue, ByteAttachEvent, ProtocolE
private Integer cols;
private boolean colsSet;
private Field<Bytes> data = Field.omitted();
private Field<KittyGraphicsState> kittyGraphicsState = Field.omitted();
private Field<List<KittyImageAlias>> kittyImageAliases = Field.omitted();
private Field<Bytes> replay = Field.omitted();
private Integer rows;
private boolean rowsSet;
@@ -116,6 +134,14 @@ public final class ResizedEvent implements WireValue, ByteAttachEvent, ProtocolE
this.data = Field.of(value);
return this;
}
public Builder kittyGraphicsState(KittyGraphicsState value) {
this.kittyGraphicsState = Field.of(value);
return this;
}
public Builder kittyImageAliases(List<KittyImageAlias> value) {
this.kittyImageAliases = Field.of(value);
return this;
}
public Builder replay(Bytes value) {
this.replay = Field.of(value);
return this;
@@ -15,6 +15,8 @@ public final class VtStateEvent implements WireValue, ByteAttachEvent, ProtocolE
private final Field<TerminalColors> colors;
private final int cols;
private final Bytes data;
private final Field<KittyGraphicsState> kittyGraphicsState;
private final Field<List<KittyImageAlias>> kittyImageAliases;
private final int rows;
private final UInt64 surface;
@@ -24,6 +26,8 @@ public final class VtStateEvent implements WireValue, ByteAttachEvent, ProtocolE
this.cols = builder.cols;
if (!builder.dataSet) throw new IllegalArgumentException("data is required");
this.data = Wire.nonNull(builder.data, "data");
this.kittyGraphicsState = builder.kittyGraphicsState;
this.kittyImageAliases = builder.kittyImageAliases.map(value -> List.copyOf(value));
if (!builder.rowsSet) throw new IllegalArgumentException("rows is required");
this.rows = builder.rows;
if (!builder.surfaceSet) throw new IllegalArgumentException("surface is required");
@@ -35,6 +39,8 @@ public final class VtStateEvent implements WireValue, ByteAttachEvent, ProtocolE
public Field<TerminalColors> colors() { return colors; }
public int cols() { return cols; }
public Bytes data() { return data; }
public Field<KittyGraphicsState> kittyGraphicsState() { return kittyGraphicsState; }
public Field<List<KittyImageAlias>> kittyImageAliases() { return kittyImageAliases; }
public int rows() { return rows; }
public UInt64 surface() { return surface; }
@Override public String event() { return "vt-state"; }
@@ -51,6 +57,14 @@ public final class VtStateEvent implements WireValue, ByteAttachEvent, ProtocolE
builder.cols(Wire.uint16(rawCols, "VtStateEvent.cols"));
Object rawData = Wire.required(object, "data");
builder.data(Wire.bytes(rawData, "VtStateEvent.data"));
Object rawKittyGraphicsState = Wire.optional(object, "kitty_graphics_state");
if (!Wire.isMissing(rawKittyGraphicsState)) {
builder.kittyGraphicsState(KittyGraphicsState.fromWire(rawKittyGraphicsState));
}
Object rawKittyImageAliases = Wire.optional(object, "kitty_image_aliases");
if (!Wire.isMissing(rawKittyImageAliases)) {
builder.kittyImageAliases(Wire.array(rawKittyImageAliases, "VtStateEvent.kitty_image_aliases", item -> KittyImageAlias.fromWire(item)));
}
Object rawRows = Wire.required(object, "rows");
builder.rows(Wire.uint16(rawRows, "VtStateEvent.rows"));
Object rawSurface = Wire.required(object, "surface");
@@ -65,6 +79,8 @@ public final class VtStateEvent implements WireValue, ByteAttachEvent, ProtocolE
Wire.put(object, "colors", colors);
Wire.put(object, "cols", cols);
Wire.put(object, "data", data);
Wire.put(object, "kitty_graphics_state", kittyGraphicsState);
Wire.put(object, "kitty_image_aliases", kittyImageAliases);
Wire.put(object, "rows", rows);
Wire.put(object, "surface", surface);
return Collections.unmodifiableMap(object);
@@ -73,11 +89,11 @@ public final class VtStateEvent implements WireValue, ByteAttachEvent, ProtocolE
@Override
public boolean equals(Object other) {
if (!(other instanceof VtStateEvent that)) return false;
return Objects.equals(colors, that.colors) && Objects.equals(cols, that.cols) && Objects.equals(data, that.data) && Objects.equals(rows, that.rows) && Objects.equals(surface, that.surface);
return Objects.equals(colors, that.colors) && Objects.equals(cols, that.cols) && Objects.equals(data, that.data) && Objects.equals(kittyGraphicsState, that.kittyGraphicsState) && Objects.equals(kittyImageAliases, that.kittyImageAliases) && Objects.equals(rows, that.rows) && Objects.equals(surface, that.surface);
}
@Override
public int hashCode() { return Objects.hash(colors, cols, data, rows, surface); }
public int hashCode() { return Objects.hash(colors, cols, data, kittyGraphicsState, kittyImageAliases, rows, surface); }
@Override
public String toString() { return "VtStateEvent" + toWire(); }
@@ -88,6 +104,8 @@ public final class VtStateEvent implements WireValue, ByteAttachEvent, ProtocolE
private boolean colsSet;
private Bytes data;
private boolean dataSet;
private Field<KittyGraphicsState> kittyGraphicsState = Field.omitted();
private Field<List<KittyImageAlias>> kittyImageAliases = Field.omitted();
private Integer rows;
private boolean rowsSet;
private UInt64 surface;
@@ -107,6 +125,14 @@ public final class VtStateEvent implements WireValue, ByteAttachEvent, ProtocolE
this.dataSet = true;
return this;
}
public Builder kittyGraphicsState(KittyGraphicsState value) {
this.kittyGraphicsState = Field.of(value);
return this;
}
public Builder kittyImageAliases(List<KittyImageAlias> value) {
this.kittyImageAliases = Field.of(value);
return this;
}
public Builder rows(int value) {
this.rows = value;
this.rowsSet = true;
@@ -13,6 +13,8 @@ import java.util.Objects;
public final class VtStateResult implements WireValue {
private final int cols;
private final Bytes data;
private final Field<KittyGraphicsState> kittyGraphicsState;
private final Field<List<KittyImageAlias>> kittyImageAliases;
private final int rows;
private VtStateResult(Builder builder) {
@@ -20,6 +22,8 @@ public final class VtStateResult implements WireValue {
this.cols = builder.cols;
if (!builder.dataSet) throw new IllegalArgumentException("data is required");
this.data = Wire.nonNull(builder.data, "data");
this.kittyGraphicsState = builder.kittyGraphicsState;
this.kittyImageAliases = builder.kittyImageAliases.map(value -> List.copyOf(value));
if (!builder.rowsSet) throw new IllegalArgumentException("rows is required");
this.rows = builder.rows;
}
@@ -28,6 +32,8 @@ public final class VtStateResult implements WireValue {
public int cols() { return cols; }
public Bytes data() { return data; }
public Field<KittyGraphicsState> kittyGraphicsState() { return kittyGraphicsState; }
public Field<List<KittyImageAlias>> kittyImageAliases() { return kittyImageAliases; }
public int rows() { return rows; }
public static VtStateResult fromWire(Object value) {
@@ -37,6 +43,14 @@ public final class VtStateResult implements WireValue {
builder.cols(Wire.uint16(rawCols, "VtStateResult.cols"));
Object rawData = Wire.required(object, "data");
builder.data(Wire.bytes(rawData, "VtStateResult.data"));
Object rawKittyGraphicsState = Wire.optional(object, "kitty_graphics_state");
if (!Wire.isMissing(rawKittyGraphicsState)) {
builder.kittyGraphicsState(KittyGraphicsState.fromWire(rawKittyGraphicsState));
}
Object rawKittyImageAliases = Wire.optional(object, "kitty_image_aliases");
if (!Wire.isMissing(rawKittyImageAliases)) {
builder.kittyImageAliases(Wire.array(rawKittyImageAliases, "VtStateResult.kitty_image_aliases", item -> KittyImageAlias.fromWire(item)));
}
Object rawRows = Wire.required(object, "rows");
builder.rows(Wire.uint16(rawRows, "VtStateResult.rows"));
return builder.build();
@@ -47,6 +61,8 @@ public final class VtStateResult implements WireValue {
LinkedHashMap<String, Object> object = new LinkedHashMap<>();
Wire.put(object, "cols", cols);
Wire.put(object, "data", data);
Wire.put(object, "kitty_graphics_state", kittyGraphicsState);
Wire.put(object, "kitty_image_aliases", kittyImageAliases);
Wire.put(object, "rows", rows);
return Collections.unmodifiableMap(object);
}
@@ -54,11 +70,11 @@ public final class VtStateResult implements WireValue {
@Override
public boolean equals(Object other) {
if (!(other instanceof VtStateResult that)) return false;
return Objects.equals(cols, that.cols) && Objects.equals(data, that.data) && Objects.equals(rows, that.rows);
return Objects.equals(cols, that.cols) && Objects.equals(data, that.data) && Objects.equals(kittyGraphicsState, that.kittyGraphicsState) && Objects.equals(kittyImageAliases, that.kittyImageAliases) && Objects.equals(rows, that.rows);
}
@Override
public int hashCode() { return Objects.hash(cols, data, rows); }
public int hashCode() { return Objects.hash(cols, data, kittyGraphicsState, kittyImageAliases, rows); }
@Override
public String toString() { return "VtStateResult" + toWire(); }
@@ -68,6 +84,8 @@ public final class VtStateResult implements WireValue {
private boolean colsSet;
private Bytes data;
private boolean dataSet;
private Field<KittyGraphicsState> kittyGraphicsState = Field.omitted();
private Field<List<KittyImageAlias>> kittyImageAliases = Field.omitted();
private Integer rows;
private boolean rowsSet;
@@ -81,6 +99,14 @@ public final class VtStateResult implements WireValue {
this.dataSet = true;
return this;
}
public Builder kittyGraphicsState(KittyGraphicsState value) {
this.kittyGraphicsState = Field.of(value);
return this;
}
public Builder kittyImageAliases(List<KittyImageAlias> value) {
this.kittyImageAliases = Field.of(value);
return this;
}
public Builder rows(int value) {
this.rows = value;
this.rowsSet = true;
@@ -62,6 +62,7 @@ public final class ErgonomicsTest {
writeObject(
connection,
success(probe.get("id"), Map.of(
"epoch", 1L,
"rows", List.of(),
"start", 0L,
"total", 10L
@@ -76,6 +77,7 @@ public final class ErgonomicsTest {
writeObject(
connection,
success(page.get("id"), Map.of(
"epoch", 1L,
"rows", List.of(
renderRow(0, "eight"),
renderRow(1, "nine"),
@@ -20,8 +20,8 @@ public final class GeneratedCoverageTest {
public static void main(String[] args) throws Exception {
check(Protocol.VERSION == 10, "protocol version");
check("1.0.0".equals(Protocol.SDK_VERSION), "SDK release version");
check(Commands.ALL.size() == 91, "all 91 commands generated");
check(Events.ALL.size() == 44, "all 44 events generated");
check(Commands.ALL.size() == 92, "all 92 commands generated");
check(Events.ALL.size() == 45, "all 45 events generated");
Map<String, Method> methods = Arrays.stream(GeneratedCmuxClient.class.getDeclaredMethods())
.filter(method -> Modifier.isPublic(method.getModifiers()))
@@ -7,32 +7,32 @@
},
{
"path": "_schema.py",
"sha256": "c0be4080fa1bf06841cc8560d53eaaa9360e5867683553c03b1c4861f5d5a29b",
"size": 127251
"sha256": "43f1495b6ecb8ab6848981aeddab98adc0e6bcb095dc60f40d4f8ca8497cfd75",
"size": 136058
},
{
"path": "client.py",
"sha256": "1ae5c4ac44d1d2b5b33db4a400529cf63e7084d1833b86d557e19eff2aa8deb8",
"size": 33487
"sha256": "c89f1a0cc1724d1ddaedd656082b9818aa6c62153a72bcac2e4d75c8ec566757",
"size": 33705
},
{
"path": "codec.py",
"sha256": "065e6e4a2a39a6ca78c0f0c351237e36b6d7cdc5ac9b861eb768f15b01217c59",
"size": 25384
"sha256": "f0d6c6220dbee14c3d125b144ea765d3b39b43eb4cd2a635c1d5ee7facce5a3e",
"size": 26046
},
{
"path": "metadata.py",
"sha256": "0332e8cab5864222cb55edc006206059eba5c3338dd6e80579c52439756623ef",
"size": 40517
"sha256": "cae9a6576b6b4c3494a7e3b85ab173b86846dd56a598af4c396b678781f21c49",
"size": 40788
},
{
"path": "models.py",
"sha256": "d886e2badc473844bf19ae97572c82c49d03ccd96ac151d4ccbedbe58d32c916",
"size": 70223
"sha256": "81c58f09ce4c59679a667c19d83504343634e00a4e71eea02440c28a3db19686",
"size": 74913
}
],
"format": 1,
"ir_sha256": "4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e",
"ir_sha256": "17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350",
"language": "python",
"mux_protocol": 10,
"schema_version": 2
File diff suppressed because one or more lines are too long
@@ -102,6 +102,9 @@ class GeneratedClientMixin:
def focus_pane(self, pane: Id) -> EmptyResult:
return self._invoke_command('focus-pane', FocusPaneRequest(pane=pane))
def get_cell_pixels(self) -> GetCellPixelsResult:
return self._invoke_command('get-cell-pixels', GetCellPixelsRequest())
def get_frontend_projection(self, frontend: str, scope: str, subject_key: str) -> FrontendProjection:
return self._invoke_command('get-frontend-projection', GetFrontendProjectionRequest(frontend=frontend, scope=scope, subject_key=subject_key))
@@ -316,6 +319,7 @@ GeneratedClientMixin.detach_client.__cmux_command__ = COMMANDS['detach-client']
GeneratedClientMixin.export_layout.__cmux_command__ = COMMANDS['export-layout']
GeneratedClientMixin.focus_direction.__cmux_command__ = COMMANDS['focus-direction']
GeneratedClientMixin.focus_pane.__cmux_command__ = COMMANDS['focus-pane']
GeneratedClientMixin.get_cell_pixels.__cmux_command__ = COMMANDS['get-cell-pixels']
GeneratedClientMixin.get_frontend_projection.__cmux_command__ = COMMANDS['get-frontend-projection']
GeneratedClientMixin.identify.__cmux_command__ = COMMANDS['identify']
GeneratedClientMixin.ids.__cmux_command__ = COMMANDS['ids']
@@ -21,6 +21,7 @@ MODEL_BY_PATH = {
'types/BrowserFrame': models.BrowserFrame,
'types/CellPixelFailure': models.CellPixelFailure,
'types/CellPixelResize': models.CellPixelResize,
'types/CellPixelSurface': models.CellPixelSurface,
'types/ClientInfo': models.ClientInfo,
'types/ClientSize': models.ClientSize,
'types/CloseTerminalResult': models.CloseTerminalResult,
@@ -34,9 +35,12 @@ MODEL_BY_PATH = {
'types/ExportedPane': models.ExportedPane,
'types/FocusDirectionResult': models.FocusDirectionResult,
'types/FrontendProjection': models.FrontendProjection,
'types/GetCellPixelsResult': models.GetCellPixelsResult,
'types/IdMapping': models.IdMapping,
'types/IdentifyResult': models.IdentifyResult,
'types/IdsResult': models.IdsResult,
'types/KittyGraphicsState': models.KittyGraphicsState,
'types/KittyImageAlias': models.KittyImageAlias,
'types/Layout/variants/leaf': models.LayoutLeaf,
'types/Layout/variants/split': models.LayoutSplit,
'types/Layout/variants/stack': models.LayoutStack,
@@ -56,6 +60,10 @@ MODEL_BY_PATH = {
'types/ReadScreenResult': models.ReadScreenResult,
'types/ReadScrollbackResult': models.ReadScrollbackResult,
'types/RenderCursor': models.RenderCursor,
'types/RenderGraphicImage': models.RenderGraphicImage,
'types/RenderGraphicPlacement': models.RenderGraphicPlacement,
'types/RenderGraphics': models.RenderGraphics,
'types/RenderGraphicsDelta': models.RenderGraphicsDelta,
'types/RenderRow': models.RenderRow,
'types/RenderRun': models.RenderRun,
'types/ReportAgentResult': models.ReportAgentResult,
@@ -112,6 +120,7 @@ MODEL_BY_PATH = {
'commands/export-layout/request': models.ExportLayoutRequest,
'commands/focus-direction/request': models.FocusDirectionRequest,
'commands/focus-pane/request': models.FocusPaneRequest,
'commands/get-cell-pixels/request': models.GetCellPixelsRequest,
'commands/get-frontend-projection/request': models.GetFrontendProjectionRequest,
'commands/identify/request': models.IdentifyRequest,
'commands/ids/request': models.IdsRequest,
@@ -186,6 +195,7 @@ MODEL_BY_PATH = {
'events/empty/payload': models.EmptyEvent,
'events/frame/payload': models.FrameEvent,
'events/frontend-projection-changed/payload': models.FrontendProjectionChangedEvent,
'events/graphics-status/payload': models.GraphicsStatusEvent,
'events/layout-changed/payload': models.LayoutChangedEvent,
'events/notification/payload': models.NotificationEvent,
'events/output/payload': models.OutputEvent,
@@ -228,6 +238,7 @@ ENUM_BY_PATH = {
'types/CursorStyle': models.CursorStyle,
'types/NotificationLevel': models.NotificationLevel,
'types/PaneDirection': models.PaneDirection,
'types/RenderGraphicFormat': models.RenderGraphicFormat,
'types/RenderUnderline': models.RenderUnderline,
'types/SplitDirection': models.SplitDirection,
'types/TerminalKey': models.TerminalKey,
@@ -8,7 +8,7 @@ from typing import Mapping, Optional, Tuple
SCHEMA_VERSION = 2
MUX_PROTOCOL = 10
IR_SHA256 = '4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e'
IR_SHA256 = '17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350'
@dataclass(frozen=True)
@@ -440,6 +440,16 @@ COMMANDS = {
'pane': CommandFieldMetadata(None, None),
},
),
'get-cell-pixels': CommandMetadata(
'get-cell-pixels',
'frontend',
6,
None,
('frontend',),
None,
{
},
),
'get-frontend-projection': CommandMetadata(
'get-frontend-projection',
'control',
@@ -1238,6 +1248,7 @@ EVENTS = {
'empty': EventMetadata('empty', 5, None, ('subscribe',), 'emitted'),
'frame': EventMetadata('frame', 6, None, ('attach-browser',), 'emitted'),
'frontend-projection-changed': EventMetadata('frontend-projection-changed', 7, None, ('subscribe',), 'emitted'),
'graphics-status': EventMetadata('graphics-status', 10, None, ('subscribe',), 'emitted'),
'layout-changed': EventMetadata('layout-changed', 6, None, ('subscribe',), 'emitted'),
'notification': EventMetadata('notification', 6, None, ('subscribe', 'attach-byte', 'attach-browser'), 'emitted'),
'output': EventMetadata('output', 5, None, ('attach-byte',), 'emitted'),
@@ -74,6 +74,10 @@ class PaneDirection(str, Enum):
UP = 'up'
DOWN = 'down'
class RenderGraphicFormat(str, Enum):
RGB = 'rgb'
RGBA = 'rgba'
class RenderUnderline(str, Enum):
SINGLE = 'single'
DOUBLE = 'double'
@@ -262,6 +266,14 @@ class CellPixelResize:
rows: int
@dataclass(frozen=True)
class CellPixelSurface:
__cmux_schema_path__: ClassVar[str] = 'types/CellPixelSurface'
surface: Id
height_px: int
width_px: int
@dataclass(frozen=True)
class ClientInfo:
__cmux_schema_path__: ClassVar[str] = 'types/ClientInfo'
@@ -375,6 +387,14 @@ class FrontendProjection:
replayed: Union[bool, MissingType] = field(default=MISSING)
@dataclass(frozen=True)
class GetCellPixelsResult:
__cmux_schema_path__: ClassVar[str] = 'types/GetCellPixelsResult'
height_px: int
surfaces: List[CellPixelSurface]
width_px: int
@dataclass(frozen=True)
class IdMapping:
__cmux_schema_path__: ClassVar[str] = 'types/IdMapping'
@@ -407,6 +427,27 @@ class IdsResult:
ids: List[IdMapping]
@dataclass(frozen=True)
class KittyGraphicsState:
__cmux_schema_path__: ClassVar[str] = 'types/KittyGraphicsState'
alternate_next_image_id: int
alternate_replay_next_image_id: int
image_bytes: int
images: int
inflight_bytes: int
placements: int
primary_next_image_id: int
primary_replay_next_image_id: int
replay_cursor_offset: int
@dataclass(frozen=True)
class KittyImageAlias:
__cmux_schema_path__: ClassVar[str] = 'types/KittyImageAlias'
image_id: int
image_number: int
@dataclass(frozen=True)
class LayoutLeaf:
__cmux_schema_path__: ClassVar[str] = 'types/Layout/variants/leaf'
@@ -562,6 +603,7 @@ class ReadScreenResult:
@dataclass(frozen=True)
class ReadScrollbackResult:
__cmux_schema_path__: ClassVar[str] = 'types/ReadScrollbackResult'
epoch: int
rows: List[RenderRow]
start: int
total: int
@@ -578,6 +620,61 @@ class RenderCursor:
y: int
@dataclass(frozen=True)
class RenderGraphicImage:
__cmux_schema_path__: ClassVar[str] = 'types/RenderGraphicImage'
data: Base64
format: RenderGraphicFormat
generation: int
height: int
id: int
width: int
@dataclass(frozen=True)
class RenderGraphicPlacement:
__cmux_schema_path__: ClassVar[str] = 'types/RenderGraphicPlacement'
columns: int
grid_cols: int
grid_rows: int
image_id: int
ordinal: int
pixel_height: int
pixel_width: int
placement_id: int
rows: int
source_height: int
source_width: int
source_x: int
source_y: int
viewport_col: int
viewport_row: int
viewport_visible: bool
x_offset: int
y_offset: int
z: int
anchor_col: Union[int, MissingType] = field(default=MISSING)
anchor_row: Union[int, MissingType] = field(default=MISSING)
@dataclass(frozen=True)
class RenderGraphics:
__cmux_schema_path__: ClassVar[str] = 'types/RenderGraphics'
generation: int
placements: List[RenderGraphicPlacement]
images: Union[List[RenderGraphicImage], MissingType] = field(default=MISSING)
removed_image_ids: Union[List[int], MissingType] = field(default=MISSING)
@dataclass(frozen=True)
class RenderGraphicsDelta:
__cmux_schema_path__: ClassVar[str] = 'types/RenderGraphicsDelta'
generation: int
images: Union[List[RenderGraphicImage], MissingType] = field(default=MISSING)
placements: Union[List[RenderGraphicPlacement], MissingType] = field(default=MISSING)
removed_image_ids: Union[List[int], MissingType] = field(default=MISSING)
@dataclass(frozen=True)
class RenderRow:
__cmux_schema_path__: ClassVar[str] = 'types/RenderRow'
@@ -815,6 +912,8 @@ class VtStateResult:
cols: int
data: Base64
rows: int
kitty_graphics_state: Union[KittyGraphicsState, MissingType] = field(default=MISSING)
kitty_image_aliases: Union[List[KittyImageAlias], MissingType] = field(default=MISSING)
@dataclass(frozen=True)
@@ -1110,6 +1209,12 @@ class FocusPaneRequest:
pane: Id
@dataclass(frozen=True)
class GetCellPixelsRequest:
__cmux_schema_path__: ClassVar[str] = 'commands/get-cell-pixels/request'
pass
@dataclass(frozen=True)
class GetFrontendProjectionRequest:
__cmux_schema_path__: ClassVar[str] = 'commands/get-frontend-projection/request'
@@ -1724,6 +1829,21 @@ class FrontendProjectionChangedEvent(EventBase):
raw: Mapping[str, Any] = field(default_factory=dict, repr=False, compare=False, metadata={'cmux_skip': True})
@dataclass(frozen=True)
class GraphicsStatusEvent(EventBase):
__cmux_schema_path__: ClassVar[str] = 'events/graphics-status/payload'
event: Literal['graphics-status']
kind: Literal['kitty-image-budget-worker-start-failed', 'kitty-image-budget-update-failed', 'cell-pixel-update-retries-exhausted']
attempts: Union[int, MissingType] = field(default=MISSING)
cell_height: Union[int, MissingType] = field(default=MISSING)
cell_width: Union[int, MissingType] = field(default=MISSING)
error: Union[str, MissingType] = field(default=MISSING)
remaining: Union[int, MissingType] = field(default=MISSING)
retry_exhausted: Union[bool, MissingType] = field(default=MISSING)
summary: Union[str, MissingType] = field(default=MISSING)
raw: Mapping[str, Any] = field(default_factory=dict, repr=False, compare=False, metadata={'cmux_skip': True})
@dataclass(frozen=True)
class LayoutChangedEvent(EventBase):
__cmux_schema_path__: ClassVar[str] = 'events/layout-changed/payload'
@@ -1817,6 +1937,8 @@ class RenderDeltaEvent(EventBase):
rows: List[RenderRow]
default_bg: Union[ColorHex, MissingType] = field(default=MISSING)
default_fg: Union[ColorHex, MissingType] = field(default=MISSING)
graphics: Union[RenderGraphicsDelta, MissingType] = field(default=MISSING)
history_epoch: Union[int, MissingType] = field(default=MISSING)
scrollback_rows: Union[int, MissingType] = field(default=MISSING)
size: Union[Size, MissingType] = field(default=MISSING)
raw: Mapping[str, Any] = field(default_factory=dict, repr=False, compare=False, metadata={'cmux_skip': True})
@@ -1830,9 +1952,11 @@ class RenderStateEvent(EventBase):
default_bg: ColorHex
default_fg: ColorHex
event: Literal['render-state']
history_epoch: int
rows: List[RenderRow]
scrollback_rows: int
size: Size
graphics: Union[RenderGraphics, MissingType] = field(default=MISSING)
raw: Mapping[str, Any] = field(default_factory=dict, repr=False, compare=False, metadata={'cmux_skip': True})
@@ -1845,6 +1969,8 @@ class ResizedEvent(EventBase):
rows: int
colors: Union[TerminalColors, MissingType] = field(default=MISSING)
data: Union[Base64, MissingType] = field(default=MISSING)
kitty_graphics_state: Union[KittyGraphicsState, MissingType] = field(default=MISSING)
kitty_image_aliases: Union[List[KittyImageAlias], MissingType] = field(default=MISSING)
replay: Union[Base64, MissingType] = field(default=MISSING)
raw: Mapping[str, Any] = field(default_factory=dict, repr=False, compare=False, metadata={'cmux_skip': True})
@@ -2013,6 +2139,8 @@ class VtStateEvent(EventBase):
event: Literal['vt-state']
rows: int
colors: Union[TerminalColors, MissingType] = field(default=MISSING)
kitty_graphics_state: Union[KittyGraphicsState, MissingType] = field(default=MISSING)
kitty_image_aliases: Union[List[KittyImageAlias], MissingType] = field(default=MISSING)
raw: Mapping[str, Any] = field(default_factory=dict, repr=False, compare=False, metadata={'cmux_skip': True})
@@ -2093,7 +2221,7 @@ Layout = Union[LayoutLeaf, LayoutSplit, LayoutStack]
LayoutUndoResult = Union[LayoutUndoUndone, LayoutUndoConfirmationRequired]
Pane = Union[LivePane, DeadPane]
KnownEvent = Union[BellEvent, BrowserStateEvent, ClientAttachedEvent, ClientChangedEvent, ClientDetachedEvent, ClientListInvalidatedEvent, ColorsChangedEvent, ConfigReloadRequestedEvent, DetachedEvent, EmptyEvent, FrameEvent, FrontendProjectionChangedEvent, LayoutChangedEvent, NotificationEvent, OutputEvent, OverflowEvent, PairingRequestedEvent, PairingResolvedEvent, PaneAddedEvent, PaneClosedEvent, RenderDeltaEvent, RenderStateEvent, ResizedEvent, ScreenAddedEvent, ScreenClosedEvent, ScreenRenamedEvent, ScrollChangedEvent, StatusEvent, SurfaceExitedEvent, SurfaceOutputEvent, SurfaceResizeFailedEvent, SurfaceResizedEvent, TabAddedEvent, TabClosedEvent, TabRenamedEvent, TerminalRegistryChangedEvent, TitleChangedEvent, TreeChangedEvent, VtStateEvent, WindowTitleRequestedEvent, WorkspaceAddedEvent, WorkspaceClosedEvent, WorkspaceMovedEvent, WorkspaceRenamedEvent]
KnownEvent = Union[BellEvent, BrowserStateEvent, ClientAttachedEvent, ClientChangedEvent, ClientDetachedEvent, ClientListInvalidatedEvent, ColorsChangedEvent, ConfigReloadRequestedEvent, DetachedEvent, EmptyEvent, FrameEvent, FrontendProjectionChangedEvent, GraphicsStatusEvent, LayoutChangedEvent, NotificationEvent, OutputEvent, OverflowEvent, PairingRequestedEvent, PairingResolvedEvent, PaneAddedEvent, PaneClosedEvent, RenderDeltaEvent, RenderStateEvent, ResizedEvent, ScreenAddedEvent, ScreenClosedEvent, ScreenRenamedEvent, ScrollChangedEvent, StatusEvent, SurfaceExitedEvent, SurfaceOutputEvent, SurfaceResizeFailedEvent, SurfaceResizedEvent, TabAddedEvent, TabClosedEvent, TabRenamedEvent, TerminalRegistryChangedEvent, TitleChangedEvent, TreeChangedEvent, VtStateEvent, WindowTitleRequestedEvent, WorkspaceAddedEvent, WorkspaceClosedEvent, WorkspaceMovedEvent, WorkspaceRenamedEvent]
AnyEvent = Union[KnownEvent, UnknownEvent]
__all__ = [
@@ -2110,6 +2238,7 @@ __all__ = [
'CursorStyle',
'NotificationLevel',
'PaneDirection',
'RenderGraphicFormat',
'RenderUnderline',
'SplitDirection',
'TerminalKey',
@@ -2121,6 +2250,7 @@ __all__ = [
'BrowserFrame',
'CellPixelFailure',
'CellPixelResize',
'CellPixelSurface',
'ClientInfo',
'ClientSize',
'CloseTerminalResult',
@@ -2134,9 +2264,12 @@ __all__ = [
'ExportedPane',
'FocusDirectionResult',
'FrontendProjection',
'GetCellPixelsResult',
'IdMapping',
'IdentifyResult',
'IdsResult',
'KittyGraphicsState',
'KittyImageAlias',
'LayoutLeaf',
'LayoutSplit',
'LayoutStack',
@@ -2156,6 +2289,10 @@ __all__ = [
'ReadScreenResult',
'ReadScrollbackResult',
'RenderCursor',
'RenderGraphicImage',
'RenderGraphicPlacement',
'RenderGraphics',
'RenderGraphicsDelta',
'RenderRow',
'RenderRun',
'ReportAgentResult',
@@ -2212,6 +2349,7 @@ __all__ = [
'ExportLayoutRequest',
'FocusDirectionRequest',
'FocusPaneRequest',
'GetCellPixelsRequest',
'GetFrontendProjectionRequest',
'IdentifyRequest',
'IdsRequest',
@@ -2286,6 +2424,7 @@ __all__ = [
'EmptyEvent',
'FrameEvent',
'FrontendProjectionChangedEvent',
'GraphicsStatusEvent',
'LayoutChangedEvent',
'NotificationEvent',
'OutputEvent',
@@ -175,9 +175,9 @@ class RenderAndScrollbackTests(unittest.TestCase):
def test_read_scrollback_tail_probes_then_reads_tail(self) -> None:
client = CmuxClient.__new__(CmuxClient)
calls = []
tail = ReadScrollbackResult(rows=[], start=7, total=10)
tail = ReadScrollbackResult(epoch=1, rows=[], start=7, total=10)
responses = [
ReadScrollbackResult(rows=[], start=0, total=10),
ReadScrollbackResult(epoch=1, rows=[], start=0, total=10),
tail,
]
@@ -193,7 +193,7 @@ class RenderAndScrollbackTests(unittest.TestCase):
def test_read_scrollback_tail_reuses_probe_when_it_contains_tail(self) -> None:
client = CmuxClient.__new__(CmuxClient)
calls = []
probe = ReadScrollbackResult(rows=[], start=0, total=2)
probe = ReadScrollbackResult(epoch=1, rows=[], start=0, total=2)
def read_scrollback(surface, start, count):
calls.append((surface, start, count))
@@ -22,7 +22,7 @@ from cmux.raw._generated.metadata import COMMANDS, EVENTS, IR_SHA256
class GeneratedProtocolTests(unittest.TestCase):
def test_protocol_ten_inventory_is_exhaustive(self) -> None:
self.assertEqual(MUX_PROTOCOL, 10)
self.assertEqual(len(COMMANDS), 91)
self.assertEqual(len(COMMANDS), 92)
self.assertEqual(set(COMMANDS), set(SCHEMA["commands"]))
self.assertEqual(set(EVENTS), set(SCHEMA["events"]))
self.assertEqual(len(IR_SHA256), 64)
@@ -2,32 +2,32 @@
"files": [
{
"path": "commands.rs",
"sha256": "6c2d23f1b0e6464744e55c667a1c4c4f28fceb0a6f21ce82d30fbebd4740346f",
"size": 56086
"sha256": "6e161bf28e80dfae38fa688f02ecbeb116d56f4f02cc5c6cc6e583695420f387",
"size": 56378
},
{
"path": "events.rs",
"sha256": "750b3731ae011fca22fcd2dd08976e2b29b0530d2448eb0e94e5b2011347dcfe",
"size": 37107
"sha256": "6655c910e4838b75b7708e237dc772803838c9f601cd7baeea7e7ffcdc5625f1",
"size": 40682
},
{
"path": "metadata.rs",
"sha256": "48f6c26fcb938ba336c87338634034409df73e60e206578bbce648f7655506b0",
"size": 33786
"sha256": "7241ba9c80314eeeacdb5078c1f074b02264eec397bcdaa868da2f687a1b39ba",
"size": 34262
},
{
"path": "mod.rs",
"sha256": "c6ff9bc6750f92315d1036d82b0379f5fec91327ea3354304a0a5366ed7a3ba9",
"sha256": "124b3acb2e08e6545b87104a6fc333c001d285b1b040bde6e7d0c7e9d4e4972f",
"size": 365
},
{
"path": "types.rs",
"sha256": "2a4394049238dbcc8276459227ad629e46effc63b758de7595ec773c609587ba",
"size": 30346
"sha256": "c004e8dbc59fdaa092196a572313aea19ee78cc4ab00a8e172c3b7573793980e",
"size": 34273
}
],
"format": 1,
"ir_sha256": "4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e",
"ir_sha256": "17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350",
"language": "rust",
"mux_protocol": 10,
"schema_version": 2
@@ -1,5 +1,5 @@
// This file is generated. Do not edit by hand.
// cmux-tui mux protocol 10, IR 4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e.
// cmux-tui mux protocol 10, IR 17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350.
// The emitter owns this layout so generation is independent of the installed rustfmt.
use super::metadata::*;
@@ -434,6 +434,11 @@ pub struct FocusPaneRequest {
#[rustfmt::skip]
pub type FocusPaneResult = T::EmptyResult;
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct GetCellPixelsRequest {
}
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GetFrontendProjectionRequest {
@@ -1315,6 +1320,10 @@ impl CmuxClient {
self.execute(&FOCUS_PANE_METADATA, &request)
}
pub fn get_cell_pixels(&mut self, request: GetCellPixelsRequest) -> Result<T::GetCellPixelsResult> {
self.execute(&GET_CELL_PIXELS_METADATA, &request)
}
pub fn get_frontend_projection(&mut self, request: GetFrontendProjectionRequest) -> Result<GetFrontendProjectionResult> {
self.execute(&GET_FRONTEND_PROJECTION_METADATA, &request)
}
+58 -1
View File
@@ -1,5 +1,5 @@
// This file is generated. Do not edit by hand.
// cmux-tui mux protocol 10, IR 4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e.
// cmux-tui mux protocol 10, IR 17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350.
// The emitter owns this layout so generation is independent of the installed rustfmt.
use super::metadata::*;
@@ -135,6 +135,37 @@ pub struct FrontendProjectionChangedEvent {
pub subject_key: String,
}
#[rustfmt::skip]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum GraphicsStatusEventKind {
#[serde(rename = "kitty-image-budget-worker-start-failed")]
KittyImageBudgetWorkerStartFailed,
#[serde(rename = "kitty-image-budget-update-failed")]
KittyImageBudgetUpdateFailed,
#[serde(rename = "cell-pixel-update-retries-exhausted")]
CellPixelUpdateRetriesExhausted,
}
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GraphicsStatusEvent {
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub attempts: Option<u16>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub cell_height: Option<u16>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub cell_width: Option<u16>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub kind: GraphicsStatusEventKind,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub remaining: Option<u64>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub retry_exhausted: Option<bool>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
}
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LayoutChangedEvent {
@@ -214,6 +245,10 @@ pub struct RenderDeltaEvent {
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub default_fg: Option<T::ColorHex>,
pub full: bool,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub graphics: Option<T::RenderGraphicsDelta>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub history_epoch: Option<u64>,
pub rows: Vec<T::RenderRow>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub scrollback_rows: Option<u32>,
@@ -228,6 +263,9 @@ pub struct RenderStateEvent {
pub cursor: T::RenderCursor,
pub default_bg: T::ColorHex,
pub default_fg: T::ColorHex,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub graphics: Option<T::RenderGraphics>,
pub history_epoch: u64,
pub rows: Vec<T::RenderRow>,
pub scrollback_rows: u32,
pub size: T::Size,
@@ -244,6 +282,10 @@ pub struct ResizedEvent {
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub data: Option<T::Base64>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub kitty_graphics_state: Option<T::KittyGraphicsState>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub kitty_image_aliases: Option<Vec<T::KittyImageAlias>>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub replay: Option<T::Base64>,
pub rows: u16,
pub surface: T::Id,
@@ -382,6 +424,10 @@ pub struct VtStateEvent {
pub colors: Option<T::TerminalColors>,
pub cols: u16,
pub data: T::Base64,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub kitty_graphics_state: Option<T::KittyGraphicsState>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub kitty_image_aliases: Option<Vec<T::KittyImageAlias>>,
pub rows: u16,
pub surface: T::Id,
}
@@ -475,6 +521,7 @@ pub enum Event {
Empty(EmptyEvent),
Frame(FrameEvent),
FrontendProjectionChanged(FrontendProjectionChangedEvent),
GraphicsStatus(GraphicsStatusEvent),
LayoutChanged(LayoutChangedEvent),
Notification(NotificationEvent),
Output(OutputEvent),
@@ -526,6 +573,7 @@ impl Event {
Self::Empty(_) => Some("empty"),
Self::Frame(_) => Some("frame"),
Self::FrontendProjectionChanged(_) => Some("frontend-projection-changed"),
Self::GraphicsStatus(_) => Some("graphics-status"),
Self::LayoutChanged(_) => Some("layout-changed"),
Self::Notification(_) => Some("notification"),
Self::Output(_) => Some("output"),
@@ -576,6 +624,7 @@ impl Event {
Self::Empty(_) => Some(&EMPTY_EVENT_METADATA),
Self::Frame(_) => Some(&FRAME_EVENT_METADATA),
Self::FrontendProjectionChanged(_) => Some(&FRONTEND_PROJECTION_CHANGED_EVENT_METADATA),
Self::GraphicsStatus(_) => Some(&GRAPHICS_STATUS_EVENT_METADATA),
Self::LayoutChanged(_) => Some(&LAYOUT_CHANGED_EVENT_METADATA),
Self::Notification(_) => Some(&NOTIFICATION_EVENT_METADATA),
Self::Output(_) => Some(&OUTPUT_EVENT_METADATA),
@@ -713,6 +762,14 @@ pub fn decode_event(raw: Value) -> Event {
decode_error: Some(error.to_string()),
}),
},
Some("graphics-status") => match serde_json::from_value::<GraphicsStatusEvent>(raw.clone()) {
Ok(event) => Event::GraphicsStatus(event),
Err(error) => Event::Unknown(UnknownEvent {
name,
raw,
decode_error: Some(error.to_string()),
}),
},
Some("layout-changed") => match serde_json::from_value::<LayoutChangedEvent>(raw.clone()) {
Ok(event) => Event::LayoutChanged(event),
Err(error) => Event::Unknown(UnknownEvent {
@@ -1,12 +1,12 @@
// This file is generated. Do not edit by hand.
// cmux-tui mux protocol 10, IR 4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e.
// cmux-tui mux protocol 10, IR 17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350.
// The emitter owns this layout so generation is independent of the installed rustfmt.
use crate::{CommandMetadata, EventMetadata, ProfileMetadata, StreamMetadata};
pub const SDK_SCHEMA_VERSION: u32 = 2;
pub const MUX_PROTOCOL_VERSION: u32 = 10;
pub const SDK_IR_SHA256: &str = "4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e";
pub const SDK_IR_SHA256: &str = "17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350";
#[rustfmt::skip]
pub const CONTROL_PROFILE: ProfileMetadata = ProfileMetadata {
@@ -314,6 +314,15 @@ pub const FOCUS_PANE_METADATA: CommandMetadata = CommandMetadata {
stream: None,
};
#[rustfmt::skip]
pub const GET_CELL_PIXELS_METADATA: CommandMetadata = CommandMetadata {
name: "get-cell-pixels",
since: 6,
capability: None,
authority: "frontend",
stream: None,
};
#[rustfmt::skip]
pub const GET_FRONTEND_PROJECTION_METADATA: CommandMetadata = CommandMetadata {
name: "get-frontend-projection",
@@ -971,6 +980,15 @@ pub const FRONTEND_PROJECTION_CHANGED_EVENT_METADATA: EventMetadata = EventMetad
emission: "emitted",
};
#[rustfmt::skip]
pub const GRAPHICS_STATUS_EVENT_METADATA: EventMetadata = EventMetadata {
name: "graphics-status",
since: 10,
capability: None,
streams: &["subscribe"],
emission: "emitted",
};
#[rustfmt::skip]
pub const LAYOUT_CHANGED_EVENT_METADATA: EventMetadata = EventMetadata {
name: "layout-changed",
@@ -1262,6 +1280,6 @@ pub const WORKSPACE_RENAMED_EVENT_METADATA: EventMetadata = EventMetadata {
#[rustfmt::skip]
pub static PROFILES: &[ProfileMetadata] = &[CONTROL_PROFILE, FRONTEND_PROFILE, LOCAL_ADMIN_PROFILE, PROVIDER_AUTHORITY_PROFILE];
#[rustfmt::skip]
pub static COMMANDS: &[CommandMetadata] = &[APPLY_LAYOUT_METADATA, ATTACH_SURFACE_METADATA, BROWSER_ACTIVATE_METADATA, BROWSER_BACK_METADATA, BROWSER_FORWARD_METADATA, BROWSER_FRAME_PRESENTED_METADATA, BROWSER_INSERT_TEXT_METADATA, BROWSER_KEY_METADATA, BROWSER_KEY_PRESS_METADATA, BROWSER_MOUSE_METADATA, BROWSER_MOUSE_GUARDED_METADATA, BROWSER_NAVIGATE_METADATA, BROWSER_RELOAD_METADATA, BROWSER_WHEEL_METADATA, BROWSER_WHEEL_GUARDED_METADATA, CLEAR_HISTORY_METADATA, CLEAR_WINDOW_TITLE_METADATA, CLOSE_PANE_METADATA, CLOSE_PROVIDER_MANAGED_WORKSPACE_METADATA, CLOSE_SCREEN_METADATA, CLOSE_SURFACE_METADATA, CLOSE_TERMINAL_METADATA, CLOSE_WORKSPACE_METADATA, COPY_METADATA, CREATE_TERMINAL_METADATA, CREATE_WORKSPACE_METADATA, DETACH_CLIENT_METADATA, EXPORT_LAYOUT_METADATA, FOCUS_DIRECTION_METADATA, FOCUS_PANE_METADATA, GET_FRONTEND_PROJECTION_METADATA, IDENTIFY_METADATA, IDS_METADATA, LIST_AGENTS_METADATA, LIST_CLIENTS_METADATA, LIST_TERMINALS_METADATA, LIST_WORKSPACES_METADATA, MARK_WORKSPACES_PROVIDER_MANAGED_METADATA, MINT_TERMINAL_RENDERER_METADATA, MOVE_TAB_METADATA, MOVE_TERMINAL_METADATA, MOVE_WORKSPACE_METADATA, NEW_BROWSER_TAB_METADATA, NEW_PANE_METADATA, NEW_PANE_RIGHT_METADATA, NEW_SCREEN_METADATA, NEW_TAB_METADATA, NEW_WORKSPACE_METADATA, NOTIFY_METADATA, PAIRING_RESPONSE_METADATA, PANE_NEIGHBOR_METADATA, PING_METADATA, PROCESS_INFO_METADATA, PUT_FRONTEND_PROJECTION_METADATA, READ_SCREEN_METADATA, READ_SCROLLBACK_METADATA, RELEASE_SURFACE_SIZE_METADATA, RELOAD_CONFIG_METADATA, RENAME_PANE_METADATA, RENAME_PROVIDER_MANAGED_WORKSPACE_METADATA, RENAME_SCREEN_METADATA, RENAME_SURFACE_METADATA, RENAME_WORKSPACE_METADATA, REPORT_AGENT_METADATA, RESIZE_SURFACE_METADATA, RESOLVE_TERMINAL_METADATA, RUN_METADATA, SCROLL_SURFACE_METADATA, SELECT_SCREEN_METADATA, SELECT_TAB_METADATA, SELECT_WORKSPACE_METADATA, SEND_METADATA, SEND_KEY_METADATA, SET_CELL_PIXELS_METADATA, SET_CLIENT_INFO_METADATA, SET_CLIENT_SIZING_METADATA, SET_DEFAULT_COLORS_METADATA, SET_RATIO_METADATA, SET_SPLIT_RATIO_METADATA, SET_VIEWPORT_PANE_WIDTH_METADATA, SET_WINDOW_TITLE_METADATA, SHUTDOWN_DAEMON_METADATA, SIDEBAR_PLUGIN_METADATA, SPLIT_METADATA, SUBSCRIBE_METADATA, SWAP_PANE_METADATA, TERMINAL_EVENTS_METADATA, UNDO_LAYOUT_METADATA, VT_STATE_METADATA, WAIT_FOR_METADATA, ZOOM_PANE_METADATA];
pub static COMMANDS: &[CommandMetadata] = &[APPLY_LAYOUT_METADATA, ATTACH_SURFACE_METADATA, BROWSER_ACTIVATE_METADATA, BROWSER_BACK_METADATA, BROWSER_FORWARD_METADATA, BROWSER_FRAME_PRESENTED_METADATA, BROWSER_INSERT_TEXT_METADATA, BROWSER_KEY_METADATA, BROWSER_KEY_PRESS_METADATA, BROWSER_MOUSE_METADATA, BROWSER_MOUSE_GUARDED_METADATA, BROWSER_NAVIGATE_METADATA, BROWSER_RELOAD_METADATA, BROWSER_WHEEL_METADATA, BROWSER_WHEEL_GUARDED_METADATA, CLEAR_HISTORY_METADATA, CLEAR_WINDOW_TITLE_METADATA, CLOSE_PANE_METADATA, CLOSE_PROVIDER_MANAGED_WORKSPACE_METADATA, CLOSE_SCREEN_METADATA, CLOSE_SURFACE_METADATA, CLOSE_TERMINAL_METADATA, CLOSE_WORKSPACE_METADATA, COPY_METADATA, CREATE_TERMINAL_METADATA, CREATE_WORKSPACE_METADATA, DETACH_CLIENT_METADATA, EXPORT_LAYOUT_METADATA, FOCUS_DIRECTION_METADATA, FOCUS_PANE_METADATA, GET_CELL_PIXELS_METADATA, GET_FRONTEND_PROJECTION_METADATA, IDENTIFY_METADATA, IDS_METADATA, LIST_AGENTS_METADATA, LIST_CLIENTS_METADATA, LIST_TERMINALS_METADATA, LIST_WORKSPACES_METADATA, MARK_WORKSPACES_PROVIDER_MANAGED_METADATA, MINT_TERMINAL_RENDERER_METADATA, MOVE_TAB_METADATA, MOVE_TERMINAL_METADATA, MOVE_WORKSPACE_METADATA, NEW_BROWSER_TAB_METADATA, NEW_PANE_METADATA, NEW_PANE_RIGHT_METADATA, NEW_SCREEN_METADATA, NEW_TAB_METADATA, NEW_WORKSPACE_METADATA, NOTIFY_METADATA, PAIRING_RESPONSE_METADATA, PANE_NEIGHBOR_METADATA, PING_METADATA, PROCESS_INFO_METADATA, PUT_FRONTEND_PROJECTION_METADATA, READ_SCREEN_METADATA, READ_SCROLLBACK_METADATA, RELEASE_SURFACE_SIZE_METADATA, RELOAD_CONFIG_METADATA, RENAME_PANE_METADATA, RENAME_PROVIDER_MANAGED_WORKSPACE_METADATA, RENAME_SCREEN_METADATA, RENAME_SURFACE_METADATA, RENAME_WORKSPACE_METADATA, REPORT_AGENT_METADATA, RESIZE_SURFACE_METADATA, RESOLVE_TERMINAL_METADATA, RUN_METADATA, SCROLL_SURFACE_METADATA, SELECT_SCREEN_METADATA, SELECT_TAB_METADATA, SELECT_WORKSPACE_METADATA, SEND_METADATA, SEND_KEY_METADATA, SET_CELL_PIXELS_METADATA, SET_CLIENT_INFO_METADATA, SET_CLIENT_SIZING_METADATA, SET_DEFAULT_COLORS_METADATA, SET_RATIO_METADATA, SET_SPLIT_RATIO_METADATA, SET_VIEWPORT_PANE_WIDTH_METADATA, SET_WINDOW_TITLE_METADATA, SHUTDOWN_DAEMON_METADATA, SIDEBAR_PLUGIN_METADATA, SPLIT_METADATA, SUBSCRIBE_METADATA, SWAP_PANE_METADATA, TERMINAL_EVENTS_METADATA, UNDO_LAYOUT_METADATA, VT_STATE_METADATA, WAIT_FOR_METADATA, ZOOM_PANE_METADATA];
#[rustfmt::skip]
pub static EVENTS: &[EventMetadata] = &[BELL_EVENT_METADATA, BROWSER_STATE_EVENT_METADATA, CLIENT_ATTACHED_EVENT_METADATA, CLIENT_CHANGED_EVENT_METADATA, CLIENT_DETACHED_EVENT_METADATA, CLIENT_LIST_INVALIDATED_EVENT_METADATA, COLORS_CHANGED_EVENT_METADATA, CONFIG_RELOAD_REQUESTED_EVENT_METADATA, DETACHED_EVENT_METADATA, EMPTY_EVENT_METADATA, FRAME_EVENT_METADATA, FRONTEND_PROJECTION_CHANGED_EVENT_METADATA, LAYOUT_CHANGED_EVENT_METADATA, NOTIFICATION_EVENT_METADATA, OUTPUT_EVENT_METADATA, OVERFLOW_EVENT_METADATA, PAIRING_REQUESTED_EVENT_METADATA, PAIRING_RESOLVED_EVENT_METADATA, PANE_ADDED_EVENT_METADATA, PANE_CLOSED_EVENT_METADATA, RENDER_DELTA_EVENT_METADATA, RENDER_STATE_EVENT_METADATA, RESIZED_EVENT_METADATA, SCREEN_ADDED_EVENT_METADATA, SCREEN_CLOSED_EVENT_METADATA, SCREEN_RENAMED_EVENT_METADATA, SCROLL_CHANGED_EVENT_METADATA, STATUS_EVENT_METADATA, SURFACE_EXITED_EVENT_METADATA, SURFACE_OUTPUT_EVENT_METADATA, SURFACE_RESIZE_FAILED_EVENT_METADATA, SURFACE_RESIZED_EVENT_METADATA, TAB_ADDED_EVENT_METADATA, TAB_CLOSED_EVENT_METADATA, TAB_RENAMED_EVENT_METADATA, TERMINAL_REGISTRY_CHANGED_EVENT_METADATA, TITLE_CHANGED_EVENT_METADATA, TREE_CHANGED_EVENT_METADATA, VT_STATE_EVENT_METADATA, WINDOW_TITLE_REQUESTED_EVENT_METADATA, WORKSPACE_ADDED_EVENT_METADATA, WORKSPACE_CLOSED_EVENT_METADATA, WORKSPACE_MOVED_EVENT_METADATA, WORKSPACE_RENAMED_EVENT_METADATA];
pub static EVENTS: &[EventMetadata] = &[BELL_EVENT_METADATA, BROWSER_STATE_EVENT_METADATA, CLIENT_ATTACHED_EVENT_METADATA, CLIENT_CHANGED_EVENT_METADATA, CLIENT_DETACHED_EVENT_METADATA, CLIENT_LIST_INVALIDATED_EVENT_METADATA, COLORS_CHANGED_EVENT_METADATA, CONFIG_RELOAD_REQUESTED_EVENT_METADATA, DETACHED_EVENT_METADATA, EMPTY_EVENT_METADATA, FRAME_EVENT_METADATA, FRONTEND_PROJECTION_CHANGED_EVENT_METADATA, GRAPHICS_STATUS_EVENT_METADATA, LAYOUT_CHANGED_EVENT_METADATA, NOTIFICATION_EVENT_METADATA, OUTPUT_EVENT_METADATA, OVERFLOW_EVENT_METADATA, PAIRING_REQUESTED_EVENT_METADATA, PAIRING_RESOLVED_EVENT_METADATA, PANE_ADDED_EVENT_METADATA, PANE_CLOSED_EVENT_METADATA, RENDER_DELTA_EVENT_METADATA, RENDER_STATE_EVENT_METADATA, RESIZED_EVENT_METADATA, SCREEN_ADDED_EVENT_METADATA, SCREEN_CLOSED_EVENT_METADATA, SCREEN_RENAMED_EVENT_METADATA, SCROLL_CHANGED_EVENT_METADATA, STATUS_EVENT_METADATA, SURFACE_EXITED_EVENT_METADATA, SURFACE_OUTPUT_EVENT_METADATA, SURFACE_RESIZE_FAILED_EVENT_METADATA, SURFACE_RESIZED_EVENT_METADATA, TAB_ADDED_EVENT_METADATA, TAB_CLOSED_EVENT_METADATA, TAB_RENAMED_EVENT_METADATA, TERMINAL_REGISTRY_CHANGED_EVENT_METADATA, TITLE_CHANGED_EVENT_METADATA, TREE_CHANGED_EVENT_METADATA, VT_STATE_EVENT_METADATA, WINDOW_TITLE_REQUESTED_EVENT_METADATA, WORKSPACE_ADDED_EVENT_METADATA, WORKSPACE_CLOSED_EVENT_METADATA, WORKSPACE_MOVED_EVENT_METADATA, WORKSPACE_RENAMED_EVENT_METADATA];
+1 -1
View File
@@ -1,5 +1,5 @@
// This file is generated. Do not edit by hand.
// cmux-tui mux protocol 10, IR 4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e.
// cmux-tui mux protocol 10, IR 17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350.
// The emitter owns this layout so generation is independent of the installed rustfmt.
mod commands;
+114 -1
View File
@@ -1,5 +1,5 @@
// This file is generated. Do not edit by hand.
// cmux-tui mux protocol 10, IR 4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e.
// cmux-tui mux protocol 10, IR 17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350.
// The emitter owns this layout so generation is independent of the installed rustfmt.
use crate::{Nullable, Optional};
@@ -99,6 +99,14 @@ pub struct CellPixelResize {
pub surface: Id,
}
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CellPixelSurface {
pub height_px: u16,
pub surface: Id,
pub width_px: u16,
}
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ClientInfo {
@@ -245,6 +253,14 @@ pub struct FrontendProjection {
pub subject_key: String,
}
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GetCellPixelsResult {
pub height_px: u16,
pub surfaces: Vec<CellPixelSurface>,
pub width_px: u16,
}
#[rustfmt::skip]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum IdMappingKind {
@@ -293,6 +309,27 @@ pub struct IdsResult {
pub ids: Vec<IdMapping>,
}
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct KittyGraphicsState {
pub alternate_next_image_id: u32,
pub alternate_replay_next_image_id: u32,
pub image_bytes: u64,
pub images: u64,
pub inflight_bytes: u64,
pub placements: u64,
pub primary_next_image_id: u32,
pub primary_replay_next_image_id: u32,
pub replay_cursor_offset: u32,
}
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct KittyImageAlias {
pub image_id: u32,
pub image_number: u32,
}
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
@@ -491,6 +528,7 @@ pub struct ReadScreenResult {
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReadScrollbackResult {
pub epoch: u64,
pub rows: Vec<RenderRow>,
pub start: u32,
pub total: u32,
@@ -507,6 +545,77 @@ pub struct RenderCursor {
pub y: u16,
}
#[rustfmt::skip]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RenderGraphicFormat {
#[serde(rename = "rgb")]
Rgb,
#[serde(rename = "rgba")]
Rgba,
}
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RenderGraphicImage {
pub data: Base64,
pub format: RenderGraphicFormat,
pub generation: u64,
pub height: u32,
pub id: u32,
pub width: u32,
}
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RenderGraphicPlacement {
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub anchor_col: Option<u16>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub anchor_row: Option<u32>,
pub columns: u32,
pub grid_cols: u32,
pub grid_rows: u32,
pub image_id: u32,
pub ordinal: u32,
pub pixel_height: u32,
pub pixel_width: u32,
pub placement_id: u32,
pub rows: u32,
pub source_height: u32,
pub source_width: u32,
pub source_x: u32,
pub source_y: u32,
pub viewport_col: i32,
pub viewport_row: i32,
pub viewport_visible: bool,
pub x_offset: u32,
pub y_offset: u32,
pub z: i32,
}
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RenderGraphics {
pub generation: u64,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub images: Option<Vec<RenderGraphicImage>>,
pub placements: Vec<RenderGraphicPlacement>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub removed_image_ids: Option<Vec<u32>>,
}
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RenderGraphicsDelta {
pub generation: u64,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub images: Option<Vec<RenderGraphicImage>>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub placements: Option<Vec<RenderGraphicPlacement>>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub removed_image_ids: Option<Vec<u32>>,
}
#[rustfmt::skip]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RenderRow {
@@ -1082,6 +1191,10 @@ pub struct Tree {
pub struct VtStateResult {
pub cols: u16,
pub data: Base64,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub kitty_graphics_state: Option<KittyGraphicsState>,
#[serde(default, deserialize_with = "crate::presence::deserialize_optional_non_null", skip_serializing_if = "Option::is_none")]
pub kitty_image_aliases: Option<Vec<KittyImageAlias>>,
pub rows: u16,
}
+6
View File
@@ -121,6 +121,12 @@ from `cmux/raw`:
import { CmuxClient, COMMAND_METADATA } from "cmux/raw";
```
Raw render graphics follow one budget chain: 10,000,000 decoded image bytes
become at most 13,333,336 base64 characters. The 16,384-placement limit adds
at most 7,258,113 JSON characters. Their 20,591,449-character subtotal leaves
12,962,983 characters of the 33,554,432-character attach limit for image
metadata, rows, and the JSON wrapper.
Package verification builds all entry points, installs the tarball into a
clean TypeScript consumer, and checks that browser imports cannot reach Node,
raw, or generated modules:
@@ -33,6 +33,11 @@
"default": "./dist/src/node.js"
},
"./raw": {
"browser": {
"types": "./dist/src/raw/browser.d.ts",
"import": "./dist/src/raw/browser.js",
"default": "./dist/src/raw/browser.js"
},
"types": "./dist/src/raw/index.d.ts",
"import": "./dist/src/raw/index.js",
"default": "./dist/src/raw/index.js"
@@ -180,6 +180,12 @@ void write;
assert.ok(!graph.some((path) => path.endsWith("/node.js")));
}
const rawBrowserGraph = dependencyGraph(
join(installedRoot, "dist/src/raw/browser.js"),
);
assert.ok(!rawBrowserGraph.some((path) => path.endsWith("/node-transport.js")));
assert.ok(!rawBrowserGraph.some((path) => path.endsWith("/node-client.js")));
const runtimeTypes = execFileSync(process.execPath, [
"--input-type=module",
"--eval",
@@ -0,0 +1,11 @@
/** Browser-safe legacy generated protocol and numeric mux-ID API. */
export * from "./client.js";
export * from "./types.js";
export * from "./errors.js";
export * from "./protocol-codec.js";
export * from "./protocol/index.js";
export * from "./websocket-transport.js";
export * from "../base64.js";
export * from "../transport.js";
export * from "../transport-limits.js";
export * from "../wire-json.js";
+170 -22
View File
@@ -84,6 +84,13 @@ import {
} from "./generated/metadata.js";
import type { Transport, Unsubscribe } from "../transport.js";
import { validateRequestTimeout } from "../internal/request-timeout.js";
import {
RENDER_ATTACH_MAX_ENCODED_CHARS,
RENDER_GRAPHIC_MAX_ENCODED_CHARS,
RENDER_GRAPHIC_MAX_IMAGES,
RENDER_GRAPHIC_MAX_PLACEMENTS,
utf8ByteLength,
} from "../transport-limits.js";
import { parseWireJson, stringifyWireJson } from "../wire-json.js";
export interface CmuxClientOptions {
@@ -103,6 +110,8 @@ export interface CmuxClientOptions {
* It does not limit idle event streams.
*/
timeoutMs?: number;
/** Maximum time to establish an attach stream. */
attachHandshakeTimeoutMs?: number;
/** Optional default timeout for an idle stream read. The default waits indefinitely. */
streamIdleTimeoutMs?: number;
allowProtocolV6Attach?: boolean;
@@ -128,11 +137,26 @@ export interface SendRawOptions {
}
export const DEFAULT_MAX_BUFFERED_EVENTS = 256;
export const DEFAULT_MAX_ATTACH_ENCODED_CHARS = 16 * 1024 * 1024;
export const DEFAULT_MAX_ATTACH_ENCODED_CHARS = RENDER_ATTACH_MAX_ENCODED_CHARS;
export const DEFAULT_MAX_PENDING_RESPONSES = 256;
export const MIN_ATTACH_HANDSHAKE_BYTES_PER_SECOND = 64 * 1024;
export const MAX_ATTACH_HANDSHAKE_TIMEOUT_MS = 15 * 60 * 1_000;
const DEFAULT_CLIENT_AUTHORITIES =
Object.freeze(["control", "frontend"] as const satisfies readonly CmuxAuthority[]);
export function defaultAttachHandshakeTimeoutMs(
requestTimeoutMs: number,
maxAttachEncodedChars: number,
): number {
const transferMs = Math.ceil(
maxAttachEncodedChars * 1_000 / MIN_ATTACH_HANDSHAKE_BYTES_PER_SECOND,
);
return Math.max(
requestTimeoutMs,
Math.min(MAX_ATTACH_HANDSHAKE_TIMEOUT_MS, requestTimeoutMs + transferMs),
);
}
function workspaceMutationResult(result: WorkspaceMutation): WorkspaceMutation {
return result;
}
@@ -218,7 +242,8 @@ interface PendingResponse {
class MessageRouter {
private readonly pending = new Map<string, PendingResponse>();
private readonly eventHandlers = new Set<(event: UnknownEvent) => void>();
private readonly eventHandlers =
new Set<(event: UnknownEvent, receivedBytes: number) => void>();
private readonly terminalHandlers = new Set<(error: Error) => void>();
private terminalError: Error | null = null;
private decodeContext: ProtocolDecodeContext | undefined;
@@ -314,7 +339,7 @@ class MessageRouter {
});
}
onEvent(handler: (event: UnknownEvent) => void): Unsubscribe {
onEvent(handler: (event: UnknownEvent, receivedBytes: number) => void): Unsubscribe {
this.eventHandlers.add(handler);
return () => this.eventHandlers.delete(handler);
}
@@ -355,7 +380,8 @@ class MessageRouter {
);
return;
}
for (const handler of this.eventHandlers) handler(event);
const receivedBytes = utf8ByteLength(json);
for (const handler of this.eventHandlers) handler(event, receivedBytes);
return;
}
@@ -405,9 +431,14 @@ interface StreamWaiter<T> {
reject: (error: Error) => void;
}
interface BufferedStreamEvent<T> {
event: T;
retainedBytes: number;
}
/** A closeable async event stream that waits indefinitely unless an idle timeout is configured. */
export class CmuxStream<T extends { event: string }> implements AsyncIterable<T> {
private readonly buffered: T[] = [];
private readonly buffered: BufferedStreamEvent<T>[] = [];
private bufferedBytes = 0;
private readonly waiters: StreamWaiter<T>[] = [];
private closed = false;
@@ -432,10 +463,10 @@ export class CmuxStream<T extends { event: string }> implements AsyncIterable<T>
if (timeoutMs !== undefined) this.validateIdleTimeout(timeoutMs);
if (options.signal?.aborted) throw new CmuxAbortError("stream read aborted");
if (this.buffered.length > 0) {
const event = this.buffered.shift()!;
this.bufferedBytes = Math.max(0, this.bufferedBytes - this.retainedBytes(event));
const buffered = this.buffered.shift()!;
this.bufferedBytes = Math.max(0, this.bufferedBytes - buffered.retainedBytes);
if (this.endsAfterDrain && this.buffered.length === 0) this.finish();
return event;
return buffered.event;
}
if (this.terminalError) throw this.terminalError;
if (this.closed) throw new CmuxConnectionError("stream is closed");
@@ -497,8 +528,17 @@ export class CmuxStream<T extends { event: string }> implements AsyncIterable<T>
this.rejectWaiters(new CmuxConnectionError("stream is closed"));
}
push(event: T, terminal = false): void {
push(event: T, terminal = false, retainedBytesOverride?: number): void {
if (this.closed) return;
const retainedBytes = retainedBytesOverride ?? this.retainedBytes(event);
if (retainedBytes > this.maxBufferedBytes) {
this.fail(
new CmuxProtocolError(
`stream event data exceeds ${this.maxBufferedBytes} bytes`,
),
);
return;
}
let delivered = false;
while (this.waiters.length > 0) {
const waiter = this.waiters.shift()!;
@@ -512,7 +552,6 @@ export class CmuxStream<T extends { event: string }> implements AsyncIterable<T>
this.fail(new CmuxProtocolError("stream event buffer overflow"));
return;
}
const retainedBytes = this.retainedBytes(event);
if (retainedBytes > this.maxBufferedBytes - this.bufferedBytes) {
this.fail(
new CmuxProtocolError(
@@ -521,7 +560,7 @@ export class CmuxStream<T extends { event: string }> implements AsyncIterable<T>
);
return;
}
this.buffered.push(event);
this.buffered.push({ event, retainedBytes });
this.bufferedBytes += retainedBytes;
}
if (terminal) this.endsAfterDrain = true;
@@ -575,6 +614,7 @@ export class CmuxStream<T extends { event: string }> implements AsyncIterable<T>
/** Promise-based typed client for any cmux JSON transport. */
export class CmuxClient {
readonly timeoutMs: number;
readonly attachHandshakeTimeoutMs: number;
readonly streamIdleTimeoutMs: number | undefined;
readonly allowProtocolV6Attach: boolean;
readonly maxBufferedEvents: number;
@@ -623,6 +663,20 @@ export class CmuxClient {
options.maxAttachEncodedChars,
DEFAULT_MAX_ATTACH_ENCODED_CHARS,
);
const defaultAttachTimeout = defaultAttachHandshakeTimeoutMs(
this.timeoutMs,
this.maxAttachEncodedChars,
);
this.attachHandshakeTimeoutMs = options.attachHandshakeTimeoutMs === undefined
? defaultAttachTimeout
: Math.max(
this.timeoutMs,
this.securityLimit(
"attachHandshakeTimeoutMs",
options.attachHandshakeTimeoutMs,
MAX_ATTACH_HANDSHAKE_TIMEOUT_MS,
),
);
this.streamTransportFactory = options.streamTransportFactory;
this.router = new MessageRouter(this.transport, this.maxPendingResponses);
}
@@ -767,7 +821,22 @@ export class CmuxClient {
): Promise<CmuxResponseDataFor<"set-cell-pixels">> {
return this.request("set-cell-pixels", params);
}
vtState(surface: Id): Promise<VtStateResult> { return this.request("vt-state", { surface }); }
async vtState(surface: Id): Promise<VtStateResult> {
await this.ensureCommandAvailable("vt-state", { surface });
const params = encodeCommandParams("vt-state", { surface });
const response = await this.sendRaw(
{ cmd: "vt-state", ...params },
{ timeoutMs: this.attachHandshakeTimeoutMs },
);
if (response.ok) {
return decodeCommandResult(
"vt-state",
response.data,
this.protocolDecodeContext(),
) as VtStateResult;
}
throw new CmuxCommandError(response.error || "unknown error", response.id, response);
}
resolveTerminal(terminalId: string): Promise<ResolveTerminalResult> {
return this.request("resolve-terminal", { terminal_id: terminalId });
}
@@ -1007,11 +1076,14 @@ export class CmuxClient {
if (mode === "render") {
return this.openStream(
request,
(event) => event as RenderAttachEvent,
(event) => this.validateRenderAttachEvent(event),
(event, dedicated) => dedicated || this.matchesAttachEvent(event, surface, mode),
(event) => event.event === "detached" || this.isSurfaceOverflow(event, surface),
false,
undefined,
{
maxBytes: this.maxAttachEncodedChars,
retainedBytes: (_event, receivedBytes) => receivedBytes,
},
options,
);
}
@@ -1223,7 +1295,10 @@ export class CmuxClient {
accept: (event: UnknownEvent, dedicated: boolean) => boolean,
terminal: (event: T) => boolean = () => false,
exclusiveSharedSubscription = false,
buffering?: { maxBytes: number; retainedBytes: (event: T) => number },
buffering?: {
maxBytes: number;
retainedBytes: (event: T, receivedBytes: number) => number;
},
streamOptions: StreamOpenOptions = {},
): Promise<CmuxStream<T>> {
if (streamOptions.signal?.aborted) {
@@ -1273,12 +1348,16 @@ export class CmuxClient {
this.sharedSubscriptionActive = false;
}
if (dedicated) transport.close();
}, this.maxBufferedEvents, buffering?.maxBytes, buffering?.retainedBytes);
eventSubscription = router.onEvent((event) => {
}, this.maxBufferedEvents, buffering?.maxBytes);
eventSubscription = router.onEvent((event, receivedBytes) => {
if (!accept(event, dedicated)) return;
try {
const mapped = map(event);
stream.push(mapped, terminal(mapped));
stream.push(
mapped,
terminal(mapped),
buffering?.retainedBytes(mapped, receivedBytes),
);
streamError ??= stream.error;
} catch (error) {
streamError = error instanceof CmuxProtocolError
@@ -1297,7 +1376,7 @@ export class CmuxClient {
});
const response = await router.send(
payload,
this.timeoutMs,
buffering === undefined ? this.timeoutMs : this.attachHandshakeTimeoutMs,
streamOptions.signal,
).catch((error) => {
stream.fail(error as Error);
@@ -1392,17 +1471,86 @@ export class CmuxClient {
}
}
private validateRenderAttachEvent(event: UnknownEvent): RenderAttachEvent {
if (event.event !== "render-state" && event.event !== "render-delta") {
return event as RenderAttachEvent;
}
const graphics = event.graphics;
if (graphics === undefined) return event as RenderAttachEvent;
if (graphics === null || typeof graphics !== "object" || Array.isArray(graphics)) {
throw new CmuxProtocolError(`${event.event} graphics is not an object`);
}
const placements = (graphics as { placements?: unknown }).placements;
if (placements === undefined && event.event === "render-state") {
throw new CmuxProtocolError(`${event.event} graphics placements is not an array`);
}
if (placements !== undefined && !Array.isArray(placements)) {
throw new CmuxProtocolError(`${event.event} graphics placements is not an array`);
}
if (Array.isArray(placements) && placements.length > RENDER_GRAPHIC_MAX_PLACEMENTS) {
throw new CmuxProtocolError(
`${event.event} graphics exceeds ${RENDER_GRAPHIC_MAX_PLACEMENTS} placements`,
);
}
const removedImageIds =
(graphics as { removed_image_ids?: unknown }).removed_image_ids;
if (removedImageIds !== undefined) {
if (!Array.isArray(removedImageIds)) {
throw new CmuxProtocolError(
`${event.event} graphics removed_image_ids is not an array`,
);
}
if (removedImageIds.length > RENDER_GRAPHIC_MAX_IMAGES) {
throw new CmuxProtocolError(
`${event.event} graphics exceeds ${RENDER_GRAPHIC_MAX_IMAGES} removed image IDs`,
);
}
if (removedImageIds.some((id) =>
!Number.isSafeInteger(id) || id <= 0 || id > 0xffff_ffff
)) {
throw new CmuxProtocolError(
`${event.event} graphics removed_image_ids contains an invalid image ID`,
);
}
}
const images = (graphics as { images?: unknown }).images;
if (images === undefined) return event as RenderAttachEvent;
if (!Array.isArray(images)) {
throw new CmuxProtocolError(`${event.event} graphics images is not an array`);
}
if (images.length > RENDER_GRAPHIC_MAX_IMAGES) {
throw new CmuxProtocolError(
`${event.event} graphics exceeds ${RENDER_GRAPHIC_MAX_IMAGES} images`,
);
}
for (const image of images) {
if (image === null || typeof image !== "object" || Array.isArray(image)) {
throw new CmuxProtocolError(`${event.event} graphics image is not an object`);
}
this.validateAttachEncodedData(
(image as { data?: unknown }).data,
`${event.event} graphics image`,
Math.min(this.maxAttachEncodedChars, RENDER_GRAPHIC_MAX_ENCODED_CHARS),
);
}
return event as RenderAttachEvent;
}
private decodeAttachData(value: unknown, eventName: string): Uint8Array {
return decodeBase64(this.validateAttachEncodedData(value, eventName));
}
private validateAttachEncodedData(value: unknown, eventName: string): string {
private validateAttachEncodedData(
value: unknown,
eventName: string,
maxEncodedChars = this.maxAttachEncodedChars,
): string {
if (typeof value !== "string") {
throw new CmuxProtocolError(`${eventName} data is not base64 text`);
}
if (value.length > this.maxAttachEncodedChars) {
if (value.length > maxEncodedChars) {
throw new CmuxProtocolError(
`${eventName} data exceeds ${this.maxAttachEncodedChars} encoded characters`,
`${eventName} data exceeds ${maxEncodedChars} encoded characters`,
);
}
return value;
@@ -2,32 +2,32 @@
"files": [
{
"path": "commands.ts",
"sha256": "09def0b59e218447b444b081b71e5eef7a627abae10c8d3e634fbab7c2894cc7",
"size": 43304
"sha256": "ee62f3d6615086de16d5067a9fe8b29d6bcaf2f0260fe0a776b425ace185bbbd",
"size": 43642
},
{
"path": "events.ts",
"sha256": "44e2c1cc16bd9807422d10320146a27473003970f6e3cdd42bc09d6d0edb33e9",
"size": 14043
"sha256": "9533ef7351d3e35ff9373bef9548c3aef3786fa7d230e8c4722daf2c7b10c4d2",
"size": 14850
},
{
"path": "index.ts",
"sha256": "a66cd747098c1e4cd8c88fb745918b85ec54a682a585eb7ebb03fb6bb22f2d70",
"sha256": "cfacfceed3bbea6e8fcb05a63c5cabf50157c044f711ba1554a79197e08dd68e",
"size": 272
},
{
"path": "metadata.ts",
"sha256": "1107deb6081c0aa1fab0f7013a42ff147bfe09ad9550561b581980d6db816ce9",
"size": 230550
"sha256": "87f7394850a23aa7c83e940000475cbe98c45473ac5a296b7dc0e2f32b05c5bf",
"size": 246038
},
{
"path": "types.ts",
"sha256": "74600b4bb03a84aa073b23e28c98fb6aa39da3f5a1978c3136423766d43aadb1",
"size": 12971
"sha256": "b73d29218166c8533274aa24a8bd36a19f77136c804be621788c3c962471ea96",
"size": 14855
}
],
"format": 1,
"ir_sha256": "4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e",
"ir_sha256": "17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350",
"language": "typescript",
"mux_protocol": 10,
"schema_version": 2
@@ -1,5 +1,5 @@
/* This file is generated. Do not edit by hand. */
/* cmux-tui mux protocol 10, IR 4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e. */
/* cmux-tui mux protocol 10, IR 17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350. */
import type * as T from "./types.js";
@@ -300,6 +300,11 @@ export interface FocusPaneRequest extends CmuxRequestBase {
}
export type FocusPaneResult = T.EmptyResult;
/** Protocol v6; authority: frontend. */
export interface GetCellPixelsRequest extends CmuxRequestBase {
cmd: "get-cell-pixels";
}
/** Protocol v7; authority: control. */
export interface GetFrontendProjectionRequest extends CmuxRequestBase {
cmd: "get-frontend-projection";
@@ -854,6 +859,7 @@ export type CmuxRequest =
| ExportLayoutRequest
| FocusDirectionRequest
| FocusPaneRequest
| GetCellPixelsRequest
| GetFrontendProjectionRequest
| IdentifyRequest
| IdsRequest
@@ -1158,6 +1164,14 @@ export interface CmuxCommandDefinitionMap {
capability: null;
stream: null;
};
"get-cell-pixels": {
request: GetCellPixelsRequest;
result: T.GetCellPixelsResult;
authority: "frontend";
since: 6;
capability: null;
stream: null;
};
"get-frontend-projection": {
request: GetFrontendProjectionRequest;
result: GetFrontendProjectionResult;
@@ -1,5 +1,5 @@
/* This file is generated. Do not edit by hand. */
/* cmux-tui mux protocol 10, IR 4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e. */
/* cmux-tui mux protocol 10, IR 17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350. */
import type * as T from "./types.js";
@@ -92,6 +92,18 @@ export type FrontendProjectionChangedEvent = { event: "frontend-projection-chang
"subject_key": string;
};
/** Protocol v10; emission: emitted; streams: subscribe. */
export type GraphicsStatusEvent = { event: "graphics-status" } & {
"attempts"?: number;
"cell_height"?: number;
"cell_width"?: number;
"error"?: string;
"kind": "kitty-image-budget-worker-start-failed" | "kitty-image-budget-update-failed" | "cell-pixel-update-retries-exhausted";
"remaining"?: bigint;
"retry_exhausted"?: boolean;
"summary"?: string;
};
/** Protocol v6; emission: emitted; streams: subscribe. */
export type LayoutChangedEvent = { event: "layout-changed" } & {
"screen": T.Id;
@@ -157,6 +169,8 @@ export type RenderDeltaEvent = { event: "render-delta" } & {
"default_bg"?: T.ColorHex;
"default_fg"?: T.ColorHex;
"full": boolean;
"graphics"?: T.RenderGraphicsDelta;
"history_epoch"?: bigint;
"rows": Array<T.RenderRow>;
"scrollback_rows"?: number;
"size"?: T.Size;
@@ -168,6 +182,8 @@ export type RenderStateEvent = { event: "render-state" } & {
"cursor": T.RenderCursor;
"default_bg": T.ColorHex;
"default_fg": T.ColorHex;
"graphics"?: T.RenderGraphics;
"history_epoch": bigint;
"rows": Array<T.RenderRow>;
"scrollback_rows": number;
"size": T.Size;
@@ -180,6 +196,8 @@ export type ResizedEvent = { event: "resized" } & {
"cols": number;
/** Protocol 6 compatibility field. */
"data"?: T.Base64;
"kitty_graphics_state"?: T.KittyGraphicsState;
"kitty_image_aliases"?: Array<T.KittyImageAlias>;
"replay"?: T.Base64;
"rows": number;
"surface": T.Id;
@@ -300,6 +318,8 @@ export type VtStateEvent = { event: "vt-state" } & {
"colors"?: T.TerminalColors;
"cols": number;
"data": T.Base64;
"kitty_graphics_state"?: T.KittyGraphicsState;
"kitty_image_aliases"?: Array<T.KittyImageAlias>;
"rows": number;
"surface": T.Id;
};
@@ -375,6 +395,7 @@ export type KnownCmuxEvent =
| EmptyEvent
| FrameEvent
| FrontendProjectionChangedEvent
| GraphicsStatusEvent
| LayoutChangedEvent
| NotificationEvent
| OutputEvent
@@ -421,6 +442,7 @@ export type KnownSubscribeEvent =
| ConfigReloadRequestedEvent
| EmptyEvent
| FrontendProjectionChangedEvent
| GraphicsStatusEvent
| LayoutChangedEvent
| NotificationEvent
| OverflowEvent
@@ -1,5 +1,5 @@
/* This file is generated. Do not edit by hand. */
/* cmux-tui mux protocol 10, IR 4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e. */
/* cmux-tui mux protocol 10, IR 17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350. */
export * from "./types.js";
export * from "./commands.js";
@@ -1,10 +1,10 @@
/* This file is generated. Do not edit by hand. */
/* cmux-tui mux protocol 10, IR 4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e. */
/* cmux-tui mux protocol 10, IR 17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350. */
export const SDK_SCHEMA_VERSION = 2 as const;
export const MUX_PROTOCOL_VERSION = 10 as const;
export const SDK_IR_SHA256 = "4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e" as const;
export const SDK_IR_SHA256 = "17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350" as const;
export const PROTOCOL = {
"id_type": "uint64",
"javascript_id_policy": "All protocol identifiers are uint64 JSON numbers. JavaScript and TypeScript SDKs must decode them losslessly as bigint (or validated decimal strings at their public boundary), and must not expose IEEE-754 number ids. Pairing request ids, revisions, timestamps, frame sequences, and reservation ids follow the same rule.",
@@ -414,6 +414,14 @@ export const COMMAND_METADATA = {
"stream": null,
"constraints": []
},
"get-cell-pixels": {
"authority": "frontend",
"since": 6,
"capability": null,
"fields": {},
"stream": null,
"constraints": []
},
"get-frontend-projection": {
"authority": "control",
"since": 7,
@@ -1232,6 +1240,14 @@ export const EVENT_METADATA = {
],
"emission": "emitted"
},
"graphics-status": {
"since": 10,
"capability": null,
"streams": [
"subscribe"
],
"emission": "emitted"
},
"layout-changed": {
"since": 6,
"capability": null,
@@ -1726,6 +1742,36 @@ export const TYPE_SCHEMAS: Readonly<Record<string, TypeSchema>> = {
},
"kind": "object"
},
"CellPixelSurface": {
"additional_properties": false,
"fields": {
"height_px": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint16"
}
},
"surface": {
"nullable": false,
"presence": "required",
"type": {
"kind": "ref",
"name": "Id"
}
},
"width_px": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint16"
}
}
},
"kind": "object"
},
"ClientInfo": {
"additional_properties": false,
"fields": {
@@ -2245,6 +2291,39 @@ export const TYPE_SCHEMAS: Readonly<Record<string, TypeSchema>> = {
},
"kind": "object"
},
"GetCellPixelsResult": {
"additional_properties": false,
"fields": {
"height_px": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint16"
}
},
"surfaces": {
"nullable": false,
"presence": "required",
"type": {
"items": {
"kind": "ref",
"name": "CellPixelSurface"
},
"kind": "array"
}
},
"width_px": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint16"
}
}
},
"kind": "object"
},
"Id": {
"kind": "alias",
"target": {
@@ -2432,6 +2511,106 @@ export const TYPE_SCHEMAS: Readonly<Record<string, TypeSchema>> = {
"kind": "opaque_json",
"reason": "The wire field intentionally carries a frontend-authored or runtime-authored arbitrary JSON document."
},
"KittyGraphicsState": {
"additional_properties": false,
"fields": {
"alternate_next_image_id": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"alternate_replay_next_image_id": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"image_bytes": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint64"
}
},
"images": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint64"
}
},
"inflight_bytes": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint64"
}
},
"placements": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint64"
}
},
"primary_next_image_id": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"primary_replay_next_image_id": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"replay_cursor_offset": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
}
},
"kind": "object"
},
"KittyImageAlias": {
"additional_properties": false,
"fields": {
"image_id": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"image_number": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
}
},
"kind": "object"
},
"Layout": {
"kind": "tagged_union",
"tag": "type",
@@ -3162,6 +3341,15 @@ export const TYPE_SCHEMAS: Readonly<Record<string, TypeSchema>> = {
"ReadScrollbackResult": {
"additional_properties": false,
"fields": {
"epoch": {
"nullable": false,
"presence": "required",
"since": 10,
"type": {
"kind": "scalar",
"name": "uint64"
}
},
"rows": {
"nullable": false,
"presence": "required",
@@ -3246,6 +3434,335 @@ export const TYPE_SCHEMAS: Readonly<Record<string, TypeSchema>> = {
},
"kind": "object"
},
"RenderGraphicFormat": {
"kind": "enum",
"values": [
"rgb",
"rgba"
]
},
"RenderGraphicImage": {
"additional_properties": false,
"fields": {
"data": {
"nullable": false,
"presence": "required",
"type": {
"kind": "ref",
"name": "Base64"
}
},
"format": {
"nullable": false,
"presence": "required",
"type": {
"kind": "ref",
"name": "RenderGraphicFormat"
}
},
"generation": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint64"
}
},
"height": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"id": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"width": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
}
},
"kind": "object"
},
"RenderGraphicPlacement": {
"additional_properties": false,
"fields": {
"anchor_col": {
"nullable": false,
"presence": "optional",
"type": {
"kind": "scalar",
"name": "uint16"
}
},
"anchor_row": {
"nullable": false,
"presence": "optional",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"columns": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"grid_cols": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"grid_rows": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"image_id": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"ordinal": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"pixel_height": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"pixel_width": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"placement_id": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"rows": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"source_height": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"source_width": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"source_x": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"source_y": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"viewport_col": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "int32"
}
},
"viewport_row": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "int32"
}
},
"viewport_visible": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "boolean"
}
},
"x_offset": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"y_offset": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint32"
}
},
"z": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "int32"
}
}
},
"kind": "object"
},
"RenderGraphics": {
"additional_properties": false,
"fields": {
"generation": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint64"
}
},
"images": {
"nullable": false,
"presence": "optional",
"type": {
"items": {
"kind": "ref",
"name": "RenderGraphicImage"
},
"kind": "array"
}
},
"placements": {
"nullable": false,
"presence": "required",
"type": {
"items": {
"kind": "ref",
"name": "RenderGraphicPlacement"
},
"kind": "array"
}
},
"removed_image_ids": {
"nullable": false,
"presence": "optional",
"type": {
"items": {
"kind": "scalar",
"name": "uint32"
},
"kind": "array"
}
}
},
"kind": "object"
},
"RenderGraphicsDelta": {
"additional_properties": false,
"fields": {
"generation": {
"nullable": false,
"presence": "required",
"type": {
"kind": "scalar",
"name": "uint64"
}
},
"images": {
"nullable": false,
"presence": "optional",
"type": {
"items": {
"kind": "ref",
"name": "RenderGraphicImage"
},
"kind": "array"
}
},
"placements": {
"nullable": false,
"presence": "optional",
"type": {
"items": {
"kind": "ref",
"name": "RenderGraphicPlacement"
},
"kind": "array"
}
},
"removed_image_ids": {
"nullable": false,
"presence": "optional",
"type": {
"items": {
"kind": "scalar",
"name": "uint32"
},
"kind": "array"
}
}
},
"kind": "object"
},
"RenderRow": {
"additional_properties": false,
"fields": {
@@ -4668,6 +5185,27 @@ export const TYPE_SCHEMAS: Readonly<Record<string, TypeSchema>> = {
"name": "Base64"
}
},
"kitty_graphics_state": {
"nullable": false,
"presence": "optional",
"since": 10,
"type": {
"kind": "ref",
"name": "KittyGraphicsState"
}
},
"kitty_image_aliases": {
"nullable": false,
"presence": "optional",
"since": 9,
"type": {
"items": {
"kind": "ref",
"name": "KittyImageAlias"
},
"kind": "array"
}
},
"rows": {
"nullable": false,
"presence": "required",
@@ -6188,6 +6726,17 @@ export const COMMAND_SCHEMAS: Readonly<Record<string, CommandSchema>> = {
"name": "EmptyResult"
}
},
"get-cell-pixels": {
"request": {
"additional_properties": false,
"fields": {},
"kind": "object"
},
"result": {
"kind": "ref",
"name": "GetCellPixelsResult"
}
},
"get-frontend-projection": {
"request": {
"additional_properties": false,
@@ -9162,6 +9711,93 @@ export const EVENT_SCHEMAS: Readonly<Record<string, TypeSchema>> = {
},
"kind": "object"
},
"graphics-status": {
"additional_properties": false,
"constraints": [
"kitty-image-budget-worker-start-failed carries error.",
"kitty-image-budget-update-failed carries retry_exhausted and summary.",
"cell-pixel-update-retries-exhausted carries attempts, remaining, cell_width, and cell_height."
],
"fields": {
"attempts": {
"nullable": false,
"presence": "optional",
"type": {
"kind": "scalar",
"name": "uint16"
}
},
"cell_height": {
"nullable": false,
"presence": "optional",
"type": {
"kind": "scalar",
"name": "uint16"
}
},
"cell_width": {
"nullable": false,
"presence": "optional",
"type": {
"kind": "scalar",
"name": "uint16"
}
},
"error": {
"nullable": false,
"presence": "optional",
"type": {
"kind": "scalar",
"name": "string"
}
},
"event": {
"nullable": false,
"presence": "required",
"type": {
"kind": "literal",
"value": "graphics-status"
}
},
"kind": {
"nullable": false,
"presence": "required",
"type": {
"kind": "enum",
"values": [
"kitty-image-budget-worker-start-failed",
"kitty-image-budget-update-failed",
"cell-pixel-update-retries-exhausted"
]
}
},
"remaining": {
"nullable": false,
"presence": "optional",
"type": {
"kind": "scalar",
"name": "uint64"
}
},
"retry_exhausted": {
"nullable": false,
"presence": "optional",
"type": {
"kind": "scalar",
"name": "boolean"
}
},
"summary": {
"nullable": false,
"presence": "optional",
"type": {
"kind": "scalar",
"name": "string"
}
}
},
"kind": "object"
},
"layout-changed": {
"additional_properties": false,
"fields": {
@@ -9540,6 +10176,23 @@ export const EVENT_SCHEMAS: Readonly<Record<string, TypeSchema>> = {
"name": "boolean"
}
},
"graphics": {
"nullable": false,
"presence": "optional",
"type": {
"kind": "ref",
"name": "RenderGraphicsDelta"
}
},
"history_epoch": {
"nullable": false,
"presence": "optional",
"since": 10,
"type": {
"kind": "scalar",
"name": "uint64"
}
},
"rows": {
"nullable": false,
"presence": "required",
@@ -9613,6 +10266,23 @@ export const EVENT_SCHEMAS: Readonly<Record<string, TypeSchema>> = {
"value": "render-state"
}
},
"graphics": {
"nullable": false,
"presence": "optional",
"type": {
"kind": "ref",
"name": "RenderGraphics"
}
},
"history_epoch": {
"nullable": false,
"presence": "required",
"since": 10,
"type": {
"kind": "scalar",
"name": "uint64"
}
},
"rows": {
"nullable": false,
"presence": "required",
@@ -9691,6 +10361,27 @@ export const EVENT_SCHEMAS: Readonly<Record<string, TypeSchema>> = {
"value": "resized"
}
},
"kitty_graphics_state": {
"nullable": false,
"presence": "optional",
"since": 10,
"type": {
"kind": "ref",
"name": "KittyGraphicsState"
}
},
"kitty_image_aliases": {
"nullable": false,
"presence": "optional",
"since": 9,
"type": {
"items": {
"kind": "ref",
"name": "KittyImageAlias"
},
"kind": "array"
}
},
"replay": {
"nullable": false,
"presence": "optional",
@@ -10367,6 +11058,27 @@ export const EVENT_SCHEMAS: Readonly<Record<string, TypeSchema>> = {
"value": "vt-state"
}
},
"kitty_graphics_state": {
"nullable": false,
"presence": "optional",
"since": 10,
"type": {
"kind": "ref",
"name": "KittyGraphicsState"
}
},
"kitty_image_aliases": {
"nullable": false,
"presence": "optional",
"since": 9,
"type": {
"items": {
"kind": "ref",
"name": "KittyImageAlias"
},
"kind": "array"
}
},
"rows": {
"nullable": false,
"presence": "required",
@@ -1,5 +1,5 @@
/* This file is generated. Do not edit by hand. */
/* cmux-tui mux protocol 10, IR 4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e. */
/* cmux-tui mux protocol 10, IR 17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350. */
/** JSON accepted by the wire codec. bigint is serialized as an exact JSON integer. */
@@ -51,6 +51,12 @@ export type CellPixelResize = {
"surface": Id;
};
export type CellPixelSurface = {
"height_px": number;
"surface": Id;
"width_px": number;
};
export type ClientInfo = {
"attached": Array<Id>;
"client": bigint;
@@ -139,6 +145,12 @@ export type FrontendProjection = {
"subject_key": string;
};
export type GetCellPixelsResult = {
"height_px": number;
"surfaces": Array<CellPixelSurface>;
"width_px": number;
};
export type Id = bigint;
export type IdMapping = {
@@ -167,6 +179,23 @@ export type IdsResult = {
"ids": Array<IdMapping>;
};
export type KittyGraphicsState = {
"alternate_next_image_id": number;
"alternate_replay_next_image_id": number;
"image_bytes": bigint;
"images": bigint;
"inflight_bytes": bigint;
"placements": bigint;
"primary_next_image_id": number;
"primary_replay_next_image_id": number;
"replay_cursor_offset": number;
};
export type KittyImageAlias = {
"image_id": number;
"image_number": number;
};
export type Layout = ({ "type": "leaf" } & {
"pane": Id;
"type": "leaf";
@@ -291,6 +320,7 @@ export type ReadScreenResult = {
};
export type ReadScrollbackResult = {
"epoch": bigint;
"rows": Array<RenderRow>;
"start": number;
"total": number;
@@ -305,6 +335,55 @@ export type RenderCursor = {
"y": number;
};
export type RenderGraphicFormat = "rgb" | "rgba";
export type RenderGraphicImage = {
"data": Base64;
"format": RenderGraphicFormat;
"generation": bigint;
"height": number;
"id": number;
"width": number;
};
export type RenderGraphicPlacement = {
"anchor_col"?: number;
"anchor_row"?: number;
"columns": number;
"grid_cols": number;
"grid_rows": number;
"image_id": number;
"ordinal": number;
"pixel_height": number;
"pixel_width": number;
"placement_id": number;
"rows": number;
"source_height": number;
"source_width": number;
"source_x": number;
"source_y": number;
"viewport_col": number;
"viewport_row": number;
"viewport_visible": boolean;
"x_offset": number;
"y_offset": number;
"z": number;
};
export type RenderGraphics = {
"generation": bigint;
"images"?: Array<RenderGraphicImage>;
"placements": Array<RenderGraphicPlacement>;
"removed_image_ids"?: Array<number>;
};
export type RenderGraphicsDelta = {
"generation": bigint;
"images"?: Array<RenderGraphicImage>;
"placements"?: Array<RenderGraphicPlacement>;
"removed_image_ids"?: Array<number>;
};
export type RenderRow = {
"row": number;
"runs": Array<RenderRun>;
@@ -507,6 +586,8 @@ export type Tree = {
export type VtStateResult = {
"cols": number;
"data": Base64;
"kitty_graphics_state"?: KittyGraphicsState;
"kitty_image_aliases"?: Array<KittyImageAlias>;
"rows": number;
};
@@ -6,6 +6,7 @@ import type { CmuxAuthority } from "./generated/metadata.js";
import { defaultSocketPath, envSocketPath, UnixSocketTransport } from "../node-transport.js";
import { validateRequestTimeout } from "../internal/request-timeout.js";
import type { Transport } from "../transport.js";
import { RENDER_ATTACH_MAX_ENCODED_CHARS } from "../transport-limits.js";
/** Node.js client configuration, including Unix-socket defaults. */
export interface ClientOptions {
@@ -13,6 +14,7 @@ export interface ClientOptions {
session?: string;
/** Command acknowledgement timeout. */
timeoutMs?: number;
attachHandshakeTimeoutMs?: number;
/**
* Command authorities enabled for this client.
* Node Unix clients default to control, frontend, and local-admin.
@@ -39,18 +41,22 @@ export class CmuxClient extends TransportCmuxClient {
constructor(options: ClientOptions = {}) {
const timeoutMs = validateRequestTimeout(options.timeoutMs ?? 10_000);
const socketPath = options.socketPath ?? envSocketPath() ?? defaultSocketPath(options.session ?? "main");
const rawTransport = (): UnixSocketTransport => new UnixSocketTransport(socketPath, {
maxInboundMessageBytes: RENDER_ATTACH_MAX_ENCODED_CHARS,
});
const shared: CmuxClientOptions = {
transport: options.transport ?? new UnixSocketTransport(socketPath),
transport: options.transport ?? rawTransport(),
authorities: options.authorities ?? ["control", "frontend", "local-admin"],
enableProviderAuthority: options.enableProviderAuthority,
timeoutMs,
attachHandshakeTimeoutMs: options.attachHandshakeTimeoutMs,
streamIdleTimeoutMs: options.streamIdleTimeoutMs,
allowProtocolV6Attach: options.allowProtocolV6Attach,
maxBufferedEvents: options.maxBufferedEvents,
maxAttachEncodedChars: options.maxAttachEncodedChars,
maxPendingResponses: options.maxPendingResponses,
streamTransportFactory: options.streamTransportFactory
?? (options.transport ? undefined : () => new UnixSocketTransport(socketPath)),
?? (options.transport ? undefined : rawTransport),
};
super(shared);
this.socketPath = socketPath;
@@ -9,6 +9,16 @@ export const MAX_PENDING_MESSAGES = 256;
/** Matches the server's bounded regular-message bytes. */
export const MAX_PENDING_BYTES = 16_777_216;
/** Maximum decoded Kitty pixel bytes retained by one render attach. */
export const RENDER_GRAPHIC_MAX_DECODED_BYTES = 10_000_000;
/** Maximum base64 characters for the decoded Kitty pixel budget. */
export const RENDER_GRAPHIC_MAX_ENCODED_CHARS =
Math.ceil(RENDER_GRAPHIC_MAX_DECODED_BYTES / 3) * 4;
export const RENDER_GRAPHIC_MAX_IMAGES = 4_096;
export const RENDER_GRAPHIC_MAX_PLACEMENTS = 16_384;
/** Maximum encoded JSON characters accepted for a raw attach event. */
export const RENDER_ATTACH_MAX_ENCODED_CHARS = 32 * 1024 * 1024;
const encoder = new TextEncoder();
export function utf8ByteLength(value: string): number {
@@ -3,6 +3,10 @@ import test from "node:test";
import {
CmuxClient,
CmuxStream,
DEFAULT_MAX_ATTACH_ENCODED_CHARS,
MAX_ATTACH_HANDSHAKE_TIMEOUT_MS,
MIN_ATTACH_HANDSHAKE_BYTES_PER_SECOND,
defaultAttachHandshakeTimeoutMs,
type BrowserStreamEvent,
} from "../src/raw/client.js";
import {
@@ -13,9 +17,17 @@ import {
} from "../src/raw/errors.js";
import type {
DecodedResizedEvent,
RenderStateEvent,
TreeDeltaEvent,
} from "../src/raw/protocol/index.js";
import type { Transport, Unsubscribe } from "../src/transport.js";
import {
RENDER_ATTACH_MAX_ENCODED_CHARS,
RENDER_GRAPHIC_MAX_DECODED_BYTES,
RENDER_GRAPHIC_MAX_ENCODED_CHARS,
RENDER_GRAPHIC_MAX_IMAGES,
RENDER_GRAPHIC_MAX_PLACEMENTS,
} from "../src/transport-limits.js";
import { stringifyWireJson } from "../src/wire-json.js";
class ScriptedTransport implements Transport {
@@ -476,6 +488,21 @@ test("async iteration reports buffered-event overflow before the first pull", as
await assert.rejects(() => iterator.next(), /stream event buffer overflow/);
});
test("stream rejects an oversized event while a reader is already waiting", async () => {
const stream = new CmuxStream<{ event: string; bytes: number }>(
100,
() => undefined,
256,
4,
(event) => event.bytes,
);
const waiting = stream.next();
stream.push({ event: "oversized", bytes: 5 });
await assert.rejects(() => waiting, /stream event data exceeds 4 bytes/);
});
test("attachSurface rejects oversized encoded data before decoding", async () => {
const main = new ScriptedTransport((request, transport) => {
transport.emit({
@@ -593,6 +620,71 @@ type CmuxClientOptionsWithSecurityLimits = ConstructorParameters<typeof CmuxClie
maxAttachEncodedChars: number;
};
test("attach handshake deadline accounts for the largest accepted snapshot", () => {
assert.equal(MIN_ATTACH_HANDSHAKE_BYTES_PER_SECOND, 64 * 1024);
assert.equal(MAX_ATTACH_HANDSHAKE_TIMEOUT_MS, 15 * 60 * 1_000);
assert.equal(
defaultAttachHandshakeTimeoutMs(10_000, RENDER_ATTACH_MAX_ENCODED_CHARS),
522_000,
);
});
test("attach stream can acknowledge after the ordinary request deadline", async () => {
const main = new ScriptedTransport((request, transport) => {
transport.emit({
id: request.id,
ok: true,
data: { app: "cmux-tui", version: "0.1.2", protocol: 7, session: "main", pid: 1 },
});
});
const attach = new ScriptedTransport((request, transport) => {
transport.emit({
event: "render-state",
surface: 7,
size: { cols: 1, rows: 1 },
cursor: { x: 0, y: 0, style: "block", blink: false, visible: false, color: null },
default_fg: "#ffffff",
default_bg: "#000000",
scrollback_rows: 0,
rows: [],
graphics: { generation: 0, images: [], placements: [] },
});
setTimeout(() => transport.emit({ id: request.id, ok: true, data: {} }), 30);
});
const client = new CmuxClient({
transport: main,
streamTransportFactory: () => attach,
timeoutMs: 10,
attachHandshakeTimeoutMs: 100,
});
const stream = await client.attachSurface(7n, { mode: "render" });
assert.equal((await stream.next()).event, "render-state");
stream.close();
await client.close();
});
test("vtState uses the size-aware snapshot deadline", async () => {
const transport = new ScriptedTransport((request, connection) => {
assert.equal(request.cmd, "vt-state");
setTimeout(() => {
connection.emit({
id: request.id,
ok: true,
data: { cols: 80, rows: 24, data: "" },
});
}, 30);
});
const client = new CmuxClient({
transport,
timeoutMs: 10,
attachHandshakeTimeoutMs: 100,
});
assert.deepEqual(await client.vtState(7n), { cols: 80, rows: 24, data: "" });
await client.close();
});
test("resize response rejects a missing required accepted field", async () => {
const transport = new ScriptedTransport((request, connection) => {
connection.emit({
@@ -756,6 +848,7 @@ test("attachSurface decodes VT colors, output, and resized payloads", async () =
cols: 80,
rows: 24,
data: "G1s/bA==",
kitty_image_aliases: [{ image_id: 7, image_number: 70 }],
colors: {
fg: "#d8d9da",
bg: "#131415",
@@ -775,6 +868,7 @@ test("attachSurface decodes VT colors, output, and resized payloads", async () =
cols: 100,
rows: 30,
data: "AQID",
kitty_image_aliases: [{ image_id: 8, image_number: 80 }],
colors: {
fg: null,
bg: null,
@@ -798,6 +892,7 @@ test("attachSurface decodes VT colors, output, and resized payloads", async () =
assert.equal(initial.event, "vt-state");
if (initial.event === "vt-state") {
assert.deepEqual(initial.data, Uint8Array.from([27, 91, 63, 108]));
assert.deepEqual(initial.kitty_image_aliases, [{ image_id: 7, image_number: 70 }]);
assert.deepEqual(initial.colors, {
fg: "#d8d9da",
bg: "#131415",
@@ -819,6 +914,7 @@ test("attachSurface decodes VT colors, output, and resized payloads", async () =
const decoded = resized as DecodedResizedEvent;
assert.deepEqual(decoded.data, Uint8Array.from([1, 2, 3]));
assert.deepEqual(decoded.replay, decoded.data);
assert.deepEqual(decoded.kitty_image_aliases, [{ image_id: 8, image_number: 80 }]);
assert.deepEqual(decoded.colors?.palette, { "5": "#112233" });
}
stream.close();
@@ -929,7 +1025,40 @@ test("attachSurface routes colors-changed events without a surface field", async
await client.close();
});
test("attachSurface render mode yields render-state and render-delta from cached protocol v7", async () => {
const renderGraphics = {
generation: 4n,
images: [{
id: 9,
generation: 2n,
width: 1,
height: 1,
format: "rgba",
data: "/wAA/w==",
}],
placements: [{
image_id: 9,
placement_id: 3,
ordinal: 0,
x_offset: 0,
y_offset: 0,
source_x: 0,
source_y: 0,
source_width: 1,
source_height: 1,
columns: 1,
rows: 1,
grid_cols: 1,
grid_rows: 1,
pixel_width: 8,
pixel_height: 16,
viewport_col: 0,
viewport_row: 0,
viewport_visible: true,
z: 0,
}],
};
test("attachSurface render mode yields Kitty pixels and placements with render events", async () => {
let identifyRequests = 0;
const main = new ScriptedTransport((request, transport) => {
assert.equal(request.cmd, "identify");
@@ -975,6 +1104,7 @@ test("attachSurface render mode yields render-state and render-delta from cached
width_hint: 3,
}],
}],
graphics: renderGraphics,
});
transport.emit({ id: request.id, ok: true, data: {} });
transport.emit({
@@ -984,6 +1114,22 @@ test("attachSurface render mode yields render-state and render-delta from cached
full: false,
scrollback_rows: 43,
rows: [{ row: 0, runs: [{ text: "ok ", fg: "#00ff00", bg: null, attrs: 0 }] }],
graphics: {
generation: 4n,
removed_image_ids: [99],
placements: [{ ...renderGraphics.placements[0], viewport_col: 1 }],
},
});
transport.emit({
event: "render-delta",
surface: 7,
cursor: { x: 0, y: 0, style: "bar", blink: false, visible: false, color: null },
full: false,
rows: [],
graphics: {
generation: 5n,
images: [{ ...renderGraphics.images[0], generation: 3n }],
},
});
});
const client = new CmuxClient({
@@ -1015,6 +1161,7 @@ test("attachSurface render mode yields render-state and render-delta from cached
width_hint: 3,
}],
}],
graphics: renderGraphics,
});
assert.deepEqual(await stream.next(), {
event: "render-delta",
@@ -1023,11 +1170,361 @@ test("attachSurface render mode yields render-state and render-delta from cached
full: false,
scrollback_rows: 43,
rows: [{ row: 0, runs: [{ text: "ok ", fg: "#00ff00", bg: null, attrs: 0 }] }],
graphics: {
generation: 4n,
removed_image_ids: [99],
placements: [{ ...renderGraphics.placements[0], viewport_col: 1 }],
},
});
assert.deepEqual(await stream.next(), {
event: "render-delta",
surface: 7n,
cursor: { x: 0, y: 0, style: "bar", blink: false, visible: false, color: null },
full: false,
rows: [],
graphics: {
generation: 5n,
images: [{ ...renderGraphics.images[0], generation: 3n }],
},
});
stream.close();
await client.close();
});
test("attachSurface render mode rejects oversized Kitty image data before buffering it", async () => {
const main = new ScriptedTransport((request, transport) => {
transport.emit({
id: request.id,
ok: true,
data: { app: "cmux-tui", version: "0.1.2", protocol: 7, session: "main", pid: 1 },
});
});
const attach = new ScriptedTransport((request, transport) => {
transport.emit({
event: "render-state",
surface: 7,
size: { cols: 1, rows: 1 },
cursor: { x: 0, y: 0, style: "block", blink: false, visible: false, color: null },
default_fg: "#ffffff",
default_bg: "#000000",
scrollback_rows: 0,
rows: [],
graphics: {
...renderGraphics,
images: [{ ...renderGraphics.images[0], data: "AAAAA" }],
},
});
transport.emit({ id: request.id, ok: true, data: {} });
});
const client = new CmuxClient({
transport: main,
streamTransportFactory: () => attach,
timeoutMs: 100,
maxAttachEncodedChars: 4,
} as CmuxClientOptionsWithSecurityLimits);
await assert.rejects(
() => client.attachSurface(7n, { mode: "render" }),
/render-state graphics image data exceeds 4 encoded characters/,
);
await client.close();
});
test("attachSurface render mode requires a bounded Kitty placement array", async () => {
const missingPlacements = {
generation: renderGraphics.generation,
images: renderGraphics.images,
};
for (const [graphics, expected] of [
[missingPlacements, /event render-state\.graphics\.placements is required/],
[{ ...renderGraphics, placements: {} }, /event render-state\.graphics\.placements must be an array/],
[
{
...renderGraphics,
placements: new Array(RENDER_GRAPHIC_MAX_PLACEMENTS + 1)
.fill(renderGraphics.placements[0]),
},
new RegExp(`render-state graphics exceeds ${RENDER_GRAPHIC_MAX_PLACEMENTS} placements`),
],
]) {
const main = new ScriptedTransport((request, transport) => {
transport.emit({
id: request.id,
ok: true,
data: { app: "cmux-tui", version: "0.1.2", protocol: 7, session: "main", pid: 1 },
});
});
const attach = new ScriptedTransport((request, transport) => {
transport.emit({
event: "render-state",
surface: 7,
size: { cols: 1, rows: 1 },
cursor: { x: 0, y: 0, style: "block", blink: false, visible: false, color: null },
default_fg: "#ffffff",
default_bg: "#000000",
scrollback_rows: 0,
rows: [],
graphics,
});
transport.emit({ id: request.id, ok: true, data: {} });
});
const client = new CmuxClient({
transport: main,
streamTransportFactory: () => attach,
timeoutMs: 100,
});
await assert.rejects(
() => client.attachSurface(7n, { mode: "render" }),
expected as RegExp,
);
await client.close();
}
});
test("attachSurface render mode requires a bounded Kitty image array", async () => {
const main = new ScriptedTransport((request, transport) => {
transport.emit({
id: request.id,
ok: true,
data: { app: "cmux-tui", version: "0.1.2", protocol: 7, session: "main", pid: 1 },
});
});
const attach = new ScriptedTransport((request, transport) => {
transport.emit({
event: "render-state",
surface: 7,
size: { cols: 1, rows: 1 },
cursor: { x: 0, y: 0, style: "block", blink: false, visible: false, color: null },
default_fg: "#ffffff",
default_bg: "#000000",
scrollback_rows: 0,
rows: [],
graphics: {
...renderGraphics,
images: new Array(RENDER_GRAPHIC_MAX_IMAGES + 1).fill(renderGraphics.images[0]),
},
});
transport.emit({ id: request.id, ok: true, data: {} });
});
const client = new CmuxClient({
transport: main,
streamTransportFactory: () => attach,
timeoutMs: 100,
});
await assert.rejects(
() => client.attachSurface(7n, { mode: "render" }),
new RegExp(`render-state graphics exceeds ${RENDER_GRAPHIC_MAX_IMAGES} images`),
);
await client.close();
});
test("attachSurface render mode validates bounded removed Kitty image IDs", async () => {
const cases: Array<[unknown, RegExp]> = [
[{}, /event render-delta\.graphics\.removed_image_ids must be an array/],
[
new Array(RENDER_GRAPHIC_MAX_IMAGES + 1).fill(1),
new RegExp(
`render-delta graphics exceeds ${RENDER_GRAPHIC_MAX_IMAGES} removed image IDs`,
),
],
[[0], /render-delta graphics removed_image_ids contains an invalid image ID/],
[[-1], /event render-delta\.graphics\.removed_image_ids\[0\] must be in uint32 range/],
[[1.5], /event render-delta\.graphics\.removed_image_ids\[0\] must be a safe integer number/],
[["1"], /event render-delta\.graphics\.removed_image_ids\[0\] must be a safe integer number/],
];
for (const [removedImageIds, expected] of cases) {
const main = new ScriptedTransport((request, transport) => {
transport.emit({
id: request.id,
ok: true,
data: { app: "cmux-tui", version: "0.1.2", protocol: 7, session: "main", pid: 1 },
});
});
const attach = new ScriptedTransport((request, transport) => {
transport.emit({
event: "render-delta",
surface: 7,
cursor: {
x: 0,
y: 0,
style: "block",
blink: false,
visible: false,
color: null,
},
full: false,
rows: [],
graphics: {
generation: 2n,
removed_image_ids: removedImageIds,
},
});
transport.emit({ id: request.id, ok: true, data: {} });
});
const client = new CmuxClient({
transport: main,
streamTransportFactory: () => attach,
timeoutMs: 100,
});
await assert.rejects(
() => client.attachSurface(7n, { mode: "render" }),
expected,
);
await client.close();
}
});
test("render attach counts non-image JSON bytes against the retained buffer cap", async () => {
const main = new ScriptedTransport((request, transport) => {
transport.emit({
id: request.id,
ok: true,
data: { app: "cmux-tui", version: "0.1.2", protocol: 7, session: "main", pid: 1 },
});
});
const renderDelta = {
event: "render-delta",
surface: 7,
cursor: { x: 0, y: 0, style: "block", blink: false, visible: false, color: null },
full: false,
rows: [{ row: 0, runs: [{ text: "界", fg: null, bg: null, attrs: 0 }] }],
graphics: {
generation: 5n,
removed_image_ids: [9],
placements: [renderGraphics.placements[0]],
},
};
const encoded = stringifyWireJson(renderDelta);
const encodedChars = encoded.length;
assert.ok(new TextEncoder().encode(encoded).byteLength > encodedChars);
const attach = new ScriptedTransport((request, transport) => {
transport.emit(renderDelta);
transport.emit({ id: request.id, ok: true, data: {} });
});
const client = new CmuxClient({
transport: main,
streamTransportFactory: () => attach,
timeoutMs: 100,
maxAttachEncodedChars: encodedChars,
} as CmuxClientOptionsWithSecurityLimits);
await assert.rejects(
() => client.attachSurface(7n, { mode: "render" }),
new RegExp(`stream event data exceeds ${encodedChars} bytes`),
);
await client.close();
});
test("render attach accepts the full decoded-image budget below its encoded limit", async () => {
assert.equal(RENDER_GRAPHIC_MAX_DECODED_BYTES, 10_000_000);
assert.equal(RENDER_GRAPHIC_MAX_ENCODED_CHARS, 13_333_336);
assert.equal(RENDER_GRAPHIC_MAX_IMAGES, 4_096);
assert.equal(RENDER_GRAPHIC_MAX_PLACEMENTS, 16_384);
assert.equal(RENDER_ATTACH_MAX_ENCODED_CHARS, 33_554_432);
assert.equal(DEFAULT_MAX_ATTACH_ENCODED_CHARS, RENDER_ATTACH_MAX_ENCODED_CHARS);
assert.ok(RENDER_GRAPHIC_MAX_ENCODED_CHARS < RENDER_ATTACH_MAX_ENCODED_CHARS);
const main = new ScriptedTransport((request, transport) => {
transport.emit({
id: request.id,
ok: true,
data: { app: "cmux-tui", version: "0.1.2", protocol: 7, session: "main", pid: 1 },
});
});
const encoded = `${"A".repeat(RENDER_GRAPHIC_MAX_ENCODED_CHARS - 2)}==`;
const attach = new ScriptedTransport((request, transport) => {
transport.emit({
event: "render-state",
surface: 7,
size: { cols: 1, rows: 1 },
cursor: { x: 0, y: 0, style: "block", blink: false, visible: false, color: null },
default_fg: "#ffffff",
default_bg: "#000000",
scrollback_rows: 0,
rows: [],
graphics: {
generation: 1n,
images: [{
id: 1,
generation: 1n,
width: RENDER_GRAPHIC_MAX_DECODED_BYTES / 4,
height: 1,
format: "rgba",
data: encoded,
}],
placements: [],
},
});
transport.emit({ id: request.id, ok: true, data: {} });
});
const client = new CmuxClient({
transport: main,
streamTransportFactory: () => attach,
timeoutMs: 1_000,
});
await client.identify();
const stream = await client.attachSurface(7n, { mode: "render" });
const event = await stream.next() as RenderStateEvent;
assert.equal(
event.graphics?.images?.[0]?.data.length,
RENDER_GRAPHIC_MAX_ENCODED_CHARS,
);
stream.close();
await client.close();
});
test("render attach rejects an image above its protocol limit under the larger attach cap", async () => {
const main = new ScriptedTransport((request, transport) => {
transport.emit({
id: request.id,
ok: true,
data: { app: "cmux-tui", version: "0.1.2", protocol: 7, session: "main", pid: 1 },
});
});
const attach = new ScriptedTransport((request, transport) => {
transport.emit({
event: "render-state",
surface: 7,
size: { cols: 1, rows: 1 },
cursor: { x: 0, y: 0, style: "block", blink: false, visible: false, color: null },
default_fg: "#ffffff",
default_bg: "#000000",
scrollback_rows: 0,
rows: [],
graphics: {
generation: 1n,
images: [{
id: 1,
generation: 1n,
width: 1,
height: 1,
format: "rgba",
data: "A".repeat(RENDER_GRAPHIC_MAX_ENCODED_CHARS + 1),
}],
placements: [],
},
});
transport.emit({ id: request.id, ok: true, data: {} });
});
const client = new CmuxClient({
transport: main,
streamTransportFactory: () => attach,
timeoutMs: 1_000,
});
await assert.rejects(
() => client.attachSurface(7n, { mode: "render" }),
new RegExp(
`render-state graphics image data exceeds ${RENDER_GRAPHIC_MAX_ENCODED_CHARS} encoded characters`,
),
);
await client.close();
});
test("attachSurface render mode accepts a newer additive protocol", async () => {
const main = new ScriptedTransport((request, transport) => {
assert.equal(request.cmd, "identify");
@@ -12,8 +12,8 @@ import {
test("generated protocol coverage matches the canonical v10 IR", () => {
assert.equal(MUX_PROTOCOL_VERSION, 10);
assert.equal(SDK_SCHEMA_VERSION, 2);
assert.equal(Object.keys(COMMAND_METADATA).length, 91);
assert.equal(Object.keys(EVENT_METADATA).length, 44);
assert.equal(Object.keys(COMMAND_METADATA).length, 92);
assert.equal(Object.keys(EVENT_METADATA).length, 45);
assert.equal(SDK_IR_SHA256.length, 64);
assert.deepEqual(Object.keys(PROFILES).sort(), [
"control",
@@ -27,7 +27,7 @@ test("generated active events exclude serialized-only shapes", () => {
const emitted = Object.entries(EVENT_METADATA)
.filter(([, metadata]) => metadata.emission === "emitted")
.map(([name]) => name);
assert.equal(emitted.length, 43);
assert.equal(emitted.length, 44);
assert.equal(EVENT_METADATA["client-list-invalidated"].emission, "serialized-never-emitted");
assert.equal(emitted.includes("client-list-invalidated"), false);
});
+2 -2
View File
@@ -126,11 +126,11 @@ test "package consumer imports handwritten root and generated raw module" {
wheel.pointer_frame_seq,
);
try std.testing.expectEqual(
@as(usize, 91),
@as(usize, 92),
cmux.raw.protocol.command_count,
);
try std.testing.expectEqual(
@as(usize, 44),
@as(usize, 45),
cmux.raw.protocol.event_count,
);
try std.testing.expect(
+2 -2
View File
@@ -44,7 +44,7 @@ test {
_ = @import("raw/wire_presence_test.zig");
_ = @import("raw/generated/presence_test.zig");
std.testing.refAllDecls(protocol);
try std.testing.expectEqual(@as(usize, 91), protocol.command_count);
try std.testing.expectEqual(@as(usize, 92), protocol.command_count);
for ([_][]const u8{
"browser-frame-presented",
"browser-key-press",
@@ -64,5 +64,5 @@ test {
}
try std.testing.expect(found);
}
try std.testing.expectEqual(@as(usize, 44), protocol.event_count);
try std.testing.expectEqual(@as(usize, 45), protocol.event_count);
}
@@ -2,17 +2,17 @@
"files": [
{
"path": "presence_test.zig",
"sha256": "5e2ea6b296a2b20e7f6c636b40b23f1e911d81c470eec8873108e8c8f1c1578e",
"size": 4501
"sha256": "d8bd1c7beac5e0fd9389a20f4eab35a2f22c3292f05145b58a89d7279ec4ceeb",
"size": 6345
},
{
"path": "protocol.zig",
"sha256": "5a15b6e96bab857c60dbe73e54ada9b27b584343ff80dbb3fda3b30394954ce3",
"size": 136168
"sha256": "910136e09c76845dd9f0dbc8d0742423916fa8ad4036b9cb84385e6b218937fa",
"size": 142044
}
],
"format": 1,
"ir_sha256": "4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e",
"ir_sha256": "17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350",
"language": "zig",
"mux_protocol": 10,
"schema_version": 2
@@ -30,18 +30,32 @@ test "every generated optional non-null field rejects explicit null" {
try expectExplicitNullRejected(protocol.UndoLayoutRequest, "confirm_close");
try expectExplicitNullRejected(protocol.ColorsChangedEvent, "palette");
try expectExplicitNullRejected(protocol.ColorsChangedEvent, "surface");
try expectExplicitNullRejected(protocol.GraphicsStatusEvent, "attempts");
try expectExplicitNullRejected(protocol.GraphicsStatusEvent, "cell_height");
try expectExplicitNullRejected(protocol.GraphicsStatusEvent, "cell_width");
try expectExplicitNullRejected(protocol.GraphicsStatusEvent, "error");
try expectExplicitNullRejected(protocol.GraphicsStatusEvent, "remaining");
try expectExplicitNullRejected(protocol.GraphicsStatusEvent, "retry_exhausted");
try expectExplicitNullRejected(protocol.GraphicsStatusEvent, "summary");
try expectExplicitNullRejected(protocol.OutputEvent, "colors");
try expectExplicitNullRejected(protocol.OverflowEvent, "scope");
try expectExplicitNullRejected(protocol.OverflowEvent, "surface");
try expectExplicitNullRejected(protocol.RenderDeltaEvent, "default_bg");
try expectExplicitNullRejected(protocol.RenderDeltaEvent, "default_fg");
try expectExplicitNullRejected(protocol.RenderDeltaEvent, "graphics");
try expectExplicitNullRejected(protocol.RenderDeltaEvent, "history_epoch");
try expectExplicitNullRejected(protocol.RenderDeltaEvent, "scrollback_rows");
try expectExplicitNullRejected(protocol.RenderDeltaEvent, "size");
try expectExplicitNullRejected(protocol.RenderStateEvent, "graphics");
try expectExplicitNullRejected(protocol.ResizedEvent, "colors");
try expectExplicitNullRejected(protocol.ResizedEvent, "data");
try expectExplicitNullRejected(protocol.ResizedEvent, "kitty_graphics_state");
try expectExplicitNullRejected(protocol.ResizedEvent, "kitty_image_aliases");
try expectExplicitNullRejected(protocol.ResizedEvent, "replay");
try expectExplicitNullRejected(protocol.TitleChangedEvent, "title");
try expectExplicitNullRejected(protocol.VtStateEvent, "colors");
try expectExplicitNullRejected(protocol.VtStateEvent, "kitty_graphics_state");
try expectExplicitNullRejected(protocol.VtStateEvent, "kitty_image_aliases");
try expectExplicitNullRejected(protocol.WorkspaceAddedEvent, "mutation_id");
try expectExplicitNullRejected(protocol.WorkspaceAddedEvent, "origin");
try expectExplicitNullRejected(protocol.WorkspaceClosedEvent, "mutation_id");
@@ -56,6 +70,13 @@ test "every generated optional non-null field rejects explicit null" {
try expectExplicitNullRejected(protocol.LayoutUndoUndone, "confirmation_required");
try expectExplicitNullRejected(protocol.LivePane, "focused_at");
try expectExplicitNullRejected(protocol.LivePane, "short_id");
try expectExplicitNullRejected(protocol.RenderGraphicPlacement, "anchor_col");
try expectExplicitNullRejected(protocol.RenderGraphicPlacement, "anchor_row");
try expectExplicitNullRejected(protocol.RenderGraphics, "images");
try expectExplicitNullRejected(protocol.RenderGraphics, "removed_image_ids");
try expectExplicitNullRejected(protocol.RenderGraphicsDelta, "images");
try expectExplicitNullRejected(protocol.RenderGraphicsDelta, "placements");
try expectExplicitNullRejected(protocol.RenderGraphicsDelta, "removed_image_ids");
try expectExplicitNullRejected(protocol.RenderRun, "underline");
try expectExplicitNullRejected(protocol.RenderRun, "width_hint");
try expectExplicitNullRejected(protocol.Screen, "short_id");
@@ -68,6 +89,8 @@ test "every generated optional non-null field rejects explicit null" {
try expectExplicitNullRejected(protocol.Tree, "registry_id");
try expectExplicitNullRejected(protocol.Tree, "terminal_revision");
try expectExplicitNullRejected(protocol.Tree, "workspace_revision");
try expectExplicitNullRejected(protocol.VtStateResult, "kitty_graphics_state");
try expectExplicitNullRejected(protocol.VtStateResult, "kitty_image_aliases");
try expectExplicitNullRejected(protocol.Workspace, "key");
try expectExplicitNullRejected(protocol.Workspace, "short_id");
try expectExplicitNullRejected(protocol.WorkspaceMutationResult, "changed");
@@ -7,7 +7,7 @@ const client_runtime = @import("../client.zig");
pub const schema_version: u16 = 2;
pub const mux_protocol: u16 = 10;
pub const ir_sha256 = "4248b1dd1a8640da3f983118ddce2e8d5ff94db5dc2e18cb9ebc5ba41052284e";
pub const ir_sha256 = "17f8e86213cd09bd9ae05960964c3240f2a92aa4e086f7542bf6211bce9ff350";
pub const AgentRecord = struct {
session: wire.Nullable([]const u8),
@@ -114,6 +114,12 @@ pub const CellPixelResize = struct {
surface: Id,
};
pub const CellPixelSurface = struct {
height_px: u16,
surface: Id,
width_px: u16,
};
pub const ClientInfo = struct {
attached: []const Id,
client: u64,
@@ -295,6 +301,12 @@ pub const FrontendProjection = struct {
};
};
pub const GetCellPixelsResult = struct {
height_px: u16,
surfaces: []const CellPixelSurface,
width_px: u16,
};
pub const Id = u64;
pub const IdMappingKind = enum {
@@ -354,6 +366,23 @@ pub const IdsResult = struct {
/// The wire field intentionally carries a frontend-authored or runtime-authored arbitrary JSON document.
pub const JsonValue = wire.Value;
pub const KittyGraphicsState = struct {
alternate_next_image_id: u32,
alternate_replay_next_image_id: u32,
image_bytes: u64,
images: u64,
inflight_bytes: u64,
placements: u64,
primary_next_image_id: u32,
primary_replay_next_image_id: u32,
replay_cursor_offset: u32,
};
pub const KittyImageAlias = struct {
image_id: u32,
image_number: u32,
};
pub const LayoutLeaf = struct {
pane: Id,
};
@@ -600,6 +629,7 @@ pub const ReadScreenResult = struct {
};
pub const ReadScrollbackResult = struct {
epoch: u64,
rows: []const RenderRow,
start: u32,
total: u32,
@@ -614,6 +644,87 @@ pub const RenderCursor = struct {
y: u16,
};
pub const RenderGraphicFormat = enum {
rgb,
rgba,
pub fn fromWire(value: []const u8) !@This() {
if (std.mem.eql(u8, value, "rgb")) return .rgb;
if (std.mem.eql(u8, value, "rgba")) return .rgba;
return error.UnknownEnumValue;
}
pub fn toWire(self: @This()) []const u8 {
return switch (self) {
.rgb => "rgb",
.rgba => "rgba",
};
}
};
pub const RenderGraphicImage = struct {
data: Base64,
format: RenderGraphicFormat,
generation: u64,
height: u32,
id: u32,
width: u32,
};
pub const RenderGraphicPlacement = struct {
anchor_col: ?u16 = null,
anchor_row: ?u32 = null,
columns: u32,
grid_cols: u32,
grid_rows: u32,
image_id: u32,
ordinal: u32,
pixel_height: u32,
pixel_width: u32,
placement_id: u32,
rows: u32,
source_height: u32,
source_width: u32,
source_x: u32,
source_y: u32,
viewport_col: i32,
viewport_row: i32,
viewport_visible: bool,
x_offset: u32,
y_offset: u32,
z: i32,
pub const cmux_wire_optional_nonnull_fields = [_][]const u8{
"anchor_col",
"anchor_row",
};
};
pub const RenderGraphics = struct {
generation: u64,
images: ?[]const RenderGraphicImage = null,
placements: []const RenderGraphicPlacement,
removed_image_ids: ?[]const u32 = null,
pub const cmux_wire_optional_nonnull_fields = [_][]const u8{
"images",
"removed_image_ids",
};
};
pub const RenderGraphicsDelta = struct {
generation: u64,
images: ?[]const RenderGraphicImage = null,
placements: ?[]const RenderGraphicPlacement = null,
removed_image_ids: ?[]const u32 = null,
pub const cmux_wire_optional_nonnull_fields = [_][]const u8{
"images",
"placements",
"removed_image_ids",
};
};
pub const RenderRow = struct {
row: u32,
runs: []const RenderRun,
@@ -1337,7 +1448,14 @@ pub const Tree = struct {
pub const VtStateResult = struct {
cols: u16,
data: Base64,
kitty_graphics_state: ?KittyGraphicsState = null,
kitty_image_aliases: ?[]const KittyImageAlias = null,
rows: u16,
pub const cmux_wire_optional_nonnull_fields = [_][]const u8{
"kitty_graphics_state",
"kitty_image_aliases",
};
};
pub const WaitForResult = struct {
@@ -2129,6 +2247,21 @@ pub fn focusPane(client: anytype, request: FocusPaneRequest) !wire.Decoded(Focus
);
}
pub const GetCellPixelsRequest = struct {};
pub fn getCellPixels(client: anytype, request: GetCellPixelsRequest) !wire.Decoded(GetCellPixelsResult) {
return client.callTyped(
GetCellPixelsResult,
.{
.name = "get-cell-pixels",
.authority = "frontend",
.since = 6,
.capability = null,
},
request,
);
}
pub const GetFrontendProjectionRequest = struct {
frontend: []const u8,
scope: []const u8,
@@ -3627,6 +3760,49 @@ pub const FrontendProjectionChangedEvent = struct {
subject_key: []const u8,
};
pub const GraphicsStatusEventKind = enum {
kitty_image_budget_worker_start_failed,
kitty_image_budget_update_failed,
cell_pixel_update_retries_exhausted,
pub fn fromWire(value: []const u8) !@This() {
if (std.mem.eql(u8, value, "kitty-image-budget-worker-start-failed")) return .kitty_image_budget_worker_start_failed;
if (std.mem.eql(u8, value, "kitty-image-budget-update-failed")) return .kitty_image_budget_update_failed;
if (std.mem.eql(u8, value, "cell-pixel-update-retries-exhausted")) return .cell_pixel_update_retries_exhausted;
return error.UnknownEnumValue;
}
pub fn toWire(self: @This()) []const u8 {
return switch (self) {
.kitty_image_budget_worker_start_failed => "kitty-image-budget-worker-start-failed",
.kitty_image_budget_update_failed => "kitty-image-budget-update-failed",
.cell_pixel_update_retries_exhausted => "cell-pixel-update-retries-exhausted",
};
}
};
pub const GraphicsStatusEvent = struct {
attempts: ?u16 = null,
cell_height: ?u16 = null,
cell_width: ?u16 = null,
@"error": ?[]const u8 = null,
event: []const u8,
kind: GraphicsStatusEventKind,
remaining: ?u64 = null,
retry_exhausted: ?bool = null,
summary: ?[]const u8 = null,
pub const cmux_wire_optional_nonnull_fields = [_][]const u8{
"attempts",
"cell_height",
"cell_width",
"error",
"remaining",
"retry_exhausted",
"summary",
};
};
pub const LayoutChangedEvent = struct {
event: []const u8,
screen: Id,
@@ -3701,6 +3877,8 @@ pub const RenderDeltaEvent = struct {
default_fg: ?ColorHex = null,
event: []const u8,
full: bool,
graphics: ?RenderGraphicsDelta = null,
history_epoch: ?u64 = null,
rows: []const RenderRow,
scrollback_rows: ?u32 = null,
size: ?Size = null,
@@ -3709,6 +3887,8 @@ pub const RenderDeltaEvent = struct {
pub const cmux_wire_optional_nonnull_fields = [_][]const u8{
"default_bg",
"default_fg",
"graphics",
"history_epoch",
"scrollback_rows",
"size",
};
@@ -3719,10 +3899,16 @@ pub const RenderStateEvent = struct {
default_bg: ColorHex,
default_fg: ColorHex,
event: []const u8,
graphics: ?RenderGraphics = null,
history_epoch: u64,
rows: []const RenderRow,
scrollback_rows: u32,
size: Size,
surface: Id,
pub const cmux_wire_optional_nonnull_fields = [_][]const u8{
"graphics",
};
};
pub const ResizedEvent = struct {
@@ -3731,6 +3917,8 @@ pub const ResizedEvent = struct {
/// Protocol 6 compatibility field.
data: ?Base64 = null,
event: []const u8,
kitty_graphics_state: ?KittyGraphicsState = null,
kitty_image_aliases: ?[]const KittyImageAlias = null,
replay: ?Base64 = null,
rows: u16,
surface: Id,
@@ -3738,6 +3926,8 @@ pub const ResizedEvent = struct {
pub const cmux_wire_optional_nonnull_fields = [_][]const u8{
"colors",
"data",
"kitty_graphics_state",
"kitty_image_aliases",
"replay",
};
};
@@ -3861,11 +4051,15 @@ pub const VtStateEvent = struct {
cols: u16,
data: Base64,
event: []const u8,
kitty_graphics_state: ?KittyGraphicsState = null,
kitty_image_aliases: ?[]const KittyImageAlias = null,
rows: u16,
surface: Id,
pub const cmux_wire_optional_nonnull_fields = [_][]const u8{
"colors",
"kitty_graphics_state",
"kitty_image_aliases",
};
};
@@ -3959,6 +4153,7 @@ pub const Event = union(enum) {
empty: EmptyEvent,
frame: FrameEvent,
frontend_projection_changed: FrontendProjectionChangedEvent,
graphics_status: GraphicsStatusEvent,
layout_changed: LayoutChangedEvent,
notification: NotificationEvent,
output: OutputEvent,
@@ -4008,6 +4203,7 @@ pub fn eventWireName(event: Event) []const u8 {
.empty => "empty",
.frame => "frame",
.frontend_projection_changed => "frontend-projection-changed",
.graphics_status => "graphics-status",
.layout_changed => "layout-changed",
.notification => "notification",
.output => "output",
@@ -4106,6 +4302,10 @@ pub fn decodeEvent(allocator: std.mem.Allocator, value: wire.Value) !DecodedEven
const decoded = try wire.decodeLeaky(FrontendProjectionChangedEvent, arena.allocator(), value);
return .{ .arena = arena, .value = .{ .frontend_projection_changed = decoded } };
}
if (std.mem.eql(u8, name, "graphics-status")) {
const decoded = try wire.decodeLeaky(GraphicsStatusEvent, arena.allocator(), value);
return .{ .arena = arena, .value = .{ .graphics_status = decoded } };
}
if (std.mem.eql(u8, name, "layout-changed")) {
const decoded = try wire.decodeLeaky(LayoutChangedEvent, arena.allocator(), value);
return .{ .arena = arena, .value = .{ .layout_changed = decoded } };
@@ -4259,7 +4459,7 @@ pub const CommandDescriptor = struct {
stream: ?[]const u8,
};
pub const command_count: usize = 91;
pub const command_count: usize = 92;
pub const commands = [_]CommandDescriptor{
.{ .name = "apply-layout", .authority = "control", .since = 6, .capability = null, .stream = null },
.{ .name = "attach-surface", .authority = "frontend", .since = 5, .capability = null, .stream = "attach" },
@@ -4291,6 +4491,7 @@ pub const commands = [_]CommandDescriptor{
.{ .name = "export-layout", .authority = "control", .since = 6, .capability = null, .stream = null },
.{ .name = "focus-direction", .authority = "control", .since = 6, .capability = null, .stream = null },
.{ .name = "focus-pane", .authority = "control", .since = 5, .capability = null, .stream = null },
.{ .name = "get-cell-pixels", .authority = "frontend", .since = 6, .capability = null, .stream = null },
.{ .name = "get-frontend-projection", .authority = "control", .since = 7, .capability = null, .stream = null },
.{ .name = "identify", .authority = "control", .since = 5, .capability = null, .stream = null },
.{ .name = "ids", .authority = "control", .since = 6, .capability = null, .stream = null },
@@ -4374,39 +4575,40 @@ const event_streams_9 = [_][]const u8{"subscribe"};
const event_streams_10 = [_][]const u8{"attach-browser"};
const event_streams_11 = [_][]const u8{"subscribe"};
const event_streams_12 = [_][]const u8{"subscribe"};
const event_streams_13 = [_][]const u8{ "subscribe", "attach-byte", "attach-browser" };
const event_streams_14 = [_][]const u8{"attach-byte"};
const event_streams_15 = [_][]const u8{ "subscribe", "attach-byte", "attach-render", "attach-browser" };
const event_streams_16 = [_][]const u8{"subscribe"};
const event_streams_13 = [_][]const u8{"subscribe"};
const event_streams_14 = [_][]const u8{ "subscribe", "attach-byte", "attach-browser" };
const event_streams_15 = [_][]const u8{"attach-byte"};
const event_streams_16 = [_][]const u8{ "subscribe", "attach-byte", "attach-render", "attach-browser" };
const event_streams_17 = [_][]const u8{"subscribe"};
const event_streams_18 = [_][]const u8{"subscribe-deltas"};
const event_streams_18 = [_][]const u8{"subscribe"};
const event_streams_19 = [_][]const u8{"subscribe-deltas"};
const event_streams_20 = [_][]const u8{"attach-render"};
const event_streams_20 = [_][]const u8{"subscribe-deltas"};
const event_streams_21 = [_][]const u8{"attach-render"};
const event_streams_22 = [_][]const u8{"attach-byte"};
const event_streams_23 = [_][]const u8{"subscribe-deltas"};
const event_streams_22 = [_][]const u8{"attach-render"};
const event_streams_23 = [_][]const u8{"attach-byte"};
const event_streams_24 = [_][]const u8{"subscribe-deltas"};
const event_streams_25 = [_][]const u8{"subscribe-deltas"};
const event_streams_26 = [_][]const u8{ "subscribe", "attach-byte", "attach-render", "attach-browser" };
const event_streams_27 = [_][]const u8{"subscribe"};
const event_streams_26 = [_][]const u8{"subscribe-deltas"};
const event_streams_27 = [_][]const u8{ "subscribe", "attach-byte", "attach-render", "attach-browser" };
const event_streams_28 = [_][]const u8{"subscribe"};
const event_streams_29 = [_][]const u8{"subscribe"};
const event_streams_30 = [_][]const u8{"subscribe"};
const event_streams_31 = [_][]const u8{"subscribe"};
const event_streams_32 = [_][]const u8{"subscribe-deltas"};
const event_streams_32 = [_][]const u8{"subscribe"};
const event_streams_33 = [_][]const u8{"subscribe-deltas"};
const event_streams_34 = [_][]const u8{"subscribe-deltas"};
const event_streams_35 = [_][]const u8{"subscribe"};
const event_streams_35 = [_][]const u8{"subscribe-deltas"};
const event_streams_36 = [_][]const u8{"subscribe"};
const event_streams_37 = [_][]const u8{"subscribe"};
const event_streams_38 = [_][]const u8{"attach-byte"};
const event_streams_39 = [_][]const u8{"subscribe"};
const event_streams_40 = [_][]const u8{"subscribe-deltas"};
const event_streams_38 = [_][]const u8{"subscribe"};
const event_streams_39 = [_][]const u8{"attach-byte"};
const event_streams_40 = [_][]const u8{"subscribe"};
const event_streams_41 = [_][]const u8{"subscribe-deltas"};
const event_streams_42 = [_][]const u8{"subscribe-deltas"};
const event_streams_43 = [_][]const u8{"subscribe-deltas"};
const event_streams_44 = [_][]const u8{"subscribe-deltas"};
pub const event_count: usize = 44;
pub const event_count: usize = 45;
pub const events = [_]EventDescriptor{
.{ .name = "bell", .since = 5, .capability = null, .streams = &event_streams_0 },
.{ .name = "browser-state", .since = 6, .capability = null, .streams = &event_streams_1 },
@@ -4420,36 +4622,37 @@ pub const events = [_]EventDescriptor{
.{ .name = "empty", .since = 5, .capability = null, .streams = &event_streams_9 },
.{ .name = "frame", .since = 6, .capability = null, .streams = &event_streams_10 },
.{ .name = "frontend-projection-changed", .since = 7, .capability = null, .streams = &event_streams_11 },
.{ .name = "layout-changed", .since = 6, .capability = null, .streams = &event_streams_12 },
.{ .name = "notification", .since = 6, .capability = null, .streams = &event_streams_13 },
.{ .name = "output", .since = 5, .capability = null, .streams = &event_streams_14 },
.{ .name = "overflow", .since = 7, .capability = null, .streams = &event_streams_15 },
.{ .name = "pairing-requested", .since = 7, .capability = null, .streams = &event_streams_16 },
.{ .name = "pairing-resolved", .since = 7, .capability = null, .streams = &event_streams_17 },
.{ .name = "pane-added", .since = 7, .capability = null, .streams = &event_streams_18 },
.{ .name = "pane-closed", .since = 7, .capability = null, .streams = &event_streams_19 },
.{ .name = "render-delta", .since = 7, .capability = null, .streams = &event_streams_20 },
.{ .name = "render-state", .since = 7, .capability = null, .streams = &event_streams_21 },
.{ .name = "resized", .since = 6, .capability = null, .streams = &event_streams_22 },
.{ .name = "screen-added", .since = 7, .capability = null, .streams = &event_streams_23 },
.{ .name = "screen-closed", .since = 7, .capability = null, .streams = &event_streams_24 },
.{ .name = "screen-renamed", .since = 7, .capability = null, .streams = &event_streams_25 },
.{ .name = "scroll-changed", .since = 6, .capability = null, .streams = &event_streams_26 },
.{ .name = "status", .since = 5, .capability = null, .streams = &event_streams_27 },
.{ .name = "surface-exited", .since = 5, .capability = null, .streams = &event_streams_28 },
.{ .name = "surface-output", .since = 5, .capability = null, .streams = &event_streams_29 },
.{ .name = "surface-resize-failed", .since = 7, .capability = null, .streams = &event_streams_30 },
.{ .name = "surface-resized", .since = 5, .capability = null, .streams = &event_streams_31 },
.{ .name = "tab-added", .since = 7, .capability = null, .streams = &event_streams_32 },
.{ .name = "tab-closed", .since = 7, .capability = null, .streams = &event_streams_33 },
.{ .name = "tab-renamed", .since = 7, .capability = null, .streams = &event_streams_34 },
.{ .name = "terminal-registry-changed", .since = 9, .capability = null, .streams = &event_streams_35 },
.{ .name = "title-changed", .since = 5, .capability = null, .streams = &event_streams_36 },
.{ .name = "tree-changed", .since = 5, .capability = null, .streams = &event_streams_37 },
.{ .name = "vt-state", .since = 5, .capability = null, .streams = &event_streams_38 },
.{ .name = "window-title-requested", .since = 6, .capability = null, .streams = &event_streams_39 },
.{ .name = "workspace-added", .since = 7, .capability = null, .streams = &event_streams_40 },
.{ .name = "workspace-closed", .since = 7, .capability = null, .streams = &event_streams_41 },
.{ .name = "workspace-moved", .since = 7, .capability = null, .streams = &event_streams_42 },
.{ .name = "workspace-renamed", .since = 7, .capability = null, .streams = &event_streams_43 },
.{ .name = "graphics-status", .since = 10, .capability = null, .streams = &event_streams_12 },
.{ .name = "layout-changed", .since = 6, .capability = null, .streams = &event_streams_13 },
.{ .name = "notification", .since = 6, .capability = null, .streams = &event_streams_14 },
.{ .name = "output", .since = 5, .capability = null, .streams = &event_streams_15 },
.{ .name = "overflow", .since = 7, .capability = null, .streams = &event_streams_16 },
.{ .name = "pairing-requested", .since = 7, .capability = null, .streams = &event_streams_17 },
.{ .name = "pairing-resolved", .since = 7, .capability = null, .streams = &event_streams_18 },
.{ .name = "pane-added", .since = 7, .capability = null, .streams = &event_streams_19 },
.{ .name = "pane-closed", .since = 7, .capability = null, .streams = &event_streams_20 },
.{ .name = "render-delta", .since = 7, .capability = null, .streams = &event_streams_21 },
.{ .name = "render-state", .since = 7, .capability = null, .streams = &event_streams_22 },
.{ .name = "resized", .since = 6, .capability = null, .streams = &event_streams_23 },
.{ .name = "screen-added", .since = 7, .capability = null, .streams = &event_streams_24 },
.{ .name = "screen-closed", .since = 7, .capability = null, .streams = &event_streams_25 },
.{ .name = "screen-renamed", .since = 7, .capability = null, .streams = &event_streams_26 },
.{ .name = "scroll-changed", .since = 6, .capability = null, .streams = &event_streams_27 },
.{ .name = "status", .since = 5, .capability = null, .streams = &event_streams_28 },
.{ .name = "surface-exited", .since = 5, .capability = null, .streams = &event_streams_29 },
.{ .name = "surface-output", .since = 5, .capability = null, .streams = &event_streams_30 },
.{ .name = "surface-resize-failed", .since = 7, .capability = null, .streams = &event_streams_31 },
.{ .name = "surface-resized", .since = 5, .capability = null, .streams = &event_streams_32 },
.{ .name = "tab-added", .since = 7, .capability = null, .streams = &event_streams_33 },
.{ .name = "tab-closed", .since = 7, .capability = null, .streams = &event_streams_34 },
.{ .name = "tab-renamed", .since = 7, .capability = null, .streams = &event_streams_35 },
.{ .name = "terminal-registry-changed", .since = 9, .capability = null, .streams = &event_streams_36 },
.{ .name = "title-changed", .since = 5, .capability = null, .streams = &event_streams_37 },
.{ .name = "tree-changed", .since = 5, .capability = null, .streams = &event_streams_38 },
.{ .name = "vt-state", .since = 5, .capability = null, .streams = &event_streams_39 },
.{ .name = "window-title-requested", .since = 6, .capability = null, .streams = &event_streams_40 },
.{ .name = "workspace-added", .since = 7, .capability = null, .streams = &event_streams_41 },
.{ .name = "workspace-closed", .since = 7, .capability = null, .streams = &event_streams_42 },
.{ .name = "workspace-moved", .since = 7, .capability = null, .streams = &event_streams_43 },
.{ .name = "workspace-renamed", .since = 7, .capability = null, .streams = &event_streams_44 },
};
@@ -32,3 +32,7 @@ pub use rpc::{
StructuredDiffLineV1, StructuredDiffV1, StructuredFileDiffV1, WorkspaceId, WorkspaceRequest,
WorkspaceResponse,
};
/// Maximum serialized server-to-client message accepted by remote session
/// transports. Render attach and VT replay responses share this budget.
pub const REMOTE_SESSION_MESSAGE_MAX_BYTES: usize = 32 * 1024 * 1024;
+1 -1
View File
@@ -8,10 +8,10 @@ use cmux_remote_protocol::{MUX_INPUT_V1_FEATURE, RouteId, Service, ServiceContro
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::sync::{Semaphore, oneshot};
use crate::mux_codec::MAX_MUX_LINE_BYTES;
use crate::service::{ServiceError, ServiceMultiplexer, ServiceStream};
use crate::services::ServicesError;
const MAX_MUX_LINE_BYTES: usize = 16 * 1024 * 1024;
pub const DEFAULT_MAX_FORWARD_CONNECTIONS: usize = 128;
pub struct LocalPortForward {
+8 -6
View File
@@ -2,17 +2,18 @@ use std::collections::HashMap;
use std::fmt;
use bytes::{BufMut, Bytes, BytesMut};
use cmux_remote_protocol::{Lane, MAX_FRAME_PAYLOAD};
use cmux_remote_protocol::{Lane, MAX_FRAME_PAYLOAD, REMOTE_SESSION_MESSAGE_MAX_BYTES};
const MAGIC: [u8; 4] = *b"CMXL";
const HEADER_BYTES: usize = 4 + 8 + 4 + 4;
const CHUNK_BYTES: usize = MAX_FRAME_PAYLOAD - HEADER_BYTES;
const MAX_LINE_BYTES: usize = 16 * 1024 * 1024;
// The local mux transport appends a newline after each serialized message.
pub(crate) const MAX_MUX_LINE_BYTES: usize = REMOTE_SESSION_MESSAGE_MAX_BYTES + 1;
const MAX_IN_FLIGHT_LINES: usize = 256;
const MAX_IN_FLIGHT_BYTES: usize = 32 * 1024 * 1024;
const MAX_IN_FLIGHT_BYTES: usize = MAX_MUX_LINE_BYTES * 2;
pub(crate) fn encode_line(message: u64, line: &[u8]) -> Result<Vec<Bytes>, MuxCodecError> {
if line.len() > MAX_LINE_BYTES {
if line.len() > MAX_MUX_LINE_BYTES {
return Err(MuxCodecError::LineTooLarge(line.len()));
}
let parts = line.len().max(1).div_ceil(CHUNK_BYTES);
@@ -63,7 +64,8 @@ impl MuxLineAssembler {
let message = u64::from_be_bytes(packet[4..12].try_into().unwrap());
let part = u32::from_be_bytes(packet[12..16].try_into().unwrap());
let parts = u32::from_be_bytes(packet[16..20].try_into().unwrap());
if parts == 0 || part >= parts || parts as usize > MAX_LINE_BYTES.div_ceil(CHUNK_BYTES) {
if parts == 0 || part >= parts || parts as usize > MAX_MUX_LINE_BYTES.div_ceil(CHUNK_BYTES)
{
return Err(MuxCodecError::InvalidPacket);
}
if !self.lines.contains_key(&message) {
@@ -83,7 +85,7 @@ impl MuxLineAssembler {
return Err(MuxCodecError::InvalidPacket);
}
let payload = packet.slice(HEADER_BYTES..);
if line.bytes.saturating_add(payload.len()) > MAX_LINE_BYTES
if line.bytes.saturating_add(payload.len()) > MAX_MUX_LINE_BYTES
|| self.bytes.saturating_add(payload.len()) > MAX_IN_FLIGHT_BYTES
{
return Err(MuxCodecError::LineTooLarge(line.bytes.saturating_add(payload.len())));
+1
View File
@@ -14,6 +14,7 @@ workspace = true
ghostty-vt.workspace = true
cmux-tui-cdp.workspace = true
cmux-pty.workspace = true
cmux-remote-protocol.workspace = true
portable-pty.workspace = true
serde.workspace = true
serde_json.workspace = true
+24 -3
View File
@@ -703,7 +703,9 @@ fn fail_surface_route(state: &mut SurfaceRouteState, reason: &str) {
pub struct BrowserSurface {
pub(crate) meta: SurfaceMeta,
session: Mutex<Option<BrowserSession>>,
state: Mutex<BrowserState>,
// Navigation and pointer lifecycle state grows independently of the
// Surface enum. Keep that payload out of line.
state: Mutex<Box<BrowserState>>,
frame_epoch: Arc<FrameEpoch>,
dirty: AtomicBool,
dead: AtomicBool,
@@ -976,7 +978,7 @@ pub(crate) fn new_surface_with_resource_identity(
selection: Mutex::new(None),
},
session: Mutex::new(None),
state: Mutex::new(BrowserState {
state: Mutex::new(Box::new(BrowserState {
latest_frame: None,
accepted_frame_epoch: frame_epoch.current(),
accepted_navigation_epoch: frame_epoch.latest_navigation(),
@@ -1018,7 +1020,7 @@ pub(crate) fn new_surface_with_resource_identity(
last_frame_at: None,
stall_nudged: false,
not_responding_reported: false,
}),
})),
frame_epoch,
dirty: AtomicBool::new(true),
dead: AtomicBool::new(false),
@@ -1967,6 +1969,19 @@ impl BrowserSurface {
}
}
pub fn latest_frame_metadata(&self) -> Option<(u64, u32, u32, Option<u64>)> {
let state = self.state.lock().unwrap();
if matches!(state.status, BrowserStatus::Failed(_)) {
None
} else {
let pointer_frame_seq = self.exported_pointer_frame_seq_locked(&state);
state
.latest_frame
.as_ref()
.map(|frame| (frame.seq, frame.css_width, frame.css_height, pointer_frame_seq))
}
}
/// Return the opaque authority token for guarded pointer input. The token
/// identifies the latest admitted bitmap and rotates on every later bitmap.
pub fn latest_frame_seq(&self) -> Option<u64> {
@@ -2151,6 +2166,10 @@ impl BrowserSurface {
.map(|reservation_id| reservation_id.is_some())
}
pub(crate) fn cell_pixel_size(&self) -> (u16, u16) {
*self.cell_pixels.lock().unwrap()
}
pub fn set_cell_pixel_size_reporting(
&self,
width_px: u16,
@@ -5682,11 +5701,13 @@ mod tests {
browser.store_frame(test_frame(2));
assert_eq!(browser.status(), BrowserStatus::Failed("nope".into()));
assert_eq!(browser.latest_frame(), None);
assert_eq!(browser.latest_frame_metadata(), None);
// Clearing the error restores the retained frame.
browser.clear_error();
assert_eq!(browser.status(), BrowserStatus::Live);
assert_eq!(browser.latest_frame().map(|frame| frame.seq), Some(2));
assert_eq!(browser.latest_frame_metadata(), Some((2, 80, 48, None)));
}
#[test]
@@ -159,7 +159,8 @@ impl SurfaceSessionScope {
| MuxEvent::ClientListInvalidated
| MuxEvent::TreeChanged
| MuxEvent::TreeSelectionChanged => false,
MuxEvent::Status(_)
MuxEvent::GraphicsStatus(_)
| MuxEvent::Status(_)
| MuxEvent::ConfigReloadRequested
| MuxEvent::WindowTitleRequested(_)
| MuxEvent::FrontendProjectionChanged { .. }
+4 -3
View File
@@ -43,9 +43,9 @@ pub use layout::{
pub use model::{Node, Pane, Screen, State, ViewportColumn, Workspace};
pub use mux::{
AgentRecord, AgentSource, AgentState, AppliedLayout, AppliedPane, CellPixelUpdate,
CellPixelUpdateFailure, Direction, LayoutLeafSpec, LayoutRatioError, LayoutSpec,
LayoutUndoError, LayoutUndoResult, Mux, MuxEvent, NotificationEvent, NotificationLevel,
ProviderWorkspaceAuthority, ProviderWorkspaceAuthorityStatus,
CellPixelUpdateFailure, Direction, GraphicsStatus, LayoutLeafSpec, LayoutRatioError,
LayoutSpec, LayoutUndoError, LayoutUndoResult, Mux, MuxEvent, NotificationEvent,
NotificationLevel, ProviderWorkspaceAuthority, ProviderWorkspaceAuthorityStatus,
ProviderWorkspaceAuthorityUpdateError, ResourceNotification, RunPlacement,
SidebarPluginOptions, SidebarPluginStatus, SurfaceNotification, SurfaceResizeReporter,
TreeDelta, TreeDeltaKind, ViewportWidthError, WorkspaceMutationResult, WorkspacePlacement,
@@ -70,6 +70,7 @@ pub use workspace_registry::{
RegistryWorkspace, UnsupportedWorkspaceRegistrySchema, WorkspaceMutation, WorkspaceRegistry,
};
pub use cmux_remote_protocol::REMOTE_SESSION_MESSAGE_MAX_BYTES;
pub use cmux_tui_cdp::BrowserMode;
pub use ghostty_vt::{CursorShape, Rgb};
File diff suppressed because it is too large Load Diff
@@ -143,7 +143,7 @@ fn terminal_state_read(
.map_err(resource_operation_error)?
.map_err(|error| resource_operation_error(error.into()))?;
Ok(json!({
"state_base64":base64::engine::general_purpose::STANDARD.encode(state),
"state_base64":base64::engine::general_purpose::STANDARD.encode(state.bytes),
"cols":cols,
"rows":rows,
}))
File diff suppressed because it is too large Load Diff
@@ -12,9 +12,10 @@ use serde_json::{Value, json};
use sha2::{Digest, Sha256};
use crate::resource::{ResourceError, Selector, SessionPublicId, SidebarViewPublicId};
use crate::surface::RenderAttachFrameReceiver;
use crate::{
Mux, RenderAttachFrame, RenderAttachStream, ResourceSelectors, ResourceTarget, Rgb, Surface,
SurfaceKind, SurfaceRenderFrame,
Mux, RenderAttachStream, ResourceSelectors, ResourceTarget, Rgb, Surface, SurfaceKind,
SurfaceRenderFrame,
};
pub(crate) const SIDEBAR_VIEW_NAME: &str = "sidebar";
@@ -85,7 +86,7 @@ pub(crate) struct SidebarRenderAttachment {
pub sidebar_view_id: SidebarViewPublicId,
pub sidebar_view: Value,
pub initial: Arc<SurfaceRenderFrame>,
pub stream: std::sync::mpsc::Receiver<RenderAttachFrame>,
pub stream: RenderAttachFrameReceiver,
}
pub(crate) fn attach_sidebar_render(
@@ -96,7 +97,7 @@ pub(crate) fn attach_sidebar_render(
if surface.kind() != SurfaceKind::Pty || surface.is_dead() {
return Err(ResourceError::not_found("sidebar_view", id.as_str()));
}
let RenderAttachStream { initial, stream } =
let RenderAttachStream { initial, stream, .. } =
surface.attach_render_stream().map_err(|error| {
ResourceError::operation_failed(
"sidebar_view.attach",
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More