pure-x11 0.0.5 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ecd289a7ec4a0a7f44bba1d05a437774ca6ed3d4c403b572b28a8691ef2a950
4
- data.tar.gz: ac9460f0c0db0635e33358550176802119db88c9bb4dee097807f21c970f68ba
3
+ metadata.gz: 37b5a813f478de65ecb12e89bafd332ef219baabac729a1baf29cf016d055347
4
+ data.tar.gz: 357ef8cd392bad30e9c2d8a7b6771c5c4fd1dd68f3e77da1544cf93f22df14f0
5
5
  SHA512:
6
- metadata.gz: db23062260daabe75bfe5a9f419772be329023777c405ae7bb3a08125b9942f63ec8d635c1c11869648272d2c3ecad16a432fbca2d4d584d80ad2dbf15250d5a
7
- data.tar.gz: f303d01e2c4b43fa2c5d44af4e469288684887c3efe71ee644275f233d584b9e342f9ebd80374632afe1199ad0d4182cebb515a95a7408b79aa36d239c9c9ffb
6
+ metadata.gz: 81d50a93f9df23fe424f4dd62fe10fa09f840ab16eccb5de4d83b0d2f681b3cdb8fc9416e2b818f230399276974cac7455f38bfa1fe66c55968f70fc5c1661bb
7
+ data.tar.gz: 00bc7b7851c843a92988c23feacba461e6b9e1c2ef1f27b025c363f452f759492a30d4c1ff5d8117ba6bb4940eaebb19234c5fef9012d6cf8339a2ee143bf875
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
+ vendor/
2
+
1
3
  # rcov generated
2
4
  coverage
3
5
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pure-x11 (0.0.4)
4
+ pure-x11 (0.0.6)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -0,0 +1,13 @@
1
+
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'X11'
5
+
6
+ display = X11::Display.new
7
+
8
+ display.client_message(
9
+ mask: X11::Form::SubstructureNotifyMask | X11::Form::SubstructureRedirectMask,
10
+ type: :_NET_CURRENT_DESKTOP,
11
+ data: ARGV.shift.to_i
12
+ )
13
+
data/example/test.rb CHANGED
@@ -143,7 +143,7 @@ def render_glyph(display, wid, x,y, ch)
143
143
  #p img
144
144
  # p ch
145
145
  display.put_image(
146
- X11::Form::ZPixmap, wid, $gc2,
146
+ :ZPixmap, wid, $gc2,
147
147
  mtx.min_width,mtx.min_height,
148
148
  x, y - mtx.y_offset, 0, depth, data
149
149
  )
@@ -158,12 +158,12 @@ def render_str(display, wid, x,y, str)
158
158
  end
159
159
 
160
160
  def redraw(dpy, wid, gc)
161
- dpy.poly_fill_rectangle(wid, gc, [X11::Form::Rectangle.new(20,20, 60, 80)])
161
+ dpy.poly_fill_rectangle(wid, gc, [20,20, 60, 80])
162
162
  dpy.clear_area(false, wid, 30, 30, 5, 5)
163
163
  dpy.image_text16(wid, $gc2, 30, 70, "ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ")
164
164
  #"\u25f0\u25ef Hello World")
165
165
  dpy.put_image(
166
- X11::Form::ZPixmap, wid, $gc2,
166
+ :ZPixmap, wid, $gc2,
167
167
  $png.width, $png.height, 80, 120, 0, 24, $data
168
168
  )
169
169
  render_str(dpy, wid, 30,90, 'HelloWorld')
data/lib/X11/auth.rb CHANGED
@@ -11,14 +11,14 @@ module X11
11
11
  AUTHENTICATE = 2
12
12
 
13
13
  ADDRESS_TYPES = {
14
- 256 => :Local,
14
+ 0 => :Internet,
15
+ 1 => :DECnet,
16
+ 2 => :Chaos,
17
+ 252 => :LocalHost,
18
+ 253 => :Krb5Principal,
19
+ 254 => :Netname,
20
+ 256 => :Local,
15
21
  65535 => :Wild,
16
- 254 => :Netname,
17
- 253 => :Krb5Principal,
18
- 252 => :LocalHost,
19
- 0 => :Internet,
20
- 1 => :DECnet,
21
- 2 => :Chaos
22
22
  }
23
23
 
24
24
  AuthInfo = Struct.new :family, :address, :display, :auth_name, :auth_data
@@ -52,18 +52,15 @@ module X11
52
52
 
53
53
  # returns one entry from Xauthority file
54
54
  def read
55
- auth_info = [] << ADDRESS_TYPES[ @file.read(2).unpack('n').first ]
55
+ auth_info = [] << ADDRESS_TYPES[ @file.read(2).unpack1('n') ]
56
56
 
57
57
  4.times do
58
- length = @file.read(2).unpack('n').first
58
+ length = @file.read(2).unpack1('n')
59
59
  auth_info << @file.read(length)
60
60
  end
61
61
  AuthInfo[*auth_info]
62
62
  end
63
63
 
64
- def reset
65
- @file.seek(0, IO::SEEK_SET)
66
- end
67
-
64
+ def reset = @file.seek(0, IO::SEEK_SET)
68
65
  end
69
66
  end
data/lib/X11/display.rb CHANGED
@@ -17,6 +17,8 @@ module X11
17
17
  host, display_id, screen_id = $1, $2, $3
