i3-ipc 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,7 +10,7 @@ RubyGem:
10
10
 
11
11
  gem install i3-ipc
12
12
 
13
- Old school:
13
+ Old school (for the cli script only):
14
14
 
15
15
  curl -s http://github.com/badboy/i3-ipc/raw/master/i3-ipc > i3-ipc &&
16
16
  chmod 755 i3-ipc &&
@@ -24,15 +24,75 @@ Use
24
24
  i3-ipc -t 1 -j
25
25
  i3-ipc "exec xterm"
26
26
 
27
+ Read the [man-page]() for more information.
28
+
29
+ Subscribing
30
+ -----------
31
+
32
+ As of commit [3db4890]() i3 added events.
33
+ For now there's only one event: `workspace`.
34
+
35
+ According to the documentation:
36
+ > This event is sent when the user switches to a different workspace, when a new workspace is initialized or when a workspace is removed (because the last client vanished).
37
+
38
+ i3-ipc uses [EventMachine][em] to receive and handle these events.
39
+
40
+ With `i3-ipc`'s interface and EventMachine as its backend it's rather easy to subscribe to this event notifying:
41
+
42
+ I3::IPC.subscribe [:workspace] do |em, type, data|
43
+ # ...
44
+ end
45
+
46
+ There are 3 arguments passed to the block:
47
+
48
+ * `em` is the instance of the EM::Connection class.
49
+ To send data to the socket, you need to use `em.send_data`.
50
+ * `type` is the received message type.
51
+ This could be one of
52
+ * MESSAGE_REPLY_COMMAND
53
+ * MESSAGE_REPLY_COMMAND
54
+ * MESSAGE_REPLY_COMMAND
55
+ * EVENT_WORKSPACE #_
56
+ * `data` is the received data, already parsed.
57
+
58
+ For example you can use the following code to get the actual focused screen:
59
+
60
+ I3::IPC.subscribe [:workspace] do |em, type, data|
61
+ case type
62
+ when I3::IPC::MESSAGE_REPLY_GET_WORKSPACES
63
+ data.each do |e|
64
+ if e["focused"]
65
+ puts "focused: %s" % e["name"]
66
+ else
67
+ puts "unfocused: %s" % e["name"]
68
+ end
69
+ end
70
+ when I3::IPC::EVENT_WORKSPACE
71
+ em.send_data I3::IPC.format(I3::IPC::MESSAGE_TYPE_GET_WORKSPACES)
72
+ end
73
+ end
74
+
75
+ A full example of how this can be used for the workspace bar can be found in the `examples` directory.
76
+
77
+ You can use `EM.stop` to stop the connection.
78
+
79
+
80
+ What needs to be done?
81
+ ----------------------
82
+
83
+ * cleanup the subscribtion frontend
84
+ * write tests
85
+ * …
86
+
27
87
  Contributing
28
88
  ------------
29
89
 
30
90
  Once you've made your great commits:
31
91
 
32
- 1. [Fork][0] the project.
92
+ 1. [Fork]() the project.
33
93
  2. Create a topic branch - `git checkout -b my_branch`
34
94
  3. Push to your branch - `git push origin my_branch`
35
- 4. Create an [Issue][1] with a link to your branch
95
+ 4. Create an [Issue]() with a link to your branch
36
96
  5. That's it!
37
97
 
38
98
  Copyright
@@ -41,5 +101,8 @@ Copyright
41
101
  Copyright (c) 2010 Jan-Erik Rediger. See LICENSE for details.
42
102
 
43
103
  [i3]: http://i3.zekjur.net/
44
- [0]: http://help.github.com/forking/
45
- [1]: http://github.com/badboy/i3-ipc/issues
104
+ [manpage]: http://badboy.github.com/i3-ipc/
105
+ [3db4890]: http://code.stapelberg.de/git/i3/commit/?h=next&id=3db4890683e87
106
+ [em]: http://github.com/eventmachine/eventmachine
107
+ [fork]: http://help.github.com/forking/
108
+ [issue]: http://github.com/badboy/i3-ipc/issues
data/Rakefile CHANGED
@@ -1,27 +1,43 @@
1
1
  begin
2
2
  require 'mg'
3
3
  MG.new("i3-ipc.gemspec")
