SEBWIN-219: Basic startup sequence is now working.

This commit is contained in:
dbuechel 2018-02-16 13:15:16 +01:00
parent d935407ecb
commit d3dea29ecd
23 changed files with 329 additions and 114 deletions

View file

@ -7,7 +7,6 @@
*/
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
@ -56,14 +55,16 @@ namespace SafeExamBrowser.Client
{
base.OnStartup(e);
ShutdownMode = ShutdownMode.OnMainWindowClose;
instances.BuildObjectGraph();
instances.LogStartupInformation();
var success = instances.ClientController.TryStart();
if (success)
{
MainWindow = instances.Taskbar;
MainWindow.Closing += MainWindow_Closing;
MainWindow.Show();
}
else
@ -72,10 +73,12 @@ namespace SafeExamBrowser.Client
}
}
private void MainWindow_Closing(object sender, CancelEventArgs e)
protected override void OnExit(ExitEventArgs e)
{
MainWindow?.Hide();
instances.ClientController.Terminate();
instances.LogShutdownInformation();
base.OnExit(e);
}
}
}

View file

@ -17,10 +17,13 @@ namespace SafeExamBrowser.Client.Communication
{
internal class ClientHost : BaseHost, IClientHost
{
private int processId;
public Guid StartupToken { private get; set; }
public ClientHost(string address, ILogger logger) : base(address, logger)
public ClientHost(string address, ILogger logger, int processId) : base(address, logger)
{
this.processId = processId;
}
protected override bool OnConnect(Guid? token)
@ -36,13 +39,19 @@ namespace SafeExamBrowser.Client.Communication
protected override Response OnReceive(Message message)
{
// TODO
return null;
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
}
protected override Response OnReceive(MessagePurport message)
protected override Response OnReceive(SimpleMessagePurport message)
{
// TODO
return null;
switch (message)
{
case SimpleMessagePurport.Authenticate:
return new AuthenticationResponse { ProcessId = processId };
}
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
}
}
}

