├── .editorconfig ├── .gitignore ├── .yarnclean ├── LICENSE ├── README.md ├── binding.gyp ├── demo └── src │ └── index.js ├── nwb.config.js ├── package.json ├── src ├── app │ ├── index.c │ ├── index.h │ └── index.js ├── button │ ├── index.c │ ├── index.h │ └── index.js ├── index.js └── window │ ├── index.c │ ├── index.h │ └── index.js ├── tests ├── .eslintrc └── index-test.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | charset = utf-8 9 | 10 | [Makefile] 11 | indent_style = tab 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # Logs 55 | logs 56 | *.log 57 | npm-debug.log* 58 | yarn-debug.log* 59 | yarn-error.log* 60 | 61 | # Runtime data 62 | pids 63 | *.pid 64 | *.seed 65 | *.pid.lock 66 | 67 | # Directory for instrumented libs generated by jscoverage/JSCover 68 | lib-cov 69 | 70 | # Coverage directory used by tools like istanbul 71 | coverage 72 | 73 | # nyc test coverage 74 | .nyc_output 75 | 76 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 77 | .grunt 78 | 79 | # Bower dependency directory (https://bower.io/) 80 | bower_components 81 | 82 | # node-waf configuration 83 | .lock-wscript 84 | 85 | # Compiled binary addons (http://nodejs.org/api/addons.html) 86 | build/Release 87 | 88 | # Dependency directories 89 | node_modules/ 90 | jspm_packages/ 91 | 92 | # Typescript v1 declaration files 93 | typings/ 94 | 95 | # Optional npm cache directory 96 | .npm 97 | 98 | # Optional eslint cache 99 | .eslintcache 100 | 101 | # Optional REPL history 102 | .node_repl_history 103 | 104 | # Output of 'npm pack' 105 | *.tgz 106 | 107 | # Yarn Integrity file 108 | .yarn-integrity 109 | 110 | # dotenv environment variables file 111 | .env 112 | 113 | # Emacs 114 | \#*\# 115 | .\#* 116 | *~ 117 | 118 | # Application 119 | lib 120 | es/ 121 | build/ 122 | -------------------------------------------------------------------------------- /.yarnclean: -------------------------------------------------------------------------------- 1 | # test directories 2 | __tests__ 3 | test 4 | tests 5 | powered-test 6 | 7 | # asset directories 8 | docs 9 | doc 10 | website 11 | images 12 | assets 13 | 14 | # examples 15 | example 16 | examples 17 | 18 | # code coverage directories 19 | coverage 20 | .nyc_output 21 | 22 | # build scripts 23 | Makefile 24 | Gulpfile.js 25 | Gruntfile.js 26 | 27 | # configs 28 | .tern-project 29 | .gitattributes 30 | .editorconfig 31 | .*ignore 32 | .eslintrc 33 | .jshintrc 34 | .flowconfig 35 | .documentup.json 36 | .yarn-metadata.json 37 | .*.yml 38 | *.yml 39 | 40 | # misc 41 | *.gz 42 | *.md 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jam Risser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-gtk3 _Pre Release_ 2 | 3 | [![Gitter](https://img.shields.io/gitter/room/react-gtk/lobby.svg?style=flat-square)](https://gitter.im/react-gtk) 4 | 5 | This project is depricated in favor of [https://github.com/Place1/node-gir](https://github.com/Place1/node-gir). You can still contact me if you would like to contribute to [node-gir](https://github.com/Place1/node-gir). We're working very hard in our free time to make this project a reality. 6 | 7 | > Node bindings for Gtk3 8 | 9 | Existing bindings are either abandoned or require custom runtime environments. This is an attempt to create support gtk bindings for the official node engine. This project is the foundation for [react-gtk](https://github.com/jamrizzi/react-gtk). Contributors are wanted and welcomed. 10 | 11 | This project is using [ffi](https://github.com/node-ffi/node-ffi) to bind node to the gtk C libraries. 12 | 13 | Please ★ this repo if you found it useful ★ ★ ★ 14 | 15 | 16 | ## Features 17 | 18 | * Supports official node engine 19 | 20 | 21 | ## Usage 22 | 23 | ```sh 24 | git clone https://github.com/jamrizzi/node-gtk3.git && cd node-gtk3 25 | npm install 26 | npm run demo 27 | ``` 28 | 29 | 30 | # Dependancies 31 | 32 | * [GTK3](https://developer.gnome.org/gtk3/) 33 | * [NodeJS](https://nodejs.org) 34 | 35 | 36 | ## Contributing 37 | 38 | 1. Fork it! 39 | 2. Create your feature branch: `git checkout -b my-new-feature` 40 | 3. Commit your changes: `git commit -m 'Add some feature'` 41 | 4. Push to the branch: `git push origin my-new-feature` 42 | 5. Submit a pull request :D 43 | 44 | 45 | ## License 46 | 47 | [MIT License](https://github.com/jamrizzi/node-gtk3/blob/master/LICENSE) 48 | 49 | [Jam Risser](https://jamrizzi.com) © 2017 50 | 51 | 52 | ## Credits 53 | 54 | * [Jam Risser](https://jamrizzi.com) - Author 55 | * [Everton Ribeiro](https://github.com/nuxlli) - Contributor 56 | 57 | 58 | ## Changelog 59 | 60 | 0.0.2 (2017-09-06) 61 | * Added MacOS support 62 | * Refactored build system with [nwb](https://github.com/insin/nwb) 63 | 64 | 0.0.1 (2017-06-07) 65 | * Initial release 66 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "app", 5 | "type": "shared_library", 6 | "cflags": [ 7 | " { 11 | const button = new Button({ 12 | label: 'Button 1', 13 | opacity: .1, 14 | height: 100, 15 | width: 50 16 | }); 17 | button.onClicked = () => { 18 | console.log('Button was clicked'); 19 | }; 20 | button.onActivate = () => { 21 | console.log('Button was activated'); 22 | }; 23 | button.onEnter = () => { 24 | console.log('Button was entered'); 25 | }; 26 | button.onLeave = () => { 27 | console.log('Button was left'); 28 | }; 29 | button.onPressed = () => { 30 | console.log('Button was pressed'); 31 | }; 32 | button.onReleased = () => { 33 | console.log('Button was released'); 34 | }; 35 | button.attach(window); 36 | app.render(); 37 | }); 38 | -------------------------------------------------------------------------------- /nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'web-module', 3 | npm: { 4 | esModules: true, 5 | umd: false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-gtk3", 3 | "version": "0.0.7", 4 | "description": "Node bindings for Gtk3", 5 | "main": "lib/index.js", 6 | "module": "es/index.js", 7 | "files": [ 8 | "es", 9 | "lib", 10 | "umd", 11 | "src/**/*.c", 12 | "src/**/*.h", 13 | "binding.gyp" 14 | ], 15 | "scripts": { 16 | "build": "node ./node_modules/nwb/lib/bin/nwb build-web-module && node ./node_modules/node-gyp/bin/node-gyp clean configure build", 17 | "clean": "node ./node_modules/nwb/lib/bin/nwb clean && rm -rf ./build", 18 | "demo": "npm run build && node ./node_modules/babel-cli/bin/babel-node ./demo/src/index.js", 19 | "test": "node ./node_modules/nwb/lib/bin/nwb test", 20 | "test:coverage": "node ./node_modules/nwb/lib/bin/nwb test --coverage", 21 | "test:watch": "node ./node_modules/nwb/lib/bin/nwb test --server" 22 | }, 23 | "dependencies": { 24 | "ffi": "^2.2.0" 25 | }, 26 | "devDependencies": { 27 | "nwb": "^0.18.10", 28 | "node-gyp": "^3.6.2" 29 | }, 30 | "babel": { 31 | "presets": [ 32 | "es2015", 33 | "stage-2" 34 | ] 35 | }, 36 | "author": "", 37 | "homepage": "", 38 | "license": "MIT", 39 | "repository": "" 40 | } 41 | -------------------------------------------------------------------------------- /src/app/index.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "index.h" 4 | 5 | typedef struct { 6 | char *title; 7 | char *namespace; 8 | int width; 9 | int height; 10 | } Settings; 11 | Settings settings = {"Some Title", "org.gtk.example", 200, 200}; 12 | 13 | typedef struct { 14 | GtkWidget *window; 15 | } RenderPackage; 16 | 17 | GtkApplication *create(char *title, char *namespace, int width, int height) { 18 | settings.title = title; 19 | settings.namespace = namespace; 20 | settings.width = width; 21 | settings.height = height; 22 | GtkApplication *app; 23 | app = gtk_application_new(namespace, G_APPLICATION_FLAGS_NONE); 24 | return app; 25 | } 26 | 27 | void (*on_activate)(GtkWidget*); 28 | void register_on_activate(void (*on_activate_cb)(GtkWidget*)) { 29 | on_activate = on_activate_cb; 30 | } 31 | 32 | void activate(GtkApplication *app, gpointer user_data) { 33 | GtkWidget *window; 34 | window = gtk_application_window_new(app); 35 | gtk_window_set_title(GTK_WINDOW(window), settings.title); 36 | gtk_window_set_default_size(GTK_WINDOW(window), settings.width, settings.height); 37 | on_activate(window); 38 | } 39 | 40 | int init(GtkApplication *app) { 41 | g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); 42 | int status = g_application_run(G_APPLICATION(app), 0, NULL); 43 | g_object_unref(app); 44 | return status; 45 | } 46 | 47 | gboolean render_main(gpointer p_package) { 48 | RenderPackage package = *(RenderPackage*)p_package; 49 | gtk_widget_show_all(package.window); 50 | g_free((RenderPackage*)p_package); 51 | return G_SOURCE_REMOVE; 52 | } 53 | void render(GtkWidget *window) { 54 | RenderPackage *p_package = g_malloc(sizeof(RenderPackage)); 55 | p_package->window = window; 56 | gdk_threads_add_idle(render_main, p_package); 57 | } 58 | -------------------------------------------------------------------------------- /src/app/index.h: -------------------------------------------------------------------------------- 1 | #ifndef GTK_APP_HEADER 2 | #define GTK_APP_HEADER 3 | 4 | GtkApplication *create(char*, char*, int, int); 5 | int init(GtkApplication*); 6 | typedef void on_activate_cb(GtkWidget*); 7 | void register_on_activate(on_activate_cb); 8 | void render(GtkWidget*); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/app/index.js: -------------------------------------------------------------------------------- 1 | import ffi from 'ffi'; 2 | import path from 'path'; 3 | import ref from 'ref'; 4 | import { Window } from '../'; 5 | 6 | const type = { 7 | GtkApplicationPtr: ref.refType(ref.types.void), 8 | GtkWidgetPtr: ref.refType(ref.types.void), 9 | on_activate_cb: ref.refType(ref.types.void) 10 | }; 11 | 12 | const app = ffi.Library(path.resolve(__dirname, '../../build/Release/app'), { 13 | create: [type.GtkApplicationPtr, ['string', 'string', 'int', 'int']], 14 | init: ['int', [type.GtkApplicationPtr]], 15 | register_on_activate: ['void', [type.on_activate_cb]], 16 | render: ['void', [type.GtkWidgetPtr]] 17 | }); 18 | 19 | export default class App { 20 | constructor({title, namespace, width, height}) { 21 | this.title = title || 'Some Title'; 22 | this.namespace = namespace || 'org.gtk.example'; 23 | this.width = width || 200; 24 | this.height = height || 200; 25 | this.window = null; 26 | const onActivateCallback = ffi.Callback('void', [type.GtkWidgetPtr], (window) => { 27 | this.window = new Window({ pointer: window }); 28 | this.onActivate(this.window); 29 | }); 30 | app.register_on_activate(onActivateCallback); 31 | process.on('exit', () => { onActivateCallback; }); 32 | this.pointer = app.create(this.title, this.namespace, this.width, this.height); 33 | } 34 | 35 | init() { 36 | return new Promise((resolve, reject) => { 37 | this.onActivate = resolve; 38 | app.init.async(this.pointer, (err, status) => { 39 | if (status !== 0) { 40 | console.error(new Error('Program initialized with error')); 41 | } 42 | process.exit(status); 43 | }); 44 | }); 45 | } 46 | 47 | render() { 48 | return app.render(this.window.pointer); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/button/index.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "index.h" 4 | 5 | typedef struct { 6 | GtkWidget *button; 7 | GtkWidget *container; 8 | } AttachPackage; 9 | 10 | GtkWidget *create(char* label, gboolean mnemonic, gdouble opacity, gint height, gint width) { 11 | GtkWidget *button; 12 | if (mnemonic) { 13 | button = gtk_button_new_with_mnemonic(label); 14 | } else { 15 | button = gtk_button_new_with_label(label); 16 | } 17 | gtk_widget_set_opacity(button, opacity); 18 | gtk_widget_set_size_request(button, width, height); 19 | return button; 20 | } 21 | 22 | void (*on_activate)(); 23 | void register_on_activate(void (*on_activate_cb)()) { 24 | on_activate = on_activate_cb; 25 | } 26 | 27 | void (*on_clicked)(); 28 | void register_on_clicked(void (*on_clicked_cb)()) { 29 | on_clicked = on_clicked_cb; 30 | } 31 | 32 | void (*on_enter)(); 33 | void register_on_enter(void (*on_enter_cb)()) { 34 | on_enter = on_enter_cb; 35 | } 36 | 37 | void (*on_leave)(); 38 | void register_on_leave(void (*on_leave_cb)()) { 39 | on_leave = on_leave_cb; 40 | } 41 | 42 | void (*on_pressed)(); 43 | void register_on_pressed(void (*on_pressed_cb)()) { 44 | on_pressed = on_pressed_cb; 45 | } 46 | 47 | void (*on_released)(); 48 | void register_on_released(void (*on_released_cb)()) { 49 | on_released = on_released_cb; 50 | } 51 | 52 | gboolean attach_main(gpointer p_package) { 53 | AttachPackage package = *(AttachPackage*)p_package; 54 | g_signal_connect(package.button, "activate", G_CALLBACK(on_activate), NULL); 55 | g_signal_connect(package.button, "clicked", G_CALLBACK(on_clicked), NULL); 56 | g_signal_connect(package.button, "enter", G_CALLBACK(on_enter), NULL); 57 | g_signal_connect(package.button, "leave", G_CALLBACK(on_leave), NULL); 58 | g_signal_connect(package.button, "pressed", G_CALLBACK(on_pressed), NULL); 59 | g_signal_connect(package.button, "released", G_CALLBACK(on_released), NULL); 60 | GtkWidget *button_box; 61 | button_box = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL); 62 | gtk_container_add(GTK_CONTAINER(package.container), button_box); 63 | gtk_container_add(GTK_CONTAINER(button_box), package.button); 64 | gtk_widget_show_all(button_box); 65 | g_free((AttachPackage*)p_package); 66 | return G_SOURCE_REMOVE; 67 | } 68 | void attach(GtkWidget *button, GtkWidget *container) { 69 | AttachPackage *p_package = g_malloc(sizeof(AttachPackage)); 70 | p_package->button = button; 71 | p_package->container = container; 72 | gdk_threads_add_idle(attach_main, p_package); 73 | } 74 | -------------------------------------------------------------------------------- /src/button/index.h: -------------------------------------------------------------------------------- 1 | #ifndef GTK_BUTTON_HEADER 2 | #define GTK_BUTTON_HEADER 3 | 4 | GtkWidget *create(char*, gboolean, gdouble, gint, gint); 5 | 6 | typedef void on_activate_cb(); 7 | void register_on_activate(on_activate_cb); 8 | 9 | typedef void on_clicked_cb(); 10 | void register_on_clicked(on_clicked_cb); 11 | 12 | typedef void on_enter_cb(); 13 | void register_on_enter(on_enter_cb); 14 | 15 | typedef void on_leave_cb(); 16 | void register_on_leave(on_leave_cb); 17 | 18 | typedef void on_pressed_cb(); 19 | void register_on_pressed(on_pressed_cb); 20 | 21 | typedef void on_released_cb(); 22 | void register_on_released(on_released_cb); 23 | 24 | void attach(GtkWidget*, GtkWidget*); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/button/index.js: -------------------------------------------------------------------------------- 1 | import ffi from 'ffi'; 2 | import path from 'path'; 3 | import ref from 'ref'; 4 | import _ from 'lodash'; 5 | 6 | const type = { 7 | GtkWidgetPtr: ref.refType(ref.types.void), 8 | on_activate_cb: ref.refType(ref.types.void), 9 | on_clicked_cb: ref.refType(ref.types.void), 10 | on_enter_cb: ref.refType(ref.types.void), 11 | on_leave_cb: ref.refType(ref.types.void), 12 | on_pressed_cb: ref.refType(ref.types.void), 13 | on_released_cb: ref.refType(ref.types.void) 14 | }; 15 | 16 | const button = ffi.Library(path.resolve(__dirname, '../../build/Release/button'), { 17 | create: [type.GtkWidgetPtr, ['string', 'bool', 'double', 'int', 'int']], 18 | attach: [type.GtkWidgetPtr, [type.GtkWidgetPtr, type.GtkWidgetPtr]], 19 | register_on_activate: ['void', [type.on_activate_cb]], 20 | register_on_clicked: ['void', [type.on_clicked_cb]], 21 | register_on_enter: ['void', [type.on_enter_cb]], 22 | register_on_leave: ['void', [type.on_leave_cb]], 23 | register_on_pressed: ['void', [type.on_pressed_cb]], 24 | register_on_released: ['void', [type.on_released_cb]] 25 | }); 26 | 27 | export default class Button { 28 | constructor({label, mnemonic, opacity, height, width}) { 29 | this.label = label || 'Some Button'; 30 | this.mnemonic = !!mnemonic; 31 | this.opacity = opacity || 1; 32 | this.height = height || -1; 33 | this.width = width || -1; 34 | this.pointer = button.create(this.label, this.mnemonic, this.opacity, this.height, this.width); 35 | this.registerCallback('activate'); 36 | this.registerCallback('clicked'); 37 | this.registerCallback('enter'); 38 | this.registerCallback('leave'); 39 | this.registerCallback('pressed'); 40 | this.registerCallback('released'); 41 | } 42 | 43 | registerCallback(name) { 44 | const onActionCB = ffi.Callback('void', [type.GtkWidgetPtr], () => { 45 | this[`on${_.startCase(name)}`](); 46 | }); 47 | button[`register_on_${name}`](onActionCB); 48 | process.on('exit', () => { onActionCB; }); 49 | } 50 | 51 | onActivate() {} 52 | 53 | onClicked() {} 54 | 55 | onEnter() {} 56 | 57 | onLeave() {} 58 | 59 | onPressed() {} 60 | 61 | onReleased() {} 62 | 63 | attach(container) { 64 | if (container.pointer) container = container.pointer; 65 | button.attach(this.pointer, container); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export const Window = require('./window'); 2 | export const App = require('./app'); 3 | export const Button = require('./button'); 4 | -------------------------------------------------------------------------------- /src/window/index.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "index.h" 4 | 5 | GtkWidget *create() { 6 | GtkWidget *window; 7 | window = gtk_window_new(GTK_WINDOW_TOPLEVEL); 8 | return window; 9 | } 10 | -------------------------------------------------------------------------------- /src/window/index.h: -------------------------------------------------------------------------------- 1 | #ifndef GTK_WINDOW_HEADER 2 | #define GTK_WINDOW_HEADER 3 | 4 | GtkWidget *create(); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /src/window/index.js: -------------------------------------------------------------------------------- 1 | import ffi from 'ffi'; 2 | import path from 'path'; 3 | import ref from 'ref'; 4 | 5 | const type = { 6 | GtkWidgetPtr: ref.refType(ref.types.void) 7 | }; 8 | 9 | const window = ffi.Library(path.resolve(__dirname, '../../build/Release/window'), { 10 | create: [type.GtkWidgetPtr, ['string']] 11 | }); 12 | 13 | export default class Window { 14 | constructor({pointer}) { 15 | this.pointer = pointer; 16 | if (!pointer) this.pointer = window.create(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/index-test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | 3 | import message from 'src/index' 4 | 5 | describe('Module template', () => { 6 | it('displays a welcome message', () => { 7 | expect(message).toContain('Welcome to node_gtk3') 8 | }) 9 | }) 10 | --------------------------------------------------------------------------------