4
+
5
+ desc "Build a gem."
6
+ task :gem => :package
7
+
8
+ desc "Push a new version to Gemcutter and publish docs."
9
+ task :publish => :gemcutter do
10
+ require File.dirname(__FILE__) + '/lib/i3-ipc/version'
11
+
12
+ system "git tag v#{I3::Version}"
13
+ sh "git push origin master --tags"
14
+ sh "git clean -fd"
15
+ exec "rake pages"
16
+ end
4
17
  rescue LoadError
5
- nil
18
+ warn "mg not available."
19
+ warn "Install it with: gem i mg"
6
20
  end
7
21
 
8
22
  desc "Build standalone script"
9
- task :build => [ :standalone, :build_man ]
23
+ task :build => [ "build:standalone", "build:man" ]
10
24
 
11
- desc "Build standalone script"
12
- task :standalone => :load_i3_ipc do
13
- require 'i3-ipc/standalone'
14
- I3::Standalone.save('i3-ipc')
25
+ desc "Show i3-ipc manual"
26
+ task :man => "build:man" do
27
+ exec "man man/i3-ipc.1"
15
28
  end
16
29
 
17
- desc "Build i3-ipc manual"
18
- task :build_man do
19
- sh "ronn -br5 --organization=badboy --manual='i3-ipc Manual' man/*.ronn"
20
- end
30
+ namespace :build do
31
+ desc "Build i3-ipc manual"
32
+ task :man do
33
+ sh "ronn -br5 --organization=badboy --manual='i3-ipc Manual' man/*.ronn"
34
+ end
21
35
 
22
- desc "Show i3-ipc manual"
23
- task :man => :build_man do
24
- exec "man man/i3-ipc.1"
36
+ desc "Build standalone script"
37
+ task :standalone => :load_i3_ipc do
38
+ require 'i3-ipc/standalone'
39
+ I3::Standalone.save('i3-ipc')
40
+ end
25
41
  end
26
42
 
27
43
  task :load_i3_ipc do
@@ -39,7 +55,7 @@ end
39
55
  Rake.application.remove_task(:install)
40
56
 
41
57
  desc "Install standalone script and man pages"
42
- task :install => :standalone do
58
+ task :install => "build:standalone" do
43
59
  prefix = ENV['PREFIX'] || ENV['prefix'] || '/usr/local'
44
60
 
45
61
  FileUtils.mkdir_p "#{prefix}/bin"
@@ -48,3 +64,28 @@ task :install => :standalone do
48
64
  FileUtils.mkdir_p "#{prefix}/share/man/man1"
49
65
  FileUtils.cp "man/i3-ipc.1", "#{prefix}/share/man/man1"
50
66
  end
67
+
68
+ desc "Publish to GitHub Pages"
69
+ task :pages => [ "build:man" ] do
70
+ Dir['man/*.html'].each do |f|
71
+ cp f, File.basename(f).sub('.html', '.newhtml')
72
+ end
73
+
74
+ `git commit -am 'generated manual'`
75
+ `git checkout gh-pages`
76
+
77
+ Dir['*.newhtml'].each do |f|
78
+ mv f, "index.html"
79
+ end
80
+
81
+ `git add .`
82
+ `git commit -m updated`
83
+ `git push origin gh-pages`
84
+ `git checkout master`
85
+ puts :done
86
+ end
87
+
88
+ desc "fire up IRB console"
89
+ task :console do
90
+ exec "irb -Ilib -ri3-ipc"
91
+ end
@@ -1,54 +1,77 @@
1
1
  require 'socket'
2
2
  require 'json'
3
3
  require 'i3-ipc/runner'
4
+ require 'i3-ipc/subscription'
4
5
  require 'i3-ipc/manpage'
5
6
  require 'i3-ipc/version'
6
7
 
7
8
  module I3
8
9
  class IPC
9
10
  MAGIC_STRING = "i3-ipc"
11
+ SOCKET_PATH = "/tmp/i3-ipc.sock"
10
12
 
11
- class WrongAnswer < RuntimeError; end # :nodoc:
13
+ MESSAGE_TYPE_COMMAND = 0
14
+ MESSAGE_TYPE_GET_WORKSPACES = 1
15
+ MESSAGE_TYPE_SUBSCRIBE = 2
16
+
17
+ MESSAGE_REPLY_COMMAND = 0
18
+ MESSAGE_REPLY_GET_WORKSPACES = 1
19
+ MESSAGE_REPLY_SUBSCRIBE = 2
20
+
21
+ EVENT_MASK = (1 << 31)
22
+ EVENT_WORKSPACE = (EVENT_MASK | 0)
23
+
24
+ class WrongMagicCode < RuntimeError; end # :nodoc:
12
25
  class WrongType < RuntimeError; end # :nodoc:
