mirror of
https://github.com/vidarh/ruby-x11
synced 2024-12-25 09:59:00 +01:00
Major refactor of packet builder
I decided to ditch the Packet class name and go with a Form module that contains a BaseForm which child classes inherit from. I've created a few tests so you can see how the BaseForm class works and how packets can be read and sent over a socket to an X11 server
This commit is contained in:
parent
a503b8391e
commit
ad10e78306
5 changed files with 274 additions and 236 deletions
208
lib/X11/form.rb
Normal file
208
lib/X11/form.rb
Normal file
|
@ -0,0 +1,208 @@
|
|||
module X11
|
||||
module Form
|
||||
# A form object is an X11 packet definition. We use forms to encode
|
||||
# and decode X11 packets as we send and receive them over a socket.
|
||||
#
|
||||
# We can create a packet definition as follows:
|
||||
#
|
||||
# class Point < BaseForm
|
||||
# field :x, Int8
|
||||
# field :y, Int8
|
||||
# end
|
||||
#
|
||||
# p = Point.new(10,20)
|
||||
# p.x => 10
|
||||
# p.y => 20
|
||||
# p.to_packet => "\n\x14"
|
||||
#
|
||||
# You can also read from a socket:
|
||||
#
|
||||
# Point.from_packet(socket) => #<Point @x=10 @y=20>
|
||||
#
|
||||
class BaseForm
|
||||
include X11::Type
|
||||
|
||||
# initialize field accessors
|
||||
def initialize(*params)
|
||||
self.class.fields.each do |f|
|
||||
param = params.shift
|
||||
instance_variable_set("@#{f.name}", param)
|
||||
end
|
||||
end
|
||||
|
||||
def to_packet
|
||||
# fetch class level instance variable holding defined fields
|
||||
structs = self.class.instance_variable_get("@structs")
|
||||
|
||||
packet = structs.map do |s|
|
||||
# fetch value of field set in initialization
|
||||
value = instance_variable_get("@#{s.name}")
|
||||
|
||||
case s.type
|
||||
when :field
|
||||
if value.is_a?(BaseForm)
|
||||
value.to_packet
|
||||
else
|
||||
s.type_klass.pack(value)
|
||||
end
|
||||
when :unused
|
||||
"\x00" * s.size
|
||||
when :length
|
||||
s.type_klass.pack(value.size)
|
||||
when :string
|
||||
s.type_klass.pack(value)
|
||||
when :list
|
||||
value.collect do |obj|
|
||||
obj.to_packet
|
||||
end
|
||||
end
|
||||
end.join
|
||||
end
|
||||
|
||||
class << self
|
||||
def from_packet(socket)
|
||||
# fetch class level instance variable holding defined fields
|
||||
|
||||
form = new
|
||||
lengths = {}
|
||||
|
||||
@structs.each do |s|
|
||||
case s.type
|
||||
when :field
|
||||
val = if s.type_klass.superclass == BaseForm
|
||||
s.type_klass.from_packet(socket)
|
||||
else
|
||||
s.type_klass.unpack( socket.read(s.type_klass.size) )
|
||||
end
|
||||
form.instance_variable_set("@#{s.name}", val)
|
||||
when :unused
|
||||
socket.read(s.size)
|
||||
when :length
|
||||
size = s.type_klass.unpack( socket.read(s.type_klass.size) )
|
||||
lengths[s.name] = size
|
||||
when :string
|
||||
val = s.type_klass.unpack(socket, lengths[s.name])
|
||||
form.instance_variable_set("@#{s.name}", val)
|
||||
when :list
|
||||
val = lengths[s.name].times.collect do
|
||||
s.type_klass.from_packet(socket)
|
||||
end
|
||||
form.instance_variable_set("@#{s.name}", val)
|
||||
end
|
||||
end
|
||||
|
||||
return form
|
||||
end
|
||||
|
||||
def field(*args)
|
||||
name, type_klass, type = args
|
||||
class_eval { attr_accessor name }
|
||||
|
||||
s = OpenStruct.new
|
||||
s.name = name
|
||||
s.type = (type == nil ? :field : type)
|
||||
s.type_klass = type_klass
|
||||
|
||||
@structs ||= []
|
||||
@structs << s
|
||||
end
|
||||
|
||||
def unused(size)
|
||||
s = OpenStruct.new
|
||||
s.size = size
|
||||
s.type = :unused
|
||||
|
||||
@structs ||= []
|
||||
@structs << s
|
||||
end
|
||||
|
||||
def fields
|
||||
@structs.dup.delete_if{|s| s.type == :unused or s.type == :length}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
## X11 Packet Defintions
|
||||
##
|
||||
|
||||
class ClientHandshake < BaseForm
|
||||
field :byte_order, Uint8
|
||||
unused 1
|
||||
field :protocol_major_version, Uint16
|
||||
field :protocol_minor_version, Uint16
|
||||
field :auth_proto_name, Uint16, :length
|
||||
field :auth_proto_data, Uint16, :length
|
||||
unused 2
|
||||
field :auth_proto_name, String8, :string
|
||||
field :auth_proto_data, String8, :string
|
||||
end
|
||||
|
||||
class FormatInfo < BaseForm
|
||||
field :depth, Uint8
|
||||
field :bits_per_pixel, Uint8
|
||||
field :scanline_pad, Uint8
|
||||
unused 5
|
||||
end
|
||||
|
||||
class VisualInfo < BaseForm
|
||||
field :visual_id, VisualID
|
||||
field :qlass, Uint8
|
||||
field :bits_per_rgb_value, Uint8
|
||||
field :colormap_entries, Uint16
|
||||
field :red_mask, Uint32
|
||||
field :green_mask, Uint32
|
||||
field :blue_mask, Uint32
|
||||
unused 4
|
||||
end
|
||||
|
||||
class DepthInfo < BaseForm
|
||||
field :depth, Uint8
|
||||
unused 1
|
||||
field :visuals, Uint16, :length
|
||||
unused 4
|
||||
field :visuals, VisualInfo, :list
|
||||
end
|
||||
|
||||
class ScreenInfo < BaseForm
|
||||
field :root, Window
|
||||
field :default_colormap, Colormap
|
||||
field :white_pixel, Colornum
|
||||
field :black_pixel, Colornum
|
||||
field :current_input_masks, Mask
|
||||
field :width_in_pixels, Uint16
|
||||
field :height_in_pixels, Uint16
|
||||
field :width_in_millimeters, Uint16
|
||||
field :height_in_millimeters, Uint16
|
||||
field :min_installed_maps, Uint16
|
||||
field :max_installed_maps, Uint16
|
||||
field :root_visual, VisualID
|
||||
field :backing_stores, Uint8
|
||||
field :save_unders, Bool
|
||||
field :root_depth, Uint8
|
||||
field :depths, Uint8,:length
|
||||
field :depths, DepthInfo, :list
|
||||
end
|
||||
|
||||
class DisplayInfo < BaseForm
|
||||
field :release_number, Uint32
|
||||
field :resource_id_base, Uint32
|
||||
field :resource_id_mask, Uint32
|
||||
field :motion_buffer_size, Uint32
|
||||
field :vendor, Uint16, :length
|
||||
field :maximum_request_length, Uint16
|
||||
field :screens, Uint8, :length
|
||||
field :formats, Uint8, :length
|
||||
field :image_byte_order, Signifigance
|
||||
field :bitmap_bit_order, Signifigance
|
||||
field :bitmap_format_scanline_unit, Uint8
|
||||
field :bitmap_format_scanline_pad, Uint8
|
||||
field :min_keycode, KeyCode
|
||||
field :max_keycode, KeyCode
|
||||
unused 4
|
||||
field :vendor, String8, :string
|
||||
field :formats, FormatInfo, :list
|
||||
field :screens, ScreenInfo, :list
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,102 +0,0 @@
|
|||
module X11
|
||||
module Packet
|
||||
|
||||
class BasePacket
|
||||
include X11::Type
|
||||
|
||||
class << self
|
||||
def create(*parameters)
|
||||
lengths = lengths_for(parameters)
|
||||
|
||||
packet = @structs.map do |s|
|
||||
case s.type
|
||||
when :field
|
||||
s.type_klass.pack(parameters.shift)
|
||||
when :unused
|
||||
"\x00" * s.size
|
||||
when :length
|
||||
s.type_klass.pack(lengths[s.name])
|
||||
when :string
|
||||
s.type_klass.pack(parameters.shift)
|
||||
when :list
|
||||
parameters.shift.each do |obj|
|
||||
s.type_klass.create(*obj)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
((@opcode ? [X11::Type::Int8.pack(@opcode)] : []) + packet).join
|
||||
end
|
||||
|
||||
def read(socket)
|
||||
lengths = {}
|
||||
values = {}
|
||||
|
||||
@structs.each do |s|
|
||||
case s.type
|
||||
when :field
|
||||
values[s.name] = s.type_klass.unpack( socket.read(s.type_klass.size) )
|
||||
when :unused
|
||||
socket.read(s.size)
|
||||
when :length
|
||||
size = s.type_klass.unpack( socket.read(s.type_klass.size) )
|
||||
lengths[s.name] = size
|
||||
when :string
|
||||
values[s.name] = s.type_klass.unpack(socket, lengths[s.name])
|
||||
when :list
|
||||
values[s.name] = lengths[s.name].times.collect do
|
||||
s.type_klass.read(socket)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
OpenStruct.new(values)
|
||||
end
|
||||
|
||||
def field(*args)
|
||||
name, type_klass, type = args
|
||||
s = Struct.new(:name, :type_klass, :type).new
|
||||
s.name = name
|
||||
s.type = (type == nil ? :field : type)
|
||||
s.type_klass = type_klass
|
||||
|
||||
@structs ||= []
|
||||
@structs << s
|
||||
end
|
||||
|
||||
def unused(size)
|
||||
s = Struct.new(:size, :type).new
|
||||
s.size = size
|
||||
s.type = :unused
|
||||
|
||||
@structs ||= []
|
||||
@structs << s
|
||||
end
|
||||
|
||||
def opcode(value)
|
||||
@opcode = value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def lengths_for(args)
|
||||
args = args.dup
|
||||
lengths = {}
|
||||
|
||||
fields.each do |s|
|
||||
value = args.shift
|
||||
lengths[s.name] = value.size if s.type == :list or s.type == :string
|
||||
end
|
||||
|
||||
return lengths
|
||||
end
|
||||
|
||||
def fields
|
||||
@structs.dup.delete_if do |s|
|
||||
s.type == :unused or s.type == :length
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,84 +0,0 @@
|
|||
module X11
|
||||
module Packet
|
||||
|
||||
class ClientHandshake < BasePacket
|
||||
field :byte_order, Uint8
|
||||
unused 1
|
||||
field :protocol_major_version, Uint16
|
||||
field :protocol_minor_version, Uint16
|
||||
field :auth_proto_name, Uint16, :length
|
||||
field :auth_proto_data, Uint16, :length
|
||||
unused 2
|
||||
field :auth_proto_name, String8, :string
|
||||
field :auth_proto_data, String8, :string
|
||||
end
|
||||
|
||||
class FormatInfo < BasePacket
|
||||
field :depth, Uint8
|
||||
field :bits_per_pixel, Uint8
|
||||
field :scanline_pad, Uint8
|
||||
unused 5
|
||||
end
|
||||
|
||||
class VisualInfo < BasePacket
|
||||
field :visual_id, VisualID
|
||||
field :qlass, Uint8
|
||||
field :bits_per_rgb_value, Uint8
|
||||
field :colormap_entries, Uint16
|
||||
field :red_mask, Uint32
|
||||
field :green_mask, Uint32
|
||||
field :blue_mask, Uint32
|
||||
unused 4
|
||||
end
|
||||
|
||||
class DepthInfo < BasePacket
|
||||
field :depth, Uint8
|
||||
unused 1
|
||||
field :visuals, Uint16, :length
|
||||
unused 4
|
||||
field :visuals, VisualInfo, :list
|
||||
end
|
||||
|
||||
class ScreenInfo < BasePacket
|
||||
field :root, Window
|
||||
field :default_colormap, Colormap
|
||||
field :white_pixel, Colornum
|
||||
field :black_pixel, Colornum
|
||||
field :current_input_masks, EventMask
|
||||
field :width_in_pixels, Uint16
|
||||
field :height_in_pixels, Uint16
|
||||
field :width_in_millimeters, Uint16
|
||||
field :height_in_millimeters, Uint16
|
||||
field :min_installed_maps, Uint16
|
||||
field :max_installed_maps, Uint16
|
||||
field :root_visual, VisualID
|
||||
field :backing_stores, Uint8
|
||||
field :save_unders, Bool
|
||||
field :root_depth, Uint8
|
||||
field :depths, Uint8,:length
|
||||
field :depths, DepthInfo, :list
|
||||
end
|
||||
|
||||
class DisplayInfo < BasePacket
|
||||
field :release_number, Uint32
|
||||
field :resource_id_base, Uint32
|
||||
field :resource_id_mask, Uint32
|
||||
field :motion_buffer_size, Uint32
|
||||
field :vendor, Uint16, :length
|
||||
field :maximum_request_length, Uint16
|
||||
field :screens, Uint8, :length
|
||||
field :formats, Uint8, :length
|
||||
field :image_byte_order, Signifigance
|
||||
field :bitmap_bit_order, Signifigance
|
||||
field :bitmap_format_scanline_unit, Uint8
|
||||
field :bitmap_format_scanline_pad, Uint8
|
||||
field :min_keycode, KeyCode
|
||||
field :max_keycode, KeyCode
|
||||
unused 4
|
||||
field :vendor, String8, :string
|
||||
field :formats, FormatInfo, :list
|
||||
field :screens, ScreenInfo, :list
|
||||
end
|
||||
|
||||
end
|
||||
end
|
66
test/form_test.rb
Normal file
66
test/form_test.rb
Normal file
|
@ -0,0 +1,66 @@
|
|||
require File.expand_path('../helper', __FILE__)
|
||||
|
||||
class MockSocket
|
||||
def initialize(packet)
|
||||
@packet = packet
|
||||
end
|
||||
|
||||
def read(amount)
|
||||
@packet.slice!(0..amount-1)
|
||||
end
|
||||
end
|
||||
|
||||
class Point < X11::Form::BaseForm
|
||||
field :x, Int8
|
||||
field :y, Int8
|
||||
end
|
||||
|
||||
class Child < X11::Form::BaseForm
|
||||
field :name, Uint16, :length
|
||||
field :name, String8, :string
|
||||
end
|
||||
|
||||
class Parent < X11::Form::BaseForm
|
||||
field :value, Uint8
|
||||
field :point, Point
|
||||
|
||||
field :name, Uint16, :length
|
||||
field :name, String8, :string
|
||||
|
||||
field :children, Uint16, :length
|
||||
field :children, Child, :list
|
||||
end
|
||||
|
||||
describe X11::Form::BaseForm do
|
||||
it "setters and getters on form should work" do
|
||||
|
||||
# we can create partial form objects
|
||||
# without specifying all parameters.
|
||||
parent = Parent.new(1337)
|
||||
|
||||
# To fill in the rest of the parameters
|
||||
# we use attr_accessors
|
||||
parent.name = "Parent Form"
|
||||
parent.point = Point.new(0,0)
|
||||
|
||||
parent.point.must_be_instance_of Point
|
||||
parent.name.must_equal "Parent Form"
|
||||
|
||||
parent.children = []
|
||||
parent.children << Child.new
|
||||
parent.children << Child.new
|
||||
|
||||
assert_equal parent.children.size, 2
|
||||
end
|
||||
|
||||
it "should encode/decode a packet" do
|
||||
parent = Parent.new(255,Point.new(23,17), "Parent Form", [])
|
||||
socket = MockSocket.new(parent.to_packet)
|
||||
|
||||
decoded = Parent.from_packet(socket)
|
||||
decoded.value.must_equal 255
|
||||
decoded.name.must_equal "Parent Form"
|
||||
decoded.point.x.must_equal 23
|
||||
decoded.point.y.must_equal 17
|
||||
end
|
||||
end
|
|
@ -1,50 +0,0 @@
|
|||
require File.expand_path('../helper', __FILE__)
|
||||
|
||||
class MockSocket
|
||||
def initialize(packet)
|
||||
@packet = packet
|
||||
end
|
||||
|
||||
def read(amount)
|
||||
@packet.slice!(0..amount-1)
|
||||
end
|
||||
end
|
||||
|
||||
class Child < X11::Packet::BasePacket
|
||||
field :name, Uint16, :length
|
||||
field :name, String8, :string
|
||||
end
|
||||
|
||||
class Parent < X11::Packet::BasePacket
|
||||
field :a, Uint8
|
||||
field :b, Uint16
|
||||
field :c, Uint32
|
||||
|
||||
field :d, Uint16, :length
|
||||
field :d, String8, :string
|
||||
|
||||
field :children, Uint16, :length
|
||||
field :children, Child, :list
|
||||
end
|
||||
|
||||
describe X11::Packet::BasePacket do
|
||||
it "should create and read a packet" do
|
||||
children = []
|
||||
children << Child.create("#1")
|
||||
children << Child.create("#2")
|
||||
children << Child.create("#3")
|
||||
|
||||
packet = Parent.create(1,2,3,"Hello World", children)
|
||||
socket = MockSocket.new(packet)
|
||||
reader = Parent.read(socket)
|
||||
|
||||
reader.a.must_equal 1
|
||||
reader.b.must_equal 2
|
||||
reader.c.must_equal 3
|
||||
reader.d.must_equal "Hello World"
|
||||
|
||||
reader.children.shift.name.must_equal("#1")
|
||||
reader.children.shift.name.must_equal("#2")
|
||||
reader.children.shift.name.must_equal("#3")
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue