rbgl 0.1.0 → 1.0.1

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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +20 -2
  4. data/README.md +130 -74
  5. data/Rakefile +7 -0
  6. data/bench/renderer_bench.rb +283 -0
  7. data/docs/.nojekyll +1 -0
  8. data/docs/index.html +38 -0
  9. data/examples/array_test.rb +1 -1
  10. data/examples/color_test.rb +1 -1
  11. data/examples/cube_spinning.rb +1 -1
  12. data/examples/gradient.rb +1 -1
  13. data/examples/multi_return_test.rb +1 -1
  14. data/examples/plasma.rb +1 -1
  15. data/examples/sphere_raymarch.rb +1 -1
  16. data/examples/triangle_window.rb +1 -1
  17. data/exe/rbgl +42 -0
  18. data/lib/rbgl/cli/doctor.rb +155 -0
  19. data/lib/rbgl/engine/buffer.rb +150 -40
  20. data/lib/rbgl/engine/clip_space_clipper.rb +163 -0
  21. data/lib/rbgl/engine/context.rb +133 -63
  22. data/lib/rbgl/engine/framebuffer.rb +153 -48
  23. data/lib/rbgl/engine/immutable_color.rb +32 -0
  24. data/lib/rbgl/engine/pipeline.rb +38 -7
  25. data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +195 -0
  26. data/lib/rbgl/engine/rasterizer/line_renderer.rb +88 -0
  27. data/lib/rbgl/engine/rasterizer/point_renderer.rb +35 -0
  28. data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +136 -0
  29. data/lib/rbgl/engine/rasterizer.rb +65 -171
  30. data/lib/rbgl/engine/shader/base_shader.rb +51 -0
  31. data/lib/rbgl/engine/shader/builtins.rb +257 -0
  32. data/lib/rbgl/engine/shader/dynamic_data.rb +73 -0
  33. data/lib/rbgl/engine/shader.rb +5 -318
  34. data/lib/rbgl/engine/texture.rb +264 -51
  35. data/lib/rbgl/engine.rb +4 -1
  36. data/lib/rbgl/gui/backend.rb +21 -34
  37. data/lib/rbgl/gui/backend_factory.rb +89 -0
  38. data/lib/rbgl/gui/cocoa/backend.rb +59 -46
  39. data/lib/rbgl/gui/cocoa/key_mapper.rb +48 -0
  40. data/lib/rbgl/gui/event.rb +4 -2
  41. data/lib/rbgl/gui/file_backend/bmp_writer.rb +63 -0
  42. data/lib/rbgl/gui/file_backend/frame_writer.rb +35 -0
  43. data/lib/rbgl/gui/file_backend/png_writer.rb +18 -0
  44. data/lib/rbgl/gui/file_backend/ppm_writer.rb +26 -0
  45. data/lib/rbgl/gui/file_backend.rb +50 -55
  46. data/lib/rbgl/gui/wayland/backend.rb +214 -60
  47. data/lib/rbgl/gui/wayland/codec.rb +50 -0
  48. data/lib/rbgl/gui/wayland/connection.rb +188 -231
  49. data/lib/rbgl/gui/wayland/event_dispatcher.rb +165 -0
  50. data/lib/rbgl/gui/wayland/global_binder.rb +46 -0
  51. data/lib/rbgl/gui/wayland/input_mapper.rb +61 -0
  52. data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +63 -0
  53. data/lib/rbgl/gui/wayland/protocol_objects/display.rb +54 -0
  54. data/lib/rbgl/gui/wayland/protocol_objects/seat.rb +115 -0
  55. data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +157 -0
  56. data/lib/rbgl/gui/wayland/protocol_objects/surface.rb +33 -0
  57. data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +60 -0
  58. data/lib/rbgl/gui/wayland/protocol_objects.rb +8 -0
  59. data/lib/rbgl/gui/window/event_dispatcher.rb +30 -0
  60. data/lib/rbgl/gui/window/render_loop.rb +111 -0
  61. data/lib/rbgl/gui/window.rb +74 -84
  62. data/lib/rbgl/gui/x11/atom_cache.rb +29 -0
  63. data/lib/rbgl/gui/x11/backend.rb +73 -49
  64. data/lib/rbgl/gui/x11/connection.rb +284 -219
  65. data/lib/rbgl/gui/x11/event_parser.rb +71 -0
  66. data/lib/rbgl/gui/x11/key_mapper.rb +48 -0
  67. data/lib/rbgl/gui/x11/pixel_encoder.rb +164 -0
  68. data/lib/rbgl/gui/x11/request_encoder.rb +155 -0
  69. data/lib/rbgl/gui/x11/setup_parser.rb +138 -0
  70. data/lib/rbgl/gui/x11/transport.rb +81 -0
  71. data/lib/rbgl/gui/x11/x_authority.rb +107 -0
  72. data/lib/rbgl/gui.rb +3 -5
  73. data/lib/rbgl/version.rb +1 -1
  74. metadata +63 -24
  75. data/examples/chemical_heartbeat.rb +0 -166
  76. data/examples/dark_transit.rb +0 -166
  77. data/examples/fractured_orb.rb +0 -428
  78. data/examples/fractured_orb_rb.rb +0 -598
  79. data/examples/hexagonal_flow.rb +0 -333
  80. data/examples/teapot.rb +0 -362
  81. data/examples/teapot_mcu.rb +0 -344
