├── .jshintignore ├── .babelrc ├── icons ├── darwin │ ├── icon.icns │ ├── icon-setup.icns │ ├── background-setup.png │ ├── icon-tray-Template.png │ └── icon-tray-Template@2x.png └── linux │ ├── 16x16.png │ ├── 24x24.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 64x64.png │ ├── 96x96.png │ ├── icon.png │ ├── 128x128.png │ ├── 256x256.png │ ├── 512x512.png │ ├── 1024x1024.png │ ├── icon-setup.png │ └── icon-tray.png ├── app ├── fonts │ ├── arial-black.eot │ ├── arial-black.ttf │ └── arial-black.woff ├── images │ ├── icon-window-visible-Template.png │ ├── icon-launch-on-startup-Template.png │ ├── icon-window-visible-Template@2x.png │ └── icon-launch-on-startup-Template@2x.png ├── html │ └── main.html ├── scripts │ ├── main │ │ ├── services │ │ │ ├── power-service.js │ │ │ ├── debug-service.js │ │ │ ├── notification-service.js │ │ │ ├── messenger-service.js │ │ │ └── updater-service.js │ │ ├── components │ │ │ └── application.js │ │ └── managers │ │ │ └── configuration-manager.js │ └── renderer │ │ └── main.js ├── styles │ └── styles.css └── vendor │ └── glitch.js ├── resources ├── graphics │ ├── icon.png │ ├── icon-tray.psd │ ├── icon-setup.png │ ├── ecorp-wallpaper.jpg │ └── icon-tray-opaque.svg └── screenshots │ ├── screenshot-linux-1.gif │ ├── screenshot-macos-1.gif │ ├── screenshot-macos-2.gif │ └── screenshot-macos-3.gif ├── .editorconfig ├── .gitignore ├── RELEASENOTES.md ├── .jshintrc ├── RELEASENOTES.json ├── .npmignore ├── bin └── cli.js ├── lib ├── required-count.js ├── platform-helper.js ├── language.js ├── is-env.js ├── releasenotes.js ├── localsetup.js ├── dom-helper.js ├── build.js ├── deploy.js └── logger.js ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE.md └── CONTRIBUTING.md ├── .appveyor.yml ├── .travis.yml ├── .jscsrc ├── CONTRIBUTING.md ├── gulpfile.babel.js ├── package.json ├── LICENSE.md └── README.md /.jshintignore: -------------------------------------------------------------------------------- 1 | resources/** -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["electron"] 3 | } 4 | -------------------------------------------------------------------------------- /icons/darwin/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/darwin/icon.icns -------------------------------------------------------------------------------- /icons/linux/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/linux/16x16.png -------------------------------------------------------------------------------- /icons/linux/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/linux/24x24.png -------------------------------------------------------------------------------- /icons/linux/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/linux/32x32.png -------------------------------------------------------------------------------- /icons/linux/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/linux/48x48.png -------------------------------------------------------------------------------- /icons/linux/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/linux/64x64.png -------------------------------------------------------------------------------- /icons/linux/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/linux/96x96.png -------------------------------------------------------------------------------- /icons/linux/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/linux/icon.png -------------------------------------------------------------------------------- /icons/linux/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/linux/128x128.png -------------------------------------------------------------------------------- /icons/linux/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/linux/256x256.png -------------------------------------------------------------------------------- /icons/linux/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/linux/512x512.png -------------------------------------------------------------------------------- /app/fonts/arial-black.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/app/fonts/arial-black.eot -------------------------------------------------------------------------------- /app/fonts/arial-black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/app/fonts/arial-black.ttf -------------------------------------------------------------------------------- /app/fonts/arial-black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/app/fonts/arial-black.woff -------------------------------------------------------------------------------- /icons/linux/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/linux/1024x1024.png -------------------------------------------------------------------------------- /icons/linux/icon-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/linux/icon-setup.png -------------------------------------------------------------------------------- /icons/linux/icon-tray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/linux/icon-tray.png -------------------------------------------------------------------------------- /resources/graphics/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/resources/graphics/icon.png -------------------------------------------------------------------------------- /icons/darwin/icon-setup.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/darwin/icon-setup.icns -------------------------------------------------------------------------------- /resources/graphics/icon-tray.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/resources/graphics/icon-tray.psd -------------------------------------------------------------------------------- /icons/darwin/background-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/darwin/background-setup.png -------------------------------------------------------------------------------- /resources/graphics/icon-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/resources/graphics/icon-setup.png -------------------------------------------------------------------------------- /icons/darwin/icon-tray-Template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/darwin/icon-tray-Template.png -------------------------------------------------------------------------------- /icons/darwin/icon-tray-Template@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/icons/darwin/icon-tray-Template@2x.png -------------------------------------------------------------------------------- /resources/graphics/ecorp-wallpaper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/resources/graphics/ecorp-wallpaper.jpg -------------------------------------------------------------------------------- /app/images/icon-window-visible-Template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/app/images/icon-window-visible-Template.png -------------------------------------------------------------------------------- /resources/screenshots/screenshot-linux-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/resources/screenshots/screenshot-linux-1.gif -------------------------------------------------------------------------------- /resources/screenshots/screenshot-macos-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/resources/screenshots/screenshot-macos-1.gif -------------------------------------------------------------------------------- /resources/screenshots/screenshot-macos-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/resources/screenshots/screenshot-macos-2.gif -------------------------------------------------------------------------------- /resources/screenshots/screenshot-macos-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/resources/screenshots/screenshot-macos-3.gif -------------------------------------------------------------------------------- /app/images/icon-launch-on-startup-Template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/app/images/icon-launch-on-startup-Template.png -------------------------------------------------------------------------------- /app/images/icon-window-visible-Template@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/app/images/icon-window-visible-Template@2x.png -------------------------------------------------------------------------------- /app/images/icon-launch-on-startup-Template@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/live-wallpaper-ecorp/HEAD/app/images/icon-launch-on-startup-Template@2x.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.yml] 16 | indent_size = 2 17 | 18 | [package.json] 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # BASELINE 2 | # macOS 3 | .DS_Store 4 | 5 | # Logs 6 | logs 7 | *.log 8 | 9 | # Temporary 10 | temp 11 | tmp 12 | 13 | # Caches 14 | .cache 15 | cache 16 | .sass-cache 17 | 18 | # JetBrains 19 | .idea 20 | *.sln.iml 21 | 22 | # VSCode 23 | .vscode 24 | 25 | # Compiled 26 | build 27 | 28 | # Dependencies 29 | node_modules 30 | jspm_packages 31 | bower_components 32 | -------------------------------------------------------------------------------- /app/html/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
e
11 |
12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /RELEASENOTES.md: -------------------------------------------------------------------------------- 1 | ## 2.0.2 2 | 3 | #### 🍾 Features 4 | 5 | - Adds 'Hide Wallpaper' feature 6 | - Adds 'Launch on Startup' feature 7 | - UI enhancements 8 | - Canvas rendering improvements 9 | 10 | #### 🚨 Fixes 11 | 12 | - Fixes window positioning (Linux) 13 | 14 | #### 📒 Documentation 15 | 16 | - Augments README.MD 17 | - Adds CONTRIBUTING.MD 18 | - Adds issue template 19 | - Adds pull request template 20 | 21 | #### 👷 Internals 22 | 23 | - Upgrades Electron to 1.7.1 24 | - Migrates dependencies to npm 25 | - Upgrades build system 26 | - Adds deployment system 27 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "esnext": true, 4 | "bitwise": false, 5 | "camelcase": false, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": false, 9 | "indent": 4, 10 | "latedef": true, 11 | "noarg": true, 12 | "quotmark": "single", 13 | "undef": true, 14 | "unused": true, 15 | "strict": true, 16 | "trailing": true, 17 | "smarttabs": true, 18 | "sub": true, 19 | "node": true, 20 | "globals": { 21 | "Audio": false, 22 | "global": false, 23 | "Notification": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /RELEASENOTES.json: -------------------------------------------------------------------------------- 1 | { 2 | "2.0.2": { 3 | "🍾 features": [ 4 | "Adds 'Hide Wallpaper' feature", 5 | "Adds 'Launch on Startup' feature", 6 | "UI enhancements", 7 | "Canvas rendering improvements" 8 | ], 9 | "🚨 fixes": [ 10 | "Fixes window positioning (Linux)" 11 | ], 12 | "📒 documentation": [ 13 | "Augments README.MD", 14 | "Adds CONTRIBUTING.MD", 15 | "Adds issue template", 16 | "Adds pull request template" 17 | ], 18 | "👷 internals": [ 19 | "Upgrades Electron to 1.7.1", 20 | "Migrates dependencies to npm", 21 | "Upgrades build system", 22 | "Adds deployment system" 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # BASELINE 2 | # macOS 3 | .DS_Store 4 | 5 | # Logs 6 | logs 7 | *.log 8 | 9 | # Temporary 10 | temp 11 | tmp 12 | 13 | # Cache 14 | .cache 15 | cache 16 | .sass-cache/ 17 | 18 | # JetBrains 19 | .idea/ 20 | *.sln.iml 21 | 22 | # Compiled 23 | build 24 | 25 | # Node 26 | node_modules 27 | jspm_packages 28 | 29 | 30 | ## NPM 31 | # Runtime data 32 | pids 33 | *.pid 34 | *.seed 35 | 36 | # Directory for instrumented libs generated by jscoverage/JSCover 37 | lib-cov 38 | 39 | # Coverage directory used by tools like istanbul 40 | coverage 41 | 42 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 43 | .grunt 44 | 45 | 46 | # ELECTRON-CLOUD-DEPLOY CACHES 47 | electron-cloud-deploy-cache -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | 5 | /** 6 | * Modules 7 | * Node 8 | * @global 9 | * @constant 10 | */ 11 | const path = require('path'); 12 | const childProcess = require('child_process'); 13 | 14 | /** 15 | * Modules 16 | * External 17 | * @global 18 | * @constant 19 | */ 20 | const appRootPath = require('app-root-path').path; 21 | const electron = require('electron'); 22 | 23 | /** 24 | * Modules 25 | * Internal 26 | * @global 27 | * @constant 28 | */ 29 | const packageJson = require(path.join(appRootPath, 'package.json')); 30 | 31 | 32 | /** 33 | * Path to Electron application 34 | * @global 35 | */ 36 | let appMain = path.join(appRootPath, packageJson.main); 37 | 38 | 39 | /** 40 | * Main 41 | */ 42 | childProcess.spawn(electron, [ appMain ], { 43 | stdio: 'inherit' 44 | }); 45 | -------------------------------------------------------------------------------- /lib/required-count.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const fs = require('fs-extra'); 10 | const os = require('os'); 11 | const path = require('path'); 12 | const util = require('util'); 13 | 14 | /** 15 | * Modules 16 | * External 17 | * @constant 18 | */ 19 | const appRootPath = require('app-root-path')['path']; 20 | 21 | /** 22 | * Modules 23 | * Internal 24 | */ 25 | let count; 26 | 27 | let isFirst = () => { 28 | return Boolean(count); 29 | }; 30 | 31 | let getCount = () => { 32 | let moduleCache = require.cache[__filename]; 33 | if (moduleCache.hasOwnProperty('requireCount')) { 34 | moduleCache['requireCount'] = moduleCache['requireCount'] + 1; 35 | } 36 | else { 37 | moduleCache['requireCount'] = 0; 38 | } 39 | 40 | return count = moduleCache['requireCount']; 41 | }; 42 | 43 | 44 | module.exports = { 45 | isFirst: isFirst, 46 | getCount: getCount 47 | }; 48 | 49 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 📋 Description 4 | 7 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. 8 | 9 | ## 🗂 Type 10 | 13 | - [ ] 🍾 Feature 14 | - [ ] 🚨 Bugfix 15 | - [ ] 📒 Documentation 16 | - [ ] 👷 Internals 17 | 18 | ## 🔥 Severity 19 | 22 | - [ ] 💎 Non-Breaking Changes 23 | - [ ] 💔 Breaking Changes 24 | 25 | ## 🖥 Platforms 26 | 29 | - [x] 🍏 macOS 30 | - [x] 💾 Windows 31 | - [x] 🐧 Linux 32 | 33 | ## 🛃 Tests 34 | 37 | - [ ] My changes have been tested manually. 38 | - [ ] My changes are covered by automated testing methods. 39 | 40 | ## 👨‍🎓 Miscellaneous 41 | 44 | - [ ] My changes follow the style guide. 45 | - [ ] My changes require updates to the documentation. 46 | 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 🤷🏽‍♂️ Current Behaviour 4 | 8 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. 9 | 10 | ## 🎯 Expected Behaviour 11 | 15 | At vero eos et accusam et justo duo dolores et ea rebum. 16 | 17 | ## 👟 Steps to Reproduce (S2R) 18 | 21 | 1. At vero eos et accusam, 22 | 2. justo duo dolores et ea rebum, 23 | 3. stet clita kasd gubergren, 24 | 3. no sea takimata sanctus est. 25 | 26 | ## 🏡 Environmental Context 27 | 30 | **App Version** 31 | v0.0.1 32 | **Installation Type** 33 | Setup 34 | **Operating System** 35 | Windows 10 Enterprise x64 (15042.00) 36 | 37 | 38 | -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | os: Visual Studio 2015 2 | 3 | platform: 4 | - x64 5 | 6 | branches: 7 | only: 8 | - release 9 | 10 | version: '{build}-{branch}' 11 | 12 | cache: 13 | - 'node_modules' 14 | - '%APPDATA%\npm-cache' 15 | - '%USERPROFILE%\.electron' 16 | 17 | init: 18 | - cmd: echo 🚦 Authorizing Build 19 | - ps: if ($env:OS -eq "Windows_NT" -And $env:DEPLOY_WINDOWS -eq "0") { $host.SetShouldExit(0) } 20 | - cmd: git config --global core.autocrlf input 21 | 22 | install: 23 | - cmd: echo 🔧 Setting up Environment 24 | - ps: Install-Product node 7.8.0 25 | - cmd: npm --global update npm 26 | 27 | before_build: 28 | - cmd: echo 📥 Installing Dependencies 29 | - cmd: npm install 30 | 31 | build_script: 32 | - cmd: echo 📦 Building 33 | - cmd: npm run build --metadata 34 | 35 | deploy_script: 36 | - cmd: echo 📮 Deploying 37 | - cmd: npm run deploy 38 | 39 | artifacts: 40 | - path: build\output\*.exe 41 | - path: build\output\*.yml 42 | 43 | notifications: 44 | - provider: Webhook 45 | url: https://webhooks.gitter.im/e/4254b68cb8c0df23d61f 46 | method: GET 47 | on_build_success: true 48 | on_build_failure: true 49 | on_build_status_changed: true 50 | 51 | test: off 52 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | include: 3 | - os: osx 4 | osx_image: xcode8.2 5 | sudo: required 6 | - os: linux 7 | dist: trusty 8 | sudo: required 9 | compiler: clang 10 | 11 | language: c 12 | 13 | branches: 14 | only: 15 | - release 16 | 17 | cache: 18 | directories: 19 | - "$HOME/.electron" 20 | - "./node_modules" 21 | 22 | addons: 23 | apt: 24 | packages: 25 | - bsdtar 26 | - g++-multilib 27 | - gcc-multilib 28 | - graphicsmagick 29 | - icnsutils 30 | - rpm 31 | - xz-utils 32 | 33 | before_install: 34 | - echo "🚦 Authorizing Build" 35 | - if [[ "${OSTYPE}" == "darwin"* ]] && [[ "${DEPLOY_MACOS}" == 0 ]]; then travis_terminate 0; fi 36 | - if [[ "${OSTYPE}" == "linux"* ]] && [[ "${DEPLOY_LINUX}" == 0 ]]; then travis_terminate 0; fi 37 | - echo "🔧 Setting up Environment" 38 | - curl -o- https://raw.githubusercontent.com/creationix/nvm/master/install.sh | NVM_DIR="${HOME}"/.nvm sh 39 | - source "${HOME}"/.nvm/nvm.sh && nvm install 7.8.0 && nvm use 7.8.0 40 | - npm --global update npm 41 | 42 | install: 43 | - echo "📥 Installing Dependencies" 44 | - npm install 45 | 46 | script: 47 | - echo "📦 Building" 48 | - npm run build --metadata 49 | 50 | after_success: 51 | - echo "📮 Deploying" 52 | - npm run deploy 53 | 54 | notifications: 55 | webhooks: 56 | urls: 57 | - https://webhooks.gitter.im/e/a1a9de50af1c703bfe6c 58 | on_success: change 59 | on_failure: always 60 | on_start: never 61 | 62 | -------------------------------------------------------------------------------- /lib/platform-helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Current platform name 6 | * @type {string} 7 | */ 8 | let getName = process.platform.indexOf('win') === 0 ? 'win' : process.platform.indexOf('darwin') === 0 ? 'darwin' : 'linux'; 9 | 10 | /** 11 | * Current Architecture 12 | * @type {string} 13 | */ 14 | let getArch = process.arch === 'ia32' ? '32' : '64'; 15 | 16 | /** 17 | * Get standard image extension for current Platform 18 | * @param {String} platformName - Platform Name 19 | * @returns {string} 20 | */ 21 | let getIconImageExtension = (platformName) => { 22 | return platformName.indexOf('win') === 0 ? '.ico' : platformName.indexOf('darwin') === 0 ? '.icns' : '.png'; 23 | }; 24 | 25 | /** 26 | * Get standard icon extension for current Platform 27 | * @param {String} platformName - Platform Name 28 | * @returns {string} 29 | */ 30 | let getTemplateImageExtension = (platformName) => { 31 | return platformName.indexOf('darwin') === 0 ? '-Template.png' : '.png'; 32 | }; 33 | 34 | 35 | /** 36 | * @exports 37 | */ 38 | module.exports = { 39 | isDarwin: getName === 'darwin', 40 | isOSX: getName === 'darwin', 41 | isMacOS: getName === 'darwin', 42 | isWin: getName === 'win', 43 | isWindows: getName === 'win', 44 | isLinux: getName === 'linux', 45 | name: process.platform + getArch, 46 | type: process.platform, 47 | arch: getArch, 48 | trayImageExtension: '.png', 49 | menuItemImageExtension: '-Template.png', 50 | iconImageExtension: getIconImageExtension, 51 | templateImageExtension: getTemplateImageExtension 52 | }; 53 | -------------------------------------------------------------------------------- /app/scripts/main/services/power-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const path = require('path'); 10 | 11 | /** 12 | * Modules 13 | * Electron 14 | * @constant 15 | */ 16 | const electron = require('electron'); 17 | const { app } = electron || electron.remote; 18 | 19 | /** 20 | * Modules 21 | * External 22 | * @constant 23 | */ 24 | const appRootPath = require('app-root-path')['path']; 25 | 26 | /** 27 | * Modules 28 | * Internal 29 | * @constant 30 | */ 31 | const logger = require(path.join(appRootPath, 'lib', 'logger'))({ write: true }); 32 | 33 | 34 | /** 35 | * @constant 36 | * @default 37 | */ 38 | const defaultTimeout = 5000; 39 | 40 | 41 | /** 42 | * Init 43 | */ 44 | let init = () => { 45 | logger.debug('init'); 46 | 47 | /** 48 | * @listens Electron.powerMonitor#suspend 49 | */ 50 | electron.powerMonitor.on('suspend', () => { 51 | logger.log('webview#suspend'); 52 | }); 53 | 54 | /** 55 | * @listens Electron.powerMonitor#resume 56 | */ 57 | electron.powerMonitor.on('resume', () => { 58 | logger.log('webview#resume'); 59 | 60 | let timeout = setTimeout(() => { 61 | logger.log('webview#resume', 'relaunching app'); 62 | 63 | app.relaunch(); 64 | app.exit(); 65 | 66 | clearTimeout(timeout); 67 | }, defaultTimeout); 68 | }); 69 | }; 70 | 71 | 72 | /** 73 | * @listens Electron.App#ready 74 | */ 75 | app.once('ready', () => { 76 | logger.debug('app#ready'); 77 | 78 | init(); 79 | }); 80 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Creating Issues 4 | 5 | To file bug reports and feature suggestions, use the [issues page](../../issues?q=is%3Aissue). 6 | 7 | 1. Make sure the issue has not been filed before. 8 | 9 | 1. [Create a new Issue](../../issues/new) by filling out the form. 10 | 11 | 1. If an issue requires more information and receives no further input, it will be closed. 12 | 13 | 14 | ## Creating Pull Requests 15 | 16 | To create pull requests, use the [Pull Requests page](../../pulls). 17 | 18 | 1. [Create a new Issue](#creating-issues) describing the Bug or Feature you are addressing, to let others know you are working on it. 19 | 20 | 1. If a related issue exists, add a comment to let others know that you'll submit a pull request. 21 | 22 | 1. [Create a new Pull Request](../../pulls/new) by filling out the form. 23 | 24 | 25 | ### Setup 26 | 27 | 1. Fork the repository. 28 | 1. Clone your fork. 29 | 1. Make a branch for your change. 30 | 1. Run `npm install`. 31 | 32 | ## Commit Message 33 | 34 | Use the AngularJS commit message format: 35 | 36 | ``` 37 | type(scope): subject 38 | ``` 39 | 40 | #### type 41 | - `feat` New feature 42 | - `fix` A bugfix 43 | - `refactor` Code changes which are neither bugfix nor feature 44 | - `docs`: Documentation changes 45 | - `test`: New tests or changes to existing tests 46 | - `chore`: Changes to tooling or library changes 47 | 48 | #### scope 49 | The context of the changes, e.g. `preferences-window` or `compiler`. Use consistent names. 50 | 51 | #### subject 52 | A **brief, yet descriptive** description of the changes, using the following format: 53 | 54 | - present tense 55 | - lowercase 56 | - no period at the end 57 | - describe what the commit does 58 | - reference to issues via their id – e.g. `(#1337)` 59 | 60 | -------------------------------------------------------------------------------- /lib/language.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const path = require('path'); 10 | 11 | /** 12 | * Modules 13 | * External 14 | * @constant 15 | */ 16 | const appRootPath = require('app-root-path')['path']; 17 | 18 | /** 19 | * Modules 20 | * Internal 21 | * @constant 22 | */ 23 | const isDebug = require(path.join(appRootPath, 'lib', 'is-env'))('debug'); 24 | 25 | 26 | /** 27 | * List Event Handlers 28 | * @param {HTMLElement} target - Target Element 29 | * @return {Array|undefined} - List Event Handlers 30 | * @function 31 | * 32 | * @public 33 | */ 34 | let getEventHandlersList = (target) => { 35 | if (!isDebug || !window.chrome) { return; } 36 | 37 | //noinspection JSUnresolvedFunction,JSHint 38 | return getEventListeners(target); 39 | }; 40 | 41 | /** 42 | * Get Prototype chain 43 | * @param {*} object - Variable 44 | * @returns {Array} - List of prototypes names 45 | * @function 46 | * 47 | * @public 48 | */ 49 | let getPrototypeList = (object) => { 50 | let prototypeList = []; 51 | let parent = object; 52 | 53 | while (true) { 54 | parent = Object.getPrototypeOf(parent); 55 | if (parent === null) { 56 | break; 57 | } 58 | prototypeList.push(Object.prototype.toString.call(parent).match(/^\[object\s(.*)]$/)[1]); 59 | } 60 | return prototypeList; 61 | }; 62 | 63 | /** 64 | * Get root Prototype 65 | * @param {*} object - Variable 66 | * @returns {String} - Type 67 | * @function 68 | * 69 | * @public 70 | */ 71 | let getPrototype = (object) => { 72 | return getPrototypeList(object)[0]; 73 | }; 74 | 75 | 76 | /** 77 | * @exports 78 | */ 79 | module.exports = { 80 | getEventHandlers: getEventHandlersList, 81 | getPrototype: getPrototype, 82 | getPrototypes: getPrototypeList 83 | }; 84 | 85 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowKeywords": ["with"], 3 | "disallowMixedSpacesAndTabs": true, 4 | "disallowMultipleLineStrings": true, 5 | "disallowNewlineBeforeBlockStatements": true, 6 | "disallowSpaceAfterObjectKeys": true, 7 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], 8 | "disallowSpaceBeforeBinaryOperators": [","], 9 | "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], 10 | "disallowSpacesInAnonymousFunctionExpression": { 11 | "beforeOpeningRoundBrace": true 12 | }, 13 | "disallowSpacesInFunctionDeclaration": { 14 | "beforeOpeningRoundBrace": true 15 | }, 16 | "disallowSpacesInNamedFunctionExpression": { 17 | "beforeOpeningRoundBrace": true 18 | }, 19 | "disallowSpacesInsideParentheses": true, 20 | "disallowTrailingComma": true, 21 | "disallowTrailingWhitespace": true, 22 | "requireCommaBeforeLineBreak": true, 23 | "requireLineFeedAtFileEnd": true, 24 | "requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"], 25 | "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"], 26 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"], 27 | "requireSpaceBeforeBlockStatements": true, 28 | "requireSpacesInConditionalExpression": { 29 | "afterTest": true, 30 | "beforeConsequent": true, 31 | "afterConsequent": true, 32 | "beforeAlternate": true 33 | }, 34 | "requireSpacesInFunction": { 35 | "beforeOpeningCurlyBrace": true 36 | }, 37 | "validateLineBreaks": "LF", 38 | "validateParameterSeparator": ", ", 39 | "jsDoc": { 40 | "checkParamNames": true, 41 | "requireParamTypes": true 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Creating Issues 4 | 5 | To file bug reports and feature suggestions, use the ["Issues"](https://github.com/sidneys/pb-for-desktop/issues?q=is%3Aissue) page. 6 | 7 | 1. Make sure the issue has not been filed before. 8 | 1. Create a new issue by filling out [the issue form](https://github.com/sidneys/pb-for-desktop/issues/new). 9 | 1. If an issue requires more information and receives no further input, it will be closed. 10 | 11 | 12 | ## Creating Pull Requests 13 | 14 | To create pull requests, use the ["Pull Requests"](https://github.com/sidneys/pb-for-desktop/pulls) page. 15 | 16 | 1. [Create a new Issue](#creating-issues) describing the Bug or Feature you are addressing, to let others know you are working on it. 17 | 1. If a related issue exists, add a comment to let others know that you'll submit a pull request. 18 | 1. Create a new pull request by filling out [the pull request form](https://github.com/sidneys/pb-for-desktop/pulls/compare). 19 | 20 | 21 | ### Setup 22 | 23 | 1. Fork the repository. 24 | 1. Clone your fork. 25 | 1. Make a branch for your change. 26 | 1. Run `npm install`. 27 | 28 | ## Commit Message 29 | 30 | Use the AngularJS commit message format: 31 | 32 | ``` 33 | type(scope): subject 34 | ``` 35 | 36 | #### type 37 | - `feat` New feature 38 | - `fix` A bugfix 39 | - `refactor` Code changes which are neither bugfix nor feature 40 | - `docs`: Documentation changes 41 | - `test`: New tests or changes to existing tests 42 | - `chore`: Changes to tooling or library changes 43 | 44 | #### scope 45 | The context of the changes, e.g. `preferences-window` or `compiler`. Use consistent names. 46 | 47 | #### subject 48 | A **brief, yet descriptive** description of the changes, using the following format: 49 | 50 | - present tense 51 | - lowercase 52 | - no period at the end 53 | - describe what the commit does 54 | - reference to issues via their id – e.g. `(#1337)` 55 | 56 | -------------------------------------------------------------------------------- /app/scripts/main/services/debug-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const path = require('path'); 10 | 11 | /** 12 | * Modules 13 | * Electron 14 | * @constant 15 | */ 16 | const electron = require('electron'); 17 | const { app, webContents } = electron || electron.remote; 18 | 19 | /** 20 | * Modules 21 | * External 22 | * @constant 23 | */ 24 | const appRootPath = require('app-root-path')['path']; 25 | const tryRequire = require('try-require'); 26 | 27 | /** 28 | * Modules 29 | * Internal 30 | * @constant 31 | */ 32 | const isDebug = require(path.join(appRootPath, 'lib', 'is-env'))('debug'); 33 | const isLivereload = require(path.join(appRootPath, 'lib', 'is-env'))('livereload'); 34 | const logger = require(path.join(appRootPath, 'lib', 'logger'))({ write: true }); 35 | 36 | 37 | /** 38 | * @constant 39 | * @default 40 | */ 41 | const defaultTimeout = 5000; 42 | 43 | 44 | /** 45 | * Init 46 | */ 47 | let init = () => { 48 | logger.debug('init'); 49 | 50 | let timeout = setTimeout(() => { 51 | webContents.getAllWebContents().forEach((contents) => { 52 | 53 | /** 54 | * Developer Tools 55 | */ 56 | if (isDebug) { 57 | logger.info('opening developer tools:', `"${contents.getURL()}"`); 58 | 59 | contents.openDevTools({ mode: 'undocked' }); 60 | } 61 | 62 | /** 63 | * Live Reload 64 | */ 65 | if (isLivereload) { 66 | logger.info('starting live reload:', `"${contents.getURL()}"`); 67 | 68 | tryRequire('electron-connect').client.create(); 69 | } 70 | }); 71 | clearTimeout(timeout); 72 | }, defaultTimeout); 73 | }; 74 | 75 | 76 | /** 77 | * @listens Electron.App#ready 78 | */ 79 | app.once('ready', () => { 80 | logger.debug('app#ready'); 81 | 82 | init(); 83 | }); 84 | -------------------------------------------------------------------------------- /app/scripts/main/services/notification-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const path = require('path'); 10 | 11 | /** 12 | * Modules 13 | * Electron 14 | * @constant 15 | */ 16 | const electron = require('electron'); 17 | const { webContents } = electron || electron.remote; 18 | 19 | /** 20 | * Modules 21 | * External 22 | * @constant 23 | */ 24 | const _ = require('lodash'); 25 | const appRootPath = require('app-root-path')['path']; 26 | 27 | /** 28 | * Modules 29 | * Internal 30 | * @constant 31 | */ 32 | const logger = require(path.join(appRootPath, 'lib', 'logger'))({ write: true }); 33 | const packageJson = require(path.join(appRootPath, 'package.json')); 34 | const platformHelper = require(path.join(appRootPath, 'lib', 'platform-helper')); 35 | 36 | 37 | /** 38 | * Application 39 | * @constant 40 | * @default 41 | */ 42 | const appIcon = path.join(appRootPath, 'icons', platformHelper.type, `icon${platformHelper.iconImageExtension(platformHelper.type)}`); 43 | const appProductName = packageJson.productName || packageJson.name; 44 | 45 | 46 | /** 47 | * Default HTML5 notification options 48 | * @constant 49 | * @default 50 | */ 51 | const defaultOptions = { 52 | silent: true 53 | }; 54 | 55 | /** 56 | * Show Notification 57 | * @param {String=} title - Title 58 | * @param {Object=} options - Title 59 | * @function 60 | * 61 | * @public 62 | */ 63 | let showNotification = (title, options) => { 64 | logger.debug('showNotification'); 65 | 66 | if (!_.isString(title)) { return; } 67 | 68 | const notificationTitle = _.trim(title); 69 | const notificationOptions = JSON.stringify(_.defaultsDeep(options, defaultOptions)); 70 | 71 | const code = `new Notification('${notificationTitle}', ${notificationOptions});`; 72 | 73 | if (webContents.getAllWebContents().length === 0) { 74 | logger.warn('could not show notification', 'no webcontents available'); 75 | return; 76 | } 77 | 78 | webContents.getAllWebContents()[0].executeJavaScript(code, true); 79 | }; 80 | 81 | 82 | /** 83 | * @exports 84 | */ 85 | module.exports = { 86 | show: showNotification 87 | }; 88 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const path = require('path'); 10 | 11 | /** 12 | * Modules 13 | * Electron 14 | * @constant 15 | */ 16 | const electron = require('electron'); 17 | const { app } = electron; 18 | 19 | /** 20 | * Modules 21 | * External 22 | * @constant 23 | */ 24 | const appRootPath = require('app-root-path'); 25 | const electronConnect = require('electron-connect'); 26 | const gulp = require('gulp'); 27 | 28 | /** 29 | * Modules 30 | * Configuration 31 | */ 32 | appRootPath.setPath(path.join(__dirname)); 33 | 34 | /** 35 | * Modules 36 | * Internal 37 | * @constant 38 | */ 39 | const packageJson = require(path.join(appRootPath['path'], 'package.json')); 40 | 41 | 42 | /** 43 | * Filesystem 44 | * @constant 45 | * @default 46 | */ 47 | const applicationPath = path.join(appRootPath['path'], packageJson.main); 48 | 49 | /** 50 | * Electron Connect Server 51 | * Init 52 | */ 53 | const electronConnectServer = electronConnect.server.create({ 54 | //logLevel: 2, 55 | //verbose: true, 56 | stopOnClose: true, 57 | electron: electron, 58 | path: applicationPath, 59 | useGlobalElectron: false 60 | }); 61 | 62 | /** 63 | * Electron Connect Server 64 | * Files 65 | */ 66 | let appSources = { 67 | main: [ 68 | path.join(appRootPath['path'], 'app', 'fonts', '**', '*.*'), 69 | path.join(appRootPath['path'], 'app', 'html', '**', '*.*'), 70 | path.join(appRootPath['path'], 'app', 'images', '**', '*.*'), 71 | path.join(appRootPath['path'], 'app', 'scripts', 'main', '**', '*.*'), 72 | path.join(appRootPath['path'], 'app', 'styles', '**', '*.*'), 73 | path.join(appRootPath['path'], 'package.json') 74 | ], 75 | renderer: [ 76 | path.join(appRootPath['path'], 'app', 'scripts', 'renderer', '**', '*.*') 77 | ] 78 | }; 79 | 80 | 81 | /** 82 | * Server 83 | * start 84 | */ 85 | gulp.task('livereload', () => { 86 | electronConnectServer.start(); 87 | gulp.watch(appSources.main, ['main:restart']); 88 | gulp.watch(appSources.renderer, ['renderer:reload']); 89 | }); 90 | 91 | /** 92 | * Main Process 93 | * restart 94 | */ 95 | gulp.task('main:restart', (callback) => { 96 | electronConnectServer.restart(); 97 | callback(); 98 | }); 99 | 100 | /** 101 | * Renderer Process 102 | * restart 103 | */ 104 | gulp.task('renderer:reload', (callback) => { 105 | electronConnectServer.reload(); 106 | callback(); 107 | }); 108 | 109 | 110 | gulp.task('default', ['livereload']); 111 | 112 | -------------------------------------------------------------------------------- /lib/is-env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const chalk = require('chalk'); 10 | const chalkline = require('chalkline'); 11 | const minimist = require('minimist'); 12 | const path = require('path'); 13 | const _ = require('lodash'); 14 | 15 | /** 16 | * Modules 17 | * External 18 | * @constant 19 | */ 20 | const appRootPath = require('app-root-path')['path']; 21 | 22 | 23 | /** 24 | * Modules 25 | * Internal 26 | * @constant 27 | */ 28 | const requiredCount = require(path.join(appRootPath, 'lib', 'required-count')); 29 | 30 | 31 | /** 32 | * @return {Boolean} 33 | * @function 34 | * 35 | * @public 36 | */ 37 | let lookupEnvironment = () => { 38 | let name = _.lowerCase(module.exports.environmentName); 39 | 40 | // Arguments 41 | let globalArgvObj = {}; 42 | let npmArgvObj = {}; 43 | try { globalArgvObj = minimist(process.argv); } catch (err) {} 44 | try { npmArgvObj = minimist(JSON.parse(process.env.npm_config_argv).original); } catch (err) {} 45 | 46 | // Node 47 | const nodeEnvName = process.env['NODE_ENV']; 48 | const npmScriptName = process.env.hasOwnProperty('npm_lifecycle_event') ? process.env['npm_lifecycle_event'] : false; 49 | 50 | // Global 51 | const globalValue = process.env[_.lowerCase(name)] || process.env[_.upperCase(name)]; 52 | 53 | let isEnv = 54 | // if DEBUG=1, not if DEBUG=0 55 | globalValue && _.isFinite(parseInt(globalValue)) && parseInt(globalValue) > 0 || 56 | // if DEBUG=text 57 | globalValue && !_.isFinite(parseInt(globalValue)) && Boolean(globalValue) === true || 58 | // if NODE_ENV=environmentName 59 | nodeEnvName === name || 60 | // commandline argument 61 | globalArgvObj[name] === true || 62 | // commandline argument (npm) 63 | npmArgvObj[name] === true || 64 | // npm script name from package.json 65 | npmScriptName && npmScriptName.includes(`:${name}`); 66 | 67 | return Boolean(isEnv); 68 | }; 69 | 70 | /** 71 | * Prints Environment once 72 | */ 73 | let printEnvironmentName = () => { 74 | let active = lookupEnvironment(); 75 | let count = requiredCount.getCount(); 76 | let name = module.exports.environmentName; 77 | let style = chalk['white'].bold; 78 | 79 | if (count === 0 && active) { 80 | chalkline.white(); 81 | console.log(style('ENVIRONMENT:'), style(_.upperCase(name))); 82 | chalkline.white(); 83 | } 84 | }; 85 | 86 | 87 | let init = () => { 88 | 89 | printEnvironmentName(); 90 | }; 91 | 92 | 93 | /** 94 | * @exports 95 | */ 96 | module.exports = (name) => { 97 | module.exports.environmentName = name; 98 | module.exports.environmentActive = lookupEnvironment(); 99 | 100 | init(); 101 | 102 | return module.exports.environmentActive; 103 | }; 104 | -------------------------------------------------------------------------------- /app/scripts/renderer/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const path = require('path'); 10 | 11 | /** 12 | * Modules 13 | * External 14 | * @constant 15 | */ 16 | const appRootPath = require('app-root-path'); 17 | const html2canvas = require('html2canvas'); 18 | require(path.join(appRootPath.path, 'app', 'vendor', 'glitch')); 19 | const glitch = window.glitch; 20 | 21 | /** 22 | * Modules 23 | * Internal 24 | * @constant 25 | */ 26 | const logger = require(path.join(appRootPath.path, 'lib', 'logger'))({ write: true }); 27 | const domHelper = require(path.join(appRootPath.path, 'lib', 'dom-helper')); 28 | 29 | /** 30 | * @constant 31 | * @default 32 | */ 33 | const defaultCanvasCount = 5; 34 | const defaultGlitchIntervalMinimum = 3000; 35 | const defaultGlitchIntervalMaximum = 10000; 36 | 37 | /** 38 | * DOM 39 | */ 40 | let elementLogo = document.querySelector('.logo__container'); 41 | let elementText = elementLogo.querySelector('.logo__text'); 42 | let elementCanvas; 43 | 44 | /** 45 | * Text 46 | */ 47 | let textNormal = elementText.dataset.textNormal; 48 | let textGlitched = elementText.dataset.textGlitched; 49 | 50 | 51 | /** 52 | * @instance 53 | */ 54 | let canvasCount = 0; 55 | let canvasList = []; 56 | 57 | /** 58 | * @constant 59 | * @default 60 | */ 61 | const defaultTimeout = 60; 62 | 63 | 64 | /** 65 | * Generate random integer 66 | * @param {Number} min - Minimum 67 | * @param {Number} max - Maximum 68 | * @returns {Number} Integer 69 | */ 70 | let getRandomInt = (min, max) => { 71 | return Math.floor(Math.random() * (max - min + 1)) + min; 72 | } 73 | 74 | /** 75 | * Render 76 | */ 77 | let render = () => { 78 | logger.debug('render'); 79 | 80 | if (elementCanvas != null) { 81 | document.body.removeChild(elementCanvas); 82 | } 83 | 84 | if ((canvasList.length > 0) && (canvasList.length > canvasCount)) { 85 | elementCanvas = canvasList[canvasCount]; 86 | document.body.insertBefore(elementCanvas, document.body.firstChild); 87 | canvasCount++; 88 | elementText.innerHTML = textGlitched; 89 | 90 | let timeout = setTimeout(() => { 91 | render(); 92 | 93 | clearTimeout(timeout); 94 | }, defaultTimeout); 95 | } else { 96 | if (canvasCount >= canvasList.length) { 97 | canvasCount = 0; 98 | } 99 | 100 | elementCanvas = null; 101 | elementText.innerText = textNormal; 102 | 103 | let timeout = setTimeout(() => { 104 | render(); 105 | 106 | clearTimeout(timeout); 107 | }, getRandomInt(defaultGlitchIntervalMinimum, defaultGlitchIntervalMaximum)); 108 | } 109 | } 110 | 111 | 112 | /** 113 | * @listens window:Event#load 114 | */ 115 | window.addEventListener('load', () => { 116 | logger.debug('window#load'); 117 | 118 | for (var i = 0; i < defaultCanvasCount; ++i) { 119 | glitch(document.body, { 120 | amount: i, 121 | complete(canvas) { 122 | canvas.style.position = 'absolute'; 123 | canvas.style.top = canvas.style.right = canvas.style.bottom = canvas.style.left = 0; 124 | canvasList.push(canvas); 125 | } 126 | }); 127 | } 128 | 129 | render(); 130 | }); 131 | -------------------------------------------------------------------------------- /app/styles/styles.css: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | FONT-FACE 3 | ========================================================================== */ 4 | 5 | @font-face { 6 | font-family: "Arial Black Regular"; 7 | font-weight: 400; 8 | font-style: normal; 9 | src: url("../fonts/arial-black.eot"); 10 | src: url("../fonts/arial-black.eot?#iefix") format("embedded-opentype"), 11 | url("../fonts/arial-black.woff") format("woff"), 12 | url("../fonts/arial-black.ttf") format("truetype"); 13 | } 14 | 15 | /* ========================================================================== 16 | GLOBAL 17 | ========================================================================== */ 18 | 19 | html { 20 | font-size: 62.5%; 21 | background-color: transparent; 22 | margin: 0; 23 | padding: 0; 24 | width: 100%; 25 | height: 100%; 26 | overflow: hidden; 27 | -webkit-font-smoothing: antialiased; 28 | font-family: "Arial Black Regular", "Arial", sans-serif; 29 | } 30 | 31 | /* ========================================================================== 32 | LOGO 33 | ========================================================================== */ 34 | 35 | .logo__container { 36 | width: 50%; 37 | padding: 0; 38 | height: 50%; 39 | margin: auto; 40 | position: absolute; 41 | top: 25%; 42 | left: 25%; 43 | filter: opacity(0.95); 44 | transform: scale(1.5); 45 | } 46 | 47 | .logo__letter { 48 | font-size: 33rem; 49 | transform: rotate(-40deg) translateX(-2rem); 50 | line-height: 100%; 51 | color: rgb(255, 255, 255); 52 | text-align: center; 53 | text-transform: uppercase; 54 | font-weight: 400; 55 | } 56 | 57 | .logo__text { 58 | font-size: 6rem; 59 | margin-top: 1rem; 60 | white-space: nowrap; 61 | color: rgb(255, 255, 255); 62 | text-align: center; 63 | text-transform: uppercase; 64 | font-weight: 400; 65 | } 66 | 67 | /* ========================================================================== 68 | OVERLAY 69 | ========================================================================== */ 70 | 71 | .overlay { 72 | top: 0; 73 | left: 0; 74 | bottom: 0; 75 | right: 0; 76 | position: absolute; 77 | overflow: hidden; 78 | } 79 | 80 | .overlay::after { 81 | content: " "; 82 | display: block; 83 | position: absolute; 84 | top: 0; 85 | left: 0; 86 | bottom: 0; 87 | right: 0; 88 | opacity: 0; 89 | pointer-events: none; 90 | } 91 | 92 | .overlay::before { 93 | content: " "; 94 | display: block; 95 | position: absolute; 96 | top: 0; 97 | left: 0; 98 | bottom: 0; 99 | right: 0; 100 | pointer-events: none; 101 | background-size: 12px 4px; 102 | background-image: 103 | linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.15) 75%), 104 | linear-gradient(90deg, rgba(255, 0, 0, 0.02), rgba(0, 255, 0, 0.01), rgba(0, 0, 255, 0.01)); 105 | } 106 | 107 | /* ========================================================================== 108 | STATES 109 | ========================================================================== */ 110 | 111 | /* drag + drop 112 | ========================================================================== */ 113 | 114 | .draggable { 115 | app-region: drag !important; 116 | -webkit-app-region: drag !important; 117 | } 118 | 119 | /* visibility 120 | ========================================================================== */ 121 | 122 | .hide { 123 | opacity: 0 !important; 124 | } 125 | 126 | .show { 127 | opacity: 1 !important; 128 | } 129 | -------------------------------------------------------------------------------- /lib/releasenotes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Modules 3 | * Node 4 | * @constant 5 | */ 6 | 'use strict'; 7 | 8 | 9 | const fs = require('fs-extra'); 10 | const json2md = require('json2md'); 11 | const os = require('os'); 12 | const path = require('path'); 13 | 14 | /** 15 | * Modules 16 | * External 17 | * @constant 18 | */ 19 | const _ = require('lodash'); 20 | const appRootPath = require('app-root-path')['path']; 21 | 22 | /** 23 | * Modules 24 | * Internal 25 | * @constant 26 | */ 27 | const logger = require(path.join(appRootPath, 'lib', 'logger'))({ timestamp: false }); 28 | const packageJson = require(path.join(appRootPath, 'package.json')); 29 | 30 | 31 | /** 32 | * Filesystem 33 | * @constant 34 | * @default 35 | */ 36 | const filename = 'RELEASENOTES'; 37 | const inputFilepath = path.join(appRootPath, `${filename}.json`); 38 | const outputFilepath = path.join(appRootPath, `${filename}.md`); 39 | 40 | 41 | /** 42 | * Release Notes Template 43 | * @constant 44 | * @default 45 | */ 46 | const releasenotesTemplate = { 47 | 'features': [], 48 | 'bugfixes': [], 49 | 'documentation': [], 50 | 'internals': [] 51 | }; 52 | 53 | 54 | /** 55 | * Transform Release Notes Object to markdown 56 | * @param {Object} releasenotesObject - Release Notes object 57 | * @returns {Array} - Markdown lines 58 | */ 59 | let transformToMarkdown = (releasenotesObject) => { 60 | logger.debug('transformToMarkdown'); 61 | 62 | let markdownList = []; 63 | 64 | Object.keys(releasenotesObject).forEach((value) => { 65 | markdownList.push(json2md({ h4: _.startCase(value) })); 66 | 67 | let entryContent = { ul: [] }; 68 | releasenotesObject[value].forEach((note) => { 69 | entryContent.ul.push(note); 70 | }); 71 | 72 | markdownList.push(json2md(entryContent) + os.EOL); 73 | }); 74 | 75 | return markdownList; 76 | }; 77 | 78 | /** 79 | * Write release notes to disk 80 | * 81 | * @public 82 | */ 83 | let updateFile = () => { 84 | logger.debug('writeReleasenotes'); 85 | 86 | let notesVersionsList = []; 87 | let notesVersionsObject = {}; 88 | let notesVersionsText; 89 | 90 | // Read from RELEASENOTES.json 91 | try { 92 | notesVersionsObject = JSON.parse(fs.readFileSync(inputFilepath).toString()); 93 | } catch (err) { 94 | logger.error(`release notes file read error:`, inputFilepath); 95 | return; 96 | } 97 | 98 | // Parse RELEASENOTES.json 99 | Object.keys(notesVersionsObject).forEach((version) => { 100 | notesVersionsList.push(json2md({ h2: version }) + os.EOL); 101 | notesVersionsList = notesVersionsList.concat(transformToMarkdown(notesVersionsObject[version])); 102 | }); 103 | 104 | notesVersionsText = notesVersionsList.join(os.EOL); 105 | 106 | // Write to RELEASENOTES.md 107 | fs.writeFileSync(outputFilepath, notesVersionsText); 108 | 109 | logger.info('release notes updated:', outputFilepath); 110 | }; 111 | 112 | /** 113 | * Get latest Release Notes 114 | * @returns {String} - Release notes text 115 | * 116 | * @public 117 | */ 118 | let getLatest = () => { 119 | logger.debug('getReleasenotes'); 120 | 121 | let notesList = []; 122 | let notesVersionsObject = {}; 123 | let notesText; 124 | 125 | // Read from CHANGELOG.json 126 | try { 127 | notesVersionsObject = JSON.parse(fs.readFileSync(inputFilepath).toString()); 128 | } catch (err) { 129 | logger.error(`release notes file read error:`, inputFilepath); 130 | } 131 | 132 | if (notesVersionsObject.hasOwnProperty(packageJson.version)) { 133 | notesList = transformToMarkdown(notesVersionsObject[packageJson.version]); 134 | logger.info('release notes found for:', `v${packageJson.version}`); 135 | } else { 136 | notesList = transformToMarkdown(releasenotesTemplate); 137 | logger.warn('release notes missing for:', `v${packageJson.version}`); 138 | } 139 | 140 | notesText = json2md(notesList); 141 | 142 | return notesText; 143 | }; 144 | 145 | 146 | /** 147 | * Main 148 | */ 149 | if (require.main === module) { 150 | updateFile(); 151 | } 152 | 153 | 154 | /** 155 | * @exports 156 | */ 157 | module.exports = { 158 | getLatest: getLatest, 159 | update: updateFile 160 | }; 161 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "live-wallpaper-ecorp", 3 | "productName": "E-Corp Live Wallpaper", 4 | "version": "2.0.2", 5 | "description": "Electron-based live wallpaper showing a glitched E Corp / Evil Corp logo", 6 | "license": "MIT", 7 | "homepage": "https://sidneys.github.io/live-wallpaper-ecorp", 8 | "author": { 9 | "name": "sidneys", 10 | "email": "sidneys.github.io@outlook.com", 11 | "url": "https://github.com/sidneys" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/sidneys/live-wallpaper-ecorp.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/sidneys/live-wallpaper-ecorp/issues" 19 | }, 20 | "engines": { 21 | "node": ">=7.0.0" 22 | }, 23 | "os": [ 24 | "darwin", 25 | "linux" 26 | ], 27 | "keywords": [ 28 | "pushbullet", 29 | "pushes", 30 | "notification", 31 | "chrome", 32 | "safari", 33 | "desktop", 34 | "electron", 35 | "mac", 36 | "macos", 37 | "osx", 38 | "windows", 39 | "linux", 40 | "ubuntu", 41 | "native", 42 | "pushover" 43 | ], 44 | "preferGlobal": true, 45 | "dependencies": { 46 | "app-root-path": "^2.0.1", 47 | "appdirectory": "^0.1.0", 48 | "auto-launch": "git://github.com/sidneys/node-auto-launch.git#master", 49 | "babel-cli": "^6.24.1", 50 | "babel-preset-electron": "^1.4.15", 51 | "chalk": "^1.1.3", 52 | "chalkline": "0.0.5", 53 | "electron-editor-context-menu": "^1.1.1", 54 | "electron-settings": "^3.0.14", 55 | "electron-updater": "^1.15.0", 56 | "file-type": "^4.3.0", 57 | "file-url": "^2.0.2", 58 | "fs-extra": "^3.0.1", 59 | "html2canvas": "^0.5.0-beta4", 60 | "keypath": "^0.0.1", 61 | "lodash": "^4.17.4", 62 | "minimist": "^1.2.0", 63 | "moment": "^2.18.1", 64 | "parse-semver": "^1.1.1", 65 | "present": "^1.0.0", 66 | "read-chunk": "^2.0.0", 67 | "semver-compare": "^1.0.0", 68 | "simple-reload": "0.0.4", 69 | "try-require": "latest" 70 | }, 71 | "devDependencies": { 72 | "electron": "^1.7.1", 73 | "electron-builder": "^17.8.0", 74 | "electron-connect": "^0.6.1", 75 | "electron-prebuilt-compile": "^1.7.1", 76 | "fkill": "^4.1.0", 77 | "git-branch": "^0.3.0", 78 | "glob": "^7.1.1", 79 | "gulp": "^3.9.1", 80 | "gulp-load-plugins": "^1.5.0", 81 | "hash-files": "^1.1.1", 82 | "is-ci": "^1.0.10", 83 | "json2md": "^1.5.8", 84 | "progress": "^2.0.0", 85 | "publish-release": "^1.3.3", 86 | "yamljs": "^0.2.10" 87 | }, 88 | "main": "./app/scripts/main/components/application.js", 89 | "bin": { 90 | "live-wallpaper-ecorp": "./bin/cli.js" 91 | }, 92 | "scripts": { 93 | "build": "node ./lib/build.js", 94 | "deploy": "node ./lib/deploy.js", 95 | "livereload": "./node_modules/.bin/gulp", 96 | "localsetup": "./node_modules/.bin/babel-node ./lib/localsetup.js", 97 | "releasenotes": "node ./lib/releasenotes.js", 98 | "dev": "./node_modules/.bin/electron ./app/scripts/main/components/application.js" 99 | }, 100 | "build": { 101 | "appId": "de.sidneys.live-wallpaper-ecorp", 102 | "directories": { 103 | "buildResources": "./build/staging", 104 | "output": "./build/output" 105 | }, 106 | "files": [ 107 | "!build", 108 | "!gh-pages", 109 | "!resources", 110 | "!website" 111 | ], 112 | "mac": { 113 | "category": "public.app-category.utilities", 114 | "icon": "./icons/darwin/icon.icns", 115 | "target": [ 116 | "dmg", 117 | "zip" 118 | ] 119 | }, 120 | "win": { 121 | "icon": "./icons/win32/icon.ico", 122 | "target": [ 123 | "nsis" 124 | ] 125 | }, 126 | "linux": { 127 | "category": "Utility", 128 | "icon": "./icons/linux", 129 | "target": [ 130 | "AppImage", 131 | "deb", 132 | "pacman", 133 | "rpm" 134 | ] 135 | }, 136 | "dmg": { 137 | "background": "./icons/darwin/background-setup.png", 138 | "icon": "./icons/darwin/icon-setup.icns" 139 | }, 140 | "nsis": { 141 | "artifactName": "${name}-${version}-setup.${ext}", 142 | "deleteAppDataOnUninstall": true, 143 | "installerHeader": "./icons/win32/header-setup.bmp", 144 | "installerHeaderIcon": "./icons/win32/icon.ico", 145 | "installerIcon": "./icons/win32/icon-setup.ico", 146 | "installerSidebar": "./icons/win32/background-setup.bmp", 147 | "oneClick": false, 148 | "runAfterFinish": false 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /resources/graphics/icon-tray-opaque.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 25 | 27 | image/svg+xml 28 | 30 | 31 | 32 | 33 | 34 | 36 | 61 | 66 | 67 | -------------------------------------------------------------------------------- /lib/localsetup.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | 5 | /** 6 | * Modules 7 | * Node 8 | * @constant 9 | */ 10 | const childProcess = require('child_process'); 11 | const fs = require('fs-extra'); 12 | const os = require('os'); 13 | const path = require('path'); 14 | 15 | /** 16 | * Modules 17 | * External 18 | * @constant 19 | */ 20 | const appRootPath = require('app-root-path')['path']; 21 | const fkill = require('fkill'); 22 | const glob = require('glob'); 23 | const minimist = require('minimist'); 24 | 25 | /** 26 | * Modules 27 | * Internal 28 | * @constant 29 | */ 30 | const isDebug = require(path.join(appRootPath, 'lib', 'is-env'))('debug'); 31 | const logger = require(path.join(appRootPath, 'lib', 'logger'))({ timestamp: false }); 32 | const packageJson = require(path.join(appRootPath, 'package.json')); 33 | const platformHelper = require(path.join(appRootPath, 'lib', 'platform-helper')); 34 | 35 | 36 | /** 37 | * Application 38 | * @constant 39 | * @default 40 | */ 41 | const appProductName = packageJson.productName || packageJson.name; 42 | const appName = packageJson.name; 43 | const appVersion = packageJson.version; 44 | 45 | /** 46 | * Filesystem 47 | * @constant 48 | * @default 49 | */ 50 | const targetDirectory = path.join(appRootPath, packageJson.build.directories.output); 51 | 52 | 53 | /** 54 | * Init 55 | * @default 56 | */ 57 | let startInDebug = false; 58 | 59 | 60 | /** 61 | * Build 62 | */ 63 | let build = () => { 64 | logger.debug('build'); 65 | 66 | childProcess.execSync(packageJson.scripts.build, { cwd: appRootPath, stdio: [0, 1, 2] }); 67 | }; 68 | 69 | /** 70 | * Quit application, install and launch 71 | */ 72 | let main = () => { 73 | /** 74 | * Forward debug setting 75 | */ 76 | if (startInDebug === true) { 77 | process.env.DEBUG = true; 78 | } 79 | 80 | /** 81 | * macOS 82 | */ 83 | if (platformHelper.isMacOS) { 84 | let sourceFilePath = path.normalize(path.join(targetDirectory, 'mac', `${appProductName}.app`)); 85 | let destinationFilePath = path.join('/Applications', `${appProductName}.app`); 86 | 87 | // DEBUG 88 | logger.debug('macos', 'sourceFilePath', sourceFilePath); 89 | logger.debug('macos', 'destinationFilePath', destinationFilePath); 90 | 91 | logger.info('closing'); 92 | fkill(appProductName, { force: true }); 93 | logger.info('removing application'); 94 | fs.removeSync(destinationFilePath); 95 | logger.info('installing application'); 96 | fs.copySync(sourceFilePath, destinationFilePath, { clobber: true, preserveTimestamps: true }); 97 | logger.info('starting'); 98 | childProcess.execSync(`open "${destinationFilePath}"`); 99 | } 100 | 101 | /** 102 | * Windows 103 | */ 104 | if (platformHelper.isWindows) { 105 | let installerFilePathPattern = path.join(targetDirectory, 'win', `*${appProductName}*${appVersion}.exe`); 106 | let installerFilePathList = glob.sync(installerFilePathPattern); 107 | let installerFilePath = installerFilePathList[0]; 108 | 109 | // DEBUG 110 | logger.debug('windows', 'installerFilePathPattern', installerFilePathPattern); 111 | logger.debug('windows', 'installerFilePathList', installerFilePathList); 112 | logger.debug('windows', 'installerFilePath', installerFilePath); 113 | 114 | logger.info('closing'); 115 | fkill(appProductName, { force: true }); 116 | logger.info('starting'); 117 | childProcess.execSync(`start "" "${installerFilePath}"`, { stdio: [0, 1, 2] }); 118 | } 119 | 120 | /** 121 | * Linux 122 | */ 123 | if (platformHelper.isLinux) { 124 | let installerArch; 125 | 126 | switch (os.arch()) { 127 | case 'arm7l': 128 | installerArch = 'arm'; 129 | break; 130 | case 'x64': 131 | installerArch = 'amd64'; 132 | break; 133 | case 'ia32': 134 | installerArch = 'i386'; 135 | break; 136 | } 137 | 138 | let installerFilePathPattern = path.normalize(path.join(targetDirectory, `*${appName}*${appVersion}*${installerArch}*.deb`)); 139 | let installerFilePathList = glob.sync(installerFilePathPattern); 140 | let installerFilePath = installerFilePathList[0]; 141 | let destinationFilePath = path.join('/usr/bin', appName); 142 | 143 | // DEBUG 144 | logger.debug('linux', 'installerFilePathPattern', installerFilePathPattern); 145 | logger.debug('linux', 'installerFilePathList', installerFilePathList); 146 | logger.debug('linux', 'destinationFilePath', destinationFilePath); 147 | 148 | logger.info('closing'); 149 | fkill(appName, { force: true }); 150 | logger.info('installing'); 151 | childProcess.execSync(`sudo dpkg --install --force-overwrite "${installerFilePath}"`); 152 | logger.info('starting'); 153 | let child = childProcess.spawn(destinationFilePath, [], { detached: true, stdio: 'ignore' }); 154 | child.unref(); 155 | } 156 | }; 157 | 158 | 159 | /** 160 | * Main 161 | */ 162 | if (require.main === module) { 163 | // Arguments 164 | let npmArgvObj = {}; 165 | try { npmArgvObj = minimist(JSON.parse(process.env.npm_config_argv).original); } catch (err) {} 166 | 167 | if (npmArgvObj['build']) { build(); } 168 | 169 | startInDebug = isDebug; 170 | 171 | main(); 172 | 173 | process.exit(0); 174 | } 175 | -------------------------------------------------------------------------------- /app/scripts/main/components/application.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const path = require('path'); 10 | const url = require('url'); 11 | 12 | /** 13 | * Modules 14 | * Electron 15 | * @constant 16 | */ 17 | const electron = require('electron'); 18 | const { app, BrowserWindow, ipcMain, Menu, Tray } = electron; 19 | 20 | /** 21 | * Modules 22 | * External 23 | * @constant 24 | */ 25 | const appRootPath = require('app-root-path'); 26 | const EventEmitter = require('events'); 27 | 28 | /** 29 | * Modules 30 | * Configuration 31 | */ 32 | EventEmitter.defaultMaxListeners = Infinity; 33 | appRootPath.setPath(path.join(__dirname, '..', '..', '..', '..')); 34 | 35 | /** 36 | * Modules 37 | * Internal 38 | * @constant 39 | */ 40 | const isDebug = require(path.join(appRootPath.path, 'lib', 'is-env'))('debug'); 41 | const logger = require(path.join(appRootPath.path, 'lib', 'logger'))({ write: true }); 42 | const packageJson = require(path.join(appRootPath.path, 'package.json')); 43 | const platformHelper = require(path.join(appRootPath.path, 'lib', 'platform-helper')); 44 | const configurationManager = require(path.join(appRootPath.path, 'app', 'scripts', 'main', 'managers', 'configuration-manager')); // jshint ignore:line 45 | const updaterService = require(path.join(appRootPath.path, 'app', 'scripts', 'main', 'services', 'updater-service')); // jshint ignore:line 46 | const debugService = require(path.join(appRootPath.path, 'app', 'scripts', 'main', 'services', 'debug-service')); // jshint ignore:line 47 | 48 | /** 49 | * Application 50 | * @constant 51 | * @default 52 | */ 53 | const appProductName = packageJson.productName || packageJson.name; 54 | const appVersion = packageJson.version; 55 | 56 | /** 57 | * Filesystem 58 | * @constant 59 | * @default 60 | */ 61 | const appTrayIcon = path.join(appRootPath.path, 'icons', platformHelper.type, `icon-tray${platformHelper.templateImageExtension(platformHelper.type)}`); 62 | const wallpaperUrl = url.format({ protocol: 'file:', pathname: path.join(appRootPath.path, 'app', 'html', 'main.html') }); 63 | 64 | 65 | /** 66 | * @instance 67 | */ 68 | let trayMenu = {}; 69 | let appWindowList; 70 | 71 | /** 72 | * Tray Menu Template 73 | * @function 74 | */ 75 | let getTrayMenuTemplate = () => { 76 | return [ 77 | { 78 | id: 'productName', 79 | label: `${appProductName}`, 80 | enabled: false 81 | }, 82 | { 83 | id: 'appVersion', 84 | label: `Version ${appVersion}`, 85 | type: 'normal', 86 | enabled: false 87 | }, 88 | { 89 | type: 'separator' 90 | }, 91 | { 92 | id: 'windowVisible', 93 | label: 'Hide Wallpaper', 94 | icon: path.join(appRootPath.path, 'app', 'images', `icon-window-visible${platformHelper.menuItemImageExtension}`), 95 | type: 'checkbox', 96 | checked: !configurationManager('windowVisible').get(), 97 | click(menuItem) { 98 | configurationManager('windowVisible').set(!menuItem.checked); 99 | } 100 | }, 101 | { 102 | type: 'separator' 103 | }, 104 | { 105 | id: 'launchOnStartup', 106 | label: 'Launch on Startup', 107 | icon: path.join(appRootPath.path, 'app', 'images', `icon-launch-on-startup${platformHelper.menuItemImageExtension}`), 108 | type: 'checkbox', 109 | checked: configurationManager('launchOnStartup').get(), 110 | click(menuItem) { 111 | configurationManager('launchOnStartup').set(menuItem.checked); 112 | } 113 | }, 114 | { 115 | type: 'separator' 116 | }, 117 | { 118 | label: `Quit ${appProductName}`, 119 | click() { 120 | app.quit(); 121 | } 122 | } 123 | ]; 124 | }; 125 | 126 | 127 | /** 128 | * @listens app#ready 129 | */ 130 | app.on('ready', () => { 131 | // Create window for each display 132 | let appWindowList = electron.screen.getAllDisplays(); 133 | appWindowList.forEach(function(display) { 134 | let wallpaperWindow = new BrowserWindow({ 135 | backgroundColor: '#000000', 136 | enableLargerThanScreen: true, 137 | frame: false, 138 | hasShadow: false, 139 | movable: false, 140 | resizable: false, 141 | show: false, 142 | transparent: false, 143 | type: 'desktop', 144 | webPreferences: { 145 | allowDisplayingInsecureContent: true, 146 | allowRunningInsecureContent: true, 147 | experimentalFeatures: true, 148 | nodeIntegration: true, 149 | webaudio: true, 150 | webgl: false, 151 | webSecurity: false 152 | } 153 | }); 154 | 155 | /** 156 | * @listens Electron~WebContents#dom-ready 157 | */ 158 | wallpaperWindow.webContents.on('dom-ready', () => { 159 | logger.debug('AppWindow.webContents#dom-ready'); 160 | }); 161 | 162 | wallpaperWindow.setBounds({ 163 | x: display.bounds.x, 164 | y: display.bounds.y, 165 | width: isDebug ? parseInt(display.bounds.width/4) : (display.bounds.width + 1), 166 | height: isDebug ? parseInt(display.bounds.height/4) : (display.bounds.height + 1), 167 | }, false); 168 | 169 | wallpaperWindow.loadURL(wallpaperUrl); 170 | }); 171 | 172 | // Create Tray 173 | trayMenu = new Tray(appTrayIcon); 174 | trayMenu.setImage(appTrayIcon); 175 | trayMenu.setToolTip(appProductName); 176 | trayMenu.setContextMenu(Menu.buildFromTemplate(getTrayMenuTemplate())); 177 | 178 | // Hide Dock / Taskbar 179 | if (platformHelper.isMacOS) { 180 | app.dock.hide(); 181 | } else { 182 | BrowserWindow.getAllWindows()[0].setSkipTaskbar(true); 183 | } 184 | }); 185 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | ================== 3 | 4 | Statement of Purpose 5 | --------------------- 6 | 7 | The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). 8 | 9 | Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. 10 | 11 | For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 12 | 13 | 1. Copyright and Related Rights. 14 | -------------------------------- 15 | A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: 16 | 17 | i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; 18 | ii. moral rights retained by the original author(s) and/or performer(s); 19 | iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; 20 | iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; 21 | v. rights protecting the extraction, dissemination, use and reuse of data in a Work; 22 | vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and 23 | vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 24 | 25 | 2. Waiver. 26 | ----------- 27 | To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 28 | 29 | 3. Public License Fallback. 30 | ---------------------------- 31 | Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 32 | 33 | 4. Limitations and Disclaimers. 34 | -------------------------------- 35 | 36 | a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. 37 | b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. 38 | c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. 39 | d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. 40 | -------------------------------------------------------------------------------- /app/scripts/main/services/messenger-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const fs = require('fs-extra'); 10 | const os = require('os'); 11 | const path = require('path'); 12 | 13 | /** 14 | * Modules 15 | * Electron 16 | * @constant 17 | */ 18 | const electron = require('electron'); 19 | const { app, dialog } = electron || electron.remote; 20 | 21 | /** 22 | * Modules 23 | * External 24 | * @constant 25 | */ 26 | const _ = require('lodash'); 27 | const appRootPath = require('app-root-path')['path']; 28 | const fileType = require('file-type'); 29 | const readChunk = require('read-chunk'); 30 | 31 | /** 32 | * Modules 33 | * Internal 34 | * @constant 35 | */ 36 | const logger = require(path.join(appRootPath, 'lib', 'logger'))({ write: true }); 37 | const packageJson = require(path.join(appRootPath, 'package.json')); 38 | const platformHelper = require(path.join(appRootPath, 'lib', 'platform-helper')); 39 | 40 | 41 | /** 42 | * Application 43 | * @constant 44 | * @default 45 | */ 46 | const appProductName = packageJson.productName || packageJson.name; 47 | 48 | /** 49 | * @constant 50 | * @default 51 | */ 52 | const defaultTimeout = 250; 53 | 54 | 55 | /** 56 | * Display Message Box 57 | * @param {String} title - Title 58 | * @param {String} message - Message 59 | * @param {Array} buttonList - Buttons 60 | * @param {Boolean} isError - Buttons 61 | * @param {Function=} callback - Callback 62 | * @function 63 | */ 64 | let displayDialog = function(title, message, buttonList, isError, callback = () => {}) { 65 | let dialogTitle = title || appProductName; 66 | let dialogMessage = message || title; 67 | 68 | let timeout = setTimeout(() => { 69 | dialog.showMessageBox({ 70 | type: isError ? 'error' : 'warning', 71 | buttons: buttonList || ['OK'], 72 | defaultId: 0, 73 | title: dialogTitle, 74 | message: dialogTitle, 75 | detail: os.EOL + dialogMessage + os.EOL 76 | }, (response) => { 77 | logger.debug('displayDialog', `title: '${title}' message: '${message}' response: '${response} (${buttonList[response]})'`); 78 | callback(response); 79 | }); 80 | 81 | clearTimeout(timeout); 82 | }, defaultTimeout); 83 | }; 84 | 85 | /** 86 | * Validate Files by Mimetype 87 | * @function 88 | */ 89 | let validateFileType = function(file, acceptedFiletype, callback) { 90 | logger.debug('validateFileType', file, acceptedFiletype); 91 | 92 | let filePath = path.normalize(file.toString()); 93 | 94 | fs.stat(filePath, function(err) { 95 | if (err) { return callback(err); } 96 | 97 | let detectedType = fileType(readChunk.sync(filePath, 0, 262)).mime; 98 | let isValidFile = _.startsWith(detectedType, acceptedFiletype); 99 | 100 | if (!isValidFile) { 101 | logger.error('validFileType', detectedType); 102 | 103 | return callback(new Error(`Filetype incorrect: ${detectedType}`)); 104 | } 105 | 106 | callback(null, filePath); 107 | }); 108 | }; 109 | 110 | 111 | /** 112 | * Info 113 | * @param {String=} title - Title 114 | * @param {String=} message - Message 115 | * @param {Function=} callback - Callback 116 | * @function 117 | * 118 | * @public 119 | */ 120 | let showInfo = function(title, message, callback = () => {}) { 121 | return displayDialog(title, message, ['Dismiss'], false, callback); 122 | }; 123 | 124 | 125 | /** 126 | * Info 127 | * @param {String=} title - Title 128 | * @param {String} fileType - audio,video 129 | * @param {String=} folder - Initial lookup folder 130 | * @param {Function=} callback - Callback 131 | * @function 132 | * 133 | * @public 134 | */ 135 | let openFile = function(title, fileType, folder, callback = () => {}) { 136 | let dialogTitle = title || appProductName; 137 | let initialFolder = folder || app.getPath(name); 138 | 139 | let fileTypes = { 140 | image: ['jpg', 'jpeg', 'bmg', 'png', 'tif'], 141 | audio: ['aiff', 'm4a', 'mp3', 'mp4', 'wav'] 142 | }; 143 | 144 | 145 | if (!fileTypes[fileType]) { 146 | return; 147 | } 148 | 149 | logger.debug('initialFolder', initialFolder); 150 | logger.debug('dialogTitle', dialogTitle); 151 | logger.debug('title', title); 152 | logger.debug('fileType', fileType); 153 | 154 | 155 | dialog.showOpenDialog({ 156 | title: dialogTitle, 157 | properties: ['openFile', 'showHiddenFiles'], 158 | defaultPath: initialFolder, 159 | filters: [{ name: 'Sound', extensions: fileTypes[fileType] }] 160 | }, (filePath) => { 161 | 162 | if (!filePath) { 163 | logger.error('showOpenDialog', 'filepath required'); 164 | return callback(new Error(`Filepath missing`)); 165 | } 166 | 167 | validateFileType(filePath, fileType, function(err, filePath) { 168 | if (err) { 169 | return displayDialog(`Incompatible file.${os.EOL}`, `Compatible formats are: ${fileTypes[fileType]}`, ['Dismiss'], false, () => { 170 | logger.error('validateFileType', err); 171 | callback(new Error(`File content error: ${filePath}`)); 172 | }); 173 | } 174 | 175 | callback(null, filePath); 176 | }); 177 | }); 178 | }; 179 | 180 | /** 181 | * Yes/No 182 | * @param {String=} title - Title 183 | * @param {String=} message - Error Message 184 | * @param {Function=} callback - Callback 185 | * @function 186 | * 187 | * @public 188 | */ 189 | let showQuestion = function(title, message, callback = () => {}) { 190 | app.focus(); 191 | 192 | return displayDialog(title, message, ['Yes', 'No'], false, callback); 193 | }; 194 | 195 | /** 196 | * Error 197 | * @param {String} message - Error Message 198 | * @param {Function=} callback - Callback 199 | * @function 200 | * 201 | * @public 202 | */ 203 | let showError = function(message, callback = () => {}) { 204 | // Add Quit button 205 | callback = (result) => { 206 | if (result === 2) { return app.quit(); } 207 | return callback; 208 | }; 209 | 210 | if (platformHelper.isMacOS) { 211 | app.dock.bounce('critical'); 212 | } 213 | 214 | app.focus(); 215 | 216 | return displayDialog('Error', message, ['Cancel', 'OK', `Quit ${appProductName}`], true, callback); 217 | }; 218 | 219 | 220 | /** 221 | * @exports 222 | */ 223 | module.exports = { 224 | openFile: openFile, 225 | showError: showError, 226 | showInfo: showInfo, 227 | showQuestion: showQuestion 228 | }; 229 | -------------------------------------------------------------------------------- /lib/dom-helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const fs = require('fs'); 10 | const path = require('path'); 11 | 12 | /** 13 | * Modules 14 | * External 15 | * @constant 16 | */ 17 | const appRootPath = require('app-root-path')['path']; 18 | const fileUrl = require('file-url'); 19 | 20 | /** 21 | * Modules 22 | * Internal 23 | * @constant 24 | */ 25 | const logger = require(path.join(appRootPath, 'lib', 'logger'))({ write: true }); 26 | const language = require(path.join(appRootPath, 'lib', 'language')); 27 | 28 | 29 | /** 30 | * Add platform name as class to elements 31 | * @param {String=} element - Element (Default: ) 32 | */ 33 | let addPlatformClass = (element) => { 34 | let elementName = element || 'html'; 35 | let elementTarget = document.querySelector(elementName); 36 | 37 | // Add nodejs platform name 38 | elementTarget.classList.add(process.platform); 39 | 40 | // Add readable platform name 41 | switch (process.platform) { 42 | case 'darwin': 43 | elementTarget.classList.add('macos'); 44 | elementTarget.classList.add('osx'); 45 | break; 46 | case 'win32': 47 | elementTarget.classList.add('windows'); 48 | elementTarget.classList.add('win'); 49 | break; 50 | case 'linux': 51 | elementTarget.classList.add('unix'); 52 | break; 53 | } 54 | }; 55 | 56 | /** 57 | * Check if Object is an HTML Element 58 | * @param {*} object - Object 59 | * @returns {Boolean} - Type 60 | */ 61 | let isHtmlElement = (object) => { 62 | return language.getPrototypes(object).indexOf('HTMLElement') === 1; 63 | }; 64 | 65 | /** 66 | * Load external scripts 67 | * @param {String} filePath - Path to JavaScript 68 | */ 69 | let loadScript = (filePath) => { 70 | let url = fileUrl(filePath); 71 | 72 | let script = document.createElement('script'); 73 | script.src = url; 74 | script.type = 'text/javascript'; 75 | 76 | script.onload = () => { 77 | console.debug('dom-helper', 'loadScript', 'complete', url); 78 | }; 79 | 80 | document.getElementsByTagName('head')[0].appendChild(script); 81 | }; 82 | 83 | /** 84 | * Load external stylesheets 85 | * @param {String} filePath - Path to CSS 86 | */ 87 | let loadStylesheet = (filePath) => { 88 | let url = fileUrl(filePath); 89 | 90 | let link = document.createElement('link'); 91 | link.href = url; 92 | link.type = 'text/css'; 93 | link.rel = 'stylesheet'; 94 | 95 | link.onload = () => { 96 | console.debug('dom-helper', 'loadStylesheet', 'complete', url); 97 | }; 98 | 99 | document.getElementsByTagName('head')[0].appendChild(link); 100 | }; 101 | 102 | /** 103 | * Set element text content 104 | * @param {HTMLElement} element - Element 105 | * @param {String} text - Text 106 | * @param {Number=} delay - Delay 107 | */ 108 | let setText = (element, text = '', delay = 0) => { 109 | let timeout = setTimeout(() => { 110 | element.innerText = text; 111 | clearTimeout(timeout); 112 | }, delay); 113 | }; 114 | 115 | /** 116 | * Set element visibility 117 | * @param {HTMLElement} element - Element 118 | * @param {Boolean} visible - Show or hide 119 | * @param {Number=} delay - Delay 120 | */ 121 | let setVisibility = (element, visible, delay = 0) => { 122 | let timeout = setTimeout(() => { 123 | if (visible) { 124 | element.classList.add('show'); 125 | element.classList.remove('hide'); 126 | } else { 127 | element.classList.add('hide'); 128 | element.classList.remove('show'); 129 | } 130 | clearTimeout(timeout); 131 | }, delay); 132 | }; 133 | 134 | /** 135 | * Hide element 136 | * @param {HTMLElement} element - Element 137 | * @param {Number=} delay - Delay 138 | */ 139 | let show = (element, delay = 0) => { 140 | setVisibility(element, true, delay); 141 | }; 142 | 143 | /** 144 | * Show element 145 | * @param {HTMLElement} element - Element 146 | * @param {Number=} delay - Delay 147 | */ 148 | let hide = (element, delay = 0) => { 149 | setVisibility(element, false, delay); 150 | }; 151 | 152 | /** 153 | * Inject CSS 154 | * @param {Electron.WebViewElement|HTMLElement|Electron.WebContents} webview - Electron Webview 155 | * @param {String} filepath - Stylesheet filepath 156 | * @param {Function=} callback - Callback Function 157 | */ 158 | let injectCSS = (webview, filepath, callback = () => {}) => { 159 | //logger.debug('injectStylesheet'); 160 | 161 | fs.readFile(filepath, (err, data) => { 162 | if (err) { 163 | logger.error('injectStylesheet', err); 164 | return callback(err); 165 | } 166 | 167 | webview.insertCSS(data.toString()); 168 | 169 | callback(null, filepath); 170 | }); 171 | }; 172 | 173 | /** 174 | * Adds #removeEventListener to Events 175 | */ 176 | EventTarget.prototype.addEventListenerBase = EventTarget.prototype.addEventListener; 177 | EventTarget.prototype.addEventListener = function(type, listener) { 178 | if (!this.EventList) { this.EventList = []; } 179 | this.addEventListenerBase.apply(this, arguments); 180 | if (!this.EventList[type]) { this.EventList[type] = []; } 181 | const list = this.EventList[type]; 182 | for (let index = 0; index !== list.length; index++) { 183 | if (list[index] === listener) { return; } 184 | } 185 | list.push(listener); 186 | }; 187 | EventTarget.prototype.removeEventListenerBase = EventTarget.prototype.removeEventListener; 188 | EventTarget.prototype.removeEventListener = function(type, listener) { 189 | if (!this.EventList) { this.EventList = []; } 190 | if (listener instanceof Function) { this.removeEventListenerBase.apply(this, arguments); } 191 | if (!this.EventList[type]) { return; } 192 | let list = this.EventList[type]; 193 | for (let index = 0; index !== list.length;) { 194 | const item = list[index]; 195 | if (!listener) { 196 | this.removeEventListenerBase(type, item); 197 | list.splice(index, 1); 198 | continue; 199 | } else if (item === listener) { 200 | list.splice(index, 1); 201 | break; 202 | } 203 | index++; 204 | } 205 | if (list.length === 0) { delete this.EventList[type]; } 206 | }; 207 | 208 | 209 | /** 210 | * @exports 211 | */ 212 | module.exports = { 213 | addPlatformClass: addPlatformClass, 214 | injectCSS: injectCSS, 215 | isHtmlElement: isHtmlElement, 216 | loadScript: loadScript, 217 | loadStylesheet: loadStylesheet, 218 | setText: setText, 219 | setVisibility: setVisibility, 220 | show: show, 221 | hide: hide 222 | }; 223 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # E-Corp Live Wallpaper [![Beta](https://img.shields.io/badge/status-alpha-blue.svg?style=flat)]() [![travis](https://img.shields.io/travis/sidneys/live-wallpaper-ecorp.svg?style=flat)](https://travis-ci.org/sidneys/live-wallpaper-ecorp) [![appveyor](https://ci.appveyor.com/api/projects/status/oc57pq7hfslqg3ru?svg=true)](https://ci.appveyor.com/project/sidneys/live-wallpaper-ecorp) [![npm](https://img.shields.io/npm/v/live-wallpaper-ecorp.svg?style=flat)](https://npmjs.com/package/live-wallpaper-ecorp) [![dependencies](https://img.shields.io/david/sidneys/live-wallpaper-ecorp.svg?style=flat-square)](https://npmjs.com/package/live-wallpaper-ecorp) [![devDependencies](https://img.shields.io/david/dev/sidneys/live-wallpaper-ecorp.svg?style=flat-square)](https://npmjs.com/package/live-wallpaper-ecorp) 2 | 3 |

