pure-x11 0.0.5 → 0.0.7

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 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