gir_ffi 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/DESIGN.rdoc +54 -0
  2. data/History.txt +3 -0
  3. data/README.rdoc +59 -0
  4. data/Rakefile +21 -0
  5. data/TODO.rdoc +40 -0
  6. data/examples/01_empty_window.rb +15 -0
  7. data/examples/02_hello_world.rb +30 -0
  8. data/examples/03_upgraded_hello_world.rb +45 -0
  9. data/examples/demo_ffi_inherited_layout.rb +21 -0
  10. data/examples/demo_ffi_nested_struct.rb +17 -0
  11. data/examples/demo_ffi_safe_inherited_layout.rb +43 -0
  12. data/examples/hard_coded.rb +144 -0
  13. data/lib/gir_ffi.rb +47 -0
  14. data/lib/gir_ffi/allocation_helper.rb +12 -0
  15. data/lib/gir_ffi/arg_helper.rb +77 -0
  16. data/lib/gir_ffi/base.rb +23 -0
  17. data/lib/gir_ffi/builder.rb +159 -0
  18. data/lib/gir_ffi/builder_helper.rb +32 -0
  19. data/lib/gir_ffi/class_base.rb +11 -0
  20. data/lib/gir_ffi/class_builder.rb +116 -0
  21. data/lib/gir_ffi/constructor_definition_builder.rb +20 -0
  22. data/lib/gir_ffi/function_definition_builder.rb +148 -0
  23. data/lib/gir_ffi/g_error.rb +8 -0
  24. data/lib/gir_ffi/g_type.rb +14 -0
  25. data/lib/gir_ffi/i_arg_info.rb +16 -0
  26. data/lib/gir_ffi/i_base_info.rb +45 -0
  27. data/lib/gir_ffi/i_callable_info.rb +18 -0
  28. data/lib/gir_ffi/i_callback_info.rb +7 -0
  29. data/lib/gir_ffi/i_constant_info.rb +6 -0
  30. data/lib/gir_ffi/i_enum_info.rb +13 -0
  31. data/lib/gir_ffi/i_field_info.rb +10 -0
  32. data/lib/gir_ffi/i_flags_info.rb +5 -0
  33. data/lib/gir_ffi/i_function_info.rb +16 -0
  34. data/lib/gir_ffi/i_interface_info.rb +7 -0
  35. data/lib/gir_ffi/i_object_info.rb +50 -0
  36. data/lib/gir_ffi/i_property_info.rb +7 -0
  37. data/lib/gir_ffi/i_registered_type_info.rb +8 -0
  38. data/lib/gir_ffi/i_repository.rb +108 -0
  39. data/lib/gir_ffi/i_signal_info.rb +7 -0
  40. data/lib/gir_ffi/i_struct_info.rb +22 -0
  41. data/lib/gir_ffi/i_type_info.rb +25 -0
  42. data/lib/gir_ffi/i_union_info.rb +7 -0
  43. data/lib/gir_ffi/i_value_info.rb +8 -0
  44. data/lib/gir_ffi/i_vfunc_info.rb +7 -0
  45. data/lib/gir_ffi/lib.rb +174 -0
  46. data/lib/gir_ffi/lib_c.rb +11 -0
  47. data/lib/gir_ffi/method_missing_definition_builder.rb +62 -0
  48. data/lib/gir_ffi/module_builder.rb +66 -0
  49. data/lib/gir_ffi/overrides/gtk.rb +12 -0
  50. data/lib/gir_ffi/version.rb +4 -0
  51. data/tasks/bones.rake +87 -0
  52. data/tasks/notes.rake +134 -0
  53. data/tasks/post_load.rake +25 -0
  54. data/tasks/setup.rb +138 -0
  55. data/tasks/test.rake +22 -0
  56. data/test/arg_helper_test.rb +112 -0
  57. data/test/builder_test.rb +328 -0
  58. data/test/constructor_definition_builder_test.rb +19 -0
  59. data/test/function_definition_builder_test.rb +60 -0
  60. data/test/g_type_test.rb +22 -0
  61. data/test/girffi_test.rb +11 -0
  62. data/test/gtk_overrides_test.rb +22 -0
  63. data/test/i_repository_test.rb +54 -0
  64. data/test/test_helper.rb +39 -0
  65. metadata +174 -0
