rbgl 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE +21 -0
- data/README.md +123 -0
- data/Rakefile +12 -0
- data/examples/array_test.rb +99 -0
- data/examples/chemical_heartbeat.rb +166 -0
- data/examples/color_test.rb +61 -0
- data/examples/cube_spinning.rb +87 -0
- data/examples/dark_transit.rb +166 -0
- data/examples/fractured_orb.rb +428 -0
- data/examples/fractured_orb_rb.rb +598 -0
- data/examples/gradient.rb +84 -0
- data/examples/hexagonal_flow.rb +333 -0
- data/examples/multi_return_test.rb +98 -0
- data/examples/plasma.rb +78 -0
- data/examples/sphere_raymarch.rb +126 -0
- data/examples/teapot.rb +362 -0
- data/examples/teapot_mcu.rb +344 -0
- data/examples/triangle_basic.rb +36 -0
- data/examples/triangle_window.rb +62 -0
- data/examples/window_test.rb +36 -0
- data/lib/rbgl/engine/buffer.rb +160 -0
- data/lib/rbgl/engine/context.rb +157 -0
- data/lib/rbgl/engine/framebuffer.rb +115 -0
- data/lib/rbgl/engine/pipeline.rb +35 -0
- data/lib/rbgl/engine/rasterizer.rb +213 -0
- data/lib/rbgl/engine/shader.rb +324 -0
- data/lib/rbgl/engine/texture.rb +125 -0
- data/lib/rbgl/engine.rb +15 -0
- data/lib/rbgl/gui/backend.rb +76 -0
- data/lib/rbgl/gui/cocoa/backend.rb +121 -0
- data/lib/rbgl/gui/event.rb +34 -0
- data/lib/rbgl/gui/file_backend.rb +91 -0
- data/lib/rbgl/gui/wayland/backend.rb +126 -0
- data/lib/rbgl/gui/wayland/connection.rb +331 -0
- data/lib/rbgl/gui/window.rb +148 -0
- data/lib/rbgl/gui/x11/backend.rb +156 -0
- data/lib/rbgl/gui/x11/connection.rb +344 -0
- data/lib/rbgl/gui.rb +16 -0
- data/lib/rbgl/version.rb +5 -0
- data/lib/rbgl.rb +6 -0
- metadata +114 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
|
|
5
|
+
module RBGL
|
|
6
|
+
module GUI
|
|
7
|
+
module Wayland
|
|
8
|
+
class WaylandObject
|
|
9
|
+
attr_reader :id, :connection
|
|
10
|
+
|
|
11
|
+
def initialize(connection, id)
|
|
12
|
+
@connection = connection
|
|
13
|
+
@id = id
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def send_request(opcode, *args)
|
|
17
|
+
@connection.send_request(@id, opcode, *args)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Display < WaylandObject
|
|
22
|
+
def initialize(connection)
|
|
23
|
+
super(connection, 1)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def sync
|
|
27
|
+
callback_id = @connection.allocate_id
|
|
28
|
+
send_request(0, callback_id)
|
|
29
|
+
Callback.new(@connection, callback_id)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def get_registry
|
|
33
|
+
registry_id = @connection.allocate_id
|
|
34
|
+
send_request(1, registry_id)
|
|
35
|
+
Registry.new(@connection, registry_id)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
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
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class Callback < WaylandObject
|
|
48
|
+
def initialize(connection, id)
|
|
49
|
+
super
|
|
50
|
+
@done = false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def done?
|
|
54
|
+
@done
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def handle_done
|
|
58
|
+
@done = true
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
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)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class Surface < WaylandObject
|
|
71
|
+
def attach(buffer, x, y)
|
|
72
|
+
send_request(1, buffer.id, x, y)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def damage(x, y, width, height)
|
|
76
|
+
send_request(2, x, y, width, height)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def commit
|
|
80
|
+
send_request(6)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def destroy
|
|
84
|
+
send_request(0)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
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
|
|
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
|
|
107
|
+
|
|
108
|
+
def destroy
|
|
109
|
+
send_request(1)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
class WlBuffer < WaylandObject
|
|
114
|
+
def destroy
|
|
115
|
+
send_request(0)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
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
|
|
125
|
+
|
|
126
|
+
def pong(serial)
|
|
127
|
+
send_request(3, serial)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
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
|
|
137
|
+
|
|
138
|
+
def ack_configure(serial)
|
|
139
|
+
send_request(4, serial)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def destroy
|
|
143
|
+
send_request(0)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
class XdgToplevel < WaylandObject
|
|
148
|
+
def set_title(title)
|
|
149
|
+
send_request(2, title)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def destroy
|
|
153
|
+
send_request(0)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
class Connection
|
|
158
|
+
attr_reader :compositor, :shm, :xdg_wm_base
|
|
159
|
+
|
|
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)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
@socket = UNIXSocket.new(socket_path)
|
|
168
|
+
@objects = {}
|
|
169
|
+
@next_id = 2
|
|
170
|
+
@globals = {}
|
|
171
|
+
|
|
172
|
+
@display = Display.new(self)
|
|
173
|
+
@objects[1] = @display
|
|
174
|
+
|
|
175
|
+
registry = @display.get_registry
|
|
176
|
+
@objects[registry.id] = registry
|
|
177
|
+
flush
|
|
178
|
+
roundtrip
|
|
179
|
+
|
|
180
|
+
bind_globals
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def allocate_id
|
|
184
|
+
id = @next_id
|
|
185
|
+
@next_id += 1
|
|
186
|
+
id
|
|
187
|
+
end
|
|
188
|
+
|
|
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)
|
|
193
|
+
end
|
|
194
|
+
|
|
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")
|
|
198
|
+
|
|
199
|
+
@socket.sendmsg(header + payload, 0, nil, Socket::AncillaryData.unix_rights(fd))
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def flush
|
|
203
|
+
@socket.flush
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def dispatch_pending
|
|
207
|
+
while IO.select([@socket], nil, nil, 0)
|
|
208
|
+
header = @socket.read(8)
|
|
209
|
+
break unless header && header.bytesize == 8
|
|
210
|
+
|
|
211
|
+
object_id, size_and_opcode = header.unpack("VV")
|
|
212
|
+
size = size_and_opcode >> 16
|
|
213
|
+
opcode = size_and_opcode & 0xFFFF
|
|
214
|
+
|
|
215
|
+
payload = size > 8 ? @socket.read(size - 8) : ""
|
|
216
|
+
handle_event(object_id, opcode, payload)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def roundtrip
|
|
221
|
+
callback = @display.sync
|
|
222
|
+
@objects[callback.id] = callback
|
|
223
|
+
flush
|
|
224
|
+
|
|
225
|
+
until callback.done?
|
|
226
|
+
dispatch_pending
|
|
227
|
+
sleep 0.001
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
private
|
|
232
|
+
|
|
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
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
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
|
|
259
|
+
|
|
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
|
|
266
|
+
end
|
|
267
|
+
|
|
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
|
|
275
|
+
|
|
276
|
+
flush
|
|
277
|
+
roundtrip
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
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
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
class ShmBuffer
|
|
304
|
+
attr_reader :wl_buffer
|
|
305
|
+
|
|
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)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def write(data)
|
|
315
|
+
@file.seek(0)
|
|
316
|
+
@file.write(data)
|
|
317
|
+
@file.flush
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def id
|
|
321
|
+
@wl_buffer.id
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def destroy
|
|
325
|
+
@wl_buffer.destroy
|
|
326
|
+
@file.close
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
class Window
|
|
6
|
+
attr_reader :context, :backend, :width, :height
|
|
7
|
+
|
|
8
|
+
def initialize(width:, height:, title: "RBGL", backend: :auto, **options)
|
|
9
|
+
@width = width
|
|
10
|
+
@height = height
|
|
11
|
+
@title = title
|
|
12
|
+
@context = Engine::Context.new(width: width, height: height)
|
|
13
|
+
|
|
14
|
+
@backend = case backend
|
|
15
|
+
when :auto, :native
|
|
16
|
+
detect_backend(width, height, title)
|
|
17
|
+
when :file
|
|
18
|
+
FileBackend.new(width, height, title, **options)
|
|
19
|
+
when :x11
|
|
20
|
+
require_relative "x11/backend"
|
|
21
|
+
X11::Backend.new(width, height, title)
|
|
22
|
+
when :wayland
|
|
23
|
+
require_relative "wayland/backend"
|
|
24
|
+
Wayland::Backend.new(width, height, title)
|
|
25
|
+
when :cocoa
|
|
26
|
+
require_relative "cocoa/backend"
|
|
27
|
+
Cocoa::Backend.new(width, height, title)
|
|
28
|
+
when Backend
|
|
29
|
+
backend
|
|
30
|
+
else
|
|
31
|
+
raise "Unknown backend: #{backend}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@running = false
|
|
35
|
+
@frame_callback = nil
|
|
36
|
+
@last_time = Time.now
|
|
37
|
+
@fps = 0
|
|
38
|
+
@frame_count = 0
|
|
39
|
+
@event_handlers = Hash.new { |h, k| h[k] = [] }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def on(event_type, &block)
|
|
43
|
+
@event_handlers[event_type] << block
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def on_key(&block)
|
|
47
|
+
@backend.on_key(&block)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def on_mouse(&block)
|
|
51
|
+
@backend.on_mouse(&block)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def run(&frame_callback)
|
|
55
|
+
@frame_callback = frame_callback
|
|
56
|
+
@running = true
|
|
57
|
+
@start_time = Time.now
|
|
58
|
+
|
|
59
|
+
while @running && !@backend.should_close?
|
|
60
|
+
current_time = Time.now
|
|
61
|
+
delta_time = current_time - @last_time
|
|
62
|
+
@last_time = current_time
|
|
63
|
+
|
|
64
|
+
process_events
|
|
65
|
+
|
|
66
|
+
@frame_callback&.call(@context, delta_time)
|
|
67
|
+
|
|
68
|
+
@backend.present(@context.framebuffer)
|
|
69
|
+
|
|
70
|
+
@frame_count += 1
|
|
71
|
+
elapsed = current_time - @start_time
|
|
72
|
+
@fps = @frame_count / elapsed if elapsed > 0
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
@backend.close
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def stop
|
|
79
|
+
@running = false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def present_framebuffer(framebuffer = nil)
|
|
83
|
+
fb = framebuffer || @context.framebuffer
|
|
84
|
+
@backend.present(fb)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def set_pixels(buffer)
|
|
88
|
+
@backend.set_pixels(buffer, @width, @height)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def metal_available?
|
|
92
|
+
@backend.metal_available?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def native_handle
|
|
96
|
+
@backend.native_handle
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def should_close?
|
|
100
|
+
@backend.should_close?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def poll_events_raw
|
|
104
|
+
@backend.poll_events_raw
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def close
|
|
108
|
+
@backend.close
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
attr_reader :fps
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def detect_backend(width, height, title)
|
|
116
|
+
case RUBY_PLATFORM
|
|
117
|
+
when /darwin/
|
|
118
|
+
require_relative "cocoa/backend"
|
|
119
|
+
Cocoa::Backend.new(width, height, title)
|
|
120
|
+
when /linux/
|
|
121
|
+
if ENV["WAYLAND_DISPLAY"]
|
|
122
|
+
require_relative "wayland/backend"
|
|
123
|
+
Wayland::Backend.new(width, height, title)
|
|
124
|
+
elsif ENV["DISPLAY"]
|
|
125
|
+
require_relative "x11/backend"
|
|
126
|
+
X11::Backend.new(width, height, title)
|
|
127
|
+
else
|
|
128
|
+
raise "No display server found (DISPLAY or WAYLAND_DISPLAY not set)"
|
|
129
|
+
end
|
|
130
|
+
else
|
|
131
|
+
raise "Unsupported platform: #{RUBY_PLATFORM}"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def process_events
|
|
136
|
+
events = @backend.poll_events
|
|
137
|
+
|
|
138
|
+
return unless events.is_a?(Array)
|
|
139
|
+
|
|
140
|
+
events.each do |event|
|
|
141
|
+
next unless event.is_a?(Event)
|
|
142
|
+
|
|
143
|
+
@event_handlers[event.type].each { |handler| handler.call(event) }
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "connection"
|
|
4
|
+
|
|
5
|
+
module RBGL
|
|
6
|
+
module GUI
|
|
7
|
+
module X11
|
|
8
|
+
class Backend < GUI::Backend
|
|
9
|
+
def initialize(width, height, title = "RBGL")
|
|
10
|
+
super
|
|
11
|
+
@display = Connection.new(ENV["DISPLAY"] || ":0")
|
|
12
|
+
@windows = {}
|
|
13
|
+
setup_window(width, height, title)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private def setup_window(w, h, t)
|
|
17
|
+
wid = @display.generate_id
|
|
18
|
+
|
|
19
|
+
@display.create_window(
|
|
20
|
+
depth: @display.root_depth,
|
|
21
|
+
wid: wid,
|
|
22
|
+
parent: @display.root,
|
|
23
|
+
x: 0, y: 0,
|
|
24
|
+
width: w, height: h,
|
|
25
|
+
border_width: 0,
|
|
26
|
+
window_class: :input_output,
|
|
27
|
+
visual: @display.root_visual,
|
|
28
|
+
value_mask: [:back_pixel, :event_mask],
|
|
29
|
+
values: {
|
|
30
|
+
back_pixel: @display.black_pixel,
|
|
31
|
+
event_mask: [:exposure, :key_press, :key_release,
|
|
32
|
+
:button_press, :button_release, :pointer_motion,
|
|
33
|
+
:structure_notify]
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
@display.change_property(wid, :wm_name, :string, t)
|
|
38
|
+
@display.map_window(wid)
|
|
39
|
+
@display.flush
|
|
40
|
+
|
|
41
|
+
gc_id = @display.generate_id
|
|
42
|
+
@display.create_gc(gc_id, wid)
|
|
43
|
+
|
|
44
|
+
@windows[wid] = {
|
|
45
|
+
width: w,
|
|
46
|
+
height: h,
|
|
47
|
+
gc: gc_id,
|
|
48
|
+
should_close: false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@handle = wid
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def present(framebuffer)
|
|
55
|
+
return unless @handle
|
|
56
|
+
|
|
57
|
+
window = @windows[@handle]
|
|
58
|
+
return unless window
|
|
59
|
+
|
|
60
|
+
buffer = convert_to_x11_format(framebuffer)
|
|
61
|
+
|
|
62
|
+
@display.put_image(
|
|
63
|
+
format: :z_pixmap,
|
|
64
|
+
drawable: @handle,
|
|
65
|
+
gc: window[:gc],
|
|
66
|
+
width: framebuffer.width,
|
|
67
|
+
height: framebuffer.height,
|
|
68
|
+
dst_x: 0, dst_y: 0,
|
|
69
|
+
depth: @display.root_depth,
|
|
70
|
+
data: buffer
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
@display.flush
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def poll_events
|
|
77
|
+
events = []
|
|
78
|
+
|
|
79
|
+
while @display.pending > 0
|
|
80
|
+
raw_event = @display.next_event
|
|
81
|
+
next unless raw_event
|
|
82
|
+
|
|
83
|
+
event = convert_event(raw_event)
|
|
84
|
+
if event
|
|
85
|
+
events << event
|
|
86
|
+
emit_from_event(event)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
events
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def should_close?
|
|
94
|
+
return false unless @handle
|
|
95
|
+
|
|
96
|
+
@windows[@handle]&.[](:should_close) || false
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def close
|
|
100
|
+
return unless @handle
|
|
101
|
+
|
|
102
|
+
@windows[@handle][:should_close] = true
|
|
103
|
+
@display.destroy_window(@handle)
|
|
104
|
+
@windows.delete(@handle)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def convert_to_x11_format(framebuffer)
|
|
110
|
+
# X11 uses BGRX format (blue, green, red, padding)
|
|
111
|
+
framebuffer.to_bgra_bytes
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def convert_event(raw)
|
|
115
|
+
case raw[:type]
|
|
116
|
+
when :key_press
|
|
117
|
+
Event.new(:key_press, key: raw[:keycode])
|
|
118
|
+
when :key_release
|
|
119
|
+
Event.new(:key_release, key: raw[:keycode])
|
|
120
|
+
when :button_press
|
|
121
|
+
Event.new(:mouse_press, x: raw[:x], y: raw[:y], button: raw[:button])
|
|
122
|
+
when :button_release
|
|
123
|
+
Event.new(:mouse_release, x: raw[:x], y: raw[:y], button: raw[:button])
|
|
124
|
+
when :motion_notify
|
|
125
|
+
Event.new(:mouse_move, x: raw[:x], y: raw[:y])
|
|
126
|
+
when :configure_notify
|
|
127
|
+
Event.new(:resize, width: raw[:width], height: raw[:height])
|
|
128
|
+
when :client_message
|
|
129
|
+
if @handle && @windows[@handle]
|
|
130
|
+
@windows[@handle][:should_close] = true
|
|
131
|
+
end
|
|
132
|
+
Event.new(:close)
|
|
133
|
+
else
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def emit_from_event(event)
|
|
139
|
+
case event.type
|
|
140
|
+
when :key_press, :key_release
|
|
141
|
+
emit_key(event.key, event.type == :key_press ? :press : :release)
|
|
142
|
+
when :mouse_press, :mouse_release, :mouse_move
|
|
143
|
+
action = case event.type
|
|
144
|
+
when :mouse_press then :press
|
|
145
|
+
when :mouse_release then :release
|
|
146
|
+
else :move
|
|
147
|
+
end
|
|
148
|
+
emit_mouse(event.x, event.y, event[:button], action)
|
|
149
|
+
when :resize
|
|
150
|
+
emit_resize(event.width, event.height)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|