@@ -1,329 +1,286 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "socket"
4
+ require "io/wait"
5
+ require_relative "protocol_objects"
6
+ require_relative "codec"
7
+ require_relative "event_dispatcher"
8
+ require_relative "global_binder"
4
9
 
5
10
  module RBGL
6
11
  module GUI
7
12
  module Wayland
8
- class WaylandObject
9
- attr_reader :id, :connection
13
+ class Connection
14
+ DEFAULT_ROUNDTRIP_TIMEOUT = 1.0
15
+ ROUNDTRIP_POLL_INTERVAL = 0.01
16
+ READ_CHUNK_SIZE = 16_384
10
17
 
11
- def initialize(connection, id)
12
- @connection = connection
13
- @id = id
14
- end
18
+ attr_reader :compositor, :shm, :xdg_wm_base, :seat, :registry, :globals
15
19
 
16
- def send_request(opcode, *args)
17
- @connection.send_request(@id, opcode, *args)
18
- end
19
- end
20
+ def initialize(env: ENV, socket_path: nil, roundtrip_timeout: DEFAULT_ROUNDTRIP_TIMEOUT)
21
+ @env = env
22
+ @roundtrip_timeout = roundtrip_timeout
23
+ @socket = UNIXSocket.new(resolve_socket_path(socket_path))
24
+ @objects = {}
25
+ @next_id = 2
26
+ @globals = {}
27
+ @pending_events = []
28
+ @received_fds = []
29
+ @read_buffer = String.new(encoding: Encoding::BINARY)
30
+ @closed = false
20
31
 
21
- class Display < WaylandObject
22
- def initialize(connection)
23
- super(connection, 1)
24
- end
32
+ @display = Display.new(self)
33
+ register_object(@display)
25
34
 
26
- def sync
27
- callback_id = @connection.allocate_id
28
- send_request(0, callback_id)
29
- Callback.new(@connection, callback_id)
30
- end
35
+ @registry = @display.get_registry
36
+ flush
37
+ roundtrip
31
38
 
32
- def get_registry
33
- registry_id = @connection.allocate_id
34
- send_request(1, registry_id)
35
- Registry.new(@connection, registry_id)
39
+ bind_globals
40
+ rescue StandardError => error
41
+ begin
42
+ close
43
+ rescue StandardError
44
+ nil
45
+ end
46
+ raise error
36
47
  end
37
- end
38
48
 
39
- class Registry < WaylandObject
40
- def bind(name, interface, version)
41
- new_id = @connection.allocate_id
42
- send_request(0, name, interface, version, new_id)
43
- new_id
49
+ def allocate_id
50
+ id = @next_id
51
+ @next_id += 1
52
+ id
44
53
  end
45
- end
46
54
 
47
- class Callback < WaylandObject
48
- def initialize(connection, id)
49
- super
50
- @done = false
55
+ def register_object(object)
56
+ @objects[object.id] = object
57
+ object
51
58
  end
