SEBWIN-788: Finished implementation of new (wireless) network adapter and authentication functionality.

This commit is contained in:
Damian Büchel 2024-05-21 19:11:42 +02:00
parent 4015e9a574
commit 473edc7a2e
41 changed files with 818 additions and 310 deletions

Binary file not shown.

View file

@ -698,7 +698,9 @@ namespace SafeExamBrowser.Client
private void NetworkAdapter_CredentialsRequired(CredentialsRequiredEventArgs args)
{
var dialog = uiFactory.CreateNetworkDialog("TODO", "TODO");
var message = text.Get(TextKey.CredentialsDialog_WirelessNetworkMessage).Replace("%%_NAME_%%", args.NetworkName);
var title = text.Get(TextKey.CredentialsDialog_WirelessNetworkTitle);
var dialog = uiFactory.CreateCredentialsDialog(CredentialsDialogPurpose.WirelessNetwork, message, title);
var result = dialog.Show();
args.Password = result.Password;

View file

@ -26,26 +26,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
InitializeClipboardSettings(settings);
InitializeProctoringSettings(settings);
RemoveLegacyBrowsers(settings);
settings.Applications.Blacklist.Clear();
settings.Applications.Whitelist.Add(new WhitelistApplication { ExecutableName = "mspaint.exe", ShowInShell = true });
settings.Browser.AdditionalWindow.AllowAddressBar = true;
settings.Browser.MainWindow.AllowAddressBar = true;
settings.Browser.MainWindow.AllowDeveloperConsole = true;
settings.Browser.MainWindow.AllowBackwardNavigation = true;
settings.Browser.MainWindow.AllowForwardNavigation = true;
settings.Browser.MainWindow.ShowHomeButton = true;
settings.Browser.MainWindow.UrlPolicy = Settings.Browser.UrlPolicy.BeforeTitle;
settings.Keyboard.AllowPrintScreen = true;
settings.LogLevel = Settings.Logging.LogLevel.Debug;
settings.Security.AllowApplicationLogAccess = true;
settings.Security.ClipboardPolicy = ClipboardPolicy.Allow;
settings.Security.KioskMode = KioskMode.CreateNewDesktop;
settings.Security.QuitPasswordHash = default;
settings.Service.IgnoreService = true;
settings.Service.Policy = Settings.Service.ServicePolicy.Optional;
settings.Security.VersionRestrictions.Clear();
settings.Taskbar.ShowApplicationLog = true;
}
private void AllowBrowserToolbarForReloading(AppSettings settings)

View file

@ -47,6 +47,11 @@ namespace SafeExamBrowser.I18n.Contracts
BrowserWindow_ZoomMenuMinus,
BrowserWindow_ZoomMenuPlus,
Build,
CredentialsDialog_PasswordLabel,
CredentialsDialog_UsernameLabel,
CredentialsDialog_UsernameOptionalLabel,
CredentialsDialog_WirelessNetworkMessage,
CredentialsDialog_WirelessNetworkTitle,
ExamSelectionDialog_Cancel,
ExamSelectionDialog_Message,
ExamSelectionDialog_Select,

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Build
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Passwort:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Benutzername:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Benutzername (optional):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Bitte geben Sie die erforderlichen Anmeldeinformationen ein, um eine Verbindung mit dem drahtlosen Netzwerk "%%_NAME_%%" herzustellen.
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Netzwerk-Authentifizierung erforderlich
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Abbrechen
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Build
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Password:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Username:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Username (optional):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Please enter the required credentials to establish a connection to the wireless network "%%_NAME_%%".
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Network Authentication Required
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Cancel
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Build
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Contraseña:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Nombre de usuario:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Nombre de usuario (opcional):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Por favor, introduzca las credenciales necesarias para establecer una conexión con la red inalámbrica "%%_NAME_%%".
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Autenticación de red necesaria
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Cancel
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Koosta
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Parool:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Kasutajanimi:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Kasutajanimi (vabatahtlik):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Palun sisestage vajalikud volitused, et luua ühendus traadita võrguga "%%_NAME_%%".
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Võrguautentimine nõutav
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Tühista
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Build
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Mot de passe:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Nom d'utilisateur:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Nom d'utilisateur (facultatif):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Veuillez saisir les informations d'identification requises pour établir une connexion au réseau sans fil "%%_NAME_%%".
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Authentification réseau requise
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Annuler
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Bangun
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Kata sandi:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Nama pengguna:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Nama pengguna (opsional):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Masukkan kredensial yang diperlukan untuk membuat sambungan ke jaringan nirkabel "%%_NAMA_%%".
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Diperlukan Otentikasi Jaringan
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Batal
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Build
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Password:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Nome utente:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Nome utente (facoltativo):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Inserire le credenziali richieste per stabilire una connessione alla rete wireless "%%_NAME_%%".
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
È richiesta l'autenticazione di rete
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Annulla
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Build
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Wachtwoord:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Gebruikersnaam:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Gebruikersnaam (optioneel):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Voer de vereiste gegevens in om verbinding te maken met het draadloze netwerk “%%_NAME_%%”.
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Netwerkverificatie vereist
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Annuleren
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Строить
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Пароль:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Имя пользователя:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Имя пользователя (необязательно):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Введите необходимые учетные данные для установления соединения с беспроводной сетью "%%_NAME_%%".
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Требуется сетевая аутентификация
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Отмена
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
生成
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
密码:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
用户名:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
用户名(可选):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
请输入与无线网络 "%%_NAME_%%" 建立连接所需的凭证。
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
需要网络验证
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
取消
</Entry>

View file

