Class: I3::IPC

Inherits:
Object
  • Object
show all
Defined in:
lib/i3-ipc.rb

Defined Under Namespace

Classes: WrongMagicCode, WrongType

Constant Summary collapse

MAGIC_STRING =
"i3-ipc"
COMMANDS =
[
  # Send a command to i3.
  #
  # The payload is a command for i3
  # (like the commands you can bind to keys in the configuration file)
  # and will be executed directly after receiving it.
  #
  # Returns { "success" => true } for now.
  # i3 does send this reply without checks.
  [0, :command,        :required],

  # Gets the current workspaces.
  # The reply will be the list of workspaces
  # (see the reply section of i3 docu)
  [1, :get_workspaces, :none],

  # Gets the current outputs.
  # The reply will be a JSON-encoded list of outputs
  # (see the reply section of i3 docu).
  [3, :get_outputs,    :none],

  # Gets the layout tree.
  # i3 uses a tree as data structure which includes every container.
  # The reply will be the JSON-encoded tree
  # (see the reply section of i3 docu)
  [4, :get_tree,       :none],

  # Gets a list of marks (identifiers for containers to easily jump
  # to them later).
  # The reply will be a JSON-encoded list of window marks.
  # (see the reply section of i3 docu)
  [5, :get_marks,      :none],

  # Gets the configuration (as JSON map) of the workspace bar with
  # the given ID.
  # If no ID is provided, an array with all configured bar IDs is returned instead.
  [6, :get_bar_config, :optional],
]
MESSAGE_TYPE_SUBSCRIBE =

Needed because subscribe is handled in submodule.

2
EVENT_MASK =
(1 << 31)
EVENT_WORKSPACE =
(EVENT_MASK | 0)

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(socket = nil, force_connect = false) ⇒ IPC

connects to the given i3 ipc interface

Parameters:

  • socket_path

    String the path to i3’s socket

  • force_connect (defaults to: false)

    Boolean connects to the socket if true



91
92
93
94
# File 'lib/i3-ipc.rb', line 91

def initialize(socket=nil, force_connect=false)
  @@socket_path = socket if socket
  connect if connect
end

Class Method Details

.format(type, payload = nil) ⇒ Object

Format the message. A typical message looks like

"i3-ipc" <message length> <message type> <payload>


126
127
128
129
130
131
# File 'lib/i3-ipc.rb', line 126

def self.format(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

.message_type_subscribeObject



56
57
58
# File 'lib/i3-ipc.rb', line 56

def self.message_type_subscribe
  MESSAGE_TYPE_SUBSCRIBE
end

.parse_response(response) ⇒ Object

Parse a full ipc response. Similar to handle_response, but parses full reply as received by EventMachine.

returns an Array containing the reply type and the parsed data



143
144
145
146
147
148
149
150
151
152
# File 'lib/i3-ipc.rb', line 143

def self.parse_response(response)
  if response[0, (MAGIC_STRING.length)] != MAGIC_STRING
    raise WrongMagicCode
  end

  len, recv_type = response[6, 8].unpack("LL")

  answer = response[14, len]
  [recv_type, ::JSON.parse(answer)]
end

.socket_pathObject

Get socket path via i3 itself.



84
85
86
# File 'lib/i3-ipc.rb', line 84

def self.socket_path
  @@socket_path ||= `i3 --get-socketpath`.chomp
end

.subscribe(list, socket_path = nil, &blk) ⇒ Object

shortcut



97
98
99
# File 'lib/i3-ipc.rb', line 97

def self.subscribe(list, socket_path=nil, &blk)
  Subscription.subscribe(list, socket_path || self.socket_path, &blk)
end

Instance Method Details

#closeObject

Closes the socket connection.



175
176
177
# File 'lib/i3-ipc.rb', line 175

def close
  @socket.close
end

#closed?Boolean

Alias for @socket.closed? for easy access

Returns:

  • (Boolean)


180
181
182
# File 'lib/i3-ipc.rb', line 180

def closed?
  @socket.closed?
end

#connectObject

Connects to the given socket.



170
171
172
# File 'lib/i3-ipc.rb', line 170

def connect
  @socket = UNIXSocket.new(self.class.socket_path)
end

#format(type, payload = nil) ⇒ Object



133
134
135
# File 'lib/i3-ipc.rb', line 133

def format(type, payload=nil)
  self.class.format(type, payload)
end

#handle_response(type) ⇒ Object

Reads the reply from the socket and parses the returned json into a ruby object.

Throws WrongMagicCode when magic word is wrong. Throws WrongType if returned type does not match expected.

This is a bit duplicated code

but I don't know a way to read the full send reply
without knowing its length

Raises:



110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/i3-ipc.rb', line 110

def handle_response(type)
  # reads 14 bytes
  # length of "i3-ipc" + 4 bytes length + 4 bytes type
  buffer = read 14
  raise WrongMagicCode unless buffer[0, (MAGIC_STRING.length)] == MAGIC_STRING

  len, recv_type = buffer[6..-1].unpack("LL")
  raise WrongType unless recv_type == type

  answer = read len
  ::JSON.parse(answer)
end

#parse_response(response) ⇒ Object



154
155
156
# File 'lib/i3-ipc.rb', line 154

def parse_response(response)
  self.class.parse_response(response)
end

#read(len) ⇒ Object



165
166
167
# File 'lib/i3-ipc.rb', line 165

def read(len)
  @socket.read(len)
end

#write(msg) ⇒ Object

Writes message to the socket. If socket is not connected, it connects first.



160
161
162
163
# File 'lib/i3-ipc.rb', line 160

def write(msg)
  connect if @socket.nil? || closed?
  @last_write_length = @socket.write msg
end