18
18
  family = nil
19
19
 
20
+ @debug = ENV["PUREX_DEBUG"].to_s.strip == "true"
21
+
20
22
  if host.empty?
21
23
  @socket = UNIXSocket.new("/tmp/.X11-unix/X#{display_id}")
22
24
  family = :Local
@@ -26,22 +28,27 @@ module X11
26
28
  family = :Internet
27
29
  end
28
30
 
29
- # FIXME
30
- authorize(host, family, display_id) rescue nil
31
+ authorize(host, family, display_id)
31
32
 
32
33
  @requestseq = 1
33
- @queue = []
34
-
35
- @extensions = {}
34
+ @rqueue = Queue.new # Read but not returned events
35
+ @wqueue = Queue.new
36
+ @extensions = {} # Known extensions
37
+ @atoms = {} # Interned atoms
36
38
 
37
- # Interned atoms
38
- @atoms = {}
39
+ start_io
39
40
  end
40
41
 
41
42
  def event_handler= block
42
43
  @event_handler= block
43
44
  end
44
45
 
46
+ def flush
47
+ while !@wqueue.empty?
48
+ sleep(0.01)
49
+ end
50
+ end
51
+
45
52
  def display_info
46
53
  @internal
47
54
  end
@@ -67,8 +74,15 @@ module X11
67
74
 
68
75
  def read_error data
69
76
  error = Form::Error.from_packet(StringIO.new(data))
77
+ # FIXME: Maybe make this configurable, as it means potentially
78
+ # keeping along really heavy requests. or alternative purge them
79
+ # more aggressively also when there are no errors, as otherwise
80
+ # the growth might be unbounded
81
+ error.request = @requests[error.sequence_number]
82
+ @requests.keys.find_all{|s| s <= error.sequence_number}.each do |s|
83
+ @requests.delete(s)
84
+ end
70
85
  STDERR.puts "ERROR: #{error.inspect}"
71
- raise error.inspect
72
86
  error
73
87
  end
74
88
 
@@ -116,7 +130,7 @@ module X11
116
130
  # FIXME: 30: SelectionRequest
117
131
  # FIXME: 31: SelectionNotify
118
132
  # FIXME: 32: ColormapNotify
119
- when 33 then return Form::ClientMessage.from_packet(StringIO.new(data))
133
+ when 33 then return Form::ClientMessage.from_packet(io)
120
134
  # FIXME: 34: MappingNotify
121
135
  else
122
136
  STDERR.puts "FIXME: Event: #{type}"
@@ -126,22 +140,24 @@ module X11
126
140
  end
127
141
 
128
142
  def read_full_packet(len = 32)
129
- data = @socket.read_nonblock(32)
143
+ data = @socket.read(32)
130
144
  return nil if data.nil?
131
145
  while data.length < 32
132
146
  IO.select([@socket],nil,nil,0.001)
133
147
  data.concat(@socket.read_nonblock(32 - data.length))
134
148
  end
135
149
  return data
136
- rescue IO::WaitReadable
137
- return nil
138
150
  end
139
151
 
140
- def read_packet timeout=5.0
141
- IO.select([@socket],nil,nil, timeout)
152
+ def read_packet
142
153
  data = read_full_packet(32)
143
154
  return nil if data.nil?
144
155
 
156
+ # FIXME: Make it configurable.
157
+ @requests.keys.find_all{|s| s <= @requestseq - 50}.each do |s|
158
+ @requests.delete(s)
159
+ end
160
+
145
161
  # FIXME: What is bit 8 for? Synthentic?
146
162
  type = data.unpack("C").first & 0x7f
147
163
  case type
@@ -153,61 +169,100 @@ module X11
153
169
  end
154
170
  end
155
171
 
156
- def write_raw_packet(pkt)
157
- @requestseq += 1
158
- @socket.write(pkt)
159
- end
160
-
161
172
  def write_packet(*args)
162
173
  pkt = args.join
163
174
  pkt[2..3] = u16(pkt.length/4)
164
- write_raw_packet(pkt)
175
+ @wqueue << [nil,nil,pkt]
165
176
  end
166
177
 
167
178
  def write_request ob
168
- #p data
169
- #p [:write_request, @requestseq, ob.class]
170
- data = ob.to_packet if ob.respond_to?(:to_packet)
171
- #p [:AddGlyph,data] if ob.is_a?(X11::Form::XRenderAddGlyphs)
172
- #p [ob.request_length.to_i*4, data.size]
179
+ data = ob.to_packet(self) if ob.respond_to?(:to_packet)
173
180
  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
174
- write_raw_packet(data)
175
- end
176
-
177
- def write_sync(data, reply=nil)
178
- seq = @requestseq
179
- write_request(data)
180
- pkt = next_reply(seq)
181
- return nil if !pkt
182
- return pkt if pkt.is_a?(X11::Form::Error)
183
- pp reply
181
+ STDERR.puts "write_req: #{ob.inspect}" if @debug
182
+ @wqueue << [ob,nil,data]
183
+ end
184
+
185
+ def write_sync(ob, reply=nil)
186
+ data = ob.to_packet(self) if ob.respond_to?(:to_packet)
187
+ q = Queue.new
188
+ @wqueue << [ob,q,data]
189
+ STDERR.puts "write_sync_req: #{ob.inspect}" if @debug
190
+ pkt = q.shift
191
+ STDERR.puts "write_sync_rep: #{pkt.inspect}" if @debug
192
+ raise(pkt) if pkt.is_a?(X11::Form::Error)
193
+ return pkt if !pkt.is_a?(String)
184
194
  reply ? reply.from_packet(StringIO.new(pkt)) : pkt
