diff --git a/README.markdown b/README.markdown index 2cbc39b..7fc9ae9 100644 --- a/README.markdown +++ b/README.markdown @@ -1,7 +1,7 @@ # node-gir Node-gir is node bindings to the girepository library making it possible to make -automatic and dynamic calls to any library that has GI annotations installed. +automatic and dynamic calls to any library that has GI annotations installed. This will make it possible to script a gnome desktop system entirely from node much in the way it's done today with Seed, GJS or pygtk. @@ -74,7 +74,7 @@ missing part is node bindings to libgirepository. Hence this project. ## Why not use Seed or GJS Because they are nice, but not what I'm looking for. Node is really popular and -it would be nice to be able to use it for desktop tools and applications. +it would be nice to be able to use it for desktop tools and applications. ## Implementation Notes @@ -86,31 +86,31 @@ Here are some links and notes as I try to figure out how to do this. ## API Ideas Some of these ideas will go in this binding and some will go in nice wrappers -that use it. I'll know more as we progress. +that use it. I'll know more as we progress. - - Use `camelCase` for methods that are bound to look JavaScripty. - - Use `.on(name, callback)` to attach signals. - - Keep the same constructor style used by Seed and GJS + - Keep the same constructor style used by Seed and GJS. - Make the module system as node-like as possible. + - Identify static objects/members versus instance objects/members. ## Things which work - - All classes get created - - classes get inherited - - A class has lists of all its properties, methods, signals, vfuncs and fields - - You can create a class - - functions can be called (but it does not work so well) - - property values can be set/get - - events can be watched - - flags, enums etc are set + - All classes get created. Classes get inherited. + - A class has lists of all its properties, methods, signals, vfuncs, and fields. Use inspector example to print a complete dump of object members prior to an object instance. Object instances may vary from the prototypes. + - Properties, methods, and vfuncs use `conventionalCamelCasing` for names. They are wired into the `otherwise\_unconventional\_underscores` and `dashes-in-names` counterparts on the back end. + - Events can be watched. Signals are associated with `EventEmitter` object provided by Node. Use `.on('signal-name', callback)` to attach signals, or use shortcut syntax `onSignalName(callback)`. + - Methods can be called. + - Property values can be set/get. JavaScript getters/setters are in-place to ensure that properties are queried properly. + - Flags, Enums, etc., are set. ## Things which dont work (correct) - Conversion between a v8 value and a GValue/GArgument is veeeery buggy (but everything needs it so most things are buggy) - - The API is inconsistent (classes just have \_\_call\_method\_\_, \_\_get\_prroperty\_\_ etc + - The API is becoming more consistent (classes just have \_\_call\_method\_\_, \_\_get\_prroperty\_\_ etc but the namespace has all methods [ gst.main(), gst.mainQuit()] - No support for libev/libuv; glib is using its own stuff (gst.main()) - There is no good way to delete an object (memory management sucks at all) - - You can't pass construction parameters to g_object_new + - You can't pass construction parameters to g\_object\_new - Only the GObject and Function type is implementet yet (left are GIInterfaceInfo and GIStructInfo) - types/function.cc need a rewrite + - Static members fail, usually with TypeError. This probably means the actual objects need members to be set in addition to the prototypes, but there is no way to tell which is static and which is instance. + - Some objects that are returned by methods (and possibly properties) do not have the `\_\_call\_\_`, `\_\_get\_property\_\_`, `\_\_set\_property\_\_`, and friends prototype members. This produces 'Error: bad arguments' on the JavaScript consumer. diff --git a/examples/browser.js b/examples/browser.js deleted file mode 100644 index 251fcce..0000000 --- a/examples/browser.js +++ /dev/null @@ -1,24 +0,0 @@ -var gtk = require("./gtk") - , WebKit = require("./webkit"); - -gtk.init(0); - -var win = new gtk.Window(); - -win.on('destroy', function() { - console.log('Window destroyed'); - gtk.mainQuit(); - process.exit(); -}); - -var sw = new gtk.ScrolledWindow(); -win.add(sw); - -var view = new WebKit.WebView(); -view.load_uri("http://www.google.com/"); -sw.add(view); - -win.set_size_request(640, 480); -win.show_all(); - -gtk.main(); diff --git a/examples/clutter.js b/examples/clutter.js index 8c3c4b4..7db14f8 100644 --- a/examples/clutter.js +++ b/examples/clutter.js @@ -1,2 +1,33 @@ var gir = require('../gir') - , clutter = module.exports = gir.load('Clutter'); + , clutter = gir.import('Clutter'); + +clutter.init(0); + +//I used .prototype because I need a static object. Not sure if this is right. +var sm = clutter.StageManager.prototype; + +console.log('StageManager:', sm); + +/* TODO: Fix throws TypeError: Illegal invocation */ +var stage = sm.getDefaultStage(); + +//for (var s in clutter) console.log(s); + +console.log('StageManager:', sm, 'Stage:', stage); + +stage.title = 'node-gir Clutter Example'; +stage.setSize(400,300); +stage.setColor(0,0,0,127); +stage.show(); + +stage.onButtonPressEvent(function(a,b,c) { + console.log('button press event', a, b, c); +}); + +stage.onDestroy(function() { + console.log('destroy'); + clutter.mainQuit(); + process.exit(); +}); + +clutter.main(); diff --git a/examples/dump-clutter.js b/examples/dump-clutter.js deleted file mode 100644 index 6f848cd..0000000 --- a/examples/dump-clutter.js +++ /dev/null @@ -1,2 +0,0 @@ -var clutter = require('./clutter.js'); -console.log(clutter); diff --git a/examples/dump-midgard.js b/examples/dump-midgard.js deleted file mode 100644 index c0b0f42..0000000 --- a/examples/dump-midgard.js +++ /dev/null @@ -1,2 +0,0 @@ -var Midgard = require('./Midgard'); -console.log(Midgard); diff --git a/examples/gtk.js b/examples/gtk.js index af902e7..2081982 100644 --- a/examples/gtk.js +++ b/examples/gtk.js @@ -1,2 +1,47 @@ var gir = require('../gir') - , gtk = module.exports = gir.load('Gtk', '3.0'); + , gtk = gir.import('Gtk', '3.0'); + +gtk.init(0); + +var win = new gtk.Window({type: gtk.WindowType.toplevel, title:"GTK Example"}) + , vbox = new gtk.VBox(1,3) + , label = new gtk.Label() + , button = new gtk.Button() + , quitButton = new gtk.Button(); + +win.borderWidth = 10; +win.widthRequest = 200; + +button.label = "CLICK ME!"; +quitButton.label = "Quit"; + +vbox.add(button); +vbox.add(label); +vbox.add(quitButton); + +win.name = "gtktest"; +win.add(vbox); +win.showAll(); + +//window destroyed +win.onDestroy(function() { + //debug:console.log("destroyed", arguments[0] instanceof gtk.Window); + gtk.mainQuit(); + process.exit(); +}); + +var clicks = 0; +//user clicked CLICK ME! button +button.onClicked(function() { + //debug:console.log("click :)", arguments[0] instanceof gtk.Button, arguments[0] == button); + //testing objects returned from members in c-land:console.log("click :)", button, button, button.window); + label.label = 'Clicked ' + ++clicks + ' times.'; +}); + +//user clicked Quit button +quitButton.onClicked(function() { + win.destroy(); +}); + +//start event loop +gtk.main(); diff --git a/examples/gtk_test.js b/examples/gtk_test.js deleted file mode 100644 index 7426f84..0000000 --- a/examples/gtk_test.js +++ /dev/null @@ -1,29 +0,0 @@ -var gtk = require("./gtk"); - -gtk.init(0); - -var win = new gtk.Window({type: gtk.WindowType.toplevel, title:"Node.JS GTK Window"}); -var button = new gtk.Button(); - -win.set_border_width(10); - -button.set_label("hallo, welt!"); - -win.add(button); -win.show_all(); - -var w2 = button.get_parent_window(); -console.log(w2); - -win.on("destroy", function() { - console.log("destroyed", arguments[0] instanceof gtk.Window); - gtk.mainQuit(); -}); -button.on("clicked", function() { - console.log("click :)", arguments[0] instanceof gtk.Button, arguments[0] == button); -}); - -console.log(win.set_property("name", "test")); -console.log(win.__get_property__("name")); - -gtk.main(); diff --git a/examples/inspector.js b/examples/inspector.js new file mode 100644 index 0000000..5ad2ef8 --- /dev/null +++ b/examples/inspector.js @@ -0,0 +1,255 @@ +var gir = require('../gir') + , util = require('util'); + +console.warn('[inspector] Using node version', process.version + '.'); + +process.argv.forEach(function (val, index, args) { + if (args.length < 3) { + console.log('Usage: node inspector [options] type [version [object [object ...]]]'); + console.log('Description: Inspects each type name passed as an argument.'); + console.log(''); + console.log('Syntax 1: node inspector type [version [object [object ...]]]'); + console.log('Syntax 2: node inspector [-v] type [version]'); + console.log('Syntax 3: node inspector [-g] type [version] [object]'); + console.log(''); + console.log('Parameters:'); + console.log(' options: optional, one or more options.'); + console.log(' type: required type name to load from girepository.'); + console.log(' version: optional, type version to load from girepository.'); + console.log(' object: optional, multiple, fields you intend to inspect.'); + console.log(''); + console.log('Options:'); + console.log(''); + console.log(' -v Prints version list for a given type. Do not provide objects with -v.'); + console.log(' -g Loads in GTK GUI mode. Only the first object will be browsed, if provided.'); + console.log(''); + console.log('Example 1: node inspector Gtk'); + console.log(' Prints tree for Gtk to stdout.'); + console.log(''); + console.log('Example 2: node inspector Gtk 3.0'); + console.log(' Prints tree for Gtk version 3.0 to stdout.'); + console.log(''); + console.log('Example 3: node inspector Notify > notify_tree.txt'); + console.log(' Prints tree for Notify with stdout redirected to a file.'); + console.log(''); + console.log('Example 4: node inspector Clutter 1.0 Actor Texture'); + console.log(' Prints tree for Clutter version 1.0 Actor and Texture objects.'); + console.log(''); + console.log('Example 5: node inspector -g Gtk 3.0 Window'); + console.log(' Opens GUI and browses to the GTK 3.0 Window object.'); + console.log(''); + process.exit(); + } + else { + if (args[2] == '-v') { + dumpTypeVersions(importType(args[3])); + process.exit(); + } + else if (args[2] == '-g') { + if (args.length > 3) + launchGtk.apply(this, args.slice(3)); + else + launchGtk(); + process.exit(); + } + var mod = args[2], ver = args[3]; + var module = importType(mod, ver); + if (ver != undefined) { + if (args.length > 4) for (var a = 4; a < args.length; a++) { + dumpTypeInfo(module, args[a]); + } + else + dumpTypeInfo(module); + } + else + dumpTypeInfo(module); + process.exit(); + } +}); + +/** + * Imports a girepository type with a specified version. + **/ +function importType(type, ver) { + if (ver != undefined) + try { + return gir.import(type, ver); + } + catch (e) { + console.warn('In importType(),', e); + return gir.import(type); + } + else + return gir.import(type); +} + +/** + * Prints a type or some of its objects out to a string. + **/ +function getTypeInfo(loadedType, obj) { + if (obj != undefined) + return 'Type information for .import(\'' + loadedType.__name__ + '\',\'' + loadedType.__version__ + '\').' + obj + ':' + + '\n' + util.inspect(loadedType[obj]) + '\n'; + else + return 'Type information for .import(\'' + loadedType.__name__ + '\',\'' + loadedType.__version__ + '\')' + ':' + + '\n' + util.inspect(loadedType) + '\n'; +} + +/** + * Prints a type or some of its objects out to a string. + **/ +function getTypeVersions(loadedType) { + return gir._girepository.getVersions(loadedType.__name__); +} + +/** + * Dumps a type or some of its objects out to stdout. + **/ +function dumpTypeInfo(loadedType, obj) { + if (obj != undefined) + console.log(getTypeInfo(loadedType, obj)); + else + console.log(getTypeInfo(loadedType)); +} + +/** + * Dumps type versions to stdout. + **/ +function dumpTypeVersions(loadedType) { + console.log("'"+loadedType.__name__+"'", 'available in versions', getTypeVersions(loadedType)); +} + +/** + * In progress: Dumps complete tree of every member of every installed version + * of every object returned when loading the namespace from girepository. + * Will appear in the tree view. + **/ +function gtkDumpTypeTree(win, type, brwsVer, brwsObj) { +console.log(win, type, brwsVer, brwsObj); + var loadedType = importType(type, brwsVer); + //dump info to gtk + gtkDumpTypeInfo(win, loadedType, brwsObj); + //dump info to console + dumpTypeInfo(loadedType, brwsObj); +} + +/** + * Dumps type information to GTK window. + **/ +function gtkDumpTypeInfo(win, loadedType, obj) { + if (obj != undefined) + win.textInfoBuffer.text = getTypeInfo(loadedType, obj); + else + win.textInfoBuffer.text = getTypeInfo(loadedType); +} + +/** + * Launches the GTK GUI. Returns the Gtk.Window object. + **/ +function launchGtk(type, brwsVer, brwsObj) { + var gtk = gir.import('Gtk'); + console.warn('[inspector] Using GTK version', gtk.__version__ + '.'); + gtk.init(0); + + var win = new gtk.Window({type: gtk.WindowType.toplevel, title:'GIR Inspector', name:'GIR Inspector'}); + win.gtk = gtk; + win.vboxOuter = new win.gtk.VBox(1,2); + win.hboxInner = new win.gtk.HBox(1,2); + + win.menuBar = new win.gtk.MenuBar(); + + win.menuFile = new win.gtk.Menu(); + win.menuFileFile = new win.gtk.MenuItem('File'); + win.menuFileQuit = new win.gtk.MenuItem('Quit'); + + win.menuSamples = new win.gtk.Menu(); + win.menuSamplesSamples = new win.gtk.MenuItem('Samples'); + + win.menuHelp = new win.gtk.Menu(); + win.menuHelpHelp = new win.gtk.MenuItem('Help'); + win.menuHelpAbout = new win.gtk.MenuItem('About...'); + + //these menus do not work, and i greatly suspect it may have something to do with the issues below + win.menuFileFile.setSubmenu(win.menuFile); + win.menuSamplesSamples.setSubmenu(win.menuSamples); + win.menuHelpHelp.setSubmenu(win.menuHelp); + + win.menuFile.append(win.menuFileQuit); + win.menuHelp.append(win.menuHelpAbout); + + win.menuBar.append(win.menuFile); + win.menuBar.append(win.menuSamples); + win.menuBar.append(win.menuHelp); + + win.vboxOuter.packStart(win.menuBar, false, true, 0); + win.vboxOuter.packStart(win.hboxInner, true, true, 0); + + win.vboxInnerLeft = new win.gtk.VBox(1,2); + win.hboxInnerLeftTop = new win.gtk.HBox(1,3); + + win.treeStore = new win.gtk.TreeStore('Inspect'); + win.treeView = new win.gtk.TreeView(); + win.treeView.model = win.treeStore; + win.treeColumn = new win.gtk.TreeViewColumn({title:'Inspect'}); + win.treeView.appendColumn(win.treeColumn); + win.treeView.heightRequest = 300; + + win.labelSearch = new win.gtk.Label({label: 'Object Type:'}); + win.entrySearch = new win.gtk.Entry(); + win.entrySearch.widthRequest = 200; + win.buttonSearch = new win.gtk.Button({label: 'Import Object'}); + win.buttonSearch.onClicked(function () { + gtkDumpTypeTree(win, win.entrySearch.text) + }); + + win.scrolledTextWindow = new win.gtk.ScrolledWindow(); + win.textInfo = new win.gtk.TextView(); + win.textInfoBuffer = win.textInfo.getBuffer(); + win.textInfoBufferFontTag = new win.gtk.TextTag(); + //win.textInfoBufferFontTag = win.textInfoBuffer.createTag('monofont'); //fails, see next comment + win.textInfoBufferFontTag.family = 'monospace'; + win.textInfoBuffer.tagTable.add(win.textInfoBuffer); +//debug: for (var x in win.gtk.TextBuffer.__methods__) console.log(win.gtk.TextBuffer.__methods__[x]); + win.textInfoBuffer.text = 'Enter an object type name in the search box, then click Import Object.'; +// this is broken! i think the problem is in object.cc in CallMethod(). basically, CallMethod() +// is returning a valid object with __properties__ and friends, but for some reason it left it's +// good old pals __call__, __get_property__, etc. behind. that means the object returned by a +// method can not be further interacted with. i can't seem to find the the perfect spot to plant +// an additional call to GIRObject::SetPrototypeMethods() in or around CallMethod() or the function.cc +// Func::Call(). it returns 'bad arguments', but it is the complete lack of __call__ on the object +// causing the error. --david +// win.textInfoBuffer.applyTag(win.textInfoBufferFontTag, win.textInfoBuffer.getStartIter(), win.textInfoBuffer.getEndIter()); + win.textInfo.editable = false; + win.scrolledTextWindow.widthRequest = 480; + + win.hboxInnerLeftTop.packStart(win.labelSearch, false, false, 5); + win.hboxInnerLeftTop.packStart(win.entrySearch, false, false, 5); + win.hboxInnerLeftTop.packStart(win.buttonSearch, false, false, 0); + win.vboxInnerLeft.packStart(win.hboxInnerLeftTop, false, false, 5); + win.vboxInnerLeft.packStart(win.treeView, true, true, 0); + + win.hboxInner.packStart(win.vboxInnerLeft, false, true, 5); + win.scrolledTextWindow.add(win.textInfo); + win.hboxInner.packStart(win.scrolledTextWindow, true, true, 0); + + win.add(win.vboxOuter); + + win.onDestroy(function() { + win.gtk.mainQuit(); + process.exit(); + }); + win.menuFileQuit.onActivate(function () { win.destroy(); }); + + win.menuBar.showAll(); + win.showAll(); + + if (type != undefined) { + win.entrySearch.text = type; + gtkDumpTypeTree(win, type, brwsVer, brwsObj); + } + + win.heightRequest = 500; + win.gtk.main(); + + return win; +} diff --git a/examples/libxml2.js b/examples/libxml2.js index f97c75c..ec9260a 100644 --- a/examples/libxml2.js +++ b/examples/libxml2.js @@ -1,2 +1,2 @@ var gir = require('../gir'), - , libxml2 = exports.gtk = gir.load('libxml2'); + , libxml2 = exports.gtk = gir.import('libxml2'); diff --git a/examples/midgard.js b/examples/midgard.js index 5909752..4c2d2f3 100644 --- a/examples/midgard.js +++ b/examples/midgard.js @@ -1,2 +1,2 @@ var gir = require('../gir') - , Midgard = module.exports = gir.load('Midgard'); + , Midgard = module.exports = gir.import('Midgard'); diff --git a/examples/multiple_test.js b/examples/multiple_test.js index fd91e73..668d866 100644 --- a/examples/multiple_test.js +++ b/examples/multiple_test.js @@ -1,5 +1,44 @@ -var gtk = require('./gtk') - , notify = require('./notify'); +var gir = require('../gir') + , gtk = gir.import('Gtk', '3.0') + , WebKit = gir.import('WebKit', '3.0') + , notify = gir.import('Notify') + , appTitle = 'WebKit with Notify Example'; -notify.init('a'); -gtk.init(0, null); +gtk.init(0); +notify.init(appTitle); + +var win = new gtk.Window(); + +win.onDestroy(function() { + console.log('Window destroyed'); + gtk.mainQuit(); + process.exit(); +}); + +var sw = new gtk.ScrolledWindow(); +win.add(sw); + +var view = new WebKit.WebView(); + +view.onTitleChanged(function() { + win.title = view.title + ' - ' + appTitle; +}); + +view.onLoadFinished(function() { + var n = new notify.Notification(); + n.update('URL Loaded', view.uri); + setTimeout(function () { + n.close(); + }, 1500); + n.show(); +}); + +view.loadUri("http://www.yahoo.com/"); +sw.add(view); + +win.setSizeRequest(640, 480); +win.showAll(); + +win.title = appTitle; + +gtk.main(); diff --git a/examples/namespaces.js b/examples/namespaces.js new file mode 100644 index 0000000..445db31 --- /dev/null +++ b/examples/namespaces.js @@ -0,0 +1,14 @@ +var gir = require("../gir"); + +gir.init(); +console.log(gir.searchPath()); + +gir.import("Gtk"); + +console.log(gir.loadedNamespaces()); + +console.log(gir.getDependencies("Gtk")); + +console.log(gir.getVersions("Gtk")); + +console.log(gir.isRegistered("Gtk", "3.0")); diff --git a/examples/notify.js b/examples/notify.js index e83d079..5612224 100644 --- a/examples/notify.js +++ b/examples/notify.js @@ -1,2 +1,36 @@ +//docs: http://developer.gnome.org/libnotify/0.7/NotifyNotification.html var gir = require('../gir') - , notify = module.exports = gir.load('Notify'); + , notify = gir.load('Notify'); + +console.log(notify.init('Notify Example')); + +var n = new notify.Notification(); +var created = n.__call__('new', 'a', 'a', 'a', 'a'); + +for(var k in n) { + console.log(k); +} + +console.log(notify.Notification.__methods__); + +n.update('Notify Example', 'This is an example notification from Node.JS.'); +n.show(); + +setTimeout( + function () { + n.update('Notification Update', 'The status has been updated!'); + n.show(); + setTimeout( + function () { + n.update('Ethan', 'This message will self-destruct in 5 seconds.'); + n.show(); + setTimeout( + function () { + console.log('Adios!'); + n.close(); + }, 5000 + ); + }, 4000 + ); + }, 3000 +); diff --git a/examples/notify_test.js b/examples/notify_test.js deleted file mode 100644 index f0bae00..0000000 --- a/examples/notify_test.js +++ /dev/null @@ -1,35 +0,0 @@ -//docs: http://developer.gnome.org/libnotify/0.7/NotifyNotification.html -var notify = require('./notify'); - -console.log(notify.init('notify_test.js sample application')); - -var n = new notify.Notification(); -var created = n.__call__('new', 'a', 'a', 'a', 'a'); - -for(var k in n) { - console.log(k); -} - -console.log(notify.Notification.__methods__); - -n.update('Notify Test', 'This is a test notification message via Node.JS.'); -n.show(); - -setTimeout( - function () { - n.update('Notification Update', 'The status has been updated!'); - n.show(); - setTimeout( - function () { - n.update('Ethan', 'This message will self-destruct in 5 seconds.'); - n.show(); - setTimeout( - function () { - n.close(); - console.log('Adios!'); - }, 5000 - ); - }, 4000 - ); - }, 3000 -); diff --git a/examples/webkit.js b/examples/webkit.js index 744b0ab..9a45b4b 100644 --- a/examples/webkit.js +++ b/examples/webkit.js @@ -1,2 +1,33 @@ var gir = require('../gir') - , webkit = module.exports = gir.load('WebKit', '3.0'); + , gtk = gir.import('Gtk', '3.0') + , WebKit = gir.import('WebKit', '3.0') + , appTitle = 'Webkit Example'; + +gtk.init(0); + +var win = new gtk.Window(); + +win.onDestroy(function() { + console.log('Window destroyed'); + gtk.mainQuit(); + process.exit(); +}); + +var sw = new gtk.ScrolledWindow(); +win.add(sw); + +var view = new WebKit.WebView(); + +view.onTitleChanged(function() { + win.title = view.title + ' - ' + appTitle; +}); + +view.loadUri("http://www.google.com/"); +sw.add(view); + +win.setSizeRequest(640, 480); +win.showAll(); + +win.title = appTitle; + +gtk.main(); diff --git a/gir.js b/gir.js index 0fcf77f..ae2b0b3 100644 --- a/gir.js +++ b/gir.js @@ -1,16 +1,16 @@ /** * Example use of this module: * var gir = require('./path/to/gir.js') - * , gtk = gir.load('Gtk', '3.0'); + * , gtk = gir.import('Gtk', '3.0'); **/ -//import gir library and EventEmitter -var gir = module.exports = require('./build/Release/girepository.node') +//define export object, import girepository and EventEmitter onto export object +var gir = module.exports = { '_girepository': require('./build/Release/girepository.node') } , EventEmitter = require('events').EventEmitter; /******************************************************************************/ -/* BEGIN HELPERS */ +/* BEGIN EXTERNAL HELPERS */ /** * Adopted from jquery's extend method. Under the terms of MIT License. @@ -152,50 +152,176 @@ function makeArray(array, results) { return ret; } -/* END HELPERS */ +/* END EXTERNAL HELPERS */ + +/******************************************************************************/ + +/* BEGIN INTERNAL HELPERS */ + +/** + * Converts underscored_names or dashed-names to properCamelCasing. + **/ +function camelCase(x) { + return x.replace(/[-]+/g, function (h) { + return '_'; + }).replace(/\_[a-z]/g, function(h) { + return h.substr(1).toUpperCase(); + }); +} + +/** + * Creates a onCamelCasedEvent() on the instance object's prototype that + * matches the specified regular_event_name. The method will be dispatched + * to on('event_name', callback). e.g. onNotify, onClosed, onDestroy + **/ +function defineInstanceEvent(obj, event_name) { + var camelEventName = camelCase('on_'+event_name); + //add event handler to object if possible + if (!obj.prototype[camelEventName]) + //converts method name to camelCasing on the object + obj.prototype[camelCase('on_'+event_name)] = function(event_name) { + var invocation = function() { + var args = Array.prototype.slice.call(arguments); + if (args == undefined) args = [function noCallback() { }]; + args.unshift(event_name); + //debug:console.log('called',event_name,'in',obj,'with',args[1]); + return this.on.apply(this, args); + }; + return invocation; + }(event_name); +} + +/** + * Creates a camelCasedMethod() on the instance object's prototype that + * matches the specified regular_method_name. The method will be dispatched + * to __call__('method_name', arg0, argx, ...). + **/ +function defineInstanceMethod(obj, method_name) { + var camelMethodName = camelCase(method_name); + //add method handler to object if possible + if (!obj.prototype[camelMethodName]) + //converts method name to camelCasing on the object + obj.prototype[camelMethodName] = function(method_name) { + //the internal function does all the hard work + var invocation = function() { + var args = Array.prototype.slice.call(arguments); + if (args == undefined) args = new Array(); + args.unshift(method_name); + //this helped me to catch a recent problem with object.cc + //debug:console.log('called',method_name,'in',obj); + //debug:for (var x in obj) console.log(x); + return this.__call__.apply(this, args); + }; + return invocation; + }(method_name); + //else + //debug:console.warn("[node-gir] " + subobj + " object provides it's own " + method_name + " method. Not replacing existing method. :-("); + + //Not sure if this would do anything positive: + //Check for new(), and makes new() the default constructor if it's in the gir object. + //Disabled until it would serve useful. It simply assigns the new() function as + //the JS constructor for the object. There is a small problem: some objects have multiple + //new() functions, probably due to object inheritence or overloading. :~ Not sure really, + //but it seems to add no new functionality while simultaneously not taking anything away. + //It may be thrown away in a future commit if it is decidedly useless. + //DISABLED: + /*if (method_name == 'new') { + //debug:console.log('[node-gir] Using new() as the default constructor for', subobj, 'provided by gir.'); + obj[subobj].prototype.constructor = defineObjectMethod(obj[subobj], 'new'); + }*/ +} + +/** + * Creates a camelCasedVFunc() on the instance object's prototype that + * matches the specified regular_method_name. The method will be dispatched + * to __call_v_func__('v_func_name', arg0, argx, ...). + **/ +function defineInstanceVFunc(obj, v_name) { + var camelVName = camelCase(v_name); + //add method handler to object if possible + if (!obj.prototype[camelVName]) + //converts method name to camelCasing on the object + obj.prototype[camelVName] = function(v_name) { + //the internal function does all the hard work + var invocation = function() { + var args = Array.prototype.slice.call(arguments); + if (args == undefined) args = new Array(); + args.unshift(v_name); + //call the method on the gir provided object + //debug:console.log('called',v_name,'in',obj); + this.__call_v_func__.apply(this, args); + }; + return invocation; + }(v_name); + //else + //debug:console.warn("[node-gir] " + subobj + " object provides it's own " + method_name + " method. Not replacing existing method. :-("); +} + +/** + * Creates a camelCasedProperty on the instance object's prototype that + * matches the specified regular-property-name. The getter is dispatched to + * __get_property__('property-name'). The setter is dispatched to + * __set_property__('property-name', value). + **/ +//added try{}catch{} block because of TypeError: Illegal invocation, in what appears +//to be accessing beyond object access boundaries, e.g. trying to access a private member, or +//setting a getter-only property, or something to that effect. +//perhaps someone can figure out why this version throws TypeError: Illegal invocation +//across certain boundaries while the swick/node-gir does not :~ could be a diff in binaries +function defineInstanceProperty(obj, property_name) { + var camelPropertyName = camelCase(property_name); + //add property handler to object if possible + if (!obj.prototype[camelPropertyName]) { + obj.prototype.__defineGetter__(camelPropertyName, function() { + try { + return this.__get_property__.apply(this, [property_name]); + } catch (err) { + //debug:console.warn('[node-gir] Error trying to install getter ' + camelCase(property_name) + '.', err); + return undefined; + } + }); + obj.prototype.__defineSetter__(camelPropertyName, function(newValue) { + try { + return this.__set_property__.apply(this, [property_name, newValue]); + } catch (err) { + //debug:console.warn('[node-gir] Error trying to install setter ' + camelCase(property_name) + '.', err); + return newValue; + } + }); + } + //else + //debug:console.warn("[node-gir] " + subobj + " object provides it's own " + property_name + " property. Not replacing existing property. :-("); +} + +/* END INTERNAL HELPERS */ /******************************************************************************/ /* BEGIN LOGIC */ //save default module routines -gir._gir_baseInit = gir.init; -gir._gir_baseLoad = gir.load; +gir._girepository.__init__ = gir._girepository.init; +gir._girepository.__import__ = gir._girepository.import; //add init flag property -gir._gir_hasInit = false; +gir._girepository._has_init = false; //override default init -gir.init = function() { +gir._girepository.init = function() { //don't init twice, seems useless to do so - if (!this._gir_hasInit) { - this._gir_hasInit = true; - return gir['_gir_baseInit'].apply(this, Array.prototype.slice.call(arguments)); + if (!gir._girepository._has_init) { + gir._girepository._has_init = true; + return gir._girepository.__init__.apply(gir._girepository, Array.prototype.slice.call(arguments)); } }; -//create callable method object -function CallableMethod(methodName) { - //the internal function does all the hard work - var invocation = function() { - var args = Array.prototype.slice.call(arguments); - if (args == undefined) args = new Array(); - for (var i = args.length; i > 0; i--) - args[i] = args[i-1]; - args[0] = methodName; - //call the method on the gir provided object - this.__call__.apply(this, args); - }; - return invocation; -} - -//override default loader -gir.load = function() { +//override default namespace loader +gir.load = gir.import = gir._girepository.import = function() { //auto-init if needed - if (!this._gir_hasInit) this.init(); + if (!gir._girepository._has_init) gir._girepository.init(); //load gir module - var obj = gir['_gir_baseLoad'].apply(this, Array.prototype.slice.call(arguments)); + var obj = gir._girepository.__import__.apply(gir._girepository, Array.prototype.slice.call(arguments)); //check for error if (!obj) return obj; @@ -203,8 +329,12 @@ gir.load = function() { //TODO: consider storing loaded module gir somewhere now so that it can be unloaded later ? //for each object within the loaded gir module: - // task 1. figure out which loaded objects can trigger events, and add EventEmitter as needed - // task 2. make method names callable methods + // task 1: add EventEmitter as needed + // task 2: setup signals as onEventNames shortcut functions + // task 3: expose methods and make them callable + // task 4: expose vfuncs and make them callable + // task 5: expose properties and make them settable/gettable + // task 6: expose fields and make them settable/gettable for (var subobj in obj) { //task 1: add EventEmitter as needed //determine whether eventable @@ -214,41 +344,95 @@ gir.load = function() { //combine EventEmitter logic with eventable gir objects extend(true, obj[subobj].prototype, EventEmitter.prototype); //check for prop __watch_signal__, if found, override EventEmitter.on() - if (obj[subobj].prototype['__watch_signal__'] != undefined) { - obj[subobj].prototype._baseEventEmitter_on = obj[subobj].prototype.on; + if (obj[subobj]['__signals__'] != undefined) { + obj[subobj].prototype._EventEmitter_on = obj[subobj].prototype.on; obj[subobj].prototype.on = function () { + var args = Array.prototype.slice.call(arguments); //tell gir loaded object to listen for the signal - this.__watch_signal__(arguments[0]); - //dispatch normally - this._baseEventEmitter_on(arguments[0], arguments[1]); + if (this['__watch_signal__'] != undefined) + this.__watch_signal__(args[0]); + //console.log('on hit with', args); + //dispatch EventEmitter normally + this._EventEmitter_on.apply(this, args); }; } } - //task 2: expose object methods to objects and make them callable + //task 2: setup signals as onEventNames shortcut functions + //task 3: expose methods and make them callable + //task 4: expose vfuncs and make them callable + //task 5: expose properties and make them settable/gettable + //task 6: expose fields and make them settable/gettable for (var prop in obj[subobj]) { switch (prop) { - case '__methods__': - for (var method_offset in obj[subobj][prop]) { - var method_name = obj[subobj][prop][method_offset]; - //debug:console.log(subobj + '.' + method_name + '() discovered'); - //add method handler to object if possible - if (obj[subobj].prototype[method_name] != undefined) - {}//debug:console.warn("[node-gir] " + subobj + " object provides it's own " + method_name + " method. Not replacing existing method. :-("); - else - obj[subobj].prototype[method_name] = CallableMethod(method_name); + case '__signals__': //task 2 + for (var signal in obj[subobj][prop]) { + var signal_name = obj[subobj][prop][signal]; + defineInstanceEvent(obj[subobj], signal_name); + } + break; + case '__methods__': //task 3 + for (var method in obj[subobj][prop]) { + var method_name = obj[subobj][prop][method]; + defineInstanceMethod(obj[subobj], method_name); + } + break; + case '__v_funcs__': //task 4 + for (var v in obj[subobj][prop]) { + var v_name = obj[subobj][prop][v]; + //i don't yet know the value of a v_func + defineInstanceVFunc(obj[subobj], v_name); + } + break; + case '__properties__': //task 5 + for (var property in obj[subobj][prop]) { + var property_name = obj[subobj][prop][property]; + defineInstanceProperty(obj[subobj], property_name); + } + break; + case '__fields__': //task 6 + for (var field in obj[subobj][prop]) { + var field_name = obj[subobj][prop][field]; + //this requires changes to the C++ code + //add field reference if possible + //if (!obj[subobj].prototype[field_name]) + //defineInstanceField(obj[subobj], field_name); + //debug:console.log('found field', field_name, 'in', subobj); } break; } } - //console.log(subobj, obj[subobj]); + + //keep the object name in the object for reflection + if (obj[subobj].__name__ != undefined) + console.warn("[node-gir]", arguments[0]+'.'+subobj, "provides it's own __name__. Not replacing __name__."); + else + obj[subobj].__name__ = subobj; + + //keep the namespace loader in the loaded object in case caller wants to reuse the loader + if (obj[subobj].__gir__ != undefined) + console.warn("[node-gir]", arguments[0], "provides it's own __gir__. Not replacing __gir__. Strange error? :-("); + else + obj[subobj].__gir__ = this; } - //keep the loader in the loaded object in case caller wants to reuse the loader - if (obj.gir != undefined) - console.warn("[node-gir] Object provides it's own gir. Not replacing gir. Strange error? :-("); + //keep the namespace name in the loaded object for reflection + if (obj.__name__ != undefined) + console.warn("[node-gir]", arguments[0], "provides it's own __name__. Not replacing __name__."); + else + obj.__name__ = arguments[0]; + + //keep the namespace version in the loaded object for reflection + if (obj.__version__ != undefined) + console.warn("[node-gir]", arguments[0], "provides it's own __version__. Not replacing __version__."); + else + obj.__version__ = arguments[1] ? arguments[1] : gir._girepository.getVersion(arguments[0]); + + //keep the namespace loader in the loaded namespace in case caller wants to reuse the loader + if (obj.__gir__ != undefined) + console.warn("[node-gir]", arguments[0], "provides it's own __gir__. Not replacing __gir__. Strange error? :-("); else - obj.gir = this; + obj.__gir__ = this; //return the brutally overridden object return obj; diff --git a/src/namespace_loader.cc b/src/namespace_loader.cc index c1d1a06..890e6b1 100644 --- a/src/namespace_loader.cc +++ b/src/namespace_loader.cc @@ -14,11 +14,16 @@ GIRepository *NamespaceLoader::repo = NULL; std::map NamespaceLoader::type_libs; void NamespaceLoader::Initialize(Handle target) { - GIR_SET_METHOD(target, "load", NamespaceLoader::Load); - GIR_SET_METHOD(target, "search_path", NamespaceLoader::SearchPath); + GIR_SET_METHOD(target, "import", NamespaceLoader::Import); + GIR_SET_METHOD(target, "searchPath", NamespaceLoader::SearchPath); + GIR_SET_METHOD(target, "isRegistered", NamespaceLoader::IsRegistered); + GIR_SET_METHOD(target, "getDependencies", NamespaceLoader::GetDependencies); + GIR_SET_METHOD(target, "loadedNamespaces", NamespaceLoader::LoadedNamespaces); + GIR_SET_METHOD(target, "getVersion", NamespaceLoader::GetVersion); + GIR_SET_METHOD(target, "getVersions", NamespaceLoader::GetVersions); } -Handle NamespaceLoader::Load(const Arguments &args) { +Handle NamespaceLoader::Import(const Arguments &args) { HandleScope scope; if(args.Length() < 1) { @@ -157,6 +162,116 @@ Handle NamespaceLoader::SearchPath(const Arguments &args) { return scope.Close(res); } +Handle NamespaceLoader::IsRegistered(const Arguments &args) { + HandleScope scope; + + if(!repo) { + repo = g_irepository_get_default(); + } + + if(args.Length() < 1 || !args[0]->IsString()) { + return BAD_ARGS(); + } + + String::Utf8Value namespace_(args[0]->ToString()); + char *version = NULL; + + if(args.Length() > 1 && args[1]->IsString()) { + String::Utf8Value version_(args[1]); + version = *version_; + } + + return scope.Close(Boolean::New(g_irepository_is_registered(repo, *namespace_, version))); +} + +Handle NamespaceLoader::GetDependencies(const Arguments &args) { + HandleScope scope; + + if(!repo) { + repo = g_irepository_get_default(); + } + + if(args.Length() < 1 || !args[0]->IsString()) { + return BAD_ARGS(); + } + + String::Utf8Value namespace_(args[0]->ToString()); + + char **versions = g_irepository_get_dependencies(repo, *namespace_); + + int size = 0; + while(versions[size] != NULL) { size++; } + + Handle res = Array::New(size); + for(int i=0; versions[i] != NULL; i++) { + res->Set(i, String::New(versions[i])); + } + + return scope.Close(res); +} + +Handle NamespaceLoader::LoadedNamespaces(const Arguments &args) { + HandleScope scope; + + if(!repo) { + repo = g_irepository_get_default(); + } + + char **namespaces = g_irepository_get_loaded_namespaces(repo); + + int size = 0; + while(namespaces[size] != NULL) { size++; } + + Handle res = Array::New(size); + for(int i=0; namespaces[i] != NULL; i++) { + res->Set(i, String::New(namespaces[i])); + } + + return scope.Close(res); +} + +Handle NamespaceLoader::GetVersion(const Arguments &args) { + HandleScope scope; + + if(!repo) { + repo = g_irepository_get_default(); + } + + if(args.Length() < 1 || !args[0]->IsString()) { + return BAD_ARGS(); + } + + String::Utf8Value namespace_(args[0]->ToString()); + + const char *version = g_irepository_get_version(repo, *namespace_); + + return scope.Close(String::New(version)); +} + +Handle NamespaceLoader::GetVersions(const Arguments &args) { + HandleScope scope; + + if(!repo) { + repo = g_irepository_get_default(); + } + + if(args.Length() < 1 || !args[0]->IsString()) { + return BAD_ARGS(); + } + + String::Utf8Value namespace_(args[0]->ToString()); + + GList *versions = g_irepository_enumerate_versions(repo, *namespace_); + int length = g_list_length(versions); + Handle res = Array::New(length); + + for(int i=0; iSet(i, String::New((char*)g_list_nth_data(versions, i))); + } + + return scope.Close(res); +} + } diff --git a/src/namespace_loader.h b/src/namespace_loader.h index 66ac2cf..fb5b658 100644 --- a/src/namespace_loader.h +++ b/src/namespace_loader.h @@ -14,9 +14,14 @@ class NamespaceLoader { static std::map type_libs; static void Initialize(v8::Handle target); - static v8::Handle Load(const v8::Arguments &args); + static v8::Handle Import(const v8::Arguments &args); static v8::Handle SearchPath(const v8::Arguments &args); + static v8::Handle IsRegistered(const v8::Arguments &args); + static v8::Handle GetDependencies(const v8::Arguments &args); + static v8::Handle LoadedNamespaces(const v8::Arguments &args); + static v8::Handle GetVersion(const v8::Arguments &args); + static v8::Handle GetVersions(const v8::Arguments &args); private: static v8::Handle LoadNamespace(char *namespace_, char *version); diff --git a/src/types/function.cc b/src/types/function.cc index ef4cf7e..7c1b175 100644 --- a/src/types/function.cc +++ b/src/types/function.cc @@ -5,8 +5,6 @@ #include #include -#include -#include using namespace v8; diff --git a/src/types/object.cc b/src/types/object.cc index 700be66..8e6096e 100644 --- a/src/types/object.cc +++ b/src/types/object.cc @@ -226,8 +226,13 @@ void GIRObject::Initialize(Handle target, char *namespace_) { std::vector::iterator it; std::vector::iterator temp; GIObjectInfo* parent; + std::vector roots; + Handle objs = Array::New(templates.size()); + int i = 0; for(it = templates.begin(); it != templates.end(); ++it) { + objs->Set(i++, String::New(g_base_info_get_name(it->info))); + parent = g_object_info_get_parent(it->info); if(strcmp(it->namespace_, namespace_) != 0 || !parent) { continue; @@ -236,8 +241,12 @@ void GIRObject::Initialize(Handle target, char *namespace_) { for(temp = templates.begin(); temp != templates.end(); ++temp) { if(g_base_info_equal(temp->info, parent)) { it->function->Inherit(temp->function); + break; } } + if(temp == templates.end()) { + roots.push_back(g_base_info_get_name(it->info)); + } } for(it = templates.begin(); it != templates.end(); ++it) { if(strcmp(it->namespace_, namespace_) == 0) { @@ -245,6 +254,16 @@ void GIRObject::Initialize(Handle target, char *namespace_) { } } + int rootsLength = roots.size(); + Handle v8roots = Array::New(rootsLength); + i = 0; + for(std::vector::iterator it = roots.begin(); it != roots.end(); it++) { + v8roots->Set(i++, String::New(*it)); + } + + target->Set(String::New("__roots__"), v8roots); + target->Set(String::New("__objects__"), objs); + emit_symbol = NODE_PSYMBOL("emit"); } diff --git a/wscript b/wscript index c250336..ccea997 100644 --- a/wscript +++ b/wscript @@ -14,8 +14,6 @@ def configure(conf): conf.check_tool("node_addon") conf.check_cfg(package='gobject-introspection-1.0', uselib_store='GIREPOSITORY', args='--cflags --libs') conf.check_cfg(package='glib', uselib_store='GLIB', args='--cflags --libs') - conf.check_cfg(package='gtk+-2.0', uselib_store='GTK', args='--cflags --libs') - conf.check_cfg(package='gdk-2.0', uselib_store='GDK', args='--cflags --libs') def build(bld): obj = bld.new_task_gen("cxx", "shlib", "node_addon")