ANDRIY DACENKO
SOFTWARE ENGINEER
September 9, 2015
THREE PLATFORMS ONE SOLUTION
DEVELOPERS
DEVELOPERS
DEVELOPERS
C#
C++
OBJECTIVE-C
WHAT IF I TOLD YOU
YOU CAN MAKE DESKTOP
APPS WITH HTML, CSS
AND JAVASCRIPT?
Oct 30, 2011 – Aug 15, 2015
// package.json
{
"name": "simple",
"version": "1.0.0",
"main": "index.html",
"window": {
"toolbar": false
}
}
<!-- index.html -->
<html>
<head>
<title>Node WebKit App</title>
</head>
<body>
<h1>Desktop App!</h1>
</body>
</html>
# Run application
nw /path/to/app/folder
// Load native UI library
var gui = require('nw.gui');
// Print arguments
console.log(gui.App.argv);
// Quit current app
gui.App.quit();
// Get the JSON object of the manifest file
gui.App.manifest
// Get the application's data
// path in user's directory
gui.App.dataPath
// Lots more ...
// menu.js
var gui = require('nw.gui');
// Create an empty menu
var menu = new gui.Menu();
var MenuItem = gui.MenuItem;
// Add some items
menu.append(new MenuItem({label: 'Item A'}));
menu.append(new MenuItem({label: 'Item B'}));
menu.append(new MenuItem({type: 'separator'}));
menu.append(new MenuItem({label: 'Item C'}));
// Remove one item
menu.removeAt(1);
// Popup as context menu
menu.popup(10, 10);
var gui = require('nw.gui');
// Create a tray icon
var tray = new gui.Tray({
title: 'Tray', icon: 'img/icon.png'
});
// Give it a menu
var menu = new gui.Menu();
menu.append(new gui.MenuItem({
type: 'checkbox', label: 'box1'
}));
tray.menu = menu;
// Remove the tray
tray.remove();
tray = null;
var options = {
icon: "app.png",
body: "Here is the notification text"
};
var notification = new Notification("Notification title", options);
notification.onclick = function() {
console.log('notification clicked');
}
notification.onshow = function() {
// auto close after 1 second
setTimeout(function() {
notification.close();
}, 1000);
}
var gui = require('nw.gui');
// Open URL with default browser.
gui.Shell.openExternal(
'https://github.com/nwjs/nw.js'
);
var path = process.cwd() + '/shell/test.txt';
// Open a text file with default text editor.
gui.Shell.openItem(path);
// Open a file in file explorer.
gui.Shell.showItemInFolder(path);
// Load native UI library
var gui = require('nw.gui');
// We can not create a clipboard,
// we have to receive the system clipboard
var clipboard = gui.Clipboard.get();
// Read from clipboard
var text = clipboard.get('text');
console.log(text);
// Or write something
clipboard.set('I love node-webkit :)', 'text');
// And clear it!
clipboard.clear();
var gui = require('nw.gui');
var option = {
key : "Ctrl+Shift+A",
active : function() {
console.log("Global desktop keyboard shortcut: " + this.key + " active.");
},
failed : function(msg) { console.log(msg); }
};
var shortcut = new gui.Shortcut(option);
// Register global desktop shortcut, which can work without focus.
gui.App.registerGlobalHotKey(shortcut);
shortcut.on('active', function() {
console.log("Global desktop keyboard shortcut: " + this.key + " active.");
});
shortcut.on('failed', function(msg) { console.log(msg); });
// Unregister the global desktop shortcut.
gui.App.unregisterGlobalHotKey(shortcut);
// camFeed.js
function init()
{
navigator.webkitGetUserMedia({video:true}, onSuccess, onFail);
}
function onSuccess(stream)
{
var camFeed = document.getElementById('camFeed')
camFeed.src = webkitURL.createObjectURL(stream);
}
function onFail()
{
alert('could not connect stream');
}
<video id="camFeed" width="320" height="240" autoplay></video>
gui.Screen.Init();
gui.Screen.chooseDesktopMedia(
["window", "screen"], function(streamId) {
var vid_constraint = {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: streamId,
maxWidth: 1920,
maxHeight: 1080,
minFrameRate: 1,
maxFrameRate: 5
},
optional: []
};
navigator.webkitGetUserMedia({
audio: false,
video: vid_constraint
},
function(stream) {
document.getElementById('video_1').src = URL.createObjectURL(stream);
stream.onended = function() { console.log("Ended"); };
},
function(error) {
console.log('failure', error);
});
});
// JS way
gui.Window.open('noframe/index.html', { toolbar : false, frame : false });
<body style="-webkit-app-region: drag">
<h1>No frame</h1>
<button onclick="window.close()">Close</button>
</body>
// package.json
{
"window": {
"frame": false,
"toolbar": false
}
}
var gui = require('nw.gui');
gui.Window.get().enterKioskMode();
gui.Window.get().leaveKioskMode();
gui.App.quit();
// package.json
{
"window": {
"kiosk": true
}
}
<iframe src="http://google.com"
nwdisable
nwfaketop
nwUserAgent="Safari">
</iframe>
// setting value of file input element
var f = new File('/path/to/file', 'name');
var files = new FileList();
files.append(f);
document.getElementById('input0').files = files;
chooseFile('#fileDialog');
// emulate click on file inputs
function chooseFile(name) {
var chooser = document.querySelector(name);
chooser.addEventListener("change", function(evt) {
console.log(this.value);
console.log(this.files);
}, false);
chooser.click(); // <= here
}
<input type="file" webkitdirectory />
<input type="file" nwdirectory />
<input type="file" nwsaveas="filename.txt" />
<input type="file" nwworkingdir="/home/path/" />
var gui = require('nw.gui');
var Datastore = require('nedb');
var path = require('path');
var persons = new Datastore({
filename: path.join(gui.App.dataPath, 'persons.db')
});
var gulp = require('gulp');
gulp.task('reload', function () {
for (var module_name in global.require.cache)
delete global.require.cache[module_name]
if (location) location.reload();
});
gulp.watch('**/*', ['reload']);
$ npm i nw-dev --save-dev
<script src="node_modules/nw-dev/lib/dev.js"></script>
$ npm i gulp --save-dev
// package.json
{
"window": {
"toolbar": true
}
}
require('nw.gui')
.Window.get()
.showDevTools()
# Remote Debugging
# open http://localhost:9222/ to visit the debugger remotely
nw --remote-debugging-port=9222
$ npm install -g nw-gyp
$ nw-gyp configure --target=0.12.2
$ nw-gyp build
// binding.cc
#include <node.h>
#include <v8.h>
using namespace v8;
void Method(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world"));
}
void init(Handle<Object> target) {
NODE_SET_METHOD(target, "hello", Method);
}
NODE_MODULE(binding, init);
// binding.gyp
{
"targets": [
{
"target_name": "binding",
"sources": [ "binding.cc" ]
}
]
}
// test.js
var assert = require('assert');
var binding = require('./build/Release/binding');
assert.equal('world', binding.hello());
console.log('binding.hello() =', binding.hello());
# nwjs 0.12.0-rc1+
nwjc source.js binary.bin
require('nw.gui')
.Window.get()
.evalNWBin(null, 'binary.bin');
mytest(2);
# nwjs < 0.12.0-rc1
nwsnapshot --extra_code source.js snapshot.bin
// package.json
"snapshot" : "snapshot.bin"
// source.js
function mytest(a) {
document.write(a + 42);
}
ONE DOES NOT SIMPLY
HIDE HIS SOURCE
# Zip your application into .nw file
# zipped file would be at parent directory
zip -r ../${PWD##*/}.nw *
# Windows: Making an executable file out of a .nw file.
copy /b nw.exe+hello-world.nw hello-world.exe
# Linux: Making an executable file out of a .nw file.
cat /usr/bin/nw hello-world.nw > hello-world && chmod +x hello-world
# Max OSX: Making an executable file.
# Create folder `HelloWorld.app/Contents/Resources/hello-world.nw`
# and put all contents of your code in there
var NwBuilder = require('nw-builder');
var nw = new NwBuilder({
files: './app/**/**', // use the glob format
platforms: ['osx32', 'osx64', 'win32', 'win64'],
macIcns: 'icons/app.icns'
});
// Build returns a promise
nw.build().then(function () {
console.log('all done!');
}).catch(function (error) {
console.error(error);
});
// package.json
{
"window": {
"icon": "app.png"
}
}
Q&A Time
ANDRIY DACENKO
SOFTWARE ENGINEER
September 9, 2015
THREE PLATFORMS ONE SOLUTION