185
195
  end
186
196
 
187
- def peek_packet
188
- !@queue.empty?
189
- end
197
+ def peek_packet = !@rqueue.empty?
198
+ def next_packet = @rqueue.shift
190
199
 
191
- def next_packet
192
- @queue.shift || read_packet
193
- end
200
+ def close = @rqueue.close
201
+
202
+ def start_io
203
+ @replies ||= {}
204
+ @requests ||= {}
205
+ # Read thread.
206
+ # FIXME: Drop the select.
207
+ rt = Thread.new do
208
+ while pkt = read_packet
209
+ #STDERR.puts "read: #{pkt.inspect}"
210
+ if !pkt
211
+ sleep 0.1
212
+ elsif pkt.is_a?(String)
213
+ # This is a reply. We need the sequence number.
214
+ #
215
+ seq = pkt.unpack1("@2S")
216
+ STDERR.puts " - seq= #{seq}" if @debug
217
+ STDERR.puts @replies.inspect if @debug
218
+ if @replies[seq]
219
+ q = @replies.delete(seq)
220
+ STDERR.puts " - reply to #{q}" if @debug
221
+ q << pkt
222
+ end
223
+ elsif pkt.is_a?(X11::Form::Error)
224
+ if @replies[pkt.sequence_number]
225
+ q = @replies.delete(pkt.sequence_number)
226
+ q << pkt
227
+ else
228
+ @rqueue << pkt
229
+ end
230
+ else
231
+ @rqueue << pkt
232
+ end
233
+ end
234
+ @rqueue.close
235
+ @replies.values.each(&:close)
236
+ end
194
237
 
195
- def next_reply(errseq)
196
- # FIXME: This is totally broken
197
- while pkt = read_packet
198
- if pkt.is_a?(String)
199
- return pkt
200
- elsif pkt.is_a?(X11::Form::Error) && pkt.sequence_number == errseq
201
- return pkt
202
- else
203
- @queue.push(pkt)
238
+ # Write thread
239
+ wt = Thread.new do
240
+ while msg = @wqueue.shift
241
+ ob, q, data = *msg
242
+ @requests[@requestseq] = ob
243
+ @replies[@requestseq] = q if q
244
+ @requestseq = (@requestseq + 1) % 65536
245
+ @socket.write(data)
204
246
  end
205
247
  end
248
+
249
+ at_exit do
250
+ flush
251
+ @rqueue.close
252
+ @wqueue.close
253
+ # We kill this because it may be stuck in a read
254
+ # we'll never care about
255
+ Thread.kill(rt)
256
+
257
+ # We wait for this to finish because otherwise we may
258
+ # lose side-effects
259
+ wt.join
260
+ end
206
261
  end
207
-
262
+
208
263
  def run
209
264
  loop do
210
- pkt = read_packet
265
+ pkt = next_packet
211
266
  return if !pkt
212
267
  yield(pkt)
213
268
  end
@@ -218,13 +273,15 @@ module X11
218
273
  d.depth == depth }.visuals.find{|v| v.qlass = qlass }
219
274
  end
220
275
 
276
+ def default_root = screens.first.root
277
+
221
278
  # Requests
222
279
  def create_window(x,y,w,h,
223
280
  values: {},
224
281
  depth: 32, parent: nil, border_width: 0, wclass: X11::Form::InputOutput, visual: nil
225
282
  )
226
283
  wid = new_id
227
- parent ||= screens.first.root
284
+ parent ||= default_root
228
285
 
229
286
  if visual.nil?
230
287
  visual = find_visual(0, depth).visual_id
@@ -252,24 +309,27 @@ module X11
252
309
  values = values.sort_by{_1[0]}
253
310
  mask = values.inject(0) {|acc,v| (acc | v[0]) }
254
311
  values = values.map{_1[1]}
255
- write_request(
256
- X11::Form::ChangeWindowAttributes.new(wid, mask, values)
257
- )
312
+ write_request(Form::ChangeWindowAttributes.new(wid, mask, values))
258
313
  end
259
314
 
260
315
  def select_input(w, events) = change_window_attributes(w, values: {Form::CWEventMask => events})
261
316
 
262
317
  def atom(name)
318
+ return name if name.is_a?(Integer) # Allow atom(atom_integer_or_symbol)
319
+ begin
320
+ return Form::Atoms.const_get(name.to_sym) if Form::Atoms.const_defined?(name.to_sym)
321
+ rescue
322
+ # const_defined? will throw if name isn't a valid constant name, but
323
+ # that's fine
324
+ end
263
325
  name = name.to_sym
264
326
  intern_atom(false, name) if !@atoms[name]
265
327
  @atoms[name]
266
328
  end
267
329
 
268
330
  def query_extension(name)
269
- r = write_sync(X11::Form::QueryExtension.new(name), X11::Form::QueryExtensionReply)
270
- @extensions[name] = {
271
- major: r.major_opcode
272
- }
331
+ r = write_sync(Form::QueryExtension.new(name), Form::QueryExtensionReply)
332
+ @extensions[name] = { major: r.major_opcode }
273
333
  r