View file

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using SafeExamBrowser.Browser;
using SafeExamBrowser.Client.Behaviour;
using SafeExamBrowser.Client.Behaviour.Operations;
@ -37,6 +38,10 @@ namespace SafeExamBrowser.Client
{
internal class CompositionRoot
{
private string logFilePath;
private string runtimeHostUri;
private Guid startupToken;
private ClientConfiguration configuration;
private ILogger logger;
private INativeMethods nativeMethods;
@ -49,21 +54,19 @@ namespace SafeExamBrowser.Client
internal void BuildObjectGraph()
{
var args = Environment.GetCommandLineArgs();
Validate(args);
ValidateCommandLineArguments();
configuration = new ClientConfiguration();
logger = new Logger();
nativeMethods = new NativeMethods();
systemInfo = new SystemInfo();
InitializeLogging(args[1]);
InitializeLogging();
text = new Text(logger);
uiFactory = new UserInterfaceFactory(text);
var runtimeProxy = new RuntimeProxy(args[2], new ModuleLogger(logger, typeof(RuntimeProxy)));
var runtimeProxy = new RuntimeProxy(runtimeHostUri, new ModuleLogger(logger, typeof(RuntimeProxy)));
var displayMonitor = new DisplayMonitor(new ModuleLogger(logger, typeof(DisplayMonitor)), nativeMethods);
var processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor)), nativeMethods);
var windowMonitor = new WindowMonitor(new ModuleLogger(logger, typeof(WindowMonitor)), nativeMethods);
@ -73,25 +76,38 @@ namespace SafeExamBrowser.Client
var operations = new Queue<IOperation>();
operations.Enqueue(new I18nOperation(logger, text));
operations.Enqueue(new RuntimeConnectionOperation(logger, runtimeProxy, Guid.Parse(args[3])));
operations.Enqueue(new RuntimeConnectionOperation(logger, runtimeProxy, startupToken));
operations.Enqueue(new ConfigurationOperation(configuration, logger, runtimeProxy));
operations.Enqueue(new DelayedInitializationOperation(BuildCommunicationHostOperation));
operations.Enqueue(new DelayedInitializationOperation(BuildKeyboardInterceptorOperation));
operations.Enqueue(new WindowMonitorOperation(logger, windowMonitor));
operations.Enqueue(new ProcessMonitorOperation(logger, processMonitor));
// TODO
//operations.Enqueue(new DelayedInitializationOperation(BuildKeyboardInterceptorOperation));
//operations.Enqueue(new WindowMonitorOperation(logger, windowMonitor));
//operations.Enqueue(new ProcessMonitorOperation(logger, processMonitor));
operations.Enqueue(new DisplayMonitorOperation(displayMonitor, logger, Taskbar));
operations.Enqueue(new DelayedInitializationOperation(BuildTaskbarOperation));
operations.Enqueue(new DelayedInitializationOperation(BuildBrowserOperation));
operations.Enqueue(new ClipboardOperation(logger, nativeMethods));
operations.Enqueue(new DelayedInitializationOperation(BuildMouseInterceptorOperation));
//operations.Enqueue(new DelayedInitializationOperation(BuildMouseInterceptorOperation));
var sequence = new OperationSequence(logger, operations);
ClientController = new ClientController(displayMonitor, logger, sequence, processMonitor, runtimeProxy, Taskbar, windowMonitor);
}
private void Validate(string[] args)
internal void LogStartupInformation()
{
logger.Log($"# New client instance started at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
logger.Log(string.Empty);
}
internal void LogShutdownInformation()
{
logger?.Log($"# Client instance terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
private void ValidateCommandLineArguments()
{
var args = Environment.GetCommandLineArgs();
var hasFour = args?.Length == 4;
if (hasFour)
@ -102,16 +118,20 @@ namespace SafeExamBrowser.Client
if (hasLogfilePath && hasHostUri && hasToken)
{
logFilePath = args[1];
runtimeHostUri = args[2];
startupToken = Guid.Parse(args[3]);
return;
}
}
throw new ArgumentException("Invalid parameters! Required: SafeExamBrowser.Client.exe <logfile path> <host URI> <token>");
throw new ArgumentException("Invalid arguments! Required: SafeExamBrowser.Client.exe <logfile path> <host URI> <token>");
}
private void InitializeLogging(string filePath)
private void InitializeLogging()
{
var logFileWriter = new LogFileWriter(new DefaultLogFormatter(), filePath);
var logFileWriter = new LogFileWriter(new DefaultLogFormatter(), logFilePath);
logFileWriter.Initialize();
logger.Subscribe(logFileWriter);
@ -128,9 +148,12 @@ namespace SafeExamBrowser.Client
private IOperation BuildCommunicationHostOperation()
{
var host = new ClientHost(configuration.RuntimeInfo.ClientAddress, new ModuleLogger(logger, typeof(ClientHost)));
var processId = Process.GetCurrentProcess().Id;
var host = new ClientHost(configuration.RuntimeInfo.ClientAddress, new ModuleLogger(logger, typeof(ClientHost)), processId);
var operation = new CommunicationOperation(host, logger);
host.StartupToken = startupToken;
return operation;
}

View file

@ -73,6 +73,9 @@ namespace SafeExamBrowser.Configuration
};
settings.Browser.StartUrl = "https://www.duckduckgo.com";
settings.Taskbar.AllowApplicationLog = true;
settings.Taskbar.AllowKeyboardLayout = true;
settings.Taskbar.AllowWirelessNetwork = true;
CurrentSettings = settings;

View file

@ -19,6 +19,7 @@ namespace SafeExamBrowser.Contracts.Communication
[ServiceKnownType(typeof(AuthenticationResponse))]
[ServiceKnownType(typeof(ConfigurationResponse))]
[ServiceKnownType(typeof(ClientConfiguration))]
[ServiceKnownType(typeof(SimpleResponse))]
public interface ICommunication
{
/// <summary>

View file

@ -20,11 +20,13 @@ namespace SafeExamBrowser.Contracts.Communication
/// <summary>
/// Starts the host and opens it for communication.
/// </summary>
/// <exception cref="System.ServiceModel.CommunicationException">If the host fails to start.</exception>
void Start();
/// <summary>
/// Closes and terminates the host.
/// </summary>
/// <exception cref="System.ServiceModel.CommunicationException">If the host fails to terminate.</exception>
void Stop();
}
}

View file

@ -17,5 +17,10 @@ namespace SafeExamBrowser.Contracts.Communication.Messages
/// The communication token needed for authentication.
/// </summary>
public Guid CommunicationToken { get; set; }
public override string ToString()
{
return GetType().Name;
}
}
}

