Module: Plushie::Protocol::Encode

Defined in:
lib/plushie/protocol/encode.rb

Overview

Outbound message encoding for the wire protocol.

Every method produces wire-ready bytes (iodata for msgpack, JSONL string for json). The session field defaults to "" and is injected by the Connection or SessionPool when multiplexing.

See Also:

  • "Incoming messages"

Class Method Summary collapse

Class Method Details

.encode(map, format = :msgpack) ⇒ String

Encode an arbitrary hash as wire-format bytes.

Parameters:

  • map (Hash)

    the message hash (symbol keys)

  • format (:msgpack, :json) (defaults to: :msgpack)

    wire format

Returns:

  • (String)

    encoded bytes



23
24
25
26
27
28
29
30
31
32
33
# File 'lib/plushie/protocol/encode.rb', line 23

def encode(map, format = :msgpack)
  case format
  when :json
    JSON.generate(stringify_keys(map)) + "\n"
  when :msgpack
    require "msgpack"
    MessagePack.pack(stringify_keys(map))
  else
    raise ArgumentError, "unknown format: #{format.inspect}"
  end
end

.encode_advance_frame(timestamp, format = :msgpack) ⇒ String

Advance the animation clock by one frame.

Parameters:

  • timestamp (Integer)

    frame timestamp in milliseconds

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


385
386
387
# File 'lib/plushie/protocol/encode.rb', line 385

def encode_advance_frame(timestamp, format = :msgpack)
  encode({type: "advance_frame", session: "", timestamp: timestamp}, format)
end

.encode_binary(data, format) ⇒ String

Encode binary data for the wire format. JSON: base64-encoded string. MessagePack: raw binary pass-through.

Parameters:

  • data (String)

    binary data

  • format (:msgpack, :json)

Returns:

  • (String)


439
440
441
442
443
444
# File 'lib/plushie/protocol/encode.rb', line 439

def encode_binary(data, format)
  case format
  when :json then Base64.strict_encode64(data)
  when :msgpack then data
  end
end

.encode_binary_field(payload, key, format) ⇒ Hash

Encode a binary field in a payload hash if present. Returns a new hash with the field encoded for the wire format, or the original hash if the field is absent or nil.

Parameters:

  • payload (Hash)

    the payload hash

  • key (Symbol)

    the field key to encode

  • format (:msgpack, :json)

Returns:

  • (Hash)


454
455
456
457
# File 'lib/plushie/protocol/encode.rb', line 454

def encode_binary_field(payload, key, format)
  return payload unless payload.is_a?(Hash) && payload.key?(key) && payload[key].is_a?(String)
  payload.merge(key => encode_binary(payload[key], format))
end

.encode_command(id, family, value = nil, format = :msgpack) ⇒ String

Send a command to a widget by ID.

Uses the unified wire format matching events: "command", id: "widget_id", family: "op_name", value: ...

Parameters:

  • id (String)

    target widget ID (scoped path)

  • family (String)

    operation name

  • value (Object, nil) (defaults to: nil)

    operation-specific data

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


274
275
276
277
278
279
# File 'lib/plushie/protocol/encode.rb', line 274

def encode_command(id, family, value = nil, format = :msgpack)
  encode({
    type: "command", session: "",
    id: id, family: family.to_s, value: value
  }, format)
end

.encode_commands(commands, format = :msgpack) ⇒ String

Send multiple widget-targeted commands in a single message.

Parameters:

  • commands (Array<Hash>)

    each with :id, :family, :value

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


286
287
288
289
290
291
292
293
# File 'lib/plushie/protocol/encode.rb', line 286

def encode_commands(commands, format = :msgpack)
  encode({
    type: "commands", session: "",
    commands: commands.map { |c|
      {id: c[:id], family: c[:family].to_s, value: c[:value]}
    }
  }, format)
end

.encode_effect(id, kind, payload, format = :msgpack) ⇒ String

Request a platform effect (file dialog, clipboard, notification).

Parameters:

  • id (String)

    unique request ID for correlation

  • kind (String)

    effect kind (e.g. "file_open", "clipboard_read")

  • payload (Hash)

    effect-specific parameters

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


