diff --git a/.gitignore b/.gitignore index 97a550ed..d951f938 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,14 @@ .DS_Store *~ +.buildconfig +.ycm_extra_conf.* +*/.tags \#*\# -build/ -node_modules/ + npm-debug.log package-lock.json -.buildconfig -*/.tags -.ycm_extra_conf.* + +build/ +node_modules/ + +lib/binding diff --git a/Makefile b/Makefile deleted file mode 100644 index 1f5aa8a3..00000000 --- a/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -.PHONY: build default release - -VERSION := $(shell node -e "console.log(require('./package.json').version)") -BOLD := $(shell tput bold) -RED := $(shell tput setaf 1) -RESET := $(shell tput sgr0) - -build: - @echo "Did you mean 'make release' ?" - -default: - @echo "Did you mean 'make release' ?" - -release: - @if [ $(NODE_PRE_GYP_GITHUB_TOKEN) = "" ]; then echo "$(RED)Please specify a $(BOLD)NODE_PRE_GYP_GITHUB_TOKEN$(RESET)"; echo ""; exit 1; fi - @echo "Current Version: $(BOLD)$(VERSION)$(RESET)" - @if [ "$(TAG)" = "" ]; then echo "$(BOLD)$(RED)Please specify a new version$(RESET)"; fi - @if [ "$(TAG)" = "" ]; then echo "$(BOLD)TAG=$(VERSION)1 make release$(RESET)"; echo ""; exit 1; fi - @if [ "$(TAG)" = $(VERSION) ]; then echo "$(BOLD)$(RED)Please specify a different version$(RESET)"; echo ""; exit 1; fi - @echo "Creating Release: $(BOLD)$(TAG)$(RESET)" - @echo "" - @node -e "var pkg=require('./package.json');pkg.version='$(TAG)';pkg.binary.host=pkg.binary.host.replace(/\/\d+\.\d+\.\d+\$$/,'/$(TAG)');require('fs').writeFileSync('./package.json', JSON.stringify(pkg,null,' '));" - @echo "Building Package" - ./node_modules/node-pre-gyp/bin/node-pre-gyp configure - ./node_modules/node-pre-gyp/bin/node-pre-gyp rebuild - node-pre-gyp-github package - @echo "Adding updated package.json" - @git add . - @git commit -m "Release $(TAG)" - @git push - @echo "Tagging master Release $(BOLD)$(TAG)$(RESET)" - @git tag -m "Release $(TAG)" $(TAG) - @echo "Pushing master tags to GitHub" - @git push --tags - NODE_PRE_GYP_GITHUB_TOKEN="$(NODE_PRE_GYP_GITHUB_TOKEN)" node-pre-gyp-github publish - @echo "Publishing to npm" - npm publish diff --git a/binding.gyp b/binding.gyp index 49f155b4..e9acfa8b 100644 --- a/binding.gyp +++ b/binding.gyp @@ -1,7 +1,7 @@ { "targets": [ { - "target_name": "node-gtk", + "target_name": "node_gtk", "sources": [ "src/boxed.cc", "src/closure.cc", @@ -53,6 +53,17 @@ ] }] ] + }, + { + "target_name": "action_after_build", + "type": "none", + "dependencies": [ "<(module_name)" ], + "copies": [ + { + "files": [ "<(PRODUCT_DIR)/<(module_name).node" ], + "destination": "<(module_path)" + } + ] } ] } diff --git a/doc/overrides.md b/doc/overrides.md new file mode 100644 index 00000000..c2f1bb06 --- /dev/null +++ b/doc/overrides.md @@ -0,0 +1,20 @@ +## Implementing overrides + + - [Functions that create GMainLoop](#functions-that-create-gmainloop) + +### Functions that create GMainLoop + +Functions that create a GMainLoop should be wrapped as shown in the snippet below. +The function to quit the created loop must be pushed unto the `loopStack`. +Internally, NodeGTK uses this stack to quit all running loops when an exception occurs. + +```javascript +const originalMain = Gtk.main +Gtk.main = function main() { + const loopStack = require('../native.js').GetLoopStack() + + loopStack.push(Gtk.mainQuit) + originalMain() + loopStack.pop() +} +``` diff --git a/examples/entry.js b/examples/entry.js index 8054a785..c493a4ca 100644 --- a/examples/entry.js +++ b/examples/entry.js @@ -10,6 +10,20 @@ const Gdk = gi.require('Gdk') gi.startLoop() Gtk.init() + +process.on('uncaughtException', (err) => { + console.log('process.uncaughtException', err) + process.exit(1) +}) +process.on('exit', (code) => { + console.log('process.exit', code) +}) +process.on('SIGINT', () => { + console.log('process.SIGINT') + process.exit(2) +}) + + // main program window const window = new Gtk.Window({ type : Gtk.WindowType.TOPLEVEL @@ -20,17 +34,6 @@ const entry = new Gtk.Entry() entry.on('key-press-event', (event) => { console.log(event) console.log(event.string) - console.log('') - - console.log(event, Object.keys(event)) - console.log(event.__proto__, Object.keys(event.__proto__)) - - const e = new Gdk.EventKey() - console.log(e, Object.keys(e)) - console.log(e.__proto__, Object.keys(e.__proto__)) - - console.log(e.__proto__ === event.__proto__) - console.log(e.__proto__.__proto__ === event.__proto__.__proto__) }) @@ -38,8 +41,6 @@ entry.on('key-press-event', (event) => { window.setDefaultSize(200, 50) window.setResizable(true) window.connect('show', () => { - // bring it on top in OSX - // window.setKeepAbove(true) Gtk.main() }) window.on('destroy', () => Gtk.mainQuit()) diff --git a/install.sh b/install.sh index c25cf866..2cef7414 100755 --- a/install.sh +++ b/install.sh @@ -1,15 +1,10 @@ #! /bin/sh # install.sh -# Copyright (C) 2018 rgregoir +# Copyright (C) 2018 romgrk # if [ "$(uname)" = "Darwin" ] && [ "$(which brew)" != "" ]; then export PKG_CONFIG_PATH=$(brew --prefix libffi)/lib/pkgconfig fi -node-pre-gyp install - -if [ -n $? ]; then - node-gyp configure - node-gyp build -fi +node-pre-gyp install --fallback-to-build diff --git a/lib/index.js b/lib/index.js index 32df522d..3088a44d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -5,12 +5,7 @@ const camelCase = require('lodash.camelcase') const snakeCase = require('lodash.snakecase') -let gi; -try { - gi = require('../build/Release/node-gtk'); -} catch(e) { - gi = require('../build/Debug/node-gtk'); -} +const gi = require('./native.js') // The bootstrap from C here contains functions and methods for each object, // namespaced with underscores. See gi.cc for more information. diff --git a/lib/native.js b/lib/native.js new file mode 100644 index 00000000..fcbbd232 --- /dev/null +++ b/lib/native.js @@ -0,0 +1,13 @@ +/* + * native.js + */ + +const binary = require('node-pre-gyp') +const path = require('path') + +const packagePath = path.resolve(path.join(__dirname,'../package.json')) +const bindingPath = binary.find(packagePath) + +const binding = require(bindingPath) + +module.exports = binding diff --git a/lib/overrides/Gtk-3.0.js b/lib/overrides/Gtk-3.0.js index 3de543cc..aca14965 100644 --- a/lib/overrides/Gtk-3.0.js +++ b/lib/overrides/Gtk-3.0.js @@ -2,5 +2,17 @@ * Gtk-3.0.js */ +const internal = require('../native.js') + exports.apply = (Gtk) => { + + const originalMain = Gtk.main + Gtk.main = function main() { + const loopStack = internal.GetLoopStack() + + loopStack.push(Gtk.mainQuit) + originalMain() + loopStack.pop() + } + } diff --git a/package.json b/package.json index 2df874af..a8531557 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "scripts": { "install": "./install.sh", "test": "mocha tests/__run__.js", - "build": "node-gyp rebuild", - "build:incremental": "node-gyp build" + "build": "node-pre-gyp rebuild", + "build:incremental": "node-pre-gyp build" }, "repository": { "type": "git", @@ -22,6 +22,9 @@ "bindings" ], "author": "Jasper St. Pierre", + "contributors": [ + "Romain Grégoire (https://github.com/romgrk)" + ], "license": "MIT", "bugs": { "url": "https://github.com/romgrk/node-gtk/issues" @@ -35,16 +38,17 @@ "node-pre-gyp": "0.10.2", "node-pre-gyp-github": "1.3.1" }, - "binary": { - "module_name": "node-gtk", - "module_path": "./build/{configuration}/{node_abi}-{platform}-{arch}/", - "package_name": "{node_abi}-{platform}-{arch}.tar.gz", - "host": "https://github.com/romgrk/node-gtk/releases/download/", - "remote_path": "{version}" - }, "devDependencies": { "assert": "^1.4.1", + "aws-sdk": "^2.276.1", "chalk": "^2.4.1", "mocha": "^5.2.0" + }, + "binary": { + "module_name": "node_gtk", + "module_path": "./lib/binding/{node_abi}-{platform}-{arch}/", + "remote_path": "./{module_name}/v{version}/", + "package_name": "{module_name}-{node_abi}-{platform}-{arch}.tar.gz", + "host": "https://node-gtk-1.s3-us-east-1.amazonaws.com" } } diff --git a/publish.sh b/publish.sh new file mode 100755 index 00000000..a999b3f6 --- /dev/null +++ b/publish.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# +# publish.sh +# Copyright (C) 2018 romgrk +# +# Distributed under terms of the MIT license. + +if [ -f /usr/share/nvm/init-nvm.sh ]; then + source /usr/share/nvm/init-nvm.sh +fi + +## NodeJS versions +# We publish more than the versions we officially support, to be nice. +# Should be updated when a new NODE_MODULE_VERSION appears in https://nodejs.org/en/download/releases/ +declare -a versions=("6.0.0" "7.0.0" "8.0.0" "9.0.0" "10.0.0") + +## Publish each version +for version in "${versions[@]}" +do + echo "##### Installing: $version ######" + nvm install $version + nvm use $version + npm install + ./node_modules/.bin/node-pre-gyp package publish +done diff --git a/src/closure.cc b/src/closure.cc index b4e7a927..c6e511a6 100644 --- a/src/closure.cc +++ b/src/closure.cc @@ -1,8 +1,9 @@ #include #include +#include "closure.h" #include "debug.h" -#include "function.h" +#include "loop.h" #include "type.h" #include "value.h" @@ -10,25 +11,6 @@ using namespace v8; namespace GNodeJS { -struct Closure { - GClosure base; - Persistent persistent; - GISignalInfo* info; - - ~Closure() { - if (info) - g_base_info_unref(info); - } - - static void Marshal(GClosure *closure, - GValue *g_return_value, - uint argc, const GValue *g_argv, - gpointer invocation_hint, - gpointer marshal_data); - - static void Invalidated(gpointer data, GClosure *closure); -}; - void Closure::Marshal(GClosure *base, GValue *g_return_value, uint argc, const GValue *g_argv, @@ -78,12 +60,11 @@ void Closure::Marshal(GClosure *base, } } else { - auto stackTrace = try_catch.StackTrace(); - if (!stackTrace.IsEmpty()) - printf("%s\n", *Nan::Utf8String(stackTrace.ToLocalChecked())); - else - printf("%s\n", *Nan::Utf8String(try_catch.Exception())); - exit(1); + log("'%s' did throw", g_base_info_get_name (closure->info)); + + GNodeJS::QuitLoopStack(); + + try_catch.ReThrow(); } #ifndef __linux__ @@ -96,7 +77,7 @@ void Closure::Invalidated(gpointer data, GClosure *base) { closure->~Closure(); } -GClosure *MakeClosure(Isolate *isolate, Local function, GISignalInfo* info) { +GClosure *MakeClosure(Isolate *isolate, Local function, GIBaseInfo* info) { Closure *closure = (Closure *) g_closure_new_simple (sizeof (*closure), NULL); closure->persistent.Reset(isolate, function); closure->info = info; diff --git a/src/closure.h b/src/closure.h index 96212e1d..9a14b3cc 100644 --- a/src/closure.h +++ b/src/closure.h @@ -6,8 +6,31 @@ #include #include +using v8::Function; +using v8::Persistent; + namespace GNodeJS { +struct Closure { + GClosure base; + Persistent persistent; + GIBaseInfo* info; + + ~Closure() { + persistent.Reset(); + if (info) + g_base_info_unref(info); + } + + static void Marshal(GClosure *closure, + GValue *g_return_value, + uint argc, const GValue *g_argv, + gpointer invocation_hint, + gpointer marshal_data); + + static void Invalidated(gpointer data, GClosure *closure); +}; + GClosure *MakeClosure(v8::Isolate *isolate, v8::Handle function, GISignalInfo* info); }; diff --git a/src/debug.h b/src/debug.h index ffe61580..5111aa5a 100644 --- a/src/debug.h +++ b/src/debug.h @@ -1,6 +1,7 @@ #pragma once +#include #include #define FILE_NAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) @@ -17,6 +18,18 @@ printf("\x1b[0m\n"); } \ while (0) +#ifdef NDEBUG +#define warn(...) +#else +#define warn(...) \ + do { \ + printf("\x1b[1;38;5;202m"); \ + printf("%s:\x1b[0m\x1b[1m %s: %i: \x1b[0m", FILE_NAME, FUNCTION_NAME, __LINE__); \ + printf(__VA_ARGS__); \ + printf("\n"); \ + } while (0) +#endif + #ifdef NDEBUG #define log(...) #else diff --git a/src/gi.cc b/src/gi.cc index c2829502..db1181ff 100644 --- a/src/gi.cc +++ b/src/gi.cc @@ -17,11 +17,11 @@ using GNodeJS::BaseInfo; namespace GNodeJS { - Nan::Persistent moduleCache(Nan::New()); - G_DEFINE_QUARK(gnode_js_object, object); G_DEFINE_QUARK(gnode_js_template, template); + Nan::Persistent moduleCache(Nan::New()); + Local GetModuleCache() { return Nan::New(GNodeJS::moduleCache); } @@ -331,6 +331,11 @@ NAN_METHOD(GetTypeSize) { info.GetReturnValue().Set(Nan::New(size)); } +NAN_METHOD(GetLoopStack) { + auto stack = GNodeJS::GetLoopStack(); + info.GetReturnValue().Set(stack); +} + void InitModule(Local exports, Local module, void *priv) { NAN_EXPORT(exports, Bootstrap); NAN_EXPORT(exports, GetModuleCache); @@ -348,6 +353,7 @@ void InitModule(Local exports, Local module, void *priv) { NAN_EXPORT(exports, InternalFieldCount); NAN_EXPORT(exports, GetBaseClass); NAN_EXPORT(exports, GetTypeSize); + NAN_EXPORT(exports, GetLoopStack); } -NODE_MODULE(gi, InitModule) +NODE_MODULE(node_gtk, InitModule) diff --git a/src/loop.cc b/src/loop.cc index 4e074351..b55062df 100644 --- a/src/loop.cc +++ b/src/loop.cc @@ -1,6 +1,10 @@ #include #include +#include +#include +#include "debug.h" +#include "gi.h" #include "loop.h" #include "util.h" @@ -11,8 +15,12 @@ * either uv allows external sources to drive prepare/check, or until GLib * exposes an epoll fd to wait on... */ +using namespace v8; + namespace GNodeJS { +static Nan::Persistent loopStack(Nan::New ()); + struct uv_loop_source { GSource source; uv_loop_t *loop; @@ -70,4 +78,23 @@ void StartLoop() { g_source_attach (source, NULL); } +Local GetLoopStack() { + return Nan::New(loopStack); +} + +void QuitLoopStack() { + Local stack = GetLoopStack(); + + for (uint32_t i = 0; i < stack->Length(); i++) { + Local fn = Nan::Get(stack, i).ToLocalChecked()->ToObject(); + Local self = fn; + + log("calling %s", *Nan::Utf8String(Nan::Get(fn, UTF8("name")).ToLocalChecked())); + + Nan::CallAsFunction(fn, self, 0, nullptr); + } + + loopStack.Reset(Nan::New()); +} + }; diff --git a/src/loop.h b/src/loop.h index 64972e90..5e5196b1 100644 --- a/src/loop.h +++ b/src/loop.h @@ -1,8 +1,17 @@ #pragma once +#include +#include + +using namespace v8; + namespace GNodeJS { -void StartLoop(); + void StartLoop(); + + void QuitLoopStack(); + + Local GetLoopStack(); }; diff --git a/tests/loop.js b/tests/loop.js new file mode 100644 index 00000000..f6d3159c --- /dev/null +++ b/tests/loop.js @@ -0,0 +1,33 @@ +/* + * loop.js + */ + + +const gi = require('../lib/') +const Gtk = gi.require('Gtk', '3.0') + +gi.startLoop() +Gtk.init() + +let didCallTimeout = false +let didCallPromise = false + +setTimeout(() => { + console.log('timeout') + didCallTimeout = true +}, 500) + +const promise = new Promise(resolve => resolve('promise resolved')) +promise.then(result => { + console.log(result) + didCallPromise = true +}) + +setTimeout(() => { + Gtk.mainQuit() + console.assert(didCallTimeout, 'did not call timeout') + console.assert(didCallPromise, 'did not call promise') + console.log('done') +}, 1000) + +Gtk.main()