i3ipc 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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.travis.yml +9 -0
- data/.yardopts +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +177 -0
- data/Rakefile +7 -0
- data/i3ipc.gemspec +31 -0
- data/lib/i3ipc.rb +4 -0
- data/lib/i3ipc/connection.rb +66 -0
- data/lib/i3ipc/protocol.rb +136 -0
- data/lib/i3ipc/reply.rb +116 -0
- data/lib/i3ipc/version.rb +3 -0
- data/spec/i3_mock_server.rb +57 -0
- data/spec/i3ipc/connection_spec.rb +46 -0
- data/spec/i3ipc/protocol_spec.rb +132 -0
- data/spec/i3ipc/reply_spec.rb +117 -0
- data/spec/spec_helper.rb +8 -0
- metadata +114 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: e40ae80557aa74e152aa616ae813744d8b755586
|
|
4
|
+
data.tar.gz: a5f833254c367b6d9bc4b4bdecd8303020388194
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4af0b717ee2d06d19d858694305c21eef45c2ef0b1e74427acafd18c32c9db0835694c9c84fadc5456d1a55bb51a7b1e5664d58f50f564ba4aed75095a1769de
|
|
7
|
+
data.tar.gz: 38fef1144589bacb4c4b7edcae7427f435aeaa95fca85a0b45789f4805f2e9b21deade91c6f396096bf4e201a8cd0603b0207e21dc25787f0a50f10113403208
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2015 veelenga
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# I3ipc [](https://rubygems.org/gems/i3ipc) [](https://travis-ci.org/veelenga/i3ipc-ruby)
|
|
2
|
+
|
|
3
|
+
- [Installation](#installation)
|
|
4
|
+
- [Usage](#usage)
|
|
5
|
+
- [Command](#command)
|
|
6
|
+
- [Workspaces](#workspaces)
|
|
7
|
+
- [Subscribe](#subscribe)
|
|
8
|
+
- [Outputs](#outputs)
|
|
9
|
+
- [Tree](#tree)
|
|
10
|
+
- [Marks](#marks)
|
|
11
|
+
- [Bar config](#bar-config)
|
|
12
|
+
- [Version](#version)
|
|
13
|
+
- [Contributing](#contributing)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
An improved Ruby library to control [i3wm](http://i3wm.org/).
|
|
17
|
+
|
|
18
|
+
i3's interprocess communication (or [ipc](http://i3wm.org/docs/ipc.html)) is the interface i3 wm uses to receive commands from the clients. It also features a publish/subscribe mechanism for notifying interested parties of window manager events.
|
|
19
|
+
|
|
20
|
+
This gem will be useful for example for controlling i3 windows manager or to get various information like the current workspaces or to implemennt external workspace bar in `Ruby` language.
|
|
21
|
+
|
|
22
|
+
Inspired by [i3ipc-python](https://github.com/acrisci/i3ipc-python), [i3ipc-gjs](https://github.com/acrisci/i3ipc-gjs), [i3ipc-lua](https://github.com/acrisci/i3ipc-lua) and reworked mainly from [i3-ipc](https://github.com/badboy/i3-ipc) (thanks to [@badboy](https://github.com/badboy) for this gem).
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Nothing special here:
|
|
27
|
+
```sh
|
|
28
|
+
$ gem install i3ipc
|
|
29
|
+
```
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
Usage is very simple and straightforward:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
require 'i3ipc'
|
|
36
|
+
|
|
37
|
+
i3 = I3Ipc::Connection.new
|
|
38
|
+
# communicate with i3 server...
|
|
39
|
+
# ...
|
|
40
|
+
i3.close
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Below you can find examples of usage some replies from local i3 wm. Output depend on my config and will be different in other env. A list of messages to send and replies you can find in [Receiving replies from i3](https://i3wm.org/docs/ipc.html#_receiving_replies_from_i3).
|
|
45
|
+
|
|
46
|
+
Each reply from i3 wm will be parsed and packed in a special object. That object responds to any method with a name of an original name of i3 wm attribute in the reply. So you can access attributes in a very useful way. Find examples below.
|
|
47
|
+
|
|
48
|
+
### Command
|
|
49
|
+
|
|
50
|
+
Executes one or more command at a time. Reply contains the property `success (bool)` for each command:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
>> command = i3.command('workspace 0; focus left')
|
|
54
|
+
>> puts command[0]
|
|
55
|
+
{
|
|
56
|
+
"success": true
|
|
57
|
+
}
|
|
58
|
+
>> puts command[0].success
|
|
59
|
+
true
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
or a human readable error message in the property `error (string)`
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
>> command = i3.command('this a bad command')
|
|
66
|
+
>> puts command[0].success
|
|
67
|
+
false
|
|
68
|
+
>> puts command[0].error
|
|
69
|
+
Expected one of these tokens: <end>, '[', 'move', 'exec', 'exit', 'restart', 'reload', 'shmlog', 'debuglog', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'unmark', 'resize', 'rename', 'nop', 'scratchpad', 'mode', 'bar'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Workspaces
|
|
73
|
+
|
|
74
|
+
Reply consists of a list of workspaces. Each workspace has some properties:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
>> workspaces = i3.workspaces
|
|
78
|
+
>> puts workspaces[0]
|
|
79
|
+
{
|
|
80
|
+
"num": 1,
|
|
81
|
+
"name": "1 Browse",
|
|
82
|
+
"visible": true,
|
|
83
|
+
"focused": false,
|
|
84
|
+
"rect": {
|
|
85
|
+
"x": 1366,
|
|
86
|
+
"y": 20,
|
|
87
|
+
"width": 1920,
|
|
88
|
+
"height": 1060
|
|
89
|
+
},
|
|
90
|
+
"output": "VGA1",
|
|
91
|
+
"urgent": false
|
|
92
|
+
}
|
|
93
|
+
>> puts workspaces[0].name
|
|
94
|
+
1 Browse
|
|
95
|
+
>> puts workspaces[0].rect.width
|
|
96
|
+
1920
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Subscribe
|
|
100
|
+
|
|
101
|
+
To be done (not implemented yet)
|
|
102
|
+
|
|
103
|
+
### Outputs
|
|
104
|
+
|
|
105
|
+
Reply consists of a list of outputs:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
>> outputs = i3.outputs
|
|
109
|
+
>> puts oututs[0].name
|
|
110
|
+
LVDS1
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Tree
|
|
114
|
+
|
|
115
|
+
The reply consists information about i3 tree. Each node in the tree (representing one container) has some properties:
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
>> tree = i3.tree
|
|
119
|
+
>> puts tree.id
|
|
120
|
+
8214416
|
|
121
|
+
>> puts tree.nodes[0].name
|
|
122
|
+
VGA1
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Marks
|
|
126
|
+
|
|
127
|
+
Reply consists of a single array of string for each container that has a mark.
|
|
128
|
+
|
|
129
|
+
First we need to create some marks:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
>> i3.command('mark terminal; focus right; mark vim')
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Then can get a list of available marks:
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
>> puts i3.marks
|
|
139
|
+
terminal
|
|
140
|
+
vim
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
And use those marks:
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
>> i3.command("focus right; [con_mark=\"terminal\"] focus")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Bar config
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
>> puts i3.bar_config
|
|
153
|
+
bar-0
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Version
|
|
157
|
+
|
|
158
|
+
Reply describes a current version of i3 windows manager:
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
>> puts i3.version
|
|
162
|
+
{
|
|
163
|
+
"major": 4,
|
|
164
|
+
"minor": 10,
|
|
165
|
+
"patch": 2,
|
|
166
|
+
"human_readable": "4.10.2 (2015-04-16, branch \"4.10.2\")"
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Contributing
|
|
171
|
+
|
|
172
|
+
1. Fork it
|
|
173
|
+
1. Create your feature branch (`git checkout -b my-new-feature`)
|
|
174
|
+
1. Commit your changes (`git commit -am 'Add some feature'`)
|
|
175
|
+
1. Run tests (`bundle exec rspec`)
|
|
176
|
+
1. Push to the branch (`git push origin my-new-feature`)
|
|
177
|
+
1. Create a new Pull Request
|
data/Rakefile
ADDED
data/i3ipc.gemspec
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'i3ipc/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = 'i3ipc'
|
|
8
|
+
spec.version = I3ipc::VERSION
|
|
9
|
+
spec.authors = ['Vitalii Elengaupt']
|
|
10
|
+
spec.email = ['velenhaupt@gmail.com']
|
|
11
|
+
spec.summary = 'Interprocess communication with i3 wm'
|
|
12
|
+
spec.description = <<-DESC
|
|
13
|
+
Implementation of interface for i3 tiling window manager.
|
|
14
|
+
Useful for example to remote-control i3 or to get various
|
|
15
|
+
information like the current workspace to implement an
|
|
16
|
+
external workspace bar etc. in Ruby language.
|
|
17
|
+
DESC
|
|
18
|
+
spec.homepage = 'https://github.com/veelenga/i3ipc-ruby'
|
|
19
|
+
spec.license = 'MIT'
|
|
20
|
+
|
|
21
|
+
spec.files = `git ls-files`.split($RS)
|
|
22
|
+
spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
|
|
23
|
+
spec.test_files = spec.files.grep(/^spec\//)
|
|
24
|
+
spec.require_paths = ['lib']
|
|
25
|
+
|
|
26
|
+
spec.required_ruby_version = '>= 1.9.3'
|
|
27
|
+
|
|
28
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
|
29
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
|
30
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
31
|
+
end
|
data/lib/i3ipc.rb
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require 'i3ipc/protocol'
|
|
2
|
+
require 'i3ipc/reply'
|
|
3
|
+
|
|
4
|
+
module I3Ipc
|
|
5
|
+
# Entry point for communication with i3-ipc.
|
|
6
|
+
# Able to send/receive messages and convert
|
|
7
|
+
# responses.
|
|
8
|
+
#
|
|
9
|
+
# Usage example:
|
|
10
|
+
# con = Connection.new
|
|
11
|
+
# p con.version.human_readable # => 4.10.2 (2015-0...
|
|
12
|
+
# p con.command('focus left').success? # => true
|
|
13
|
+
# p con.workspaces[0].name # => 0 Term
|
|
14
|
+
# # ...
|
|
15
|
+
# con.close
|
|
16
|
+
class Connection
|
|
17
|
+
|
|
18
|
+
def initialize(connect = true)
|
|
19
|
+
@protocol = Protocol.new
|
|
20
|
+
connect && @protocol.connect
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def connect
|
|
24
|
+
@protocol.connect
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def disconnect
|
|
28
|
+
@protocol.disconnect
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def command(cmds)
|
|
32
|
+
reply_for(0, cmds)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def workspaces
|
|
36
|
+
reply_for(1)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def outputs
|
|
40
|
+
reply_for(3)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def tree
|
|
44
|
+
reply_for(4)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def marks
|
|
48
|
+
reply_for(5)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def bar_config
|
|
52
|
+
reply_for(6)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def version
|
|
56
|
+
reply_for(7)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def reply_for(type, message = nil)
|
|
62
|
+
@protocol.send(type, message)
|
|
63
|
+
Reply.parse(@protocol.receive type)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
|
|
3
|
+
module I3Ipc
|
|
4
|
+
# Communication interface with i3-ipc.
|
|
5
|
+
#
|
|
6
|
+
# Can connect to i3-ipc socket, disconnect, send and receive messages.
|
|
7
|
+
#
|
|
8
|
+
# Usage example:
|
|
9
|
+
# protocol = Protocol.new
|
|
10
|
+
# protocol.send(7)
|
|
11
|
+
# puts protocol.receive
|
|
12
|
+
# protocol.disconnect
|
|
13
|
+
#
|
|
14
|
+
# For i3-ipc interface details refer to https://i3wm.org/docs/ipc.html.
|
|
15
|
+
class Protocol
|
|
16
|
+
# Magic string for i3-ipc protocol to ensure the integrity of messages.
|
|
17
|
+
MAGIC_STRING = 'i3-ipc'
|
|
18
|
+
|
|
19
|
+
# Throws when received data with not expected magic string.
|
|
20
|
+
# Usually this means that protocol is not compatible with
|
|
21
|
+
# current i3-ipc version.
|
|
22
|
+
class WrongMagicString < RuntimeError
|
|
23
|
+
def initialize(magic_string)
|
|
24
|
+
@magic_string = magic_string
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def message
|
|
28
|
+
%Q{Magic code expected '#{MAGIC_STRING}', but was '#{@magic_string}'}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Throws when received data with not expected type.
|
|
33
|
+
class WrongType < RuntimeError
|
|
34
|
+
def initialize(expected, actual)
|
|
35
|
+
@expected = expected
|
|
36
|
+
@actual = actual
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def message
|
|
40
|
+
%Q{Message type expected '#{@expected}', but was '#{@actual}'}
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Throws when protocol trying to do some action
|
|
45
|
+
# on non-connected channel.
|
|
46
|
+
class NotConnected < RuntimeError; end
|
|
47
|
+
|
|
48
|
+
def initialize(socketpath = nil)
|
|
49
|
+
@socketpath = socketpath ? socketpath : get_socketpath
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Connects to i3-ipc server socket using Socket::UNIXSocket.
|
|
53
|
+
# Does nothing if already connected.
|
|
54
|
+
def connect
|
|
55
|
+
@socket = UNIXSocket.new(@socketpath) unless @socket
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Disconnects from i3-ipc server socket.
|
|
59
|
+
# Does nothing if not connected.
|
|
60
|
+
def disconnect
|
|
61
|
+
@socket && @socket.close
|
|
62
|
+
@socket = nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Sends packed message to i3-ipc server socket.
|
|
66
|
+
#
|
|
67
|
+
# Throws:
|
|
68
|
+
# * NotConnected if protocol is not connected.
|
|
69
|
+
#
|
|
70
|
+
# +type+: type of the message.
|
|
71
|
+
# +payload+: payload of the message
|
|
72
|
+
def send(type, payload = nil)
|
|
73
|
+
check_connected
|
|
74
|
+
@socket.write(pack(type, payload))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Receives message from i3-ipc server socket.
|
|
78
|
+
#
|
|
79
|
+
# Throws:
|
|
80
|
+
# * NotConnected if protocol is not connected.
|
|
81
|
+
# * WrongMagicString if got message with not expected magic string.
|
|
82
|
+
# * WrongType if got message with not expected magic type.
|
|
83
|
+
#
|
|
84
|
+
# +type+: expected type of the message.
|
|
85
|
+
def receive(type = nil)
|
|
86
|
+
check_connected
|
|
87
|
+
# length of "i3-ipc" + 4 bytes length + 4 bytes type
|
|
88
|
+
data = @socket.read 14
|
|
89
|
+
magic, len, recv_type = unpack_header(data)
|
|
90
|
+
|
|
91
|
+
raise WrongMagicString.new(magic) unless MAGIC_STRING.eql? magic
|
|
92
|
+
type && (raise WrongType.new(type, recv_type) unless type == recv_type)
|
|
93
|
+
|
|
94
|
+
@socket.read(len)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
# Packs the message.
|
|
100
|
+
# A typical message looks like:
|
|
101
|
+
# <header><payload>
|
|
102
|
+
# where a header is:
|
|
103
|
+
# <magic string><message length><message type>
|
|
104
|
+
#
|
|
105
|
+
# +type+: type of the message
|
|
106
|
+
# +payload+: patload of the message
|
|
107
|
+
def pack(type, payload=nil)
|
|
108
|
+
size = payload ? payload.to_s.bytes.count : 0
|
|
109
|
+
msg = MAGIC_STRING + [size, type].pack("LL")
|
|
110
|
+
msg << payload.to_s if payload
|
|
111
|
+
msg
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Unpacks the header.
|
|
115
|
+
# A typical header looks like:
|
|
116
|
+
# <magic_string><message length><message type>
|
|
117
|
+
#
|
|
118
|
+
# +data+: data to be unpacked.
|
|
119
|
+
def unpack_header(data)
|
|
120
|
+
struct_header_len = MAGIC_STRING.size
|
|
121
|
+
magic_message = data[0, struct_header_len]
|
|
122
|
+
len, type = data[struct_header_len..-1].unpack("LL")
|
|
123
|
+
[magic_message, len, type]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def get_socketpath
|
|
127
|
+
path = `i3 --get-socketpath`.chomp!
|
|
128
|
+
raise 'Unable to get i3 socketpath' unless path
|
|
129
|
+
path
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def check_connected
|
|
133
|
+
raise NotConnected unless @socket
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
data/lib/i3ipc/reply.rb
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module I3Ipc
|
|
4
|
+
# Wrapper for reply from i3-ipc.
|
|
5
|
+
#
|
|
6
|
+
# Represents response from i3 as object tree.
|
|
7
|
+
#
|
|
8
|
+
# Able to parse Numeric, String, TrueClass, FalseClass,
|
|
9
|
+
# Array, Hash from passed JSON string.
|
|
10
|
+
#
|
|
11
|
+
# For example:
|
|
12
|
+
# response = Reply.parse(
|
|
13
|
+
# %Q{
|
|
14
|
+
# {
|
|
15
|
+
# "name": "LVDS1",
|
|
16
|
+
# "active": true,
|
|
17
|
+
# "current_workspace": "4",
|
|
18
|
+
# "rect": {
|
|
19
|
+
# "x": 0,
|
|
20
|
+
# "y": 0,
|
|
21
|
+
# "width": 1280,
|
|
22
|
+
# "height": 800
|
|
23
|
+
# }
|
|
24
|
+
# }
|
|
25
|
+
# }
|
|
26
|
+
# )
|
|
27
|
+
#
|
|
28
|
+
# p response.name # => "LVDS1"
|
|
29
|
+
# p response.active # => true
|
|
30
|
+
# p response.rect.width # => 1280
|
|
31
|
+
# # ...
|
|
32
|
+
#
|
|
33
|
+
# response = Reply.parse(%Q{ {"data": [{"key1": true}, {"key2": false}]} })
|
|
34
|
+
# p response.data[0].key1 # => true
|
|
35
|
+
# p response.data[0].key2 # => false
|
|
36
|
+
class Reply
|
|
37
|
+
def initialize(data)
|
|
38
|
+
@data = data.dup
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Parses response from I3-ipc protocol.
|
|
42
|
+
#
|
|
43
|
+
# Returns Reply object with dynamically accessed values.
|
|
44
|
+
#
|
|
45
|
+
# +response+: string, that represents response from i3 in json format.
|
|
46
|
+
def self.parse(response)
|
|
47
|
+
parse_data JSON.parse(response)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Returns false if this reply represents and error
|
|
51
|
+
# from i3-ipc protocol. Otherwise returns true, which
|
|
52
|
+
# meens that request is successful and reply has some
|
|
53
|
+
# data.
|
|
54
|
+
def success?
|
|
55
|
+
not self.respond_to? :error
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def method_missing(name, *args, &block)
|
|
59
|
+
if @data.include?(name)
|
|
60
|
+
raise ArgumentError.new('wrong number of arguments (%d for 0)' % args.length) if args.length > 0
|
|
61
|
+
return @data[name]
|
|
62
|
+
else
|
|
63
|
+
super
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def respond_to?(method_sym, include_private = false)
|
|
68
|
+
if @data.include?(method_sym)
|
|
69
|
+
true
|
|
70
|
+
else
|
|
71
|
+
super
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def to_s
|
|
76
|
+
JSON.pretty_generate(to_h)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def to_h
|
|
80
|
+
data = @data.dup
|
|
81
|
+
data.each do |k, v|
|
|
82
|
+
data[k] = Reply.unparse_data v
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def self.parse_data(data)
|
|
89
|
+
case data
|
|
90
|
+
when Numeric, String, TrueClass, FalseClass, NilClass
|
|
91
|
+
return data
|
|
92
|
+
when Array
|
|
93
|
+
return data.map {|v| parse_data(v)}
|
|
94
|
+
when Hash
|
|
95
|
+
data.each do |k, v|
|
|
96
|
+
data[k] = parse_data v
|
|
97
|
+
end
|
|
98
|
+
return Reply.new(Hash[data.map {|k, v| [k.to_sym, v]}])
|
|
99
|
+
else
|
|
100
|
+
raise "Unable to parse data of type #{data.class}"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def self.unparse_data(data)
|
|
105
|
+
case data
|
|
106
|
+
when Numeric, String, TrueClass, FalseClass, NilClass
|
|
107
|
+
data
|
|
108
|
+
when Reply
|
|
109
|
+
data.to_h
|
|
110
|
+
when Array
|
|
111
|
+
data.map! {|x| self.unparse_data(x)}
|
|
112
|
+
data
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
4
|
+
module I3Ipc
|
|
5
|
+
# Simple socket server that communicates with
|
|
6
|
+
# client simulating i3-ipc messages.
|
|
7
|
+
class I3MockServer
|
|
8
|
+
SOCKET_PATH = '/tmp/i3-mock-server.sock'
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
remove_sock
|
|
12
|
+
@server = UNIXServer.new(SOCKET_PATH)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def accept_client
|
|
16
|
+
@client = @server.accept_nonblock
|
|
17
|
+
self
|
|
18
|
+
rescue IO::WaitReadable, Errno::EINTR
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def client_alive?
|
|
23
|
+
return false unless @client
|
|
24
|
+
@client.write 'hi'
|
|
25
|
+
true
|
|
26
|
+
rescue Errno::EPIPE
|
|
27
|
+
false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def receive(len)
|
|
31
|
+
raise 'Client not accepted yet' unless @client
|
|
32
|
+
@client.read(len)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def send(data)
|
|
36
|
+
raise 'Client not accepted yet' unless @client
|
|
37
|
+
@client.write(data)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def close_client
|
|
41
|
+
@client.close if @client
|
|
42
|
+
@client = nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def close
|
|
46
|
+
close_client
|
|
47
|
+
@server.close unless @server.closed?
|
|
48
|
+
remove_sock
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def remove_sock
|
|
54
|
+
FileUtils.rm(SOCKET_PATH) if File.exist?(SOCKET_PATH)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module I3Ipc
|
|
4
|
+
describe Connection do
|
|
5
|
+
|
|
6
|
+
describe '#command' do
|
|
7
|
+
|
|
8
|
+
before(:all) do
|
|
9
|
+
class Connection
|
|
10
|
+
def initialize
|
|
11
|
+
@protocol = Protocol.new ''
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'returns Reply object' do
|
|
17
|
+
allow_any_instance_of(Protocol).to receive(:send)
|
|
18
|
+
allow_any_instance_of(Protocol).to receive(:receive)
|
|
19
|
+
.and_return("{}")
|
|
20
|
+
|
|
21
|
+
expect(Connection.new.command('cmd')).to be_a Reply
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'sends correct command and receives success response' do
|
|
25
|
+
allow_any_instance_of(Protocol).to receive(:send)
|
|
26
|
+
allow_any_instance_of(Protocol).to receive(:receive)
|
|
27
|
+
.and_return(%Q[{"success": true}])
|
|
28
|
+
|
|
29
|
+
connection = Connection.new
|
|
30
|
+
reply = connection.command('focus left')
|
|
31
|
+
expect(reply.success).to be true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'sends incorrect command and receives error response' do
|
|
35
|
+
allow_any_instance_of(Protocol).to receive(:send)
|
|
36
|
+
allow_any_instance_of(Protocol).to receive(:receive)
|
|
37
|
+
.and_return(%Q[{"success": false, "error": "wrong command"}])
|
|
38
|
+
|
|
39
|
+
connection = Connection.new
|
|
40
|
+
reply = connection.command('my command')
|
|
41
|
+
expect(reply.success).to be false
|
|
42
|
+
expect(reply.error).to eql 'wrong command'
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module I3Ipc
|
|
4
|
+
describe Protocol do
|
|
5
|
+
|
|
6
|
+
before(:all) do
|
|
7
|
+
# open internal methods for verification needs
|
|
8
|
+
class Protocol
|
|
9
|
+
public :pack, :unpack_header
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
before(:each, :i3 => :simulate) do
|
|
14
|
+
@i3_srv = I3MockServer.new
|
|
15
|
+
subject.connect
|
|
16
|
+
@i3_srv.accept_client
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
after(:each, :i3 => :simulate) do
|
|
20
|
+
@i3_srv.close
|
|
21
|
+
subject.disconnect
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
subject { Protocol.new(I3MockServer::SOCKET_PATH) }
|
|
25
|
+
|
|
26
|
+
it 'has MAGIC_STRING string constant' do
|
|
27
|
+
expect(Protocol::MAGIC_STRING).to be_a String
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe '#connect' do
|
|
31
|
+
it 'fails to connect if server not running' do
|
|
32
|
+
expect { subject.connect }.to raise_error Errno::ENOENT
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'connects if server running', :i3 => :simulate do
|
|
36
|
+
expect(@i3_srv.client_alive?).to be true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'reconnects if disconnected', :i3 => :simulate do
|
|
40
|
+
subject.disconnect
|
|
41
|
+
subject.connect
|
|
42
|
+
expect(@i3_srv.accept_client.client_alive?).to be true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'does nothing if already connected', :i3 => :simulate do
|
|
46
|
+
subject.connect
|
|
47
|
+
expect(@i3_srv.accept_client).to be nil
|
|
48
|
+
expect(@i3_srv.client_alive?).to be true
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe '#disconnect', :i3 => :simulate do
|
|
53
|
+
it 'disconnects if connected' do
|
|
54
|
+
subject.disconnect
|
|
55
|
+
expect(@i3_srv.client_alive?).to be false
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'does nothing if aready disconnected' do
|
|
59
|
+
subject.disconnect
|
|
60
|
+
subject.disconnect
|
|
61
|
+
expect(@i3_srv.client_alive?).to be false
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe '#send', :i3 => :simulate do
|
|
66
|
+
let(:type) { 42 }
|
|
67
|
+
let(:message) { 'test_send' }
|
|
68
|
+
|
|
69
|
+
it 'is able to send packed type' do
|
|
70
|
+
subject.send(type)
|
|
71
|
+
|
|
72
|
+
data = @i3_srv.receive(14)
|
|
73
|
+
header = subject.unpack_header data
|
|
74
|
+
expect(header).to match_array [Protocol::MAGIC_STRING, 0, type]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'is able to send packed type and payload' do
|
|
78
|
+
subject.send(type, message)
|
|
79
|
+
|
|
80
|
+
data = @i3_srv.receive(14)
|
|
81
|
+
header = subject.unpack_header data
|
|
82
|
+
expect(header).to match_array [Protocol::MAGIC_STRING, message.size, type]
|
|
83
|
+
|
|
84
|
+
data = @i3_srv.receive(header[1])
|
|
85
|
+
expect(data).to eq message
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'throws error if not connected' do
|
|
89
|
+
subject.disconnect
|
|
90
|
+
expect { subject.send(type) }.to raise_error
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
describe '#receive', :i3 => :simulate do
|
|
95
|
+
let(:type) { 40 }
|
|
96
|
+
let(:message) { 'test_receive' }
|
|
97
|
+
|
|
98
|
+
context 'if server send packed message' do
|
|
99
|
+
before (:each) do
|
|
100
|
+
data = subject.pack(type, message)
|
|
101
|
+
@i3_srv.send(data)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'returns unpacked message' do
|
|
105
|
+
recv_message = subject.receive
|
|
106
|
+
expect(recv_message).to eq(message)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'throws WrongType if received type does not match expected one' do
|
|
110
|
+
expect { subject.receive(type + 1) }.to raise_error Protocol::WrongType
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
context 'if server send wrong message' do
|
|
115
|
+
before (:each) do
|
|
116
|
+
data = 'i4-ipc' + [message.size, type].pack("LL") + message
|
|
117
|
+
@i3_srv.send(data)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'throws WrongMagicString' do
|
|
121
|
+
expect { subject.receive(type) }.to raise_error Protocol::WrongMagicString
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'throws error if not connected' do
|
|
126
|
+
subject.disconnect
|
|
127
|
+
expect { subject.receive }.to raise_error Protocol::NotConnected
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module I3Ipc
|
|
4
|
+
describe Reply do
|
|
5
|
+
|
|
6
|
+
describe '.parse' do
|
|
7
|
+
context 'when valid JSON string passed' do
|
|
8
|
+
|
|
9
|
+
it 'properly parses boolean attributes' do
|
|
10
|
+
expect(Reply.parse('{"success": true}').success).to be true
|
|
11
|
+
expect(Reply.parse('{"success": false}').success).to be false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'properly parses numeric attributes' do
|
|
15
|
+
reply = Reply.parse(%Q[{ "int": 2, "float": 4.2 }])
|
|
16
|
+
expect(reply.int).to eql 2
|
|
17
|
+
expect(reply.float).to eql 4.2
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'properly parses string attributes' do
|
|
21
|
+
expect(Reply.parse('{"output": "LVDS1"}').output).to eql "LVDS1"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'properly parses array of hashes' do
|
|
25
|
+
reply = Reply.parse( %Q[{ "arr": [{"key1": true}, {"key2": false}] }])
|
|
26
|
+
expect(reply.arr[0].key1).to be true
|
|
27
|
+
expect(reply.arr[1].key2).to be false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'properly parses sub-hashes' do
|
|
31
|
+
reply = Reply.parse(%Q[{ "ha":{ "key1": "val1", "key2": "val2"} }])
|
|
32
|
+
expect(reply.ha.key1).to eql 'val1'
|
|
33
|
+
expect(reply.ha.key2).to eql 'val2'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'properly parses sub-arrays' do
|
|
37
|
+
reply = Reply.parse(%Q[{ "arr": [[ 1, 2 ]] }])
|
|
38
|
+
expect(reply.arr[0]).to match_array [1, 2]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'properly parses empty array' do
|
|
42
|
+
reply = Reply.parse(%Q[{"ar" : []}])
|
|
43
|
+
expect(reply.ar).to be_a Array
|
|
44
|
+
expect(reply.ar).to be_empty
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'property parses empty hash' do
|
|
48
|
+
reply = Reply.parse(%Q[{"ha": {}}]);
|
|
49
|
+
expect(reply.ha).to be_a Reply
|
|
50
|
+
expect(reply.ha.to_s).to eq "{\n}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'returns new Reply object' do
|
|
54
|
+
expect(Reply.parse('{}')).to be_a Reply
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
context 'when NOT valid JSON string passed' do
|
|
59
|
+
it 'raise JSON::ParserError' do
|
|
60
|
+
expect{Reply.parse(%Q[{"data": }])}.to raise_error JSON::ParserError
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe '#success?' do
|
|
66
|
+
it 'returns true if response without error' do
|
|
67
|
+
reply = Reply.parse(%Q[{}])
|
|
68
|
+
expect(reply.success?).to be true
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'returns false if response with error' do
|
|
72
|
+
reply = Reply.parse(%Q[{"error": "Wrong command"}])
|
|
73
|
+
expect(reply.success?).to be false
|
|
74
|
+
expect(reply.error).to eql 'Wrong command'
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe '#to_h' do
|
|
79
|
+
it 'converts it back to hash' do
|
|
80
|
+
hash = {:f => 1, :a => 2, :inner => {:ar => [true, false, "v"]}}
|
|
81
|
+
reply = Reply.parse(hash.to_json)
|
|
82
|
+
expect(reply.to_h). to eql hash
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe '#to_s' do
|
|
87
|
+
it 'returns property formatter structure with hash, arrays and primitives' do
|
|
88
|
+
reply = Reply.parse(%Q[{"d":{"1":[{"k":"v"}]} }])
|
|
89
|
+
expect(reply.to_s).to eql "{\n \"d\": {\n \"1\": [\n {\n \"k\": \"v\"\n }\n ]\n }\n}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe '#method_missing' do
|
|
94
|
+
it 'returns value with dynamic method based on input data' do
|
|
95
|
+
expect(Reply.new({:meth => 'val'}).meth).to eql 'val'
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'throws ArgumentError if one or more parameters passed to dynamic method' do
|
|
99
|
+
expect{ Reply.new({:meth => 'val'}).meth(0) }.to raise_error ArgumentError
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'still throws NoMethodError if not existed method called' do
|
|
103
|
+
expect{ Reply.new({:meth => 'val'}).no_such_method }.to raise_error NoMethodError
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
describe '#respond_to' do
|
|
108
|
+
it 'responds to method from input data' do
|
|
109
|
+
expect(Reply.new({:meth => 'val'})).to respond_to :meth
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'still does not respond to not existed methods' do
|
|
113
|
+
expect(Reply.new({:meth => 'val'})).not_to respond_to :no_such_method
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: i3ipc
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Vitalii Elengaupt
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2015-05-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.7'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.7'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.0'
|
|
55
|
+
description: |2
|
|
56
|
+
Implementation of interface for i3 tiling window manager.
|
|
57
|
+
Useful for example to remote-control i3 or to get various
|
|
58
|
+
information like the current workspace to implement an
|
|
59
|
+
external workspace bar etc. in Ruby language.
|
|
60
|
+
email:
|
|
61
|
+
- velenhaupt@gmail.com
|
|
62
|
+
executables: []
|
|
63
|
+
extensions: []
|
|
64
|
+
extra_rdoc_files: []
|
|
65
|
+
files:
|
|
66
|
+
- ".gitignore"
|
|
67
|
+
- ".travis.yml"
|
|
68
|
+
- ".yardopts"
|
|
69
|
+
- Gemfile
|
|
70
|
+
- LICENSE.txt
|
|
71
|
+
- README.md
|
|
72
|
+
- Rakefile
|
|
73
|
+
- i3ipc.gemspec
|
|
74
|
+
- lib/i3ipc.rb
|
|
75
|
+
- lib/i3ipc/connection.rb
|
|
76
|
+
- lib/i3ipc/protocol.rb
|
|
77
|
+
- lib/i3ipc/reply.rb
|
|
78
|
+
- lib/i3ipc/version.rb
|
|
79
|
+
- spec/i3_mock_server.rb
|
|
80
|
+
- spec/i3ipc/connection_spec.rb
|
|
81
|
+
- spec/i3ipc/protocol_spec.rb
|
|
82
|
+
- spec/i3ipc/reply_spec.rb
|
|
83
|
+
- spec/spec_helper.rb
|
|
84
|
+
homepage: https://github.com/veelenga/i3ipc-ruby
|
|
85
|
+
licenses:
|
|
86
|
+
- MIT
|
|
87
|
+
metadata: {}
|
|
88
|
+
post_install_message:
|
|
89
|
+
rdoc_options: []
|
|
90
|
+
require_paths:
|
|
91
|
+
- lib
|
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 1.9.3
|
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - ">="
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
requirements: []
|
|
103
|
+
rubyforge_project:
|
|
104
|
+
rubygems_version: 2.4.6
|
|
105
|
+
signing_key:
|
|
106
|
+
specification_version: 4
|
|
107
|
+
summary: Interprocess communication with i3 wm
|
|
108
|
+
test_files:
|
|
109
|
+
- spec/i3_mock_server.rb
|
|
110
|
+
- spec/i3ipc/connection_spec.rb
|
|
111
|
+
- spec/i3ipc/protocol_spec.rb
|
|
112
|
+
- spec/i3ipc/reply_spec.rb
|
|
113
|
+
- spec/spec_helper.rb
|
|
114
|
+
has_rdoc:
|