52
59
 
53
- def done?
54
- @done
60
+ def object_for(object_id)
61
+ @objects[object_id]
55
62
  end
56
63
 
57
- def handle_done
58
- @done = true
64
+ def unregister_object(object_id)
65
+ @objects.delete(object_id)
59
66
  end
60
- end
61
67
 
62
- class Compositor < WaylandObject
63
- def create_surface
64
- surface_id = @connection.allocate_id
65
- send_request(0, surface_id)
66
- Surface.new(@connection, surface_id)
68
+ def store_global(interface, name:, version:)
69
+ @globals[interface] = { name: name, version: version }
67
70
  end
68
- end
69
71
 
70
- class Surface < WaylandObject
71
- def attach(buffer, x, y)
72
- send_request(1, buffer.id, x, y)
72
+ def remove_global(name)
73
+ @globals.delete_if { |_interface, global| global[:name] == name }
73
74
  end
74
75
 
75
- def damage(x, y, width, height)
76
- send_request(2, x, y, width, height)
76
+ def consume_received_fd
77
+ @received_fds.shift
77
78
  end
78
79
 
79
- def commit
80
- send_request(6)
80
+ def queue_event(event)
81
+ @pending_events << event
81
82
  end
82
83
 
83
- def destroy
84
- send_request(0)
84
+ def send_request(object_id, opcode, *args)
85
+ payload = pack_args(args)
86
+ header = [object_id, ((payload.bytesize + 8) << 16) | opcode].pack("VV")
87
+ @socket.write(header + payload)
85
88
  end
86
- end
87
89
 
88
- class Shm < WaylandObject
89
- def create_pool(fd, size)
90
- pool_id = @connection.allocate_id
91
- @connection.send_request_with_fd(@id, 0, pool_id, size, fd)
92
- ShmPool.new(@connection, pool_id)
93
- end
94
- end
90
+ def send_request_with_fd(object_id, opcode, *args, fd)
91
+ io = fd.respond_to?(:to_io) ? fd.to_io : fd
92
+ unless io.is_a?(IO)
93
+ raise ArgumentError, "Wayland file descriptor arguments must be IO objects"
94
+ end
95
95
 
96
- class ShmPool < WaylandObject
97
- def create_buffer(offset, width, height, stride, format)
98
- buffer_id = @connection.allocate_id
99
- format_val = case format
100
- when :argb8888 then 0
101
- when :xrgb8888 then 1
102
- else 0
103
- end
104
- send_request(0, buffer_id, offset, width, height, stride, format_val)
105
- WlBuffer.new(@connection, buffer_id)
106
- end
96
+ payload = pack_args(args)
97
+ header = [object_id, ((payload.bytesize + 8) << 16) | opcode].pack("VV")
107
98
 
108
- def destroy
109
- send_request(1)
99
+ @socket.sendmsg(header + payload, 0, nil, Socket::AncillaryData.unix_rights(io))
110
100
  end
111
- end
112
101
 
113
- class WlBuffer < WaylandObject
114
- def destroy
115
- send_request(0)
102
+ def flush
103
+ @socket.flush
116
104
  end
117
- end
118
105
 
119
- class XdgWmBase < WaylandObject
120
- def get_xdg_surface(surface)
121
- xdg_surface_id = @connection.allocate_id
122
- send_request(2, xdg_surface_id, surface.id)
123
- XdgSurface.new(@connection, xdg_surface_id)
124
- end
106
+ def dispatch_pending
107
+ pump_events(timeout: 0)
125
108
 
126
- def pong(serial)
127
- send_request(3, serial)
109
+ events = @pending_events
110
+ @pending_events = []
111
+ events
128
112
  end
129
- end
130
113
 
131
- class XdgSurface < WaylandObject
132
- def get_toplevel
133
- toplevel_id = @connection.allocate_id
134
- send_request(1, toplevel_id)
135
- XdgToplevel.new(@connection, toplevel_id)
136
- end
114
+ def pump_events(timeout: nil)
115
+ read_from_socket(timeout) unless complete_message?
116
+ drain_messages
117
+ read_from_socket(0) if @socket.wait_readable(0)
118
+ drain_messages
137
119
 
