i3-ipc 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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