├── .eslintignore ├── src ├── main │ ├── icon.ico │ ├── 300x300.png │ ├── config-window.js │ ├── services │ │ ├── actions-service.js │ │ └── keybinds-service.js │ ├── preload.js │ ├── main.js │ ├── util │ │ └── lsdir.js │ ├── providers │ │ ├── releases-provider.js │ │ ├── macros-provider.js │ │ └── extensions-provider.js │ ├── main-window.js │ ├── app.js │ └── ipc.js ├── hyperkeys-extensions │ ├── switch-window │ │ ├── store-switch-window.js │ │ ├── build.sh │ │ ├── index.js │ │ ├── action-set-switch-window.js │ │ └── action-show-switch-window.js │ ├── switch-audio │ │ ├── index.js │ │ └── action-switch-audio.js │ ├── ifttt-webhook │ │ ├── index.js │ │ ├── action-ifttt-webhook.js │ │ └── configscreen.js │ ├── run-command │ │ ├── index.js │ │ ├── action-run-command.js │ │ └── configscreen.js │ ├── window-pin-by-name │ │ ├── index.js │ │ ├── action-show-window.js │ │ └── configscreen.js │ └── window-pin-by-path │ │ ├── index.js │ │ ├── configscreen.js │ │ └── action-show-window.js └── webapp │ ├── assets │ ├── fonts │ │ ├── lato-400.woff2 │ │ ├── lato-700.woff2 │ │ ├── lato-400i.woff2 │ │ ├── fontawesome-webfont.woff2 │ │ └── glyphicons-halflings-regular.woff2 │ ├── style.scss │ └── font-awesome.min.css │ ├── package.js │ ├── store.js │ ├── reducers │ ├── metadatas.js │ ├── latest-release.js │ ├── index.js │ └── macros.js │ ├── util │ └── inject-css.js │ ├── actions │ └── index.js │ ├── components │ ├── external-link.js │ ├── layout │ │ └── flex-layout.js │ └── checkbox.js │ ├── index.html │ ├── react-shortcut-renderer.js │ ├── react-view-main.js │ ├── react-popup-setshortcut.js │ ├── webapp.js │ ├── react-popup.js │ ├── react-view-macros.js │ ├── react-popup-addmacro.js │ ├── react-shortcuts-list.js │ ├── react-shortcut-input.js │ └── react-macros-list.js ├── .github ├── FUNDING.yml ├── workflows │ ├── onstar.yml │ ├── prepare_new_version.yml │ └── nodejs.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── natives ├── linux │ ├── wmctrl │ ├── xdotool │ ├── xprop │ ├── xwininfo │ └── libappindicator3.so.1 └── win32 │ ├── libstdc++-6.dll │ └── libgcc_s_dw2-1.dll ├── run.sh ├── .gitignore ├── scripts ├── package-linux.sh ├── package-windows.sh ├── build_package_json.js ├── build-dependencies.sh ├── watch.sh └── build.sh ├── .babelrc.main ├── .babelrc.web ├── util └── lsdir.js ├── HOWTO_RELEASE.md ├── patches └── active-win.patch ├── README.md ├── .eslintrc.js ├── webpack.config.extension.js ├── package.json ├── webpack.config.js └── LICENSE /.eslintignore: -------------------------------------------------------------------------------- 1 | /test/* 2 | /dist/* 3 | /lab/* 4 | 5 | .eslintrc.js 6 | -------------------------------------------------------------------------------- /src/main/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xurei/hyperkeys/HEAD/src/main/icon.ico -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: xurei 4 | -------------------------------------------------------------------------------- /natives/linux/wmctrl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xurei/hyperkeys/HEAD/natives/linux/wmctrl -------------------------------------------------------------------------------- /natives/linux/xdotool: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xurei/hyperkeys/HEAD/natives/linux/xdotool -------------------------------------------------------------------------------- /natives/linux/xprop: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xurei/hyperkeys/HEAD/natives/linux/xprop -------------------------------------------------------------------------------- /src/main/300x300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xurei/hyperkeys/HEAD/src/main/300x300.png -------------------------------------------------------------------------------- /natives/linux/xwininfo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xurei/hyperkeys/HEAD/natives/linux/xwininfo -------------------------------------------------------------------------------- /src/hyperkeys-extensions/switch-window/store-switch-window.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | }; 4 | -------------------------------------------------------------------------------- /natives/win32/libstdc++-6.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xurei/hyperkeys/HEAD/natives/win32/libstdc++-6.dll -------------------------------------------------------------------------------- /natives/win32/libgcc_s_dw2-1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xurei/hyperkeys/HEAD/natives/win32/libgcc_s_dw2-1.dll -------------------------------------------------------------------------------- /natives/linux/libappindicator3.so.1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xurei/hyperkeys/HEAD/natives/linux/libappindicator3.so.1 -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DIR=$(dirname $0) 4 | 5 | $DIR/node_modules/.bin/electron $DIR/build --no-sandbox 6 | -------------------------------------------------------------------------------- /src/webapp/assets/fonts/lato-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xurei/hyperkeys/HEAD/src/webapp/assets/fonts/lato-400.woff2 -------------------------------------------------------------------------------- /src/webapp/assets/fonts/lato-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xurei/hyperkeys/HEAD/src/webapp/assets/fonts/lato-700.woff2 -------------------------------------------------------------------------------- /src/webapp/assets/fonts/lato-400i.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xurei/hyperkeys/HEAD/src/webapp/assets/fonts/lato-400i.woff2 -------------------------------------------------------------------------------- /src/webapp/assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xurei/hyperkeys/HEAD/src/webapp/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/webapp/assets/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xurei/hyperkeys/HEAD/src/webapp/assets/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | dist 3 | distr.old 4 | bin 5 | build 6 | out 7 | dist_packages/ 8 | node_modules 9 | .* 10 | !.gitignore 11 | !.babelrc* 12 | !.eslint* 13 | !.github/ 14 | -------------------------------------------------------------------------------- /src/webapp/package.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | module.exports = JSON.parse(`${fs.readFileSync(path.resolve(__dirname, '..', 'package.json'))}`); 5 | -------------------------------------------------------------------------------- /src/webapp/store.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | import reducers from './reducers'; 3 | 4 | const store = createStore(reducers, global.__REDUX_DEVTOOLS_EXTENSION__ && global.__REDUX_DEVTOOLS_EXTENSION__()); 5 | 6 | export default store; 7 | -------------------------------------------------------------------------------- /src/hyperkeys-extensions/switch-window/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | BASEPATH=$(realpath $(dirname $0)) 5 | 6 | echo "OS type: $OSTYPE" 7 | 8 | #if [[ "$OSTYPE" == "win32" || "$OSTYPE" == "msys" ]]; then 9 | # g++ $BASEPATH/win32/foregroundwin.cpp 10 | #fi 11 | -------------------------------------------------------------------------------- /src/webapp/reducers/metadatas.js: -------------------------------------------------------------------------------- 1 | const metadatas = (state = {}, action) => { 2 | switch (action.type) { 3 | case 'SET_METADATAS': 4 | return action.metadatas; 5 | default: 6 | return state; 7 | } 8 | }; 9 | 10 | export default metadatas; 11 | -------------------------------------------------------------------------------- /src/webapp/reducers/latest-release.js: -------------------------------------------------------------------------------- 1 | const latestRelease = (state = null, action) => { 2 | switch (action.type) { 3 | case 'SET_LATEST_RELEASE': 4 | return action.release; 5 | default: 6 | return state; 7 | } 8 | }; 9 | 10 | export default latestRelease; 11 | -------------------------------------------------------------------------------- /src/webapp/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import macros from './macros'; 3 | import metadatas from './metadatas'; 4 | import latestRelease from './latest-release'; 5 | 6 | const App = combineReducers({ 7 | macros, 8 | metadatas, 9 | latestRelease, 10 | }); 11 | 12 | export default App; 13 | -------------------------------------------------------------------------------- /scripts/package-linux.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BASEPATH=$(realpath $(dirname $0)/..) 5 | echo $BASEPATH 6 | 7 | node_modules/.bin/electron-builder --dir --linux 8 | 9 | # Remove unneeded libs 10 | 11 | # Create AppImage 12 | node_modules/.bin/electron-builder --prepackaged=dist_packages/linux-unpacked --linux --p always 13 | -------------------------------------------------------------------------------- /scripts/package-windows.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BASEPATH=$(realpath $(dirname $0)/..) 5 | 6 | node_modules/.bin/electron-builder --dir --win 7 | 8 | # Remove unneeded libs 9 | echo "Purging useless libs..." 10 | 11 | ## Create AppImage 12 | node_modules/.bin/electron-builder --prepackaged=dist_packages/win-unpacked --win --p always 13 | -------------------------------------------------------------------------------- /src/webapp/util/inject-css.js: -------------------------------------------------------------------------------- 1 | const injectedCss = {}; 2 | 3 | export function injectCss(id, css) { 4 | if (typeof(window) !== 'undefined') { 5 | const window = global; 6 | const document = window.document; 7 | if (!injectedCss[id]) { 8 | injectedCss[id] = true; 9 | const styleCode = document.createElement('STYLE'); 10 | styleCode.appendChild(document.createTextNode(css)); 11 | document.head.appendChild(styleCode); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /scripts/build_package_json.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const json = JSON.parse(`${fs.readFileSync(path.resolve(__dirname, '..', 'package.json'))}`); 5 | 6 | const prodJson = [ 7 | 'name', 8 | 'version', 9 | 'description', 10 | 'author', 11 | 'private', 12 | 'main', 13 | 'dependencies', 14 | ].reduce((a,b) => { 15 | a[b] = json[b]; 16 | return a; 17 | }, {}); 18 | 19 | fs.writeFileSync(path.resolve(__dirname, '..', 'build/package.json'), JSON.stringify(prodJson, null, ' ')); 20 | -------------------------------------------------------------------------------- /src/hyperkeys-extensions/switch-audio/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | actions: [ 3 | require('./action-switch-audio'), 4 | ], 5 | platforms: ['linux'], 6 | metadata: { 7 | name: 'SWITCH_AUDIO', 8 | title: 'Switch audio output', 9 | description: 'Switch between your audio outputs. Works with PipeWire and PulseAudio. You need pactl to make it work (on Ubuntu/Debian: apt install pulseaudio-utils)', 10 | actions: { 11 | SWITCH_AUDIO: {title: 'Switch audio'}, 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /.github/workflows/onstar.yml: -------------------------------------------------------------------------------- 1 | name: On Star 2 | 3 | on: 4 | watch: 5 | types: [started] 6 | 7 | jobs: 8 | nimrod: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Warn via Nimrod 13 | shell: bash 14 | env: # Or as an environment variable 15 | NIMROD_API_KEY: ${{ secrets.NIMROD_API_KEY }} 16 | run: | 17 | curl -X POST -H "Content-Type: application/json" -d "{\"api_key\": \"$NIMROD_API_KEY\", \"message\":\"⭐⭐⭐ New Star on Hyperkeys ⭐⭐⭐\" }" "https://www.nimrod-messenger.io/api/v1/message" 18 | -------------------------------------------------------------------------------- /src/webapp/reducers/macros.js: -------------------------------------------------------------------------------- 1 | const uuid = require('uuid').v4; 2 | 3 | const macros = (state = [], action) => { 4 | switch (action.type) { 5 | case 'SET_MACROS': 6 | return action.macros; 7 | case 'ADD_MACRO': 8 | var macro = Object.assign({id: uuid()}, action.macro); 9 | state.push(macro); 10 | return state; 11 | case 'REMOVE_MACRO': 12 | return state.filter((marco) => marco.id !== action.id); 13 | default: 14 | return state; 15 | } 16 | }; 17 | 18 | export default macros; 19 | -------------------------------------------------------------------------------- /scripts/build-dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BASEPATH=$(realpath "$(dirname $0)") 5 | 6 | # Build production package.json 7 | node $BASEPATH/build_package_json.js 8 | 9 | # Copy native files 10 | cp -R $BASEPATH/../natives $BASEPATH/../build 11 | 12 | # Add non-dev js dependencies 13 | cd $BASEPATH/../build 14 | npm install 15 | 16 | # Node rebuild 17 | $BASEPATH/../node_modules/.bin/electron-rebuild 18 | 19 | # Patching dependency files to use local binaries 20 | patch -N node_modules/active-win/lib/linux.js ../patches/active-win.patch 21 | 22 | cd $BASEPATH/.. 23 | -------------------------------------------------------------------------------- /src/hyperkeys-extensions/switch-window/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | actions: [ 3 | require('./action-set-switch-window'), 4 | require('./action-show-switch-window'), 5 | ], 6 | metadata: { 7 | name: 'SWITCH_WINDOW', 8 | title: 'Window Pin', 9 | description: 'Pin a window with a shortcut, and bring it back to front with another.', 10 | actions: { 11 | SET_SWITCH_WINDOW: {title: 'Pin current'}, 12 | SHOW_SWITCH_WINDOW: {title: 'Bring to front'}, 13 | }, 14 | defaultConfig: {}, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /.babelrc.main: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "modules": "commonjs", 5 | "useBuiltIns": false, 6 | "targets": { 7 | "node": true 8 | } 9 | }], 10 | "@babel/preset-react" 11 | ], 12 | "plugins": [ 13 | ["@babel/plugin-transform-runtime", { 14 | "corejs": 3, 15 | "proposals": true 16 | }], 17 | ["@babel/plugin-proposal-decorators", { 18 | "legacy": true 19 | }], 20 | "@babel/plugin-proposal-class-properties" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.babelrc.web: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "modules": "commonjs", 5 | "useBuiltIns": false, 6 | "targets": { 7 | "node": true 8 | } 9 | }], 10 | "@babel/preset-react" 11 | ], 12 | "plugins": [ 13 | ["@babel/plugin-transform-runtime", { 14 | "corejs": 3, 15 | "proposals": true 16 | }], 17 | ["@babel/plugin-proposal-decorators", { 18 | "legacy": true 19 | }], 20 | "@babel/plugin-proposal-class-properties" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /src/hyperkeys-extensions/ifttt-webhook/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | actions: [ 3 | require('./action-ifttt-webhook'), 4 | ], 5 | metadata: { 6 | name: 'IFTTT_WEBHOOK', 7 | title: (config) => config && config.event ? `IFTTT Webhook '${config.event}'` : 'IFTTT Webhook', 8 | description: 'Trigger an IFTTT webhook ', 9 | actions: { 10 | IFTTT_WEBHOOK: {title: 'Trigger'}, 11 | }, 12 | configScreen: { 13 | enabled: true, 14 | }, 15 | defaultConfig: { 16 | event: '', 17 | apiKey: '', 18 | }, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /.github/workflows/prepare_new_version.yml: -------------------------------------------------------------------------------- 1 | name: Prepare new version 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | create-commit: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - run: git pull origin master 14 | - run: git config --global user.email "ci@github.com" 15 | - run: git config --global user.name "Github Actions" 16 | - run: npm version patch --no-git-tag-version 17 | - run: git add package.json && git add package-lock.json 18 | - run: git commit -m "Started New version development" 19 | - run: git push origin HEAD:master 20 | -------------------------------------------------------------------------------- /src/hyperkeys-extensions/run-command/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | actions: [ 5 | require('./action-run-command'), 6 | ], 7 | metadata: { 8 | name: 'RUN_COMMAND', 9 | title: (config) => config && config.command ? `Run '${path.basename(config.command)}'` : 'Run command', 10 | description: 'Run a command line instruction', 11 | actions: { 12 | RUN_COMMAND: {title: 'Run command'}, 13 | }, 14 | configScreen: { 15 | enabled: true, 16 | }, 17 | defaultConfig: { 18 | command: '', 19 | }, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Environment** 11 | - Operating system: 12 | - Hyperkeys version: 13 | 14 | **Describe the bug and how to reproduce** 15 | A clear and concise description of what the bug is. 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Config file** 21 | Please provide the `macros.json` file, found at `%APPDATA%\hyperkeys\storage` on windows, `~/.config/hyperkeys/storage` on Linux. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/main/config-window.js: -------------------------------------------------------------------------------- 1 | const {BrowserWindow} = require('electron'); 2 | 3 | // Create the browser window. 4 | const OptionWindow = function(parentWindow, opts) { 5 | 6 | const config = Object.assign({ 7 | width: 1024, 8 | height: 768, 9 | webPreferences: { 10 | nodeIntegration: true, 11 | }, 12 | }, 13 | opts, 14 | { 15 | parent: parentWindow, 16 | modal: true, 17 | show: true, 18 | }); 19 | 20 | const out = new BrowserWindow(config); 21 | out.setMenu(null); 22 | out.setTitle('Hyperkeys'); 23 | out.webContents.openDevTools(); 24 | return out; 25 | }; 26 | 27 | module.exports = OptionWindow; 28 | -------------------------------------------------------------------------------- /src/hyperkeys-extensions/window-pin-by-name/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | actions: [ 3 | require('./action-show-window'), 4 | ], 5 | metadata: { 6 | name: 'WINDOW_PIN_BY_NAME', 7 | title: (config) => config && config.name ? `Window Pin '${config.name}'` : 'Window Pin by name', 8 | description: 'Pin a window with a specific name, and bring it back to front with a shortcut.', 9 | actions: { 10 | SHOW_WINDOW_PIN_BY_NAME: {title: 'Bring to front'}, 11 | }, 12 | configScreen: { 13 | enabled: true, 14 | }, 15 | defaultConfig: { 16 | name: '', 17 | fallbackCommand: null, 18 | }, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/main/services/actions-service.js: -------------------------------------------------------------------------------- 1 | const isset = require('xurei-util').isset; 2 | const debug = require('debug')('hyperkeys-actions-service'); 3 | 4 | const actionFactories = {}; 5 | 6 | const ActionService = { 7 | registerActionFactory: (name, factoryMethod) => { 8 | debug(`Registered action \`${name}\``); 9 | actionFactories[name] = factoryMethod; 10 | }, 11 | 12 | buildActionObject: (action) => { 13 | const factory = actionFactories[action.name]; 14 | if (isset(factory)) { 15 | return factory(action); 16 | } 17 | else { 18 | throw `No factory found for action ${ action.name}`; 19 | } 20 | }, 21 | }; 22 | 23 | module.exports = ActionService; 24 | -------------------------------------------------------------------------------- /util/lsdir.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const xutil = require('xurei-util'); 4 | let isset = xutil.isset; 5 | 6 | let lsDir = function(dir, recursive) { 7 | if (!isset(recursive)) { 8 | recursive = true; 9 | } 10 | let results = []; 11 | let list = fs.readdirSync(dir); 12 | list.forEach(function(file) { 13 | let stat = fs.statSync(path.join(dir, file)); 14 | if (stat && stat.isDirectory()) { 15 | if (recursive) { 16 | results = results.concat(lsDir(path.join(dir, file))); 17 | } 18 | else { 19 | results.push(path.join(dir, file)); 20 | } 21 | } 22 | else { 23 | results.push(path.join(dir, file)); 24 | } 25 | }); 26 | return results; 27 | }; 28 | 29 | module.exports = lsDir; -------------------------------------------------------------------------------- /src/hyperkeys-extensions/window-pin-by-path/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | actions: [ 3 | require('./action-show-window'), 4 | ], 5 | platforms: ['linux'], 6 | metadata: { 7 | name: 'WINDOW_PIN_BY_PATH', 8 | title: (config) => config && config.path ? `Window Pin '${config.path}'` : 'Window Pin by path', 9 | description: 'Pin a window with a specific executable path. If not specified, run it', 10 | actions: { 11 | SHOW_WINDOW_PIN_BY_PATH: {title: 'Bring to front'}, 12 | }, 13 | configScreen: { 14 | enabled: true, 15 | }, 16 | defaultConfig: { 17 | path: '', 18 | fallbackCommand: null, 19 | }, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /src/main/preload.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer } = require('electron'); 2 | const { contextBridge } = require('electron'); 3 | 4 | contextBridge.exposeInMainWorld('ipc', { 5 | on: (type, callback) => { 6 | ipcRenderer.on(type, callback); 7 | }, 8 | send: (message, data) => { 9 | ipcRenderer.send(message, data); 10 | }, 11 | }); 12 | 13 | process.once('loaded', () => { 14 | console.log('preload loaded'); 15 | global.addEventListener('message', event => { 16 | // do something with custom event 17 | const message = event.data; 18 | if (message) { 19 | if (message.action) { 20 | ipcRenderer.send(message.action, message.data); 21 | } 22 | } 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/main/main.js: -------------------------------------------------------------------------------- 1 | const {app} = require('electron'); 2 | const myapp = require('./app'); 3 | 4 | console.log('getPath', app.getPath('exe')); 5 | console.log('getDataPath', app.getPath('userData')); 6 | 7 | //PLATFORM DETECTION 8 | process.on('uncaughtException', function(err) { 9 | console.log('EXCEPTION OCCURRED'); 10 | console.log(err); 11 | app.exit(); 12 | }); 13 | 14 | app.commandLine.appendSwitch('disable-http-cache', ''); 15 | 16 | // Quit when all windows are closed. 17 | app.on('window-all-closed', function() { 18 | if (process.platform !== 'darwin') 19 | {app.quit();} 20 | }); 21 | 22 | // This method will be called when Electron has done everything 23 | // initialization and ready for creating browser windows. 24 | app.on('ready', myapp.ready); 25 | -------------------------------------------------------------------------------- /src/webapp/actions/index.js: -------------------------------------------------------------------------------- 1 | export const setMetadatas = (metadatas) => { 2 | return { 3 | type: 'SET_METADATAS', 4 | metadatas: metadatas, 5 | }; 6 | }; 7 | 8 | export const setMacros = (macros) => { 9 | return { 10 | type: 'SET_MACROS', 11 | macros: macros, 12 | }; 13 | }; 14 | 15 | export const addKeybind = (keybind) => { 16 | return { 17 | type: 'ADD_KEYBIND', 18 | keybind: keybind, 19 | }; 20 | }; 21 | 22 | export const removeKeybind = (keybind) => { 23 | return { 24 | type: 'REMOVE_KEYBIND', 25 | keybind: keybind, 26 | }; 27 | }; 28 | 29 | export const setLatestRelease = (release) => { 30 | return { 31 | type: 'SET_LATEST_RELEASE', 32 | release: release, 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /scripts/watch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BASEPATH=$(realpath $(dirname $0)) 5 | EXTPATH="$BASEPATH/../src/hyperkeys-extensions" 6 | 7 | function compile_module { 8 | MODULE_NAME=$1 9 | echo "$EXTPATH/$MODULE_NAME/configscreen.js" 10 | if test -f "$EXTPATH/$MODULE_NAME/configscreen.js"; then 11 | webpack --watch "$EXTPATH/$MODULE_NAME/configscreen.js" --output-path "$BASEPATH/../build/hyperkeys-extensions/$MODULE_NAME/" 12 | fi 13 | } 14 | 15 | # Build hyperkeys modules 16 | #TODO iterate over folders and build instead of hard-coded 17 | #compile_module run-command 18 | #compile_module switch-window 19 | #compile_module window-pin-by-name 20 | #compile_module switch-audio 21 | #compile_module ifttt-webhook 22 | 23 | trap 'kill %1; kill %2' SIGINT 24 | 25 | # Build hyperkeys module 26 | compile_module $1 & 27 | 28 | # Build app 29 | webpack --watch 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/util/lsdir.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const xutil = require('xurei-util'); 4 | const isset = xutil.isset; 5 | 6 | const lsDir = function(dir, recursive) { 7 | if (!isset(recursive)) { 8 | recursive = true; 9 | } 10 | let results = []; 11 | const list = fs.readdirSync(dir); 12 | list.forEach(function(file) { 13 | const stat = fs.statSync(path.join(dir, file)); 14 | if (stat && stat.isDirectory()) { 15 | if (recursive) { 16 | results = results.concat(lsDir(path.join(dir, file))); 17 | } 18 | else { 19 | results.push(path.join(dir, file)); 20 | } 21 | } 22 | else { 23 | results.push(path.join(dir, file)); 24 | } 25 | }); 26 | return results; 27 | }; 28 | 29 | module.exports = lsDir; 30 | -------------------------------------------------------------------------------- /src/webapp/components/external-link.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; //eslint-disable-line no-unused-vars 2 | import PropTypes from 'prop-types'; //eslint-disable-line no-unused-vars 3 | import deepEqual from 'deep-eql'; 4 | import autobind from 'autobind-decorator'; 5 | 6 | class ExternalLink extends React.Component { 7 | static propTypes = { 8 | href: PropTypes.string, 9 | }; 10 | 11 | render() { 12 | const props = this.props; 13 | return ( 14 | {props.children} 15 | ); 16 | } 17 | 18 | @autobind 19 | handleClick(e) { 20 | e.preventDefault(); 21 | global.ipc.send('open_external', this.props.href); 22 | } 23 | 24 | shouldComponentUpdate(nextProps) { 25 | return !deepEqual(this.props, nextProps); 26 | } 27 | } 28 | 29 | export { ExternalLink }; 30 | -------------------------------------------------------------------------------- /HOWTO_RELEASE.md: -------------------------------------------------------------------------------- 1 | # How to release 2 | 3 | This document is for maintainers of Hyperkeys only. It lists the steps to create a new release of Hyperkeys. 4 | 5 | 1. First, create a new draft release : https://github.com/xurei/hyperkeys/releases/new. 6 | 7 | 2. In the release draft, create the tag name with the same version as in the package.json, prefixed with `v` (example: `v1.2.34`). 8 | 9 | 3. Re-run the last github actions to update the release artifacts, or push your latest changes. 10 | If you push another commit, Github will update the artifacts to match the latest successfully built commit. 11 | 12 | 4. While it builds, check and write the description of your release. 13 | 14 | 5. Once the CI has finished, publish the release. It will create the right tag at the right commit. 15 | 16 | 6. `npm version [patch|minor|major] && git commit --amend -m "Start x.y.z Development"` 17 | (You can do manually as well if required) 18 | 19 | 7. BONUS : create the next release draft already ;-) 20 | -------------------------------------------------------------------------------- /src/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HyperKeys 7 | 8 | 9 |
10 |
LOADING...
11 |
12 |
13 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /patches/active-win.patch: -------------------------------------------------------------------------------- 1 | Index: app/node_modules/active-win/lib/linux.js 2 | IDEA additional info: 3 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP 4 | <+>UTF-8 5 | =================================================================== 6 | --- app/node_modules/active-win/lib/linux.js 7 | +++ app/node_modules/active-win/lib/linux.js 8 | @@ -7,8 +7,8 @@ 9 | const readFile = promisify(fs.readFile); 10 | const readlink = promisify(fs.readlink); 11 | 12 | -const xpropBin = 'xprop'; 13 | -const xwininfoBin = 'xwininfo'; 14 | +const xpropBin = __dirname + '/../../../natives/linux/xprop'; 15 | +const xwininfoBin = __dirname + '/../../../natives/linux/xwininfo'; 16 | const xpropActiveArgs = ['-root', '\t$0', '_NET_ACTIVE_WINDOW']; 17 | const xpropDetailsArgs = ['-id']; 18 | 19 | @@ -49,7 +49,7 @@ 20 | return { 21 | platform: 'linux', 22 | title: JSON.parse(result['_NET_WM_NAME(UTF8_STRING)'] || result['WM_NAME(STRING)']) || null, 23 | - id: windowId, 24 | + id: activeWindowId, 25 | owner: { 26 | name: JSON.parse(result['WM_CLASS(STRING)'].split(',').pop()), 27 | processId 28 | -------------------------------------------------------------------------------- /src/main/services/keybinds-service.js: -------------------------------------------------------------------------------- 1 | const {globalShortcut} = require('electron'); 2 | const actionService = require('./actions-service'); 3 | const debug = require('debug')('hyperkeys-keybinds-service'); 4 | 5 | const KeybindsService = { 6 | registerKey: (keybind) => { 7 | try { 8 | const action = actionService.buildActionObject(keybind.action); 9 | globalShortcut.register(keybind.key, action.execute); 10 | 11 | if (!globalShortcut.isRegistered(keybind.key)) { 12 | console.error(`Cannot register keybind \`${ keybind.key }\`. Already registered.`); 13 | } else { 14 | debug(`Registered keybind \`${ keybind.key }\` => \`${ keybind.action.name }\``); 15 | } 16 | } 17 | catch(e) { 18 | console.error(`Cannot register keybind \`${ keybind.key }\`. Exception:`); 19 | console.error(e); 20 | } 21 | }, 22 | 23 | unregisterKey: (keybind) => { 24 | // TODO use unredisterKey() 25 | globalShortcut.unregister(keybind.key); 26 | }, 27 | }; 28 | 29 | module.exports = KeybindsService; 30 | -------------------------------------------------------------------------------- /src/main/providers/releases-provider.js: -------------------------------------------------------------------------------- 1 | const request = require('request-promise-native'); 2 | 3 | let _latestRelease; 4 | 5 | const provider = { 6 | loadLatestRelease: function() { 7 | if (!_latestRelease) { 8 | return request({ 9 | method: 'GET', 10 | json: true, 11 | headers: { 12 | Accept: 'application/vnd.github.v3+json', 13 | 'User-Agent': 'Hyperkeys-App', 14 | }, 15 | url: `https://api.github.com/repos/xurei/hyperkeys/releases?per_page=10&page=0`, 16 | }) 17 | .then((releases) => { 18 | return releases.filter(r => !r.prerelease); 19 | }) 20 | .then((releases) => { 21 | _latestRelease = releases[0]; 22 | return _latestRelease; 23 | }) 24 | .catch(e => { 25 | console.error(e); 26 | return null; 27 | }); 28 | } 29 | else { 30 | return Promise.resolve(_latestRelease); 31 | } 32 | }, 33 | }; 34 | 35 | module.exports = provider; 36 | -------------------------------------------------------------------------------- /src/hyperkeys-extensions/run-command/action-run-command.js: -------------------------------------------------------------------------------- 1 | const spawn = require('child_process').spawn; 2 | const path = require('path'); 3 | const debug = require('debug')(`hyperkeys-${path.basename(__dirname)}`); 4 | const NotificationService = require('hyperkeys-api').NotificationService; 5 | 6 | module.exports = { 7 | name: 'RUN_COMMAND', 8 | factory: function(action) { 9 | return { 10 | execute: () => { 11 | const onError = (e) => { 12 | NotificationService.notify({ 13 | 'title': 'Run command', 14 | 'message': `Couldn't run '${action.config.command}'`, 15 | }); 16 | }; 17 | 18 | const command = action.config.command; 19 | debug('Running command', command); 20 | try { 21 | const child = spawn(command, { 22 | detached: true, 23 | shell: true, 24 | }); 25 | child.on('error', onError); 26 | } 27 | catch (e) { 28 | onError(e); 29 | } 30 | }, 31 | }; 32 | }, 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /src/hyperkeys-extensions/switch-window/action-set-switch-window.js: -------------------------------------------------------------------------------- 1 | const store = require('./store-switch-window'); 2 | const path = require('path'); 3 | const debug = require('debug')(`hyperkeys-${path.basename(__dirname)}`); 4 | const NotificationService = require('hyperkeys-api').NotificationService; 5 | const activeWin = require('active-win'); 6 | 7 | module.exports = { 8 | name: 'SET_SWITCH_WINDOW', 9 | factory: function(action) { 10 | return { 11 | execute: () => { 12 | activeWin().then((winData) => { 13 | const id = winData.id; 14 | debug(`Action mapped to ${id}`); 15 | store[action.id_macro] = id; 16 | NotificationService.notify({ 17 | 'title': 'Window Pinner', 18 | 'message': `Pinned ${winData.title || 'window'}`, 19 | }); 20 | return; 21 | }) 22 | .catch(error => { 23 | debug('Error occured'); 24 | debug(error); 25 | 26 | NotificationService.notify({ 27 | 'title': 'Window Pinner', 28 | 'message': 'ERROR : cannot pin window', 29 | }); 30 | }); 31 | }, 32 | }; 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /src/webapp/react-shortcut-renderer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; //eslint-disable-line no-unused-vars 2 | import PropTypes from 'prop-types'; //eslint-disable-line no-unused-vars 3 | import deepEqual from 'deep-eql'; 4 | 5 | class ShortcutRenderer extends React.Component { 6 | static propTypes = { 7 | shortcut: PropTypes.string, 8 | noEditHover: PropTypes.bool, 9 | }; 10 | 11 | render() { 12 | const props = this.props; 13 | const shortcut = (props.shortcut || '').replace('++','+Plus'); 14 | 15 | let i = 0; 16 | const keys = ( 17 | shortcut.split('+') 18 | .filter((k) => k !== '') 19 | .map((k) => { 20 | ++i; 21 | return ( 22 | 23 | {' '} 24 | {k.toUpperCase()} 25 | ); 26 | }) 27 | ); 28 | 29 | return ( 30 | 31 | {keys} 32 | 33 | ); 34 | } 35 | 36 | shouldComponentUpdate(nextProps) { 37 | return !deepEqual(this.props, nextProps); 38 | } 39 | } 40 | 41 | export default ShortcutRenderer; 42 | -------------------------------------------------------------------------------- /src/main/providers/macros-provider.js: -------------------------------------------------------------------------------- 1 | const storage = require('electron-json-storage'); 2 | 3 | //TODO default macros if none found 4 | 5 | const provider = { 6 | saveMacros: function(macros) { 7 | return new Promise((resolve, reject) => { 8 | storage.set('macros', macros, function(error, data) { 9 | if (error) { 10 | reject(error); 11 | } 12 | resolve(data); 13 | }); 14 | }) 15 | .catch(e => console.error(e)); 16 | }, 17 | 18 | loadMacros: function() { 19 | return new Promise((resolve, reject) => { 20 | storage.get('macros', function(error, data) { 21 | if (error) { 22 | reject(error); 23 | } 24 | 25 | if (!Array.isArray(data)) { 26 | data = []; 27 | } 28 | 29 | //TODO add default config on first launch ? 30 | 31 | //By default, macros are enabled 32 | data.forEach(macro => { 33 | if (typeof(macro.enabled) === 'undefined') { 34 | macro.enabled = true; 35 | } 36 | }); 37 | 38 | resolve(data); 39 | }); 40 | }) 41 | .catch(e => console.error(e)); 42 | }, 43 | }; 44 | 45 | module.exports = provider; 46 | -------------------------------------------------------------------------------- /src/hyperkeys-extensions/ifttt-webhook/action-ifttt-webhook.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const debug = require('debug')(`hyperkeys-${path.basename(__dirname)}`); 3 | const NotificationService = require('hyperkeys-api').NotificationService; 4 | const request = require('request-promise-native'); 5 | 6 | module.exports = { 7 | name: 'IFTTT_WEBHOOK', 8 | factory: function(action) { 9 | return { 10 | execute: async() => { 11 | const onError = (e) => { 12 | NotificationService.notify({ 13 | 'title': 'IFTTT Webhook', 14 | 'message': `Couldn't trigger action: ${e.toString()}`, 15 | }); 16 | }; 17 | 18 | const event = action.config.event; 19 | const apiKey = action.config.apiKey; 20 | debug('IFTTT Webhook', event); 21 | try { 22 | await request({ 23 | method: 'GET', 24 | url: `https://maker.ifttt.com/trigger/${event}/with/key/${apiKey}`, 25 | }); 26 | NotificationService.notify({ 27 | 'title': 'IFTTT Webhook', 28 | 'message': `Action trigerred !`, 29 | }); 30 | } 31 | catch (e) { 32 | onError(e); 33 | } 34 | }, 35 | }; 36 | }, 37 | }; 38 | 39 | -------------------------------------------------------------------------------- /src/webapp/components/layout/flex-layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; //eslint-disable-line no-unused-vars 2 | import PropTypes from 'prop-types'; //eslint-disable-line no-unused-vars 3 | import Styled from 'styled-components'; 4 | 5 | function FlexLayout(props) { 6 | const style = Object.assign({}, props.style, { 7 | flexDirection: props.direction || 'row', 8 | flexWrap: props.wrap || 'nowrap', 9 | }); 10 | return ( 11 |
12 | {props.children} 13 |
14 | ); 15 | } 16 | FlexLayout.propTypes = { 17 | direction: PropTypes.oneOf(['column', 'column-reverse', 'row', 'row-reverse']), 18 | wrap: PropTypes.oneOf(['nowrap', 'wrap', 'wrap-reverse']), 19 | }; 20 | 21 | //language=SCSS 22 | //eslint-disable-next-line no-func-assign 23 | FlexLayout = Styled(FlexLayout)` 24 | & { 25 | position: relative; 26 | display: flex; 27 | height: 100%; 28 | width: 100%; 29 | } 30 | `; 31 | 32 | function FlexChild(props) { 33 | const style = Object.assign({}, props.style, { 34 | flexGrow: props.grow || 0, 35 | flexShrink: props.shrink || 0, 36 | width: props.width, 37 | height: props.height, 38 | }); 39 | 40 | return ( 41 |
42 | {props.children} 43 |
44 | ); 45 | } 46 | FlexChild.propTypes = { 47 | grow: PropTypes.number, 48 | shrink: PropTypes.number, 49 | }; 50 | 51 | export { FlexLayout, FlexChild }; 52 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BASEPATH=$(realpath $(dirname $0)) 5 | 6 | function compile_module { 7 | MODULE_NAME=$1 8 | echo "-- Building $MODULE_NAME..." 9 | node_modules/.bin/babel --config-file $BASEPATH/../.babelrc.main src/hyperkeys-extensions/$MODULE_NAME/* -d build/hyperkeys-extensions/$MODULE_NAME --copy-files 10 | if test -f "src/hyperkeys-extensions/$MODULE_NAME/configscreen.js"; then 11 | webpack "./src/hyperkeys-extensions/$MODULE_NAME/configscreen.js" --config "./webpack.config.extension.js" --output-path "./build/hyperkeys-extensions/$MODULE_NAME" 12 | mv "build/hyperkeys-extensions/$MODULE_NAME/main.js" "build/hyperkeys-extensions/$MODULE_NAME/configscreen.js" 13 | mv "build/hyperkeys-extensions/$MODULE_NAME/main.js.map" "build/hyperkeys-extensions/$MODULE_NAME/configscreen.js.map" || true 14 | fi 15 | if test -f "src/hyperkeys-extensions/$MODULE_NAME/build.sh"; then 16 | bash "src/hyperkeys-extensions/$MODULE_NAME/build.sh"; 17 | fi 18 | echo "" 19 | } 20 | 21 | if [[ $1 != '' ]]; then 22 | echo "Building $1 ONLY" 23 | compile_module $1 24 | exit 0; 25 | else 26 | # Build main 27 | echo "-- Building Main" 28 | node_modules/.bin/babel --config-file $BASEPATH/../.babelrc.main src/main -d build --copy-files 29 | 30 | # Build package.json 31 | node $BASEPATH/build_package_json.js 32 | 33 | # Build hyperkeys modules 34 | ls -l $BASEPATH/../src/hyperkeys-extensions | grep '^d' | awk '{print $9}' | \ 35 | while read line; do 36 | compile_module $line 37 | done 38 | 39 | # Build app 40 | webpack 41 | fi 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Logo 3 |

HyperKeys

4 |

Keyboard shortcuts on steroids

5 | hyperkeys.xureilab.com 6 |
7 |
8 | 9 |
10 | 11 | Build artifacts 12 | 13 | 14 | Release 15 | 16 | 17 | Dependencies 18 | 19 |
20 |
21 | 22 | License GPL-3 23 | 24 | 25 | Sponsor 26 | 27 |
28 | 29 | Unleash the power of your keyboard by letting you bind ANY action to a shortcut. 30 | 31 | 32 | ## Features 33 | - Window Pinner 34 | - Run Command 35 | - IFTTT Webhook 36 | - Switch Audio outputs (experimental; Linux only) 37 | 38 | More info on [hyperkeys.xureilab.com](https://hyperkeys.xureilab.com) 39 | 40 | ## Support Open-Source ♥ 41 | Support my work on https://github.com/sponsors/xurei 42 | -------------------------------------------------------------------------------- /src/main/main-window.js: -------------------------------------------------------------------------------- 1 | const { BrowserWindow, shell } = require('electron'); 2 | const path = require('path'); 3 | const releasesProvider = require('./providers/releases-provider'); 4 | const semver = require('semver'); 5 | //noinspection JSFileReferences 6 | const pkg = require('./package.json'); 7 | 8 | // Create the browser window. 9 | const mainWindow = new BrowserWindow({ 10 | width: 1024, 11 | height: 768, 12 | minWidth: 1010, 13 | sandbox: true, 14 | show: false, 15 | icon: path.join(__dirname, '300x300.png'), 16 | webPreferences: { 17 | preload: path.join(__dirname, 'preload.js'), 18 | contextIsolation: true, 19 | }, 20 | }); 21 | mainWindow.setMenu(null); 22 | mainWindow.setTitle('Hyperkeys'); 23 | // and load the index.html of the app 24 | mainWindow.loadURL(`file://${ __dirname }/index.html`); 25 | 26 | mainWindow.on('show', function(e) { 27 | releasesProvider.loadLatestRelease() 28 | .then((release) => { 29 | console.log(`Latest release: ${release.tag_name} vs ${pkg.version}`); 30 | if (semver.gt(release.tag_name.substring(1), pkg.version)) { 31 | release.new_version = true; 32 | console.log(`NEW VERSION ${release.tag_name}`); 33 | } 34 | mainWindow.webContents.send('latest_version', release); 35 | return; 36 | }) 37 | .catch(e => { 38 | console.error(e); 39 | }); 40 | }); 41 | 42 | // Open external links in the default browser 43 | mainWindow.webContents.setWindowOpenHandler(({ url }) => { 44 | shell.openExternal(url); 45 | return { action: 'deny' }; 46 | }); 47 | 48 | // Emitted when the window is closed. 49 | mainWindow.on('close', function(e) { 50 | if (mainWindow !== null) { 51 | e.preventDefault(); 52 | mainWindow.hide(); 53 | } 54 | }); 55 | 56 | module.exports = mainWindow; 57 | -------------------------------------------------------------------------------- /src/webapp/react-view-main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; //eslint-disable-line no-unused-vars 2 | import Styled from 'styled-components'; 3 | import MacrosView from './react-view-macros'; 4 | //eslint-disable-next-line xurei/no-relative-parent-imports 5 | import * as pkg from '../../package.json'; 6 | import { connect as redux_connect } from 'react-redux'; 7 | 8 | class MainView extends React.Component { 9 | render() { 10 | const props = this.props; 11 | return ( 12 |
13 | {props.release && props.release.new_version && ( 14 |
15 | A new version of Hyperkeys is available ! 16 | {' '} 17 | {props.release.tag_name} 18 | {' '} 19 | Download 20 |
21 | )} 22 |
23 |
24 | 25 |
26 | Version: {pkg.version} 27 |
28 |
29 |
30 | ); 31 | } 32 | } 33 | 34 | MainView = redux_connect( 35 | (state) => { 36 | return ({ 37 | release: state.latestRelease, 38 | }); 39 | }, 40 | )(MainView); 41 | 42 | //language=SCSS 43 | MainView = Styled(MainView)` 44 | & { 45 | .main-content { 46 | padding: 0; 47 | margin: 0 auto; 48 | } 49 | 50 | .new-version { 51 | background: #D17000; 52 | position: relative; 53 | padding: 10px 40px; 54 | 55 | a { 56 | color: #320; 57 | text-decoration: underline; 58 | } 59 | } 60 | } 61 | `; 62 | 63 | export default MainView; 64 | -------------------------------------------------------------------------------- /src/webapp/react-popup-setshortcut.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; //eslint-disable-line no-unused-vars 2 | import PropTypes from 'prop-types'; //eslint-disable-line no-unused-vars 3 | import deepEqual from 'deep-eql'; 4 | import autobind from 'autobind-decorator'; 5 | 6 | import ShortcutInput from './react-shortcut-input'; 7 | 8 | import { Button } from 'reactstrap'; 9 | 10 | class PopupSetShortcut extends React.Component { 11 | static propTypes = { 12 | onClose: PropTypes.func.isRequired, 13 | onSubmit: PropTypes.func.isRequired, 14 | }; 15 | 16 | constructor(props) { 17 | super(props); 18 | this.state = { 19 | shortcut: null, 20 | }; 21 | } 22 | 23 | @autobind 24 | handleChange(shortcut) { 25 | this.setState({shortcut: shortcut}); 26 | } 27 | 28 | @autobind 29 | handleSubmit(e) { 30 | this.props.onSubmit(this.state.shortcut); 31 | this.props.onClose(e); 32 | } 33 | 34 | componentDidMount() { 35 | //this.refs.add_shortcut_input.focus(); 36 | } 37 | 38 | render() { 39 | return ( 40 |
41 | 42 |
43 |