214
215
216
217
218
219
# File 'lib/plushie/protocol/encode.rb', line 214

def encode_effect(id, kind, payload, format = :msgpack)
  encode({
    type: "effect", session: "",
    id: id, kind: kind.to_s, payload: payload
  }, format)
end

.encode_image_op(op, payload, format = :msgpack) ⇒ String

Manage in-memory image handles (create, update, delete, list, clear).

Uses the unified +_op+ envelope: op-specific data lives under +payload+. Binary fields (data, pixels) are base64-encoded for JSON and passed as raw binary for MessagePack.

Field set per op:

  • +create_image+/+update_image+: +handle+, plus +data+ or (+pixels+, +width+, +height+).
  • +delete_image+: +handle+.
  • +list+: +tag+ (response routes through op_query_response).
  • +clear+: empty payload.

Parameters:

  • op (String)
  • payload (Hash)
  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/plushie/protocol/encode.rb', line 242

def encode_image_op(op, payload, format = :msgpack)
  op_payload = {}
  op_payload[:handle] = payload[:handle] if payload[:handle]
  op_payload[:tag] = payload[:tag].to_s if payload[:tag]

  if payload[:data]
    op_payload[:data] = encode_binary(payload[:data], format)
  end

  if payload[:pixels]
    op_payload[:pixels] = encode_binary(payload[:pixels], format)
    op_payload[:width] = payload[:width]
    op_payload[:height] = payload[:height]
  end

  encode({type: "image_op", session: "", op: op.to_s, payload: op_payload}, format)
end

.encode_interact(id, action, selector = nil, payload = {}, format = :msgpack) ⇒ String

Simulate a user interaction (click, type, toggle, etc.).

Parameters:

  • id (String)

    request ID for response correlation

  • action (String)

    action name (click, type_text, toggle, etc.)

  • selector (Hash, nil) (defaults to: nil)

    target widget selector

  • payload (Hash) (defaults to: {})

    action-specific parameters

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


325
326
327
328
329
330
331
332
# File 'lib/plushie/protocol/encode.rb', line 325

def encode_interact(id, action, selector = nil, payload = {}, format = :msgpack)
  msg = {
    type: "interact", session: "",
    id: id, action: action.to_s, payload: payload
  }
  msg[:selector] = selector if selector
  encode(msg, format)
end

.encode_load_font(family, data, format = :msgpack) ⇒ String

Encode a typed +load_font+ message.

Wire shape: +type = "load_font"+ with +payload = data+. JSON framing emits +data+ as a base64 string; MessagePack framing emits +data+ as a native binary value.

Parameters:

  • family (String)

    font family name the renderer registers the data under

  • data (String)

    raw TrueType or OpenType font bytes

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


147
148
149
150
# File 'lib/plushie/protocol/encode.rb', line 147

def encode_load_font(family, data, format = :msgpack)
  payload = encode_binary_field({family: family, data: data}, :data, format)
  encode({type: "load_font", session: "", payload: payload}, format)
end

.encode_patch(ops, format = :msgpack) ⇒ String

Incrementally patch the existing tree.

Parameters:

  • ops (Array<Hash>)

    patch operations

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


89
90
91
# File 'lib/plushie/protocol/encode.rb', line 89

def encode_patch(ops, format = :msgpack)
  encode({type: "patch", session: "", ops: ops}, format)
end

.encode_query(id, target, selector = {}, format = :msgpack) ⇒ String

Query the renderer's tree (find widget, get full tree).

Parameters:

  • id (String)

    request ID for response correlation

  • target (String)

    "find" or "tree"

  • selector (Hash) (defaults to: {})

    selector (e.g. {by: "id", value: "btn1"})

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


306
307
308
309
310
311
# File 'lib/plushie/protocol/encode.rb', line 306

def encode_query(id, target, selector = {}, format = :msgpack)
  encode({
    type: "query", session: "",
    id: id, target: target, selector: selector
  }, format)
end

.encode_register_effect_stub(kind, response, format = :msgpack) ⇒ String

Register an effect stub with the renderer. The renderer will return the given response immediately for any effect of the given kind, without executing the real effect.