274
334
  end
275
335
 
@@ -282,38 +342,33 @@ module X11
282
342
  end
283
343
 
284
344
  def intern_atom(flag, name)
285
- reply = write_sync(X11::Form::InternAtom.new(flag, name.to_s),
286
- X11::Form::InternAtomReply)
345
+ reply = write_sync(Form::InternAtom.new(flag, name.to_s),Form::InternAtomReply)
287
346
  if reply
288
347
  @atoms[name.to_sym] = reply.atom
289
348
  end
290
349
  end
291
350
 
292
- def get_atom_name(atom)
293
- reply = write_sync(X11::Form::GetAtomName.new(atom), X11::Form::AtomName)
294
- reply&.name
295
- end
296
-
297
- def destroy_window(window) = write_request(X11::Form::DestroyWindow.new(window))
298
- def get_geometry(drawable) = write_sync(X11::Form::GetGeometry.new(drawable), X11::Form::Geometry)
351
+ def get_atom_name(atom) = write_sync(Form::GetAtomName.new(atom), Form::AtomName)&.name
352
+ def destroy_window(window) = write_request(Form::DestroyWindow.new(window))
353
+ def get_geometry(drawable) = write_sync(Form::GetGeometry.new(drawable), Form::Geometry)
299
354
 
300
355
  def get_keyboard_mapping(min_keycode=display_info.min_keycode, count= display_info.max_keycode - min_keycode)
301
- write_sync(X11::Form::GetKeyboardMapping.new(min_keycode, count), X11::Form::GetKeyboardMappingReply)
356
+ write_sync(Form::GetKeyboardMapping.new(min_keycode, count), Form::GetKeyboardMappingReply)
302
357
  end
303
358
 
304
359
  def create_colormap(alloc, window, visual)
305
360
  mid = new_id
306
- write_request(X11::Form::CreateColormap.new(alloc, mid, window, visual))
361
+ write_request(Form::CreateColormap.new(alloc, mid, window, visual))
307
362
  mid
308
363
  end
309
364
 
310
365
  def get_property(window, property, type, offset: 0, length: 4, delete: false)
311
- property = atom(property) if !property.is_a?(Integer)
366
+ property = atom(property)
312
367
  type = atom_enum(type)
313
368
 
314
- result = write_sync(X11::Form::GetProperty.new(
369
+ result = write_sync(Form::GetProperty.new(
315
370
  delete, window, property, type, offset, length
316
- ), X11::Form::Property)
371
+ ), Form::Property)
317
372
 
318
373
  if result && result.format != 0
319
374
  case result.format
@@ -335,30 +390,30 @@ module X11
335
390
 
336
391
  mode = open_enum(mode, {replace: 0, prepend: 1, append: 2})
337
392
  type = atom_enum(type)
338
- write_request(X11::Form::ChangeProperty.new(mode, window, property, type, format, data))
339
- end
340
-
341
- def list_fonts(*args)
342
- write_sync(X11::Form::ListFonts.new(*args),
343
- X11::Form::ListFontsReply)
393
+ write_request(Form::ChangeProperty.new(mode, window, property, type, format, data))
344
394
  end
345
395
 
346
- def open_font(*args) = write_request(X11::Form::OpenFont.new(*args))
347
- def change_gc(*args) = write_request(X11::Form::ChangeGC.new(*args))
348
- def change_save_set(...)= write_request(X11::Form::ChangeSaveSet.new(...))
396
+ def list_fonts(...) = write_sync(Form::ListFonts.new(...), Form::ListFontsReply)
397
+ def open_font(...) = write_request(Form::OpenFont.new(...))
398
+ def change_gc(...) = write_request(Form::ChangeGC.new(...))
399
+ def change_save_set(...)= write_request(Form::ChangeSaveSet.new(...))
400
+
349
401
  def reparent_window(window, parent, x, y, save: true)
350
402
  # You so almost always want this that it should've been a single request
351
403
  change_save_set(0, window) if save
352
- write_request(X11::Form::ReparentWindow.new(window, parent, x,y))
404
+ write_request(Form::ReparentWindow.new(window, parent, x,y))
353
405
  end
354
406
 
355
- def map_window(*args) = write_request(X11::Form::MapWindow.new(*args))
356
- def unmap_window(*args) = write_request(X11::Form::UnmapWindow.new(*args))
407
+ def map_window(...) = write_request(Form::MapWindow.new(...))
408
+ def unmap_window(...) = write_request(Form::UnmapWindow.new(...))
357
409
 
358
410
  def u8(*args) = args.pack("c*")
359
411
  def u16(*args) = args.pack("v*")
360
412
  def u32(*args) = args.pack("V*")
361
- def atom_enum(val) = open_enum(val, {cardinal: Form::CardinalAtom, atom: Form::AtomAtom, window: Form::WindowAtom})
413
+ def atom_enum(val)
414
+ open_enum(val, {cardinal: Form::CardinalAtom, atom: Form::AtomAtom, window: Form::WindowAtom}) || atom(val)
415
+ end
416
+
362
417
  def window(*args)
363
418
  args.each {|a| raise "Window expected" if a.nil? }
364
419
  u32(*args)
@@ -369,15 +424,15 @@ module X11
369
424
  def set_input_focus(revert_to, focus, time=:now)