@ -9,22 +9,27 @@
namespace SafeExamBrowser.SystemComponents.Contracts.Network.Events
{
/// <summary>
///
/// The event arguments for the <see cref="CredentialsRequiredEventHandler"/>.
/// </summary>
public class CredentialsRequiredEventArgs
{
/// <summary>
///
/// The name of the network which requires credentials.
/// </summary>
public string NetworkName { get; set; }
/// <summary>
/// The password as specified by the user.
/// </summary>
public string Password { get; set; }
/// <summary>
///
/// Indicates whether the credentials could be successfully retrieved or not.
/// </summary>
public bool Success { get; set; }
/// <summary>
///
/// The username as specified by the user.
/// </summary>
public string Username { get; set; }
}

View file

@ -9,7 +9,7 @@
namespace SafeExamBrowser.SystemComponents.Contracts.Network.Events
{
/// <summary>
///
/// Indicates that credentials are required to connect to a network.
/// </summary>
public delegate void CredentialsRequiredEventHandler(CredentialsRequiredEventArgs args);
}

View file

@ -45,5 +45,15 @@ namespace SafeExamBrowser.SystemComponents.Contracts.Network
/// Retrieves all currently available wireless networks.
/// </summary>
IEnumerable<IWirelessNetwork> GetWirelessNetworks();
/// <summary>
/// Starts periodically scanning the available wireless networks.
/// </summary>
void StartWirelessNetworkScanning();
/// <summary>
/// Stops the periodical scanning of wireless networks.
/// </summary>
void StopWirelessNetworkScanning();
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using SafeExamBrowser.SystemComponents.Contracts.Network;
using Windows.Devices.WiFi;
namespace SafeExamBrowser.SystemComponents.Network
{
internal static class Extensions
{
internal static IOrderedEnumerable<WiFiAvailableNetwork> FilterAndOrder(this IReadOnlyList<WiFiAvailableNetwork> networks)
{
return networks.Where(n => !string.IsNullOrEmpty(n.Ssid)).GroupBy(n => n.Ssid).Select(g => g.First()).OrderBy(n => n.Ssid);
}
internal static WirelessNetwork ToWirelessNetwork(this WiFiAvailableNetwork network)
{
return new WirelessNetwork
{
Name = network.Ssid,
Network = network,
SignalStrength = Convert.ToInt32(Math.Max(0, Math.Min(100, (network.NetworkRssiInDecibelMilliwatts + 100) * 2))),
Status = ConnectionStatus.Disconnected
};
}
}
}

View file

@ -14,21 +14,15 @@ using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.Network;
using SafeExamBrowser.SystemComponents.Contracts.Network.Events;
using SafeExamBrowser.WindowsApi.Contracts;
using SimpleWifi;
using SimpleWifi.Win32;
using SimpleWifi.Win32.Interop;
using Windows.Devices.Enumeration;
using Windows.Devices.WiFi;
using Windows.Foundation;
using Windows.Networking.Connectivity;
using Windows.Security.Credentials;
using Timer = System.Timers.Timer;
namespace SafeExamBrowser.SystemComponents.Network
{
/// <summary>
/// Switch to the following WiFi library:
/// https://github.com/emoacht/ManagedNativeWifi
/// https://www.nuget.org/packages/ManagedNativeWifi
///
/// Potentially useful:
/// https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation.networkinterface?view=netframework-4.8
/// </summary>
public class NetworkAdapter : INetworkAdapter
{
private readonly object @lock = new object();
@ -38,7 +32,9 @@ namespace SafeExamBrowser.SystemComponents.Network
private readonly List<WirelessNetwork> wirelessNetworks;
private Timer timer;
private Wifi wifi;
private WiFiAdapter adapter;
private bool HasWirelessAdapter => adapter != default;
public ConnectionStatus Status { get; private set; }
public ConnectionType Type { get; private set; }
@ -55,36 +51,36 @@ namespace SafeExamBrowser.SystemComponents.Network
public void ConnectToWirelessNetwork(string name)
{
var network = default(WiFiAvailableNetwork);
lock (@lock)
{
var network = wirelessNetworks.FirstOrDefault(n => n.Name == name);
network = wirelessNetworks.FirstOrDefault(n => n.Name == name)?.Network;
}
if (network != default)
if (network != default)
{
var isOpen = network.SecuritySettings.NetworkAuthenticationType == NetworkAuthenticationType.Open80211 && network.SecuritySettings.NetworkEncryptionType == NetworkEncryptionType.None;
if (isOpen)
{
try
{
var accessPoint = network.AccessPoint;
var request = new AuthRequest(accessPoint);
logger.Info($"Attempting to connect to open wireless network '{name}'...");
if (accessPoint.HasProfile || accessPoint.IsConnected || TryGetCredentials(request))
{
logger.Info($"Attempting to connect to wireless network '{network.Name}' with{(request.Password == default ? "out" : "")} credentials...");
// TODO: Retry resp. alert of password error on failure and then ignore profile?!
accessPoint.ConnectAsync(request, false, (success) => ConnectionAttemptCompleted(network.Name, success));
Status = ConnectionStatus.Connecting;
}
}
catch (Exception e)
{
logger.Error($"Failed to connect to wireless network '{network.Name}!'", e);
}
adapter.ConnectAsync(network, WiFiReconnectionKind.Automatic).Completed = (o, s) => Adapter_ConnectCompleted(name, o, s);
Status = ConnectionStatus.Connecting;
}
else
else if (TryGetCredentials(name, out var credentials))
{
logger.Warn($"Could not find wireless network '{name}'!");
logger.Info($"Attempting to connect to wireless network '{name}' with credentials...");
adapter.ConnectAsync(network, WiFiReconnectionKind.Automatic, credentials).Completed = (o, s) => Adapter_ConnectCompleted(name, o, s);
Status = ConnectionStatus.Connecting;
}
}
else
{
logger.Warn($"Could not find wireless network '{name}'!");
}
Changed?.Invoke();
}
@ -101,103 +97,233 @@ namespace SafeExamBrowser.SystemComponents.Network
{
const int FIVE_SECONDS = 5000;
NetworkChange.NetworkAddressChanged += (o, args) => Update();
NetworkChange.NetworkAvailabilityChanged += (o, args) => Update();
wifi = new Wifi();
wifi.ConnectionStatusChanged += (o, args) => Update();
timer = new Timer(FIVE_SECONDS);
timer.Elapsed += (o, args) => Update();
timer.AutoReset = true;
timer.Start();
InitializeAdapter();
NetworkChange.NetworkAddressChanged += NetworkChange_NetworkAddressChanged;
NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAvailabilityChanged;
NetworkInformation.NetworkStatusChanged += NetworkInformation_NetworkStatusChanged;
Update();
logger.Info("Started monitoring the network adapter.");
}
public void Terminate()
public void StartWirelessNetworkScanning()
{
if (timer != null)
timer?.Start();
if (HasWirelessAdapter)
{
timer.Stop();
logger.Info("Stopped monitoring the network adapter.");
_ = adapter.ScanAsync();
}
}
private void ConnectionAttemptCompleted(string name, bool success)
public void StopWirelessNetworkScanning()
{
lock (@lock)
timer?.Stop();
}
public void Terminate()
{
NetworkChange.NetworkAddressChanged -= NetworkChange_NetworkAddressChanged;
NetworkChange.NetworkAvailabilityChanged -= NetworkChange_NetworkAvailabilityChanged;
NetworkInformation.NetworkStatusChanged -= NetworkInformation_NetworkStatusChanged;
if (HasWirelessAdapter)
{
// This handler seems to be called before the connection has been fully established, thus we don't yet set the status to connected...
Status = success ? ConnectionStatus.Connecting : ConnectionStatus.Disconnected;
adapter.AvailableNetworksChanged -= Adapter_AvailableNetworksChanged;
}
if (success)
if (timer != default)
{
timer.Stop();
}
logger.Info("Stopped monitoring the network adapter.");
}
private void InitializeAdapter()
{
try
{
// Requesting access is required as of fall 2024 and must be granted manually by the user, otherwise all wireless functionality will
// be denied by the system (see also https://learn.microsoft.com/en-us/windows/win32/nativewifi/wi-fi-access-location-changes).
var task = WiFiAdapter.RequestAccessAsync().AsTask();
var status = task.GetAwaiter().GetResult();
if (status == WiFiAccessStatus.Allowed)
{
var findAll = DeviceInformation.FindAllAsync(WiFiAdapter.GetDeviceSelector()).AsTask();
var devices = findAll.GetAwaiter().GetResult();
if (devices.Any())
{
var id = devices.First().Id;
var getById = WiFiAdapter.FromIdAsync(id).AsTask();
logger.Debug($"Found {devices.Count()} wireless network adapter(s).");
adapter = getById.GetAwaiter().GetResult();
adapter.AvailableNetworksChanged += Adapter_AvailableNetworksChanged;
logger.Debug($"Successfully initialized wireless network adapter '{id}'.");
}
else
{
logger.Info("Could not find a wireless network adapter.");
}
}
else
{
logger.Error($"Access to the wireless network adapter has been denied ({status})!");
}
}
catch (Exception e)
{
logger.Error("Failed to initialize wireless network adapter!", e);
}
}
private void Adapter_AvailableNetworksChanged(WiFiAdapter sender, object args)
{
Update(false);
}
private void Adapter_ConnectCompleted(string name, IAsyncOperation<WiFiConnectionResult> operation, AsyncStatus status)
{
var connectionStatus = default(WiFiConnectionStatus?);
if (status == AsyncStatus.Completed)
{
connectionStatus = operation.GetResults()?.ConnectionStatus;
}
else
{
logger.Error($"Failed to complete connection operation! Status: {status}.");
}
if (connectionStatus == WiFiConnectionStatus.Success)
{
Status = ConnectionStatus.Connected;
logger.Info($"Successfully connected to wireless network '{name}'.");
}
else
{
logger.Error($"Failed to connect to wireless network '{name}!'");
Status = ConnectionStatus.Disconnected;
logger.Error($"Failed to connect to wireless network '{name}'! Reason: {connectionStatus}.");
}
Update();
}
private bool TryGetCredentials(AuthRequest request)
private void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
{
var args = new CredentialsRequiredEventArgs();
logger.Debug("Network address changed.");
Update();
}
private void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
{
logger.Debug($"Network availability changed ({(e.IsAvailable ? "available" : "unavailable")}.");
Update();
}
private void NetworkInformation_NetworkStatusChanged(object sender)
{
logger.Debug("Network status changed.");
Update();
}
private bool TryGetCredentials(string network, out PasswordCredential credentials)
{
var args = new CredentialsRequiredEventArgs { NetworkName = network };
credentials = new PasswordCredential();
CredentialsRequired?.Invoke(args);
if (args.Success)
{
request.Password = args.Password;
request.Username = args.Username;
if (!string.IsNullOrEmpty(args.Password))
{
credentials.Password = args.Password;
}
if (!string.IsNullOrEmpty(args.Username))
{
credentials.UserName = args.Username;
}
}
return args.Success;
}
private void Update()
private bool TryGetCurrentWirelessNetwork(out string name)
{
name = default;
if (HasWirelessAdapter)
{
try
{
var getProfile = adapter.NetworkAdapter.GetConnectedProfileAsync().AsTask();
var profile = getProfile.GetAwaiter().GetResult();
if (profile?.IsWlanConnectionProfile == true)
{
name = profile.WlanConnectionProfileDetails.GetConnectedSsid();
}
}
catch
{
}
}
return name != default;
}
private void Update(bool rescan = true)
{
try
{
lock (@lock)
{
var current = default(WirelessNetwork);
var hasInternet = nativeMethods.HasInternetConnection();
var hasWireless = !wifi.NoWifiAvailable && !IsTurnedOff();
var isConnecting = Status == ConnectionStatus.Connecting;
var previousStatus = Status;
wirelessNetworks.Clear();
if (hasWireless)
if (HasWirelessAdapter)
{
foreach (var wirelessNetwork in wifi.GetAccessPoints().Select(a => ToWirelessNetwork(a)))
{
wirelessNetworks.Add(wirelessNetwork);
TryGetCurrentWirelessNetwork(out var currentNetwork);
if (wirelessNetwork.Status == ConnectionStatus.Connected)
foreach (var network in adapter.NetworkReport.AvailableNetworks.FilterAndOrder())
{
var wirelessNetwork = network.ToWirelessNetwork();
if (network.Ssid == currentNetwork)
{
current = wirelessNetwork;
wirelessNetwork.Status = ConnectionStatus.Connected;
}
wirelessNetworks.Add(wirelessNetwork);
}
if (rescan)
{
_ = adapter.ScanAsync();
}
}
Type = hasWireless ? ConnectionType.Wireless : (hasInternet ? ConnectionType.Wired : ConnectionType.Undefined);
Status = hasInternet ? ConnectionStatus.Connected : (hasWireless && isConnecting ? ConnectionStatus.Connecting : ConnectionStatus.Disconnected);
Type = HasWirelessAdapter ? ConnectionType.Wireless : (hasInternet ? ConnectionType.Wired : ConnectionType.Undefined);
Status = hasInternet ? ConnectionStatus.Connected : (HasWirelessAdapter && isConnecting ? ConnectionStatus.Connecting : ConnectionStatus.Disconnected);
if (previousStatus != ConnectionStatus.Connected && Status == ConnectionStatus.Connected)
{
logger.Info($"Connection established ({Type}{(current != default ? $", {current.Name}" : "")}).");
}
if (previousStatus != ConnectionStatus.Disconnected && Status == ConnectionStatus.Disconnected)
{
logger.Info("Connection lost.");
}
LogNetworkChanges(previousStatus, wirelessNetworks.FirstOrDefault(n => n.Status == ConnectionStatus.Connected));
}
}
catch (Exception e)
@ -208,40 +334,17 @@ namespace SafeExamBrowser.SystemComponents.Network
Changed?.Invoke();
}
private bool IsTurnedOff()
private void LogNetworkChanges(ConnectionStatus previousStatus, WirelessNetwork currentNetwork)
{
try
if (previousStatus != ConnectionStatus.Connected && Status == ConnectionStatus.Connected)
{
var client = new WlanClient();
foreach (var @interface in client.Interfaces)
{
foreach (var state in @interface.RadioState.PhyRadioState)
{
if (state.dot11SoftwareRadioState == Dot11RadioState.On && state.dot11HardwareRadioState == Dot11RadioState.On)
{
return false;
}
}
}
}
catch (Exception e)
{
logger.Error("Failed to determine the radio state of the wireless adapter(s)! Assuming it is (all are) turned off...", e);
logger.Info($"Connection established ({Type}{(currentNetwork != default ? $", {currentNetwork.Name}" : "")}).");
}
return true;
}
private WirelessNetwork ToWirelessNetwork(AccessPoint accessPoint)
{
return new WirelessNetwork
if (previousStatus != ConnectionStatus.Disconnected && Status == ConnectionStatus.Disconnected)
{
AccessPoint = accessPoint,
Name = accessPoint.Name,
SignalStrength = Convert.ToInt32(accessPoint.SignalStrength),
Status = accessPoint.IsConnected ? ConnectionStatus.Connected : ConnectionStatus.Disconnected
};
logger.Info("Connection lost.");
}
}
}
}

View file

@ -7,13 +7,13 @@
*/
using SafeExamBrowser.SystemComponents.Contracts.Network;
using SimpleWifi;
using Windows.Devices.WiFi;
namespace SafeExamBrowser.SystemComponents.Network
{
internal class WirelessNetwork : IWirelessNetwork
{
internal AccessPoint AccessPoint { get; set; }
internal WiFiAvailableNetwork Network { get; set; }
public string Name { get; set; }
public int SignalStrength { get; set; }

View file

@ -58,12 +58,12 @@ namespace SafeExamBrowser.SystemComponents.PowerSupply
public void Initialize()
{
const int TWO_SECONDS = 2000;
const int FIVE_SECONDS = 5000;
critical = SanitizeThreshold(settings.ChargeThresholdCritical);
low = SanitizeThreshold(settings.ChargeThresholdLow);
timer = new Timer(TWO_SECONDS);
timer = new Timer(FIVE_SECONDS);
timer.Elapsed += Timer_Elapsed;
timer.AutoReset = true;
timer.Start();

View file

@ -49,44 +49,10 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Win32.Registry, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Win32.Registry.5.0.0\lib\net461\Microsoft.Win32.Registry.dll</HintPath>
</Reference>
<Reference Include="NAudio, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.2.2.1\lib\net472\NAudio.dll</HintPath>
</Reference>
<Reference Include="NAudio.Asio, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.Asio.2.2.1\lib\netstandard2.0\NAudio.Asio.dll</HintPath>
</Reference>
<Reference Include="NAudio.Core, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.Core.2.2.1\lib\netstandard2.0\NAudio.Core.dll</HintPath>
</Reference>
<Reference Include="NAudio.Midi, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.Midi.2.2.1\lib\netstandard2.0\NAudio.Midi.dll</HintPath>
</Reference>
<Reference Include="NAudio.Wasapi, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.Wasapi.2.2.1\lib\netstandard2.0\NAudio.Wasapi.dll</HintPath>
</Reference>
<Reference Include="NAudio.WinForms, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.WinForms.2.2.1\lib\net472\NAudio.WinForms.dll</HintPath>
</Reference>
<Reference Include="NAudio.WinMM, Version=2.2.1.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.WinMM.2.2.1\lib\netstandard2.0\NAudio.WinMM.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="SimpleWifi, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Libraries\SimpleWifi.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Management" />
<Reference Include="System.Security.AccessControl, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Security.AccessControl.6.0.0\lib\net461\System.Security.AccessControl.dll</HintPath>
</Reference>
<Reference Include="System.Security.Principal.Windows, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Security.Principal.Windows.5.0.0\lib\net461\System.Security.Principal.Windows.dll</HintPath>
</Reference>
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
@ -94,6 +60,7 @@
<Compile Include="FileSystem.cs" />
<Compile Include="Keyboard\KeyboardLayout.cs" />
<Compile Include="Keyboard\Keyboard.cs" />
<Compile Include="Network\Extensions.cs" />
<Compile Include="PowerSupply\PowerSupply.cs" />
<Compile Include="PowerSupply\PowerSupplyStatus.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@ -125,7 +92,20 @@
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Win32.Registry">
<Version>5.0.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Windows.SDK.Contracts">
<Version>10.0.17134.1000</Version>
</PackageReference>
<PackageReference Include="NAudio">
<Version>2.2.1</Version>
</PackageReference>
<PackageReference Include="System.Security.AccessControl">
<Version>6.0.1</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Win32.Registry" version="5.0.0" targetFramework="net48" />
<package id="NAudio" version="2.2.1" targetFramework="net48" />
<package id="NAudio.Asio" version="2.2.1" targetFramework="net48" />
<package id="NAudio.Core" version="2.2.1" targetFramework="net48" />
<package id="NAudio.Midi" version="2.2.1" targetFramework="net48" />
<package id="NAudio.Wasapi" version="2.2.1" targetFramework="net48" />
<package id="NAudio.WinForms" version="2.2.1" targetFramework="net48" />
<package id="NAudio.WinMM" version="2.2.1" targetFramework="net48" />
<package id="System.Security.AccessControl" version="6.0.0" targetFramework="net48" />
<package id="System.Security.Principal.Windows" version="5.0.0" targetFramework="net48" />
</packages>

View file

@ -58,6 +58,11 @@ namespace SafeExamBrowser.UserInterface.Contracts
/// </summary>
IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow, ILogger logger);
/// <summary>
/// Creates a credentials dialog for the given purpose and with the specified message and title.
/// </summary>
ICredentialsDialog CreateCredentialsDialog(CredentialsDialogPurpose purpose, string message, string title);
/// <summary>
/// Creates an exam selection dialog for the given exams.
/// </summary>
@ -83,11 +88,6 @@ namespace SafeExamBrowser.UserInterface.Contracts
/// </summary>
ISystemControl CreateNetworkControl(INetworkAdapter adapter, Location location);
/// <summary>
/// Creates a network dialog with the given message and title.
/// </summary>
INetworkDialog CreateNetworkDialog(string message, string title);
/// <summary>
/// Creates a notification control for the given notification, initialized for the specified location.
/// </summary>

View file

@ -93,16 +93,17 @@
<Compile Include="Shell\ITaskviewActivator.cs" />
<Compile Include="Shell\ITerminationActivator.cs" />
<Compile Include="Shell\Location.cs" />
<Compile Include="Windows\Data\CredentialsDialogPurpose.cs" />
<Compile Include="Windows\Data\ExamSelectionDialogResult.cs" />
<Compile Include="Windows\Data\LockScreenOption.cs" />
<Compile Include="Windows\Data\LockScreenResult.cs" />
<Compile Include="Windows\Data\NetworkDialogResult.cs" />
<Compile Include="Windows\Data\CredentialsDialogResult.cs" />
<Compile Include="Windows\Data\ServerFailureDialogResult.cs" />
<Compile Include="Windows\Events\WindowClosedEventHandler.cs" />
<Compile Include="Windows\Events\WindowClosingEventHandler.cs" />
<Compile Include="Windows\IExamSelectionDialog.cs" />
<Compile Include="Windows\ILockScreen.cs" />
<Compile Include="Windows\INetworkDialog.cs" />
<Compile Include="Windows\ICredentialsDialog.cs" />
<Compile Include="Windows\IPasswordDialog.cs" />
<Compile Include="Windows\Data\PasswordDialogResult.cs" />
<Compile Include="Proctoring\IProctoringFinalizationDialog.cs" />

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.UserInterface.Contracts.Windows.Data
{
/// <summary>
/// Defines the purpose of a <see cref="ICredentialsDialog"/>.
/// </summary>
public enum CredentialsDialogPurpose
{
/// <summary>
/// Credentials for generic purposes.
/// </summary>
Generic,
/// <summary>
/// Credentials for wireless network authentication.
/// </summary>
WirelessNetwork
}
}

View file

@ -9,9 +9,9 @@
namespace SafeExamBrowser.UserInterface.Contracts.Windows.Data
{
/// <summary>
/// Defines the user interaction result of an <see cref="INetworkDialog"/>.
/// Defines the user interaction result of an <see cref="ICredentialsDialog"/>.
/// </summary>
public class NetworkDialogResult
public class CredentialsDialogResult
{
/// <summary>
/// The password entered by the user, or <c>default(string)</c> if the interaction was unsuccessful.

View file

@ -11,13 +11,13 @@ using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
namespace SafeExamBrowser.UserInterface.Contracts.Windows
{
/// <summary>
/// Defines the functionality of a network dialog.
/// Defines the functionality of a dialog to retrieve user credentials.
/// </summary>
public interface INetworkDialog : IWindow
public interface ICredentialsDialog : IWindow
{
/// <summary>
/// Shows the dialog as topmost window. If a parent window is specified, the dialog is rendered modally for the given parent.
/// </summary>
NetworkDialogResult Show(IWindow parent = null);
CredentialsDialogResult Show(IWindow parent = null);
}
}

View file

@ -63,6 +63,12 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
}
Popup.IsOpen = Popup.IsMouseOver;
}));
Popup.Closed += (o, args) =>
{
adapter.StopWirelessNetworkScanning();
Grid.Background = originalBrush;
lastOpenedBySpacePress = false;
};
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
{
if (Popup.IsOpen && lastOpenedBySpacePress)
@ -73,6 +79,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
}));
Popup.Opened += (o, args) =>
{
adapter.StartWirelessNetworkScanning();
Grid.Background = Brushes.Gray;
Task.Delay(100).ContinueWith((task) => Dispatcher.Invoke(() =>
{
@ -82,11 +89,6 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
}
}));
};
Popup.Closed += (o, args) =>
{
Grid.Background = originalBrush;
lastOpenedBySpacePress = false;
};
WirelessIcon.Child = GetWirelessIcon(0);
Update();
@ -94,23 +96,6 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
private void Update()
{
WirelessNetworksStackPanel.Children.Clear();
foreach (var network in adapter.GetWirelessNetworks())
{
var button = new NetworkButton(network);
button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Name);
if (network.Status == ConnectionStatus.Connected)
{
WirelessIcon.Child = GetWirelessIcon(network.SignalStrength);
UpdateText(text.Get(TextKey.SystemControl_NetworkWirelessConnected).Replace("%%NAME%%", network.Name));
}
WirelessNetworksStackPanel.Children.Add(button);
}
switch (adapter.Type)
{
case ConnectionType.Wired:
@ -154,6 +139,23 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
WirelessIcon.Child = GetWirelessIcon(0);
break;
}
WirelessNetworksStackPanel.Children.Clear();
foreach (var network in adapter.GetWirelessNetworks())
{
var button = new NetworkButton(network);
button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Name);
if (network.Status == ConnectionStatus.Connected)
{
WirelessIcon.Child = GetWirelessIcon(network.SignalStrength);
UpdateText(text.Get(TextKey.SystemControl_NetworkWirelessConnected).Replace("%%NAME%%", network.Name));
}
WirelessNetworksStackPanel.Children.Add(button);
}
}
private void UpdateText(string text)