138
- def ack_configure(serial)
139
- send_request(4, serial)
120
+ @pending_events.length
140
121
  end
141
122
 
142
- def destroy
143
- send_request(0)
144
- end
145
- end
123
+ def roundtrip
124
+ callback = @display.sync
125
+ flush
126
+ deadline = monotonic_time + @roundtrip_timeout
146
127
 
147
- class XdgToplevel < WaylandObject
148
- def set_title(title)
149
- send_request(2, title)
150
- end
128
+ until callback.done?
129
+ raise BackendUnavailable, "Wayland roundtrip timed out" if monotonic_time >= deadline
151
130
 
152
- def destroy
153
- send_request(0)
131
+ pump_events(timeout: roundtrip_poll_interval(deadline))
132
+ end
154
133
  end
155
- end
156
134
 
157
- class Connection
158
- attr_reader :compositor, :shm, :xdg_wm_base
135
+ def wait_until(timeout: @roundtrip_timeout)
136
+ deadline = monotonic_time + timeout
137
+
138
+ until yield
139
+ return false if monotonic_time >= deadline
159
140
 
160
- def initialize
161
- socket_path = ENV["WAYLAND_DISPLAY"] || "wayland-0"
162
- unless socket_path.start_with?("/")
163
- runtime_dir = ENV["XDG_RUNTIME_DIR"] || "/run/user/#{Process.uid}"
164
- socket_path = File.join(runtime_dir, socket_path)
141
+ pump_events(timeout: roundtrip_poll_interval(deadline))
165
142
  end
166
143
 
167
- @socket = UNIXSocket.new(socket_path)
168
- @objects = {}
169
- @next_id = 2
170
- @globals = {}
144
+ true
145
+ end
171
146
 
172
- @display = Display.new(self)
173
- @objects[1] = @display
147
+ def close
148
+ return if @closed
174
149
 
175
- registry = @display.get_registry
176
- @objects[registry.id] = registry
177
- flush
178
- roundtrip
150
+ error = nil
151
+ @received_fds&.each do |fd|
152
+ fd.close unless fd.closed?
153
+ rescue StandardError => close_error
154
+ error ||= close_error
155
+ end
156
+ @received_fds&.reject!(&:closed?)
179
157
 
180
- bind_globals
158
+ begin
159
+ @socket&.close unless @socket&.closed?
160
+ rescue StandardError => close_error
161
+ error ||= close_error
162
+ end
163
+
164
+ @closed = @received_fds.to_a.empty? && (!@socket || @socket.closed?)
165
+ raise error if error
181
166
  end
182
167
 
183
- def allocate_id
184
- id = @next_id
185
- @next_id += 1
186
- id
168
+ def closed?
169
+ @closed || !@socket || @socket.closed?
187
170
  end
188
171
 
189
- def send_request(object_id, opcode, *args)
190
- payload = pack_args(args)
191
- header = [object_id, (payload.bytesize + 8) << 16 | opcode].pack("VV")
192
- @socket.write(header + payload)
172
+ private
173
+
174
+ def resolve_socket_path(socket_path = nil)
175
+ socket_path ||= @env["WAYLAND_DISPLAY"] || "wayland-0"
176
+ return socket_path if socket_path.start_with?("/")
177
+
178
+ runtime_dir = @env["XDG_RUNTIME_DIR"] || "/run/user/#{Process.uid}"
179
+ File.join(runtime_dir, socket_path)
193
180
  end
194
181
 
195
- def send_request_with_fd(object_id, opcode, *args, fd)
196
- payload = pack_args(args)
197
- header = [object_id, (payload.bytesize + 8) << 16 | opcode].pack("VV")
182
+ def roundtrip_poll_interval(deadline)
183
+ [deadline - monotonic_time, ROUNDTRIP_POLL_INTERVAL].min.clamp(0.0, ROUNDTRIP_POLL_INTERVAL)
184
+ end
198
185
 
199
- @socket.sendmsg(header + payload, 0, nil, Socket::AncillaryData.unix_rights(fd))
186
+ def monotonic_time
187
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
200
188
  end
201
189
 
