virtualbox-com 0.9.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|