pure-x11 0.0.2
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 +48 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +19 -0
- data/LICENSE.txt +20 -0
- data/README.md +12 -0
- data/Rakefile +7 -0
- data/docs/protocol.pdf +0 -0
- data/example/genie.png +0 -0
- data/example/test.rb +108 -0
- data/lib/X11/auth.rb +69 -0
- data/lib/X11/display.rb +450 -0
- data/lib/X11/event.rb +0 -0
- data/lib/X11/form.rb +865 -0
- data/lib/X11/keysyms.rb +328 -0
- data/lib/X11/protocol.rb +16 -0
- data/lib/X11/screen.rb +34 -0
- data/lib/X11/type.rb +116 -0
- data/lib/X11/version.rb +3 -0
- data/lib/X11.rb +12 -0
- data/ruby-x11.gemspec +24 -0
- data/test/core_test.rb +15 -0
- data/test/form_test.rb +66 -0
- data/test/helper.rb +11 -0
- metadata +66 -0
data/lib/X11/display.rb
ADDED
@@ -0,0 +1,450 @@
|
|
1
|
+
# FIXME: Temp workaround
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module X11
|
5
|
+
|
6
|
+
class DisplayError < X11Error; end
|
7
|
+
class ConnectionError < X11Error; end
|
8
|
+
class AuthorizationError < X11Error; end
|
9
|
+
class ProtocolError < X11Error; end
|
10
|
+
|
11
|
+
class Display
|
12
|
+
attr_accessor :socket
|
13
|
+
|
14
|
+
# Open a connection to the specified display (numbered from 0) on the specified host
|
15
|
+
def initialize(target = ENV['DISPLAY'])
|
16
|
+
target =~ /^([\w.-]*):(\d+)(?:.(\d+))?$/
|
17
|
+
host, display_id, screen_id = $1, $2, $3
|
18
|
+
family = nil
|
19
|
+
|
20
|
+
if host.empty?
|
21
|
+
@socket = UNIXSocket.new("/tmp/.X11-unix/X#{display_id}")
|
22
|
+
family = :Local
|
23
|
+
host = nil
|
24
|
+
else
|
25
|
+
@socket = TCPSocket.new(host,6000+display_id)
|
26
|
+
family = :Internet
|
27
|
+
end
|
28
|
+
|
29
|
+
authorize(host, family, display_id)
|
30
|
+
|
31
|
+
@requestseq = 1
|
32
|
+
@queue = []
|
33
|
+
|
34
|
+
@extensions = {}
|
35
|
+
|
36
|
+
# Interned atoms
|
37
|
+
@atoms = {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def event_handler= block
|
41
|
+
@event_handler= block
|
42
|
+
end
|
43
|
+
|
44
|
+
def display_info
|
45
|
+
@internal
|
46
|
+
end
|
47
|
+
|
48
|
+
def screens
|
49
|
+
@internal.screens.map do |s|
|
50
|
+
Screen.new(self, s)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# The resource-id-mask contains a single contiguous set of bits (at least 18).
|
56
|
+
# The client allocates resource IDs for types WINDOW, PIXMAP, CURSOR, FONT,
|
57
|
+
# GCONTEXT, and COLORMAP by choosing a value with only some subset of these
|
58
|
+
# bits set and ORing it with resource-id-base.
|
59
|
+
|
60
|
+
def new_id
|
61
|
+
id = (@xid_next ||= 0)
|
62
|
+
@xid_next += 1
|
63
|
+
|
64
|
+
(id & @internal.resource_id_mask) | @internal.resource_id_base
|
65
|
+
end
|
66
|
+
|
67
|
+
def read_error data
|
68
|
+
error = Form::Error.from_packet(StringIO.new(data))
|
69
|
+
STDERR.puts "ERROR: #{error.inspect}"
|
70
|
+
error
|
71
|
+
end
|
72
|
+
|
73
|
+
def read_reply data
|
74
|
+
len = data.unpack("@4L")[0]
|
75
|
+
extra = len > 0 ? @socket.read(len*4) : ""
|
76
|
+
#STDERR.puts "REPLY: #{data.inspect}"
|
77
|
+
#STDERR.puts "EXTRA: #{extra.inspect}"
|
78
|
+
data + extra
|
79
|
+
end
|
80
|
+
|
81
|
+
def read_event type, data, event_class
|
82
|
+
case type
|
83
|
+
when 2
|
84
|
+
return Form::KeyPress.from_packet(StringIO.new(data))
|
85
|
+
when 3
|
86
|
+
return Form::KeyRelease.from_packet(StringIO.new(data))
|
87
|
+
when 4
|
88
|
+
return Form::ButtonPress.from_packet(StringIO.new(data))
|
89
|
+
when 6
|
90
|
+
return Form::MotionNotify.from_packet(StringIO.new(data))
|
91
|
+
when 12
|
92
|
+
return Form::Expose.from_packet(StringIO.new(data))
|
93
|
+
when 14
|
94
|
+
return Form::NoExposure.from_packet(StringIO.new(data))
|
95
|
+
when 19
|
96
|
+
return Form::MapNotify.from_packet(StringIO.new(data))
|
97
|
+
when 22
|
98
|
+
return Form::ConfigureNotify.from_packet(StringIO.new(data))
|
99
|
+
else
|
100
|
+
STDERR.puts "FIXME: Event: #{type}"
|
101
|
+
STDERR.puts "EVENT: #{data.inspect}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def read_full_packet(len = 32)
|
106
|
+
data = @socket.read_nonblock(32)
|
107
|
+
return nil if data.nil?
|
108
|
+
while data.length < 32
|
109
|
+
IO.select([@socket],nil,nil,0.001)
|
110
|
+
data.concat(@socket.read_nonblock(32 - data.length))
|
111
|
+
end
|
112
|
+
return data
|
113
|
+
rescue IO::WaitReadable
|
114
|
+
return nil
|
115
|
+
end
|
116
|
+
|
117
|
+
def read_packet timeout=5.0
|
118
|
+
IO.select([@socket],nil,nil, timeout)
|
119
|
+
data = read_full_packet(32)
|
120
|
+
return nil if data.nil?
|
121
|
+
|
122
|
+
type = data.unpack("C").first
|
123
|
+
case type
|
124
|
+
when 0
|
125
|
+
read_error(data)
|
126
|
+
when 1
|
127
|
+
read_reply(data)
|
128
|
+
when 2..34
|
129
|
+
read_event(type, data, nil)
|
130
|
+
else
|
131
|
+
raise ProtocolError, "Unsupported reply type: #{type}"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def write_request ob
|
136
|
+
#p data
|
137
|
+
#p [:write_request, @requestseq, ob.class]
|
138
|
+
data = ob.to_packet if ob.respond_to?(:to_packet)
|
139
|
+
#p [:AddGlyph,data] if ob.is_a?(X11::Form::XRenderAddGlyphs)
|
140
|
+
#p [ob.request_length.to_i*4, data.size]
|
141
|
+
raise "BAD LENGTH for #{ob.inspect} (#{ob.request_length.to_i*4} ! #{data.size} " if ob.request_length && ob.request_length.to_i*4 != data.size
|
142
|
+
@requestseq += 1
|
143
|
+
@socket.write(data)
|
144
|
+
end
|
145
|
+
|
146
|
+
def write_sync(data, reply=nil)
|
147
|
+
write_request(data)
|
148
|
+
pkt = next_reply
|
149
|
+
return nil if !pkt
|
150
|
+
reply ? reply.from_packet(StringIO.new(pkt)) : pkt
|
151
|
+
end
|
152
|
+
|
153
|
+
def peek_packet
|
154
|
+
!@queue.empty?
|
155
|
+
end
|
156
|
+
|
157
|
+
def next_packet
|
158
|
+
@queue.shift || read_packet
|
159
|
+
end
|
160
|
+
|
161
|
+
def next_reply
|
162
|
+
# FIXME: This is totally broken
|
163
|
+
while pkt = read_packet
|
164
|
+
if pkt.is_a?(String)
|
165
|
+
return pkt
|
166
|
+
else
|
167
|
+
@queue.push(pkt)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def run
|
173
|
+
loop do
|
174
|
+
pkt = read_packet
|
175
|
+
return if !pkt
|
176
|
+
yield(pkt)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Requests
|
181
|
+
def create_window(*args)
|
182
|
+
write_request(X11::Form::CreateWindow.new(*args))
|
183
|
+
end
|
184
|
+
|
185
|
+
def atom(name)
|
186
|
+
intern_atom(false, name) if !@atoms[name]
|
187
|
+
@atoms[name]
|
188
|
+
end
|
189
|
+
|
190
|
+
def query_extension(name)
|
191
|
+
r = write_sync(X11::Form::QueryExtension.new(name), X11::Form::QueryExtensionReply)
|
192
|
+
@extensions[name] = {
|
193
|
+
major: r.major_opcode
|
194
|
+
}
|
195
|
+
r
|
196
|
+
end
|
197
|
+
|
198
|
+
def major_opcode(name)
|
199
|
+
if !@extensions[name]
|
200
|
+
query_extension(name)
|
201
|
+
end
|
202
|
+
raise "No such extension '#{name}'" if !@extensions[name]
|
203
|
+
@extensions[name][:major]
|
204
|
+
end
|
205
|
+
|
206
|
+
def intern_atom(flag, name)
|
207
|
+
reply = write_sync(X11::Form::InternAtom.new(flag, name.to_s),
|
208
|
+
X11::Form::InternAtomReply)
|
209
|
+
if reply
|
210
|
+
@atoms[name.to_sym] = reply.atom
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def get_keyboard_mapping(min_keycode=display_info.min_keycode, count= display_info.max_keycode - min_keycode)
|
215
|
+
write_sync(X11::Form::GetKeyboardMapping.new(min_keycode, count), X11::Form::GetKeyboardMappingReply)
|
216
|
+
end
|
217
|
+
|
218
|
+
def create_colormap(alloc, window, visual)
|
219
|
+
mid = new_id
|
220
|
+
write_request(X11::Form::CreateColormap.new(alloc, mid, window, visual))
|
221
|
+
mid
|
222
|
+
end
|
223
|
+
|
224
|
+
def change_property(*args)
|
225
|
+
write_request(X11::Form::ChangeProperty.new(*args))
|
226
|
+
end
|
227
|
+
|
228
|
+
def list_fonts(*args)
|
229
|
+
write_sync(X11::Form::ListFonts.new(*args),
|
230
|
+
X11::Form::ListFontsReply)
|
231
|
+
end
|
232
|
+
|
233
|
+
def open_font(*args)
|
234
|
+
write_request(X11::Form::OpenFont.new(*args))
|
235
|
+
end
|
236
|
+
|
237
|
+
def change_gc(*args)
|
238
|
+
write_request(X11::Form::ChangeGC.new(*args))
|
239
|
+
end
|
240
|
+
|
241
|
+
def map_window(*args)
|
242
|
+
write_request(X11::Form::MapWindow.new(*args))
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
def create_gc(window, foreground: nil, background: nil)
|
247
|
+
mask = 0
|
248
|
+
args = []
|
249
|
+
|
250
|
+
# FIXME:
|
251
|
+
# The rest can be found here:
|
252
|
+
# https://tronche.com/gui/x/xlib/GC/manipulating.html#XGCValues
|
253
|
+
if foreground
|
254
|
+
mask |= 0x04
|
255
|
+
args << foreground
|
256
|
+
end
|
257
|
+
if background
|
258
|
+
mask |= 0x08
|
259
|
+
args << background
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
gc = new_id
|
264
|
+
write_request(X11::Form::CreateGC.new(gc, window, mask, args))
|
265
|
+
gc
|
266
|
+
end
|
267
|
+
|
268
|
+
def put_image(*args)
|
269
|
+
write_request(X11::Form::PutImage.new(*args))
|
270
|
+
end
|
271
|
+
|
272
|
+
def clear_area(*args)
|
273
|
+
write_request(X11::Form::ClearArea.new(*args))
|
274
|
+
end
|
275
|
+
|
276
|
+
def copy_area(*args)
|
277
|
+
write_request(X11::Form::CopyArea.new(*args))
|
278
|
+
end
|
279
|
+
|
280
|
+
def image_text8(*args)
|
281
|
+
write_request(X11::Form::ImageText8.new(*args))
|
282
|
+
end
|
283
|
+
|
284
|
+
def image_text16(*args)
|
285
|
+
write_request(X11::Form::ImageText16.new(*args))
|
286
|
+
end
|
287
|
+
|
288
|
+
def poly_fill_rectangle(*args)
|
289
|
+
write_request(X11::Form::PolyFillRectangle.new(*args))
|
290
|
+
end
|
291
|
+
|
292
|
+
def create_pixmap(depth, drawable, w,h)
|
293
|
+
pid = new_id
|
294
|
+
write_request(X11::Form::CreatePixmap.new(depth, pid, drawable, w,h))
|
295
|
+
pid
|
296
|
+
end
|
297
|
+
|
298
|
+
# XRender
|
299
|
+
|
300
|
+
def render_opcode
|
301
|
+
return @render_opcode if @render_opcode
|
302
|
+
@render_opcode = major_opcode("RENDER")
|
303
|
+
if @render_opcode
|
304
|
+
@render_version = write_sync(X11::Form::XRenderQueryVersion.new(
|
305
|
+
@render_opcode,0,11),
|
306
|
+
X11::Form::XRenderQueryVersionReply
|
307
|
+
)
|
308
|
+
end
|
309
|
+
@render_opcode
|
310
|
+
end
|
311
|
+
|
312
|
+
def render_create_picture(drawable, format, vmask=0, vlist=[])
|
313
|
+
pid = new_id
|
314
|
+
write_request(X11::Form::XRenderCreatePicture.new(
|
315
|
+
render_opcode, pid, drawable, format, vmask, vlist))
|
316
|
+
pid
|
317
|
+
end
|
318
|
+
|
319
|
+
def render_query_pict_formats
|
320
|
+
@render_formats ||= write_sync(
|
321
|
+
X11::Form::XRenderQueryPictFormats.new(render_opcode),
|
322
|
+
X11::Form::XRenderQueryPictFormatsReply
|
323
|
+
)
|
324
|
+
end
|
325
|
+
|
326
|
+
def render_find_visual_format(visual)
|
327
|
+
# FIXME.
|
328
|
+
render_query_pict_formats.screens.map do |s|
|
329
|
+
s.depths.map do |d|
|
330
|
+
d.visuals.map {|v| v.visual == visual ? v : nil }
|
331
|
+
end
|
332
|
+
end.flatten.compact.first.format
|
333
|
+
end
|
334
|
+
|
335
|
+
def render_find_standard_format(sym)
|
336
|
+
# A pox be on the people who made this necessary
|
337
|
+
|
338
|
+
formats = render_query_pict_formats
|
339
|
+
|
340
|
+
case sym
|
341
|
+
when :a8
|
342
|
+
@a8 ||= formats.formats.find do |f|
|
343
|
+
f.type == 1 &&
|
344
|
+
f.depth == 8 &&
|
345
|
+
f.direct.alpha_mask == 255
|
346
|
+
end
|
347
|
+
when :rgb24
|
348
|
+
@rgb24 ||= formats.formats.find do |f|
|
349
|
+
f.type == 1 &&
|
350
|
+
f.depth == 24 &&
|
351
|
+
f.direct.red == 16 &&
|
352
|
+
f.direct.green == 8 &&
|
353
|
+
f.direct.blue == 0
|
354
|
+
end
|
355
|
+
when :argb24
|
356
|
+
@argb24 ||= formats.formats.find do |f|
|
357
|
+
f.type == 1 &&
|
358
|
+
f.depth == 32 &&
|
359
|
+
f.direct.alpha == 24 &&
|
360
|
+
f.direct.red == 16 &&
|
361
|
+
f.direct.green == 8 &&
|
362
|
+
f.direct.blue == 0
|
363
|
+
end
|
364
|
+
else
|
365
|
+
raise "Unsupported format (a4/a1 by omission)"
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
def render_create_glyph_set(format)
|
370
|
+
glyphset = new_id
|
371
|
+
write_request(X11::Form::XRenderCreateGlyphSet.new(
|
372
|
+
major_opcode("RENDER"),glyphset, format))
|
373
|
+
glyphset
|
374
|
+
end
|
375
|
+
|
376
|
+
def render_add_glyphs(glyphset, glyphids, glyphinfos, data)
|
377
|
+
write_request(X11::Form::XRenderAddGlyphs.new(render_opcode,
|
378
|
+
glyphset, Array(glyphids), Array(glyphinfos), data))
|
379
|
+
end
|
380
|
+
|
381
|
+
def render_fill_rectangles(op, dst, color, rects)
|
382
|
+
color = Form::XRenderColor.new(*color) if color.is_a?(Array)
|
383
|
+
rects = rects.map{|r| r.is_a?(Array) ? Form::Rectangle.new(*r) : r}
|
384
|
+
write_request(Form::XRenderFillRectangles.new(render_opcode, op, dst, color, rects))
|
385
|
+
end
|
386
|
+
|
387
|
+
def render_composite_glyphs32(op, src, dst, fmt, glyphset, srcx,srcy, *elts)
|
388
|
+
write_request(X11::Form::XRenderCompositeGlyphs32.new(
|
389
|
+
render_opcode,
|
390
|
+
op, src, dst, fmt,
|
391
|
+
glyphset,
|
392
|
+
srcx, srcy,
|
393
|
+
elts.map {|e| e.is_a?(Array) ? Form::GlyphElt32.new(*e) : e }
|
394
|
+
))
|
395
|
+
end
|
396
|
+
|
397
|
+
def render_create_solid_fill(*color)
|
398
|
+
if color.length == 1 && color.is_a?(Form::XRenderColor)
|
399
|
+
color = color[0]
|
400
|
+
else
|
401
|
+
color = Form::XRenderColor.new(*color)
|
402
|
+
end
|
403
|
+
fill = new_id
|
404
|
+
write_request(Form::XRenderCreateSolidFill.new(render_opcode,fill,color))
|
405
|
+
fill
|
406
|
+
end
|
407
|
+
|
408
|
+
private
|
409
|
+
|
410
|
+
def authorize(host, family, display_id)
|
411
|
+
auth = Auth.new
|
412
|
+
auth_info = auth.get_by_hostname(host||"localhost", family, display_id)
|
413
|
+
|
414
|
+
auth_name, auth_data = auth_info.auth_name, auth_info.auth_data
|
415
|
+
p [auth_name, auth_data]
|
416
|
+
|
417
|
+
handshake = Form::ClientHandshake.new(
|
418
|
+
Protocol::BYTE_ORDER,
|
419
|
+
Protocol::MAJOR,
|
420
|
+
Protocol::MINOR,
|
421
|
+
auth_name,
|
422
|
+
auth_data
|
423
|
+
)
|
424
|
+
|
425
|
+
@socket.write(handshake.to_packet)
|
426
|
+
|
427
|
+
data = @socket.read(1)
|
428
|
+
raise AuthorizationError, "Failed to read response from server" if !data
|
429
|
+
|
430
|
+
case data.unpack("w").first
|
431
|
+
when X11::Auth::FAILED
|
432
|
+
len, major, minor, xlen = @socket.read(7).unpack("CSSS")
|
433
|
+
reason = @socket.read(xlen * 4)
|
434
|
+
reason = reason[0..len]
|
435
|
+
raise AuthorizationError, "Connection to server failed -- (version #{major}.#{minor}) #{reason}"
|
436
|
+
when X11::Auth::AUTHENTICATE
|
437
|
+
raise AuthorizationError, "Connection requires authentication"
|
438
|
+
when X11::Auth::SUCCESS
|
439
|
+
@socket.read(7) # skip unused bytes
|
440
|
+
@internal = Form::DisplayInfo.from_packet(@socket)
|
441
|
+
else
|
442
|
+
raise AuthorizationError, "Received unknown opcode #{type}"
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
def to_s
|
447
|
+
"#<X11::Display:0x#{object_id.to_s(16)} screens=#{@internal.screens.size}>"
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
data/lib/X11/event.rb
ADDED
File without changes
|