├── .gitignore
├── screenshot.png
├── assets-osx
├── README.md
├── icon.icns
├── dmg_bg.png
├── dmg_bg@2x.png
├── dmg_icon.icns
└── dmg.json
├── assets-windows
├── README.md
├── icon.ico
└── installer.nsi
├── src
├── images
│ ├── icon.png
│ ├── loading.gif
│ ├── icon_tray.png
│ ├── icon_menubar.tiff
│ ├── icon_tray_alert.png
│ └── icon_menubar_alert.tiff
├── app.html
├── app.css
├── components
│ ├── platform.js
│ ├── dispatcher.js
│ ├── notification.js
│ ├── updater.js
│ ├── settings.js
│ ├── window-behaviour.js
│ └── menus.js
├── package.json
└── app.js
├── assets-linux
├── icons
│ ├── 48
│ │ └── whatsappfordesktop.png
│ ├── 256
│ │ └── whatsappfordesktop.png
│ └── scalable
│ │ └── whatsappfordesktop.svg
├── README.md
├── after-remove.sh
├── supforwhatsapp.desktop
└── after-install.sh
├── DISCLAIMER
├── package.json
├── LICENSE
├── README.md
└── gulpfile.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /dist/
3 | /cache/
4 | node_modules/
5 | .idea
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zweicoder/Sup-for-WhatsApp/HEAD/screenshot.png
--------------------------------------------------------------------------------
/assets-osx/README.md:
--------------------------------------------------------------------------------
1 | # OS X related assets
2 |
3 | - `dmg.json` config file for node-appdmg
4 |
--------------------------------------------------------------------------------
/assets-osx/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zweicoder/Sup-for-WhatsApp/HEAD/assets-osx/icon.icns
--------------------------------------------------------------------------------
/assets-windows/README.md:
--------------------------------------------------------------------------------
1 | # Windows related assets
2 |
3 | - `installer.nsi` config file for nsis
4 |
--------------------------------------------------------------------------------
/src/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zweicoder/Sup-for-WhatsApp/HEAD/src/images/icon.png
--------------------------------------------------------------------------------
/assets-osx/dmg_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zweicoder/Sup-for-WhatsApp/HEAD/assets-osx/dmg_bg.png
--------------------------------------------------------------------------------
/assets-windows/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zweicoder/Sup-for-WhatsApp/HEAD/assets-windows/icon.ico
--------------------------------------------------------------------------------
/src/images/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zweicoder/Sup-for-WhatsApp/HEAD/src/images/loading.gif
--------------------------------------------------------------------------------
/assets-osx/dmg_bg@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zweicoder/Sup-for-WhatsApp/HEAD/assets-osx/dmg_bg@2x.png
--------------------------------------------------------------------------------
/assets-osx/dmg_icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zweicoder/Sup-for-WhatsApp/HEAD/assets-osx/dmg_icon.icns
--------------------------------------------------------------------------------
/src/images/icon_tray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zweicoder/Sup-for-WhatsApp/HEAD/src/images/icon_tray.png
--------------------------------------------------------------------------------
/src/images/icon_menubar.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zweicoder/Sup-for-WhatsApp/HEAD/src/images/icon_menubar.tiff
--------------------------------------------------------------------------------
/src/images/icon_tray_alert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zweicoder/Sup-for-WhatsApp/HEAD/src/images/icon_tray_alert.png
--------------------------------------------------------------------------------
/src/images/icon_menubar_alert.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zweicoder/Sup-for-WhatsApp/HEAD/src/images/icon_menubar_alert.tiff
--------------------------------------------------------------------------------
/assets-linux/icons/256/whatsappfordesktop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zweicoder/Sup-for-WhatsApp/HEAD/assets-linux/icons/256/whatsappfordesktop.png
--------------------------------------------------------------------------------
/assets-linux/icons/48/whatsappfordesktop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zweicoder/Sup-for-WhatsApp/HEAD/assets-linux/icons/48/whatsappfordesktop.png
--------------------------------------------------------------------------------
/assets-linux/README.md:
--------------------------------------------------------------------------------
1 | # Linux related assets
2 |
3 | - `after-install` and `after-remove` scripts
4 | - `whatsappfordesktop.desktop` and icons for launchers
5 |
--------------------------------------------------------------------------------
/assets-linux/after-remove.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Link to the binary
4 | rm -f /usr/local/bin/supforwhatsapp
5 |
6 | # Launcher files
7 | rm -f /usr/share/applications/supforwhatsapp.desktop
8 |
--------------------------------------------------------------------------------
/assets-linux/supforwhatsapp.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Version=1.0
3 | Type=Application
4 | Name=supforwhatsapp
5 | Icon=whatsappfordesktop
6 | Exec=/opt/supforwhatsapp/supforwhatsapp
7 | Comment=Sup
8 | Categories=Network;Chat;
9 | Terminal=false
10 | Name[en_SG]=supforwhatsapp.desktop
11 |
--------------------------------------------------------------------------------
/assets-linux/after-install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Link to the binary
4 | ln -sf /opt/supforwhatsapp/supforwhatsapp /usr/local/bin/supforwhatsapp
5 |
6 | # REMEMBER TO CHOWN /OPT/SUPFORWHATSAPP AND CHMOD +X FOR PERMISSION ERRORS
7 |
8 | # Launcher icon
9 | desktop-file-install /opt/supforwhatsapp/supforwhatsapp.desktop
10 |
--------------------------------------------------------------------------------
/DISCLAIMER:
--------------------------------------------------------------------------------
1 | This project does not attempt to reverse engineer the WhatsApp API or
2 | attempt to reimplement any part of the WhatsApp client. Any communication
3 | between the user and WhatsApp servers is handled by WhatsApp Web itself;
4 | this is just a native wrapper for WhatsApp Web, more akin to a browser
5 | than any WhatsApp software.
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Sup For WhatsApp
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/assets-osx/dmg.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Unofficial WhatsApp",
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/SupForWhatsApp/osx64/SupForWhatsApp.app" }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/src/app.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | border: none;
5 | }
6 |
7 | html, body, iframe {
8 | width: 100%;
9 | height: 100%;
10 | overflow: hidden;
11 | }
12 |
13 | body {
14 | background-image: url('images/loading.gif');
15 | background-size: 24px 19px;
16 | background-repeat: no-repeat;
17 | background-attachment: fixed;
18 | background-position: center;
19 | }
20 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "supforwhatsapp",
3 | "version": "2.0.0",
4 | "description": "A Native desktop wrapper for WhatsApp. Not an official app.",
5 | "packages": {
6 | "win32": "https://github.com/zweicoder/Sup-for-WhatsApp/releases/download/v2.0.0/SupForWhatsApp.exe"
7 | },
8 | "repository": {
9 | "type": "git",
10 | "url": "git@github.com:zweicoder/Sup-for-WhatsApp.git"
11 | },
12 | "scripts": {
13 | "postinstall": "(cd src && npm install)"
14 | },
15 | "devDependencies": {
16 | "coffee-script": "^1.9.3",
17 | "gulp": "^3.8.11",
18 | "gulp-github-release": "^1.0.3",
19 | "gulp-json-editor": "^2.2.1",
20 | "gulp-load-plugins": "^0.10.0",
21 | "gulp-node-webkit-builder": "^1.1.1",
22 | "gulp-uglify": "^1.2.0",
23 | "gulp-zip": "^3.0.2",
24 | "merge-stream": "^0.1.7",
25 | "run-sequence": "^1.1.0",
26 | "semver": "^5.0.3",
27 | "shelljs": "^0.5.0",
28 | "strformat": "0.0.7"
29 | },
30 | "optionalDependencies": {
31 | "gulp-appdmg": "^1.0.2"
32 | },
33 | "license": "MIT"
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Authors of this source code
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 |
--------------------------------------------------------------------------------
/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "app.html",
3 | "name": "supforwhatsapp",
4 | "version": "2.0.0",
5 | "app-id": "com.zweicoder.supforwhatsapp",
6 | "chromium-args": "--disable-setuid-sandbox",
7 | "window": {
8 | "width": 800,
9 | "height": 600,
10 | "title": "Sup for WhatsApp",
11 | "icon": "images/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/zweicoder/Sup-for-WhatsApp/master/src/package.json",
26 | "packages": {
27 | "osx64": "https://github.com/zweicoder/Sup-for-WhatsApp/releases/download/2.0.0/SupForWhatsApp.dmg",
28 | "win32": "https://github.com/zweicoder/Sup-for-WhatsApp/releases/download/2.0.0/SupForWhatsApp.exe",
29 | "linux32": "https://github.com/zweicoder/Sup-for-WhatsApp/releases/download/2.0.0/SupForWhatsApp_32.deb",
30 | "linux64": "https://github.com/zweicoder/Sup-for-WhatsApp/releases/download/2.0.0/SupForWhatsApp_64.deb"
31 | },
32 | "repository": {
33 | "type": "git",
34 | "url": "git@github.com:zweicoder/Sup-for-WhatsApp.git"
35 | },
36 | "devDependencies": {
37 | "rimraf": "^2.4.3"
38 | },
39 | "license": "MIT"
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/notification.js:
--------------------------------------------------------------------------------
1 | var settings = require('./settings');
2 |
3 | module.exports = {
4 | /**
5 | * Inject a callback in the onclick event.
6 | */
7 | inject: function (targetWindow, win) {
8 | var NativeNotification = targetWindow.Notification;
9 |
10 | targetWindow.Notification = function (title, options) {
11 | var defaultOnClick = options.onclick;
12 | delete options.onclick;
13 | if (settings.hideNotificationBody) {
14 | var timestamp = options.body.match(/-.+:.+$/)[0];
15 | options.body =
16 | options.body.replace(/-.+:.+$/, "") // time stamp
17 | .replace(/\w/g, "*") // eng words
18 | .replace(/[^\x00-\x7F]/g, "*") // characters not in ASCII range
19 | .concat(timestamp);
20 | }
21 | var notif = new NativeNotification(title, options);
22 | notif.addEventListener('click', function () {
23 | win.show();
24 | win.focus();
25 |
26 | if (defaultOnClick) {
27 | defaultOnClick();
28 | }
29 | });
30 |
31 | return notif;
32 | };
33 |
34 | targetWindow.Notification.prototype = NativeNotification.prototype;
35 | targetWindow.Notification.permission = NativeNotification.permission;
36 | targetWindow.Notification.requestPermission = NativeNotification.requestPermission.bind(targetWindow.Notification);
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/assets-windows/installer.nsi:
--------------------------------------------------------------------------------
1 | !include "MUI2.nsh"
2 |
3 | Name "Sup For WhatsApp"
4 | BrandingText " "
5 |
6 | # set the icon
7 | !define MUI_ICON "icon.ico"
8 |
9 | # define the resulting installer's name:
10 | OutFile "..\dist\SupForWhatsApp.exe"
11 |
12 | # set the installation directory
13 | InstallDir "$PROGRAMFILES\Sup For WhatsApp\"
14 |
15 | # app dialogs
16 | !insertmacro MUI_PAGE_WELCOME
17 | !insertmacro MUI_PAGE_INSTFILES
18 |
19 | !define MUI_FINISHPAGE_RUN_TEXT "Start Sup For WhatsApp"
20 | !define MUI_FINISHPAGE_RUN "$INSTDIR\SupForWhatsApp.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\SupForWhatsApp\win32\*
36 |
37 | # create the uninstaller
38 | WriteUninstaller "$INSTDIR\Uninstall Sup For WhatsApp.exe"
39 |
40 | # create shortcuts in the start menu and on the desktop
41 | CreateShortCut "$SMPROGRAMS\Sup For WhatsApp.lnk" "$INSTDIR\SupForWhatsApp.exe"
42 | CreateShortCut "$SMPROGRAMS\Uninstall Sup For WhatsApp.lnk" "$INSTDIR\Uninstall Sup For WhatsApp.exe"
43 | CreateShortCut "$DESKTOP\Sup For WhatsApp.lnk" "$INSTDIR\SupForWhatsApp.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\Sup For WhatsApp.lnk"
55 | Delete "$SMPROGRAMS\Uninstall Sup For WhatsApp.lnk"
56 | Delete "$DESKTOP\Sup For WhatsApp.lnk"
57 |
58 | SectionEnd
59 |
--------------------------------------------------------------------------------
/assets-linux/icons/scalable/whatsappfordesktop.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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/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: true,
8 | openLinksInBrowser: true,
9 | asMenuBarAppOSX: false,
10 | hideNotificationBody: false,
11 | windowState: {}
12 | };
13 |
14 | var db = new Store(path.join(gui.App.dataPath, 'preferences.json'));
15 | var settings = db.getSync('settings');
16 | var watchers = {};
17 |
18 | // Watch changes to the storage
19 | settings.watch = function (name, callback) {
20 | if (!Array.isArray(watchers[name])) {
21 | watchers[name] = [];
22 | }
23 |
24 | watchers[name].push(callback);
25 | };
26 |
27 | // Save settings every time a change is made and notify watchers
28 | Object.observe(settings, function (changes) {
29 | db.save('settings', settings, function (err) {
30 | if (err) {
31 | console.error('Could not save settings', err);
32 | }
33 | });
34 |
35 | changes.forEach(function (change) {
36 | var newValue = change.object[change.name];
37 | var keyWatchers = watchers[change.name];
38 |
39 | // Call all the watcher functions for the changed key
40 | if (keyWatchers && keyWatchers.length) {
41 | for (var i = 0; i < keyWatchers.length; i++) {
42 | try {
43 | keyWatchers[i](newValue);
44 | } catch (ex) {
45 | console.error(ex);
46 | keyWatchers.splice(i--, 1);
47 | }
48 | }
49 | }
50 | });
51 | });
52 |
53 | // Ensure the default values exist
54 | Object.keys(DEFAULT_SETTINGS).forEach(function (key) {
55 | if (!settings.hasOwnProperty(key)) {
56 | settings[key] = DEFAULT_SETTINGS[key];
57 | }
58 | });
59 |
60 | module.exports = settings;
61 |
--------------------------------------------------------------------------------
/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 notification = require('./components/notification');
10 | var dispatcher = require('./components/dispatcher');
11 | var bindTabForWhatsApp = require('./AwesomeScripts/WhatsApp/tabforwhatsapp');
12 | var bindEmojiShortcuts = require('./AwesomeScripts/WhatsApp/emojiShortcuts');
13 |
14 | // Ensure there's an app shortcut for toast notifications to work on Windows
15 | if (platform.isWindows) {
16 | gui.App.createShortcut(process.env.APPDATA + "\\Microsoft\\Windows\\Start Menu\\Programs\\Unofficial WhatsApp.lnk");
17 | }
18 |
19 | // Add dispatcher events
20 | dispatcher.addEventListener('win.alert', function(data) {
21 | data.win.window.alert(data.message);
22 | });
23 |
24 | dispatcher.addEventListener('win.confirm', function(data) {
25 | data.callback(data.win.window.confirm(data.message));
26 | });
27 |
28 | // Window state
29 | windowBehaviour.restoreWindowState(win);
30 | windowBehaviour.bindWindowStateEvents(win);
31 |
32 | // Check for update
33 | if (settings.checkUpdateOnLaunch) {
34 | updater.checkAndPrompt(gui.App.manifest, win);
35 | }
36 |
37 | // Run as menu bar app
38 | if (settings.asMenuBarAppOSX) {
39 | win.setShowInTaskbar(false);
40 | menus.loadTrayIcon(win);
41 | }
42 |
43 | // Load the app menus
44 | menus.loadMenuBar(win);
45 | //if (platform.isWindows) {
46 | // menus.loadTrayIcon(win);
47 | //}
48 |
49 | // Adjust the default behaviour of the main window
50 | windowBehaviour.set(win);
51 | windowBehaviour.setNewWinPolicy(win);
52 |
53 | // Inject logic into the app when it's loaded
54 | var iframe = document.querySelector('iframe');
55 | iframe.onload = function() {
56 | // Inject a callback in the notification API
57 | notification.inject(iframe.contentWindow, win);
58 |
59 | // Add a context menu
60 | menus.injectContextMenu(win, iframe.contentWindow, iframe.contentDocument);
61 |
62 | // Bind native events to the content window
63 | windowBehaviour.bindEvents(win, iframe.contentWindow);
64 |
65 | // Watch the iframe periodically to sync the badge and the title
66 | windowBehaviour.syncBadgeAndTitle(win, document, iframe.contentDocument);
67 |
68 | // Enable Tab Key to switch chat
69 | bindTabForWhatsApp(iframe.contentDocument);
70 |
71 | // Enable Ctrl + Up to toggle Emoji panel, and up down left right + enter
72 | // to select emoji
73 | bindEmojiShortcuts(iframe.contentDocument);
74 | };
75 |
76 | // Reload the app periodically until it loads
77 | var reloadIntervalId = setInterval(function() {
78 | if (win.window.navigator.onLine) {
79 | clearInterval(reloadIntervalId);
80 | } else {
81 | win.reload();
82 | }
83 | }, 10 * 1000);
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sup for WhatsApp
2 |
3 | ### The project has migrated and is no longer being maintained. Check out [this repo](https://github.com/zweicoder/AwesomeScripts/tree/master/WhatsApp) for a updated and maintained version of SupForWhatsApp, built on Electron.
4 |
5 | Forked from [Aluxian's Repo](https://github.com/Aluxian/WhatsApp-Desktop) then reworked with some additional features and major fixes.
6 |
7 |
8 | A Native desktop wrapper for WhatsApp which runs mainly on Windows. Built with [NW.js](http://nwjs.io/). OSX and Linux builds need help as I'm having trouble compiling on Windows! Get the latest [release](https://github.com/zweicoder/Sup-for-WhatsApp/releases)!
9 |
10 | This is a hobby project I'm working on mainly because I found it very troublesome to cycle to the Web WhatsApp tab (and back) frequently. I also wanted some additional features lacking in WebWhatsApp like hotkeys.
11 |
12 | ####This is **NOT** affiliated with WhatsApp or Facebook. This is **NOT** an official product. Read the [DISCLAIMER](https://github.com/zweicoder/Sup-for-WhatsApp/blob/master/DISCLAIMER).
13 |
14 | 
15 |
16 | ## Features
17 |
18 | * **Able to hide notification body (Right click in the app and check the option)**
19 | * **Able to cycle through conversations through (Shift) Tab**
20 | * More convenient to cycle through native windows as compared to cycling through tabs in your browser
21 | * All features of Web WhatsApp (since this is a wrapper around it)
22 | * Preferences in the right-click context menu (or menu bar for OS X, tray menu for Windows)
23 | * **Any ideas or requests? submit an issue!**
24 |
25 | ## Build
26 |
27 | ### Pre-requisites
28 |
29 | # install gulp
30 | npm install -g gulp
31 |
32 | # install dependencies
33 | npm install
34 |
35 | * **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
36 | to set the correct icon for the exe. If you don't have Wine, you can comment out the `winIco` field in `gulpfile`.
37 | * **makensis**: Required by the `pack:win32` task in `gulpfile` to create the Windows installer.
38 | * [**fpm**](https://github.com/jordansissel/fpm): Required by the `pack:linux{32|64}:deb` tasks in `gulpfile` to create the Linux installers.
39 |
40 | Quick install on OS X:
41 |
42 | brew install wine makensis
43 | sudo gem install fpm
44 |
45 | ### OS X: pack the app in a .app
46 |
47 | gulp pack:osx64
48 |
49 | ### Windows: create the installer
50 |
51 | gulp pack:win32
52 |
53 | ### Linux 32/64-bit: pack the app in a .deb (Needs help)
54 |
55 | gulp pack:linux{32|64}:deb
56 |
57 | The output is in `./dist`. Take a look at `gulpfile.js` for additional tasks.
58 |
59 | ## Note to WhatsApp
60 |
61 | This project does not attempt to reverse engineer the WhatsApp API or attempt to reimplement any part of the WhatsApp client. Any communication between the user and WhatsApp servers is handled by WhatsApp Web itself; this is just a native wrapper for WhatsApp Web, more akin to a browser than any WhatsApp software.
62 |
63 | ## Contributions
64 |
65 | Contributions are welcome! For feature requests and bug reports please [submit an issue](https://github.com/zweicoder/WhatsApp-Desktop/issues).
66 |
67 | ## License
68 |
69 | The MIT License (MIT)
70 |
71 | Copyright (c) 2015 Authors of this source code
72 |
73 | Permission is hereby granted, free of charge, to any person obtaining a copy
74 | of this software and associated documentation files (the "Software"), to deal
75 | in the Software without restriction, including without limitation the rights
76 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
77 | copies of the Software, and to permit persons to whom the Software is
78 | furnished to do so, subject to the following conditions:
79 |
80 | The above copyright notice and this permission notice shall be included in all
81 | copies or substantial portions of the Software.
82 |
83 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
84 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
85 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
86 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
87 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
88 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
89 | SOFTWARE.
90 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var plugins, fs, gulp, manifest, mergeStream, runSequence, shelljs;
2 | gulp = require('gulp');
3 | shelljs = require('shelljs');
4 | mergeStream = require('merge-stream');
5 | runSequence = require('run-sequence');
6 | manifest = require('./package.json');
7 | plugins = require('gulp-load-plugins')();
8 | fs = require('fs');
9 | var strformat = require('strformat');
10 | var semver = require('semver');
11 | var zip = require('gulp-zip');
12 |
13 | gulp.task('clean', function () {
14 | shelljs.rm('-rf', './build');
15 | return shelljs.rm('-rf', './dist');
16 | });
17 |
18 | ['win32', 'osx64', 'linux32', 'linux64'].forEach(function (platform) {
19 | return gulp.task('build:' + platform, function () {
20 | if (process.argv.indexOf('--toolbar') > 0) {
21 | shelljs.sed('-i', '"toolbar": false', '"toolbar": true', './src/package.json');
22 | }
23 | return gulp.src('./src/**').pipe(plugins.nodeWebkitBuilder({
24 | platforms: [platform],
25 | version: '0.12.2',
26 | winIco: process.argv.indexOf('--noicon') > 0 ? void 0 : './assets-windows/icon.ico',
27 | macIcns: './assets-osx/icon.icns',
28 | macZip: true,
29 | macPlist: {
30 | NSHumanReadableCopyright: 'aluxian.com',
31 | CFBundleIdentifier: 'com.zweicoder.supforwhatsapp'
32 | }
33 | })).on('end', function () {
34 | if (process.argv.indexOf('--toolbar') > 0) {
35 | return shelljs.sed('-i', '"toolbar": true', '"toolbar": false', './src/package.json');
36 | }
37 | });
38 | });
39 | });
40 |
41 | gulp.task('sign:osx64', ['build:osx64'], function () {
42 | shelljs.exec('codesign -v -f -s "Alexandru Rosianu Apps" ./build/supforwhatsapp/osx64/supforwhatsapp.app/Contents/Frameworks/*');
43 | shelljs.exec('codesign -v -f -s "Alexandru Rosianu Apps" ./build/supforwhatsapp/osx64/supforwhatsapp.app');
44 | shelljs.exec('codesign -v --display ./build/supforwhatsapp/osx64/supforwhatsapp.app');
45 | return shelljs.exec('codesign -v --verify ./build/supforwhatsapp/osx64/supforwhatsapp.app');
46 | });
47 |
48 | gulp.task('pack:osx64', ['sign:osx64'], function () {
49 | shelljs.mkdir('-p', './dist');
50 | shelljs.rm('-f', './dist/supforwhatsapp-Mac.zip');
51 | return gulp.src('./build/supforwhatsapp/osx64/**')
52 | .pipe(zip('supforwhatsapp-Mac.zip'))
53 | .pipe(gulp.dest('./dist'));
54 | });
55 |
56 | gulp.task('pack:win32', ['build:win32'], function () {
57 | return shelljs.exec('makensis ./assets-windows/installer.nsi');
58 | });
59 |
60 | [32, 64].forEach(function (arch) {
61 | return ['deb', 'rpm'].forEach(function (target) {
62 | return gulp.task("pack:linux" + arch + ":" + target, ['build:linux' + arch], function () {
63 | var move_opt, move_png256, move_png48, move_svg;
64 | shelljs.rm('-rf', './build/linux');
65 | move_opt = gulp.src(['./assets-linux/supforwhatsapp.desktop', './assets-linux/after-install.sh', './assets-linux/after-remove.sh', './build/supforwhatsapp/linux' + arch + '/**']).pipe(gulp.dest('./build/linux/opt/supforwhatsapp'));
66 | move_png48 = gulp.src('./assets-linux/icons/48/whatsappfordesktop.png').pipe(gulp.dest('./build/linux/usr/share/icons/hicolor/48x48/apps'));
67 | move_png256 = gulp.src('./assets-linux/icons/256/whatsappfordesktop.png').pipe(gulp.dest('./build/linux/usr/share/icons/hicolor/256x256/apps'));
68 | move_svg = gulp.src('./assets-linux/icons/scalable/whatsappfordesktop.png').pipe(gulp.dest('./build/linux/usr/share/icons/hicolor/scalable/apps'));
69 | return mergeStream(move_opt, move_png48, move_png256, move_svg).on('end', function () {
70 | var output, port;
71 | shelljs.cd('./build/linux');
72 | port = arch === 32 ? 'i386' : 'amd64';
73 | output = "../../dist/supforwhatsapp_linux" + arch + "." + target;
74 | shelljs.mkdir('-p', '../../dist');
75 | shelljs.rm('-f', output);
76 | shelljs.exec("fpm -s dir -t " + target + " -a " + port + " -n supforwhatsapp --after-install ./opt/supforwhatsapp/after-install.sh --after-remove ./opt/supforwhatsapp/after-remove.sh --license MIT --category Chat --description \"A simple and beautiful app for Facebook WhatsApp. Chat without distractions on any OS. Not an official client.\" -m \"zweicoder\" -p " + output + " -v " + manifest.version + " .");
77 | return shelljs.cd('../..');
78 | });
79 | });
80 | });
81 | });
82 |
83 | gulp.task('pack:all', function (callback) {
84 | return runSequence('pack:osx64', 'pack:win32', 'pack:linux32:deb', 'pack:linux64:deb', callback);
85 | });
86 |
87 | gulp.task('run:osx64', ['build:osx64'], function () {
88 | return shelljs.exec('open ./build/supforwhatsapp/osx64/supforwhatsapp.app');
89 | });
90 |
91 | gulp.task('open:osx64', function () {
92 | return shelljs.exec('open ./build/supforwhatsapp/osx64/supforwhatsapp.app');
93 | });
94 |
95 | gulp.task('release', ['pack:all'], function (callback) {
96 | return gulp.src('./dist/*')
97 | .pipe(plugins.githubRelease({
98 | token: process.env.GITHUB_TOKEN,
99 | draft: true,
100 | manifest: manifest
101 | }));
102 | });
103 |
104 | gulp.task('default', ['pack:all']);
105 |
106 | gulp.task('bump', function () {
107 | if (process.argv[3] == null) {
108 | console.log("Usage: gulp bump --[major | minor | patch ]");
109 | return;
110 | }
111 | var newVer,
112 | bumpType = process.argv[3].toLowerCase().replace("--", "");
113 | if (['major', 'minor', 'patch', 'version'].indexOf(bumpType) > 0) {
114 | console.log("Usage: gulp bump --[major | minor | patch ]");
115 | return;
116 | }
117 | var pkg = JSON.parse(fs.readFileSync('./src/package.json', 'utf8'));
118 | newVer = semver.inc(pkg.version, bumpType);
119 | var urlPath = strformat("https://github.com/zweicoder/Sup-for-WhatsApp/releases/download/{version}/", {version: newVer});
120 | return gulp.src('./src/package.json')
121 | .pipe(plugins.jsonEditor({
122 | "version": newVer,
123 | "packages": {
124 | "osx64": urlPath + 'supforwhatsapp.dmg',
125 | "win32": urlPath + 'supforwhatsapp.exe',
126 | "linux32": urlPath + 'supforwhatsapp_32.deb',
127 | "linux64": urlPath + 'supforwhatsapp_64.deb'
128 | }
129 | }))
130 | .pipe(gulp.dest('./src/'));
131 | });
132 |
--------------------------------------------------------------------------------
/src/components/window-behaviour.js:
--------------------------------------------------------------------------------
1 | var gui = window.require('nw.gui');
2 | var fs = require('fs');
3 | var path = require('path');
4 | var rimraf = require('rimraf');
5 | var platform = require('./platform');
6 | var settings = require('./settings');
7 |
8 | module.exports = {
9 | /**
10 | * Update the behaviour of the given window object.
11 | */
12 | set: function (win) {
13 | // Show the window when the dock icon is pressed
14 | gui.App.removeAllListeners('reopen');
15 | gui.App.on('reopen', function () {
16 | win.show();
17 | //this.restoreWindowState()
18 | });
19 |
20 | win.on('close', function () {
21 | this.saveWindowState(win);
22 | this.deleteFileCache();
23 | win.close(true);
24 |
25 | }.bind(this));
26 | },
27 | /**
28 | * Clicks on the chat before/after the active one with Shift + Tab / Tab
29 | */
30 | enableSwitchWithTabKey: function (doc) {
31 | doc.onkeydown = function (e) {
32 | if (e.keyCode == 9 && !e.repeat) {
33 | e.preventDefault();
34 | var delta = e.shiftKey ? 1 : -1;
35 | var activeChat = doc.querySelector('.active');
36 | var items = doc.getElementsByClassName('infinite-list-item');
37 | if (activeChat) {
38 | var idx = parseInt(getZIndex(activeChat)) + delta;
39 | // ignore if cannot find cause probably out of bounds like top of the chat list
40 | //console.log("Wanted: " + idx);
41 | for (var i = 0; i < items.length; i++) {
42 | if (items[i].style.zIndex == idx) {
43 | //console.log("Click on ",items[i].childNodes[0]);
44 | items[i].childNodes[0].childNodes[0].click();
45 | return false;
46 | }
47 | }
48 | } else {
49 | var lastMax = 0;
50 | var idx = -1;
51 | for (var i = 0; i < items.length; i++) {
52 | var zIndex = items[i].style.zIndex;
53 | if (zIndex > lastMax) {
54 | //console.log("Click on ",items[i].childNodes[0]);
55 | lastMax = zIndex;
56 | idx = i;
57 | }
58 | }
59 | items[idx].childNodes[0].childNodes[0].click();
60 | return false;
61 | }
62 | }
63 | }
64 | },
65 |
66 | /**
67 | * Change the new window policy to open links in the browser or another window.
68 | */
69 | setNewWinPolicy: function (win) {
70 | win.removeAllListeners('new-win-policy');
71 | win.on('new-win-policy', function (frame, url, policy) {
72 | if (settings.openLinksInBrowser) {
73 | gui.Shell.openExternal(url);
74 | policy.ignore();
75 | } else {
76 | policy.forceNewWindow();
77 | }
78 | });
79 | },
80 |
81 | /**
82 | * Listen for window state events.
83 | */
84 | bindWindowStateEvents: function (win) {
85 | win.removeAllListeners('maximize');
86 | win.on('maximize', function () {
87 | win.sizeMode = 'maximized';
88 | });
89 |
90 | win.removeAllListeners('unmaximize');
91 | win.on('unmaximize', function () {
92 | win.sizeMode = 'normal';
93 | });
94 |
95 | win.removeAllListeners('minimize');
96 | win.on('minimize', function () {
97 | win.sizeMode = 'minimized';
98 | });
99 |
100 | win.removeAllListeners('restore');
101 | win.on('restore', function () {
102 | win.sizeMode = 'normal';
103 | });
104 | },
105 |
106 | /**
107 | * Bind the events of the node window to the content window.
108 | */
109 | bindEvents: function (win, contentWindow) {
110 | ['focus', 'blur'].forEach(function (name) {
111 | win.removeAllListeners(name);
112 | win.on(name, function () {
113 | if (contentWindow.dispatchEvent && contentWindow.Event) {
114 | contentWindow.dispatchEvent(new contentWindow.Event(name));
115 | }
116 | });
117 | });
118 | },
119 |
120 | /**
121 | * Set an interval to sync the badge and the title.
122 | */
123 | syncBadgeAndTitle: function (win, parentDoc, childDoc) {
124 | var notifCountRegex = /\((\d)\)/;
125 |
126 | setInterval(function () {
127 | // Sync title
128 | parentDoc.title = childDoc.title;
129 |
130 | // Find count
131 | var countMatch = notifCountRegex.exec(childDoc.title);
132 | var label = countMatch && countMatch[1] || '';
133 | win.setBadgeLabel(label);
134 |
135 | // Update the tray icon too
136 | if (win.tray) {
137 | var type = platform.isOSX ? 'menubar' : 'tray';
138 | var alert = label ? '_alert' : '';
139 | var extension = platform.isOSX ? '.tiff' : '.png';
140 | win.tray.icon = 'images/icon_' + type + alert + extension;
141 | }
142 | }, 100);
143 | },
144 |
145 | /**
146 | * Store the window state.
147 | */
148 | saveWindowState: function (win) {
149 | var state = {
150 | mode: win.sizeMode || 'normal'
151 | };
152 |
153 | if (state.mode == 'normal') {
154 | state.x = win.x;
155 | state.y = win.y;
156 | state.width = win.width;
157 | state.height = win.height;
158 | }
159 |
160 | settings.windowState = state;
161 | },
162 |
163 | /**
164 | * Restore the window size and position.
165 | */
166 | restoreWindowState: function (win) {
167 | var state = settings.windowState;
168 | if (state.mode == 'maximized') {
169 | win.maximize();
170 | } else {
171 | win.resizeTo(state.width, state.height);
172 | win.moveTo(state.x, state.y);
173 | }
174 |
175 | win.show();
176 | },
177 |
178 | /**
179 | * Delete folder cache after close application
180 | */
181 | deleteFileCache: function () {
182 | var pathFileCache = path.join(gui.App.dataPath, 'Application Cache');
183 | rimraf(pathFileCache, function (err) {
184 | console.log(err);
185 | });
186 | }
187 | };
188 |
189 | var getZIndex = function (ele) {
190 | return ele.style.zIndex || getZIndex(ele.parentNode);
191 | };
--------------------------------------------------------------------------------
/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: 'Run as Menu Bar App',
28 | setting: 'asMenuBarAppOSX',
29 | platforms: ['osx'],
30 | click: function () {
31 | settings.asMenuBarAppOSX = this.checked;
32 | win.setShowInTaskbar(!this.checked);
33 |
34 | if (this.checked) {
35 | self.loadTrayIcon(win);
36 | } else if (win.tray) {
37 | win.tray.remove();
38 | win.tray = null;
39 | }
40 | }
41 | }, {
42 | type: 'checkbox',
43 | label: 'Launch on Startup',
44 | setting: 'launchOnStartup',
45 | platforms: ['osx', 'win'],
46 | click: function () {
47 | settings.launchOnStartup = this.checked;
48 |
49 | var launcher = new AutoLaunch({
50 | name: 'Unofficial WhatsApp',
51 | isHidden: true // hidden on launch - only works on a mac atm
52 | });
53 |
54 | launcher.isEnabled(function (enabled) {
55 | if (settings.launchOnStartup && !enabled) {
56 | launcher.enable(function (error) {
57 | if (error) {
58 | console.error(error);
59 | }
60 | });
61 | }
62 |
63 | if (!settings.launchOnStartup && enabled) {
64 | launcher.disable(function (error) {
65 | if (error) {
66 | console.error(error);
67 | }
68 | });
69 | }
70 | });
71 | }
72 | }, {
73 | label: 'Launch Dev Tools',
74 | click: function () {
75 | win.showDevTools();
76 | }
77 | }, {
78 | type: "checkbox",
79 | label: 'Hide Notification Body',
80 | setting: 'hideNotificationBody',
81 | click: function () {
82 | settings.hideNotificationBody = this.checked;
83 | }
84 | }
85 | ].map(function (item) {
86 | // If the item has a 'setting' property, use some predefined values
87 | if (item.setting) {
88 | if (!item.hasOwnProperty('checked')) {
89 | item.checked = settings[item.setting];
90 | }
91 |
92 | if (!item.hasOwnProperty('click')) {
93 | item.click = function () {
94 | settings[item.setting] = item.checked;
95 | };
96 | }
97 | }
98 |
99 | return item;
100 | }).filter(function (item) {
101 | // Remove the item if the current platform is not supported
102 | return !Array.isArray(item.platforms) || (item.platforms.indexOf(platform.type) != -1);
103 | }).map(function (item) {
104 | var menuItem = new gui.MenuItem(item);
105 | menuItem.setting = item.setting;
106 | return menuItem;
107 | });
108 | },
109 | /**
110 | * Create the menu bar for the given window, only on OS X.
111 | */
112 | loadMenuBar: function (win) {
113 | if (!platform.isOSX) {
114 | return;
115 | }
116 |
117 | var menu = new gui.Menu({
118 | type: 'menubar'
119 | });
120 |
121 | menu.createMacBuiltin('Unofficial WhatsApp');
122 | var submenu = menu.items[0].submenu;
123 |
124 | submenu.insert(new gui.MenuItem({
125 | type: 'separator'
126 | }), 1);
127 |
128 | // Add the main settings
129 | this.settingsItems(win, true).forEach(function (item, index) {
130 | submenu.insert(item, index + 2);
131 | });
132 |
133 | // Watch the items that have a 'setting' property
134 | submenu.items.forEach(function (item) {
135 | if (item.setting) {
136 | settings.watch(item.setting, function (value) {
137 | item.checked = value;
138 | });
139 | }
140 | });
141 |
142 | win.menu = menu;
143 | },
144 |
145 | /**
146 | * Create the menu for the tray icon.
147 | */
148 | createTrayMenu: function (win) {
149 | var menu = new gui.Menu();
150 |
151 | // Add the main settings
152 | this.settingsItems(win, true).forEach(function (item) {
153 | menu.append(item);
154 | });
155 |
156 | menu.append(new gui.MenuItem({
157 | type: 'separator'
158 | }));
159 |
160 | menu.append(new gui.MenuItem({
161 | label: 'Show Unofficial WhatsApp',
162 | click: function () {
163 | win.show();
164 | }
165 | }));
166 |
167 | menu.append(new gui.MenuItem({
168 | label: 'Quit Unofficial WhatsApp',
169 | click: function () {
170 | windowBehaviour.deleteFileCache();
171 | windowBehaviour.saveWindowState(win);
172 | win.close(true);
173 | }
174 | }));
175 |
176 | // Watch the items that have a 'setting' property
177 | menu.items.forEach(function (item) {
178 | if (item.setting) {
179 | settings.watch(item.setting, function (value) {
180 | item.checked = value;
181 | });
182 | }
183 | });
184 |
185 | return menu;
186 | },
187 |
188 | /**
189 | * Create the tray icon.
190 | */
191 | loadTrayIcon: function (win) {
192 | if (win.tray) {
193 | win.tray.remove();
194 | win.tray = null;
195 | }
196 |
197 | var tray = new gui.Tray({
198 | icon: 'images/icon_' + (platform.isOSX ? 'menubar.tiff' : 'tray.png')
199 | });
200 |
201 | tray.on('click', function () {
202 | win.show();
203 | });
204 |
205 | tray.tooltip = 'Unofficial WhatsApp for Desktop';
206 | tray.menu = this.createTrayMenu(win);
207 |
208 | // keep the object in memory
209 | win.tray = tray;
210 | },
211 |
212 | /**
213 | * Create a context menu for the window and document.
214 | */
215 | createContextMenu: function (win, window, document, targetElement) {
216 | var menu = new gui.Menu();
217 |
218 | if (targetElement.tagName.toLowerCase() == 'input') {
219 | menu.append(new gui.MenuItem({
220 | label: "Cut",
221 | click: function () {
222 | clipboard.set(targetElement.value);
223 | targetElement.value = '';
224 | }
225 | }));
226 |
227 | menu.append(new gui.MenuItem({
228 | label: "Copy",
229 | click: function () {
230 | clipboard.set(targetElement.value);
231 | }
232 | }));
233 |
234 | menu.append(new gui.MenuItem({
235 | label: "Paste",
236 | click: function () {
237 | targetElement.value = clipboard.get();
238 | }
239 | }));
240 | } else if (targetElement.tagName.toLowerCase() == 'a') {
241 | menu.append(new gui.MenuItem({
242 | label: "Copy Link",
243 | click: function () {
244 | clipboard.set(targetElement.href);
245 | }
246 | }));
247 | } else {
248 | var selection = window.getSelection().toString();
249 | if (selection.length > 0) {
250 | menu.append(new gui.MenuItem({
251 | label: "Copy",
252 | click: function () {
253 | clipboard.set(selection);
254 | }
255 | }));
256 | }
257 | }
258 |
259 | this.settingsItems(win, false).forEach(function (item) {
260 | menu.append(item);
261 | });
262 |
263 | return menu;
264 | },
265 |
266 | /**
267 | * Listen for right clicks and show a context menu.
268 | */
269 | injectContextMenu: function (win, window, document) {
270 | document.body.addEventListener('contextmenu', function (event) {
271 | event.preventDefault();
272 | this.createContextMenu(win, window, document, event.target).popup(event.x, event.y);
273 | return false;
274 | }.bind(this));
275 | }
276 | };
277 |
--------------------------------------------------------------------------------