├── .gitignore
├── CHANGELOG
├── LICENSE
├── README.md
├── assets-linux
├── README.md
├── after-install.sh
├── after-remove.sh
├── icons
│ ├── 48
│ │ └── starter.png
│ ├── 256
│ │ └── starter.png
│ └── scalable
│ │ └── starter.svg
└── starter.desktop
├── assets-osx
├── README.md
├── dmg.json
├── dmg_bg.png
├── dmg_bg@2x.png
├── dmg_icon.icns
└── icon.icns
├── assets-windows
├── README.md
├── icon.ico
└── installer.nsi
├── design.sketch
├── gulpfile.coffee
├── package.json
├── screenshot.jpg
├── src
├── app.css
├── app.html
├── app.js
├── components
│ ├── dispatcher.js
│ ├── menus.js
│ ├── platform.js
│ ├── settings.js
│ ├── updater.js
│ └── window-behaviour.js
├── images
│ ├── icon.png
│ ├── icon_menubar.tiff
│ ├── icon_menubar_alert.tiff
│ ├── icon_tray.png
│ └── icon_tray_alert.png
└── package.json
└── version.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /dist/
3 | /cache/
4 | node_modules/
5 |
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 | 0.1.0
2 | - First release
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Alexandru Rosianu
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 | # NW.js Starter
2 |
3 | [](https://gitter.im/Aluxian/nwjs-starter?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4 | [](https://www.codementor.io/aluxian)
5 |
6 | This is a sample project to get you up and running with [NW.js](http://nwjs.io/).
7 |
8 | If you need help, read my tutorial [here](http://blog.aluxian.com/how-to-create-cross-platform-desktop-apps-with-nw-js/). This is a screenshot of the app that the tutorial guides you through, Klout:
9 |
10 | 
11 |
12 | ## Features
13 |
14 | * native notifications
15 | * auto update
16 | * gulp tasks to
17 | * build each release
18 | * sign the OS X app
19 | * pack
20 | * Windows: nsis installer
21 | * OSX: dmg
22 | * Linux: deb package
23 | * upload to GitHub
24 | * remembers window position when closed
25 | * customizable settings in the right-click menu
26 | * run as menu bar app
27 | * launch on OS startup
28 | * open links in the browser
29 |
30 | What doesn't work:
31 |
32 | * tray icon on linux
33 | * rpm builds
34 |
35 | Not done yet:
36 | * Windows binary signing
37 |
38 | ## Pre-requisites
39 |
40 | # install gulp
41 | npm install -g gulp
42 |
43 | # install dependencies
44 | npm install
45 |
46 | * **wine**: If you're on OS X/Linux and want to build for Windows, you need [Wine](http://winehq.org/) installed. Wine is required in order
47 | to set the correct icon for the exe. If you don't have Wine, you can comment out the `winIco` field in `gulpfile`.
48 | * **makensis**: Required by the `pack:win32` task in `gulpfile` to create the Windows installer.
49 | * [**fpm**](https://github.com/jordansissel/fpm): Required by the `pack:linux{32|64}:deb` tasks in `gulpfile` to create the Linux installers.
50 |
51 | Quickly install this on OS X:
52 |
53 | brew install wine makensis
54 | sudo gem install fpm
55 |
56 | ## Build
57 |
58 | ### OS X: pack the app in a .dmg
59 |
60 | gulp pack:osx64
61 |
62 | ### Windows: create the installer
63 |
64 | gulp pack:win32
65 |
66 | ### Linux 32/64-bit: pack the app in a .deb
67 |
68 | gulp pack:linux{32|64}:deb
69 |
70 | The output is in `./dist`. Take a look at `gulpfile.coffee` for additional tasks.
71 |
72 | ## Tips
73 |
74 | Use the `--toolbar` parameter to quickly build the app with the toolbar on. E.g. `gulp build:win32 --toolbar`.
75 |
76 | Use `gulp build:win32 --noicon` to quickly build the Windows app without the icon, which normally takes some seconds.
77 |
78 | For OS X, use the `run:osx64` task to build the app and run it immediately.
79 |
80 | Use these to create the .tiff images for the OS X menu bar:
81 |
82 | $ tiffutil -cathidpicheck icon_menubar.png icon_menubar@2x.png -out icon_menubar.tiff
83 | $ tiffutil -cathidpicheck icon_menubar_alert.png icon_menubar_alert@2x.png -out icon_menubar_alert.tiff
84 |
85 | ## Contributions
86 |
87 | Contributions are welcome! For feature requests and bug reports please [submit an issue](https://github.com/Aluxian/nwjs-starter/issues).
88 |
89 | ## License
90 |
91 | The MIT License (MIT)
92 |
93 | Copyright (c) 2015 Alexandru Rosianu
94 |
95 | Permission is hereby granted, free of charge, to any person obtaining a copy
96 | of this software and associated documentation files (the "Software"), to deal
97 | in the Software without restriction, including without limitation the rights
98 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99 | copies of the Software, and to permit persons to whom the Software is
100 | furnished to do so, subject to the following conditions:
101 |
102 | The above copyright notice and this permission notice shall be included in all
103 | copies or substantial portions of the Software.
104 |
105 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
106 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
107 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
108 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
109 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
110 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
111 | SOFTWARE.
112 |
--------------------------------------------------------------------------------
/assets-linux/README.md:
--------------------------------------------------------------------------------
1 | # Linux related assets
2 |
3 | - `after-install` and `after-remove` scripts
4 | - `starter.desktop` and icons for launchers
5 |
--------------------------------------------------------------------------------
/assets-linux/after-install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Link to the binary
4 | ln -sf /opt/starter/Starter /usr/local/bin/starter
5 |
6 | # Launcher icon
7 | desktop-file-install /opt/starter/starter.desktop
8 |
--------------------------------------------------------------------------------
/assets-linux/after-remove.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Link to the binary
4 | rm -f /usr/local/bin/starter
5 |
6 | # Launcher files
7 | rm -f /usr/share/applications/starter.desktop
8 |
--------------------------------------------------------------------------------
/assets-linux/icons/256/starter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aluxian/nwjs-starter/1eefb7bf0ad89091143644c849314c94771a2da4/assets-linux/icons/256/starter.png
--------------------------------------------------------------------------------
/assets-linux/icons/48/starter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aluxian/nwjs-starter/1eefb7bf0ad89091143644c849314c94771a2da4/assets-linux/icons/48/starter.png
--------------------------------------------------------------------------------
/assets-linux/icons/scalable/starter.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets-linux/starter.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Version=1.0
3 | Type=Application
4 | Name=Starter
5 | Icon=starter
6 | Exec=/opt/starter/Starter
7 | Comment=Starter
8 | Categories=GNOME;GTK;Development;Documentation;
9 | Terminal=false
10 |
--------------------------------------------------------------------------------
/assets-osx/README.md:
--------------------------------------------------------------------------------
1 | # OS X related assets
2 |
3 | - `dmg.json` config file for node-appdmg
4 |
--------------------------------------------------------------------------------
/assets-osx/dmg.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Starter",
3 | "icon": "dmg_icon.icns",
4 | "background": "dmg_bg.png",
5 | "icon-size": 80,
6 | "contents": [
7 | { "x": 448, "y": 344, "type": "link", "path": "/Applications" },
8 | { "x": 192, "y": 344, "type": "file", "path": "../build/Starter/osx64/Starter.app" }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/assets-osx/dmg_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aluxian/nwjs-starter/1eefb7bf0ad89091143644c849314c94771a2da4/assets-osx/dmg_bg.png
--------------------------------------------------------------------------------
/assets-osx/dmg_bg@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aluxian/nwjs-starter/1eefb7bf0ad89091143644c849314c94771a2da4/assets-osx/dmg_bg@2x.png
--------------------------------------------------------------------------------
/assets-osx/dmg_icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aluxian/nwjs-starter/1eefb7bf0ad89091143644c849314c94771a2da4/assets-osx/dmg_icon.icns
--------------------------------------------------------------------------------
/assets-osx/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aluxian/nwjs-starter/1eefb7bf0ad89091143644c849314c94771a2da4/assets-osx/icon.icns
--------------------------------------------------------------------------------
/assets-windows/README.md:
--------------------------------------------------------------------------------
1 | # Windows related assets
2 |
3 | - `installer.nsi` config file for nsis
4 |
--------------------------------------------------------------------------------
/assets-windows/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aluxian/nwjs-starter/1eefb7bf0ad89091143644c849314c94771a2da4/assets-windows/icon.ico
--------------------------------------------------------------------------------
/assets-windows/installer.nsi:
--------------------------------------------------------------------------------
1 | !include "MUI2.nsh"
2 |
3 | Name "Starter"
4 | BrandingText "aluxian.com"
5 |
6 | # set the icon
7 | !define MUI_ICON "icon.ico"
8 |
9 | # define the resulting installer's name:
10 | OutFile "..\dist\StarterSetup.exe"
11 |
12 | # set the installation directory
13 | InstallDir "$PROGRAMFILES\Starter\"
14 |
15 | # app dialogs
16 | !insertmacro MUI_PAGE_WELCOME
17 | !insertmacro MUI_PAGE_INSTFILES
18 |
19 | !define MUI_FINISHPAGE_RUN_TEXT "Start Starter"
20 | !define MUI_FINISHPAGE_RUN "$INSTDIR\Starter.exe"
21 |
22 | !insertmacro MUI_PAGE_FINISH
23 | !insertmacro MUI_LANGUAGE "English"
24 |
25 | # default section start
26 | Section
27 |
28 | # delete the installed files
29 | RMDir /r $INSTDIR
30 |
31 | # define the path to which the installer should install
32 | SetOutPath $INSTDIR
33 |
34 | # specify the files to go in the output path
35 | File /r ..\build\Starter\win32\*
36 |
37 | # create the uninstaller
38 | WriteUninstaller "$INSTDIR\Uninstall Starter.exe"
39 |
40 | # create shortcuts in the start menu and on the desktop
41 | CreateShortCut "$SMPROGRAMS\Starter.lnk" "$INSTDIR\Starter.exe"
42 | CreateShortCut "$SMPROGRAMS\Uninstall Starter.lnk" "$INSTDIR\Uninstall Starter.exe"
43 | CreateShortCut "$DESKTOP\Starter.lnk" "$INSTDIR\Starter.exe"
44 |
45 | SectionEnd
46 |
47 | # create a section to define what the uninstaller does
48 | Section "Uninstall"
49 |
50 | # delete the installed files
51 | RMDir /r $INSTDIR
52 |
53 | # delete the shortcuts
54 | Delete "$SMPROGRAMS\Starter.lnk"
55 | Delete "$SMPROGRAMS\Uninstall Starter.lnk"
56 | Delete "$DESKTOP\Starter.lnk"
57 |
58 | SectionEnd
59 |
--------------------------------------------------------------------------------
/design.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aluxian/nwjs-starter/1eefb7bf0ad89091143644c849314c94771a2da4/design.sketch
--------------------------------------------------------------------------------
/gulpfile.coffee:
--------------------------------------------------------------------------------
1 | gulp = require 'gulp'
2 | shelljs = require 'shelljs'
3 | mergeStream = require 'merge-stream'
4 | runSequence = require 'run-sequence'
5 | manifest = require './package.json'
6 | $ = require('gulp-load-plugins')()
7 |
8 | # Remove directories used by the tasks
9 | gulp.task 'clean', ->
10 | shelljs.rm '-rf', './build'
11 | shelljs.rm '-rf', './dist'
12 |
13 | # Build for each platform; on OSX/Linux, you need Wine installed to build win32 (or remove winIco below)
14 | ['win32', 'osx64', 'linux32', 'linux64'].forEach (platform) ->
15 | gulp.task 'build:' + platform, ->
16 | if process.argv.indexOf('--toolbar') > 0
17 | shelljs.sed '-i', '"toolbar": false', '"toolbar": true', './src/package.json'
18 |
19 | gulp.src './src/**'
20 | .pipe $.nodeWebkitBuilder
21 | platforms: [platform]
22 | version: '0.12.2'
23 | winIco: if process.argv.indexOf('--noicon') > 0 then undefined else './assets-windows/icon.ico'
24 | macIcns: './assets-osx/icon.icns'
25 | macZip: true
26 | macPlist:
27 | NSHumanReadableCopyright: 'aluxian.com'
28 | CFBundleIdentifier: 'com.aluxian.starter'
29 | .on 'end', ->
30 | if process.argv.indexOf('--toolbar') > 0
31 | shelljs.sed '-i', '"toolbar": true', '"toolbar": false', './src/package.json'
32 |
33 | # Only runs on OSX (requires XCode properly configured)
34 | gulp.task 'sign:osx64', ['build:osx64'], ->
35 | shelljs.exec 'codesign -v -f -s "Alexandru Rosianu Apps" ./build/Starter/osx64/Starter.app/Contents/Frameworks/*'
36 | shelljs.exec 'codesign -v -f -s "Alexandru Rosianu Apps" ./build/Starter/osx64/Starter.app'
37 | shelljs.exec 'codesign -v --display ./build/Starter/osx64/Starter.app'
38 | shelljs.exec 'codesign -v --verify ./build/Starter/osx64/Starter.app'
39 |
40 | # Create a DMG for osx64; only works on OS X because of appdmg
41 | gulp.task 'pack:osx64', ['sign:osx64'], ->
42 | shelljs.mkdir '-p', './dist' # appdmg fails if ./dist doesn't exist
43 | shelljs.rm '-f', './dist/Starter.dmg' # appdmg fails if the dmg already exists
44 |
45 | gulp.src []
46 | .pipe require('gulp-appdmg')
47 | source: './assets-osx/dmg.json'
48 | target: './dist/Starter.dmg'
49 |
50 | # Create a nsis installer for win32; must have `makensis` installed
51 | gulp.task 'pack:win32', ['build:win32'], ->
52 | shelljs.exec 'makensis ./assets-windows/installer.nsi'
53 |
54 | # Create packages for linux
55 | [32, 64].forEach (arch) ->
56 | ['deb', 'rpm'].forEach (target) ->
57 | gulp.task "pack:linux#{arch}:#{target}", ['build:linux' + arch], ->
58 | shelljs.rm '-rf', './build/linux'
59 |
60 | move_opt = gulp.src [
61 | './assets-linux/starter.desktop'
62 | './assets-linux/after-install.sh'
63 | './assets-linux/after-remove.sh'
64 | './build/Starter/linux' + arch + '/**'
65 | ]
66 | .pipe gulp.dest './build/linux/opt/starter'
67 |
68 | move_png48 = gulp.src './assets-linux/icons/48/starter.png'
69 | .pipe gulp.dest './build/linux/usr/share/icons/hicolor/48x48/apps'
70 |
71 | move_png256 = gulp.src './assets-linux/icons/256/starter.png'
72 | .pipe gulp.dest './build/linux/usr/share/icons/hicolor/256x256/apps'
73 |
74 | move_svg = gulp.src './assets-linux/icons/scalable/starter.png'
75 | .pipe gulp.dest './build/linux/usr/share/icons/hicolor/scalable/apps'
76 |
77 | mergeStream move_opt, move_png48, move_png256, move_svg
78 | .on 'end', ->
79 | shelljs.cd './build/linux'
80 |
81 | port = if arch == 32 then 'i386' else 'x86_64'
82 | output = "../../dist/Starter_linux#{arch}.#{target}"
83 |
84 | shelljs.mkdir '-p', '../../dist' # it fails if the dir doesn't exist
85 | shelljs.rm '-f', output # it fails if the package already exists
86 |
87 | shelljs.exec "fpm -s dir -t #{target} -a #{port} --rpm-os linux -n starter --after-install ./opt/starter/after-install.sh --after-remove ./opt/starter/after-remove.sh --license MIT --category Chat --url \"https://example.com\" --description \"A sample NW.js app.\" -m \"Alexandru Rosianu \" -p #{output} -v #{manifest.version} ."
88 | shelljs.cd '../..'
89 |
90 | # Make packages for all platforms
91 | gulp.task 'pack:all', (callback) ->
92 | runSequence 'pack:osx64', 'pack:win32', 'pack:linux32:deb', 'pack:linux64:deb', callback
93 |
94 | # Build osx64 and run it
95 | gulp.task 'run:osx64', ['build:osx64'], ->
96 | shelljs.exec 'open ./build/Starter/osx64/Starter.app'
97 |
98 | # Run osx64 without building
99 | gulp.task 'open:osx64', ->
100 | shelljs.exec 'open ./build/Starter/osx64/Starter.app'
101 |
102 | # Upload release to GitHub
103 | gulp.task 'release', ['pack:all'], (callback) ->
104 | gulp.src './dist/*'
105 | .pipe $.githubRelease
106 | draft: true
107 | manifest: manifest
108 |
109 | # Make packages for all platforms by default
110 | gulp.task 'default', ['pack:all']
111 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "starter",
3 | "version": "0.1.0",
4 | "description": "NW.js starter project powered by Gulp.",
5 | "repository": {
6 | "type": "git",
7 | "url": "git@github.com:Aluxian/nwjs-starter.git"
8 | },
9 | "scripts": {
10 | "postinstall": "(cd src && npm install)"
11 | },
12 | "devDependencies": {
13 | "coffee-script": "^1.9.3",
14 | "gulp": "^3.8.11",
15 | "gulp-github-release": "^1.0.3",
16 | "gulp-load-plugins": "^0.10.0",
17 | "gulp-node-webkit-builder": "^1.1.1",
18 | "gulp-uglify": "^1.2.0",
19 | "merge-stream": "^0.1.7",
20 | "run-sequence": "^1.1.0",
21 | "shelljs": "^0.5.0"
22 | },
23 | "optionalDependencies": {
24 | "gulp-appdmg": "^1.0.2"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aluxian/nwjs-starter/1eefb7bf0ad89091143644c849314c94771a2da4/screenshot.jpg
--------------------------------------------------------------------------------
/src/app.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | border: none;
5 | }
6 |
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Starter
5 |
6 |
7 |
8 | Hello World!
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | var gui = require('nw.gui');
2 | var win = gui.Window.get();
3 |
4 | var platform = require('./components/platform');
5 | var updater = require('./components/updater');
6 | var menus = require('./components/menus');
7 | var settings = require('./components/settings');
8 | var windowBehaviour = require('./components/window-behaviour');
9 | var dispatcher = require('./components/dispatcher');
10 |
11 | // Ensure there's an app shortcut for toast notifications to work on Windows
12 | if (platform.isWindows) {
13 | gui.App.createShortcut(process.env.APPDATA + "\\Microsoft\\Windows\\Start Menu\\Programs\\Starter.lnk");
14 | }
15 |
16 | // Add dispatcher events
17 | dispatcher.addEventListener('win.alert', function(data) {
18 | data.win.window.alert(data.message);
19 | });
20 |
21 | dispatcher.addEventListener('win.confirm', function(data) {
22 | data.callback(data.win.window.confirm(data.message));
23 | });
24 |
25 | // Window state
26 | windowBehaviour.restoreWindowState(win);
27 | windowBehaviour.bindWindowStateEvents(win);
28 |
29 | // Check for update
30 | if (settings.checkUpdateOnLaunch) {
31 | updater.checkAndPrompt(gui.App.manifest, win);
32 | }
33 |
34 | // Run as menu bar app
35 | if (settings.asMenuBarAppOSX) {
36 | win.setShowInTaskbar(false);
37 | menus.loadTrayIcon(win);
38 | }
39 |
40 | // Load the app menus
41 | menus.loadMenuBar(win)
42 | if (platform.isWindows) {
43 | menus.loadTrayIcon(win);
44 | }
45 |
46 | // Adjust the default behaviour of the main window
47 | windowBehaviour.set(win);
48 | windowBehaviour.setNewWinPolicy(win);
49 |
50 | // Add a context menu
51 | menus.injectContextMenu(win, window, document);
52 |
53 | // Reload the app periodically until it loads
54 | var reloadIntervalId = setInterval(function() {
55 | if (win.window.navigator.onLine) {
56 | clearInterval(reloadIntervalId);
57 | } else {
58 | win.reload();
59 | }
60 | }, 10 * 1000);
61 |
--------------------------------------------------------------------------------
/src/components/dispatcher.js:
--------------------------------------------------------------------------------
1 | var callbacks = {};
2 | var queue = [];
3 |
4 | setInterval(function() {
5 | while (queue.length) {
6 | var event = queue[0][0];
7 | var data = queue[0][1];
8 |
9 | callbacks[event].forEach(function(callback) {
10 | callback(data);
11 | });
12 |
13 | queue.splice(0, 1);
14 | }
15 | }, 100);
16 |
17 | /**
18 | * Like an EventEmitter, but runs differently. Workaround for dialog crashes. Singleton.
19 | */
20 | module.exports = {
21 | addEventListener: function(event, callback) {
22 | if (!callbacks[event]) {
23 | callbacks[event] = [];
24 | }
25 |
26 | callbacks[event].push(callback);
27 | },
28 |
29 | removeAllListeners: function(event) {
30 | callbacks[event] = [];
31 | },
32 |
33 | trigger: function(event, data) {
34 | queue.push([event, data]);
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/src/components/menus.js:
--------------------------------------------------------------------------------
1 | var gui = window.require('nw.gui');
2 | var clipboard = gui.Clipboard.get();
3 | var AutoLaunch = require('auto-launch');
4 | var windowBehaviour = require('./window-behaviour');
5 | var dispatcher = require('./dispatcher');
6 | var platform = require('./platform');
7 | var settings = require('./settings');
8 | var updater = require('./updater');
9 |
10 | module.exports = {
11 | /**
12 | * The main settings items. Their placement differs for each platform:
13 | * - on OS X they're in the top menu bar
14 | * - on Windows they're in the tray icon's menu
15 | * - on all 3 platform, they're also in the right-click context menu
16 | */
17 | settingsItems: function(win, keep) {
18 | var self = this;
19 | return [{
20 | label: 'Reload',
21 | click: function() {
22 | windowBehaviour.saveWindowState(win);
23 | win.reload();
24 | }
25 | }, {
26 | type: 'checkbox',
27 | label: 'Open Links in the Browser',
28 | setting: 'openLinksInBrowser',
29 | click: function() {
30 | settings.openLinksInBrowser = this.checked;
31 | windowBehaviour.setNewWinPolicy(win);
32 | }
33 | }, {
34 | type: 'separator'
35 | }, {
36 | type: 'checkbox',
37 | label: 'Run as Menu Bar App',
38 | setting: 'asMenuBarAppOSX',
39 | platforms: ['osx'],
40 | click: function() {
41 | settings.asMenuBarAppOSX = this.checked;
42 | win.setShowInTaskbar(!this.checked);
43 |
44 | if (this.checked) {
45 | self.loadTrayIcon(win);
46 | } else if (win.tray) {
47 | win.tray.remove();
48 | win.tray = null;
49 | }
50 | }
51 | }, {
52 | type: 'checkbox',
53 | label: 'Launch on Startup',
54 | setting: 'launchOnStartup',
55 | platforms: ['osx', 'win'],
56 | click: function() {
57 | settings.launchOnStartup = this.checked;
58 |
59 | var launcher = new AutoLaunch({
60 | name: 'Starter',
61 | isHidden: true // hidden on launch - only works on a mac atm
62 | });
63 |
64 | launcher.isEnabled(function(enabled) {
65 | if (settings.launchOnStartup && !enabled) {
66 | launcher.enable(function(error) {
67 | if (error) {
68 | console.error(error);
69 | }
70 | });
71 | }
72 |
73 | if (!settings.launchOnStartup && enabled) {
74 | launcher.disable(function(error) {
75 | if (error) {
76 | console.error(error);
77 | }
78 | });
79 | }
80 | });
81 | }
82 | }, {
83 | type: 'checkbox',
84 | label: 'Check for Update on Launch',
85 | setting: 'checkUpdateOnLaunch'
86 | }, {
87 | type: 'separator'
88 | }, {
89 | label: 'Check for Update',
90 | click: function() {
91 | updater.check(gui.App.manifest, function(error, newVersionExists, newManifest) {
92 | if (error || newVersionExists) {
93 | updater.prompt(win, false, error, newVersionExists, newManifest);
94 | } else {
95 | dispatcher.trigger('win.alert', {
96 | win: win,
97 | message: 'You’re using the latest version: ' + gui.App.manifest.version
98 | });
99 | }
100 | });
101 | }
102 | }, {
103 | label: 'Launch Dev Tools',
104 | click: function() {
105 | win.showDevTools();
106 | }
107 | }].map(function(item) {
108 | // If the item has a 'setting' property, use some predefined values
109 | if (item.setting) {
110 | if (!item.hasOwnProperty('checked')) {
111 | item.checked = settings[item.setting];
112 | }
113 |
114 | if (!item.hasOwnProperty('click')) {
115 | item.click = function() {
116 | settings[item.setting] = item.checked;
117 | };
118 | }
119 | }
120 |
121 | return item;
122 | }).filter(function(item) {
123 | // Remove the item if the current platform is not supported
124 | return !Array.isArray(item.platforms) || (item.platforms.indexOf(platform.type) != -1);
125 | }).map(function(item) {
126 | var menuItem = new gui.MenuItem(item);
127 | menuItem.setting = item.setting;
128 | return menuItem;
129 | });
130 | },
131 |
132 | /**
133 | * Create the menu bar for the given window, only on OS X.
134 | */
135 | loadMenuBar: function(win) {
136 | if (!platform.isOSX) {
137 | return;
138 | }
139 |
140 | var menu = new gui.Menu({
141 | type: 'menubar'
142 | });
143 |
144 | menu.createMacBuiltin('Starter');
145 | var submenu = menu.items[0].submenu;
146 |
147 | submenu.insert(new gui.MenuItem({
148 | type: 'separator'
149 | }), 1);
150 |
151 | // Add the main settings
152 | this.settingsItems(win, true).forEach(function(item, index) {
153 | submenu.insert(item, index + 2);
154 | });
155 |
156 | // Watch the items that have a 'setting' property
157 | submenu.items.forEach(function(item) {
158 | if (item.setting) {
159 | settings.watch(item.setting, function(value) {
160 | item.checked = value;
161 | });
162 | }
163 | });
164 |
165 | win.menu = menu;
166 | },
167 |
168 | /**
169 | * Create the menu for the tray icon.
170 | */
171 | createTrayMenu: function(win) {
172 | var menu = new gui.Menu();
173 |
174 | // Add the main settings
175 | this.settingsItems(win, true).forEach(function(item) {
176 | menu.append(item);
177 | });
178 |
179 | menu.append(new gui.MenuItem({
180 | type: 'separator'
181 | }));
182 |
183 | menu.append(new gui.MenuItem({
184 | label: 'Show Starter',
185 | click: function() {
186 | win.show();
187 | }
188 | }));
189 |
190 | menu.append(new gui.MenuItem({
191 | label: 'Quit Starter',
192 | click: function() {
193 | win.close(true);
194 | }
195 | }));
196 |
197 | // Watch the items that have a 'setting' property
198 | menu.items.forEach(function(item) {
199 | if (item.setting) {
200 | settings.watch(item.setting, function(value) {
201 | item.checked = value;
202 | });
203 | }
204 | });
205 |
206 | return menu;
207 | },
208 |
209 | /**
210 | * Create the tray icon.
211 | */
212 | loadTrayIcon: function(win) {
213 | if (win.tray) {
214 | win.tray.remove();
215 | win.tray = null;
216 | }
217 |
218 | var tray = new gui.Tray({
219 | icon: 'images/icon_' + (platform.isOSX ? 'menubar.tiff' : 'tray.png')
220 | });
221 |
222 | tray.on('click', function() {
223 | win.show();
224 | });
225 |
226 | tray.tooltip = 'Starter';
227 | tray.menu = this.createTrayMenu(win);
228 |
229 | // keep the object in memory
230 | win.tray = tray;
231 | },
232 |
233 | /**
234 | * Create a context menu for the window and document.
235 | */
236 | createContextMenu: function(win, window, document, targetElement) {
237 | var menu = new gui.Menu();
238 |
239 | if (targetElement.tagName.toLowerCase() == 'input') {
240 | menu.append(new gui.MenuItem({
241 | label: "Cut",
242 | click: function() {
243 | clipboard.set(targetElement.value);
244 | targetElement.value = '';
245 | }
246 | }));
247 |
248 | menu.append(new gui.MenuItem({
249 | label: "Copy",
250 | click: function() {
251 | clipboard.set(targetElement.value);
252 | }
253 | }));
254 |
255 | menu.append(new gui.MenuItem({
256 | label: "Paste",
257 | click: function() {
258 | targetElement.value = clipboard.get();
259 | }
260 | }));
261 | } else if (targetElement.tagName.toLowerCase() == 'a') {
262 | menu.append(new gui.MenuItem({
263 | label: "Copy Link",
264 | click: function() {
265 | clipboard.set(targetElement.href);
266 | }
267 | }));
268 | } else {
269 | var selection = window.getSelection().toString();
270 | if (selection.length > 0) {
271 | menu.append(new gui.MenuItem({
272 | label: "Copy",
273 | click: function() {
274 | clipboard.set(selection);
275 | }
276 | }));
277 | }
278 | }
279 |
280 | this.settingsItems(win, false).forEach(function(item) {
281 | menu.append(item);
282 | });
283 |
284 | return menu;
285 | },
286 |
287 | /**
288 | * Listen for right clicks and show a context menu.
289 | */
290 | injectContextMenu: function(win, window, document) {
291 | document.body.addEventListener('contextmenu', function(event) {
292 | event.preventDefault();
293 | this.createContextMenu(win, window, document, event.target).popup(event.x, event.y);
294 | return false;
295 | }.bind(this));
296 | }
297 | };
298 |
--------------------------------------------------------------------------------
/src/components/platform.js:
--------------------------------------------------------------------------------
1 | var platform = process.platform;
2 | var arch = process.arch === 'ia32' ? '32' : '64';
3 |
4 | platform = platform.indexOf('win') === 0 ? 'win'
5 | : platform.indexOf('darwin') === 0 ? 'osx'
6 | : 'linux';
7 |
8 | module.exports = {
9 | isOSX: platform === 'osx',
10 | isWindows: platform === 'win',
11 | isLinux: platform === 'linux',
12 | name: platform + arch,
13 | type: platform,
14 | arch: arch
15 | };
16 |
--------------------------------------------------------------------------------
/src/components/settings.js:
--------------------------------------------------------------------------------
1 | var Store = require('jfs');
2 | var path = require('path');
3 | var gui = window.require('nw.gui');
4 |
5 | var DEFAULT_SETTINGS = {
6 | launchOnStartup: false,
7 | checkUpdateOnLaunch: false,
8 | openLinksInBrowser: true,
9 | asMenuBarAppOSX: false,
10 | windowState: {},
11 | };
12 |
13 | var db = new Store(path.join(gui.App.dataPath, 'preferences.json'));
14 | var settings = db.getSync('settings');
15 | var watchers = {};
16 |
17 | // Watch changes to the storage
18 | settings.watch = function(name, callback) {
19 | if (!Array.isArray(watchers[name])) {
20 | watchers[name] = [];
21 | }
22 |
23 | watchers[name].push(callback);
24 | };
25 |
26 | // Save settings every time a change is made and notify watchers
27 | Object.observe(settings, function(changes) {
28 | db.save('settings', settings, function(err) {
29 | if (err) {
30 | console.error('Could not save settings', err);
31 | }
32 | });
33 |
34 | changes.forEach(function(change) {
35 | var newValue = change.object[change.name];
36 | var keyWatchers = watchers[change.name];
37 |
38 | // Call all the watcher functions for the changed key
39 | if (keyWatchers && keyWatchers.length) {
40 | for (var i = 0; i < keyWatchers.length; i++) {
41 | try {
42 | keyWatchers[i](newValue);
43 | } catch(ex) {
44 | console.error(ex);
45 | keyWatchers.splice(i--, 1);
46 | }
47 | }
48 | }
49 | });
50 | });
51 |
52 | // Ensure the default values exist
53 | Object.keys(DEFAULT_SETTINGS).forEach(function(key) {
54 | if (!settings.hasOwnProperty(key)) {
55 | settings[key] = DEFAULT_SETTINGS[key];
56 | }
57 | });
58 |
59 | module.exports = settings;
60 |
--------------------------------------------------------------------------------
/src/components/updater.js:
--------------------------------------------------------------------------------
1 | var gui = window.require('nw.gui');
2 | var platform = require('./platform');
3 | var dispatcher = require('./dispatcher');
4 | var request = require('request');
5 | var semver = require('semver');
6 |
7 | module.exports = {
8 | /**
9 | * Check if there's a new version available.
10 | */
11 | check: function(manifest, callback) {
12 | request(manifest.manifestUrl, function(error, response, body) {
13 | if (error) {
14 | return callback(error);
15 | }
16 |
17 | var newManifest = JSON.parse(body);
18 | var newVersionExists = semver.gt(newManifest.version, manifest.version);
19 |
20 | callback(null, newVersionExists, newManifest);
21 | });
22 | },
23 |
24 | /**
25 | * Show a dialog to ask the user to update.
26 | */
27 | prompt: function(win, ignoreError, error, newVersionExists, newManifest) {
28 | if (error) {
29 | if (!ignoreError) {
30 | dispatcher.trigger('win.alert', {
31 | win: win,
32 | message: 'Error while trying to update: ' + error
33 | });
34 | }
35 |
36 | return;
37 | }
38 |
39 | if (newVersionExists) {
40 | var updateMessage = 'There’s a new version available (' + newManifest.version + '). Would you like to download the update now?';
41 |
42 | dispatcher.trigger('win.confirm', {
43 | win: win,
44 | message: updateMessage,
45 | callback: function(result) {
46 | if (result) {
47 | gui.Shell.openExternal(newManifest.packages[platform.name]);
48 | gui.App.quit();
49 | }
50 | }
51 | });
52 | }
53 | },
54 |
55 | /**
56 | * Check for update and ask the user to update.
57 | */
58 | checkAndPrompt: function(manifest, win) {
59 | this.check(manifest, this.prompt.bind(this, win, true));
60 | }
61 | };
62 |
--------------------------------------------------------------------------------
/src/components/window-behaviour.js:
--------------------------------------------------------------------------------
1 | var gui = window.require('nw.gui');
2 | var platform = require('./platform');
3 | var settings = require('./settings');
4 |
5 | module.exports = {
6 | /**
7 | * Update the behaviour of the given window object.
8 | */
9 | set: function(win) {
10 | // Show the window when the dock icon is pressed
11 | gui.App.removeAllListeners('reopen');
12 | gui.App.on('reopen', function() {
13 | win.show();
14 | });
15 |
16 | // Don't quit the app when the window is closed
17 | if (!platform.isLinux) {
18 | win.removeAllListeners('close');
19 | win.on('close', function(quit) {
20 | if (quit) {
21 | this.saveWindowState(win);
22 | win.close(true);
23 | } else {
24 | win.hide();
25 | }
26 | }.bind(this));
27 | }
28 | },
29 |
30 | /**
31 | * Change the new window policy to open links in the browser or another window.
32 | */
33 | setNewWinPolicy: function(win) {
34 | win.removeAllListeners('new-win-policy');
35 | win.on('new-win-policy', function(frame, url, policy) {
36 | if (settings.openLinksInBrowser) {
37 | gui.Shell.openExternal(url);
38 | policy.ignore();
39 | } else {
40 | policy.forceNewWindow();
41 | }
42 | });
43 | },
44 |
45 | /**
46 | * Listen for window state events.
47 | */
48 | bindWindowStateEvents: function(win) {
49 | win.removeAllListeners('maximize');
50 | win.on('maximize', function() {
51 | win.sizeMode = 'maximized';
52 | });
53 |
54 | win.removeAllListeners('unmaximize');
55 | win.on('unmaximize', function() {
56 | win.sizeMode = 'normal';
57 | });
58 |
59 | win.removeAllListeners('minimize');
60 | win.on('minimize', function() {
61 | win.sizeMode = 'minimized';
62 | });
63 |
64 | win.removeAllListeners('restore');
65 | win.on('restore', function() {
66 | win.sizeMode = 'normal';
67 | });
68 | },
69 |
70 | /**
71 | * Store the window state.
72 | */
73 | saveWindowState: function(win) {
74 | var state = {
75 | mode: win.sizeMode || 'normal'
76 | };
77 |
78 | if (state.mode == 'normal') {
79 | state.x = win.x;
80 | state.y = win.y;
81 | state.width = win.width;
82 | state.height = win.height;
83 | }
84 |
85 | settings.windowState = state;
86 | },
87 |
88 | /**
89 | * Restore the window size and position.
90 | */
91 | restoreWindowState: function(win) {
92 | var state = settings.windowState;
93 |
94 | if (state.mode == 'maximized') {
95 | win.maximize();
96 | } else {
97 | win.resizeTo(state.width, state.height);
98 | win.moveTo(state.x, state.y);
99 | }
100 |
101 | win.show();
102 | }
103 | };
104 |
--------------------------------------------------------------------------------
/src/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aluxian/nwjs-starter/1eefb7bf0ad89091143644c849314c94771a2da4/src/images/icon.png
--------------------------------------------------------------------------------
/src/images/icon_menubar.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aluxian/nwjs-starter/1eefb7bf0ad89091143644c849314c94771a2da4/src/images/icon_menubar.tiff
--------------------------------------------------------------------------------
/src/images/icon_menubar_alert.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aluxian/nwjs-starter/1eefb7bf0ad89091143644c849314c94771a2da4/src/images/icon_menubar_alert.tiff
--------------------------------------------------------------------------------
/src/images/icon_tray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aluxian/nwjs-starter/1eefb7bf0ad89091143644c849314c94771a2da4/src/images/icon_tray.png
--------------------------------------------------------------------------------
/src/images/icon_tray_alert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aluxian/nwjs-starter/1eefb7bf0ad89091143644c849314c94771a2da4/src/images/icon_tray_alert.png
--------------------------------------------------------------------------------
/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "app.html",
3 | "name": "Starter",
4 | "version": "0.1.0",
5 | "app-id": "com.aluxian.starter",
6 | "chromium-args": "--disable-setuid-sandbox",
7 | "window": {
8 | "width": 800,
9 | "height": 600,
10 | "title": "Starter",
11 | "icon": "icons/icon.png",
12 | "toolbar": false,
13 | "show": false
14 | },
15 | "webkit": {
16 | "plugin": true
17 | },
18 | "dependencies": {
19 | "async": "^1.0.0",
20 | "auto-launch": "^0.1.18",
21 | "jfs": "^0.2.5",
22 | "request": "^2.56.0",
23 | "semver": "^4.3.5"
24 | },
25 | "manifestUrl": "https://raw.githubusercontent.com/Aluxian/nwjs-starter/master/src/package.json",
26 | "packages": {
27 | "osx64": "https://github.com/Aluxian/nwjs-starter/releases/download/v0.1.0/Starter.dmg",
28 | "win32": "https://github.com/Aluxian/nwjs-starter/releases/download/v0.1.0/StarterSetup.exe",
29 | "linux32": "https://github.com/Aluxian/nwjs-starter/releases/download/v0.1.0/Starter_linux32.deb",
30 | "linux64": "https://github.com/Aluxian/nwjs-starter/releases/download/v0.1.0/Starter_linux64.deb"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Use this file to quickly change the app version.
4 | # It will also tag, commit the change and push it.
5 | #
6 | # Usage: ./version.sh 1.2.0
7 |
8 | # Check $1
9 | if [ -z "$1" ]
10 | then
11 | echo "Version is required."
12 | fi
13 |
14 | # Replace version in package.json files
15 | sed -i.bak "s/\"version\": \".*\"/\"version\": \"$1\"/g" ./package.json
16 | sed -i.bak "s/\"version\": \".*\"/\"version\": \"$1\"/g" ./src/package.json
17 | sed -i.bak "s/download\/v.*\/UnofficialWhatsApp/download\/v$1\/UnofficialWhatsApp/g" ./src/package.json
18 |
19 | # Clean up
20 | rm ./package.json.bak
21 | rm ./src/package.json.bak
22 |
23 | # Edit CHANGELOG
24 | vim ./CHANGELOG
25 |
26 | # Git commit
27 | git add .
28 | git commit -m "New version v$1"
29 | git tag -a "v$1" -m "v$1"
30 |
31 | # TODO Paste all commits since the last tag into CHANGELOG
32 |
--------------------------------------------------------------------------------