202
- def flush
203
- @socket.flush
190
+ def handle_event(object_id, opcode, payload)
191
+ event_dispatcher.handle_event(object_id, opcode, payload)
204
192
  end
205
193
 
206
- def dispatch_pending
207
- while IO.select([@socket], nil, nil, 0)
208
- header = @socket.read(8)
209
- break unless header && header.bytesize == 8
194
+ def complete_message?
195
+ return false if @read_buffer.bytesize < 8
196
+
197
+ size = @read_buffer.byteslice(4, 4).unpack1("V") >> 16
198
+ raise GUI::BackendUnavailable, "Invalid Wayland message size: #{size}" if size < 8
199
+
200
+ @read_buffer.bytesize >= size
201
+ end
210
202
 
211
- object_id, size_and_opcode = header.unpack("VV")
203
+ def drain_messages
204
+ while complete_message?
205
+ object_id, size_and_opcode = @read_buffer.byteslice(0, 8).unpack("VV")
212
206
  size = size_and_opcode >> 16
213
207
  opcode = size_and_opcode & 0xFFFF
214
-
215
- payload = size > 8 ? @socket.read(size - 8) : ""
216
- handle_event(object_id, opcode, payload)
208
+ message = @read_buffer.slice!(0, size)
209
+ handle_event(object_id, opcode, message.byteslice(8, size - 8))
217
210
  end
218
211
  end
219
212
 
220
- def roundtrip
221
- callback = @display.sync
222
- @objects[callback.id] = callback
223
- flush
213
+ def read_from_socket(timeout)
214
+ return false unless @socket.wait_readable(timeout)
224
215
 
225
- until callback.done?
226
- dispatch_pending
227
- sleep 0.001
228
- end
229
- end
216
+ read_any = false
217
+ loop do
218
+ message = receive_message
219
+ break if message == :wait_readable
230
220
 
231
- private
221
+ chunk, ancillary_data = message
222
+ raise GUI::BackendUnavailable, "Wayland compositor closed the connection" if chunk.nil?
232
223
 
233
- def handle_event(object_id, opcode, payload)
234
- case object_id
235
- when 2
236
- if opcode == 0
237
- name = payload[0, 4].unpack1("V")
238
- interface_len = payload[4, 4].unpack1("V")
239
- interface = payload[8, interface_len - 1]
240
- version = payload[8 + pad_length(interface_len), 4].unpack1("V")
241
- @globals[interface] = { name: name, version: version }
242
- end
243
- else
244
- obj = @objects[object_id]
245
- if obj.is_a?(Callback) && opcode == 0
246
- obj.handle_done
224
+ @read_buffer << chunk
225
+ ancillary_data.each do |control|
226
+ @received_fds.concat(control.unix_rights)
227
+ rescue TypeError
228
+ next
247
229
  end
230
+ read_any = true
248
231
  end
232
+ read_any
249
233
  end
250
234
 
251
- def bind_globals
252
- if @globals["wl_compositor"]
253
- id = allocate_id
254
- g = @globals["wl_compositor"]
255
- @objects[2].bind(g[:name], "wl_compositor", [g[:version], 4].min)
256
- @compositor = Compositor.new(self, id)
257
- @objects[id] = @compositor
258
- end
235
+ def receive_message
236
+ unless @socket.respond_to?(:recvmsg_nonblock)
237
+ chunk = @socket.read_nonblock(READ_CHUNK_SIZE, exception: false)
238
+ return :wait_readable if chunk == :wait_readable
259
239
 
260
- if @globals["wl_shm"]
261
- id = allocate_id
262
- g = @globals["wl_shm"]
263
- @objects[2].bind(g[:name], "wl_shm", [g[:version], 1].min)
264
- @shm = Shm.new(self, id)
265
- @objects[id] = @shm
240
+ return [chunk, []]
266
241
  end
267
242
 
268
- if @globals["xdg_wm_base"]
269
- id = allocate_id
270
- g = @globals["xdg_wm_base"]
271
- @objects[2].bind(g[:name], "xdg_wm_base", [g[:version], 2].min)
272
- @xdg_wm_base = XdgWmBase.new(self, id)
273
- @objects[id] = @xdg_wm_base
274
- end
243
+ result = @socket.recvmsg_nonblock(
244
+ READ_CHUNK_SIZE,
245
+ 0,
246
+ nil,
247
+ scm_rights: true,
248
+ exception: false
249
+ )
250
+ return :wait_readable if result == :wait_readable
251
+ return [nil, []] if !result || result[0].empty?
275
252
 