View file

@ -16,6 +16,16 @@ namespace SafeExamBrowser.Contracts.Communication.Messages
/// <summary>
/// The purport of the message.
/// </summary>
public MessagePurport Purport { get; set; }
public SimpleMessagePurport Purport { get; set; }
public SimpleMessage(SimpleMessagePurport purport)
{
Purport = purport;
}
public override string ToString()
{
return $"{base.ToString()} -> {Purport}";
}
}
}

View file

@ -11,7 +11,7 @@ using System;
namespace SafeExamBrowser.Contracts.Communication.Messages
{
[Serializable]
public enum MessagePurport
public enum SimpleMessagePurport
{
/// <summary>
/// Requests an interlocutor to submit data for authentication.

View file

@ -16,6 +16,6 @@ namespace SafeExamBrowser.Contracts.Communication.Responses
/// <summary>
/// The process identifier used for authentication.
/// </summary>
public int ProcessId { get; }
public int ProcessId { get; set; }
}
}

View file

@ -13,5 +13,9 @@ namespace SafeExamBrowser.Contracts.Communication.Responses
[Serializable]
public class Response
{
public override string ToString()
{
return GetType().Name;
}
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* 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;
namespace SafeExamBrowser.Contracts.Communication.Responses
{
[Serializable]
public class SimpleResponse : Response
{
/// <summary>
/// The purport of the response.
/// </summary>
public SimpleResponsePurport Purport { get; set; }
public SimpleResponse(SimpleResponsePurport purport)
{
Purport = purport;
}
public override string ToString()
{
return $"{base.ToString()} -> {Purport}";
}
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* 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;
namespace SafeExamBrowser.Contracts.Communication.Responses
{
[Serializable]
public enum SimpleResponsePurport
{
/// <summary>
/// Signals an interlocutor that a message has been understood.
/// </summary>
Acknowledged = 1,
/// <summary>
/// Signals an interlocutor that a message has not been understood.
/// </summary>
UnknownMessage
}
}

View file

@ -66,13 +66,15 @@
<Compile Include="Communication\IServiceProxy.cs" />
<Compile Include="Communication\Messages\Message.cs" />
<Compile Include="Communication\Messages\DisconnectionMessage.cs" />
<Compile Include="Communication\Messages\MessagePurport.cs" />
<Compile Include="Communication\Messages\SimpleMessagePurport.cs" />
<Compile Include="Communication\Messages\SimpleMessage.cs" />
<Compile Include="Communication\Responses\AuthenticationResponse.cs" />
<Compile Include="Communication\Responses\ConfigurationResponse.cs" />
<Compile Include="Communication\Responses\ConnectionResponse.cs" />
<Compile Include="Communication\Responses\DisconnectionResponse.cs" />
<Compile Include="Communication\Responses\Response.cs" />
<Compile Include="Communication\Responses\SimpleResponsePurport.cs" />
<Compile Include="Communication\Responses\SimpleResponse.cs" />
<Compile Include="Configuration\ClientConfiguration.cs" />
<Compile Include="Configuration\RuntimeInfo.cs" />
<Compile Include="Configuration\ISession.cs" />

View file

@ -8,6 +8,7 @@
using System;
using System.ServiceModel;
using System.Threading;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Messages;
using SafeExamBrowser.Contracts.Communication.Responses;
@ -18,16 +19,26 @@ namespace SafeExamBrowser.Core.Communication
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
public abstract class BaseHost : ICommunication, ICommunicationHost
{
private const int TWO_SECONDS = 2000;
private readonly object @lock = new object();
private string address;
private ILogger logger;
private ServiceHost host;
private Thread hostThread;
protected Guid? CommunicationToken { get; private set; }
protected ILogger Logger { get; private set; }
public bool IsRunning
{
get { return host?.State == CommunicationState.Opened; }
get
{
lock (@lock)
{
return host?.State == CommunicationState.Opened;
}
}
}
public BaseHost(string address, ILogger logger)
@ -39,86 +50,111 @@ namespace SafeExamBrowser.Core.Communication
protected abstract bool OnConnect(Guid? token);
protected abstract void OnDisconnect();
protected abstract Response OnReceive(Message message);
protected abstract Response OnReceive(MessagePurport message);
protected abstract Response OnReceive(SimpleMessagePurport message);
public ConnectionResponse Connect(Guid? token = null)
{
logger.Debug($"Received connection request with authentication token '{token}'.");
var response = new ConnectionResponse();
var connected = OnConnect(token);
if (connected)
lock (@lock)
{
response.CommunicationToken = CommunicationToken = Guid.NewGuid();
response.ConnectionEstablished = true;
logger.Debug($"Received connection request with authentication token '{token}'.");
var response = new ConnectionResponse();
var connected = OnConnect(token);
if (connected)
{
response.CommunicationToken = CommunicationToken = Guid.NewGuid();
response.ConnectionEstablished = true;
}
logger.Debug($"{(connected ? "Accepted" : "Denied")} connection request.");
return response;
}
logger.Debug($"{(connected ? "Accepted" : "Denied")} connection request.");
return response;
}
public DisconnectionResponse Disconnect(DisconnectionMessage message)
{
var response = new DisconnectionResponse();
// TODO: Compare with ToString in BaseProxy - needed?
logger.Debug($"Received disconnection request with message '{message}'.");
if (IsAuthorized(message?.CommunicationToken))
lock (@lock)
{
OnDisconnect();
var response = new DisconnectionResponse();
CommunicationToken = null;
response.ConnectionTerminated = true;
logger.Debug($"Received disconnection request with message '{ToString(message)}'.");
if (IsAuthorized(message?.CommunicationToken))
{
OnDisconnect();
CommunicationToken = null;
response.ConnectionTerminated = true;
}
return response;
}
return response;
}
public Response Send(Message message)
{
Response response = null;
logger.Debug($"Received message '{message}'.");
if (IsAuthorized(message?.CommunicationToken))
lock (@lock)
{
if (message is SimpleMessage)
Response response = null;
logger.Debug($"Received message '{ToString(message)}'.");
if (IsAuthorized(message?.CommunicationToken))
{
response = OnReceive((message as SimpleMessage).Purport);
}
else
{
response = OnReceive(message);
if (message is SimpleMessage)
{
response = OnReceive((message as SimpleMessage).Purport);
}
else
{
response = OnReceive(message);
}
}
logger.Debug($"Sending response '{ToString(response)}'.");
return response;
}
logger.Debug($"Sending response '{response}'.");
return response;
}
public void Start()
{
host = new ServiceHost(this);
host.AddServiceEndpoint(typeof(ICommunication), new NetNamedPipeBinding(NetNamedPipeSecurityMode.Transport), address);
host.Closed += Host_Closed;
host.Closing += Host_Closing;
host.Faulted += Host_Faulted;
host.Opened += Host_Opened;
host.Opening += Host_Opening;
host.UnknownMessageReceived += Host_UnknownMessageReceived;
host.Open();
lock (@lock)
{
var exception = default(Exception);
var startedEvent = new AutoResetEvent(false);
logger.Debug($"Successfully started communication host for endpoint '{address}'.");
hostThread = new Thread(() => TryStartHost(startedEvent, out exception));
hostThread.SetApartmentState(ApartmentState.STA);
hostThread.IsBackground = true;
hostThread.Start();
var success = startedEvent.WaitOne(TWO_SECONDS);
if (!success)
{
throw new CommunicationException($"Failed to start communication host for endpoint '{address}' within {TWO_SECONDS / 1000} seconds!", exception);
}
}
}
public void Stop()
{
host?.Close();
logger.Debug($"Terminated communication host for endpoint '{address}'.");
lock (@lock)
{
var success = TryStopHost(out Exception exception);
if (success)
{
logger.Debug($"Terminated communication host for endpoint '{address}'.");
}
else
{
throw new CommunicationException($"Failed to terminate communication host for endpoint '{address}'!", exception);
}
}
}
private bool IsAuthorized(Guid? token)
@ -126,6 +162,52 @@ namespace SafeExamBrowser.Core.Communication
return CommunicationToken == token;
}
private void TryStartHost(AutoResetEvent startedEvent, out Exception exception)
{
exception = null;
try
{
host = new ServiceHost(this);
host.AddServiceEndpoint(typeof(ICommunication), new NetNamedPipeBinding(NetNamedPipeSecurityMode.Transport), address);
host.Closed += Host_Closed;
host.Closing += Host_Closing;
host.Faulted += Host_Faulted;
host.Opened += Host_Opened;
host.Opening += Host_Opening;
host.UnknownMessageReceived += Host_UnknownMessageReceived;
host.Open();
logger.Debug($"Successfully started communication host for endpoint '{address}'.");
startedEvent.Set();
}
catch (Exception e)
{
exception = e;
}
}
private bool TryStopHost(out Exception exception)
{
var success = false;
exception = null;
try
{
host?.Close();
success = hostThread.Join(TWO_SECONDS);
}
catch (Exception e)
{
exception = e;
success = false;
}
return success;
}
private void Host_Closed(object sender, EventArgs e)
{
logger.Debug("Communication host has been closed.");
@ -155,5 +237,15 @@ namespace SafeExamBrowser.Core.Communication
{
logger.Debug($"Communication host has received an unknown message: {e?.Message}.");
}
private string ToString(Message message)
{
return message != null ? message.ToString() : "<null>";
}
private string ToString(Response response)
{
return response != null ? response.ToString() : "<null>";
}
}
}

View file

@ -40,7 +40,7 @@ namespace SafeExamBrowser.Core.Communication
(channel as ICommunicationObject).Opened += BaseProxy_Opened;
(channel as ICommunicationObject).Opening += BaseProxy_Opening;
Logger.Debug($"Trying to connect to endpoint {address} with authentication token '{token}'...");
Logger.Debug($"Trying to connect to endpoint {address}{(token.HasValue ? $" with authentication token '{token}'" : string.Empty)}...");
var response = channel.Connect(token);
@ -70,21 +70,14 @@ namespace SafeExamBrowser.Core.Communication
var response = channel.Send(message);
Logger.Debug($"Sent {ToString(message)}, got {ToString(response)}.");
Logger.Debug($"Sent message '{ToString(message)}', got response '{ToString(response)}'.");
return response;
}
protected Response Send(MessagePurport purport)
protected Response Send(SimpleMessagePurport purport)
{
FailIfNotConnected(nameof(Send));
var message = new SimpleMessage { Purport = purport };
var response = channel.Send(message);
Logger.Debug($"Sent {ToString(message)}, got {ToString(response)}.");
return response;
return Send(new SimpleMessage(purport));
}
private void BaseProxy_Closed(object sender, EventArgs e)
@ -132,12 +125,12 @@ namespace SafeExamBrowser.Core.Communication
private string ToString(Message message)
{
return message != null ? $"message of type '{message.GetType()}'" : "no message";
return message != null ? message.ToString() : "<null>";
}
private string ToString(Response response)
{
return response != null ? $"response of type '{response.GetType()}'" : "no response";
return response != null ? response.ToString() : "<null>";
}
}
}