@@ -0,0 +1,54 @@
1
+ = Design of Gir-FFI
2
+
3
+ == Basic Idea
4
+
5
+ Gir-FFI uses FFI to read information from the GObject Introspection
6
+ Repository. Based on that it creates bindings for the information read.
7
+
8
+ == Class and method creation
9
+
10
+ GirFFI::Builder creates classes and modules at runtime and adds appropriate
11
+ method_missing methods to them to generate methods and perhaps other
12
+ classes when required.
13
+
14
+ The following options were discarded, at least for now.
15
+
16
+ * Create classes and all of their methods at runtime. This would be very
17
+ similar to the method chosen, but would concentrate all the overhead at
18
+ start-up, some of which would be used for creating methods that will
19
+ never get called.
20
+
21
+ * Allow offline creation of ruby source generated from the GIR. This is
22
+ still in interesting idea, but off-line source code generation is not
23
+ really the Ruby way.
24
+
25
+ == Method Naming
26
+
27
+ Probably, get_x/set_x pairs should become x and x= to be more Ruby-like.
28
+ This should be done either by defining them as such directly, or by
29
+ aliasing. Blindly going by the name leads to weird results thoough, like
30
+ having x, x= and x_full= as a set. Also, some get_ or set_ methods take
31
+ extra arguments. These probably shouldn't become accessors either.
32
+
33
+ Boolean-valued methods could get a ? at the end.
34
+
35
+ This requires a lot more thought. For now, the full method names as
36
+ defined in the GIR are used.
37
+
38
+ == Ruby-GNOME Compatibility
39
+
40
+ Full Ruby-GNOME compatibility cannot be achieved automatically, since its
41
+ object hierarchy differs from that of standard GObject: It puts Object in
42
+ the GLib namespace, and defines signal_connect and friends as methods of
43
+ GLib::Instantiable; In standard GObject they are functions.
44
+
45
+ Possibly, compatibility enhancing code can be added for these specific
46
+ exceptions.
47
+
48
+ == Bootstrapping Class Design
49
+
50
+ The interface to the GObject Introspection Repository itself is also
51
+ introspectable. The interface is specified in terms of structs and
52
+ functions rather than objects and methods. For now, the hard-coded Ruby
53
+ bindings for this don't follow the introspected specification: Gir-FFI
54
+ cannot bootstrap itself.
@@ -0,0 +1,3 @@
1
+ == 0.0.1 / 2009-11-09
2
+
3
+ * Nothing to see yet, move along.
@@ -0,0 +1,59 @@
1
+ = GirFFI
2
+
3
+ by Matijs van Zuijlen
4
+
5
+ http://www.github.com/mvz/ruby-gir-ffi
6
+
7
+ == Description
8
+
9
+ Ruby-FFI-based binding of the GObject Introspection Repository
10
+
11
+ == Features/Problems
12
+
13
+ * Create bindings to GObject-based libraries at runtime
14
+ * Not done yet
15
+
16
+ == Synopsis
17
+
18
+ require 'gir_ffi'
19
+
20
+ GirFFI.setup :Gtk
21
+ Gtk.init
22
+ win = Gtk::Window.new :toplevel
23
+
24
+ == Requirements
25
+
26
+ * Ruby-FFI of course
27
+ * gobject-introspection installed with some introspection data
28
+
29
+ The current implementation needs the actual libraries to be available under
30
+ the name ending in just `.so`. On Debian and Ubuntu at least, this means
31
+ you have to install the -dev packages of any library you may want to
32
+ access.
33
+
34
+ On Ubuntu, the following set of packages should do the trick:
35
+ `libffi-ruby`, `libgirepository1.0-dev`, `libgtk2.0-dev`,
36
+ `libshoulda-ruby`, `gir1.0-everything-1.0`.
37
+
38
+ On Debian it's the same, but the `gir1.0-everything-1.0` package is only
39
+ available in experimental. You can take one of the patches attached to bug
40
+ 550478 and recompile `gobject-introspection` to get the files GirFFI needs.
41
+
42
+ == Hacking
43
+
44
+ This is still very much a work in progress. You can start exploring by
45
+ running the example programs in the examples/ folder. Some illustrate what
46
+ works, some are a test bed for how things should work. Have a look at
47
+ +rake+ +-T+.
48
+
49
+ == Install
50
+
51
+ * sudo gem install gir_ffi
52
+
53
+ == License
54
+
55
+ Copyright (c) 2009--2010 Matijs van Zuijlen
56
+
57
+ GirFFI is free software, distributed under the terms of the GNU Lesser
58
+ General Public License, version 2.1 or later. See the file COPYING.LIB for
59
+ more information.
@@ -0,0 +1,21 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ load 'tasks/setup.rb'
6
+
7
+ ensure_in_path 'lib'
8
+ require 'gir_ffi'
9
+
10
+ task :default => 'test:run'
11
+
12
+ PROJ.name = 'gir_ffi'
13
+ PROJ.authors = 'Matijs van Zuijlen'
14
+ PROJ.email = 'matijs@matijs.net'
15
+ PROJ.url = 'http://www.github.com/mvz/ruby-gir-ffi'
16
+ PROJ.version = GirFFI::VERSION
17
+ PROJ.readme_file = 'README.rdoc'
18
+
19
+ PROJ.exclude << ["^tmp/", "\\.swp$", "^\\.gitignore$", "^\\.autotest$"]
20
+
21
+ # EOF
@@ -0,0 +1,40 @@
1
+ = TODO
2
+
3
+ == Use GIR to bootstrap the GIRepository namespace
4
+
5
+ Currently, all the classes used to read the GIR are hand-coded. It should
6
+ be possible to hand-code only part of it and use that to generate the rest.
7
+ This would also integrate that properly with the rest of the GObject type
8
+ system.
9
+
10
+ Update: This has been tried, but the problem is that the GIRepository
11
+ namespace is not object-oriented: The Info structs are not GObjects, and
12
+ the methods that act upon them are just functions in the GIRepository
13
+ namespace. Perhaps some custom method_missing can be implemented to handle
14
+ this, though.
15
+
16
+ == Handle passing of generic pointers
17
+
18
+ Many GObject methods take a pointer to 'user data'. This means we should be
19
+ able to pass any Ruby object. On the other hand, these cases cannot be
20
+ distinguished, based on the GIR data, from methods that take a pointer to
21
+ any GObject.
22
+
23
+ I'm currently leaning towards passing the object id as the value of the
24
+ 'gpointer'. Special overrided will have to be used for the cases where the
25
+ 'gpointer' actually needs to be a GObject. I consider it an omission in
26
+ GIRepository that these two cases are not distinguished.
27
+
28
+ == Compatibility with all implementations.
29
+
30
+ Currently, there are the following incompatibilities:
31
+
32
+ * The MRI implementation does not allow invoking layout more than once. In
33
+ particular, this means you cannot subclass a struct and change (augment)
34
+ the layout. This *not* a problem in JRuby.
35
+
36
+ * JRuby disables ObjectSpace by default, so using _id2ref is not ideal.
37
+
38
+ == See Also
39
+
40
+ rake notes
@@ -0,0 +1,15 @@
1
+ #
2
+ # Based on the empty window Gtk+ tutorial example at
3
+ # http://library.gnome.org/devel/gtk-tutorial/2.90/c39.html
4
+ #
5
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
6
+ require 'gir_ffi'
7
+
8
+ GirFFI.setup :GObject
9
+ GirFFI.setup :Gtk
10
+
11
+ Gtk.init
12
+ win = Gtk::Window.new :toplevel
13
+ win.show
14
+ GObject.signal_connect_data win, "destroy", Proc.new { Gtk.main_quit }, nil, nil, 0
15
+ Gtk.main
@@ -0,0 +1,30 @@
1
+ #
2
+ # Based on the 'Hello world' Gtk+ tutorial example at
3
+ # http://library.gnome.org/devel/gtk-tutorial/2.90/c39.html#SEC-HELLOWORLD
4
+ #
5
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
6
+ require 'gir_ffi'
7
+
8
+ GirFFI.setup :GObject
9
+ GirFFI.setup :Gtk
10
+
11
+ Gtk.init
12
+
13
+ win = Gtk::Window.new(:toplevel)
14
+ GObject.signal_connect_data win, "delete-event", FFI::Function.new(:bool, [:pointer, :pointer]) {
15
+ puts "delete event occured"
16
+ true
17
+ }, nil, nil, 0
18
+
19
+ GObject.signal_connect_data win, "destroy", Proc.new { Gtk.main_quit }, nil, nil, 0
20
+ win.set_border_width 10
21
+
22
+ but = Gtk::Button.new_with_label("Hello World")
23
+ GObject.signal_connect_data but, "clicked", Proc.new { win.destroy }, nil, nil, :swapped
24
+
25
+ win.add but
26
+
27
+ but.show
28
+ win.show
29
+
30
+ Gtk.main
@@ -0,0 +1,45 @@
1
+ #
2
+ # Based on the 'Upgraded Hello world' Gtk+ tutorial example at
3
+ # http://library.gnome.org/devel/gtk-tutorial/2.90/x344.html
4
+ #
5
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
6
+ require 'gir_ffi'
7
+
8
+ GirFFI.setup :GObject
9
+ GirFFI.setup :Gtk
10
+
11
+ callback = FFI::Function.new :void, [:pointer, :pointer],
12
+ &GirFFI::ArgHelper.mapped_callback_args { |widget, data|
13
+ puts "Hello again - #{data} was pressed"
14
+ }
15
+
16
+ Gtk.init
17
+
18
+ win = Gtk::Window.new(:toplevel)
19
+ win.set_title "Hello Buttons!"
20
+
21
+ GObject.signal_connect_data win, "delete-event", FFI::Function.new(:bool, [:pointer, :pointer]) {
22
+ Gtk.main_quit
23
+ false
24
+ }, nil, nil, 0
25
+
26
+ win.set_border_width 10
27
+
28
+ box = Gtk::HBox.new(false, 0)
29
+ win.add box
30
+
31
+ button = Gtk::Button.new_with_label("Button 1")
32
+ GObject.signal_connect_data button, "clicked", callback, "button 1", nil, 0
33
+ box.pack_start button, true, true, 0
34
+ button.show
35
+
36
+ button = Gtk::Button.new_with_label("Button 2")
37
+ GObject.signal_connect_data button, "clicked", callback, "button 2", nil, 0
38
+ box.pack_start button, true, true, 0
39
+ button.show
40
+
41
+ box.show
42
+ win.show
43
+
44
+ Gtk.main
45
+
@@ -0,0 +1,21 @@
1
+ # Demonstration program for FFI functionality.
2
+ #
3
+ # Show what happens if we call layout again in a subclass. This works in
4
+ # JRuby, but not in MRI (gives warnings with ffi 0.6.3, is explicitely
5
+ # forbidden later).
6
+ #
7
+ require 'ffi'
8
+
9
+ class Foo < FFI::Struct
10
+ layout :a, :int, :b, :int
11
+ end
12
+
13
+ class Bar < Foo
14
+ layout :p, Foo, :c, :int
15
+ end
16
+
17
+ bar = Bar.new
18
+ foo = Foo.new(bar.to_ptr)
19
+ foo[:a] = 20
20
+ puts "bar[:p][:a] = #{bar[:p][:a]}"
21
+
@@ -0,0 +1,17 @@
1
+ # Demonstration program for FFI functionality.
2
+ #
3
+ # Basic demo of nested struct. Works in MRI, YARV, and JRuby. Does not work
4
+ # in Rubinius.
5
+ #
6
+ require 'ffi'
7
+
8
+ module LibTest
9
+ class Foo < FFI::Struct
10
+ layout :a, :int, :b, :int
11
+ end
12
+
13
+ class Bar < FFI::Struct
14
+ layout :f, Foo, :g, :int
15
+ end
16
+ end
17
+ puts LibTest::Bar.members.inspect
@@ -0,0 +1,43 @@
1
+ # Demonstrate safe inheritance with layout.
2
+ #
3
+ # Uses nested Struct class to separate inheritance from FFI::Struct from
4
+ # main inheritance structure. Works with MRI and JRuby, without warnings.
5
+
6
+ require 'ffi'
7
+ require 'forwardable'
8
+
9
+ class Foo
10
+ extend Forwardable
11
+ def_delegators :@struct, :[], :to_ptr
12
+
13
+ class Struct < FFI::Struct
14
+ layout :a, :int, :b, :int
15
+ end
16
+
17
+ def initialize(ptr=nil)
18
+ @struct = ptr.nil? ?
19
+ self.ffi_structure.new :
20
+ self.ffi_structure.new(ptr)
21
+ end
22
+
23
+ def ffi_structure
24
+ self.class.ffi_structure
25
+ end
26
+
27
+ class << self
28
+ def ffi_structure
29
+ self.const_get(:Struct)
30
+ end
31
+ end
32
+ end
33
+
34
+ class Bar < Foo
35
+ class Struct < FFI::Struct
36
+ layout :p, Foo.ffi_structure, :c, :int
37
+ end
38
+ end
39
+
40
+ bar = Bar.new
41
+ bar[:p][:a] = 20
42
+ foo = Foo.new(bar.to_ptr)
43
+ puts foo[:a]
@@ -0,0 +1,144 @@
1
+ # Hard-code FFI-based Gtk+ test program. Nothing is generated here.
2
+ require 'ffi'
3
+ module GObject
4
+
5
+ module Lib
6
+ extend FFI::Library
7
+ CALLBACKS = []
8
+ ffi_lib "gobject-2.0"
9
+ callback :GCallback, [], :void
10
+ enum :GConnectFlags, [:AFTER, (1<<0), :SWAPPED, (1<<1)]
11
+
12
+ attach_function :g_signal_connect_data, [:pointer, :string, :GCallback,
13
+ :pointer, :pointer, :GConnectFlags], :ulong
14
+ end
15
+
16
+ def self.signal_connect_data gobject, signal, prc, data, destroy_data, connect_flags
17
+ Lib::CALLBACKS << prc
18
+ Lib.g_signal_connect_data gobject.to_ptr, signal, prc, data, destroy_data, connect_flags
19
+ end
20
+ end
21
+
22
+ module Gtk
23
+ module Lib
24
+ extend FFI::Library
25
+
26
+ ffi_lib "gtk-x11-2.0"
27
+ attach_function :gtk_init, [:pointer, :pointer], :void
28
+ attach_function :gtk_main, [], :void
29
+ attach_function :gtk_main_quit, [], :void
30
+
31
+ attach_function :gtk_widget_show, [:pointer], :pointer
32
+ attach_function :gtk_widget_destroy, [:pointer], :void
33
+ attach_function :gtk_container_add, [:pointer, :pointer], :void
34
+
35
+ enum :GtkWindowType, [:GTK_WINDOW_TOPLEVEL, :GTK_WINDOW_POPUP]
36
+ attach_function :gtk_window_new, [:GtkWindowType], :pointer
37
+ attach_function :gtk_button_new, [], :pointer
38
+ attach_function :gtk_button_new_with_label, [:string], :pointer
39
+ attach_function :gtk_label_new, [:string], :pointer
40
+ end
41
+
42
+ def self.init size, ary
43
+ argv = self.string_array_to_inoutptr ary
44
+ argc = self.int_to_inoutptr(size)
45
+
46
+ Lib.gtk_init argc, argv
47
+
48
+ outsize = self.outptr_to_int argc
49
+ outary = self.outptr_to_string_array argv, ary.nil? ? 0 : ary.size
50
+
51
+ return outsize, outary
52
+ end
53
+
54
+ def self.int_to_inoutptr val
55
+ ptr = FFI::MemoryPointer.new(:int)
56
+ ptr.write_int val
57
+ return ptr
58
+ end
59
+
60
+ # Note: This implementation would dump core if the garbage collector runs
61
+ # before the contents of the pointers is used.
62
+ def self.string_array_to_inoutptr ary
63
+ ptrs = ary.map {|a| FFI::MemoryPointer.from_string(a)}
64
+ block = FFI::MemoryPointer.new(:pointer, ptrs.length)
65
+ block.write_array_of_pointer ptrs
66
+ argv = FFI::MemoryPointer.new(:pointer)
67
+ argv.write_pointer block
68
+ argv
69
+ end
70
+
71
+ def self.outptr_to_int ptr
72
+ return ptr.read_int
73
+ end
74
+
75
+ def self.outptr_to_string_array ptr, size
76
+ block = ptr.read_pointer
77
+ ptrs = block.read_array_of_pointer(size)
78
+ return ptrs.map {|p| p.null? ? nil : p.read_string}
79
+ end
80
+
81
+ def self.main; Lib.gtk_main; end
82
+ def self.main_quit; Lib.gtk_main_quit; end
83
+
84
+ class Widget
85
+ def show
86
+ Lib.gtk_widget_show(@gobj)
87
+ end
88
+ def destroy
89
+ Lib.gtk_widget_destroy(@gobj)
90
+ end
91
+ def to_ptr
92
+ @gobj
93
+ end
94
+ end
95
+
96
+ class Container < Widget
97
+ def add widget
98
+ Lib.gtk_container_add self.to_ptr, widget.to_ptr
99
+ end
100
+ end
101
+
102
+ class Window < Container
103
+ def initialize type
104
+ @gobj = Lib.gtk_window_new(type)
105
+ end
106
+ end
107
+
108
+ class Button < Container
109
+ def initialize ptr
110
+ @gobj = ptr
111
+ end
112
+ class << self
113
+ alias :real_new :new
114
+ end
115
+ def self.new
116
+ self.real_new Lib.gtk_button_new()
117
+ end
118
+ def self.new_with_label text
119
+ self.real_new Lib.gtk_button_new_with_label(text)
120
+ end
121
+ end
122
+ end
123
+
124
+ (my_len, my_args) = Gtk.init ARGV.length + 1, [$0, *ARGV]
125
+ p my_len, my_args
126
+ win = Gtk::Window.new(:GTK_WINDOW_TOPLEVEL)
127
+ btn = Gtk::Button.new_with_label('Hello World')
128
+ win.add btn
129
+
130
+ quit_prc = Proc.new { Gtk.main_quit }
131
+
132
+ # We can create callbacks with a different signature by using FFI::Function
133
+ # directly.
134
+ del_prc = FFI::Function.new(:bool, [:pointer, :pointer]) {|a, b|
135
+ puts "delete event occured"
136
+ true
137
+ }
138
+ GObject.signal_connect_data(win, "destroy", quit_prc, nil, nil, 0)
139
+ GObject.signal_connect_data(win, "delete-event", del_prc, nil, nil, 0)
140
+ GObject.signal_connect_data(btn, "clicked", Proc.new { win.destroy }, nil, nil, :SWAPPED)
141
+
142
+ btn.show
143
+ win.show
144
+ Gtk.main