gir_ffi 0.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.
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