View file

@ -21,7 +21,9 @@ namespace SafeExamBrowser.Core.Communication
public AuthenticationResponse RequestAuthentication()
{
return (AuthenticationResponse) Send(MessagePurport.ClientIsReady);
var response = Send(SimpleMessagePurport.Authenticate);
return response as AuthenticationResponse;
}
}
}

View file

@ -22,12 +22,12 @@ namespace SafeExamBrowser.Core.Communication
public ClientConfiguration GetConfiguration()
{
return ((ConfigurationResponse) Send(MessagePurport.ConfigurationNeeded)).Configuration;
return ((ConfigurationResponse) Send(SimpleMessagePurport.ConfigurationNeeded)).Configuration;
}
public void InformClientReady()
{
Send(MessagePurport.ClientIsReady);
Send(SimpleMessagePurport.ClientIsReady);
}
}
}

View file

@ -81,6 +81,15 @@ namespace SafeExamBrowser.Core.Logging
details.AppendLine();
details.AppendLine(exception.StackTrace);
for (var inner = exception.InnerException; inner != null; inner = inner.InnerException)
{
details.AppendLine();
details.AppendLine($" Inner Exception Message: {inner.Message}");
details.AppendLine($" Inner Exception Type: {inner.GetType()}");
details.AppendLine();
details.AppendLine(inner.StackTrace);
}
Add(LogLevel.Error, message);
Add(new LogText(details.ToString()));
}

