From 9c90e19760b0a906648594c61f1dda7bb526915b Mon Sep 17 00:00:00 2001 From: Matheus Fernandes Date: Fri, 11 Nov 2016 15:18:04 -0200 Subject: [PATCH] Add Windows support and first-class Linux support (#946) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * `child_pty` => `pty.js` * Create a frameless window on Windows and Linux * Add a brand new UI for Linux and Windows :nail_care: * [Windows] Fix plugin installation * [Windows] Fix the `build` script * [Windows] Add a bigger `icon.ico` * [Mac] Add `WebKitAppRegion: drag` when running on macOS * Fix code style :thinking: * Add `appveyor.yml` * Fix code style (again) * [Windows] Fix AppVeyor's `install` script * [Windows] Try a new AppVeyor config * [Windows] Set the binary path so Spectron can run the tests * [Windows] Try to build on x64 * Try again to build on x64 * Try one more time :weary: * Throw an error to indicate that `pty.js` was built incorrectly * [Win/Linux] Add `display: hidden` to if tabs.length === 1 * [Win/Linux] Reorganize SVGs – via @CodeTheory * [Win/Linux] Fix the hamburger menu height * Make the SVGs look better with `shape-rendering: crispEdges;` * [Win/Linux] Add config options for the window controls and the :hamburger: menu * Add `electron-squirrel-startup` dependency * [Win] Handle Squirrel commands * [Win/Linux] Fix default color for the :hamburger: and window controls – via @CodeTheory * [Win/Linux] Add some padding - via @CodeTheory * [Win/Linux] Add hover states – via @CodeTheory * [Win] Fix empty window/tab titles * [Win] Fix opening Preferences (#978) * [Win] Fix opening Preferences * Update ui.js * Update ui.js * Enhance messages and default editor * [Win] Add dependency instructions to the README.md [skip ci] * Fix code style * [Win/Linux] Check the number of open windows before quitting the app --- README.md | 4 +- app/config-default.js | 10 +++ app/index.js | 20 ++++- app/package.json | 3 +- app/plugins.js | 2 +- app/session.js | 37 +++++---- appveyor.yml | 31 ++++++++ assets/icons.svg | 44 ++++++++++- build/icon.ico | Bin 32038 -> 370070 bytes lib/actions/header.js | 35 ++++++++- lib/actions/ui.js | 19 +++-- lib/components/header.js | 165 ++++++++++++++++++++++++++++++++++++++- lib/components/tab.js | 5 +- lib/components/tabs.js | 76 ++++++++++-------- lib/components/terms.js | 11 ++- lib/constants/ui.js | 3 + lib/containers/header.js | 18 ++++- lib/containers/terms.js | 3 +- lib/reducers/ui.js | 14 +++- package.json | 3 +- test/index.js | 4 + 21 files changed, 433 insertions(+), 74 deletions(-) create mode 100644 appveyor.yml diff --git a/README.md b/README.md index 86af0afb..951b6b64 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,9 @@ $ brew cask install hyper ## Contribute -1. If you are running Linux, install "icnsutils", "graphicsmagick" and "xz-utils" +1. Install the dependencies + * If you are running Linux, install `icnsutils`, `graphicsmagick` and `xz-utils` + * If you are running Windows, install [VC++ Build Tools Technical Preview](http://go.microsoft.com/fwlink/?LinkId=691126) using the **Default Install option**; Install Python 2.7 and add it to your `%PATH%`; Run `npm config set msvs_version 2015 --global` 2. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device 3. Install the dependencies: `npm install` 4. Build the code, watch for changes and run the app: `npm start` diff --git a/app/config-default.js b/app/config-default.js index 66db6c4d..8d1072a1 100644 --- a/app/config-default.js +++ b/app/config-default.js @@ -27,6 +27,16 @@ module.exports = { // custom css to embed in the terminal window termCSS: '', + // set to `true` if you're using a Linux set up + // that doesn't shows native menus + // default: `false` on Linux, `true` on Windows (ignored on macOS) + showHamburgerMenu: '', + + // set to `false` if you want to hide the minimize, maximize and close buttons + // additionally, set to `'left'` if you want them on the left, like in Ubuntu + // default: `true` on windows and Linux (ignored on macOS) + showWindowControls: '', + // custom padding (css format, i.e.: `top right bottom left`) padding: '12px 14px', diff --git a/app/index.js b/app/index.js index 7bf09f49..ae761ea6 100644 --- a/app/index.js +++ b/app/index.js @@ -1,3 +1,5 @@ +// eslint-disable-next-line curly, unicorn/no-process-exit +if (require('electron-squirrel-startup')) process.exit(); // Native const {resolve} = require('path'); @@ -108,9 +110,11 @@ app.on('ready', () => installDevExtensions(isDev).then(() => { height, minHeight: 190, minWidth: 370, - titleBarStyle: 'hidden-inset', + titleBarStyle: 'hidden-inset', // macOS only title: 'Hyper.app', backgroundColor: toElectronBackgroundColor(cfg.backgroundColor || '#000'), + // we want to go frameless on windows and linux + frame: process.platform === 'darwin', transparent: true, icon: resolve(__dirname, 'static/icon.png'), // we only want to show when the prompt is ready for user input @@ -235,6 +239,18 @@ app.on('ready', () => installDevExtensions(isDev).then(() => { rpc.emit('move'); }); + rpc.on('open hamburger menu', ({x, y}) => { + Menu.getApplicationMenu().popup(x, y); + }); + + rpc.on('minimize', () => { + win.minimize(); + }); + + rpc.on('close', () => { + win.close(); + }); + const deleteSessions = () => { sessions.forEach((session, key) => { session.removeAllListeners(); @@ -305,7 +321,7 @@ app.on('ready', () => installDevExtensions(isDev).then(() => { }); win.on('closed', () => { - if (process.platform !== 'darwin') { + if (process.platform !== 'darwin' && windowSet.size === 0) { app.quit(); } }); diff --git a/app/package.json b/app/package.json index 670e6318..bddee34e 100644 --- a/app/package.json +++ b/app/package.json @@ -11,18 +11,19 @@ "repository": "zeit/hyper", "xo": false, "dependencies": { - "child_pty": "3.0.1", "color": "0.11.3", "convert-css-color-name-to-hex": "0.1.1", "default-shell": "1.0.1", "electron-config": "0.2.1", "electron-is-dev": "0.1.1", + "electron-squirrel-startup": "1.0.0", "file-uri-to-path": "0.0.2", "gaze": "1.1.2", "git-describe": "3.0.2", "mkdirp": "0.5.1", "ms": "0.7.1", "node-fetch": "1.6.3", + "pty.js": "https://github.com/Tyriar/pty.js/tarball/c75c2dcb6dcad83b0cb3ef2ae42d0448fb912642", "semver": "5.3.0", "shell-env": "0.2.0", "uuid": "2.0.2" diff --git a/app/plugins.js b/app/plugins.js index f5b26b6d..a2d83f82 100644 --- a/app/plugins.js +++ b/app/plugins.js @@ -236,7 +236,7 @@ function install(fn) { env.npm_config_target = '1.3.0'; env.npm_config_disturl = 'https://atom.io/download/atom-shell'; /* eslint-enable camelcase */ - exec('npm prune; npm install --production', { + exec('npm prune && npm install --production', { cwd: path, env, shell diff --git a/app/session.js b/app/session.js index 9f75439f..3b9f08f7 100644 --- a/app/session.js +++ b/app/session.js @@ -8,16 +8,13 @@ const {getDecoratedEnv} = require('./plugins'); const {productName, version} = require('./package'); const config = require('./config'); +const createPtyJsError = () => new Error('`pty.js` failed to load. Typically this means that it was built incorrectly. Please check the `README.me` to more info.'); + let spawn; try { - spawn = require('child_pty').spawn; + spawn = require('pty.js').spawn; } catch (err) { - console.error( - 'A native module failed to load. Typically this means ' + - 'you installed the modules incorrectly.\n Use `scripts/install.sh` ' + - 'to trigger the installation.\n ' + - 'More information: https://github.com/zeit/hyper/issues/72' - ); + throw createPtyJsError(); } const envFromConfig = config.getConfig().env || {}; @@ -37,12 +34,20 @@ module.exports = class Session extends EventEmitter { const defaultShellArgs = ['--login']; - this.pty = spawn(shell || defaultShell, shellArgs || defaultShellArgs, { - columns, - rows, - cwd, - env: getDecoratedEnv(baseEnv) - }); + try { + this.pty = spawn(shell || defaultShell, shellArgs || defaultShellArgs, { + columns, + rows, + cwd, + env: getDecoratedEnv(baseEnv) + }); + } catch (err) { + if (/is not a function/.test(err.message)) { + throw createPtyJsError(); + } else { + throw err; + } + } this.pty.stdout.on('data', data => { if (this.ended) { @@ -69,9 +74,9 @@ module.exports = class Session extends EventEmitter { this.pty.stdin.write(data); } - resize({cols: columns, rows}) { + resize({cols, rows}) { try { - this.pty.stdout.resize({columns, rows}); + this.pty.stdout.resize(cols, rows); } catch (err) { console.error(err.stack); } @@ -79,7 +84,7 @@ module.exports = class Session extends EventEmitter { destroy() { try { - this.pty.kill('SIGHUP'); + this.pty.kill(); } catch (err) { console.error('exit error', err.stack); } diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..6cd75c18 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,31 @@ +# https://github.com/sindresorhus/appveyor-node/blob/master/appveyor.yml + +environment: + matrix: + - platform: x64 + +image: Visual Studio 2015 + +init: + - npm config set msvs_version 2015 # we need this to build `pty.js` + +cache: + - node_modules + +install: + - ps: Install-Product node 6 x64 + - set CI=true + - npm -g install npm@latest + - npm install + +build: off + +shallow_clone: true + +test_script: + - node --version + - npm --version + - npm run test + +on_success: + - npm run dist diff --git a/assets/icons.svg b/assets/icons.svg index 482a666b..71a7b8e0 100644 --- a/assets/icons.svg +++ b/assets/icons.svg @@ -1,8 +1,48 @@ - - close + + close tab + + hamburger menu + + + + + + minimize window + + + + + + + + + + + maximize window + + + + + + + + + + + + + close window + + + + + + + + diff --git a/build/icon.ico b/build/icon.ico index c857abd995094f8073ad28adefc49f392bc987ae..3984592db1836cfcdc3d891bf16479ff5e756b07 100644 GIT binary patch literal 370070 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 diff --git a/lib/actions/header.js b/lib/actions/header.js index bfc05faf..3b5ed07b 100644 --- a/lib/actions/header.js +++ b/lib/actions/header.js @@ -1,5 +1,5 @@ import {CLOSE_TAB, CHANGE_TAB} from '../constants/tabs'; -import {UI_WINDOW_MAXIMIZE, UI_WINDOW_UNMAXIMIZE} from '../constants/ui'; +import {UI_WINDOW_MAXIMIZE, UI_WINDOW_UNMAXIMIZE, UI_OPEN_HAMBURGER_MENU, UI_WINDOW_MINIMIZE, UI_WINDOW_CLOSE} from '../constants/ui'; import rpc from '../rpc'; import {userExitTermGroup, setActiveGroup} from './term-groups'; @@ -48,3 +48,36 @@ export function unmaximize() { }); }; } + +export function openHamburgerMenu(coordinates) { + return dispatch => { + dispatch({ + type: UI_OPEN_HAMBURGER_MENU, + effect() { + rpc.emit('open hamburger menu', coordinates); + } + }); + }; +} + +export function minimize() { + return dispatch => { + dispatch({ + type: UI_WINDOW_MINIMIZE, + effect() { + rpc.emit('minimize'); + } + }); + }; +} + +export function close() { + return dispatch => { + dispatch({ + type: UI_WINDOW_CLOSE, + effect() { + rpc.emit('close'); + } + }); + }; +} diff --git a/lib/actions/ui.js b/lib/actions/ui.js index 7e9ab35f..133aa774 100644 --- a/lib/actions/ui.js +++ b/lib/actions/ui.js @@ -201,7 +201,12 @@ export function moveTo(i) { } export function showPreferences() { - const editorFallback = process.platform === 'win32' ? 'notepad' : 'nano'; + // eslint-disable-next-line no-template-curly-in-string + const command = process.platform === 'win32' ? ' start notepad "%userprofile%\\.hyper.js"' : ' bash -c \'exec env ${EDITOR:=nano} ~/.hyper.js\''; + const message = process.platform === 'win32' ? + ' echo Attempting to open ^%userprofile^%\\.hyper.js with notepad' : + ' echo Attempting to open ~/.hyper.js with your \$EDITOR'; // eslint-disable-line no-useless-escape + return dispatch => { dispatch({ type: UI_SHOW_PREFERENCES, @@ -212,12 +217,12 @@ export function showPreferences() { rpc.once('session data', () => { dispatch(sendSessionData( uid, - // Leading space prevents command to be stored in shell history - [' echo Attempting to open ~/.hyper.js with your \$EDITOR', // eslint-disable-line no-useless-escape - ' echo If it fails, open it manually with your favorite editor!', - ' bash -c \'exec env ${EDITOR:=' + editorFallback + '} ~/.hyper.js\'', - '' - ].join('\n') + [ // Leading space prevents command to be stored in shell history + ' echo Attempting to open ~/.hyper.js with your \$EDITOR', // eslint-disable-line no-useless-escape + message, + command, + '' + ].join('\n\r') )); }); }); diff --git a/lib/components/header.js b/lib/components/header.js index 8daea89c..a02b9afe 100644 --- a/lib/components/header.js +++ b/lib/components/header.js @@ -14,6 +14,10 @@ export default class Header extends Component { this.onChangeIntent = this.onChangeIntent.bind(this); this.handleHeaderClick = this.handleHeaderClick.bind(this); this.handleHeaderMouseDown = this.handleHeaderMouseDown.bind(this); + this.handleHamburgerMenuClick = this.handleHamburgerMenuClick.bind(this); + this.handleMaximizeClick = this.handleMaximizeClick.bind(this); + this.handleMinimizeClick = this.handleMinimizeClick.bind(this); + this.handleCloseClick = this.handleCloseClick.bind(this); } onChangeIntent(active) { @@ -68,11 +72,51 @@ export default class Header extends Component { } } + handleHamburgerMenuClick(event) { + let {right: x, bottom: y} = event.currentTarget.getBoundingClientRect(); + x -= 15; // to compensate padding + y -= 12; // ^ same + this.props.openHamburgerMenu({x, y}); + } + + handleMaximizeClick() { + if (this.props.maximized) { + this.props.unmaximize(); + } else { + this.props.maximize(); + } + } + + handleMinimizeClick() { + this.props.minimize(); + } + + handleCloseClick() { + this.props.close(); + } + componentWillUnmount() { delete this.clicks; clearTimeout(this.clickTimer); } + getWindowHeaderConfig() { + const {showHamburgerMenu, showWindowControls} = this.props; + const ret = { + hambMenu: process.platform === 'win32', // show by default on windows + winCtrls: !this.props.isMac // show by default on windows and linux + }; + if (!this.props.isMac) { // allow the user to override the defaults if not on macOS + if (showHamburgerMenu !== '') { + ret.hambMenu = showHamburgerMenu; + } + if (showWindowControls !== '') { + ret.winCtrls = showWindowControls; + } + } + return ret; + } + template(css) { const {isMac} = this.props; const props = getTabsProps(this.props, { @@ -81,11 +125,60 @@ export default class Header extends Component { onClose: this.props.onCloseTab, onChange: this.onChangeIntent }); + const {borderColor} = props; + let title = 'Hyper'; + if (props.tabs.length === 1 && props.tabs[0].title) { + // if there's only one tab we use its title as the window title + title = props.tabs[0].title; + } + const {hambMenu, winCtrls} = this.getWindowHeaderConfig(); + const left = winCtrls === 'left'; return (

+ { + !isMac && +
1 && 'windowHeaderWithBorder')} + style={{borderColor}} + > + { + hambMenu && + + + + } + {title} + { + winCtrls && +
+ + + + + + + + + +
+ } +
+ } { this.props.customChildrenBefore } { this.props.customChildren } @@ -105,7 +198,77 @@ export default class Header extends Component { headerRounded: { borderTopLeftRadius: '4px', borderTopRightRadius: '4px' - } + }, + + windowHeader: { + height: '34px', + width: '100%', + position: 'fixed', + top: '1px', + left: '1px', + right: '1px', + WebkitAppRegion: 'drag', + display: 'flex', + justifyContent: 'center', + alignItems: 'center' + }, + + windowHeaderWithBorder: { + borderColor: '#ccc', + borderBottomStyle: 'solid', + borderBottomWidth: '1px' + }, + + appTitle: { + fontSize: '12px', + fontFamily: `-apple-system, BlinkMacSystemFont, + "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", + "Droid Sans", "Helvetica Neue", sans-serif` + }, + + shape: { + width: '40px', + height: '34px', + padding: '12px 15px 12px 15px', + WebkitAppRegion: 'no-drag', + color: '#808080', + shapeRendering: 'crispEdges', + ':hover': { + color: '#FFFFFF' + } + }, + + hamburgerMenuLeft: { + position: 'fixed', + top: '0', + left: '0' + }, + + hamburgerMenuRight: { + position: 'fixed', + top: '0', + right: '0' + }, + + windowControls: { + display: 'flex', + width: '120px', + height: '34px', + justifyContent: 'space-between', + position: 'fixed', + right: '0' + }, + + windowControlsLeft: {left: '0px'}, + + closeWindowLeft: {order: 1}, + + minimizeWindowLeft: {order: 2}, + + maximizeWindowLeft: {order: 3}, + + closeWindow: {':hover': {color: '#FE354E'}} }; } diff --git a/lib/components/tab.js b/lib/components/tab.js index a51eb8d1..7d3ef5d2 100644 --- a/lib/components/tab.js +++ b/lib/components/tab.js @@ -79,7 +79,7 @@ export default class Tab extends Component { onClick={this.props.onClose} > - + { this.props.customChildren } @@ -185,7 +185,8 @@ export default class Tab extends Component { width: '6px', height: '6px', verticalAlign: 'middle', - fill: 'currentColor' + fill: 'currentColor', + shapeRendering: 'crispEdges' } }; } diff --git a/lib/components/tabs.js b/lib/components/tabs.js index a9811328..26150b24 100644 --- a/lib/components/tabs.js +++ b/lib/components/tabs.js @@ -18,40 +18,45 @@ export default class Tabs extends Component { onClose } = this.props; - return (