///|
/// Transport-independent session. All mutable buffers are owned by the session.
pub struct Client {
priv options : Options
priv mut phase : Phase
priv mut pending : Array[Int]
priv outgoing : Array[Int]
priv events : Array[Event]
priv mut fb : Framebuffer?
priv mut format : PixelFormat
priv mut queued_cost : Int
}
///|
pub fn client(options? : Options = default_options()) -> Client raise RfbError {
if options.max_pixels <= 0 ||
options.max_pixels > 16777216 ||
options.max_buffer < 1024 ||
options.max_buffer > 67108864 ||
options.max_text < 0 ||
options.max_text > options.max_buffer {
raise Invalid("resource limits")
}
let password = match options.password {
Some(p) => Some(checked_bytes(p))
None => None
}
{
options: { ..options, password, },
phase: Version,
pending: [],
outgoing: [],
events: [],
fb: None,
format: rgb32(),
queued_cost: 0,
}
}
///|
pub fn Client::is_ready(self : Client) -> Bool {
self.phase == Running
}
///|
pub fn Client::is_failed(self : Client) -> Bool {
self.phase == Failed
}
///|
pub fn Client::take_output(self : Client) -> Array[Int] {
let result = self.outgoing.copy()
self.outgoing.clear()
result
}
///|
pub fn Client::take_events(self : Client) -> Array[Event] {
let result = self.events.copy()
self.events.clear()
self.queued_cost = 0
result
}
///|
pub fn Client::snapshot(self : Client) -> Array[Int] raise RfbError {
match self.fb {
Some(f) => f.snapshot()
None => raise Invalid("not initialized")
}
}
///|
pub fn Client::size(self : Client) -> (Int, Int) raise RfbError {
match self.fb {
Some(f) => f.size()
None => raise Invalid("not initialized")
}
}
///| Feed arbitrary byte fragments. Malformed input poisons the session.
///|
/// Take events/output even if a later message in this feed fails.
pub fn Client::feed(self : Client, data : Array[Int]) -> Unit raise RfbError {
if self.phase == Failed || self.phase == Closed {
raise Invalid("session closed or failed")
}
errdefer {
self.phase = Failed
self.pending.clear()
}
if data.length() > self.options.max_buffer - self.pending.length() {
raise Limit("receive buffer")
}
self.pending.append(checked_bytes(data))
let mut offset = 0
while offset < self.pending.length() {
let r = { data: self.pending, pos: offset, }
let complete = try {
if self.phase == Running {
let (w, h) = self.size()
let msg = server_message(r, self.format, w, h, self.options)
self.apply_message(msg)
} else {
let (next, output, screen) = handshake_step(r, self.phase, self.options)
self.phase = next
self.outgoing.append(output)
match screen {
Some(s) => {
self.fb = Some(
framebuffer(s.width, s.height, max_pixels=self.options.max_pixels),
)
self.events.push(Ready(s))
self.format = rgb32()
self.outgoing.append(set_pixel_format(self.format))
self.outgoing.append(set_encodings())
self.outgoing.append(update_request(0, 0, s.width, s.height, false))
}
None => ()
}
}
true
} catch {
NeedMore => false
e => raise e
}
if !complete {
break
}
offset = r.pos
}
self.pending = self.pending[offset:].to_owned()
}
///|
/// EOF at a partial handshake/message is an error, not silent success.
pub fn Client::finish(self : Client) -> Unit raise RfbError {
if self.phase != Running || !self.pending.is_empty() {
self.phase = Failed
self.pending.clear()
raise Invalid("truncated session")
}
self.phase = Closed
}
///|
fn Client::apply_message(
self : Client,
msg : ServerMessage,
) -> Unit raise RfbError {
let (count, cost) = match msg {
Ring => (1, 1)
Text(bytes) => (1, bytes.length() + 1)
Frame(updates) => {
let mut cost = updates.length() + 1
for u in updates {
if u is SetCursor(c) {
cost += c.pixels.length() * 2
}
}
(updates.length() + 1, cost)
}
}
if count > 8192 - self.events.length() ||
cost > self.options.max_buffer / 4 - self.queued_cost {
raise Limit("event queue: drain events")
}
self.queued_cost += cost
match msg {
Ring => self.events.push(Bell)
Text(bytes) => self.events.push(Clipboard(bytes))
Frame(updates) => {
let fb = match self.fb {
Some(f) => f
None => raise Invalid("not initialized")
}
for update in updates {
match update {
Paint(rect, pixels) => {
fb.paint(rect, pixels)
self.events.push(Painted(rect))
}
Copy(rect, x, y) => {
fb.copy_rect(rect, x, y)
self.events.push(Copied(rect, x, y))
}
Resize(w, h) => {
fb.resize(w, h)
self.events.push(Resized(w, h))
}
SetCursor(cursor) => self.events.push(CursorChanged(cursor))
}
}
self.events.push(UpdateComplete)
}
}
}