Skip to content

Commit

Permalink
Show full disk access instructions in split tunneling view
Browse files Browse the repository at this point in the history
  • Loading branch information
raksooo authored and dlon committed Oct 29, 2024
1 parent 0b277b7 commit 5ff1269
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 8 deletions.
4 changes: 4 additions & 0 deletions gui/locales/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
7 changes: 7 additions & 0 deletions gui/src/main/daemon-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
3 changes: 3 additions & 0 deletions gui/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
2 changes: 2 additions & 0 deletions gui/src/renderer/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand Down
4 changes: 4 additions & 0 deletions gui/src/renderer/components/SmallButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
76 changes: 68 additions & 8 deletions gui/src/renderer/components/SplitTunnelingSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
StyledPageCover,
StyledSearchBar,
StyledSpinnerRow,
StyledSystemSettingsButton,
} from './SplitTunnelingSettingsStyles';
import Switch from './Switch';

Expand Down Expand Up @@ -313,16 +314,36 @@ 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,
);

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);
Expand Down Expand Up @@ -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 && (
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions gui/src/renderer/components/SplitTunnelingSettingsStyles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -122,3 +123,8 @@ export const StyledSearchBar = styled(SearchBar)({
marginRight: measurements.viewMargin,
marginBottom: measurements.buttonVerticalMargin,
});

export const StyledSystemSettingsButton = styled(SmallButton)({
width: '100%',
marginTop: '24px',
});
3 changes: 3 additions & 0 deletions gui/src/shared/ipc-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>(),
Expand Down

0 comments on commit 5ff1269

Please sign in to comment.