Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Xcode 16 simulator fixes #10149

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/Building/ios.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,5 @@ Once Xcode has opened the project, select the `mozillavpn` target and start the

Tips:
* If you can't see a simulator target in the Xcode interface, look at Product -> Destination -> Destination Architectures -> Show Both
* Due to lack of low level networking support, it is not possible to turn on the VPN from the iOS simulator in Xcode.
* Simulator builds can only be run on Rosetta versions of simulators. Simulator builds use the mocked VPN controller.
* When switching between building for Simulator and building for a device, the build folder must be completely removed.
24 changes: 0 additions & 24 deletions docs/Building/macos.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,6 @@ Install extra conda packages

./scripts/macos/conda_install_extras.sh

Your Xcode install comes with a copy of the MacOS-SDK.
We need to tell the conda environment where to find it.

Find the sdk path

xcrun --sdk macosx --show-sdk-path

If xcrun didn't work, default paths where you probably find your SDK:
* Default Xcode-command-line tool path: `/Library/Developer/CommandLineTools/SDKs/MacOSX.<VersionNumber>.sdk`
* Default Xcode.app path: `/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk`

Add it to the conda env

conda env config vars set SDKROOT=$SDK_PATH

Reactivate your conda env

conda activate vpn

You can view your set variables

conda env config vars list

The variable config step only needs to be done once.
When you next want to start building the VPN, all you need to do is activate your conda environment (`conda activate vpn`).

## Get Qt
Expand Down
4 changes: 4 additions & 0 deletions src/commands/commandstatus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ int CommandStatus::run(QStringList& tokens) {
case Controller::StateSwitching:
stream << "switching";
break;

case Controller::State::StateOnboarding:
stream << "onboarding";
break;
}

stream << Qt::endl;
Expand Down
8 changes: 8 additions & 0 deletions src/commands/commandui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ int CommandUI::run(QStringList& tokens) {
MockDaemon* daemon = new MockDaemon(qApp);
qputenv("MVPN_CONTROL_SOCKET", daemon->socketPath().toLocal8Bit());
}
#ifdef MZ_IOS
else if (!qEnvironmentVariable("SIMULATOR_DEVICE_NAME").isEmpty()) {
// If we are running in the iOS device simulator, the network extension
// is not supported in this environment - use a mocked daemon instead.
MockDaemon* daemon = new MockDaemon(qApp);
qputenv("MVPN_CONTROL_SOCKET", daemon->socketPath().toLocal8Bit());
}
#endif

MozillaVPN vpn;
logger.info() << "MozillaVPN" << Constants::versionString();
Expand Down
37 changes: 31 additions & 6 deletions src/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ Controller::Reason stateToReason(Controller::State state) {
return Controller::ReasonConfirming;
}

if (state == Controller::StateOnboarding) {
return Controller::ReasonOnboarding;
}

return Controller::ReasonNone;
}
} // namespace
Expand Down Expand Up @@ -119,6 +123,12 @@ QString Controller::useLocalSocketPath() const {
}
#elif defined(MZ_WINDOWS)
return Constants::WINDOWS_DAEMON_PATH;
#elif defined(MZ_IOS)
// The IOS simulator also uses a mocked daemon.
bool isSimDevice = !qEnvironmentVariable("SIMULATOR_DEVICE_NAME").isEmpty();
if (isSimDevice && !path.isEmpty()) {
return path;
}
#endif

