virtualbox-com 0.9.6
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.
- data/.gitignore +8 -0
- data/Gemfile +9 -0
- data/LICENSE +19 -0
- data/Rakefile +8 -0
- data/Readme.md +59 -0
- data/examples/simple.rb +43 -0
- data/lib/virtualbox-com.rb +1 -0
- data/lib/virtualbox/com.rb +51 -0
- data/lib/virtualbox/com/abstract_enum.rb +51 -0
- data/lib/virtualbox/com/abstract_interface.rb +144 -0
- data/lib/virtualbox/com/abstract_model.rb +14 -0
- data/lib/virtualbox/com/exceptions.rb +32 -0
- data/lib/virtualbox/com/iid.rb +43 -0
- data/lib/virtualbox/com/model/4.2-gen.rb +2720 -0
- data/lib/virtualbox/com/model/4.2.rb +97 -0
- data/lib/virtualbox/com/util.rb +119 -0
- data/lib/virtualbox/com/version.rb +5 -0
- data/lib/virtualbox/com/xpcomc-ffi.rb +76 -0
- data/lib/virtualbox/com/xpcomc-ffi/binding.rb +87 -0
- data/lib/virtualbox/com/xpcomc-ffi/implementer.rb +86 -0
- data/lib/virtualbox/com/xpcomc-ffi/lib.rb +90 -0
- data/lib/virtualbox/com/xpcomc-ffi/model-types.rb +15 -0
- data/lib/virtualbox/com/xpcomc-ffi/sig.rb +342 -0
- data/lib/virtualbox/com/xpcomc-ffi/spec.rb +58 -0
- data/lib/virtualbox/com/xpcomc-ffi/xpcomc-vbox.rb +54 -0
- data/scripts/xidl-conv.rb +124 -0
- data/virtualbox-com.gemspec +27 -0
- metadata +123 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
require_relative '4.2-gen'
|
2
|
+
|
3
|
+
module VirtualBox
|
4
|
+
module COM
|
5
|
+
module Model
|
6
|
+
|
7
|
+
class Progress < AbstractInterface
|
8
|
+
# This method blocks the execution while the operations represented
|
9
|
+
# by this {Progress} object execute, but yields a block every `x`
|
10
|
+
# percent (interval given in parameters).
|
11
|
+
def wait(interval_percent=1)
|
12
|
+
# If no block is given we just wait until completion, not worrying
|
13
|
+
# about tracking percentages.
|
14
|
+
if !block_given?
|
15
|
+
wait_for_completion(-1)
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
# Initial value forces the 0% yield
|
20
|
+
last_reported = -100
|
21
|
+
|
22
|
+
while true
|
23
|
+
delta = percent - last_reported
|
24
|
+
last_reported += delta
|
25
|
+
yield self if delta >= interval_percent
|
26
|
+
|
27
|
+
# This either sleeps for half a second or returns on completion
|
28
|
+
wait_for_completion(500)
|
29
|
+
|
30
|
+
break if completed || canceled
|
31
|
+
|
32
|
+
# Pass off execution so other threads can run
|
33
|
+
Thread.pass
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
class EventSource < AbstractInterface
|
40
|
+
MODEL_MAP = {
|
41
|
+
:machine_event => :MachineEvent,
|
42
|
+
:snapshot_event => :SnapshotEvent,
|
43
|
+
:on_machine_state_changed => :MachineStateChangedEvent,
|
44
|
+
:on_machine_data_changed => :MachineDataChangedEvent,
|
45
|
+
:on_extra_data_changed => :ExtraDataChangedEvent,
|
46
|
+
:on_extra_data_can_change => :ExtraDataCanChangeEvent,
|
47
|
+
:on_medium_registered => :MediumRegisteredEvent,
|
48
|
+
:on_machine_registered => :MachineRegisteredEvent,
|
49
|
+
:on_session_state_changed => :SessionStateChangedEvent,
|
50
|
+
:on_snapshot_taken => :SnapshotTakenEvent,
|
51
|
+
:on_snapshot_deleted => :SnapshotDeletedEvent,
|
52
|
+
:on_snapshot_changed => :SnapshotChangedEvent,
|
53
|
+
:on_guest_property_changed => :GuestPropertyChangedEvent,
|
54
|
+
:on_mouse_pointer_shape_changed => :MousePointerShapEvent,
|
55
|
+
:on_mouse_capability_changed => :MouseCapabilityChangedEvent,
|
56
|
+
:on_keyboard_leds_changed => :KeyboardLedsChangedEvent,
|
57
|
+
:on_state_changed => :StateChangedEvent,
|
58
|
+
:on_additions_state_changed => :AdditionsStateChangedEvent,
|
59
|
+
:on_network_adapter_changed => :NetworkAdapterChangedEvent,
|
60
|
+
:on_serial_port_changed => :SerialPortChangedEvent,
|
61
|
+
:on_parallel_port_changed => :ParallelPortChangedEvent,
|
62
|
+
:on_storage_controller_changed => :StorageControllerChangedEvent,
|
63
|
+
:on_medium_changed => :MediumChangedEvent,
|
64
|
+
:on_vrde_server_changed => :VRDEServerChangedEvent,
|
65
|
+
:on_usb_controller_changed => :USBControllerChangedEvent,
|
66
|
+
:on_usb_device_state_changed => :USBDeviceStateChangedEvent,
|
67
|
+
:on_shared_folder_changed => :SharedFolderChangedEvent,
|
68
|
+
:on_runtime_error => :RuntimeErrorEvent,
|
69
|
+
:on_can_show_window => :CanShowWindowEvent,
|
70
|
+
:on_show_window => :ShowWindowEvent,
|
71
|
+
:on_cpu_changed => :CPUChangedEvent,
|
72
|
+
:on_vrde_server_info_changed => :VRDEServerInfoChangedEvent,
|
73
|
+
:on_event_source_changed => :EventSourceChangedEvent,
|
74
|
+
:on_cpu_execution_cap_changed => :CPUExecutionCapChangedEvent,
|
75
|
+
:on_guest_keyboard => :GuestKeyboardEvent,
|
76
|
+
:on_guest_mouse => :GuestMouseEvent,
|
77
|
+
:on_nat_redirect => :NATRedirectEvent,
|
78
|
+
:on_host_pci_device_plug => :HostPCIDevicePlugEvent,
|
79
|
+
:on_vbox_svc_availability_changed => :VBoxSVCAvailabilityChangedEvent,
|
80
|
+
:on_bandwidth_group_changed => :BandwidthGroupChangedEvent,
|
81
|
+
:on_guest_monitor_changed => :GuestMonitorChangedEvent,
|
82
|
+
:on_storage_device_changed => :StorageDeviceChangedEvent,
|
83
|
+
:on_clipboard_mode_changed => :ClipboardModeChangedEvent,
|
84
|
+
:on_drag_and_drop_mode_changed => :DragAndDropModeChangedEvent,
|
85
|
+
}
|
86
|
+
|
87
|
+
def getEvent(*args)
|
88
|
+
if e = get_event(*args)
|
89
|
+
(model = MODEL_MAP[e.type]) ? e.cast(model) : e
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
module COM
|
3
|
+
|
4
|
+
module Util
|
5
|
+
def self.platform
|
6
|
+
case RbConfig::CONFIG["host_os"].downcase
|
7
|
+
when /darwin/ then :mac
|
8
|
+
when /mswin|mingw|cygwin/ then :windows
|
9
|
+
when /linux/ then :linux
|
10
|
+
when /solaris/ then :solaris
|
11
|
+
when /freebsd/ then :freebsd
|
12
|
+
else :unknown
|
13
|
+
end
|
14
|
+
end
|
15
|
+
def self.jruby?
|
16
|
+
RbConfig::CONFIG["ruby_install_name"] == "jruby"
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# An "almost complete" camel-caser. Camel cases a string with a few
|
21
|
+
# exceptions. For example: `get_foo` becomes `GetFoo`, but `get_os_type`
|
22
|
+
# becomes `GetOSType` since `os` is a special case.
|
23
|
+
#
|
24
|
+
# @param [String] string The string to camel case
|
25
|
+
# @return [String]
|
26
|
+
def self.camelize(string)
|
27
|
+
string.to_s.split(/_/).map {|part|
|
28
|
+
CAMELCASE_SPECIALS[part] || part.capitalize }.join
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.uncamelize(name)
|
32
|
+
CAMELCASE_SPECIALS.each{|k,v|
|
33
|
+
name = name.gsub(v, k.capitalize) }
|
34
|
+
name.gsub(/([A-Z])/, "_\\0").downcase.gsub(/^_/, '')
|
35
|
+
end
|
36
|
+
|
37
|
+
CAMELCASE_SPECIALS = { # Order is important
|
38
|
+
"fourcc_rgb" => "FOURCC_RGB",
|
39
|
+
"am79c970a" => "Am79C970A",
|
40
|
+
"am79c973" => "Am79C973",
|
41
|
+
"i82540em" => "I82540EM",
|
42
|
+
"i82543gc" => "I82543GC",
|
43
|
+
"i82545em" => "I82545EM",
|
44
|
+
"efidual" => "EFIDUAL",
|
45
|
+
"split2g" => "Split2G",
|
46
|
+
"cpuid" => "CPUID",
|
47
|
+
"cdrom" => "CDROM",
|
48
|
+
"efi32" => "EFI32",
|
49
|
+
"efi64" => "EFI64",
|
50
|
+
"piix3" => "PIIX3",
|
51
|
+
"piix4" => "PIIX4",
|
52
|
+
"winmm" => "WinMM",
|
53
|
+
"ac97" => "AC97",
|
54
|
+
"acpi" => "ACPI",
|
55
|
+
"alsa" => "ALSA",
|
56
|
+
"apic" => "APIC",
|
57
|
+
"bios" => "BIOS",
|
58
|
+
"csam" => "CSAM",
|
59
|
+
"dhcp" => "DHCP",
|
60
|
+
"fifo" => "FIFO",
|
61
|
+
"ich6" => "ICH6",
|
62
|
+
"ich9" => "ICH9",
|
63
|
+
"macs" => "MACs",
|
64
|
+
"mmpm" => "MMPM",
|
65
|
+
"patm" => "PATM",
|
66
|
+
"sata" => "SATA",
|
67
|
+
"sb16" => "SB16",
|
68
|
+
"scsi" => "SCSI",
|
69
|
+
"slip" => "SLIP",
|
70
|
+
"tftp" => "TFTP",
|
71
|
+
"vbox" => "VBox",
|
72
|
+
"vpid" => "VPID",
|
73
|
+
"vram" => "VRAM",
|
74
|
+
"vrde" => "VRDE",
|
75
|
+
"vrdp" => "VRDP",
|
76
|
+
"api" => "API",
|
77
|
+
"cpu" => "CPU",
|
78
|
+
"dns" => "DNS",
|
79
|
+
"dvd" => "DVD",
|
80
|
+
"efi" => "EFI",
|
81
|
+
"esx" => "ESX",
|
82
|
+
"gid" => "GID",
|
83
|
+
"hda" => "HDA",
|
84
|
+
"hdd" => "HDD",
|
85
|
+
"ide" => "IDE",
|
86
|
+
"irq" => "IRQ",
|
87
|
+
"mac" => "MAC",
|
88
|
+
"nat" => "NAT",
|
89
|
+
"oss" => "OSS",
|
90
|
+
"pae" => "PAE",
|
91
|
+
"pci" => "PCI",
|
92
|
+
"pid" => "PID",
|
93
|
+
"png" => "PNG",
|
94
|
+
"ppp" => "PPP",
|
95
|
+
"ps2" => "PS2",
|
96
|
+
"pxe" => "PXE",
|
97
|
+
"ram" => "RAM",
|
98
|
+
"rtc" => "RTC",
|
99
|
+
"sas" => "SAS",
|
100
|
+
"svc" => "SVC",
|
101
|
+
"tcp" => "TCP",
|
102
|
+
"udp" => "UDP",
|
103
|
+
"uid" => "UID",
|
104
|
+
"usb" => "USB",
|
105
|
+
"utc" => "UTC",
|
106
|
+
"vdi" => "VDI",
|
107
|
+
"vfs" => "VFS",
|
108
|
+
"3d" => "3D",
|
109
|
+
"hw" => "HW",
|
110
|
+
"io" => "IO",
|
111
|
+
"ip" => "IP",
|
112
|
+
"os" => "OS",
|
113
|
+
"vm" => "VM",
|
114
|
+
}
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# Load FFI support
|
2
|
+
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
|
3
|
+
require 'ffi'
|
4
|
+
end
|
5
|
+
|
6
|
+
|
7
|
+
# Add model types to fit XPCOMC FFI implementation:
|
8
|
+
# INT8, INT16, INT32, INT64, UINT8, UINT16, UINT32, UINT64, WSTRING, BOOL
|
9
|
+
require_relative 'xpcomc-ffi/model-types'
|
10
|
+
|
11
|
+
module VirtualBox
|
12
|
+
module COM
|
13
|
+
class IID
|
14
|
+
class FFIStruct < ::FFI::Struct
|
15
|
+
layout :m0, :uint32,
|
16
|
+
:m1, :uint16,
|
17
|
+
:m2, :uint16,
|
18
|
+
:m3, [:uint8, 8]
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_ffi
|
22
|
+
@ffi ||= begin
|
23
|
+
data = FFIStruct.new
|
24
|
+
data[:m0] = to_a[0]
|
25
|
+
data[:m1] = to_a[1]
|
26
|
+
data[:m2] = to_a[2]
|
27
|
+
to_a[3..-1].each_index{|i| data[:m3][i] = to_a[3..-1][i] }
|
28
|
+
data.freeze
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module VirtualBox
|
36
|
+
module COM
|
37
|
+
class AbstractInterface
|
38
|
+
def self.to_ffi
|
39
|
+
:pointer
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
module VirtualBox
|
47
|
+
module COM
|
48
|
+
class AbstractEnum
|
49
|
+
def self.to_ffi
|
50
|
+
UINT32
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
# Load FFI implementation
|
58
|
+
require_relative 'xpcomc-ffi/xpcomc-vbox'
|
59
|
+
require_relative 'xpcomc-ffi/binding'
|
60
|
+
require_relative 'xpcomc-ffi/implementer'
|
61
|
+
require_relative 'xpcomc-ffi/spec'
|
62
|
+
require_relative 'xpcomc-ffi/lib'
|
63
|
+
|
64
|
+
# Patch VirtualBox::COM
|
65
|
+
module VirtualBox
|
66
|
+
module COM
|
67
|
+
Implementer = XPCOMC::Implementer
|
68
|
+
Spec = XPCOMC::Spec
|
69
|
+
|
70
|
+
def self.virtualbox ; XPCOMC::Lib.virtualbox ; end
|
71
|
+
def self.session ; XPCOMC::Lib.session ; end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Init library
|
76
|
+
VirtualBox::COM::XPCOMC::Lib.init
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
module COM
|
3
|
+
module XPCOMC
|
4
|
+
|
5
|
+
# The Binding class hold all the FFI infrastructure
|
6
|
+
class Binding
|
7
|
+
extend ::FFI::Library
|
8
|
+
attr_reader :object
|
9
|
+
|
10
|
+
|
11
|
+
# Retrieve a Binding class corresponding to the Model
|
12
|
+
# This will avoid polluting the model object with implementation data
|
13
|
+
def self.get(name)
|
14
|
+
Binding.const_get(name, false)
|
15
|
+
rescue NameError
|
16
|
+
const_set(name, Class.new(Binding)).bind(Model.get(name))
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Create the binding with the Model class
|
21
|
+
# This will also create the internal classes Object and Vtbl
|
22
|
+
# representing the FFI::Struct, all the necessary callbacks,
|
23
|
+
# a hash of the name/signatures
|
24
|
+
def self.bind(model)
|
25
|
+
raise "model already bound" if const_defined?(:Object, false)
|
26
|
+
|
27
|
+
# List of functions (name, signature)
|
28
|
+
# Defined in the order they appear in the Model definition
|
29
|
+
sigs = model.members.inject({}) do |list, spec|
|
30
|
+
list.merge!(spec.signatures)
|
31
|
+
end
|
32
|
+
const_set(:Sig, sigs)
|
33
|
+
|
34
|
+
# Register ffi callbacks
|
35
|
+
sigs.each {|name, sig| callback(name, sig.to_ffi, :uint) }
|
36
|
+
|
37
|
+
# Object layout
|
38
|
+
const_set(:Object, Class.new(::FFI::Struct))
|
39
|
+
.layout(:vtbl, :pointer)
|
40
|
+
|
41
|
+
# Vtbl layout
|
42
|
+
const_set(:Vtbl, Class.new(::FFI::Struct))
|
43
|
+
.layout(*sigs.map {|name,| [name, name] }.flatten)
|
44
|
+
|
45
|
+
# IID
|
46
|
+
const_set(:IID, model::IID.to_ffi)
|
47
|
+
|
48
|
+
# Model
|
49
|
+
const_set(:Model, model)
|
50
|
+
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
# Initializes the interface to the FFI struct with the given pointer. The
|
56
|
+
# pointer is used to initialize the Object which is used to initialize
|
57
|
+
# the Vtbl itself.
|
58
|
+
def initialize(pointer)
|
59
|
+
@object = self.class::Object.new(pointer)
|
60
|
+
@vtbl = self.class::Vtbl.new(@object[:vtbl])
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# Calls a function on the vtbl of the FFI struct. This function handles
|
65
|
+
# converting the spec to proper arguments and also handles reading out
|
66
|
+
# the arguments, dereferencing pointers, setting up objects, etc. so that
|
67
|
+
# the return value is filled with nicely formatted Ruby objects.
|
68
|
+
#
|
69
|
+
# If the vtbl function being called only has one out parameter, then the
|
70
|
+
# return value will be that single object. If it has multiple, then it will
|
71
|
+
# be an array of objects.
|
72
|
+
def call(name, *args)
|
73
|
+
unless sig = self.class::Sig[name]
|
74
|
+
raise ArgumentError, "unknown function #{name} in Vtbl"
|
75
|
+
end
|
76
|
+
ffi_args = sig.prepare_args(args)
|
77
|
+
result = @vtbl[name].call(@object, *ffi_args)
|
78
|
+
if (result & 0x8000_0000) != 0
|
79
|
+
raise COMException, :vtbl => name, :code => result
|
80
|
+
end
|
81
|
+
sig.retrieve_values(ffi_args)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
module COM
|
3
|
+
module XPCOMC
|
4
|
+
|
5
|
+
# Implementer is a wrapper for the Binding class.
|
6
|
+
# Performs lazy initialisation and enhance function call
|
7
|
+
class Implementer
|
8
|
+
EXCEPTION_MAP = Hash.new(COMException).merge!({
|
9
|
+
0x8000_4001 => NotImplementedException,
|
10
|
+
0x8000_4002 => NoInterfaceException,
|
11
|
+
0x80BB_0001 => ObjectNotFoundException,
|
12
|
+
0x80BB_0002 => InvalidVMStateException,
|
13
|
+
0x80BB_0003 => VMErrorException,
|
14
|
+
0x80BB_0004 => FileErrorException,
|
15
|
+
0x80BB_0005 => SubsystemException,
|
16
|
+
0x80BB_0006 => PDMException,
|
17
|
+
0x80BB_0007 => InvalidObjectStateException,
|
18
|
+
0x80BB_0008 => HostErrorException,
|
19
|
+
0x80BB_0009 => NotSupportedException,
|
20
|
+
0x80BB_000A => XMLErrorException,
|
21
|
+
0x80BB_000B => InvalidSessionStateException,
|
22
|
+
0x80BB_000C => ObjectInUseException,
|
23
|
+
0x8007_0057 => InvalidArgException
|
24
|
+
}).freeze
|
25
|
+
|
26
|
+
|
27
|
+
# Initialize implementation of the COM interface
|
28
|
+
def initialize(interface, pointer)
|
29
|
+
unless interface.kind_of?(AbstractInterface)
|
30
|
+
raise ArgumentError, "only COM interface can be implemented"
|
31
|
+
end
|
32
|
+
@interface = interface # For lazy creation of the
|
33
|
+
@pointer = pointer # "binding" attribute
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# Cast to another interface.
|
38
|
+
# Will raise NoInterfaceException if not supported
|
39
|
+
def cast(interface, pointer)
|
40
|
+
iid = Binding.get(interface)::IID
|
41
|
+
Model.create(interface, binding.call(:QueryInterface, iid))
|
42
|
+
rescue COMException => e
|
43
|
+
e.data.merge!(:mode => :cast)
|
44
|
+
raise EXCEPTION_MAP[e.data[:code]], e.data
|
45
|
+
end
|
46
|
+
|
47
|
+
# Reads a property
|
48
|
+
def read_property(spec)
|
49
|
+
binding.call(spec.getter)
|
50
|
+
rescue COMException => e
|
51
|
+
e.data.merge!(:property => spec.name, :mode => :read)
|
52
|
+
raise EXCEPTION_MAP[e.data[:code]], e.data
|
53
|
+
end
|
54
|
+
|
55
|
+
# Writes a property
|
56
|
+
def write_property(spec, value)
|
57
|
+
binding.call(spec.setter, [value])
|
58
|
+
rescue COMException => e
|
59
|
+
e.data.merge!(:property => spec.name, :mode => :write)
|
60
|
+
raise EXCEPTION_MAP[e.data[:code]], e.data
|
61
|
+
end
|
62
|
+
|
63
|
+
# Calls a function
|
64
|
+
def call_function(spec, *args)
|
65
|
+
binding.call(spec.name, *args)
|
66
|
+
rescue COMException => e
|
67
|
+
e.data.merge!(:function => spec.name, :mode => :call)
|
68
|
+
raise EXCEPTION_MAP[e.data[:code]], e.data
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
#--[ Private ]---------------------------------------------------------
|
73
|
+
# private
|
74
|
+
|
75
|
+
# Lazy initialisation of the binding attribute
|
76
|
+
def binding
|
77
|
+
@binding ||= begin
|
78
|
+
name = @interface.class.name.split("::").last
|
79
|
+
Binding.get(name).new(@pointer)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|