4 | Animated Wallpaper featuring a glitched E-Corp company logo,
5 | Known as Evil Corp from Mr. Robot.

6 |

7 | Not affiliated with USA Network, Anonymous Content, Universal Cable Productions or NBC Universal Television Distribution. 8 |

9 | 10 | ------ 11 | 12 | 13 | ## Contents 14 | 15 | 1. [TV Show](#tv-show) 16 | 1. [Installation](#installation) 17 | 1. [Developers](#development) 18 | 1. [Continuous Integration](#continuous-integration) 19 | 1. [Up Next](#up-next) 20 | 1. [Contact](#contact) 21 | 1. [Author](#author) 22 | 23 | 24 | ## The Show 25 | 26 | 27 | [whoismrrobot.com](https://www.whoismrrobot.com) 28 | 29 | 30 | ## Installation 31 | 32 | ### Standard Installation 33 | 34 | Download the latest version of E-Corp Live Wallpaper on the [Releases](https://github.com/sidneys/live-wallpaper-ecorp/releases) page. 35 | 36 | ### Installation as Commandline Tool 37 | 38 | ```bash 39 | npm install --global live-wallpaper-ecorp # Installs the node CLI module 40 | live-wallpaper-ecorp # Runs it 41 | ``` 42 | 43 | 44 | ## Developers 45 | 46 | ### Sources 47 | 48 | Clone the repo and install dependencies. 49 | 50 | ```shell 51 | git clone https://github.com/sidneys/live-wallpaper-ecorp.git live-wallpaper-ecorp 52 | cd live-wallpaper-ecorp 53 | npm install 54 | ``` 55 | 56 | ### Scripts 57 | 58 | #### npm run **start** 59 | 60 | Run the app with integrated Electron. 61 | 62 | ```bash 63 | npm run start 64 | npm run start:dev # with Debugging Tools 65 | npm run start:livereload # with Debugging Tools and Livereload 66 | ``` 67 | 68 | #### npm run **localsetup** 69 | 70 | Install the app in the System app folder and start it. 71 | 72 | ```bash 73 | npm run localsetup 74 | npm run localsetup:rebuild # Build before installation 75 | npm run localsetup:rebuild:dev # Build before installation, use Developer Tools 76 | ``` 77 | 78 | #### npm run **build** 79 | 80 | Build the app and create installers (see [requirements](#build-requirements)). 81 | 82 | ```bash 83 | npm run build # build all available platforms 84 | npm run build macos windows # build specific platforms (macos/linux/windows) 85 | ``` 86 | 87 | ### Build Requirements 88 | 89 | * Building for Windows requires [`wine`](https://winehq.org) and [`mono`](https://nsis.sourceforge.net/Docs/Chapter3.htm) (on macOS, Linux) 90 | * Building for Linux requires [`fakeroot`](https://wiki.debian.org/FakeRoot) and [`dpkg `](https://wiki.ubuntuusers.de/dpkg/) (on macOS, Windows) 91 | * Only macOS can build for other platforms. 92 | 93 | #### macOS Build Setup 94 | 95 | Install [Homebrew](https://brew.sh), then run: 96 | 97 | ```bash 98 | brew install wine mono fakeroot dpkg 99 | ``` 100 | 101 | #### Linux Build Setup 102 | 103 | ```bash 104 | sudo apt-get install wine mono fakeroot dpkg 105 | ``` 106 | 107 | 108 | ## Continuous Integration 109 | 110 | > Turnkey **build-in-the-cloud** for Windows 10, macOS and Linux. 111 | 112 | The process is managed by a custom layer of node scripts and Electron-optimized configuration templates. 113 | Completed Installation packages are deployed to [GitHub Releases](https://github.com/sidneys/live-wallpaper-ecorp/releases). Builds for all platforms and architectures take about 5 minutes. 114 | Backed by the open-source-friendly guys at [Travis](https://travis-ci.org/) and [AppVeyor](https://ci.appveyor.com/) and running [electron-packager](https://github.com/electron-userland/electron-packager) under the hood. 115 | 116 | ### Setup 117 | 118 | 1. [Fork](https://github.com/sidneys/live-wallpaper-ecorp/fork) the repo 119 | 2. Generate your GitHub [Personal Access Token](https://github.com/settings/tokens) using "repo" as scope. Copy it to the clipboard. 120 | 3. **macOS + Linux** 121 | 1. Sign in to [Travis](https://travis-ci.org/) using GitHub. 122 | 2. Open your [Travis Profile](https://travis-ci.org/profile), click "Sync Account" and wait for the process to complete. 123 | 3. Find this repository in the list, enable it and click "⚙" to open its settings. 124 | 4. Create a new Environment Variable named **GITHUB_TOKEN**. Paste your Token from step 2 as *value*. 125 | 4. **Windows** 126 | 1. Sign in to [AppVeyor](https://ci.appveyor.com/) using GitHub. 127 | 2. Click on ["New Project"](https://ci.appveyor.com/projects/new), select "GitHub", look up this repo in the list and click "Add". 128 | 3. After import navigate to the *Settings* > *Environment* subsection 129 | 4. Select "Add Variable", insert **GITHUB_TOKEN** for *name*, paste your Token as *value*. Save. 130 | 131 | ### Triggering Builds 132 | 133 | 1. Add a new Tag to start the build process: 134 | 135 | ```shell 136 | git tag -a v1.0.1 137 | git push --tags 138 | ``` 139 | The builds are started in parallel and added to the "Releases" page of the GitHub repo (in draft mode). 140 | 141 | 2. Use the editing feature to publish the new app version. 142 | 143 | 3. There is no step 3 144 | 145 | 146 | ## Up Next ![img](https://img.shields.io/badge/proposals-welcome-green.svg?style=flat) 147 | 148 | ### Windows Version 149 | At time of print, wallpaper apps - which are in essence Desktop applications claiming a special UI layer between the icon- and wallpaper space - are not readily implementable using the [Electron framework](http://electron.atom.io), due to current limitations of the [BrowserWindow](https://github.com/electron/electron/blob/master/docs/api/browser-window.md) API with regards to the Windows platform. 150 | 151 | If this status quo changes, so will this application. 152 | 153 | 154 | ## Contact ![Contributions Wanted](https://img.shields.io/badge/contributions-wanted-red.svg?style=flat) 155 | 156 | * [Gitter](https://gitter.im/sidneys/live-wallpaper-ecorp) Developer Chat 157 | * [Issues](https://github.com/sidneys/live-wallpaper-ecorp/issues) File, track and discuss features and issues 158 | * [Wiki](https://github.com/sidneys/live-wallpaper-ecorp/wiki) Read or contribute to the project Wiki 159 | 160 | 161 | ## Author 162 | 163 | [sidneys](https://sidneys.github.io) 2017 164 | -------------------------------------------------------------------------------- /lib/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const child_process = require('child_process'); 10 | const fs = require('fs-extra'); 11 | const path = require('path'); 12 | 13 | /** 14 | * Modules 15 | * External 16 | * @constant 17 | */ 18 | const _ = require('lodash'); 19 | const appRootPath = require('app-root-path')['path']; 20 | const hashFiles = require('hash-files'); 21 | const minimist = require('minimist'); 22 | const YAML = require('yamljs'); 23 | 24 | /** 25 | * Modules 26 | * Internal 27 | * @constant 28 | */ 29 | const deploy = require(path.join(appRootPath, 'lib', 'deploy')).github; 30 | const logger = require(path.join(appRootPath, 'lib', 'logger'))({ timestamp: false }); 31 | const packageJson = require(path.join(appRootPath, 'package.json')); 32 | const platformHelper = require(path.join(appRootPath, 'lib', 'platform-helper')); 33 | const releasenotes = require(path.join(appRootPath, 'lib', 'releasenotes')); 34 | 35 | 36 | /** 37 | * Filesystem 38 | * @constant 39 | * @default 40 | */ 41 | const builderPath = path.join(appRootPath, 'node_modules', '.bin', 'build'); 42 | 43 | /** 44 | * GitHub 45 | * @const 46 | * @default 47 | */ 48 | const artifactsDirectory = process.env.DEPLOY_DIRECTORY || path.join(appRootPath, packageJson.build.directories.output); 49 | 50 | /** 51 | * GitHub 52 | * @const 53 | * @default 54 | */ 55 | const defaults = { 56 | metadata: process.env.BUILD_METADATA, 57 | releasenotes: process.env.BUILD_RELEASENOTES 58 | }; 59 | 60 | 61 | /** 62 | * Renames installers to -- 63 | */ 64 | let renameArtifacts = () => { 65 | logger.debug('renameArtifacts'); 66 | 67 | // Get list of artifacts 68 | let artifactsList = deploy.getArtifacts(); 69 | 70 | // Rename 71 | artifactsList.forEach((currentFilepath) => { 72 | let currentExtension = path.extname(currentFilepath); 73 | let currentDirectory = path.dirname(currentFilepath); 74 | 75 | let newFilename = path.basename(currentFilepath, currentExtension); 76 | // convert: lowercase 77 | newFilename = _.toLower(newFilename); 78 | // replace: productName > name 79 | newFilename = _(newFilename).replace(packageJson.productName, packageJson.name); 80 | // remove: version 81 | // newFilename = _(newFilename).replace(packageJson.version, ''); 82 | // replace: whitespace -> underscore -> hyphen 83 | newFilename = _(newFilename).replace(/ /g, '_'); 84 | newFilename = _(newFilename).replace(/_/g, '-'); 85 | // remove: consecutive special characters 86 | newFilename = _(newFilename).replace(/\s\s+/g, ' '); 87 | newFilename = _(newFilename).replace(/[-]+/g, '-'); 88 | newFilename = _(newFilename).replace(/[_]+/g, '_'); 89 | // trim: special characters 90 | newFilename = _(newFilename).trim(' '); 91 | newFilename = _(newFilename).trim('-'); 92 | newFilename = _(newFilename).trim('_'); 93 | 94 | let newFilepath = path.join(currentDirectory, `${newFilename}${currentExtension}`); 95 | 96 | if (currentFilepath !== newFilepath) { 97 | fs.renameSync(currentFilepath, newFilepath); 98 | logger.debug('renamed installer', `'${path.basename(currentFilepath)}' --> '${newFilename}${currentExtension}'`); 99 | } 100 | }); 101 | }; 102 | 103 | /** 104 | * Generates Release Metadata (latest-mac.json, latest.yml) 105 | */ 106 | let writeMetadata = () => { 107 | logger.debug('writeMetadata'); 108 | 109 | const artifactsList = deploy.getArtifacts(); 110 | 111 | if (artifactsList.length === 0) { 112 | logger.error('writeMetadata', 'no artifacts found'); 113 | return; 114 | } 115 | 116 | artifactsList.forEach((artifactFilepath) => { 117 | /** 118 | * latest-mac.json 119 | */ 120 | if (artifactFilepath.includes('-mac.zip')) { 121 | const content = { 122 | releaseDate: new Date().toISOString(), 123 | releaseName: deploy.defaults.releaseName, 124 | releaseNotes: releasenotes.getLatest(), 125 | url: `https://github.com/${deploy.defaults.owner}/${deploy.defaults.repository}/releases/download/v${packageJson.version}/${path.basename(artifactFilepath)}`, 126 | version: packageJson.version 127 | }; 128 | 129 | const file = path.join(artifactsDirectory, 'github', 'latest-mac.json'); 130 | 131 | fs.mkdirpSync(path.dirname(file)); 132 | fs.writeFileSync(file, JSON.stringify(content, null, 2)); 133 | 134 | logger.debug('writeMetadata', path.relative(appRootPath, file)); 135 | } 136 | 137 | /** 138 | * latest.yml 139 | */ 140 | if (path.extname(artifactFilepath) === '.exe') { 141 | const content = { 142 | githubArtifactName: path.basename(artifactFilepath), 143 | path: path.basename(artifactFilepath), 144 | releaseDate: new Date().toISOString(), 145 | releaseName: deploy.defaults.releaseName, 146 | releaseNotes: releasenotes.getLatest(), 147 | sha2: hashFiles.sync({ algorithm: 'sha256', files: [artifactFilepath] }), 148 | version: packageJson.version 149 | }; 150 | 151 | const file = path.join(artifactsDirectory, 'latest.yml'); 152 | 153 | fs.mkdirpSync(path.dirname(file)); 154 | fs.writeFileSync(file, YAML.stringify(content, 2)); 155 | 156 | logger.debug('writeMetadata', path.relative(appRootPath, file)); 157 | } 158 | }); 159 | }; 160 | 161 | 162 | /** 163 | * Main, wraps electron-builder 164 | * @param {Array=} targetList - Platforms to build 165 | */ 166 | let buildPlatform = (targetList) => { 167 | logger.debug('main'); 168 | 169 | targetList.forEach((targetName) => { 170 | logger.info('building platform', targetName); 171 | 172 | targetName = targetName === 'darwin' ? 'macos' : targetName; 173 | targetName = targetName === 'win32' ? 'windows' : targetName; 174 | child_process.execSync(`${builderPath} --${targetName} --ia32 --x64`, { cwd: appRootPath, stdio: [0, 1, 2] }); 175 | }); 176 | 177 | // Rename 178 | logger.info('renaming artifacts', `for ${targetList.length} platforms`); 179 | renameArtifacts(); 180 | }; 181 | 182 | 183 | /** 184 | * Init 185 | * @param {Boolean=} metadata - Generate latest-mac.json, latest.yml 186 | * @param {Boolean=} releasenotes - Update release notes 187 | */ 188 | if (require.main === module) { 189 | let argv; 190 | let targetList = []; 191 | 192 | try { 193 | argv = minimist(JSON.parse(process.env.npm_config_argv).original, { 194 | 'boolean': ['macos', 'windows', 'linux', 'metadata', 'releasenotes'], 195 | 'unknown': () => { return false; } 196 | }); 197 | 198 | targetList = Object.keys(argv).filter((prop) => { 199 | return argv[prop] === true && prop !== 'metadata' && prop !== 'releasenotes'; 200 | }); 201 | 202 | targetList = targetList.length === 0 ? [platformHelper.type] : targetList; 203 | } catch (err) {} 204 | 205 | buildPlatform(targetList); 206 | 207 | if (argv['metadata'] || !!defaults.metadata) { 208 | logger.info('generating metadata', `for ${targetList.length} platforms`); 209 | writeMetadata(); 210 | } 211 | 212 | if (argv['releasenotes'] || !!defaults.releasenotes) { 213 | logger.info('updating release notes'); 214 | releasenotes.update(); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /app/scripts/main/services/updater-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const os = require('os'); 10 | const path = require('path'); 11 | 12 | /** 13 | * Modules 14 | * Electron 15 | * @constant 16 | */ 17 | const electron = require('electron'); 18 | const { app, BrowserWindow } = electron || electron.remote; 19 | 20 | /** 21 | * Modules 22 | * External 23 | * @constant 24 | */ 25 | const appRootPath = require('app-root-path')['path']; 26 | const semverCompare = require('semver-compare'); 27 | const { autoUpdater } = require('electron-updater'); 28 | 29 | /** 30 | * Modules 31 | * Internal 32 | * @constant 33 | */ 34 | const isDebug = require(path.join(appRootPath, 'lib', 'is-env'))('debug'); 35 | const logger = require(path.join(appRootPath, 'lib', 'logger'))({ write: true }); 36 | const messengerService = require(path.join(appRootPath, 'app', 'scripts', 'main', 'services', 'messenger-service')); 37 | const packageJson = require(path.join(appRootPath, 'package.json')); 38 | const platformHelper = require(path.join(appRootPath, 'lib', 'platform-helper')); 39 | const configurationManager = require(path.join(appRootPath, 'app', 'scripts', 'main', 'managers', 'configuration-manager')); 40 | const notificationService = require(path.join(appRootPath, 'app', 'scripts', 'main', 'services', 'notification-service')); 41 | 42 | /** 43 | * Application 44 | * @constant 45 | * @default 46 | */ 47 | const appProductName = packageJson.productName || packageJson.name; 48 | const appVersion = packageJson.version; 49 | 50 | 51 | /** 52 | * @instance 53 | */ 54 | let updaterService; 55 | 56 | /** 57 | * Updater 58 | * @returns autoUpdater 59 | * @class 60 | */ 61 | class Updater { 62 | constructor() { 63 | if (platformHelper.isLinux) { return; } 64 | 65 | this.init(); 66 | } 67 | 68 | init() { 69 | logger.debug('init'); 70 | 71 | // Extend stateful 72 | autoUpdater.isUpdating = false; 73 | 74 | // Set Logger 75 | autoUpdater.logger = logger; 76 | 77 | /** 78 | * @listens AutoUpdater#error 79 | */ 80 | autoUpdater.on('error', (error) => { 81 | logger.error('autoUpdater#error', error.message); 82 | 83 | autoUpdater.isUpdating = false; 84 | }); 85 | 86 | /** 87 | * @listens AutoUpdater#checking-for-update 88 | */ 89 | autoUpdater.on('checking-for-update', () => { 90 | logger.info('autoUpdater#checking-for-update'); 91 | 92 | autoUpdater.isUpdating = true; 93 | }); 94 | 95 | /** 96 | * @listens AutoUpdater#update-available 97 | */ 98 | autoUpdater.on('update-available', (info) => { 99 | logger.info('autoUpdater#update-available', info); 100 | 101 | autoUpdater.isUpdating = true; 102 | 103 | notificationService.show(`Update available for ${appProductName}`, { body: `Version: ${info.version}` }); 104 | }); 105 | 106 | /** 107 | * @listens AutoUpdater#update-not-available 108 | */ 109 | autoUpdater.on('update-not-available', (info) => { 110 | logger.info('autoUpdater#update-not-available', info); 111 | 112 | autoUpdater.isUpdating = false; 113 | }); 114 | 115 | /** 116 | * @listens AutoUpdater#download-progress 117 | */ 118 | autoUpdater.on('download-progress', (progress) => { 119 | logger.info('autoUpdater#download-progress', progress.percent); 120 | 121 | // Show update progress bar (Windows only) 122 | if (platformHelper.isWindows) { 123 | const win = BrowserWindow.getAllWindows()[0]; 124 | if (!win) { return; } 125 | 126 | win.setProgressBar(progress.percent / 100); 127 | } 128 | }); 129 | 130 | /** 131 | * @listens AutoUpdater#update-downloaded 132 | */ 133 | autoUpdater.on('update-downloaded', (info) => { 134 | logger.info('autoUpdater#update-downloaded', info); 135 | 136 | autoUpdater.isUpdating = true; 137 | 138 | notificationService.show(`Update ready to install for ${appProductName}`, { body: `Version: ${info.version}` }); 139 | 140 | if (Boolean(info.releaseNotes)) { 141 | configurationManager('releaseNotes').set(info.releaseNotes); 142 | logger.info('autoUpdater#update-downloaded', 'releaseNotes', info.releaseNotes); 143 | } 144 | 145 | messengerService.showQuestion( 146 | `Update successfully installed`, 147 | `${appProductName} has been updated successfully.${os.EOL}${os.EOL}` + 148 | `To apply the changes and complete the updating process, the app needs to be restarted.${os.EOL}${os.EOL}` + 149 | `Restart now?`, (response) => { 150 | if (response === 0) { 151 | BrowserWindow.getAllWindows().forEach((window) => { window.destroy(); }); 152 | autoUpdater.quitAndInstall(); 153 | } 154 | if (response === 1) { return true; } 155 | 156 | return true; 157 | }); 158 | }); 159 | 160 | autoUpdater.checkForUpdates(); 161 | 162 | return autoUpdater; 163 | } 164 | } 165 | 166 | 167 | /** 168 | * Updates internal version to current version 169 | * @function 170 | */ 171 | let bumpInternalVersion = () => { 172 | logger.debug('bumpInternalVersion'); 173 | 174 | let internalVersion = configurationManager('internalVersion').get(); 175 | 176 | // DEBUG 177 | logger.debug('bumpInternalVersion', 'packageJson.version', packageJson.version); 178 | logger.debug('bumpInternalVersion', 'internalVersion', internalVersion); 179 | logger.debug('bumpInternalVersion', 'semverCompare(packageJson.version, internalVersion)', semverCompare(packageJson.version, internalVersion)); 180 | 181 | // Initialize version 182 | if (!internalVersion) { 183 | configurationManager('internalVersion').set(packageJson.version); 184 | 185 | return; 186 | } 187 | 188 | // Compare internal/current version 189 | let wasUpdated = Boolean(semverCompare(packageJson.version, internalVersion) === 1); 190 | 191 | // DEBUG 192 | logger.debug('bumpInternalVersion', 'wasUpdated', wasUpdated); 193 | 194 | // Update internal version 195 | if (wasUpdated) { 196 | configurationManager('internalVersion').set(packageJson.version); 197 | 198 | const releaseNotes = configurationManager('releaseNotes').get(); 199 | 200 | if (Boolean(releaseNotes)) { 201 | messengerService.showInfo(`${appProductName} has been updated to ${appVersion}.`, `Release Notes:${os.EOL}${os.EOL}${releaseNotes}`); 202 | logger.info(`${appProductName} has been updated to ${appVersion}.`, `Release Notes:${os.EOL}${os.EOL}${releaseNotes}`); 203 | } else { 204 | messengerService.showInfo(`Update complete`, `${appProductName} has been updated to ${appVersion}.`); 205 | logger.info(`Update complete`, `${appProductName} has been updated to ${appVersion}.`); 206 | } 207 | 208 | notificationService.show(`Update complete for ${appProductName}`, { body: `Version: ${appVersion}` }); 209 | } 210 | }; 211 | 212 | 213 | /** 214 | * Init 215 | */ 216 | let init = () => { 217 | logger.debug('init'); 218 | 219 | // Only update if run from within purpose-built (signed) Electron binary 220 | if (process.defaultApp) { return; } 221 | 222 | updaterService = new Updater(); 223 | 224 | bumpInternalVersion(); 225 | }; 226 | 227 | 228 | /** 229 | * @listens Electron.App#browser-window-focus 230 | */ 231 | app.on('browser-window-focus', () => { 232 | logger.debug('app#browser-window-focus'); 233 | 234 | if (!updaterService) { return; } 235 | 236 | if (Boolean(updaterService.isUpdating) === false) { 237 | if (updaterService.checkForUpdates) { 238 | updaterService.checkForUpdates(); 239 | } 240 | } 241 | }); 242 | 243 | /** 244 | * @listens Electron.App#ready 245 | */ 246 | app.once('ready', () => { 247 | logger.debug('app#ready'); 248 | 249 | init(); 250 | }); -------------------------------------------------------------------------------- /lib/deploy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const path = require('path'); 10 | const util = require('util'); 11 | 12 | /** 13 | * Modules 14 | * External 15 | * @constant 16 | */ 17 | const gitBranch = require('git-branch'); 18 | const appRootPath = require('app-root-path')['path']; 19 | const glob = require('glob'); 20 | const isCi = require('is-ci'); 21 | const minimist = require('minimist'); 22 | const ProgressBar = require('progress'); 23 | const publishRelease = require('publish-release'); 24 | const _ = require('lodash'); 25 | 26 | /** 27 | * Modules 28 | * Internal 29 | * @constant 30 | */ 31 | const logger = require(path.join(appRootPath, 'lib', 'logger'))({ timestamp: false }); 32 | const packageJson = require(path.join(appRootPath, 'package.json')); 33 | const releasenotes = require(path.join(appRootPath, 'lib', 'releasenotes')); 34 | 35 | 36 | /** 37 | * GitHub 38 | * @const 39 | * @default 40 | */ 41 | const defaults = { 42 | branch: process.env.DEPLOY_BRANCH || process.env.TARGET_BRANCH || process.env.TRAVIS_BRANCH || process.env.APPVEYOR_REPO_BRANCH || gitBranch.sync(), 43 | directory: process.env.DEPLOY_DIRECTORY || path.join(appRootPath, packageJson.build.directories.output), 44 | releaseName: process.env.DEPLOY_RELEASENAME || packageJson.productName + ' v' + packageJson.version, 45 | releaseNotes: releasenotes.getLatest(), 46 | releaseTag: process.env.DEPLOY_TAG || 'v' + packageJson.version, 47 | owner: process.env.DEPLOY_OWNER || packageJson.author.name || packageJson.author, 48 | repository: process.env.DEPLOY_REPOSITORY || packageJson.name, 49 | token: process.env.DEPLOY_TOKEN || process.env.GITHUB_TOKEN || process.env.GH_TOKEN 50 | }; 51 | 52 | 53 | /** 54 | * Create list of absolute paths of build artifacts 55 | * @param {String=} directory - Directory containing assets 56 | * @returns {Array|undefined} List of absolute paths to files to be published 57 | */ 58 | let getArtifactsList = function(directory) { 59 | logger.debug('getArtifactsList'); 60 | 61 | const artifactsDirectory = directory || defaults.directory; 62 | 63 | if (!_.isString(artifactsDirectory)) { 64 | logger.error('getArtifactsList', 'artifactsDirectory', 'missing'); 65 | return; 66 | } 67 | 68 | let installerFilePathPattern = path.join(path.resolve(artifactsDirectory), '**', '*.{AppImage,deb,dmg,exe,json,pacman,rpm,yml,zip}'); 69 | let installerIgnorePatternList = [ 70 | path.join(path.resolve(artifactsDirectory), 'mac', '*.app', '**', '*'), 71 | path.join(path.resolve(artifactsDirectory), '*-unpacked', '**', '*') 72 | ]; 73 | 74 | const artifactsList = glob.sync(installerFilePathPattern, { ignore: installerIgnorePatternList }) || []; 75 | 76 | // DEBUG 77 | logger.debug('getArtifactsList', 'installerFilePathList', artifactsList); 78 | 79 | return artifactsList; 80 | }; 81 | 82 | /** 83 | * Get deploy defaults 84 | * @returns {Object} defaults 85 | */ 86 | let getDefaults = function() { 87 | logger.debug('getDefaults'); 88 | 89 | return defaults; 90 | }; 91 | 92 | /** 93 | * Create Release Configuration 94 | * @param {Array} artifacts - List of Artifacts 95 | * @param {String} token - GitHub Token 96 | * @param {String=} branch - Release Branch 97 | * @param {String=} name - Release Name 98 | * @param {String=} notes - Release Notes 99 | * @param {String=} tag - Release Tag 100 | * @returns {Object|void} - Configuration Object 101 | */ 102 | let getConfiguration = function(artifacts, token, branch, name, notes, tag) { 103 | logger.debug('getConfiguration'); 104 | 105 | const artifactList = artifacts || []; 106 | 107 | const releaseBranch = branch || defaults.branch; 108 | const githubToken = token || defaults.token; 109 | 110 | const releaseName = name || defaults.releaseName; 111 | const releaseNotes = notes || defaults.releaseNotes; 112 | const releaseTag = tag || defaults.releaseTag; 113 | 114 | const owner = defaults.owner; 115 | const repository = defaults.repository; 116 | 117 | if (artifactList.length === 0) { 118 | logger.error('getConfiguration', 'artifactList', 'empty'); 119 | return; 120 | } 121 | 122 | if (!_.isString(releaseBranch)) { 123 | logger.error('getConfiguration', 'releaseBranch', 'missing'); 124 | return; 125 | } 126 | 127 | if (!_.isString(githubToken)) { 128 | logger.error('getConfiguration', 'githubToken', 'missing'); 129 | return; 130 | } 131 | 132 | if (!_.isString(releaseNotes)) { 133 | logger.error('getConfiguration', 'releaseNotes', 'missing'); 134 | return; 135 | } 136 | 137 | if (!_.isObjectLike(packageJson)) { 138 | logger.error('getConfiguration', 'package', 'missing'); 139 | return; 140 | } 141 | 142 | let releaseConfiguration = { 143 | assets: artifactList, 144 | draft: true, 145 | name: releaseName, 146 | notes: releaseNotes, 147 | owner: owner, 148 | prerelease: false, 149 | repo: repository, 150 | reuseDraftOnly: false, 151 | reuseRelease: true, 152 | tag: releaseTag, 153 | target_commitish: releaseBranch, 154 | token: githubToken 155 | }; 156 | 157 | logger.debug('getConfiguration', util.inspect(releaseConfiguration)); 158 | 159 | return releaseConfiguration; 160 | }; 161 | 162 | /** 163 | * Add upload progress event handler 164 | * @param {PublishRelease} publishReleaseObject - PublishRelease object 165 | * @returns {Boolean|void} - Result of add event handlers 166 | */ 167 | let addProgressHandlers = (publishReleaseObject) => { 168 | logger.debug('addProgressHandlers'); 169 | 170 | if (!publishReleaseObject || !_.isObject(publishReleaseObject)) { 171 | logger.error('addProgressHandlers', 'release missing or wrong format.'); 172 | return; 173 | } 174 | 175 | let bar = {}; 176 | let uploaded = {}; 177 | 178 | // Upload started 179 | publishReleaseObject.on('upload-asset', function(fileName) { 180 | logger.info('release', `upload started: ${fileName}`); 181 | }); 182 | 183 | // Release created 184 | publishReleaseObject.on('created-release', () => { 185 | logger.info('release', 'created'); 186 | }); 187 | 188 | // Release reused 189 | publishReleaseObject.on('reuse-release', () => { 190 | logger.info('release', 're-using'); 191 | }); 192 | 193 | // Upload complete 194 | publishReleaseObject.on('uploaded-asset', function(fileName) { 195 | // Complete Progressbar 196 | if (bar[fileName]) { 197 | bar[fileName].update(1); 198 | } 199 | 200 | logger.info('release', `upload complete: ${fileName}`); 201 | }); 202 | 203 | // Upload progress update 204 | publishReleaseObject.on('upload-progress', function(fileName, event) { 205 | if (!uploaded[fileName]) { uploaded[fileName] = { size: 0, percentage: 0 }; } 206 | 207 | let currentPercentage = uploaded[fileName].percentage; 208 | 209 | uploaded[fileName].size += event.delta; 210 | uploaded[fileName].percentage = parseInt((uploaded[fileName].size / event.length) * 100); 211 | 212 | // Continuous Environment 213 | if (isCi) { 214 | if (currentPercentage !== uploaded[fileName].percentage) { 215 | logger.info('release', `uploading: ${fileName} (${uploaded[fileName].percentage} %)`); 216 | } 217 | return; 218 | } 219 | 220 | // Interactive Environment 221 | if (!bar[fileName]) { 222 | bar[fileName] = new ProgressBar(`'Release uploading: ${fileName} [:bar] :percent (ETA: :etas)`, { 223 | complete: 'x', 224 | incomplete: ' ', 225 | width: 30, 226 | total: event.length, 227 | clear: true 228 | }); 229 | return; 230 | } 231 | 232 | if (!bar[fileName].complete) { 233 | bar[fileName].tick(event.delta); 234 | } 235 | }); 236 | }; 237 | 238 | /** 239 | * Deploys all Release artifacts 240 | * @param {String=} directory - Directory containing build artifacts 241 | * @param {String=} token - Github Token 242 | * @param {String=} branch - Target Branch 243 | * @param {String=} name - Release Name 244 | * @param {String=} notes - Release Notes 245 | * @param {String=} tag - Release Tag 246 | * @param {Function=} callback - Callback 247 | */ 248 | let deployRelease = function(directory, token, branch, name, notes, tag, callback) { 249 | logger.debug('deployRelease'); 250 | 251 | let cb = callback || function() {}; 252 | 253 | // Create list of artifacts 254 | let artifactsList = getArtifactsList(directory); 255 | if (artifactsList.length === 0) { 256 | cb(new Error('artifactsList empty.')); 257 | return; 258 | } 259 | 260 | // Create Configuration 261 | let releaseConfiguration = getConfiguration(artifactsList, token, branch, name, notes, tag); 262 | if (!releaseConfiguration) { 263 | cb(new Error('releaseConfiguration missing.')); 264 | return; 265 | } 266 | 267 | // Call publish-release module 268 | let publishReleaseHandler = publishRelease(releaseConfiguration, function(err, result) { 269 | if (err) { 270 | cb(err); 271 | return; 272 | } 273 | 274 | cb(null, result); 275 | }); 276 | 277 | // Add progress handlers 278 | addProgressHandlers(publishReleaseHandler); 279 | }; 280 | 281 | /** 282 | * Main 283 | * 284 | * @public 285 | */ 286 | let main = () => { 287 | logger.debug('main'); 288 | 289 | const argv = minimist(process.argv.slice(2)); 290 | argv.directory = argv._[0]; 291 | argv.token = argv._[1]; 292 | argv.branch = argv._[2]; 293 | argv.name = argv._[3]; 294 | argv.notes = argv._[4]; 295 | argv.tag = argv._[5]; 296 | 297 | deployRelease(argv.directory, argv.token, argv.branch, argv.name, argv.notes, argv.tag, function(err, result) { 298 | if (err) { 299 | logger.error(err); 300 | return process.exit(1); 301 | } 302 | 303 | logger.log('release complete'); 304 | logger.debug('result', result); 305 | 306 | process.exit(0); 307 | }); 308 | }; 309 | 310 | 311 | /** 312 | * Main 313 | */ 314 | if (require.main === module) { 315 | main(); 316 | } 317 | 318 | 319 | /** 320 | * @exports 321 | */ 322 | module.exports = { 323 | github: { 324 | defaults: getDefaults(), 325 | getConfiguration: getConfiguration, 326 | getArtifacts: getArtifactsList, 327 | release: deployRelease 328 | } 329 | }; 330 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const fs = require('fs-extra'); 10 | const os = require('os'); 11 | const path = require('path'); 12 | const util = require('util'); 13 | 14 | /** 15 | * Modules 16 | * External 17 | * @constant 18 | */ 19 | const Appdirectory = require('appdirectory'); 20 | const chalk = require('chalk'); 21 | const present = require('present'); 22 | const _ = require('lodash'); 23 | 24 | /** 25 | * Modules 26 | * External 27 | * @constant 28 | */ 29 | const appRootPath = require('app-root-path')['path']; 30 | 31 | /** 32 | * Modules 33 | * Internal 34 | * @constant 35 | */ 36 | const isDebug = require(path.join(appRootPath, 'lib', 'is-env'))('debug'); 37 | const isNolog = require(path.join(appRootPath, 'lib', 'is-env'))('nolog'); 38 | const packageJson = require(path.join(appRootPath, 'package.json')); 39 | 40 | 41 | /** 42 | * Application 43 | * @constant 44 | * @default 45 | */ 46 | const appName = packageJson.name; 47 | 48 | /** 49 | * Filesystem 50 | * @constant 51 | * @default 52 | */ 53 | const appLogDirectory = (new Appdirectory(appName)).userLogs(); 54 | const appLogFile = path.join(appLogDirectory, appName + '.log'); 55 | 56 | 57 | /** 58 | * @constant 59 | * @default 60 | */ 61 | const typeToChalkStyle = { 62 | debug: 'cyan', 63 | error: 'red', 64 | information: 'magenta', 65 | log: 'cyan', 66 | warning: 'yellow' 67 | }; 68 | 69 | /** 70 | * @constant 71 | * @default 72 | */ 73 | let typeToRgb = { 74 | debug: [100, 100, 100], 75 | error: [230, 70, 50], 76 | information: [255, 100, 150], 77 | log: [0, 128, 255], 78 | warning: [200, 100, 30] 79 | }; 80 | 81 | /** 82 | * @constant 83 | * @default 84 | */ 85 | const typeEmojiMap = { 86 | debug: '🔧', 87 | error: '🚨', 88 | information: 'ℹ️', 89 | log: '📝', 90 | warning: '⚠️' 91 | }; 92 | 93 | 94 | /** 95 | * Log to console and file 96 | * @param {*} entry - Log Message 97 | */ 98 | let writeToFile = function(entry) { 99 | if (!module.exports.options.write || isNolog) { 100 | return; 101 | } 102 | 103 | const date = (new Date()); 104 | const dateString = date.toISOString().replace(/Z|T|\..+/gi, ' ').trim().split(' ').reverse().join(' '); 105 | const logEntry = '[' + dateString + '] ' + entry; 106 | 107 | // Create Directory 108 | fs.mkdirp(path.dirname(appLogFile), (err) => { 109 | if (err) { 110 | console.error('logger', 'writeToFile', 'fs.mkdirp', err); 111 | return; 112 | } 113 | // Append Log 114 | fs.appendFile(appLogFile, (`${logEntry}${os.EOL}`), (err) => { 115 | if (err) { 116 | console.error('logger', 'writeToFile', 'fs.appendFile', err); 117 | } 118 | }); 119 | }); 120 | }; 121 | 122 | /** 123 | * Format log messages 124 | * @returns {Object} 125 | */ 126 | let getParsedMessage = function() { 127 | const namespace = module.exports.options.namespace; 128 | const namespaceList = _.map(global[packageJson.name].namespaces, 'namespace'); 129 | const namespacePosition = namespaceList.indexOf(namespace); 130 | const namespaceThread = namespacePosition & 1; 131 | 132 | const timestamp = module.exports.options.timestamp; 133 | 134 | let messageList = Array.from(arguments); 135 | const type = messageList[0]; 136 | messageList.shift(); 137 | 138 | const chalkStyle = chalk[typeToChalkStyle[type]]; 139 | const indent = (process.type !== 'renderer') ? `i [${namespace}] `.length : `i [${namespace}] `.length; 140 | 141 | let body; 142 | let title; 143 | 144 | for (let index in messageList) { 145 | if (_.isObjectLike(messageList[index])) { 146 | if (_.isArray(messageList[index])) { 147 | messageList[index] = os.EOL + ' '.repeat(indent) + '[' + os.EOL + ' '.repeat(indent + 2) + messageList[index].join(',' + os.EOL + ' '.repeat(indent + 2)) + os.EOL + ' '.repeat(indent) + ']'; 148 | } else { 149 | messageList[index] = os.EOL + util.inspect(messageList[index], { 150 | depth: null, showProxy: true, showHidden: true 151 | }); 152 | messageList[index] = messageList[index].replace(new RegExp(os.EOL, 'gi'), `${os.EOL}${' '.repeat(indent)}`); 153 | } 154 | 155 | messageList[index - 1] = `${messageList[index - 1]}`; 156 | } 157 | } 158 | 159 | if (messageList.length > 1) { 160 | title = messageList[0]; 161 | messageList.shift(); 162 | } 163 | 164 | body = messageList.join(' '); 165 | 166 | // if there's no title, remove body 167 | if (!title) { title = body; } 168 | 169 | // consolidate title, body 170 | if (title === body) { body = ''; } 171 | 172 | return { 173 | chalk: chalkStyle, 174 | emoji: typeEmojiMap[type], 175 | body: body, 176 | namespace: namespace, 177 | rgb: typeToRgb[type].join(), 178 | timestamp: timestamp, 179 | title: title, 180 | type: _.toUpper(type), 181 | thread: namespaceThread 182 | }; 183 | }; 184 | 185 | 186 | /** 187 | * Print to BROWSER 188 | */ 189 | let printBrowserMessage = function() { 190 | if (arguments.length === 0) { return; } 191 | 192 | const parameters = getParsedMessage.apply(this, arguments); 193 | 194 | if (!this.previousLog) { 195 | this.previousLog = present(); 196 | } 197 | 198 | console.log.apply(this, [ 199 | `${parameters.emoji} %c[%s] %c %c%s%c %c%s%c %s`, 200 | //%c 201 | `background-color: rgba(${parameters.rgb}, 0.2); color: rgba(${parameters.rgb}, 0.8); padding: 0 0px; font-weight: normal`, 202 | //%s 203 | parameters.namespace, 204 | //%c 205 | '', 206 | //%c 207 | `background-color: rgba(${parameters.rgb}, 0.0); color: rgba(${parameters.rgb}, 1.0); padding: 0 0px; font-weight: bold`, 208 | //%s 209 | parameters.title, 210 | //%c 211 | '', 212 | //%c 213 | `background-color: rgba(${parameters.rgb}, 0.1); color: rgba(${parameters.rgb}, 1.0); padding: 0 0px; font-weight: normal`, 214 | //%s 215 | parameters.body, 216 | //%c 217 | `background-color: rgba(${parameters.rgb}, 0.0); color: rgba(${parameters.rgb}, 0.5); padding: 0 0px; font-weight: normal`, 218 | //%s 219 | parameters.timestamp ? ((present() - this.previousLog).toFixed(4) + ' ms') : '' 220 | ]); 221 | 222 | writeToFile(util.format( 223 | `[renderer] [${parameters.type}] [%s] %s %s`, 224 | parameters.namespace, parameters.title, parameters.body 225 | )); 226 | 227 | this.previousLog = present(); 228 | }; 229 | 230 | /** 231 | * Print TERMINAL 232 | * @param {...*} arguments - Error Messages or entities to print. 233 | */ 234 | let printTerminalMessage = function() { 235 | if (arguments.length === 0) { return; } 236 | 237 | const parameters = getParsedMessage.apply(this, arguments); 238 | 239 | if (process.type === 'renderer') { return printBrowserMessage.apply(this, arguments); } 240 | 241 | if (!this.previousLog) { 242 | this.previousLog = present(); 243 | } 244 | 245 | 246 | console.log(util.format( 247 | `${parameters.emoji} [%s] %s %s %s`, 248 | //[%s] 249 | parameters.thread ? parameters.chalk(parameters.namespace) : parameters.chalk.underline(parameters.namespace), 250 | //%s 251 | parameters.chalk.bold(parameters.title), 252 | //%s 253 | parameters.chalk(parameters.body), 254 | //%s 255 | parameters.timestamp ? (parameters.chalk.italic((present() - this.previousLog).toFixed(4) + ' ms')) : '' 256 | )); 257 | 258 | writeToFile(util.format( 259 | `[main] [${parameters.type}] [%s] %s %s`, 260 | parameters.namespace, parameters.title, parameters.body 261 | )); 262 | 263 | this.previousLog = present(); 264 | }; 265 | 266 | 267 | /** 268 | * Debug 269 | * @param {...*} arguments - Error Messages or entities to print. 270 | */ 271 | let printDebug = function() { 272 | const type = 'debug'; 273 | 274 | if (!isDebug) { return; } 275 | 276 | let args = Array.from(arguments); 277 | args.unshift(type); 278 | return printTerminalMessage.apply(this, args); 279 | }; 280 | 281 | /** 282 | * Error 283 | * @param {...*} arguments - Error Messages or entities to print. 284 | */ 285 | let printError = function() { 286 | const type = 'error'; 287 | 288 | let args = Array.from(arguments); 289 | args.unshift(type); 290 | return printTerminalMessage.apply(this, args); 291 | }; 292 | 293 | /** 294 | * Info 295 | * @param {...*} arguments - Error Messages or entities to print. 296 | */ 297 | let printInfo = function() { 298 | const type = 'information'; 299 | 300 | let args = Array.from(arguments); 301 | args.unshift(type); 302 | return printTerminalMessage.apply(this, args); 303 | }; 304 | 305 | /** 306 | * Log 307 | * @param {...*} arguments - Messages or entities to print. 308 | */ 309 | let printLog = function() { 310 | const type = 'log'; 311 | 312 | let args = Array.from(arguments); 313 | args.unshift(type); 314 | return printTerminalMessage.apply(this, args); 315 | }; 316 | 317 | /** 318 | * Warn 319 | * @param {...*} arguments - Error Messages or entities to print. 320 | */ 321 | let printWarning = function() { 322 | const type = 'warning'; 323 | 324 | let args = Array.from(arguments); 325 | args.unshift(type); 326 | return printTerminalMessage.apply(this, args); 327 | }; 328 | 329 | 330 | /** 331 | * @exports 332 | */ 333 | module.exports = (options) => { 334 | 335 | const file = (module.parent && module.parent.filename) || module.filename; 336 | const namespace = path.basename(file) || packageJson.name; 337 | 338 | // Instance Options 339 | let defaultOptions = { 340 | namespace: namespace, 341 | timestamp: true, 342 | write: false 343 | }; 344 | 345 | options = _.defaultsDeep(options, defaultOptions); 346 | module.exports.options = options; 347 | 348 | // Global Configuration 349 | global[packageJson.name] = { 350 | namespaces: [] 351 | }; 352 | 353 | global[packageJson.name].namespaces.push(options); 354 | 355 | // Remove filename from cache, enables detection of requiring module names 356 | delete require.cache[__filename]; 357 | 358 | return { 359 | browser: printBrowserMessage, 360 | debug: printDebug, 361 | error: printError, 362 | info: printInfo, 363 | information: printInfo, 364 | log: printLog, 365 | warn: printWarning, 366 | warning: printWarning 367 | }; 368 | }; 369 | -------------------------------------------------------------------------------- /app/scripts/main/managers/configuration-manager.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const fs = require('fs-extra'); 10 | const path = require('path'); 11 | const util = require('util'); 12 | 13 | /** 14 | * Modules 15 | * Electron 16 | * @constant 17 | */ 18 | const electron = require('electron'); 19 | const { remote } = electron; 20 | const app = electron.app ? electron.app : remote.app; 21 | const BrowserWindow = electron.BrowserWindow ? electron.BrowserWindow : remote.BrowserWindow; 22 | 23 | /** 24 | * Modules 25 | * External 26 | * @constant 27 | */ 28 | const _ = require('lodash'); 29 | const appRootPath = require('app-root-path')['path']; 30 | const Appdirectory = require('appdirectory'); 31 | const AutoLaunch = require('auto-launch'); 32 | const electronSettings = require('electron-settings'); 33 | 34 | /** 35 | * Modules 36 | * Internal 37 | * @constant 38 | */ 39 | const logger = require(path.join(appRootPath, 'lib', 'logger'))({ write: true }); 40 | const packageJson = require(path.join(appRootPath, 'package.json')); 41 | const platformHelper = require(path.join(appRootPath, 'lib', 'platform-helper')); 42 | const messengerService = require(path.join(appRootPath, 'app', 'scripts', 'main', 'services', 'messenger-service')); 43 | 44 | /** 45 | * Application 46 | * @constant 47 | * @default 48 | */ 49 | const appName = packageJson.name; 50 | const appVersion = packageJson.version; 51 | 52 | /** 53 | * Modules 54 | * Configuration 55 | */ 56 | let autoLauncher = new AutoLaunch({ name: appName, mac: { useLaunchAgent: true } }); 57 | 58 | /** 59 | * Filesystem 60 | * @constant 61 | * @default 62 | */ 63 | const appLogDirectory = (new Appdirectory(appName)).userLogs(); 64 | const appSoundDirectory = path.join(appRootPath, 'sounds').replace('app.asar', 'app.asar.unpacked'); 65 | 66 | /** 67 | * @constant 68 | * @default 69 | */ 70 | const defaultInterval = 1000; 71 | const defaultDebounce = 300; 72 | 73 | 74 | /** 75 | * Get Main Window 76 | * @returns {Electron.BrowserWindow} 77 | * @function 78 | */ 79 | let getPrimaryWindow = () => { 80 | logger.debug('getPrimaryWindow'); 81 | 82 | return BrowserWindow.getAllWindows()[0]; 83 | }; 84 | 85 | /** 86 | * Show app in menubar or task bar only 87 | * @param {Boolean} enable - True: show dock icon, false: hide icon 88 | */ 89 | let setWindowInTrayOnly = (enable) => { 90 | logger.debug('setWindowInTrayOnly'); 91 | 92 | let interval = setInterval(() => { 93 | const win = getPrimaryWindow(); 94 | if (!win) { return; } 95 | 96 | switch (platformHelper.type) { 97 | case 'darwin': 98 | if (enable) { 99 | app.dock.hide(); 100 | } else { app.dock.show(); } 101 | break; 102 | case 'win32': 103 | win.setSkipTaskbar(enable); 104 | break; 105 | case 'linux': 106 | win.setSkipTaskbar(enable); 107 | break; 108 | } 109 | 110 | clearInterval(interval); 111 | }, defaultInterval); 112 | }; 113 | 114 | /** 115 | * Configuration Items 116 | * @namespace 117 | */ 118 | let configurationItems = { 119 | /** 120 | * Application version 121 | * @readonly 122 | */ 123 | internalVersion: { 124 | keypath: 'internalVersion', 125 | default: appVersion, 126 | init() { 127 | logger.debug(this.keypath, 'init'); 128 | }, 129 | get() { 130 | logger.debug(this.keypath, 'get'); 131 | 132 | return electronSettings.get(this.keypath); 133 | }, 134 | set(value) { 135 | logger.debug(this.keypath, 'set'); 136 | 137 | electronSettings.set(this.keypath, value); 138 | } 139 | }, 140 | /** 141 | * Launch on startup 142 | */ 143 | launchOnStartup: { 144 | keypath: 'launchOnStartup', 145 | default: false, 146 | init() { 147 | logger.debug(this.keypath, 'init'); 148 | 149 | this.implement(this.get()); 150 | }, 151 | get() { 152 | logger.debug(this.keypath, 'get'); 153 | 154 | return electronSettings.get(this.keypath); 155 | }, 156 | set(value) { 157 | logger.debug(this.keypath, 'set', value); 158 | 159 | this.implement(value); 160 | electronSettings.set(this.keypath, value); 161 | }, 162 | implement(value) { 163 | logger.debug(this.keypath, 'implement', value); 164 | 165 | if (value) { 166 | autoLauncher.enable(); 167 | } else { 168 | autoLauncher.disable(); 169 | } 170 | } 171 | }, 172 | /** 173 | * Application log file 174 | * @readonly 175 | */ 176 | logFile: { 177 | keypath: 'logFile', 178 | default: path.join(appLogDirectory, appName + '.log'), 179 | init() { 180 | logger.debug(this.keypath, 'init'); 181 | }, 182 | get() { 183 | logger.debug(this.keypath, 'get'); 184 | 185 | return electronSettings.get(this.keypath); 186 | }, 187 | set(value) { 188 | logger.debug(this.keypath, 'set'); 189 | 190 | electronSettings.set(this.keypath, value); 191 | } 192 | }, 193 | /** 194 | * Application update release notes 195 | * @readonly 196 | */ 197 | releaseNotes: { 198 | keypath: 'releaseNotes', 199 | default: '', 200 | init() { 201 | logger.debug(this.keypath, 'init'); 202 | }, 203 | get() { 204 | logger.debug(this.keypath, 'get'); 205 | 206 | return electronSettings.get(this.keypath); 207 | }, 208 | set(value) { 209 | logger.debug(this.keypath, 'set'); 210 | 211 | electronSettings.set(this.keypath, value); 212 | } 213 | }, 214 | /** 215 | * Main Window visibility 216 | * @readonly 217 | */ 218 | windowVisible: { 219 | keypath: 'windowVisible', 220 | default: true, 221 | init() { 222 | logger.debug(this.keypath, 'init'); 223 | 224 | // Wait for main window 225 | let interval = setInterval(() => { 226 | const win = getPrimaryWindow(); 227 | if (!win) { return; } 228 | 229 | this.implement(this.get()); 230 | 231 | /** 232 | * @listens Electron.BrowserWindow#show 233 | */ 234 | win.on('show', () => { 235 | this.set(true); 236 | }); 237 | 238 | /** 239 | * @listens Electron.BrowserWindow#hide 240 | */ 241 | win.on('hide', () => { 242 | this.set(false); 243 | }); 244 | 245 | clearInterval(interval); 246 | }, defaultInterval); const winList = BrowserWindow.getAllWindows(); 247 | 248 | winList.forEach((win) => { 249 | if (value) { 250 | win.show(); 251 | } else { 252 | win.hide(); 253 | } 254 | }); 255 | }, 256 | get() { 257 | logger.debug(this.keypath, 'get'); 258 | 259 | return electronSettings.get(this.keypath); 260 | }, 261 | set(value) { 262 | logger.debug(this.keypath, 'set', value); 263 | 264 | this.implement(value); 265 | electronSettings.set(this.keypath, value); 266 | }, 267 | implement(value) { 268 | logger.debug(this.keypath, 'implement', value); 269 | 270 | const winList = BrowserWindow.getAllWindows(); 271 | 272 | winList.forEach((win) => { 273 | if (value) { 274 | win.show(); 275 | } else { 276 | win.hide(); 277 | } 278 | }); 279 | } 280 | } 281 | }; 282 | 283 | /** 284 | * Access single item 285 | * @returns {Object|void} 286 | * @function 287 | * 288 | * @public 289 | */ 290 | let getItem = (itemId) => { 291 | logger.debug('getConfigurationItem', itemId); 292 | 293 | if (configurationItems.hasOwnProperty(itemId)) { 294 | return configurationItems[itemId]; 295 | } 296 | }; 297 | 298 | /** 299 | * Get defaults of all items 300 | * @returns {Object} 301 | * @function 302 | */ 303 | let getConfigurationDefaults = () => { 304 | logger.debug('getConfigurationDefaults'); 305 | 306 | let defaults = {}; 307 | for (let item of Object.keys(configurationItems)) { 308 | defaults[item] = getItem(item).default; 309 | } 310 | 311 | return defaults; 312 | }; 313 | 314 | /** 315 | * Set defaults of all items 316 | * @returns {Object} 317 | * @function 318 | */ 319 | let setConfigurationDefaults = (callback = () => {}) => { 320 | logger.debug('setConfigurationDefaults'); 321 | 322 | let configuration = electronSettings.getAll(); 323 | let configurationDefaults = getConfigurationDefaults(); 324 | 325 | electronSettings.setAll(_.defaultsDeep(configuration, configurationDefaults)); 326 | 327 | callback(null); 328 | }; 329 | 330 | /** 331 | * Initialize all items – calling their init() method 332 | * @param {Function=} callback - Callback 333 | * @function 334 | */ 335 | let initializeItems = (callback = () => {}) => { 336 | logger.debug('initConfigurationItems'); 337 | 338 | let configurationItemList = Object.keys(configurationItems); 339 | 340 | configurationItemList.forEach((item, itemIndex) => { 341 | getItem(item).init(); 342 | 343 | // Last item 344 | if (configurationItemList.length === (itemIndex + 1)) { 345 | logger.debug('initConfigurationItems', 'complete'); 346 | callback(null); 347 | } 348 | }); 349 | }; 350 | 351 | /** 352 | * Remove unknown items 353 | * @param {Function=} callback - Callback 354 | * @function 355 | */ 356 | let removeLegacyItems = (callback = () => {}) => { 357 | logger.debug('cleanConfiguration'); 358 | 359 | let savedSettings = electronSettings.getAll(); 360 | let savedSettingsList = Object.keys(savedSettings); 361 | 362 | savedSettingsList.forEach((item, itemIndex) => { 363 | if (!configurationItems.hasOwnProperty(item)) { 364 | electronSettings.delete(item); 365 | logger.debug('cleanConfiguration', 'deleted', item); 366 | } 367 | 368 | // Last item 369 | if (savedSettingsList.length === (itemIndex + 1)) { 370 | logger.debug('cleanConfiguration', 'complete'); 371 | callback(null); 372 | } 373 | }); 374 | }; 375 | 376 | 377 | /** 378 | * @listens Electron.App#ready 379 | */ 380 | app.once('ready', () => { 381 | logger.debug('app#ready'); 382 | 383 | // Remove item unknown 384 | setConfigurationDefaults(() => { 385 | // Initialize items 386 | initializeItems(() => { 387 | // Set Defaults 388 | removeLegacyItems(() => { 389 | logger.debug('app#will-finish-launching', 'complete'); 390 | }); 391 | }); 392 | }); 393 | }); 394 | 395 | /** 396 | * @listens Electron.App#before-quit 397 | */ 398 | app.on('before-quit', () => { 399 | logger.debug('app#before-quit'); 400 | 401 | logger.info('settings', electronSettings.getAll()); 402 | logger.info('file', electronSettings.file()); 403 | }); 404 | 405 | /** 406 | * @exports 407 | */ 408 | module.exports = getItem; 409 | -------------------------------------------------------------------------------- /app/vendor/glitch.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license glitch.js v0.1 3 | Released under MIT License 4 | 5 | Copyright (c) 2012 Simon Hewitt. 6 | http://www.twitter.com/sjhewitt 7 | */ 8 | (function($){ 9 | /*global html2canvas */ 10 | 11 | var noop = function(){}, 12 | /** 13 | * Set default properties on an object 14 | * @param {Object} obj The target object 15 | * @param {Object} defaults The default properties 16 | * @return {Object} The target obj 17 | */ 18 | defaults = function(obj, defaults) { 19 | for (var prop in defaults) { 20 | if (obj[prop] == null) 21 | obj[prop] = defaults[prop]; 22 | } 23 | return obj; 24 | }, 25 | /** 26 | * Generates an integer between min and max 27 | * 28 | * @param {Number} min The lower bound 29 | * @param {Number} max The upper bound 30 | * @return {Number} A random number 31 | */ 32 | getRandInt = function(min, max) { 33 | return (Math.floor(Math.random() * (max - min) + min)); 34 | }; 35 | 36 | 37 | /** 38 | * Apply the glitch effect to a canvas object 39 | * 40 | * @param {HTMLCanvasElement} canvas The canvas (or HTMLImageElement) to apply the glitch to 41 | * @param {number} amount The amount to glitch the canvas (default: 6) 42 | * @return {HTMLCanvasElement} A canvas containing a glitched version 43 | * of the original canvas 44 | */ 45 | var _glitch = function(canvas, amount) { 46 | var 47 | // cache the width and height of the canvas locally 48 | x, y, w = canvas.width, h = canvas.height, 49 | 50 | // _len is an iterator limit, initially storing the number of slices 51 | // to create 52 | i, _len = amount || 6, 53 | 54 | // pick a random amount to offset the color channel 55 | channelOffset = (getRandInt(-_len*2, _len*2) * w * + getRandInt(-_len, _len)) * 4, 56 | 57 | // the maximum amount to offset a chunk of the image is a function of its width 58 | maxOffset = _len * _len / 100 * w, 59 | 60 | // vars for the width and height of the chunk that gets offset 61 | chunkWidth, chunkHeight, 62 | 63 | // create a temporary canvas to hold the image we're working on 64 | tempCanvas = document.createElement("canvas"), 65 | tempCtx = tempCanvas.getContext("2d"), 66 | 67 | srcData, targetImageData, data; 68 | 69 | // set the dimensions of the working canvas 70 | tempCanvas.width = w; 71 | tempCanvas.height = h; 72 | 73 | // draw the initial image onto the working canvas 74 | tempCtx.drawImage(canvas, 0, 0, w, h); 75 | 76 | // store the data of the original image for use when offsetting a channel 77 | srcData = tempCtx.getImageData(0, 0, w, h).data; 78 | 79 | // randomly offset slices horizontally 80 | for (i = 0; i < _len; i++) { 81 | 82 | // pick a random y coordinate to slice at 83 | y = getRandInt(0, h); 84 | 85 | // pick a random height of the slice 86 | chunkHeight = Math.min(getRandInt(1, h / 4), h - y); 87 | 88 | // pick a random horizontal distance to offset the slice 89 | x = getRandInt(1, maxOffset); 90 | chunkWidth = w - x; 91 | 92 | // draw the first chunk 93 | tempCtx.drawImage(canvas, 94 | 0, y, chunkWidth, chunkHeight, 95 | x, y, chunkWidth, chunkHeight); 96 | 97 | // draw the rest 98 | tempCtx.drawImage(canvas, 99 | chunkWidth, y, x, chunkHeight, 100 | 0, y, x, chunkHeight); 101 | } 102 | 103 | // get hold of the ImageData for the working image 104 | targetImageData = tempCtx.getImageData(0, 0, w, h); 105 | 106 | // and get a local reference to the rgba data array 107 | data = targetImageData.data; 108 | 109 | // Copy a random color channel from the original image into 110 | // the working canvas, offsetting it by a random amount 111 | // 112 | // ImageData arrays are a single dimension array that contains 113 | // 4 values for each pixel. 114 | // so, by initializing `i` to a random number between 0 and 2, 115 | // and incrementing by 4 on each iteration, we can replace only 116 | // a single channel in the image 117 | for(i = getRandInt(0, 3), _len = srcData.length; i < _len; i += 4) { 118 | data[i+channelOffset] = srcData[i]; 119 | } 120 | 121 | // Make the image brighter by doubling the rgb values 122 | for(i = 0; i < _len; i++) { 123 | data[i++] *= 2; 124 | data[i++] *= 2; 125 | data[i++] *= 2; 126 | } 127 | 128 | // TODO: The above loops are the most costly in this function, iterating 129 | // over all the pixels in the image twice. 130 | // It maybe possible to optimize this by combining both loops into one, 131 | // and only processing every other line, as alternate lines are replaced 132 | // with black in the 'scan lines' block belop 133 | 134 | // copy the tweaked ImageData back into the context 135 | tempCtx.putImageData(targetImageData, 0, 0); 136 | 137 | // add scan lines 138 | tempCtx.fillStyle = "rgb(0,0,0)"; 139 | for (i = 0; i < h; i += 2) { 140 | tempCtx.fillRect (0, i, w, 1); 141 | } 142 | 143 | return tempCanvas; 144 | }; 145 | 146 | /** 147 | * Creates a canvas containing a glitched version of the element 148 | * @param {DOMElement} el The element to glitch 149 | * @param {Object} options An object containing the complete callback, 150 | * the amount to glitch the image, and any 151 | * html2canvas options 152 | */ 153 | var glitch = function(el, options) { 154 | options = defaults(options || {}, { 155 | // the amount to glitch the image 156 | amount: 6, 157 | // a callback that takes the glitched canvas as its only argument 158 | complete: noop 159 | }); 160 | 161 | // callback for when the element has been rendered 162 | options.onrendered = function(canvas) { 163 | options.complete(_glitch(canvas, options.amount)); 164 | }; 165 | 166 | // render the element onto a canvas 167 | html2canvas(el[0] ? el : [el], options); 168 | }; 169 | 170 | /** 171 | * Replace el with a glitched version of it 172 | * @param {DOMElement} el The element to glitch 173 | * @param {Object} options An object containing the options for glitch 174 | */ 175 | glitch.replace = function(el, options) { 176 | options = options || {}; 177 | // store a reference to the complete callback so we can use the same 178 | // options for the glitch function call 179 | var _complete = options.complete; 180 | options.complete = function(canvas) { 181 | if($ && el instanceof $) { 182 | el.after(canvas).detach(); 183 | } else { 184 | // no jQuery... 185 | el.parentNode.insertBefore(canvas, el); 186 | el.parentNode.removeChild(el); 187 | } 188 | if(_complete){ 189 | _complete(); 190 | } 191 | }; 192 | 193 | glitch(el, options); 194 | }; 195 | 196 | /** 197 | * Replace `el` with `newEl` by overlaying a glitched version of `el`, then 198 | * animating it out to reveal `newEl` 199 | * 200 | * The animation will take into account elements of different sizes by sliding 201 | * the container to reveal it, however it looks best if the elements to be 202 | * transitioned between are of similar sizes 203 | * 204 | * @param {jQuery} el The original element that will be glitched 205 | * @param {jQuery} newEl The element to show 206 | * @param {Object} options An object containing the options for the animation 207 | * and any options for html2canvas 208 | */ 209 | glitch.transition = function(el, newEl, options) { 210 | // set the default options 211 | options = defaults(options || {}, { 212 | // the amount to glitch the image 213 | amount: 6, 214 | // A callback when the animation is complete 215 | complete: noop, 216 | // The delay after rendering the glitched element until starting the transition 217 | delay: 300, 218 | // The duration of the transition effect 219 | duration: 500, 220 | // The z-index to apply to the overlay. You might need to tweak this if 221 | // you have things that appear above the element, or are using high 222 | // z-indexes in your page 223 | zIndex: 1000, 224 | // the transition effect to use. This may be "fade" or "slide" 225 | effect: "fade", 226 | // The size of the top border. Set to 0 to disable, only used in slide mode 227 | borderSize: 2, 228 | // The color of the top border, only used in slide mode 229 | borderColor: "green" 230 | }); 231 | 232 | // add the new element to the dom so we can properly calculate its dimensions 233 | newEl.insertAfter(el); 234 | 235 | // store a reference to the complete callback so we can use the same 236 | // options for the glitch function call 237 | var _complete = options.complete, 238 | // get the dimensions of the elements so we can resize the targetContainer 239 | // to reveal all the content after the glitch transition 240 | origHeight = el.outerHeight(true), 241 | origWidth = el.outerWidth(true), 242 | targetHeight = newEl.outerHeight(true), 243 | targetWidth = newEl.outerWidth(true), 244 | origOverflow = newEl.css("overflow"); 245 | 246 | // take the new element out of the dom again 247 | newEl.detach(); 248 | 249 | // create a callback that will 250 | options.complete = function(canvas){ 251 | // position the canvas absolutely within the container 252 | var $canvas = $(canvas).css("position", "absolute"), 253 | offset = el.offset(), 254 | // create a container element that contains the canvas and position it 255 | // over the element we're replacing 256 | container = $("
").css({ 257 | "border-top": options.borderSize ? options.borderSize + "px solid " + 258 | options.borderColor : "none", 259 | position: "absolute", 260 | left: offset.left, 261 | top: offset.top - options.borderSize, 262 | width: canvas.width, 263 | height: canvas.height, 264 | overflow: "hidden", 265 | "z-index": options.zIndex 266 | }) 267 | // add the canvas as a child to the container 268 | .html(canvas) 269 | // add the container to the dom 270 | .appendTo("body") 271 | // delay the animation a bit 272 | .delay(options.delay), 273 | 274 | targetContainer = $("
").css({ 275 | width: origWidth, 276 | height: origHeight, 277 | overflow: "hidden", 278 | border: "none", 279 | "box-sizing": "border-box" 280 | }) 281 | .html(newEl), 282 | 283 | // the default transition effect is to fade out 284 | animation = { 285 | opacity: 0 286 | }, 287 | animateOptions = { 288 | duration: options.duration, 289 | complete: function(){ 290 | // when the animation is done: 291 | // remove the container from the dom 292 | container.remove(); 293 | 294 | // then animate the height of the new element back to its measured 295 | // height and width 296 | targetContainer.animate({ 297 | height: targetHeight, 298 | width: targetWidth 299 | }, 300 | { 301 | duration: 100, 302 | complete: function(){ 303 | 304 | // take the targetContainer element out of the dom 305 | newEl.detach().insertAfter(targetContainer); 306 | targetContainer.remove(); 307 | 308 | // call the complete callback 309 | _complete(); 310 | 311 | // and clear all references 312 | options = $canvas = container = null; 313 | 314 | } 315 | }); 316 | 317 | } 318 | }; 319 | 320 | if(options.effect === "slide") { 321 | // for the slide effect, we move move the container down 322 | animation = { 323 | top: offset.top + canvas.height, 324 | height: 0 325 | }; 326 | 327 | // and on each step of the animation, we need to offset the top so it 328 | // remains in the same place on the screen 329 | animateOptions.step = function(now, fx){ 330 | if(fx.prop === "top") { 331 | $canvas.css("top", fx.start - now); 332 | } 333 | }; 334 | } 335 | 336 | // apply the animation 337 | container.animate(animation, animateOptions); 338 | 339 | // replace the original element with the new one 340 | // we use detatch so that the event handlers on the old 341 | // element are retained. 342 | targetContainer.insertAfter(el); 343 | el.detach(); 344 | 345 | }; 346 | 347 | // create a glitched version of the start element 348 | glitch(el, options); 349 | }; 350 | 351 | window.glitch = glitch; 352 | 353 | if($) { 354 | /** 355 | * jQuery glitch.js plugin 356 | * 357 | * This can be called in the following ways: 358 | * 359 | * Replace the element with a glitched version 360 | * $("#el").glitch() 361 | * 362 | * Create a glitched canvas and pass it to a callback function 363 | * $("#el").glitch(function(canvas){}) 364 | * 365 | * Transition effect 366 | * $("#el").glitch('transition', $("#newEl"), {}) 367 | */ 368 | $.fn.glitch = function(method) { 369 | var args = Array.prototype.splice.call(arguments, 1); 370 | method = method || 'replace'; 371 | return this.each(function(){ 372 | if(method instanceof $) { 373 | glitch.transition($(this), method, args[0]); 374 | } else if(typeof method == 'function') { 375 | // just a callback passed in 376 | glitch($(this), { 377 | complete: method 378 | }); 379 | } else if(typeof method == 'object') { 380 | // an options object passed in 381 | glitch($(this), method); 382 | } else if(glitch.hasOwnProperty(method)) { 383 | // explicitly call a method 384 | glitch[method].apply(null, [$(this)].concat(args)); 385 | } else { 386 | $.error('Method ' + method + ' does not exist on jQuery.glitch'); 387 | } 388 | }); 389 | }; 390 | } 391 | })(window.jQuery); 392 | --------------------------------------------------------------------------------