-
Notifications
You must be signed in to change notification settings - Fork 18
/
socket.go
184 lines (157 loc) · 4.08 KB
/
socket.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// Author: slowpoke <mail plus git at slowpoke dot io>
// Repository: https://github.com/proxypoke/i3ipc
//
// This program is free software under the terms of the
// Do What The Fuck You Want To Public License.
// It comes without any warranty, to the extent permitted by
// applicable law. For a copy of the license, see COPYING or
// head to http://sam.zoy.org/wtfpl/COPYING.
package i3ipc
import (
"bytes"
"fmt"
"net"
"os/exec"
"strings"
"unsafe"
)
const (
// Magic string for the IPC API.
_MAGIC string = "i3-ipc"
// The length of the i3 message "header" is 14 bytes: 6 for the _MAGIC
// string, 4 for the length of the payload (int32 in native byte order) and
// another 4 for the message type (also int32 in NBO).
_HEADERLEN = 14
)
// A message from i3. Can either be a Reply or an Event.
type Message struct {
Payload []byte
IsEvent bool
Type int32
}
// The types of messages that Raw() accepts.
type MessageType int32
const (
I3Command MessageType = iota
I3GetWorkspaces
I3Subscribe
I3GetOutputs
I3GetTree
I3GetMarks
I3GetBarConfig
I3GetVersion
)
// Error for unknown message types.
type MessageTypeError string
func (self MessageTypeError) Error() string {
return string(self)
}
// Error for communiation failures.
type MessageError string
func (self MessageError) Error() string {
return string(self)
}
// An Unix socket to communicate with i3.
type IPCSocket struct {
socket net.Conn
open bool
subscribers []chan Event
}
// Close the connection to the underlying Unix socket.
func (self *IPCSocket) Close() error {
self.open = false
return self.socket.Close()
}
// Create a new IPC socket.
func GetIPCSocket() (ipc *IPCSocket, err error) {
var out bytes.Buffer
ipc = &IPCSocket{}
cmd := exec.Command("i3", "--get-socketpath")
cmd.Stdout = &out
err = cmd.Run()
if err != nil {
return
}
path := strings.TrimSpace(out.String())
sock, err := net.Dial("unix", path)
ipc.socket = sock
ipc.open = true
return
}
// Receive a raw json bytestring from the socket and return a Message.
func (self *IPCSocket) recv() (msg Message, err error) {
header := make([]byte, _HEADERLEN)
n, err := self.socket.Read(header)
// Check if this is a valid i3 message.
if n != _HEADERLEN || err != nil {
return
}
magic_string := string(header[:len(_MAGIC)])
if magic_string != _MAGIC {
err = MessageError(fmt.Sprintf(
"Invalid magic string: got %q, expected %q.",
magic_string, _MAGIC))
return
}
var bytelen [4]byte
// Copy the byte values from the slice into the byte array. This is
// necessary because the adress of a slice does not point to the actual
// values in memory.
for i, b := range header[len(_MAGIC) : len(_MAGIC)+4] {
bytelen[i] = b
}
length := *(*int32)(unsafe.Pointer(&bytelen))
msg.Payload = make([]byte, length)
n, err = self.socket.Read(msg.Payload)
if n != int(length) || err != nil {
return
}
// Figure out the type of message.
var bytetype [4]byte
for i, b := range header[len(_MAGIC)+4 : len(_MAGIC)+8] {
bytetype[i] = b
}
type_ := *(*uint32)(unsafe.Pointer(&bytetype))
// Reminder: event messages have the highest bit of the type set to 1
if type_>>31 == 1 {
msg.IsEvent = true
}
// Use the remaining bits
msg.Type = int32(type_ & 0x7F)
return
}
// Send raw messages to i3. Returns a json bytestring.
func (self *IPCSocket) Raw(type_ MessageType, args string) (json_reply []byte, err error) {
// Set up the parts of the message.
var (
message []byte = []byte(_MAGIC)
payload []byte = []byte(args)
length int32 = int32(len(payload))
bytelen [4]byte
bytetype [4]byte
)
// Black Magic™.
bytelen = *(*[4]byte)(unsafe.Pointer(&length))
bytetype = *(*[4]byte)(unsafe.Pointer(&type_))
for _, b := range bytelen {
message = append(message, b)
}
for _, b := range bytetype {
message = append(message, b)
}
for _, b := range payload {
message = append(message, b)
}
_, err = self.socket.Write(message)
if err != nil {
return
}
msg, err := self.recv()
if err == nil {
json_reply = msg.Payload
}
if msg.IsEvent {
err = MessageTypeError("Received an event instead of a reply.")
}
return
}