class I3Ipc::Protocol

Communication interface with i3-ipc. Can connect to i3-ipc socket, disconnect, send and receive messages.

For i3-ipc interface details refer to i3wm.org/docs/ipc.html.

@example

protocol = Protocol.new
protocol.send(7)
puts protocol.receive
protocol.disconnect

Constants

MAGIC_STRING

Magic string for i3-ipc protocol to ensure the integrity of messages.

Public Class Methods

new(socketpath = nil) click to toggle source
# File lib/i3ipc/protocol.rb, line 48
def initialize(socketpath = nil)
  @socketpath = socketpath ? socketpath : get_socketpath
end

Public Instance Methods

connect() click to toggle source

Connects to i3-ipc server socket using {::UNIXSocket}. Does nothing if already connected.

# File lib/i3ipc/protocol.rb, line 54
def connect
  @socket = UNIXSocket.new(@socketpath) unless @socket
end
disconnect() click to toggle source

Disconnects from i3-ipc server socket. Does nothing if not connected.

# File lib/i3ipc/protocol.rb, line 60
def disconnect
  @socket && @socket.close
  @socket = nil
end
receive(type = nil) click to toggle source

Receives message from i3-ipc server socket.

@param [Integer] type expected type of the message.

@return [String] unpacked response from i3 server.

@raise [NotConnected] if protocol is not connected. @raise [WrongMagicString] if got message with wrong magic string. @raise [WrongType] if got message with not expected type.

# File lib/i3ipc/protocol.rb, line 84
def receive(type = nil)
  check_connected
  # length of "i3-ipc" + 4 bytes length + 4 bytes type
  data = @socket.read 14
  magic, len, recv_type = unpack_header(data)

  raise WrongMagicString.new(magic) unless MAGIC_STRING.eql? magic
  type && (raise WrongType.new(type, recv_type) unless type == recv_type)

  @socket.read(len)
end
receive_event(type = nil) click to toggle source

Receives event from i3-ipc server socket.

@param [Integer] type expected type of the message.

@return [String] unpacked response from i3 server.

@raise [NotConnected] if protocol is not connected. @raise [WrongMagicString] if got message with wrong magic string. @raise [WrongType] if got message with not expected type.

# File lib/i3ipc/protocol.rb, line 105
def receive_event(type = nil)
  check_connected
  # length of "i3-ipc" + 4 bytes length + 4 bytes type
  data = @socket.read 14
  magic, len, recv_type = unpack_header(data)

  # Strip highest bit
  recv_type = recv_type & 2147483647

  raise WrongMagicString.new(magic) unless MAGIC_STRING.eql? magic
  type && (raise WrongType.new(type, recv_type) unless type == recv_type)

  @socket.read(len)
end
send(type, payload = nil) click to toggle source

Sends packed message to i3-ipc server socket. @param [Integer] type type of the message. @param [String] payload message payload.

@raise [NotConnected] if protocol is not connected.

# File lib/i3ipc/protocol.rb, line 70
def send(type, payload = nil)
  check_connected
  @socket.write(pack(type, payload))
end

Private Instance Methods

check_connected() click to toggle source
# File lib/i3ipc/protocol.rb, line 165
def check_connected
  raise NotConnected unless @socket
end
get_socketpath() click to toggle source
# File lib/i3ipc/protocol.rb, line 152
def get_socketpath
  cmd = if system('i3 --version')
    'i3'
  elsif system('sway --version')
    'sway'
  else
    raise 'Unable to find i3 compatible window manager'
  end
  path = `#{cmd} --get-socketpath`.chomp!
  raise 'Unable to get i3 compatible socketpath' unless path
  path
end
pack(type, payload=nil) click to toggle source

Packs the message. A typical message looks like: @example

<header><payload>

where a header is: @example

<magic string><message length><message type>

@param [Integer] type type of the message. @param [String] payload payload of the message.

# File lib/i3ipc/protocol.rb, line 132
def pack(type, payload=nil)
  size = payload ? payload.to_s.bytes.count : 0
  msg = MAGIC_STRING + [size, type].pack("LL")
  msg << payload.to_s if payload
  msg
end
unpack_header(data) click to toggle source

Unpacks the header. A typical header looks like: @example

<magic_string><message length><message type>

@param [String] data: data to be unpacked.

# File lib/i3ipc/protocol.rb, line 145
def unpack_header(data)
  struct_header_len = MAGIC_STRING.size
  magic_message = data[0, struct_header_len]
  len, type = data[struct_header_len..-1].unpack("LL")
  [magic_message, len, type]
end