253
+ [result[0], result.drop(3)]
254
+ end
255
+
256
+ def bind_globals
257
+ bound = global_binder.bind!(@registry, @globals)
258
+ @compositor = bound[:compositor]
259
+ @shm = bound[:shm]
260
+ @xdg_wm_base = bound[:xdg_wm_base]
261
+ @seat = bound[:seat]
276
262
  flush
277
263
  roundtrip
278
264
  end
279
265
 
280
266
  def pack_args(args)
281
- result = String.new
282
- args.each do |arg|
283
- case arg
284
- when Integer
285
- result << [arg].pack("V")
286
- when String
287
- len = arg.bytesize + 1
288
- result << [len].pack("V")
289
- result << arg << "\x00"
290
- result << "\x00" * ((4 - len % 4) % 4)
291
- when Float
292
- result << [(arg * 256).to_i].pack("V")
293
- end
294
- end
295
- result
296
- end
297
-
298
- def pad_length(len)
299
- ((len + 3) / 4) * 4
267
+ codec.pack_args(args)
300
268
  end
301
- end
302
-
303
- class ShmBuffer
304
- attr_reader :wl_buffer
305
269
 
306
- def initialize(fd, size, wl_buffer)
307
- @fd = fd
308
- @size = size
309
- @wl_buffer = wl_buffer
310
- @file = File.open("/proc/self/fd/#{fd}", "r+b")
311
- @file.seek(0)
270
+ def pad_length(length)
271
+ codec.pad_length(length)
312
272
  end
313
273
 
314
- def write(data)
315
- @file.seek(0)
316
- @file.write(data)
317
- @file.flush
274
+ def codec
275
+ @codec ||= Codec.new
318
276
  end
319
277
 
320
- def id
321
- @wl_buffer.id
278
+ def event_dispatcher
279
+ @event_dispatcher ||= EventDispatcher.new(self, codec: codec)
322
280
  end
323
281
 
324
- def destroy
325
- @wl_buffer.destroy
326
- @file.close
282
+ def global_binder
283
+ @global_binder ||= GlobalBinder.new(self)
327
284
  end
328
285
  end
329
286
  end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "input_mapper"