// Otherwise, we will need some other controller.
Expand Down Expand Up @@ -329,6 +339,8 @@ qint64 Controller::connectionTimestamp() const {
case Controller::State::StateInitializing:
[[fallthrough]];
case Controller::State::StateOff:
[[fallthrough]];
case Controller::State::StateOnboarding:
return 0;
case Controller::State::StateOn:
[[fallthrough]];
Expand Down Expand Up @@ -628,7 +640,7 @@ void Controller::activateNext() {
return;
}

if (m_state != StateSilentSwitching) {
if ((m_state != StateSilentSwitching) && (m_state != StateOnboarding)) {
// Move to the StateConfirming if we are awaiting any connection handshakes
setState(StateConfirming);
}
Expand Down Expand Up @@ -775,6 +787,12 @@ void Controller::disconnected() {

NextStep nextStep = m_nextStep;

// Mobile onboarding is completed when we receive the disconnected signal.
if (m_state == StateOnboarding) {
logger.debug() << "Onboarding completed";
MozillaVPN::instance()->onboardingCompleted();
}

if (processNextStep()) {
setState(StateOff);
return;
Expand Down Expand Up @@ -974,6 +992,16 @@ bool Controller::activate(const ServerData& serverData,
return true;
}

// If we are in the onboarding state, this connection is being made just
// to establish system permissions. We don't actually want to connect to
// a server.
if (App::instance()->state() == App::StateOnboarding) {
setState(StateOnboarding);
clearRetryCounter();
activateInternal(DoNotForceDNSPort, RandomizeServerSelection, ClientUser);
return true;
}

if (Feature::get(Feature::Feature_checkConnectivityOnActivation)
->isSupported()) {
// Ensure that the device is connected to the Internet.
Expand Down Expand Up @@ -1008,11 +1036,8 @@ bool Controller::activate(const ServerData& serverData,

// Check if the error propagation has changed the Mozilla VPN
// state. Continue only if the user is still authenticated and
// subscribed. We can ignore this during onboarding because we are
// not actually turning the VPN on (only asking for VPN system
// config permissions)
if (App::instance()->state() != App::StateMain &&
App::instance()->state() != App::StateOnboarding) {
// subscribed.
if (App::instance()->state() != App::StateMain) {
return;
}

Expand Down
4 changes: 4 additions & 0 deletions src/controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,18 @@ class Controller : public QObject, public LogSerializer {
StateDisconnecting,
StateSilentSwitching,
StateSwitching,
StateOnboarding,
};
Q_ENUM(State)

enum Reason {
ReasonNone = 0,
ReasonSwitching,
ReasonConfirming,
ReasonOnboarding,
};
Q_ENUM(Reason)

/**
* @brief Who asked the Connection
* to be Initiated? A Webextension
Expand Down
11 changes: 11 additions & 0 deletions src/daemon/daemonlocalserverconnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,17 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
return;
}

// If this connection is being made for onboading. Don't actually connect,
// just emit a disconnected signal to confirm.
QString reason = obj.value("reason").toString();
if (!reason.isEmpty()) {
logger.error() << "Connection reason:" << reason;
}
if (reason == "onboarding") {
emit disconnected();
return;
}

if (!m_daemon->activate(config)) {
logger.error() << "Failed to activate the interface";
emit disconnected();
Expand Down
9 changes: 9 additions & 0 deletions src/daemon/mock/mockdaemon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ MockDaemon::MockDaemon(const QString& name, QObject* parent)

logger.debug() << "Mock daemon created";

#ifdef MZ_IOS
// We have to go out of our way to keep the path length under sizeof(sun_path)
// for iOS because the QDir::tempPath() used by QLocalServer winds up being
// way too long.
if (!m_socketName.startsWith('/')) {
m_socketName.prepend("/tmp/");
}
#endif

#ifndef MZ_WASM
m_server.setSocketOptions(QLocalServer::UserAccessOption);
if (!m_server.listen(m_socketName)) {
Expand Down
16 changes: 12 additions & 4 deletions src/localsocketcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QMetaEnum>
#include <QMetaType>

#include "daemon/daemonerrors.h"
Expand Down Expand Up @@ -49,8 +50,11 @@ LocalSocketController::LocalSocketController(const QString& path)
m_socket = new QLocalSocket(this);
connect(m_socket, &QLocalSocket::connected, this,
&LocalSocketController::daemonConnected);
connect(m_socket, &QLocalSocket::disconnected, this,
[&] { errorOccurred(QLocalSocket::PeerClosedError); });
connect(m_socket, &QLocalSocket::disconnected, this, [&] {
if (m_daemonState != eInitializing) {
errorOccurred(QLocalSocket::PeerClosedError);
}
});
connect(m_socket, &QLocalSocket::errorOccurred, this,
&LocalSocketController::errorOccurred);
connect(m_socket, &QLocalSocket::readyRead, this,
Expand Down Expand Up @@ -124,11 +128,15 @@ void LocalSocketController::daemonConnected() {

void LocalSocketController::activate(const InterfaceConfig& config,
Controller::Reason reason) {
Q_UNUSED(reason);

QJsonObject json = config.toJson();
json.insert("type", "activate");

if (reason != Controller::ReasonNone) {
QMetaEnum metaEnum = QMetaEnum::fromType<Controller::Reason>();
QString reasonString = metaEnum.valueToKey(reason);
json.insert("reason", reasonString.toLower().mid(6));
}

write(json);
}

Expand Down
12 changes: 2 additions & 10 deletions src/platforms/android/androidcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,7 @@ AndroidController::AndroidController() {
Qt::QueuedConnection);
connect(
activity, &AndroidVPNActivity::eventOnboardingCompleted, this,
[this]() {
auto vpn = MozillaVPN::instance();
if (vpn->state() == App::StateOnboarding) {
vpn->onboardingCompleted();
emit disconnected();
}
},
Qt::QueuedConnection);
[this]() { emit disconnected(); }, Qt::QueuedConnection);
connect(
activity, &AndroidVPNActivity::eventVpnConfigPermissionResponse, this,
[](bool granted) {
Expand Down Expand Up @@ -251,8 +244,7 @@ void AndroidController::activate(const InterfaceConfig& config,
args["isUsingShortTimerSessionPing"] =
settingsHolder->shortTimerSessionPing();

args["isOnboarding"] =
MozillaVPN::instance()->state() == App::StateOnboarding;
args["isOnboarding"] = reason == Controller::ReasonOnboarding;

QJsonDocument doc(args);
AndroidVPNActivity::sendToService(ServiceAction::ACTION_ACTIVATE,
Expand Down
14 changes: 2 additions & 12 deletions src/platforms/ios/ioscontroller.mm
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,6 @@
if (!impl) {
logger.error() << "Controller not correctly initialized";

#if TARGET_OS_SIMULATOR
if (MozillaVPN::instance()->state() == App::StateOnboarding) {
logger.debug() << "Cannot activate VPN on a simulator. Completing onboarding.";
MozillaVPN::instance()->onboardingCompleted();
}
#endif

emit disconnected();
return;
}
Expand Down Expand Up @@ -194,12 +187,9 @@
}
}
onboardingCompletedCallback:^() {
BOOL isOnboarding = MozillaVPN::instance()->state() == App::StateOnboarding;
if (isOnboarding) {
if (reason == Controller::ReasonOnboarding) {
logger.debug() << "Onboarding completed";
MozillaVPN::instance()->onboardingCompleted();
} else {
logger.debug() << "Not onboarding";
emit disconnected();
}
}
vpnConfigPermissionResponseCallback:^(BOOL granted) {
Expand Down
18 changes: 18 additions & 0 deletions src/ui/screens/home/controller/ControllerImage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,24 @@ Rectangle {
opacity: 1
}
},
State {
name: VPNController.StateOnboarding

PropertyChanges {
target: logo
showVPNOnIcon: false
opacity: 0.55
}
PropertyChanges {
target: insetCircle
color: MZTheme.colors.successAccent
}
PropertyChanges {
target: insetIcon
source: "qrc:/ui/resources/shield-off.svg"
opacity: 1
}
},
State {
name: VPNController.StateOn
PropertyChanges {
Expand Down
3 changes: 2 additions & 1 deletion src/ui/screens/home/controller/ControllerView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ Item {
states: [
State {
name: "stateInitializing"
when: VPNController.state === VPNController.StateInitializing
when: VPNController.state === VPNController.StateInitializing ||
VPNController.state === VPNController.StateOnboarding

PropertyChanges {
target: boxBackground
Expand Down
20 changes: 20 additions & 0 deletions src/ui/screens/home/controller/VPNToggle.qml
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,26 @@ MZButtonBase {
toggleColor: MZTheme.colors.vpnToggleConnected
}

},
State {
name: VPNController.StateOnboarding

PropertyChanges {
target: cursor
anchors.leftMargin: 4
}

PropertyChanges {
target: toggle
color: MZTheme.colors.vpnToggleDisconnected.defaultColor
border.color: MZTheme.colors.bgColorStronger
}

PropertyChanges {
target: toggleButton
toggleColor: MZTheme.colors.vpnToggleDisconnected
}

}
]
transitions: [
Expand Down
Loading