View file

@ -104,19 +104,19 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
{
const int TEN_SECONDS = 10000;
var clientReady = new AutoResetEvent(false);
var clientReadyHandler = new CommunicationEventHandler(() => clientReady.Set());
var clientStarted = false;
var clientReadyEvent = new AutoResetEvent(false);
var clientReadyEventHandler = new CommunicationEventHandler(() => clientReadyEvent.Set());
var clientExecutable = configuration.RuntimeInfo.ClientExecutablePath;
var clientLogFile = $"{'"' + configuration.RuntimeInfo.ClientLogFile + '"'}";
var hostUri = configuration.RuntimeInfo.RuntimeAddress;
var token = session.StartupToken.ToString("D");
runtimeHost.ClientReady += clientReadyHandler;
runtimeHost.ClientReady += clientReadyEventHandler;
session.ClientProcess = processFactory.StartNew(clientExecutable, clientLogFile, hostUri, token);
var clientStarted = clientReady.WaitOne(TEN_SECONDS);
runtimeHost.ClientReady -= clientReadyHandler;
clientStarted = clientReadyEvent.WaitOne(TEN_SECONDS);
runtimeHost.ClientReady -= clientReadyEventHandler;
// TODO: Check if client process alive!
if (clientStarted)
@ -126,7 +126,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
var response = client.RequestAuthentication();
// TODO: Further integrity checks necessary?
if (session.ClientProcess.Id == response.ProcessId)
if (session.ClientProcess.Id == response?.ProcessId)
{
sessionRunning = true;
}

View file

@ -7,7 +7,6 @@
*/
using System;
using System.Threading.Tasks;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Messages;
using SafeExamBrowser.Contracts.Communication.Responses;
@ -43,21 +42,21 @@ namespace SafeExamBrowser.Runtime.Communication
protected override Response OnReceive(Message message)
{
// TODO
return null;
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
}
protected override Response OnReceive(MessagePurport message)
protected override Response OnReceive(SimpleMessagePurport message)
{
switch (message)
{
case MessagePurport.ClientIsReady:
case SimpleMessagePurport.ClientIsReady:
ClientReady?.Invoke();
break;
case MessagePurport.ConfigurationNeeded:
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case SimpleMessagePurport.ConfigurationNeeded:
return new ConfigurationResponse { Configuration = configuration.BuildClientConfiguration() };
}
return null;
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
}
}
}