View file

@ -66,6 +66,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
}));
Popup.Closed += (o, args) =>
{
adapter.StopWirelessNetworkScanning();
Background = originalBrush;
Button.Background = originalBrush;
lastOpenedBySpacePress = false;
@ -81,6 +82,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
}));
Popup.Opened += (o, args) =>
{
adapter.StartWirelessNetworkScanning();
Background = Brushes.LightGray;
Button.Background = Brushes.LightGray;
Task.Delay(100).ContinueWith((task) => Dispatcher.Invoke(() =>
@ -106,23 +108,6 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
private void Update()
{
WirelessNetworksStackPanel.Children.Clear();
foreach (var network in adapter.GetWirelessNetworks())
{
var button = new NetworkButton(network);
button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Name);
if (network.Status == ConnectionStatus.Connected)
{
WirelessIcon.Child = GetWirelessIcon(network.SignalStrength);
UpdateText(text.Get(TextKey.SystemControl_NetworkWirelessConnected).Replace("%%NAME%%", network.Name));
}
WirelessNetworksStackPanel.Children.Add(button);
}
switch (adapter.Type)
{
case ConnectionType.Wired:
@ -166,6 +151,23 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
WirelessIcon.Child = GetWirelessIcon(0);
break;
}
WirelessNetworksStackPanel.Children.Clear();
foreach (var network in adapter.GetWirelessNetworks())
{
var button = new NetworkButton(network);
button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Name);
if (network.Status == ConnectionStatus.Connected)
{
WirelessIcon.Child = GetWirelessIcon(network.SignalStrength);
UpdateText(text.Get(TextKey.SystemControl_NetworkWirelessConnected).Replace("%%NAME%%", network.Name));
}
WirelessNetworksStackPanel.Children.Add(button);
}
}
private void UpdateText(string text)

View file

@ -168,8 +168,8 @@
<DependentUpon>LogWindow.xaml</DependentUpon>
</Compile>
<Compile Include="MessageBoxFactory.cs" />
<Compile Include="Windows\NetworkDialog.xaml.cs">
<DependentUpon>NetworkDialog.xaml</DependentUpon>
<Compile Include="Windows\CredentialsDialog.xaml.cs">
<DependentUpon>CredentialsDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\PasswordDialog.xaml.cs">
<DependentUpon>PasswordDialog.xaml</DependentUpon>
@ -397,7 +397,7 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Windows\NetworkDialog.xaml">
<Page Include="Windows\CredentialsDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>

View file

@ -85,6 +85,11 @@ namespace SafeExamBrowser.UserInterface.Desktop
return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text, logger));
}
public ICredentialsDialog CreateCredentialsDialog(CredentialsDialogPurpose purpose, string message, string title)
{
return Application.Current.Dispatcher.Invoke(() => new CredentialsDialog(purpose, message, title, text));
}
public IExamSelectionDialog CreateExamSelectionDialog(IEnumerable<Exam> exams)
{
return Application.Current.Dispatcher.Invoke(() => new ExamSelectionDialog(exams, text));
@ -143,11 +148,6 @@ namespace SafeExamBrowser.UserInterface.Desktop
}
}
public INetworkDialog CreateNetworkDialog(string message, string title)
{
return Application.Current.Dispatcher.Invoke(() => new NetworkDialog(message, title, text));
}
public INotificationControl CreateNotificationControl(INotification notification, Location location)
{
if (location == Location.ActionCenter)

View file

@ -1,11 +1,11 @@
<Window x:Class="SafeExamBrowser.UserInterface.Desktop.Windows.NetworkDialog"
<Window x:Class="SafeExamBrowser.UserInterface.Desktop.Windows.CredentialsDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:fa="http://schemas.fontawesome.io/icons/"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Windows"
mc:Ignorable="d" Height="350" Width="450" ResizeMode="NoResize" Topmost="True">
mc:Ignorable="d" Height="325" Width="450" ResizeMode="NoResize" Topmost="True">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
@ -15,17 +15,17 @@
</Window.Resources>
<Grid FocusManager.FocusedElement="{Binding ElementName=Password}">
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid Grid.Row="0" VerticalAlignment="Center">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<fa:ImageAwesome Grid.Column="0" Foreground="LightGray" Icon="Wifi" Margin="25" Width="50" />
<Grid Grid.Column="1" Margin="0,0,25,25">
<fa:ImageAwesome Grid.Column="0" Name="PurposeIcon" Foreground="LightGray" Icon="Key" Margin="25" Rotation="90" Width="50" />
<Grid Grid.Column="1" Margin="0,25,25,25">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
@ -33,18 +33,18 @@
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Name="Message" Margin="0,0,0,5" TextWrapping="WrapWithOverflow" VerticalAlignment="Bottom" />
<StackPanel Grid.Row="1" Orientation="Vertical">
<Label Name="UsernameLabel" Target="{Binding ElementName=Username}" />
<Label Name="UsernameLabel" Padding="0,5" Target="{Binding ElementName=Username}" />
<TextBox Name="Username" Height="25" Margin="0,0,0,5" VerticalContentAlignment="Center" />
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Vertical">
<Label Name="PasswordLabel" Target="{Binding ElementName=Password}" />
<Label Name="PasswordLabel" Padding="0,5" Target="{Binding ElementName=Password}" />
<PasswordBox Grid.Row="2" Name="Password" Height="25" VerticalContentAlignment="Center" />
</StackPanel>
</Grid>
</Grid>
</Grid>
<Grid Grid.Row="1" Background="{StaticResource BackgroundBrush}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<WrapPanel Orientation="Horizontal" Margin="25,0" HorizontalAlignment="Right" VerticalAlignment="Center">
<WrapPanel Orientation="Horizontal" Margin="25" HorizontalAlignment="Right" VerticalAlignment="Center">
<Button Name="ConfirmButton" Cursor="Hand" Margin="10,0" Padding="10,5" MinWidth="75" />
<Button Name="CancelButton" Cursor="Hand" Padding="10,5" MinWidth="75" />
</WrapPanel>

View file

@ -8,6 +8,7 @@
using System.Windows;
using System.Windows.Input;
using FontAwesome.WPF;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
@ -15,7 +16,7 @@ using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
namespace SafeExamBrowser.UserInterface.Desktop.Windows
{
public partial class NetworkDialog : Window, INetworkDialog
public partial class CredentialsDialog : Window, ICredentialsDialog
{
private readonly IText text;
@ -34,12 +35,12 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
remove { closing -= value; }
}
internal NetworkDialog(string message, string title, IText text)
internal CredentialsDialog(CredentialsDialogPurpose purpose, string message, string title, IText text)
{
this.text = text;
InitializeComponent();
InitializeNetworkDialog(message, title);
InitializeCredentialsDialog(purpose, message, title);
}
public void BringToForeground()
@ -47,11 +48,11 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
Dispatcher.Invoke(Activate);
}
public NetworkDialogResult Show(IWindow parent = null)
public CredentialsDialogResult Show(IWindow parent = null)
{
return Dispatcher.Invoke(() =>
{
var result = new NetworkDialogResult { Success = false };
var result = new CredentialsDialogResult { Success = false };
if (parent is Window)
{
@ -70,14 +71,24 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
});
}
private void InitializeNetworkDialog(string message, string title)
private void InitializeCredentialsDialog(CredentialsDialogPurpose purpose, string message, string title)
{
if (purpose == CredentialsDialogPurpose.WirelessNetwork)
{
PurposeIcon.Icon = FontAwesomeIcon.Wifi;
PurposeIcon.Rotation = 0;
UsernameLabel.Content = text.Get(TextKey.CredentialsDialog_UsernameOptionalLabel);
}
else
{
PurposeIcon.Icon = FontAwesomeIcon.Key;
PurposeIcon.Rotation = 90;
UsernameLabel.Content = text.Get(TextKey.CredentialsDialog_UsernameLabel);
}
PasswordLabel.Content = text.Get(TextKey.CredentialsDialog_PasswordLabel);
Message.Text = message;
// TODO
PasswordLabel.Content = "Password";
Title = title;
// TODO
UsernameLabel.Content = "Username";
WindowStartupLocation = WindowStartupLocation.CenterScreen;
Closed += (o, args) => closed?.Invoke();

View file

@ -63,6 +63,12 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter
}
Popup.IsOpen = Popup.IsMouseOver;
}));
Popup.Closed += (o, args) =>
{
adapter.StopWirelessNetworkScanning();
Grid.Background = originalBrush;
lastOpenedBySpacePress = false;
};
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
{
if (Popup.IsOpen && lastOpenedBySpacePress)
@ -73,6 +79,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter
}));
Popup.Opened += (o, args) =>
{
adapter.StartWirelessNetworkScanning();
Grid.Background = Brushes.Gray;
Task.Delay(100).ContinueWith((task) => Dispatcher.Invoke(() =>
{
@ -82,11 +89,6 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter
}
}));
};
Popup.Closed += (o, args) =>
{
Grid.Background = originalBrush;
lastOpenedBySpacePress = false;
};
WirelessIcon.Child = GetWirelessIcon(0);
Update();
@ -94,23 +96,6 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter
private void Update()
{
WirelessNetworksStackPanel.Children.Clear();
foreach (var network in adapter.GetWirelessNetworks())
{
var button = new NetworkButton(network);
button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Name);
if (network.Status == ConnectionStatus.Connected)
{
WirelessIcon.Child = GetWirelessIcon(network.SignalStrength);
UpdateText(text.Get(TextKey.SystemControl_NetworkWirelessConnected).Replace("%%NAME%%", network.Name));
}
WirelessNetworksStackPanel.Children.Add(button);
}
switch (adapter.Type)
{
case ConnectionType.Wired:
@ -154,6 +139,23 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter
WirelessIcon.Child = GetWirelessIcon(0);
break;
}
WirelessNetworksStackPanel.Children.Clear();
foreach (var network in adapter.GetWirelessNetworks())
{
var button = new NetworkButton(network);
button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Name);
if (network.Status == ConnectionStatus.Connected)
{
WirelessIcon.Child = GetWirelessIcon(network.SignalStrength);
UpdateText(text.Get(TextKey.SystemControl_NetworkWirelessConnected).Replace("%%NAME%%", network.Name));
}
WirelessNetworksStackPanel.Children.Add(button);
}
}
private void UpdateText(string text)