Parameters:

  • kind (String)

    effect kind (e.g. "clipboard_read")

  • response (Object)

    the canned response to return

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


401
402
403
# File 'lib/plushie/protocol/encode.rb', line 401

def encode_register_effect_stub(kind, response, format = :msgpack)
  encode({type: "register_effect_stub", session: "", kind: kind.to_s, response: response}, format)
end

.encode_reset(id, format = :msgpack) ⇒ String

Reset all session state.

Parameters:

  • id (String)

    request ID

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


372
373
374
# File 'lib/plushie/protocol/encode.rb', line 372

def encode_reset(id, format = :msgpack)
  encode({type: "reset", session: "", id: id}, format)
end

.encode_screenshot(id, name, width = 1024, height = 768, format = :msgpack) ⇒ String

Capture rendered pixels.

Parameters:

  • id (String)

    request ID

  • name (String)

    label for this capture

  • width (Integer) (defaults to: 1024)

    viewport width (default 1024)

  • height (Integer) (defaults to: 768)

    viewport height (default 768)

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


356
357
358
359
360
361
# File 'lib/plushie/protocol/encode.rb', line 356

def encode_screenshot(id, name, width = 1024, height = 768, format = :msgpack)
  encode({
    type: "screenshot", session: "",
    id: id, name: name, width: width, height: height
  }, format)
end

.encode_settings(settings, format = :msgpack) ⇒ String

Encode application-level settings. Sent as the first message.

All fields inside settings are optional. See protocol.md for the full list: protocol_version, default_text_size, default_font, antialiasing, vsync, fonts, scale_factor, validate_props, extension_config, default_event_rate, required_widgets.

Parameters:

  • settings (Hash)

    settings key-value pairs

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


49
50
51
52
53
# File 'lib/plushie/protocol/encode.rb', line 49

def encode_settings(settings, format = :msgpack)
  merged = {protocol_version: Protocol::PROTOCOL_VERSION}.merge(settings)
  merged = normalize_default_font(merged)
  encode({type: "settings", session: "", settings: merged}, format)
end

.encode_snapshot(tree, format = :msgpack) ⇒ String

Replace the entire UI tree.

Parameters:

  • tree (Hash)

    the tree node (id, type, props, children)

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


80
81
82
# File 'lib/plushie/protocol/encode.rb', line 80

def encode_snapshot(tree, format = :msgpack)
  encode({type: "snapshot", session: "", tree: tree}, format)
end

.encode_subscribe(kind, tag, format = :msgpack, max_rate: nil, window_id: nil) ⇒ String

Subscribe to an event category.

Parameters:

  • kind (String, Symbol)

    event category (e.g. "on_key_press")

  • tag (String, Symbol)

    routing tag

  • format (:msgpack, :json) (defaults to: :msgpack)
  • max_rate (Integer, nil) (defaults to: nil)

    max events per second (nil = unlimited)

Returns:

  • (String)


104
105
106
107
108
109
# File 'lib/plushie/protocol/encode.rb', line 104

def encode_subscribe(kind, tag, format = :msgpack, max_rate: nil, window_id: nil)
  msg = {type: "subscribe", session: "", kind: kind.to_s, tag: tag.to_s}
  msg[:max_rate] = max_rate if max_rate
  msg[:window_id] = window_id if window_id
  encode(msg, format)
end

.encode_system_op(op, payload, format = :msgpack) ⇒ String

Send a system-level operation not tied to a specific window.

Uses the unified +_op+ envelope: op-specific data lives under +payload+.

Parameters:

  • op (String, Symbol)
  • payload (Hash)
  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


181
182
183
184
185
186
# File 'lib/plushie/protocol/encode.rb', line 181

def encode_system_op(op, payload, format = :msgpack)
  encode({
    type: "system_op", session: "",
    op: op.to_s, payload: payload
  }, format)
end

.encode_system_query(op, payload, format = :msgpack) ⇒ String

Send a system-level query.

Uses the unified +_op+ envelope: query-specific data lives under +payload+.

Parameters:

  • op (String, Symbol)
  • payload (Hash)
  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