View file

@ -23,6 +23,7 @@ namespace SafeExamBrowser.SystemComponents
public class WirelessNetwork : ISystemComponent<ISystemWirelessNetworkControl>
{
private const int TWO_SECONDS = 2000;
private readonly object @lock = new object();
private ISystemWirelessNetworkControl control;
private ILogger logger;
@ -144,7 +145,7 @@ namespace SafeExamBrowser.SystemComponents
private void UpdateControl()
{
lock (networks)
lock (@lock)
{
try
{

View file

@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Classic"
xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d" Background="White" Foreground="White" Height="500" Width="750" WindowStyle="None" WindowStartupLocation="CenterScreen"
mc:Ignorable="d" Background="White" Foreground="White" Height="500" Width="850" WindowStyle="None" WindowStartupLocation="CenterScreen"
Icon="./Images/SafeExamBrowser.ico" ResizeMode="NoResize" Title="Safe Exam Browser" Topmost="True">
<Grid>
<Border x:Name="AnimatedBorder" Panel.ZIndex="10" BorderBrush="DodgerBlue" BorderThickness="5" Visibility="{Binding AnimatedBorderVisibility}">
@ -36,10 +36,10 @@
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="350" />
<ColumnDefinition Width="400" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Grid.ColumnSpan="2" Margin="-25,0,0,0" Source="pack://application:,,,/SafeExamBrowser.UserInterface.Classic;component/Images/SplashScreen.png" />
<TextBlock x:Name="InfoTextBlock" Grid.Column="1" Foreground="Gray" Margin="10,75,175,10" TextWrapping="Wrap" />
<TextBlock x:Name="InfoTextBlock" Grid.Column="1" Foreground="Gray" Margin="10,75,225,10" TextWrapping="Wrap" />
</Grid>
<ProgressBar x:Name="ProgressBar" Grid.Row="1" Background="WhiteSmoke" BorderThickness="0" Foreground="DodgerBlue"
IsIndeterminate="{Binding IsIndeterminate}" Maximum="{Binding MaxProgress}" Value="{Binding CurrentProgress}"