4
+
5
+ module RBGL
6
+ module GUI
7
+ module Wayland
8
+ class EventDispatcher
9
+ def initialize(connection, codec:)
10
+ @connection = connection
11
+ @codec = codec
12
+ end
13
+
14
+ def handle_event(object_id, opcode, payload)
15
+ object = @connection.object_for(object_id)
16
+
17
+ case object
18
+ when Display
19
+ handle_display_event(opcode, payload)
20
+ when Registry
21
+ handle_registry_event(opcode, payload)
22
+ when Callback
23
+ object.handle_done if opcode == 0
24
+ when WlBuffer
25
+ object.handle_release if opcode == 0
26
+ when Seat
27
+ object.handle_capabilities(payload.unpack1("V")) if opcode == 0
28
+ when Keyboard
29
+ handle_keyboard_event(object, opcode, payload)
30
+ when Pointer
31
+ handle_pointer_event(object, opcode, payload)
32
+ when XdgWmBase
33
+ handle_xdg_wm_base_event(object, opcode, payload)
34
+ when XdgSurface
35
+ object.handle_configure(payload.unpack1("V")) if opcode == 0
36
+ when XdgToplevel
37
+ handle_xdg_toplevel_event(object_id, opcode, payload)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def handle_display_event(opcode, payload)
44
+ case opcode
45
+ when 0
46
+ failed_object, code, length = payload.byteslice(0, 12).unpack("VVV")
47
+ message = payload.byteslice(12, length - 1)
48
+ raise GUI::BackendUnavailable,
49
+ "Wayland protocol error #{code} on object #{failed_object}: #{message}"
50
+ when 1
51
+ @connection.unregister_object(payload.unpack1("V"))
52
+ end
53
+ end
54
+
55
+ def handle_registry_event(opcode, payload)
56
+ case opcode
57
+ when 0
58
+ name = payload[0, 4].unpack1("V")
59
+ interface_length = payload[4, 4].unpack1("V")
60
+ interface = payload[8, interface_length - 1]
61
+ version = payload[8 + @codec.pad_length(interface_length), 4].unpack1("V")
62
+ @connection.store_global(interface, name: name, version: version)
63
+ when 1
64
+ @connection.remove_global(payload.unpack1("V"))
65
+ end
66
+ end
67
+
68
+ def handle_xdg_wm_base_event(object, opcode, payload)
69
+ return unless opcode == 0
70
+
71
+ object.pong(payload.unpack1("V"))
72
+ @connection.flush
73
+ end
74
+
75
+ def handle_keyboard_event(keyboard, opcode, payload)
76
+ case opcode
77
+ when 0
78
+ format, size = payload.unpack("V2")
79
+ keyboard.handle_keymap(format, size, @connection.consume_received_fd)
80
+ when 1
81
+ keyboard.focus(payload.byteslice(4, 4).unpack1("V"))
82
+ when 2
83
+ keyboard.blur
84
+ when 3
85
+ _serial, _time, keycode, state = payload.unpack("V4")
86
+ event = {
87
+ type: state == 1 ? :key_press : :key_release,
88
+ surface_id: keyboard.focused_surface_id,
89
+ key: InputMapper.key(keycode),
90
+ keycode: keycode
91
+ }
92
+ event[:modifiers] = keyboard.modifiers.dup unless keyboard.modifiers.empty?
93
+ @connection.queue_event(event)
94
+ when 4
95
+ _serial, depressed, latched, locked, group = payload.unpack("V5")
96
+ keyboard.handle_modifiers(
97
+ depressed: depressed,
98
+ latched: latched,
99
+ locked: locked,
100
+ group: group
101
+ )
102
+ end
103
+ end
104
+
105
+ def handle_pointer_event(pointer, opcode, payload)
106
+ case opcode
107
+ when 0
108
+ _serial, surface_id, x, y = payload.unpack("VVl<l<")
109
+ pointer.focus(surface_id)
110
+ queue_pointer_motion(pointer, x, y)
111
+ when 1
112
+ pointer.blur
113
+ when 2
114
+ _time, x, y = payload.unpack("Vl<l<")
115
+ queue_pointer_motion(pointer, x, y)
116
+ when 3
117
+ _serial, _time, button, state = payload.unpack("V4")
118
+ @connection.queue_event(
119
+ type: state == 1 ? :pointer_button_press : :pointer_button_release,
120
+ surface_id: pointer.focused_surface_id,
121
+ button: InputMapper.button(button),
122
+ x: pointer.x,
123
+ y: pointer.y
124
+ )
125
+ when 4
126
+ _time, axis, value = payload.unpack("VVl<")
127
+ @connection.queue_event(
128
+ type: :pointer_axis,
129
+ surface_id: pointer.focused_surface_id,
130
+ axis: { 0 => :vertical, 1 => :horizontal }.fetch(axis, axis),
131
+ value: value / 256.0,
132
+ x: pointer.x,
133
+ y: pointer.y
134
+ )
135
+ end
136
+ end
137
+
138
+ def queue_pointer_motion(pointer, fixed_x, fixed_y)
139
+ pointer.move(fixed_x / 256.0, fixed_y / 256.0)
140
+ @connection.queue_event(
141
+ type: :pointer_motion,
142
+ surface_id: pointer.focused_surface_id,
143
+ x: pointer.x,
144
+ y: pointer.y
145
+ )
146
+ end
147
+
148
+ def handle_xdg_toplevel_event(object_id, opcode, payload)
149
+ case opcode
150
+ when 0
151
+ width, height = payload[0, 8].unpack("l<l<")
152
+ @connection.queue_event(
153
+ type: :xdg_toplevel_configure,
154
+ object_id: object_id,
155
+ width: width,
156
+ height: height
157
+ )
158
+ when 1
159
+ @connection.queue_event(type: :xdg_toplevel_close, object_id: object_id)
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end