13
26
 
14
27
  # connects to the given i3 ipc interface
15
28
  # @param socket_path String the path to i3's socket
16
29
  # @param force_connect Boolean connects to the socket if true
17
- def initialize(socket_path="/tmp/i3-ipc.sock", force_connect=false)
30
+ def initialize(socket_path=SOCKET_PATH, force_connect=false)
18
31
  @socket_path = socket_path
19
32
  connect if connect
20
33
  end
21
34
 
22
- # send a message to i3
35
+ # shortcut
36
+ def self.subscribe(list, socket_path=SOCKET_PATH, &blk)
37
+ Subscription.subscribe(list, socket_path, &blk)
38
+ end
39
+
40
+ # send a command to i3
23
41
  #
24
- # the message is a command for i3
42
+ # the payload is a command for i3
25
43
  # (like the commands you can bind to keys in the configuration file)
26
44
  # and will be executed directly after receiving it.
27
45
  #
28
- # returns { "success" => true }
29
- def message(payload)
30
- write format(0, payload)
31
- handle_response 0
46
+ # returns { "success" => true } for now.
47
+ # i3 does send this reply without checks
48
+ def command(payload)
49
+ write format(MESSAGE_TYPE_COMMAND, payload)
50
+ handle_response MESSAGE_TYPE_COMMAND
32
51
  end
33
52
 
34
53
  # gets the current workspaces.
35
- # the reply will be the JSON-encoded list of workspaces
54
+ # the reply will be the list of workspaces
36
55
  # (see the reply section of i3 docu)
37
56
  def get_workspace
38
- write format(1)
39
- handle_response 1
57
+ write format(MESSAGE_TYPE_GET_WORKSPACES)
58
+ handle_response MESSAGE_TYPE_GET_WORKSPACES
40
59
  end
41
60
 
42
61
  # reads the reply from the socket
43
62
  # and parse the returned json into a ruby object
44
63
  #
45
- # throws WrongAnswer when magic word is wrong
64
+ # throws WrongMagicCode when magic word is wrong
46
65
  # throws WrongType if returned type does not match expected
66
+ #
67
+ # this is a bit duplicated code
68
+ # but I don't know a way to read the full send reply
69
+ # without knowing its length
47
70
  def handle_response(type)
48
71
  # reads 14 bytes
49
72
  # length of "i3-ipc" + 4 bytes length + 4 bytes type
50
73
  buffer = @socket.read 14
51
- raise WrongAnswer unless buffer[0, ("i3-ipc".length)]
74
+ raise WrongMagicCode unless buffer[0, (MAGIC_STRING.length)] == MAGIC_STRING
52
75
 
53
76
  len, recv_type = buffer[6..-1].unpack("LL")
54
77
  raise WrongType unless recv_type == type
@@ -60,15 +83,40 @@ module I3
60
83
  # format the message
61
84
  # a typical message looks like
62
85
  # "i3-ipc" <message length> <message type> <payload>
63
- def format(type, payload=nil)
86
+ def self.format(type, payload=nil)
64
87
  size = payload ? payload.to_s.bytes.count : 0
65
- msg = "i3-ipc%s" % [size, type].pack("LL")
88
+ msg = MAGIC_STRING + [size, type].pack("LL")
66
89
  msg << payload.to_s if payload
67
90
  msg
68
91
  end
69
92
 
93
+ def format(type, payload=nil)
94
+ self.class.format(type, payload)
95
+ end
96
+
97
+ # parse a full ipc response
98
+ # similar to handle_response,
99
+ # but parses full reply as received by EventMachine
100
+ #
101
+ # returns an Array containing the
102
+ # reply type and the parsed data
103
+ def self.parse_response(response)
104
+ if response[0, (MAGIC_STRING.length)] != MAGIC_STRING
105
+ raise WrongMagicCode
106
+ end
107
+
108
+ len, recv_type = response[6, 8].unpack("LL")
109
+
110
+ answer = response[14, len]
111
+ [recv_type, ::JSON.parse(answer)]
112
+ end
113
+
114
+ def parse_response(response)
115
+ self.class.parse_response(response)
116
+ end
117
+
70
118
  # writes message to the socket