View file

@ -66,6 +66,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar
}));
Popup.Closed += (o, args) =>
{
adapter.StopWirelessNetworkScanning();
Background = originalBrush;
Button.Background = originalBrush;
lastOpenedBySpacePress = false;
@ -81,6 +82,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar
}));
Popup.Opened += (o, args) =>
{
adapter.StartWirelessNetworkScanning();
Background = Brushes.LightGray;
Button.Background = Brushes.LightGray;
Task.Delay(100).ContinueWith((task) => Dispatcher.Invoke(() =>
@ -106,23 +108,6 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar
private void Update()
{
WirelessNetworksStackPanel.Children.Clear();
foreach (var network in adapter.GetWirelessNetworks())
{
var button = new NetworkButton(network);
button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Name);
if (network.Status == ConnectionStatus.Connected)
{
WirelessIcon.Child = GetWirelessIcon(network.SignalStrength);
UpdateText(text.Get(TextKey.SystemControl_NetworkWirelessConnected).Replace("%%NAME%%", network.Name));
}
WirelessNetworksStackPanel.Children.Add(button);
}
switch (adapter.Type)
{
case ConnectionType.Wired:
@ -166,6 +151,23 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar
WirelessIcon.Child = GetWirelessIcon(0);
break;
}
WirelessNetworksStackPanel.Children.Clear();
foreach (var network in adapter.GetWirelessNetworks())
{
var button = new NetworkButton(network);
button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Name);
if (network.Status == ConnectionStatus.Connected)
{
WirelessIcon.Child = GetWirelessIcon(network.SignalStrength);
UpdateText(text.Get(TextKey.SystemControl_NetworkWirelessConnected).Replace("%%NAME%%", network.Name));
}
WirelessNetworksStackPanel.Children.Add(button);
}
}
private void UpdateText(string text)

View file

@ -173,6 +173,9 @@
<Compile Include="Windows\MessageBoxDialog.xaml.cs">
<DependentUpon>MessageBoxDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\CredentialsDialog.xaml.cs">
<DependentUpon>CredentialsDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\PasswordDialog.xaml.cs">
<DependentUpon>PasswordDialog.xaml</DependentUpon>
</Compile>
@ -521,6 +524,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Windows\CredentialsDialog.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Windows\PasswordDialog.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>

View file

@ -85,6 +85,11 @@ namespace SafeExamBrowser.UserInterface.Mobile
return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text, logger));
}
public ICredentialsDialog CreateCredentialsDialog(CredentialsDialogPurpose purpose, string message, string title)
{
return Application.Current.Dispatcher.Invoke(() => new CredentialsDialog(purpose, message, title, text));
}
public IExamSelectionDialog CreateExamSelectionDialog(IEnumerable<Exam> exams)
{
return Application.Current.Dispatcher.Invoke(() => new ExamSelectionDialog(exams, text));
@ -143,12 +148,6 @@ namespace SafeExamBrowser.UserInterface.Mobile
}
}
public INetworkDialog CreateNetworkDialog(string message, string title)
{
// TODO
throw new System.NotImplementedException();
}
public INotificationControl CreateNotificationControl(INotification notification, Location location)
{
if (location == Location.ActionCenter)

View file

@ -0,0 +1,56 @@
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.CredentialsDialog" x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:fa="http://schemas.fontawesome.io/icons/"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile"
mc:Ignorable="d" Background="Transparent" Height="750" Width="1000" FontSize="16" ResizeMode="NoResize" Topmost="True" AllowsTransparency="True" WindowStyle="None">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Templates/Colors.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid Background="#66000000" FocusManager.FocusedElement="{Binding ElementName=Password}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" />
<Grid Grid.Row="1" Background="White">
<Grid Margin="25">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<fa:ImageAwesome Grid.Column="0" Name="PurposeIcon" Foreground="LightGray" Icon="Key" Margin="25" Rotation="90" Width="75" />
<Grid Grid.Column="1" Margin="25,0,25,25">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" x:Name="Message" Margin="0,20" TextWrapping="WrapWithOverflow" VerticalAlignment="Bottom" />
<StackPanel Grid.Row="1" Orientation="Vertical">
<Label Name="UsernameLabel" Padding="0,5" Target="{Binding ElementName=Username}" />
<TextBox Name="Username" Padding="12" VerticalContentAlignment="Center" />
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Vertical">
<Label Name="PasswordLabel" Padding="0,5" Target="{Binding ElementName=Password}" />
<PasswordBox x:Name="Password" Padding="12" VerticalContentAlignment="Center" />
</StackPanel>
</Grid>
</Grid>
</Grid>
<Grid Grid.Row="2" Background="{StaticResource BackgroundBrush}">
<WrapPanel Orientation="Horizontal" Margin="50,25" HorizontalAlignment="Right" VerticalAlignment="Center">
<Button x:Name="ConfirmButton" Cursor="Hand" Margin="20,0" Padding="20,10" MinWidth="100" />
<Button x:Name="CancelButton" Cursor="Hand" Padding="20,10" MinWidth="100" />
</WrapPanel>
</Grid>
</Grid>
</Window>

View file

@ -0,0 +1,148 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Windows;
using System.Windows.Input;
using FontAwesome.WPF;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
namespace SafeExamBrowser.UserInterface.Mobile.Windows
{
internal partial class CredentialsDialog : Window, ICredentialsDialog
{
private readonly IText text;
private WindowClosedEventHandler closed;
private WindowClosingEventHandler closing;
event WindowClosedEventHandler IWindow.Closed
{
add { closed += value; }
remove { closed -= value; }
}
event WindowClosingEventHandler IWindow.Closing
{
add { closing += value; }
remove { closing -= value; }
}
internal CredentialsDialog(CredentialsDialogPurpose purpose, string message, string title, IText text)
{
this.text = text;
InitializeComponent();
InitializeCredentialsDialog(purpose, message, title);
}
public void BringToForeground()
{
Dispatcher.Invoke(Activate);
}
public CredentialsDialogResult Show(IWindow parent = null)
{
return Dispatcher.Invoke(() =>
{
var result = new CredentialsDialogResult { Success = false };
if (parent is Window)
{
Owner = parent as Window;
}
InitializeBounds();
if (ShowDialog() is true)
{
result.Password = Password.Password;
result.Success = true;
result.Username = Username.Text;
}
return result;
});
}
private void InitializeBounds()
{
Left = 0;
Top = 0;
Height = SystemParameters.PrimaryScreenHeight;
Width = SystemParameters.PrimaryScreenWidth;
}
private void InitializeCredentialsDialog(CredentialsDialogPurpose purpose, string message, string title)
{
InitializeBounds();
if (purpose == CredentialsDialogPurpose.WirelessNetwork)
{
PurposeIcon.Icon = FontAwesomeIcon.Wifi;
PurposeIcon.Rotation = 0;
UsernameLabel.Content = text.Get(TextKey.CredentialsDialog_UsernameOptionalLabel);
}
else
{
PurposeIcon.Icon = FontAwesomeIcon.Key;
PurposeIcon.Rotation = 90;
UsernameLabel.Content = text.Get(TextKey.CredentialsDialog_UsernameLabel);
}
PasswordLabel.Content = text.Get(TextKey.CredentialsDialog_PasswordLabel);
Message.Text = message;
Title = title;
Closed += (o, args) => closed?.Invoke();
Closing += (o, args) => closing?.Invoke();
Loaded += (o, args) => Activate();
CancelButton.Content = text.Get(TextKey.PasswordDialog_Cancel);
CancelButton.Click += CancelButton_Click;
ConfirmButton.Content = text.Get(TextKey.PasswordDialog_Confirm);
ConfirmButton.Click += ConfirmButton_Click;
Password.KeyDown += Password_KeyDown;
SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
private void Password_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
DialogResult = true;
Close();
}
}
private void SystemParameters_StaticPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(SystemParameters.WorkArea))
{
Dispatcher.InvokeAsync(InitializeBounds);
}
}
}
}

View file

@ -27,11 +27,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.SystemCompo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.UserInterface.Desktop", "SafeExamBrowser.UserInterface.Desktop\SafeExamBrowser.UserInterface.Desktop.csproj", "{A502DF54-7169-4647-94BD-18B192924866}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{E03B19EC-8E4D-481C-9C1B-5C6C060D3D6B}"
ProjectSection(SolutionItems) = preProject
Libraries\SimpleWifi.dll = Libraries\SimpleWifi.dll
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Client", "SafeExamBrowser.Client\SafeExamBrowser.Client.csproj", "{7CC5A895-E0D3-4E43-9B39-CCEC05A5A6A7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Client.UnitTests", "SafeExamBrowser.Client.UnitTests\SafeExamBrowser.Client.UnitTests.csproj", "{15684416-FADF-4C51-85DE-4F343BFAB752}"