370
425
  # FIXME: This is an experiment.
371
426
  # Upside: Simpler. Downside: Doesn't work server-side.
372
- #
427
+ # Probably a bad idea.
373
428
  revert_to = open_enum(revert_to, {none: 0, pointer_root: 1, parent: 2})
374
429
  focus = open_enum(focus, {none: 0, pointer_root: 1 })
375
430
  time = open_enum(time, {current_time: 0, now: 0})
376
431
  write_packet(u8(42,revert_to), u16(3), window(focus), u32(time))
377
432
  end
378
-
433
+
379
434
  def grab_key(owner_events, grab_window, modifiers, keycode, pointer_mode, keyboard_mode)
380
- write_request(X11::Form::GrabKey.new(
435
+ write_request(Form::GrabKey.new(
381
436
  owner_events,
382
437
  grab_window,
383
438
  modifiers,
@@ -389,7 +444,7 @@ module X11
389
444
 
390
445
  def grab_button(owner_events, grab_window, event_mask, pointer_mode,
391
446
  keyboard_mode, confine_to, cursor, button, modifiers)
392
- write_request(X11::Form::GrabButton.new(
447
+ write_request(Form::GrabButton.new(
393
448
  owner_events, grab_window, event_mask,
394
449
  pointer_mode == :async ? 1 : 0,
395
450
  keyboard_mode == :async ? 1 : 0,
@@ -397,41 +452,26 @@ module X11
397
452
  )
398
453
  end
399
454
 
400
- def configure_window(window, x: nil, y: nil, width: nil, height: nil,
401
- border_width: nil, sibling: nil, stack_mode: nil)
402
-
403
- mask = 0
404
- values = []
405
-
455
+ def set_value(values, mask, x)
406
456
  if x
407
- mask |= 0x001
408
457
  values << x
458
+ mask
459
+ else
460
+ 0
409
461
  end
462
+ end
463
+
464
+ def configure_window(window, x: nil, y: nil, width: nil, height: nil,
465
+ border_width: nil, sibling: nil, stack_mode: nil)
410
466
 
411
- if y
412
- mask |= 0x002
413
- values << y
414
- end
415
-
416
- if width
417
- mask |= 0x004
418
- values << width
419
- end
420
-
421
- if height
422
- mask |= 0x008
423
- values << height
424
- end
425
-
426
- if border_width
427
- mask |= 0x010
428
- values << border_width
429
- end
430
-
431
- if sibling
432
- mask |= 0x020
433
- values << sibling
434
- end
467
+ values = []
468
+ mask = 0
469
+ mask |= set_value(values, 0x001, x)
470
+ mask |= set_value(values, 0x002, y)
471
+ mask |= set_value(values, 0x004, width)
472
+ mask |= set_value(values, 0x008, height)
473
+ mask |= set_value(values, 0x010, border_width)
474
+ mask |= set_value(values, 0x020, sibling)
435
475
 
436
476
  if stack_mode
437
477
  mask |= 0x040
@@ -448,39 +488,50 @@ module X11
448
488
  end
449
489
 
450
490
 
451
- def create_gc(window, foreground: nil, background: nil)
491
+ def create_gc(window, foreground: nil, background: nil,
492
+ graphics_exposures: nil
493
+ )
452
494
  mask = 0
453
495
  args = []
454
496
 
455
497
  # FIXME:
456
498
  # The rest can be found here:
457
499
  # https://tronche.com/gui/x/xlib/GC/manipulating.html#XGCValues
458
- if foreground
459
- mask |= 0x04
460
- args << foreground
461
- end
462
- if background
463
- mask |= 0x08
464
- args << background
465
- end
466
-
500
+ mask |= set_value(args, 0x04, foreground)
501
+ mask |= set_value(args, 0x08, background)
502
+ mask |= set_value(args, 0x10000, graphics_exposures)
467
503
 
468
504
  gc = new_id
469
505
  write_request(X11::Form::CreateGC.new(gc, window, mask, args))
470
506
  gc
471
507
  end
472
508
 
509
+ def send_event(...) = write_request(Form::SendEvent.new(...))
510
+ def client_message(window: default_root, type: :ClientMessage, format: 32, destination: default_root, mask: 0, data: [], propagate: true)
511
+ f = {8 => "C20", 16 => "S10", 32 => "L5"}[format]
512
+ p f
513
+ data = (Array(data).map{|item|atom(item)} +[0]*20).pack(f)
514
+ event = Form::ClientMessage.new(
515
+ format, 0, window, atom(type), data
516
+ )
517
+ event.code =33
518
+ pp event
519
+
520
+ send_event(propagate, destination, mask, event)
521
+ end
522
+
473
523
  def put_image(*args) = write_request(X11::Form::PutImage.new(*args))
474
524
  def clear_area(*args) = write_request(X11::Form::ClearArea.new(*args))
475
525
  def copy_area(*args) = write_request(X11::Form::CopyArea.new(*args))
476
526
  def image_text8(*args) = write_request(X11::Form::ImageText8.new(*args))
477
527
  def image_text16(*args)= write_request(X11::Form::ImageText16.new(*args))
478
- def poly_fill_rectangle(*args) = write_request(X11::Form::PolyFillRectangle.new(*args))
528
+ def poly_fill_rectangle(wid, gc, *rects)
529
+ rects = rects.map{|r| r.is_a?(Array) ? Form::Rectangle.new(*r) : r}
530
+ write_request(X11::Form::PolyFillRectangle.new(wid, gc, rects))
531
+ end
479
532
 
480
533
  def create_pixmap(depth, drawable, w,h)
481
- pid = new_id
482
- write_request(X11::Form::CreatePixmap.new(depth, pid, drawable, w,h))
483
- pid
534
+ new_id.tap{|pid| write_request(Form::CreatePixmap.new(depth, pid, drawable, w,h)) }
484
535
  end
485
536
 
486
537
  # XRender
@@ -506,8 +557,8 @@ module X11
506
557
 
507
558
  def render_query_pict_formats
508
559
  @render_formats ||= write_sync(
509
- X11::Form::XRenderQueryPictFormats.new(render_opcode),
510
- X11::Form::XRenderQueryPictFormatsReply
560
+ Form::XRenderQueryPictFormats.new(render_opcode),
561
+ Form::XRenderQueryPictFormatsReply
511
562
  )
512
563
  end
513
564
 
@@ -528,26 +579,26 @@ module X11
528
579
  case sym
529
580
  when :a8
530
581
  @a8 ||= formats.formats.find do |f|
531
- f.type == 1 &&
582
+ f.type == 1 &&
532
583
  f.depth == 8 &&
533
584
  f.direct.alpha_mask == 255
534
585
  end
535
586
  when :rgb24
536
587
  @rgb24 ||= formats.formats.find do |f|
537
- f.type == 1 &&
538
- f.depth == 24 &&
539
- f.direct.red == 16 &&
540
- f.direct.green == 8 &&
541
- f.direct.blue == 0
588
+ f.type == 1 &&
589
+ f.depth == 24 &&
590
+ f.direct.red == 16 &&
591
+ f.direct.green == 8 &&
592
+ f.direct.blue == 0
542
593
  end
543
594
  when :argb24
544
595
  @argb24 ||= formats.formats.find do |f|
545
- f.type == 1 &&
546
- f.depth == 32 &&
596
+ f.type == 1 &&
597
+ f.depth == 32 &&
547
598
  f.direct.alpha == 24 &&
548
- f.direct.red == 16 &&
549
- f.direct.green == 8 &&
550
- f.direct.blue == 0
599
+ f.direct.red == 16 &&
600
+ f.direct.green == 8 &&
601
+ f.direct.blue == 0
551
602
  end
552
603
  else
553
604
  raise "Unsupported format (a4/a1 by omission)"
@@ -605,7 +656,6 @@ module X11
605
656
  auth_name = ""
606
657
  auth_data = ""
607
658
  end
608
- p [auth_name, auth_data]
609
659
 
610
660
  handshake = Form::ClientHandshake.new(
611
661
  Protocol::BYTE_ORDER,
@@ -615,7 +665,7 @@ module X11
615
665
  auth_data
616
666
  )
617
667
 
618
- @socket.write(handshake.to_packet)
668
+ @socket.write(handshake.to_packet(self))
619
669
 
620
670
  data = @socket.read(1)
621
671
  raise AuthorizationError, "Failed to read response from server" if !data
data/lib/X11/form.rb CHANGED
@@ -44,7 +44,7 @@ module X11
44
44
  end
45
45
  end
46
46
 
47
- def to_packet
47
+ def to_packet(dpy)
48
48
  # fetch class level instance variable holding defined fields
49
49
  structs = self.class.structs
50
50
 
@@ -64,17 +64,10 @@ module X11
64
64
  #p [s,value]
65
65
 
66
66
  if value.is_a?(BaseForm)
67
- v = value.to_packet
68
- elsif value.is_a?(Symbol)
69
- #if !@atoms[value]
70
- # reply = write_sync(X11::Forms::InternAtom.new(false, value.to_s), X11::Forms::InternAtomReply)
71
- # @
72
- #end
73
- #value = @atoms[value]
74
- raise "FIXME"
67
+ v = value.to_packet(dpy)
75
68
  else
76
69
  #p [s,value]
77
- v = s.type_klass.pack(value)
70
+ v = s.type_klass.pack(value, dpy)
78
71
  end
79
72
  #p v
80
73
  v
@@ -84,15 +77,15 @@ module X11
84
77
  when :length, :format_length
85
78
  #p [s,value]
86
79
  #p [value.size]
87
- s.type_klass.pack(value.size)
80
+ s.type_klass.pack(value.size, dpy)
88
81
  when :string
89
- s.type_klass.pack(value)
82
+ s.type_klass.pack(value, dpy)
90
83
  when :list
91
84
  Array(value).collect do |obj|
92
85
  if obj.is_a?(BaseForm)
93
- obj.to_packet
86
+ obj.to_packet(dpy)
94
87
  else
95
- s.type_klass.pack(obj)
88
+ s.type_klass.pack(obj, dpy)
96
89
  end
97
90
  end
98
91
  end
@@ -191,6 +184,29 @@ module X11
191
184
  end
192
185
  end
193
186
 
187
+ # # Predefined constants, that can be used in the form of symbols
188
+
189
+ module Atoms
190
+ PRIMARY = 1
191
+ SECONDARY = 2
192
+ ARC = 3
193
+ ATOM = 4
194
+ BITMAP = 5
195
+ CARDINAL = 6
196
+ COLORMAP = 7
197
+ CURSOR = 8
198
+ #...
199
+ STRING = 31
200
+ VISUALID = 32
201
+ WINDOW = 33
202
+ WM_COMMAND = 34
203
+ WM_HINTS = 35
204
+ end
205
+
206
+ PointerWindow = 0
207
+ InputFocus = 1
208
+
209
+ # FIXME: Deprecated in favour of the Constants module
194
210
  AtomAtom=4
195
211
  CardinalAtom=6
196
212
  WindowAtom=33
@@ -293,6 +309,9 @@ module X11
293
309
  field :minor_opcode, Uint16
294
310
  field :major_opcode, Uint8
295
311
  unused 21
312
+
313
+ # The original request
314
+ attr_accessor :request
296
315
  end
297
316
 
298
317
  # XRender structures
@@ -336,8 +355,9 @@ module X11
336
355
  field :colormap, Colormap
337
356
  end
338
357
 
339
- # Requests
358
+ # # Requests
340
359
 
360
+ # Constants, p112 onwards
341
361
  CopyFromParent = 0
342
362
  InputOutput = 1
343
363
  InputOnly = 2
@@ -365,12 +385,17 @@ module X11
365
385
  PointerMotionMask = 0x000040
366
386
  PointerMotionHintMask = 0x000080
367
387
  Button1MotionMask = 0x000100
388
+ # 0x200 .. 0x40000; page 113
368
389
  ExposureMask = 0x008000
390
+ VisibilityChangeMask = 0x010000
369
391
  StructureNotifyMask = 0x020000
392
+ ResizeRedirectMask = 0x040000
370
393
  SubstructureNotifyMask = 0x080000
371
394
  SubstructureRedirectMask=0x100000
372
395
  FocusChangeMask = 0x200000
373
396
  PropertyChangeMask = 0x400000
397
+ ColormapChangeMask = 0x800000
398
+ OwnerGrabButtonMask = 0x100000
374
399
 
375
400
  class CreateWindow < BaseForm
376
401
  field :opcode, Uint8, value: 1
@@ -599,6 +624,15 @@ module X11
599
624
  field :value, String8, :string
600
625
  end
601
626
 
627
+ class SendEvent < BaseForm
628
+ field :opcode, Uint8, value: 25
629
+ field :propagate, Bool
630
+ field :request_length, Uint16, value: 11
631
+ field :destination, Window
632
+ field :event_mask, Uint32
633
+ field :event, Uint32 # FIXME: This is wrong, and will break on parsing.
634
+ end
635
+
602
636
  class GrabButton < BaseForm
603
637
  field :opcode, Uint8, value: 28
604
638
  field :owner_events, Bool
@@ -685,6 +719,7 @@ module X11
685
719
  ForegroundMask = 0x04
686
720
  BackgroundMask = 0x08
687
721
  FontMask = 0x4000
722
+ GraphicsExposures = 0x10000
688
723
 
689
724
  class CreateGC < BaseForm
690
725
  field :opcode, Uint8, value: 55
@@ -1009,6 +1044,7 @@ module X11
1009
1044
  unused 1
1010
1045
  field :sequence_number, Uint16
1011
1046
  field :event, Window
1047
+ field :window, Window
1012
1048
  field :above_sibling, Window
1013
1049
  field :x, Int16
1014
1050
  field :y, Int16
data/lib/X11/protocol.rb CHANGED
@@ -1,14 +1,12 @@
1
1
  module X11
2
2
  module Protocol
3
- # endiness of your machine
3
+ # endianess of your machine
4
4
  BYTE_ORDER = case [1].pack("L")
5
- when "\0\0\0\1"
6
- "B".ord
7
- when "\1\0\0\0"
8
- "l".ord
5
+ when "\0\0\0\1" then "B".ord
6
+ when "\1\0\0\0" then "l".ord
9
7
  else
10
8
  raise ByteOrderError.new "Cannot determine byte order"
11
- end
9
+ end
12
10
 
13
11
  MAJOR = 11
14
12
  MINOR = 0
data/lib/X11/screen.rb CHANGED
@@ -7,25 +7,11 @@ module X11
7
7
  @internal = data
8
8
  end
9
9
 
10
- def root
11
- @internal.root
12
- end
13
-
14
- def root_depth
15
- @internal.root_depth
16
- end
17
-
18
- def root_visual
19
- @internal.root_visual
20
- end
21
-
22
- def width
23
- @internal.width_in_pixels
24
- end
25
-
26
- def height
27
- @internal.height_in_pixels
28
- end
10
+ def root = @internal.root
11
+ def root_depth = @internal.root_depth
12
+ def root_visual = @internal.root_visual
13
+ def width = @internal.width_in_pixels
14
+ def height = @internal.height_in_pixels
29
15
 
30
16
  def to_s
31
17
  "#<X11::Screen(#{id}) width=#{width} height=#{height}>"
data/lib/X11/type.rb CHANGED
@@ -1,55 +1,48 @@
1
1
  # This module is used for encoding Ruby Objects to binary
2
2
  # data. The types Int8, Int16, etc. are data-types defined
3
- # in the X11 protocol. We wrap each data-type in a lambda expression
4
- # which gets evaluated when a packet is created.
3
+ # in the X11 protocol.
5
4
 
6
5
  module X11
7
6
  module Type
8
7
 
9
- def self.define(type, directive, bytesize)
10
- eval %{
11
- class X11::Type::#{type}
12
- def self.pack(x)
13
- [x].pack(\"#{directive}\")
14
- end
15
-
16
- def self.unpack(x)
17
- x.unpack(\"#{directive}\").first
18
- end
8
+ class BaseType
9
+ @directive = nil
10
+ @bytesize = nil
19
11
 
20
- def self.size
21
- #{bytesize}
22
- end
23
-
24
- def self.from_packet(sock)
25
- r = sock.read(size)
26
- r ? unpack(r) : nil
12
+ def self.config(d,b) = (@directive, @bytesize = d,b)
13
+
14
+ def self.pack(x, dpy)
15
+ if x.is_a?(Symbol)
16
+ if (t = X11::Form.const_get(x)) && t.is_a?(Numeric)
17
+ x = t
27
18
  end
28
19
  end
29
- }
30
- end
20
+ [x].pack(@directive)
21
+ rescue TypeError => e
22
+ raise "Expected #{self.name}, got #{x.class} (value: #{x})"
23
+ end
31
24
 
32
- # Primitive Types
33
- define "Int8", "c", 1
34
- define "Int16", "s", 2
35
- define "Int32", "l", 4
36
- define "Uint8", "C", 1
37
- define "Uint16", "S", 2
38
- define "Uint32", "L", 4
25
+ def self.unpack(x) = x.nil? ? nil : x.unpack1(@directive)
26
+ def self.size = @bytesize
27
+ def self.from_packet(sock) = unpack(sock.read(size))
28
+ end
29
+
30
+ class Int8 < BaseType; config("c",1); end
31
+ class Int16 < BaseType; config("s",2); end
32
+ class Int32 < BaseType; config("l",4); end
33
+ class Uint8 < BaseType; config("C",1); end
34
+ class Uint16 < BaseType; config("S",2); end
35
+ class Uint32 < BaseType; config("L",4); end
39
36
 
40
- define "Message", "c*", 20
41
-
42
37
  class Message
43
- def self.pack(x) = x.b
44
- def self.unpack(x) = x.b
45
- def self.size = 20
38
+ def self.pack(x,dpy) = x.b
39
+ def self.unpack(x) = x.b
40
+ def self.size = 20
46
41
  def self.from_packet(sock) = sock.read(2).b
47
42
  end
48
43
 
49
44
  class String8
50
- def self.pack(x)
51
- x.force_encoding("ASCII-8BIT") + "\x00"*(-x.length & 3)
52
- end
45
+ def self.pack(x, dpy) = (x.b + "\x00"*(-x.length & 3))
53
46
 
54
47
  def self.unpack(socket, size)
55
48
  raise "Expected size for String8" if size.nil?
@@ -61,8 +54,8 @@ module X11
61
54
  end
62
55
 
63
56
  class String16
64
- def self.pack(x)
65
- x.encode("UTF-16BE").force_encoding("ASCII-8BIT") + "\x00\x00"*(-x.length & 1)
57
+ def self.pack(x, dpy)
58
+ x.encode("UTF-16BE").b + "\x00\x00"*(-x.length & 1)
66
59
  end
67
60
 
68
61
  def self.unpack(socket, size)
@@ -75,28 +68,14 @@ module X11
75
68
 
76
69
 
77
70
  class String8Unpadded
78
- def self.pack(x)
79
- x
80
- end
81
-
82
-
83
- def self.unpack(socket, size)
84
- val = socket.read(size)
85
- end
71
+ def self.pack(x,dpy) = x
72
+ def self.unpack(socket, size) = socket.read(size)
86
73
  end
87
-
74
+
88
75
  class Bool
89
- def self.pack(x)
90
- x ? "\x01" : "\x00"
91
- end
92
-
93
- def self.unpack(str)
94
- str[0] == "\x01"
95
- end
96
-
97
- def self.size
98
- 1
99
- end
76
+ def self.pack(x, dpy) = (x ? "\x01" : "\x00")
77
+ def self.unpack(str) = (str[0] == "\x01")
78
+ def self.size = 1
100
79
  end
101
80
 
102
81
  KeyCode = Uint8
@@ -114,10 +93,16 @@ module X11
114
93
  Colormap = Uint32
115
94
  Drawable = Uint32
116
95
  Fontable = Uint32
117
- Atom = Uint32
118
96
  VisualID = Uint32
119
97
  Mask = Uint32
120
98
  Timestamp = Uint32
121
99
  Keysym = Uint32
100
+
101
+ class Atom
102
+ def self.pack(x,dpy) = [dpy.atom(x)].pack("L")
103
+ def self.unpack(x) = x.nil? ? nil : x.unpack1("L")
104
+ def self.size = 4
105
+ def self.from_packet(sock) = unpack(sock.read(size))
106
+ end
122
107
  end
123
108
  end
data/lib/X11/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module X11
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.7"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pure-x11
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vidar Hokstad
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-12-16 00:00:00.000000000 Z
12
+ date: 2024-01-11 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Pure Ruby X11 bindings
15
15
  email:
@@ -25,6 +25,7 @@ files:
25
25
  - README.md
26
26
  - Rakefile
27
27
  - docs/protocol.pdf
28
+ - example/client_message.rb
28
29
  - example/genie.png
29
30
  - example/test.rb
30
31
  - lib/X11.rb