lib/i3ipc/protocol.rb in i3ipc-0.1.0 vs lib/i3ipc/protocol.rb in i3ipc-0.2.0

- old
+ new

@@ -1,19 +1,19 @@ require 'socket' module I3Ipc # Communication interface with i3-ipc. - # # Can connect to i3-ipc socket, disconnect, send and receive messages. # - # Usage example: + # For i3-ipc interface details refer to https://i3wm.org/docs/ipc.html. + # + # @example # protocol = Protocol.new # protocol.send(7) # puts protocol.receive # protocol.disconnect # - # For i3-ipc interface details refer to https://i3wm.org/docs/ipc.html. class Protocol # Magic string for i3-ipc protocol to ensure the integrity of messages. MAGIC_STRING = 'i3-ipc' # Throws when received data with not expected magic string. @@ -47,11 +47,11 @@ def initialize(socketpath = nil) @socketpath = socketpath ? socketpath : get_socketpath end - # Connects to i3-ipc server socket using Socket::UNIXSocket. + # Connects to i3-ipc server socket using {::UNIXSocket}. # Does nothing if already connected. def connect @socket = UNIXSocket.new(@socketpath) unless @socket end @@ -61,29 +61,28 @@ @socket && @socket.close @socket = nil end # Sends packed message to i3-ipc server socket. + # @param [Integer] type type of the message. + # @param [String] payload message payload. # - # Throws: - # * NotConnected if protocol is not connected. - # - # +type+: type of the message. - # +payload+: payload of the message + # @raise [NotConnected] if protocol is not connected. def send(type, payload = nil) check_connected @socket.write(pack(type, payload)) end # Receives message from i3-ipc server socket. # - # Throws: - # * NotConnected if protocol is not connected. - # * WrongMagicString if got message with not expected magic string. - # * WrongType if got message with not expected magic type. + # @param [Integer] type expected type of the message. # - # +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. 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) @@ -92,31 +91,58 @@ type && (raise WrongType.new(type, recv_type) unless type == recv_type) @socket.read(len) end + # 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. + 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 + private # Packs the message. # A typical message looks like: + # @example # <header><payload> # where a header is: + # @example # <magic string><message length><message type> # - # +type+: type of the message - # +payload+: patload of the message + # @param [Integer] type type of the message. + # @param [String] payload payload of the message. 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 # Unpacks the header. # A typical header looks like: + # @example # <magic_string><message length><message type> # - # +data+: data to be unpacked. + # @param [String] data: data to be unpacked. 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]