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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +20 -2
- data/README.md +130 -74
- data/Rakefile +7 -0
- data/bench/renderer_bench.rb +283 -0
- data/docs/.nojekyll +1 -0
- data/docs/index.html +38 -0
- data/examples/array_test.rb +1 -1
- data/examples/color_test.rb +1 -1
- data/examples/cube_spinning.rb +1 -1
- data/examples/gradient.rb +1 -1
- data/examples/multi_return_test.rb +1 -1
- data/examples/plasma.rb +1 -1
- data/examples/sphere_raymarch.rb +1 -1
- data/examples/triangle_window.rb +1 -1
- data/exe/rbgl +42 -0
- data/lib/rbgl/cli/doctor.rb +155 -0
- data/lib/rbgl/engine/buffer.rb +150 -40
- data/lib/rbgl/engine/clip_space_clipper.rb +163 -0
- data/lib/rbgl/engine/context.rb +133 -63
- data/lib/rbgl/engine/framebuffer.rb +153 -48
- data/lib/rbgl/engine/immutable_color.rb +32 -0
- data/lib/rbgl/engine/pipeline.rb +38 -7
- data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +195 -0
- data/lib/rbgl/engine/rasterizer/line_renderer.rb +88 -0
- data/lib/rbgl/engine/rasterizer/point_renderer.rb +35 -0
- data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +136 -0
- data/lib/rbgl/engine/rasterizer.rb +65 -171
- data/lib/rbgl/engine/shader/base_shader.rb +51 -0
- data/lib/rbgl/engine/shader/builtins.rb +257 -0
- data/lib/rbgl/engine/shader/dynamic_data.rb +73 -0
- data/lib/rbgl/engine/shader.rb +5 -318
- data/lib/rbgl/engine/texture.rb +264 -51
- data/lib/rbgl/engine.rb +4 -1
- data/lib/rbgl/gui/backend.rb +21 -34
- data/lib/rbgl/gui/backend_factory.rb +89 -0
- data/lib/rbgl/gui/cocoa/backend.rb +59 -46
- data/lib/rbgl/gui/cocoa/key_mapper.rb +48 -0
- data/lib/rbgl/gui/event.rb +4 -2
- data/lib/rbgl/gui/file_backend/bmp_writer.rb +63 -0
- data/lib/rbgl/gui/file_backend/frame_writer.rb +35 -0
- data/lib/rbgl/gui/file_backend/png_writer.rb +18 -0
- data/lib/rbgl/gui/file_backend/ppm_writer.rb +26 -0
- data/lib/rbgl/gui/file_backend.rb +50 -55
- data/lib/rbgl/gui/wayland/backend.rb +214 -60
- data/lib/rbgl/gui/wayland/codec.rb +50 -0
- data/lib/rbgl/gui/wayland/connection.rb +188 -231
- data/lib/rbgl/gui/wayland/event_dispatcher.rb +165 -0
- data/lib/rbgl/gui/wayland/global_binder.rb +46 -0
- data/lib/rbgl/gui/wayland/input_mapper.rb +61 -0
- data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +63 -0
- data/lib/rbgl/gui/wayland/protocol_objects/display.rb +54 -0
- data/lib/rbgl/gui/wayland/protocol_objects/seat.rb +115 -0
- data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +157 -0
- data/lib/rbgl/gui/wayland/protocol_objects/surface.rb +33 -0
- data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +60 -0
- data/lib/rbgl/gui/wayland/protocol_objects.rb +8 -0
- data/lib/rbgl/gui/window/event_dispatcher.rb +30 -0
- data/lib/rbgl/gui/window/render_loop.rb +111 -0
- data/lib/rbgl/gui/window.rb +74 -84
- data/lib/rbgl/gui/x11/atom_cache.rb +29 -0
- data/lib/rbgl/gui/x11/backend.rb +73 -49
- data/lib/rbgl/gui/x11/connection.rb +284 -219
- data/lib/rbgl/gui/x11/event_parser.rb +71 -0
- data/lib/rbgl/gui/x11/key_mapper.rb +48 -0
- data/lib/rbgl/gui/x11/pixel_encoder.rb +164 -0
- data/lib/rbgl/gui/x11/request_encoder.rb +155 -0
- data/lib/rbgl/gui/x11/setup_parser.rb +138 -0
- data/lib/rbgl/gui/x11/transport.rb +81 -0
- data/lib/rbgl/gui/x11/x_authority.rb +107 -0
- data/lib/rbgl/gui.rb +3 -5
- data/lib/rbgl/version.rb +1 -1
- metadata +63 -24
- data/examples/chemical_heartbeat.rb +0 -166
- data/examples/dark_transit.rb +0 -166
- data/examples/fractured_orb.rb +0 -428
- data/examples/fractured_orb_rb.rb +0 -598
- data/examples/hexagonal_flow.rb +0 -333
- data/examples/teapot.rb +0 -362
- data/examples/teapot_mcu.rb +0 -344
|
@@ -1,86 +1,106 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "socket"
|
|
4
|
+
require_relative "../backend"
|
|
5
|
+
require_relative "atom_cache"
|
|
6
|
+
require_relative "event_parser"
|
|
7
|
+
require_relative "key_mapper"
|
|
8
|
+
require_relative "request_encoder"
|
|
9
|
+
require_relative "setup_parser"
|
|
10
|
+
require_relative "transport"
|
|
11
|
+
require_relative "x_authority"
|
|
4
12
|
|
|
5
13
|
module RBGL
|
|
6
14
|
module GUI
|
|
7
15
|
module X11
|
|
8
16
|
class Connection
|
|
17
|
+
DEFAULT_REPLY_TIMEOUT = 5.0
|
|
18
|
+
|
|
19
|
+
ATOM_NAMES = {
|
|
20
|
+
wm_protocols: "WM_PROTOCOLS",
|
|
21
|
+
wm_delete_window: "WM_DELETE_WINDOW"
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
9
24
|
attr_reader :default_screen, :resource_id_base, :resource_id_mask
|
|
10
25
|
attr_reader :root, :root_depth, :root_visual, :white_pixel, :black_pixel
|
|
26
|
+
attr_reader :maximum_request_length, :bits_per_pixel, :scanline_pad
|
|
27
|
+
attr_reader :visual_class, :red_mask, :green_mask, :blue_mask, :image_byte_order
|
|
11
28
|
|
|
12
|
-
def initialize(display_name)
|
|
13
|
-
host, display_num,
|
|
29
|
+
def initialize(display_name, env: ENV, reply_timeout: DEFAULT_REPLY_TIMEOUT)
|
|
30
|
+
host, display_num, screen_num = parse_display_name(display_name)
|
|
14
31
|
@socket = connect(host, display_num)
|
|
15
|
-
@
|
|
32
|
+
@transport = Transport.new(@socket)
|
|
16
33
|
@resource_id_counter = 0
|
|
17
34
|
@pending_events = []
|
|
18
|
-
|
|
19
|
-
|
|
35
|
+
@pending_replies = {}
|
|
36
|
+
@sequence = 0
|
|
37
|
+
@default_screen = screen_num
|
|
38
|
+
@reply_timeout = reply_timeout
|
|
39
|
+
|
|
40
|
+
handshake(host: host, display_num: display_num, env: env)
|
|
41
|
+
load_keyboard_mapping
|
|
42
|
+
rescue StandardError
|
|
43
|
+
@transport&.close
|
|
44
|
+
raise
|
|
20
45
|
end
|
|
21
46
|
|
|
22
47
|
def generate_id
|
|
23
|
-
|
|
48
|
+
masked_counter = @resource_id_counter & @resource_id_mask
|
|
49
|
+
raise GUI::BackendUnavailable, "X11 resource ID space exhausted" if masked_counter != @resource_id_counter
|
|
50
|
+
|
|
51
|
+
id = @resource_id_base | masked_counter
|
|
24
52
|
@resource_id_counter += 1
|
|
25
53
|
id
|
|
26
54
|
end
|
|
27
55
|
|
|
28
56
|
def flush
|
|
29
|
-
@
|
|
57
|
+
@transport.flush
|
|
30
58
|
end
|
|
31
59
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
ready ? 1 : 0
|
|
60
|
+
def atom(name)
|
|
61
|
+
atom_cache.fetch(name)
|
|
35
62
|
end
|
|
36
63
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
when :input_output then 1
|
|
41
|
-
when :input_only then 2
|
|
42
|
-
else 0
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
mask = 0
|
|
46
|
-
value_list = []
|
|
47
|
-
|
|
48
|
-
if value_mask.include?(:back_pixel)
|
|
49
|
-
mask |= 0x0002
|
|
50
|
-
value_list << values[:back_pixel]
|
|
51
|
-
end
|
|
64
|
+
def wm_delete_window_atom
|
|
65
|
+
atom(:wm_delete_window)
|
|
66
|
+
end
|
|
52
67
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
end
|
|
68
|
-
value_list << event_mask
|
|
69
|
-
end
|
|
68
|
+
def wm_protocols_atom
|
|
69
|
+
atom(:wm_protocols)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def enable_wm_delete_window(window)
|
|
73
|
+
change_property(window, :wm_protocols, :atom, [wm_delete_window_atom], format: 32)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def key_for_keycode(keycode)
|
|
77
|
+
load_keyboard_mapping unless @keyboard_mapping
|
|
78
|
+
keysyms = @keyboard_mapping[keycode]
|
|
79
|
+
keysym = keysyms&.find { |candidate| !candidate.zero? }
|
|
80
|
+
KeyMapper.key(keysym, fallback: keycode)
|
|
81
|
+
end
|
|
70
82
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
parent,
|
|
75
|
-
x, y,
|
|
76
|
-
width, height,
|
|
77
|
-
border_width,
|
|
78
|
-
class_val,
|
|
79
|
-
visual,
|
|
80
|
-
mask
|
|
81
|
-
].pack("CVVSSSSSVV") + value_list.pack("V*")
|
|
83
|
+
def pending
|
|
84
|
+
@pending_events.length + @transport.pending
|
|
85
|
+
end
|
|
82
86
|
|
|
83
|
-
|
|
87
|
+
def create_window(depth:, wid:, parent:, x:, y:, width:, height:,
|
|
88
|
+
border_width:, window_class:, visual:, value_mask:, values:)
|
|
89
|
+
depth_byte, request = request_encoder.create_window_data(
|
|
90
|
+
depth: depth,
|
|
91
|
+
wid: wid,
|
|
92
|
+
parent: parent,
|
|
93
|
+
x: x,
|
|
94
|
+
y: y,
|
|
95
|
+
width: width,
|
|
96
|
+
height: height,
|
|
97
|
+
border_width: border_width,
|
|
98
|
+
window_class: window_class,
|
|
99
|
+
visual: visual,
|
|
100
|
+
value_mask: value_mask,
|
|
101
|
+
values: values
|
|
102
|
+
)
|
|
103
|
+
send_request(1, request, depth_byte)
|
|
84
104
|
end
|
|
85
105
|
|
|
86
106
|
def map_window(wid)
|
|
@@ -92,82 +112,56 @@ module RBGL
|
|
|
92
112
|
end
|
|
93
113
|
|
|
94
114
|
def create_gc(gc_id, drawable, values = {})
|
|
95
|
-
|
|
96
|
-
value_list = []
|
|
97
|
-
|
|
98
|
-
if values[:foreground]
|
|
99
|
-
mask |= 0x0004
|
|
100
|
-
value_list << values[:foreground]
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
if values[:background]
|
|
104
|
-
mask |= 0x0008
|
|
105
|
-
value_list << values[:background]
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
request = [gc_id, drawable, mask].pack("VVV") + value_list.pack("V*")
|
|
115
|
+
request = request_encoder.create_gc_data(gc_id, drawable, values)
|
|
109
116
|
send_request(55, request)
|
|
110
117
|
end
|
|
111
118
|
|
|
112
|
-
def put_image(format:, drawable:, gc:, width:, height:, dst_x:, dst_y:, depth:, data:)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
else 2
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
left_pad = 0
|
|
121
|
-
|
|
122
|
-
header = [
|
|
123
|
-
drawable,
|
|
124
|
-
gc,
|
|
125
|
-
width, height,
|
|
126
|
-
dst_x, dst_y,
|
|
127
|
-
left_pad,
|
|
128
|
-
depth
|
|
129
|
-
].pack("VVvvvvCC") + "\x00\x00"
|
|
119
|
+
def put_image(format:, drawable:, gc:, width:, height:, dst_x:, dst_y:, depth:, data:, bytes_per_line:)
|
|
120
|
+
maximum_data = (@maximum_request_length * 4) - 24
|
|
121
|
+
if bytes_per_line > maximum_data
|
|
122
|
+
raise GUI::BackendUnavailable, "An X11 image row exceeds the server request limit"
|
|
123
|
+
end
|
|
130
124
|
|
|
131
|
-
|
|
125
|
+
rows_per_request = [maximum_data / bytes_per_line, 1].max
|
|
126
|
+
offset_y = 0
|
|
127
|
+
while offset_y < height
|
|
128
|
+
row_count = [rows_per_request, height - offset_y].min
|
|
129
|
+
chunk = data.byteslice(offset_y * bytes_per_line, row_count * bytes_per_line)
|
|
130
|
+
send_image_chunk(
|
|
131
|
+
format: format,
|
|
132
|
+
drawable: drawable,
|
|
133
|
+
gc: gc,
|
|
134
|
+
width: width,
|
|
135
|
+
height: row_count,
|
|
136
|
+
dst_x: dst_x,
|
|
137
|
+
dst_y: dst_y + offset_y,
|
|
138
|
+
depth: depth,
|
|
139
|
+
data: chunk
|
|
140
|
+
)
|
|
141
|
+
offset_y += row_count
|
|
142
|
+
end
|
|
132
143
|
end
|
|
133
144
|
|
|
134
|
-
def change_property(window, property, type, data, mode: :replace)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
when :append then 2
|
|
139
|
-
else 0
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
property_atom = get_atom(property)
|
|
143
|
-
type_atom = get_atom(type)
|
|
144
|
-
|
|
145
|
-
format = 8
|
|
146
|
-
data_bytes = data.to_s
|
|
147
|
-
|
|
148
|
-
request = [
|
|
145
|
+
def change_property(window, property, type, data, mode: :replace, format: 8)
|
|
146
|
+
property_atom = atom(property)
|
|
147
|
+
type_atom = atom(type)
|
|
148
|
+
mode_val, request = request_encoder.change_property_data(
|
|
149
149
|
window,
|
|
150
150
|
property_atom,
|
|
151
151
|
type_atom,
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
data,
|
|
153
|
+
mode: mode,
|
|
154
|
+
format: format
|
|
155
|
+
)
|
|
156
156
|
send_request(18, request, mode_val)
|
|
157
157
|
end
|
|
158
158
|
|
|
159
159
|
def intern_atom(name, only_if_exists: false)
|
|
160
|
-
request =
|
|
161
|
-
|
|
162
|
-
0
|
|
163
|
-
].pack("vv") + pad_to_4(name)
|
|
164
|
-
|
|
165
|
-
send_request(16, request, only_if_exists ? 1 : 0)
|
|
160
|
+
request = request_encoder.intern_atom_data(name)
|
|
161
|
+
sequence = send_request(16, request, only_if_exists ? 1 : 0)
|
|
166
162
|
flush
|
|
167
163
|
|
|
168
|
-
reply = read_reply
|
|
169
|
-
return 0 unless reply
|
|
170
|
-
|
|
164
|
+
reply = read_reply(sequence)
|
|
171
165
|
reply[8, 4].unpack1("V")
|
|
172
166
|
end
|
|
173
167
|
|
|
@@ -179,19 +173,25 @@ module RBGL
|
|
|
179
173
|
end
|
|
180
174
|
|
|
181
175
|
def read_events
|
|
182
|
-
while pending > 0
|
|
176
|
+
while @transport.pending > 0
|
|
183
177
|
event = read_event
|
|
184
178
|
@pending_events << event if event
|
|
185
179
|
end
|
|
186
180
|
end
|
|
187
181
|
|
|
182
|
+
def close
|
|
183
|
+
@transport.close
|
|
184
|
+
end
|
|
185
|
+
|
|
188
186
|
private
|
|
189
187
|
|
|
190
188
|
def parse_display_name(name)
|
|
191
|
-
if name =~ /^(?:(
|
|
192
|
-
|
|
189
|
+
if name =~ /^(?:(.*):)?(\d+)(?:\.(\d+))?$/
|
|
190
|
+
host = ::Regexp.last_match(1)
|
|
191
|
+
host = nil if host && host.empty?
|
|
192
|
+
[host, ::Regexp.last_match(2).to_i, (::Regexp.last_match(3) || 0).to_i]
|
|
193
193
|
else
|
|
194
|
-
raise "Invalid display name: #{name}"
|
|
194
|
+
raise GUI::BackendUnavailable, "Invalid X11 display name: #{name}"
|
|
195
195
|
end
|
|
196
196
|
end
|
|
197
197
|
|
|
@@ -204,140 +204,205 @@ module RBGL
|
|
|
204
204
|
end
|
|
205
205
|
end
|
|
206
206
|
|
|
207
|
-
def handshake
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
207
|
+
def handshake(host: nil, display_num: 0, env: ENV)
|
|
208
|
+
auth_host = local_display_host?(host) ? nil : host
|
|
209
|
+
cookie = XAuthority.cookie_for(host: auth_host, display_number: display_num, env: env)
|
|
210
|
+
auth_name = cookie ? XAuthority::AUTH_NAME : ""
|
|
211
|
+
auth_data = cookie || ""
|
|
212
|
+
init_request = [0x6C, 0, 11, 0, auth_name.bytesize, auth_data.bytesize, 0].pack("CCvvvvv")
|
|
213
|
+
init_request << request_encoder.pad_to_4(auth_name)
|
|
214
|
+
init_request << request_encoder.pad_to_4(auth_data)
|
|
215
215
|
|
|
216
|
-
@
|
|
217
|
-
@
|
|
216
|
+
@transport.write(init_request)
|
|
217
|
+
@transport.flush
|
|
218
218
|
|
|
219
|
-
header = @
|
|
220
|
-
|
|
219
|
+
header = @transport.read_exact(8, timeout: @reply_timeout)
|
|
220
|
+
raise GUI::BackendUnavailable, "X11 connection closed during handshake" unless header&.bytesize == 8
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
status = header.unpack1("C")
|
|
223
|
+
unless status == 1
|
|
224
|
+
reason_length = header.getbyte(1)
|
|
225
|
+
reason = if reason_length.positive?
|
|
226
|
+
@transport.read_exact(pad_length(reason_length), timeout: @reply_timeout)
|
|
227
|
+
.byteslice(0, reason_length)
|
|
228
|
+
end
|
|
229
|
+
detail = reason && !reason.empty? ? ": #{reason}" : ""
|
|
230
|
+
raise GUI::BackendUnavailable, "X11 connection failed#{detail}"
|
|
231
|
+
end
|
|
223
232
|
|
|
224
233
|
additional_length = header[6, 2].unpack1("v")
|
|
225
|
-
data = @
|
|
234
|
+
data = @transport.read_exact(additional_length * 4, timeout: @reply_timeout)
|
|
226
235
|
|
|
227
236
|
parse_server_info(data)
|
|
237
|
+
rescue EOFError, IO::WaitReadable => error
|
|
238
|
+
raise GUI::BackendUnavailable, "X11 handshake failed: #{error.message}"
|
|
228
239
|
end
|
|
229
240
|
|
|
230
241
|
def parse_server_info(data)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
242
|
+
setup = SetupParser.new.parse(data)
|
|
243
|
+
screen = setup.screens.fetch(@default_screen)
|
|
244
|
+
format = setup.pixmap_formats.find { |candidate| candidate.depth == screen.root_depth }
|
|
245
|
+
depth = screen.depths.find { |candidate| candidate.depth == screen.root_depth }
|
|
246
|
+
visual = depth&.visuals&.find { |candidate| candidate.id == screen.root_visual }
|
|
247
|
+
raise GUI::BackendUnavailable, "No X11 pixmap format for root depth #{screen.root_depth}" unless format
|
|
248
|
+
raise GUI::BackendUnavailable, "No X11 visual group for root depth #{screen.root_depth}" unless depth
|
|
249
|
+
raise GUI::BackendUnavailable, "No X11 root visual metadata for #{screen.root_visual}" unless visual
|
|
250
|
+
|
|
251
|
+
@resource_id_base = setup.resource_id_base
|
|
252
|
+
@resource_id_mask = setup.resource_id_mask
|
|
253
|
+
@maximum_request_length = setup.maximum_request_length
|
|
254
|
+
@minimum_keycode = setup.minimum_keycode
|
|
255
|
+
@maximum_keycode = setup.maximum_keycode
|
|
256
|
+
@image_byte_order = setup.image_byte_order
|
|
257
|
+
@root = screen.root
|
|
258
|
+
@root_depth = screen.root_depth
|
|
259
|
+
@root_visual = screen.root_visual
|
|
260
|
+
@white_pixel = screen.white_pixel
|
|
261
|
+
@black_pixel = screen.black_pixel
|
|
262
|
+
@bits_per_pixel = format.bits_per_pixel
|
|
263
|
+
@scanline_pad = format.scanline_pad
|
|
264
|
+
@visual_class = visual.visual_class
|
|
265
|
+
@red_mask = visual.red_mask
|
|
266
|
+
@green_mask = visual.green_mask
|
|
267
|
+
@blue_mask = visual.blue_mask
|
|
268
|
+
rescue IndexError
|
|
269
|
+
raise GUI::BackendUnavailable, "X11 screen #{@default_screen} is unavailable"
|
|
247
270
|
end
|
|
248
271
|
|
|
249
272
|
def send_request(opcode, data, extra = 0)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
padding_size = length * 4 - 4 - data.bytesize
|
|
254
|
-
padded_data = data + ("\x00" * padding_size)
|
|
255
|
-
|
|
256
|
-
@socket.write(header + padded_data)
|
|
257
|
-
@next_seq += 1
|
|
273
|
+
@transport.write(request_encoder.request_packet(opcode, data, extra))
|
|
274
|
+
next_sequence
|
|
258
275
|
end
|
|
259
276
|
|
|
260
277
|
def send_request_with_data(opcode, extra, header_data, bulk_data)
|
|
261
|
-
|
|
262
|
-
|
|
278
|
+
@transport.write(request_encoder.request_packet_with_data(opcode, extra, header_data, bulk_data))
|
|
279
|
+
next_sequence
|
|
280
|
+
end
|
|
263
281
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
282
|
+
def send_image_chunk(format:, drawable:, gc:, width:, height:, dst_x:, dst_y:, depth:, data:)
|
|
283
|
+
format_byte, header, image_data = request_encoder.put_image_data(
|
|
284
|
+
format: format,
|
|
285
|
+
drawable: drawable,
|
|
286
|
+
gc: gc,
|
|
287
|
+
width: width,
|
|
288
|
+
height: height,
|
|
289
|
+
dst_x: dst_x,
|
|
290
|
+
dst_y: dst_y,
|
|
291
|
+
depth: depth,
|
|
292
|
+
data: data
|
|
293
|
+
)
|
|
294
|
+
send_request_with_data(72, format_byte, header, image_data)
|
|
295
|
+
end
|
|
267
296
|
|
|
268
|
-
|
|
269
|
-
@
|
|
297
|
+
def load_keyboard_mapping
|
|
298
|
+
count = @maximum_keycode - @minimum_keycode + 1
|
|
299
|
+
request = [@minimum_keycode, count, 0].pack("CCv")
|
|
300
|
+
sequence = send_request(101, request)
|
|
301
|
+
flush
|
|
302
|
+
reply = read_reply(sequence)
|
|
303
|
+
keysyms_per_keycode = reply.getbyte(1)
|
|
304
|
+
keysyms = reply.byteslice(32..).unpack("V*")
|
|
305
|
+
@keyboard_mapping = {}
|
|
306
|
+
|
|
307
|
+
count.times do |index|
|
|
308
|
+
offset = index * keysyms_per_keycode
|
|
309
|
+
@keyboard_mapping[@minimum_keycode + index] = keysyms.slice(offset, keysyms_per_keycode)
|
|
310
|
+
end
|
|
270
311
|
end
|
|
271
312
|
|
|
272
|
-
def read_reply
|
|
273
|
-
|
|
274
|
-
return nil unless header && header.bytesize == 32
|
|
313
|
+
def read_reply(expected_sequence = nil)
|
|
314
|
+
return @pending_replies.delete(expected_sequence) if expected_sequence && @pending_replies.key?(expected_sequence)
|
|
275
315
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
316
|
+
loop do
|
|
317
|
+
packet = read_packet
|
|
318
|
+
type = packet.getbyte(0)
|
|
319
|
+
sequence = packet.byteslice(2, 2).unpack1("v")
|
|
320
|
+
|
|
321
|
+
case type
|
|
322
|
+
when 0
|
|
323
|
+
raise_protocol_error(packet)
|
|
324
|
+
when 1
|
|
325
|
+
return packet if expected_sequence.nil? || sequence == expected_sequence
|
|
326
|
+
|
|
327
|
+
@pending_replies[sequence] = packet
|
|
328
|
+
else
|
|
329
|
+
event = parse_event(packet)
|
|
330
|
+
@pending_events << event if event
|
|
331
|
+
end
|
|
281
332
|
end
|
|
282
333
|
end
|
|
283
334
|
|
|
284
335
|
def read_event
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
{ type: :key_press, keycode: keycode }
|
|
294
|
-
when 3
|
|
295
|
-
keycode = data[1, 1].unpack1("C")
|
|
296
|
-
{ type: :key_release, keycode: keycode }
|
|
297
|
-
when 4
|
|
298
|
-
x, y = data[24, 4].unpack("ss")
|
|
299
|
-
button = data[1, 1].unpack1("C")
|
|
300
|
-
{ type: :button_press, x: x, y: y, button: button }
|
|
301
|
-
when 5
|
|
302
|
-
x, y = data[24, 4].unpack("ss")
|
|
303
|
-
button = data[1, 1].unpack1("C")
|
|
304
|
-
{ type: :button_release, x: x, y: y, button: button }
|
|
305
|
-
when 6
|
|
306
|
-
x, y = data[24, 4].unpack("ss")
|
|
307
|
-
{ type: :motion_notify, x: x, y: y }
|
|
308
|
-
when 12
|
|
309
|
-
{ type: :exposure }
|
|
310
|
-
when 22
|
|
311
|
-
width, height = data[20, 4].unpack("vv")
|
|
312
|
-
{ type: :configure_notify, width: width, height: height }
|
|
313
|
-
when 33
|
|
314
|
-
window = data[4, 4].unpack1("V")
|
|
315
|
-
atom = data[8, 4].unpack1("V")
|
|
316
|
-
{ type: :client_message, window: window, data: atom }
|
|
317
|
-
else
|
|
318
|
-
{ type: :unknown, code: event_type }
|
|
336
|
+
packet = read_packet
|
|
337
|
+
type = packet.getbyte(0)
|
|
338
|
+
return raise_protocol_error(packet) if type.zero?
|
|
339
|
+
|
|
340
|
+
if type == 1
|
|
341
|
+
sequence = packet.byteslice(2, 2).unpack1("v")
|
|
342
|
+
@pending_replies[sequence] = packet
|
|
343
|
+
return nil
|
|
319
344
|
end
|
|
320
|
-
end
|
|
321
345
|
|
|
322
|
-
|
|
323
|
-
case name
|
|
324
|
-
when :wm_name then 39
|
|
325
|
-
when :string then 31
|
|
326
|
-
when :wm_protocols then intern_atom("WM_PROTOCOLS")
|
|
327
|
-
when :wm_delete_window then intern_atom("WM_DELETE_WINDOW")
|
|
328
|
-
else
|
|
329
|
-
name.is_a?(Integer) ? name : intern_atom(name.to_s)
|
|
330
|
-
end
|
|
346
|
+
parse_event(packet)
|
|
331
347
|
end
|
|
332
348
|
|
|
333
|
-
def
|
|
334
|
-
|
|
335
|
-
|
|
349
|
+
def resolve_atom(name)
|
|
350
|
+
return name if name.is_a?(Integer)
|
|
351
|
+
|
|
352
|
+
intern_atom(ATOM_NAMES.fetch(name, name.to_s))
|
|
336
353
|
end
|
|
337
354
|
|
|
338
355
|
def pad_length(len)
|
|
339
356
|
((len + 3) / 4) * 4
|
|
340
357
|
end
|
|
358
|
+
|
|
359
|
+
def local_display_host?(host)
|
|
360
|
+
host.nil? || host.empty? || host == "unix"
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def request_encoder
|
|
364
|
+
@request_encoder ||= RequestEncoder.new
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def event_parser
|
|
368
|
+
@event_parser ||= EventParser.new
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def atom_cache
|
|
372
|
+
@atom_cache ||= AtomCache.new { |name| resolve_atom(name) }
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def read_packet
|
|
376
|
+
header = @transport.read_exact(32, timeout: @reply_timeout)
|
|
377
|
+
return header unless header.getbyte(0) == 1
|
|
378
|
+
|
|
379
|
+
additional = header.byteslice(4, 4).unpack1("V")
|
|
380
|
+
additional.positive? ? header + @transport.read_exact(additional * 4, timeout: @reply_timeout) : header
|
|
381
|
+
rescue EOFError, IO::WaitReadable => error
|
|
382
|
+
raise GUI::BackendUnavailable, error.message
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def parse_event(packet)
|
|
386
|
+
event = event_parser.parse(packet)
|
|
387
|
+
@keyboard_mapping = nil if event&.[](:type) == :mapping_notify && event[:request] == :keyboard
|
|
388
|
+
event
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def raise_protocol_error(packet)
|
|
392
|
+
error_code = packet.getbyte(1)
|
|
393
|
+
sequence = packet.byteslice(2, 2).unpack1("v")
|
|
394
|
+
bad_value = packet.byteslice(4, 4).unpack1("V")
|
|
395
|
+
minor_opcode = packet.byteslice(8, 2).unpack1("v")
|
|
396
|
+
major_opcode = packet.getbyte(10)
|
|
397
|
+
raise GUI::BackendUnavailable,
|
|
398
|
+
"X11 protocol error #{error_code} for request #{sequence} " \
|
|
399
|
+
"(opcode #{major_opcode}.#{minor_opcode}, value #{bad_value})"
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def next_sequence
|
|
403
|
+
@sequence = (@sequence + 1) & 0xFFFF
|
|
404
|
+
end
|
|
405
|
+
|
|
341
406
|
end
|
|
342
407
|
end
|
|
343
408
|
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
module X11
|
|
6
|
+
class EventParser
|
|
7
|
+
def parse(data)
|
|
8
|
+
return nil unless data && data.bytesize == 32
|
|
9
|
+
|
|
10
|
+
event_type = data.unpack1("C") & 0x7F
|
|
11
|
+
|
|
12
|
+
case event_type
|
|
13
|
+
when 2
|
|
14
|
+
key_event(:key_press, data)
|
|
15
|
+
when 3
|
|
16
|
+
key_event(:key_release, data)
|
|
17
|
+
when 4
|
|
18
|
+
button_event(:button_press, data)
|
|
19
|
+
when 5
|
|
20
|
+
button_event(:button_release, data)
|
|
21
|
+
when 6
|
|
22
|
+
x, y = data[24, 4].unpack("s<s<")
|
|
23
|
+
{ type: :motion_notify, x: x, y: y }
|
|
24
|
+
when 12
|
|
25
|
+
{ type: :exposure }
|
|
26
|
+
when 22
|
|
27
|
+
width, height = data[20, 4].unpack("vv")
|
|
28
|
+
{ type: :configure_notify, width: width, height: height }
|
|
29
|
+
when 33
|
|
30
|
+
{
|
|
31
|
+
type: :client_message,
|
|
32
|
+
format: data[1, 1].unpack1("C"),
|
|
33
|
+
window: data[4, 4].unpack1("V"),
|
|
34
|
+
message_type: data[8, 4].unpack1("V"),
|
|
35
|
+
data32: data[12, 20].unpack("V5")
|
|
36
|
+
}
|
|
37
|
+
when 34
|
|
38
|
+
{
|
|
39
|
+
type: :mapping_notify,
|
|
40
|
+
request: mapping_request(data.getbyte(1)),
|
|
41
|
+
first_keycode: data.getbyte(4),
|
|
42
|
+
count: data.getbyte(5)
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
{ type: :unknown, code: event_type }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def key_event(type, data)
|
|
52
|
+
{ type: type, keycode: data[1, 1].unpack1("C") }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def button_event(type, data)
|
|
56
|
+
x, y = data[24, 4].unpack("s<s<")
|
|
57
|
+
{
|
|
58
|
+
type: type,
|
|
59
|
+
x: x,
|
|
60
|
+
y: y,
|
|
61
|
+
button: data[1, 1].unpack1("C")
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def mapping_request(code)
|
|
66
|
+
{ 0 => :modifier, 1 => :keyboard, 2 => :pointer }.fetch(code, :unknown)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|