196
197
198
199
200
201
# File 'lib/plushie/protocol/encode.rb', line 196

def encode_system_query(op, payload, format = :msgpack)
  encode({
    type: "system_query", session: "",
    op: op.to_s, payload: payload
  }, format)
end

.encode_tree_hash(id, name, format = :msgpack) ⇒ String

Compute a SHA-256 hash of the renderer's current tree.

Parameters:

  • id (String)

    request ID

  • name (String)

    label for this hash capture

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


344
345
346
# File 'lib/plushie/protocol/encode.rb', line 344

def encode_tree_hash(id, name, format = :msgpack)
  encode({type: "tree_hash", session: "", id: id, name: name}, format)
end

.encode_unregister_effect_stub(kind, format = :msgpack) ⇒ String

Remove a previously registered effect stub.

Parameters:

  • kind (String)

    effect kind

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


410
411
412
# File 'lib/plushie/protocol/encode.rb', line 410

def encode_unregister_effect_stub(kind, format = :msgpack)
  encode({type: "unregister_effect_stub", session: "", kind: kind.to_s}, format)
end

.encode_unsubscribe(kind, tag: nil, format: :msgpack) ⇒ String

Unsubscribe from an event category.

Parameters:

  • kind (String, Symbol)

    event category

  • tag (String, Symbol, nil) (defaults to: nil)

    specific subscription tag for targeted removal

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


117
118
119
120
121
# File 'lib/plushie/protocol/encode.rb', line 117

def encode_unsubscribe(kind, tag: nil, format: :msgpack)
  msg = {type: "unsubscribe", session: "", kind: kind.to_s}
  msg[:tag] = tag.to_s if tag
  encode(msg, format)
end

.encode_widget_op(op, payload, format = :msgpack) ⇒ String

Perform an operation on a widget (focus, scroll, etc.).

Parameters:

  • op (String, Symbol)

    operation name

  • payload (Hash)

    operation-specific parameters

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


133
134
135
# File 'lib/plushie/protocol/encode.rb', line 133

def encode_widget_op(op, payload, format = :msgpack)
  encode({type: "widget_op", session: "", op: op.to_s, payload: payload}, format)
end

.encode_window_op(op, window_id, payload, format = :msgpack) ⇒ String

Manage a window (open, close, resize, etc.).

Uses the unified +_op+ envelope: op-specific data lives under +payload+; the +window_id+ addressing field stays flat beside +op+.

Parameters:

  • op (String, Symbol)

    operation name

  • window_id (String)

    target window ID

  • payload (Hash)

    operation-specific parameters

  • format (:msgpack, :json) (defaults to: :msgpack)

Returns:

  • (String)


166
167
168
169
170
171
# File 'lib/plushie/protocol/encode.rb', line 166

def encode_window_op(op, window_id, payload, format = :msgpack)
  encode({
    type: "window_op", session: "",
    op: op.to_s, window_id: window_id, payload: payload
  }, format)
end

.normalize_default_font(settings) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The renderer reads +default_font+ strictly as an object with at least a +family+ key; a bare string is silently dropped and the renderer falls back to the platform default. The Font encoder returns a string for the +:default+ and +:monospace+ shorthands but a hash for everything else, so wrap any string result back into the canonical +...+ shape.



63
64
65
66
67
68
69
# File 'lib/plushie/protocol/encode.rb', line 63

def normalize_default_font(settings)
  return settings unless settings.key?(:default_font)

  encoded = Type::Font.encode(settings[:default_font])
  encoded = {family: encoded} if encoded.is_a?(String)
  settings.merge(default_font: encoded)
end

.stringify_keys(obj) ⇒ Object

Recursively convert all symbol keys to strings.

Parameters:

  • obj (Object)

    value to stringify

Returns:

  • (Object)

    value with string keys



422
423
424
425
426
427
428
429
430
431
# File 'lib/plushie/protocol/encode.rb', line 422

def stringify_keys(obj)
  case obj
  when Hash
    obj.each_with_object({}) { |(k, v), h| h[k.to_s] = stringify_keys(v) }
  when Array
    obj.map { |v| stringify_keys(v) }
  else
    obj
  end
end