pure-x11 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|