summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2018-08-24 21:57:42 +0300
committerAndrej Mihajlov <and@mullvad.net>2018-09-03 12:07:48 +0300
commitd363f33140d57c1f6f5154bf21075df5393922dc (patch)
treec593125ccc65f6f5ad2efcf964740a2d45e27737
parent912c0b8d18c63e27cdd0f3863529ee9c3f8bd854 (diff)
downloadmullvadvpn-d363f33140d57c1f6f5154bf21075df5393922dc.tar.xz
mullvadvpn-d363f33140d57c1f6f5154bf21075df5393922dc.zip
Calculate arrow position relative to tray icon
-rw-r--r--CHANGELOG.md9
-rw-r--r--gui/packages/desktop/src/main/window-controller.js28
-rw-r--r--gui/packages/desktop/src/renderer/app.js11
-rw-r--r--gui/packages/desktop/src/renderer/components/PlatformWindow.js32
-rw-r--r--gui/packages/desktop/src/renderer/containers/PlatformWindowContainer.js18
-rw-r--r--gui/packages/desktop/src/renderer/redux/store.js34
-rw-r--r--gui/packages/desktop/src/renderer/redux/window/actions.js17
-rw-r--r--gui/packages/desktop/src/renderer/redux/window/reducers.js22
-rw-r--r--gui/packages/desktop/src/renderer/routes.js6
9 files changed, 148 insertions, 29 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 37a0a438db..a38cf03320 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,13 +27,19 @@ Line wrap the file at 100 chars. Th
- Add option to enable or disable IPv6 on the tunnel interface.
- Log panics in the daemon to the log file.
- Warn in the Settings screen if a new version is available.
+
#### Windows
- Extend uninstaller to also remove logs, cache and optionally settings.
### Fixed
+- Fix incorrect window position when using external display.
+
#### Linux
- The app window is now shown in its previous location, instead of at the center of the screen.
+#### macOS
+- Fix edge cases when window's arrow appeared misaligned and pointed to the wrong menubar item.
+
### Changed
- The "Buy more credit" button is changed to open a dedicated account login page instead of one
having a create account form first.
@@ -43,9 +49,6 @@ Line wrap the file at 100 chars. Th
of the socket can be controlled with `MULLVAD_RPC_SOCKET_PATH`.
- Update the relay list if it's out of date when the daemon starts.
-### Fixed
-- Fix incorrect window position when using external display.
-
## [2018.2] - 2018-08-13
This release is identical to 2018.2-beta3
diff --git a/gui/packages/desktop/src/main/window-controller.js b/gui/packages/desktop/src/main/window-controller.js
index dc6165c5f3..704f9bae3e 100644
--- a/gui/packages/desktop/src/main/window-controller.js
+++ b/gui/packages/desktop/src/main/window-controller.js
@@ -5,8 +5,13 @@ import type { BrowserWindow, Tray, Display } from 'electron';
type Position = { x: number, y: number };
+export type WindowShapeParameters = {
+ arrowPosition?: number,
+};
+
interface WindowPositioning {
getPosition(window: BrowserWindow): Position;
+ getWindowShapeParameters(window: BrowserWindow): WindowShapeParameters;
}
class StandaloneWindowPositioning implements WindowPositioning {
@@ -23,6 +28,10 @@ class StandaloneWindowPositioning implements WindowPositioning {
return { x, y };
}
+
+ getWindowShapeParameters(_window: BrowserWindow): WindowShapeParameters {
+ return {};
+ }
}
class AttachedToTrayWindowPositioning implements WindowPositioning {
@@ -83,6 +92,15 @@ class AttachedToTrayWindowPositioning implements WindowPositioning {
};
}
+ getWindowShapeParameters(window: BrowserWindow): WindowShapeParameters {
+ const trayBounds = this._tray.getBounds();
+ const windowBounds = window.getBounds();
+ const arrowPosition = trayBounds.x - windowBounds.x + trayBounds.width * 0.5;
+ return {
+ arrowPosition,
+ };
+ }
+
_getTrayPlacement() {
switch (process.platform) {
case 'darwin':
@@ -156,6 +174,7 @@ export default class WindowController {
const window = this._window;
this._updatePosition();
+ this._notifyUpdateWindowShape();
window.show();
window.focus();
@@ -166,7 +185,13 @@ export default class WindowController {
this._window.setPosition(x, y, false);
}
- // Installs display event handlers to update the window position on any changes in the display or workarea dimensions.
+ _notifyUpdateWindowShape() {
+ const shapeParameters = this._windowPositioning.getWindowShapeParameters(this._window);
+ this._window.webContents.send('update-window-shape', shapeParameters);
+ }
+
+ // Installs display event handlers to update the window position on any changes in the display or
+ // workarea dimensions.
_installDisplayMetricsHandler() {
screen.addListener('display-metrics-changed', this._onDisplayMetricsChanged);
this._window.once('closed', () => {
@@ -177,6 +202,7 @@ export default class WindowController {
_onDisplayMetricsChanged = (_event: any, _display: Display, changedMetrics: Array<string>) => {
if (changedMetrics.includes('workArea') && this._window.isVisible()) {
this._updatePosition();
+ this._notifyUpdateWindowShape();
}
};
diff --git a/gui/packages/desktop/src/renderer/app.js b/gui/packages/desktop/src/renderer/app.js
index 03fae931dd..e88d3d619c 100644
--- a/gui/packages/desktop/src/renderer/app.js
+++ b/gui/packages/desktop/src/renderer/app.js
@@ -1,6 +1,7 @@
// @flow
import log from 'electron-log';
+import { remote, webFrame, ipcRenderer } from 'electron';
import * as React from 'react';
import { bindActionCreators } from 'redux';
import { Provider } from 'react-redux';
@@ -10,7 +11,6 @@ import {
replace as replaceHistory,
} from 'connected-react-router';
import { createMemoryHistory } from 'history';
-import { remote, webFrame, ipcRenderer } from 'electron';
import makeRoutes from './routes';
import ReconnectionBackoff from './lib/reconnection-backoff';
@@ -25,7 +25,9 @@ import connectionActions from './redux/connection/actions';
import settingsActions from './redux/settings/actions';
import versionActions from './redux/version/actions';
import daemonActions from './redux/daemon/actions';
+import windowActions from './redux/window/actions';
+import type { WindowShapeParameters } from '../main/window-controller';
import type {
DaemonRpcProtocol,
AccountData,
@@ -58,6 +60,7 @@ export default class AppRenderer {
settings: bindActionCreators(settingsActions, dispatch),
version: bindActionCreators(versionActions, dispatch),
daemon: bindActionCreators(daemonActions, dispatch),
+ window: bindActionCreators(windowActions, dispatch),
history: bindActionCreators(
{
push: pushHistory,
@@ -85,6 +88,12 @@ export default class AppRenderer {
}
});
+ ipcRenderer.on('update-window-shape', (_event, shapeParams: WindowShapeParameters) => {
+ if (typeof shapeParams.arrowPosition === 'number') {
+ this._reduxActions.window.updateWindowArrowPosition(shapeParams.arrowPosition);
+ }
+ });
+
// disable pinch to zoom
webFrame.setVisualZoomLevelLimits(1, 1);
}
diff --git a/gui/packages/desktop/src/renderer/components/PlatformWindow.js b/gui/packages/desktop/src/renderer/components/PlatformWindow.js
index 6c2c85e023..f143c3d381 100644
--- a/gui/packages/desktop/src/renderer/components/PlatformWindow.js
+++ b/gui/packages/desktop/src/renderer/components/PlatformWindow.js
@@ -3,16 +3,32 @@
import * as React from 'react';
import { Component, View, Styles } from 'reactxp';
-const styles = {
- darwin: Styles.createViewStyle({
- WebkitMask: `
- url(../assets/images/app-triangle.svg) 50% 0% no-repeat,
- url(../assets/images/app-header-backdrop.svg) no-repeat`,
- }),
+type Props = {
+ arrowPosition?: number,
};
-export default class PlatformWindow extends Component {
+export default class PlatformWindow extends Component<Props> {
render() {
- return <View style={styles[process.platform]}>{this.props.children}</View>;
+ let style = undefined;
+
+ if (process.platform === 'darwin') {
+ const arrowPosition = this.props.arrowPosition;
+ let arrowPositionCss = '50%';
+
+ if (typeof arrowPosition === 'number') {
+ const arrowWidth = 30;
+ const adjustedArrowPosition = arrowPosition - arrowWidth * 0.5;
+ arrowPositionCss = `${adjustedArrowPosition}px`;
+ }
+
+ const webkitMask = [
+ `url(../assets/images/app-triangle.svg) ${arrowPositionCss} 0% no-repeat`,
+ `url(../assets/images/app-header-backdrop.svg) no-repeat`,
+ ];
+
+ style = Styles.createViewStyle({ WebkitMask: webkitMask.join(',') }, false);
+ }
+
+ return <View style={style}>{this.props.children}</View>;
}
}
diff --git a/gui/packages/desktop/src/renderer/containers/PlatformWindowContainer.js b/gui/packages/desktop/src/renderer/containers/PlatformWindowContainer.js
new file mode 100644
index 0000000000..4d48b60a95
--- /dev/null
+++ b/gui/packages/desktop/src/renderer/containers/PlatformWindowContainer.js
@@ -0,0 +1,18 @@
+// @flow
+
+import { connect } from 'react-redux';
+import PlatformWindow from '../components/PlatformWindow';
+
+import type { ReduxState, ReduxDispatch } from '../redux/store';
+import type { SharedRouteProps } from '../routes';
+
+const mapStateToProps = (state: ReduxState) => ({
+ arrowPosition: state.window.arrowPosition,
+});
+
+const mapDispatchToProps = (_dispatch: ReduxDispatch, _props: SharedRouteProps) => ({});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(PlatformWindow);
diff --git a/gui/packages/desktop/src/renderer/redux/store.js b/gui/packages/desktop/src/renderer/redux/store.js
index 58c6645a45..133402e590 100644
--- a/gui/packages/desktop/src/renderer/redux/store.js
+++ b/gui/packages/desktop/src/renderer/redux/store.js
@@ -3,18 +3,20 @@
import { createStore, applyMiddleware, combineReducers, compose } from 'redux';
import { routerMiddleware, connectRouter, push, replace } from 'connected-react-router';
-import account from './account/reducers';
+import accountReducer from './account/reducers';
import accountActions from './account/actions';
-import connection from './connection/reducers';
+import connectionReducer from './connection/reducers';
import connectionActions from './connection/actions';
-import settings from './settings/reducers';
+import settingsReducer from './settings/reducers';
import settingsActions from './settings/actions';
-import support from './support/reducers';
+import supportReducer from './support/reducers';
import supportActions from './support/actions';
-import version from './version/reducers';
+import versionReducer from './version/reducers';
import versionActions from './version/actions';
-import daemon from './daemon/reducers';
+import daemonReducer from './daemon/reducers';
import daemonActions from './daemon/actions';
+import windowReducer from './window/reducers';
+import windowActions from './window/actions';
import type { Store, StoreEnhancer } from 'redux';
import type { History } from 'history';
@@ -24,6 +26,7 @@ import type { SettingsReduxState } from './settings/reducers';
import type { SupportReduxState } from './support/reducers';
import type { VersionReduxState } from './version/reducers';
import type { DaemonReduxState } from './daemon/reducers';
+import type { WindowReduxState } from './window/reducers';
import type { AccountAction } from './account/actions';
import type { ConnectionAction } from './connection/actions';
@@ -31,6 +34,7 @@ import type { SettingsAction } from './settings/actions';
import type { SupportAction } from './support/actions';
import type { VersionAction } from './version/actions';
import type { DaemonAction } from './daemon/actions';
+import type { WindowAction } from './window/actions';
export type ReduxState = {
account: AccountReduxState,
@@ -39,6 +43,7 @@ export type ReduxState = {
support: SupportReduxState,
version: VersionReduxState,
daemon: DaemonReduxState,
+ window: WindowReduxState,
};
export type ReduxAction =
@@ -47,7 +52,8 @@ export type ReduxAction =
| SettingsAction
| SupportAction
| VersionAction
- | DaemonAction;
+ | DaemonAction
+ | WindowAction;
export type ReduxStore = Store<ReduxState, ReduxAction, ReduxDispatch>;
export type ReduxGetState = () => ReduxState;
export type ReduxDispatch = (action: ReduxAction) => any;
@@ -65,17 +71,19 @@ export default function configureStore(
...supportActions,
...versionActions,
...daemonActions,
+ ...windowActions,
pushRoute: (route) => push(route),
replaceRoute: (route) => replace(route),
};
const reducers = {
- account,
- connection,
- settings,
- support,
- version,
- daemon,
+ account: accountReducer,
+ connection: connectionReducer,
+ settings: settingsReducer,
+ support: supportReducer,
+ version: versionReducer,
+ daemon: daemonReducer,
+ window: windowReducer,
};
const middlewares = [router];
diff --git a/gui/packages/desktop/src/renderer/redux/window/actions.js b/gui/packages/desktop/src/renderer/redux/window/actions.js
new file mode 100644
index 0000000000..3da45e6c65
--- /dev/null
+++ b/gui/packages/desktop/src/renderer/redux/window/actions.js
@@ -0,0 +1,17 @@
+// @flow
+
+export type UpdateWindowArrowPositionAction = {
+ type: 'UPDATE_WINDOW_ARROW_POSITION',
+ arrowPosition: number,
+};
+
+export type WindowAction = UpdateWindowArrowPositionAction;
+
+function updateWindowArrowPosition(arrowPosition: number): UpdateWindowArrowPositionAction {
+ return {
+ type: 'UPDATE_WINDOW_ARROW_POSITION',
+ arrowPosition,
+ };
+}
+
+export default { updateWindowArrowPosition };
diff --git a/gui/packages/desktop/src/renderer/redux/window/reducers.js b/gui/packages/desktop/src/renderer/redux/window/reducers.js
new file mode 100644
index 0000000000..91fcfeadd9
--- /dev/null
+++ b/gui/packages/desktop/src/renderer/redux/window/reducers.js
@@ -0,0 +1,22 @@
+// @flow
+
+import type { ReduxAction } from '../store';
+
+export type WindowReduxState = {
+ arrowPosition?: number,
+};
+
+const initialState: WindowReduxState = {};
+
+export default function(
+ state: WindowReduxState = initialState,
+ action: ReduxAction,
+): WindowReduxState {
+ switch (action.type) {
+ case 'UPDATE_WINDOW_ARROW_POSITION':
+ return { ...state, arrowPosition: action.arrowPosition };
+
+ default:
+ return state;
+ }
+}
diff --git a/gui/packages/desktop/src/renderer/routes.js b/gui/packages/desktop/src/renderer/routes.js
index 107ee616be..9a93a76936 100644
--- a/gui/packages/desktop/src/renderer/routes.js
+++ b/gui/packages/desktop/src/renderer/routes.js
@@ -3,7 +3,7 @@
import * as React from 'react';
import { Switch, Route, Redirect } from 'react-router';
import TransitionContainer from './components/TransitionContainer';
-import PlatformWindow from './components/PlatformWindow';
+import PlatformWindowContainer from './containers/PlatformWindowContainer';
import LaunchPage from './containers/LaunchPage';
import LoginPage from './containers/LoginPage';
import ConnectPage from './containers/ConnectPage';
@@ -120,7 +120,7 @@ export default function makeRoutes(
previousRoute = toRoute;
return (
- <PlatformWindow>
+ <PlatformWindowContainer>
<TransitionContainer {...transitionProps}>
<Switch key={location.key} location={location}>
<LaunchRoute exact path="/" component={LaunchPage} />
@@ -134,7 +134,7 @@ export default function makeRoutes(
<PrivateRoute exact path="/select-location" component={SelectLocationPage} />
</Switch>
</TransitionContainer>
- </PlatformWindow>
+ </PlatformWindowContainer>
);
}}
/>