Module: Plushie::Protocol::Encode

Defined in:
lib/plushie/protocol/encode.rb,
sig/plushie/protocol.rbs

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

Animation

Parameters:

  • timestamp (Integer)
  • format (Symbol) (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

Widget-targeted commands (unified wire format)

Parameters:

  • id (String)
  • family (String)
  • value (Object) (defaults to: nil)
  • format (Symbol) (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

Effects

Parameters:

  • id (String)
  • kind (String)
  • payload (Hash[Symbol, untyped])
  • format (Symbol) (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

Image operations

Parameters:

  • op (String)
  • payload (Hash[Symbol, untyped])
  • format (Symbol) (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

Interactions

Parameters:

  • id (String)
  • action (String)
  • selector (Hash[Symbol, untyped], nil) (defaults to: nil)
  • payload (Hash[Symbol, untyped]) (defaults to: {})
  • format (Symbol) (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

Font load (typed top-level message; carries raw TTF/OTF bytes)

Parameters:

  • family (String)
  • data (String)
  • format (Symbol) (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

Queries

Parameters:

  • id (String)
  • target (String)
  • selector (Hash[Symbol, untyped]) (defaults to: {})
  • format (Symbol) (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

Session management

Parameters:

  • id (String)
  • format (Symbol) (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

Settings

Parameters:

  • settings (Hash[Symbol, untyped])
  • format (Symbol) (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

Tree updates

Parameters:

  • tree (Hash[String, untyped])
  • format (Symbol) (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

Subscriptions

Parameters:

  • kind (String, Symbol)
  • tag (String, Symbol)
  • format (Symbol) (defaults to: :msgpack)
  • max_rate: (Integer, nil) (defaults to: nil)
  • window_id: (String, nil) (defaults to: nil)

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)
  • settings (Hash[Symbol, untyped])

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)
  • settings (Hash[Symbol, untyped])

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

Structural testing

Parameters:

  • id (String)
  • name (String)
  • format (Symbol) (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)
  • tag: (String, Symbol, nil) (defaults to: nil)
  • format: (Symbol) (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

Widget operations

Parameters:

  • op (String, Symbol)
  • payload (Hash[Symbol, untyped])
  • format (Symbol) (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

Window operations

Parameters:

  • op (String, Symbol)
  • window_id (String)
  • settings (Hash[Symbol | String, untyped])
  • format (Symbol) (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 {family: ...} 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

Helpers

Parameters:

  • obj (Object)

Returns:

  • (Object)


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