71
- # if socket is not connected, it calls conenct
119
+ # if socket is not connected, it connects first
72
120
  def write(msg)
73
121
  connect if @socket.nil? || closed?
74
122
  @last_write_length = @socket.write msg
@@ -5,13 +5,13 @@ module I3
5
5
  PREAMBLE = <<-preamble
6
6
  #!/usr/bin/env ruby
7
7
  #
8
- # This file, gist, is generated code.
8
+ # This file, i3-ipc, is generated code.
9
9
  # Please DO NOT EDIT or send patches for it.
10
10
  #
11
11
  # Please take a look at the source from
12
12
  # http://github.com/badboy/i3-ipc
13
13
  # and submit patches against the individual files
14
- # that build gist.
14
+ # that build i3-ipc.
15
15
  #
16
16
 
17
17
  preamble
@@ -33,10 +33,16 @@ preamble
33
33
 
34
34
  standalone = ''
35
35
  standalone << PREAMBLE
36
+ file_dir = File.expand_path(File.dirname(__FILE__))
37
+ exclude_files = %w(subscription.rb)
38
+
39
+ exclude_file_list = exclude_files.map { |file|
40
+ File.join(file_dir, file)
41
+ } + [File.expand_path(__FILE__)]
36
42
 
37
43
  Dir["#{root}/../**/*.rb"].each do |file|
38
44
  # skip standalone.rb
39
- next if File.expand_path(file) == File.expand_path(__FILE__)
45
+ next if exclude_file_list.include?(File.expand_path(file))
40
46
 
41
47
  File.readlines(file).each do |line|
42
48
  next if line =~ /^\s*#/
@@ -0,0 +1,36 @@
1
+ require 'eventmachine'
2
+
3
+ module I3
4
+ module Subscription
5
+ extend self
6
+
7
+ class SubscriptionConnection < EM::Connection
8
+ def self.connect(subscription_list, socket_path=I3::IPC::SOCKET_PATH, &blk)
9
+ new_klass = Class.new(self)
10
+ new_klass.send(:define_method, :initialize) do
11
+ @subscription_list = subscription_list
12
+ @handler = blk
13
+ end
14
+ EM.connect socket_path, new_klass
15
+ end
16
+
17
+ # send subscription to i3
18
+ def post_init
19
+ send_data I3::IPC.format(I3::IPC::MESSAGE_TYPE_SUBSCRIBE,
20
+ @subscription_list.to_json)
21
+ end
22
+
23
+ # receive data, parse it and pass on to the user-defined handler
24
+ def receive_data(data)
25
+ @handler.call(self, *I3::IPC.parse_response(data)) if @handler
26
+ end
27
+ end
28
+
29
+ def subscribe(subscription_list, socket_path=I3::IPC::SOCKET_PATH, &blk)
30
+ EM.run do
31
+ SubscriptionConnection.connect(subscription_list,
32
+ socket_path, &blk)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,3 +1,3 @@
1
1
  module I3
2
- Version = '0.0.2'
2
+ Version = '0.1.0'
3
3
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
+ - 1
7
8
  - 0
8
- - 2
9
- version: 0.0.2
9
+ version: 0.1.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Jan-Erik Rediger
@@ -14,10 +14,23 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-12 00:00:00 +01:00
17
+ date: 2010-03-14 00:00:00 +01:00
18
18
  default_executable:
19
- dependencies: []
20
-
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: eventmachine
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 12
30
+ - 10
31
+ version: 0.12.10
32
+ type: :runtime
33
+ version_requirements: *id001
21
34
  description: " uses the ipc socket of i3 to send commands or get information directly from the window manager. Useful for scripting the window manager.'\n"
22
35
  email: badboy@archlinux.us
23
36
  executables:
@@ -30,6 +43,7 @@ files:
30
43
  - README.markdown
31
44
  - Rakefile
32
45
  - LICENSE
46
+ - lib/i3-ipc/subscription.rb
33
47
  - lib/i3-ipc/manpage.rb
34
48
  - lib/i3-ipc/standalone.rb
35
49
  - lib/i3-ipc/version.rb