Created
April 30, 2012 18:43
-
-
Save Quintus/2561002 to your computer and use it in GitHub Desktop.
i3 workspaces
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# -*- coding: utf-8 -*- | |
require "pp" | |
require "json" | |
require "socket" | |
module I3 | |
class Error | |
end | |
class MsgError < Error | |
end | |
# Class representing i3 workspaces. | |
class Workspace | |
attr_reader :num | |
attr_reader :name | |
attr_reader :output | |
# Create a workspace from an i3 response. Internally used by Interface#workspaces. | |
def self.from_i3_hash(i3, hsh) # :nodoc: | |
ws = allocate | |
ws.instance_variable_set(:@i3, i3) | |
%w[num name visible focused urgent output].each do |str| | |
ws.instance_variable_set(:"@#{str}", hsh[str]) | |
end | |
%w[x y width height].each do |str| | |
ws.instance_variable_set(:"@#{str}", hsh["rect"][str]) | |
end | |
ws | |
end | |
def initialize(i3) | |
@i3 = i3 | |
raise(NotImplementedError, "Not implemented yet") | |
end | |
def inspect | |
"#<#{self.class} #@num (#@name)>" | |
end | |
def visible? | |
@visible | |
end | |
def focused? | |
@focused | |
end | |
def urgent? | |
@urgent | |
end | |
end | |
class Interface | |
# The magic string marking each message as a i3 | |
# message. | |
MAGIC_STRING = "i3-ipc".force_encoding("BINARY").freeze | |
# Map the human-readable command names to the | |
# identifiers i3 understands. | |
TYPE2INT = { | |
:command => 0, | |
:workspaces => 1, | |
:subscribe => 2, | |
:outputs => 3, | |
:tree => 4, | |
:marks => 5, | |
:bar_config => 6 | |
}.freeze | |
# The inverse of MSG_TYPE2INT. | |
INT2TYPE = TYPE2INT.invert.freeze | |
# Establish a connection to i3. | |
# === Parameter | |
# [socket] | |
# (<tt>UNIXSocket.new(`i3 --get-socketpath`)</tt>) | |
# The socket i3 listens to. If not given, this | |
# method asks i3 for it’s socket path and creates | |
# a UNIX domain socket from it. | |
# === Return value | |
# Returns a new instance of this class. | |
def initialize(socket = nil) | |
@socket = socket ? socket : UNIXSocket.new(`i3 --get-socketpath`.chomp) | |
end | |
def command(cmd) | |
raw_message(:command, cmd)["success"] | |
end | |
# Get all workspaces i3 currently manages. | |
def workspaces | |
ary = raw_message(:workspaces) | |
ary.map{|hsh| Workspace.from_i3_hash(self, hsh)} | |
end | |
# Send a raw message to i3 and return i3’s reply. | |
# | |
# === Parameters | |
# [type] The type of the message, one of the keys of the | |
# TYPE2INT hash. | |
# [str] ("") The contents of the message, which may be | |
# empty if +type+ contains all necessary | |
# information. | |
# === Raises | |
# [ArgumentError] +type+ was invalid. | |
# [I3MsgError] i3’s answer was somehow invalid. | |
# === Return value | |
# The JSON stuff returned by i3, properly converted | |
# to a Ruby hash/array structure with all strings | |
# encoded in UTF-8. | |
def raw_message(type, str = "") | |
msg = format_i3_message(TYPE2INT[type], str) | |
@socket.write(msg) | |
# First read the magic string | |
mstr = @socket.read(MAGIC_STRING.bytes.count) | |
if mstr != MAGIC_STRING | |
raise(MsgError, "Invalid reply from i3! Expected '#{MAGIC_STRING}', got '#{mstr}'!") | |
end | |
# Now read two 32-bit integers, the length and the type | |
length, ident = @socket.read(8).unpack("LL") | |
reply_type = INT2TYPE[ident] || raise(I3MsgError, "Invalid reply type #{ident} from i3!") | |
if reply_type != type | |
raise(MsgError, "i3 answered with #{reply_type} to a #{type} message!") | |
end | |
# Finally read the message contents (i3 operates | |
# on UTF-8 strings, so we can force the encoding here). | |
content = @socket.read(length).force_encoding("UTF-8") | |
# i3 always answers with JSON. Return that. | |
JSON[content] | |
end | |
private | |
# Takes what you want to send to i3 and converts it | |
# to the binary blob i3 expects. +str+ is the message | |
# payload, i.e. its contents (which may be empty), | |
# +typeident+ is one of the type identifiers known | |
# to i3 (you can get a list of possible types numbers | |
# by printing out the INT2TYPE hash). | |
def format_i3_message(typeident, str = "") | |
cmd = MAGIC_STRING.dup | |
cmd << [str.bytes.count, typeident].pack("LL") | |
cmd << str.encode("UTF-8").force_encoding("BINARY") | |
cmd | |
end | |
end | |
end | |
if $0 == __FILE__ | |
i3 = I3::Interface.new | |
i3.workspaces.each_with_index do |ws, i| | |
print(" | ") unless i.zero? | |
if ws.urgent? | |
print("¡#{ws.name}!") | |
elsif ws.focused? | |
print("[#{ws.name}]") | |
else | |
print(ws.name) | |
end | |
end | |
puts # Final newline | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment