summaryrefslogtreecommitdiffhomepage
path: root/gui
diff options
context:
space:
mode:
authorOskar <oskar@mullvad.net>2024-10-29 11:46:14 +0100
committerDavid Lönnhager <david.l@mullvad.net>2024-10-29 16:33:14 +0100
commit5ff12692ebc03e6ce4a8760cd6da1568532e631d (patch)
tree9aff356c73c260cfc2172b7a3ee09957117982dd /gui
parent0b277b75a46d5601647da422d8f0d92f8db11ecc (diff)
downloadmullvadvpn-5ff12692ebc03e6ce4a8760cd6da1568532e631d.tar.xz
mullvadvpn-5ff12692ebc03e6ce4a8760cd6da1568532e631d.zip
Show full disk access instructions in split tunneling view
Diffstat (limited to 'gui')
-rw-r--r--gui/locales/messages.pot4
-rw-r--r--gui/src/main/daemon-rpc.ts7
-rw-r--r--gui/src/main/index.ts3
-rw-r--r--gui/src/renderer/app.tsx2
-rw-r--r--gui/src/renderer/components/SmallButton.tsx4
-rw-r--r--gui/src/renderer/components/SplitTunnelingSettings.tsx76
-rw-r--r--gui/src/renderer/components/SplitTunnelingSettingsStyles.tsx6
-rw-r--r--gui/src/shared/ipc-schema.ts3
8 files changed, 97 insertions, 8 deletions
diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot
index 517423d420..4d588a5a9e 100644
--- a/gui/locales/messages.pot
+++ b/gui/locales/messages.pot
@@ -1600,6 +1600,10 @@ msgctxt "split-tunneling-view"
msgid "Please try again or send a problem report."
msgstr ""
+msgctxt "split-tunneling-view"
+msgid "To use split tunneling please enable “Full disk access” for “Mullvad VPN” in the macOS system settings."
+msgstr ""
+
#. Error message showed in a dialog when an application fails to launch.
msgctxt "split-tunneling-view"
msgid "Unable to launch selection. %(detailedErrorMessage)s"
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index c86bed047f..8cea0d4008 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -438,6 +438,13 @@ export class DaemonRpc extends GrpcClient {
await this.callBool(this.client.setSplitTunnelState, enabled);
}
+ public async needFullDiskPermissions(): Promise<boolean> {
+ const needFullDiskPermissions = await this.callEmpty<BoolValue>(
+ this.client.needFullDiskPermissions,
+ );
+ return needFullDiskPermissions.getValue();
+ }
+
public async checkVolumes(): Promise<void> {
await this.callEmpty(this.client.checkVolumes);
}
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index 400fe39d2a..c9067c78e2 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -832,6 +832,9 @@ class ApplicationMain
splitTunneling!.removeApplicationFromCache(application);
return Promise.resolve();
});
+ IpcMainEventChannel.macOsSplitTunneling.handleNeedFullDiskPermissions(() => {
+ return this.daemonRpc.needFullDiskPermissions();
+ });
IpcMainEventChannel.app.handleQuit(() => this.disconnectAndQuit());
IpcMainEventChannel.app.handleOpenUrl(async (url) => {
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index d36c27aa14..ddbb43aab7 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -345,6 +345,8 @@ export default class AppRenderer {
IpcRendererEventChannel.splitTunneling.addApplication(application);
public forgetManuallyAddedSplitTunnelingApplication = (application: ISplitTunnelingApplication) =>
IpcRendererEventChannel.splitTunneling.forgetManuallyAddedApplication(application);
+ public needFullDiskPermissions = () =>
+ IpcRendererEventChannel.macOsSplitTunneling.needFullDiskPermissions();
public setObfuscationSettings = (obfuscationSettings: ObfuscationSettings) =>
IpcRendererEventChannel.settings.setObfuscationSettings(obfuscationSettings);
public setEnableDaita = (value: boolean) =>
diff --git a/gui/src/renderer/components/SmallButton.tsx b/gui/src/renderer/components/SmallButton.tsx
index e88d719952..c91fbbdb20 100644
--- a/gui/src/renderer/components/SmallButton.tsx
+++ b/gui/src/renderer/components/SmallButton.tsx
@@ -53,6 +53,10 @@ const StyledSmallButton = styled.button<StyledSmallButtonProps>(smallText, (prop
alignItems: 'center',
justifyContent: 'center',
+ '&&:not(& + &&)': {
+ marginLeft: '0px',
+ },
+
[`${SmallButtonGroupStart} &&`]: {
marginLeft: 0,
marginRight: `${BUTTON_GROUP_GAP}px`,
diff --git a/gui/src/renderer/components/SplitTunnelingSettings.tsx b/gui/src/renderer/components/SplitTunnelingSettings.tsx
index ed999ba867..7e8830e6f8 100644
--- a/gui/src/renderer/components/SplitTunnelingSettings.tsx
+++ b/gui/src/renderer/components/SplitTunnelingSettings.tsx
@@ -43,6 +43,7 @@ import {
StyledPageCover,
StyledSearchBar,
StyledSpinnerRow,
+ StyledSystemSettingsButton,
} from './SplitTunnelingSettingsStyles';
import Switch from './Switch';
@@ -313,9 +314,12 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
removeSplitTunnelingApplication,
forgetManuallyAddedSplitTunnelingApplication,
getSplitTunnelingApplications,
+ needFullDiskPermissions,
setSplitTunnelingState,
} = useAppContext();
- const splitTunnelingEnabled = useSelector((state: IReduxState) => state.settings.splitTunneling);
+ const splitTunnelingEnabledValue = useSelector(
+ (state: IReduxState) => state.settings.splitTunneling,
+ );
const splitTunnelingApplications = useSelector(
(state: IReduxState) => state.settings.splitTunnelingApplications,
);
@@ -323,6 +327,23 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
const [searchTerm, setSearchTerm] = useState('');
const [applications, setApplications] = useState<ISplitTunnelingApplication[]>();
+ const [splitTunnelingAvailable, setSplitTunnelingAvailable] = useState(
+ window.env.platform === 'darwin' ? undefined : true,
+ );
+
+ const splitTunnelingEnabled = splitTunnelingEnabledValue && (splitTunnelingAvailable ?? false);
+
+ const fetchNeedFullDiskPermissions = useCallback(async () => {
+ const needPermissions = await needFullDiskPermissions();
+ setSplitTunnelingAvailable(!needPermissions);
+ }, [needFullDiskPermissions]);
+
+ useEffect((): void | (() => void) => {
+ if (window.env.platform === 'darwin') {
+ void fetchNeedFullDiskPermissions();
+ }
+ }, [fetchNeedFullDiskPermissions]);
+
const onMount = useEffectEvent(async () => {
const { fromCache, applications } = await getSplitTunnelingApplications();
setApplications(applications);
@@ -441,14 +462,25 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
<SettingsHeader>
<StyledHeaderTitleContainer>
<StyledHeaderTitle>{strings.splitTunneling}</StyledHeaderTitle>
- <Switch isOn={splitTunnelingEnabled} onChange={setSplitTunnelingState} />
+ <Switch
+ isOn={splitTunnelingEnabled}
+ disabled={!splitTunnelingAvailable}
+ onChange={setSplitTunnelingState}
+ />
</StyledHeaderTitleContainer>
- <HeaderSubTitle>
- {messages.pgettext(
- 'split-tunneling-view',
- 'Choose the apps you want to exclude from the VPN tunnel.',
- )}
- </HeaderSubTitle>
+ <MacOsSplitTunnelingAvailability
+ needFullDiskPermissions={
+ window.env.platform === 'darwin' && splitTunnelingAvailable === false
+ }
+ />
+ {splitTunnelingAvailable ? (
+ <HeaderSubTitle>
+ {messages.pgettext(
+ 'split-tunneling-view',
+ 'Choose the apps you want to exclude from the VPN tunnel.',
+ )}
+ </HeaderSubTitle>
+ ) : null}
</SettingsHeader>
{splitTunnelingEnabled && (
@@ -495,6 +527,34 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro
);
}
+interface MacOsSplitTunnelingAvailabilityProps {
+ needFullDiskPermissions: boolean;
+}
+
+function MacOsSplitTunnelingAvailability({
+ needFullDiskPermissions,
+}: MacOsSplitTunnelingAvailabilityProps) {
+ const { showFullDiskAccessSettings } = useAppContext();
+
+ return (
+ <>
+ {needFullDiskPermissions === true ? (
+ <>
+ <HeaderSubTitle>
+ {messages.pgettext(
+ 'split-tunneling-view',
+ 'To use split tunneling please enable “Full disk access” for “Mullvad VPN” in the macOS system settings.',
+ )}
+ </HeaderSubTitle>
+ <StyledSystemSettingsButton onClick={showFullDiskAccessSettings}>
+ Open System Settings
+ </StyledSystemSettingsButton>
+ </>
+ ) : null}
+ </>
+ );
+}
+
interface IApplicationListProps<T extends IApplication> {
applications: T[] | undefined;
rowRenderer: (application: T) => React.ReactElement;
diff --git a/gui/src/renderer/components/SplitTunnelingSettingsStyles.tsx b/gui/src/renderer/components/SplitTunnelingSettingsStyles.tsx
index 1aea5108a1..a2019fba8d 100644
--- a/gui/src/renderer/components/SplitTunnelingSettingsStyles.tsx
+++ b/gui/src/renderer/components/SplitTunnelingSettingsStyles.tsx
@@ -8,6 +8,7 @@ import ImageView from './ImageView';
import { NavigationScrollbars } from './NavigationBar';
import SearchBar from './SearchBar';
import { HeaderTitle } from './SettingsHeader';
+import { SmallButton } from './SmallButton';
export const StyledPageCover = styled.div<{ $show: boolean }>((props) => ({
position: 'absolute',
@@ -122,3 +123,8 @@ export const StyledSearchBar = styled(SearchBar)({
marginRight: measurements.viewMargin,
marginBottom: measurements.buttonVerticalMargin,
});
+
+export const StyledSystemSettingsButton = styled(SmallButton)({
+ width: '100%',
+ marginTop: '24px',
+});
diff --git a/gui/src/shared/ipc-schema.ts b/gui/src/shared/ipc-schema.ts
index 954dce1680..a2282e2849 100644
--- a/gui/src/shared/ipc-schema.ts
+++ b/gui/src/shared/ipc-schema.ts
@@ -240,6 +240,9 @@ export const ipcSchema = {
getApplications: invoke<void, ILinuxSplitTunnelingApplication[]>(),
launchApplication: invoke<ILinuxSplitTunnelingApplication | string, LaunchApplicationResult>(),
},
+ macOsSplitTunneling: {
+ needFullDiskPermissions: invoke<void, boolean>(),
+ },
splitTunneling: {
'': notifyRenderer<ISplitTunnelingApplication[]>(),
setState: invoke<boolean, void>(),