├── .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 | 3 | 4 | icon@svg 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /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 | ![Cross-platform screenshot](screenshot.png) 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 | --------------------------------------------------------------------------------