SEBWIN-106: Implemented basic popup and reload handling, revised browser control implementation and added mouse button interception for navigation (auxiliary) keys. Also finally implemented a custom template for small scrollbars in scrollviewers.

This commit is contained in:
dbuechel 2019-01-17 11:12:17 +01:00
parent 91c2417930
commit f949a19f32
40 changed files with 454 additions and 158 deletions

View file

@ -11,12 +11,14 @@ using System.Collections.Generic;
using System.Linq;
using CefSharp;
using CefSharp.WinForms;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Contracts.Browser;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.BrowserSettings;
@ -29,6 +31,7 @@ namespace SafeExamBrowser.Browser
private AppConfig appConfig;
private IApplicationButton button;
private IList<IApplicationInstance> instances;
private IMessageBox messageBox;
private IModuleLogger logger;
private BrowserSettings settings;
private IText text;
@ -39,6 +42,7 @@ namespace SafeExamBrowser.Browser
public BrowserApplicationController(
AppConfig appConfig,
BrowserSettings settings,
IMessageBox messageBox,
IModuleLogger logger,
IText text,
IUserInterfaceFactory uiFactory)
@ -46,6 +50,7 @@ namespace SafeExamBrowser.Browser
this.appConfig = appConfig;
this.instances = new List<IApplicationInstance>();
this.logger = logger;
this.messageBox = messageBox;
this.settings = settings;
this.text = text;
this.uiFactory = uiFactory;
@ -56,7 +61,7 @@ namespace SafeExamBrowser.Browser
var cefSettings = InitializeCefSettings();
var success = Cef.Initialize(cefSettings, true, null);
logger.Info("Initialized CEF.");
logger.Info("Initialized browser engine.");
if (!success)
{
@ -87,18 +92,20 @@ namespace SafeExamBrowser.Browser
Cef.Shutdown();
logger.Info("Terminated CEF.");
logger.Info("Terminated browser engine.");
}
private void CreateNewInstance()
private void CreateNewInstance(BrowserSettings custom = null)
{
var id = new BrowserInstanceIdentifier(++instanceIdCounter);
var isMainInstance = instances.Count == 0;
var instanceLogger = logger.CloneFor($"BrowserInstance {id}");
var instance = new BrowserApplicationInstance(appConfig, settings, id, isMainInstance, instanceLogger, text, uiFactory);
var instanceSettings = custom ?? settings;
var instance = new BrowserApplicationInstance(appConfig, instanceSettings, id, isMainInstance, messageBox, instanceLogger, text, uiFactory);
instance.Initialize();
instance.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args);
instance.PopupRequested += Instance_PopupRequested;
instance.Terminated += Instance_Terminated;
button.RegisterInstance(instance);
@ -120,9 +127,10 @@ namespace SafeExamBrowser.Browser
UserAgent = settings.UseCustomUserAgent ? settings.CustomUserAgent : string.Empty
};
logger.Debug($"CEF cache path is '{cefSettings.CachePath}'.");
logger.Debug($"CEF log file is '{cefSettings.LogFile}'.");
logger.Debug($"CEF log severity is '{cefSettings.LogSeverity}'.");
logger.Debug($"Browser cache path: {cefSettings.CachePath}");
logger.Debug($"Browser log file: {cefSettings.LogFile}");
logger.Debug($"Browser log severity: {cefSettings.LogSeverity}");
logger.Debug($"Browser engine version: Chromium {Cef.ChromiumVersion}, CEF {Cef.CefVersion}, CefSharp {Cef.CefSharpVersion}");
return cefSettings;
}
@ -139,6 +147,27 @@ namespace SafeExamBrowser.Browser
}
}
private void Instance_PopupRequested(PopupRequestedEventArgs args)
{
var popupSettings = new BrowserSettings
{
AllowAddressBar = false,
AllowBackwardNavigation = false,
AllowConfigurationDownloads = settings.AllowConfigurationDownloads,
AllowDeveloperConsole = settings.AllowDeveloperConsole,
AllowDownloads = settings.AllowDownloads,
AllowForwardNavigation = false,
AllowPageZoom = settings.AllowPageZoom,
AllowPopups = settings.AllowPopups,
AllowReloading = settings.AllowReloading,
ShowReloadWarning = settings.ShowReloadWarning,
StartUrl = args.Url
};
logger.Info($"Received request to create new instance for '{args.Url}'...");
CreateNewInstance(popupSettings);
}
private void Instance_Terminated(InstanceIdentifier id)
{
instances.Remove(instances.FirstOrDefault(i => i.Id == id));

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Contracts.Browser;
using SafeExamBrowser.Contracts.Configuration;
@ -16,6 +17,7 @@ using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.Browser;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
using SafeExamBrowser.Contracts.UserInterface.Windows;
namespace SafeExamBrowser.Browser
@ -26,6 +28,7 @@ namespace SafeExamBrowser.Browser
private IBrowserControl control;
private IBrowserWindow window;
private bool isMainInstance;
private IMessageBox messageBox;
private IModuleLogger logger;
private BrowserSettings settings;
private IText text;
@ -36,14 +39,16 @@ namespace SafeExamBrowser.Browser
public IWindow Window { get { return window; } }
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event NameChangedEventHandler NameChanged;
public event InstanceTerminatedEventHandler Terminated;
public event NameChangedEventHandler NameChanged;
public event PopupRequestedEventHandler PopupRequested;
public BrowserApplicationInstance(
AppConfig appConfig,
BrowserSettings settings,
InstanceIdentifier id,
bool isMainInstance,
IMessageBox messageBox,
IModuleLogger logger,
IText text,
IUserInterfaceFactory uiFactory)
@ -51,6 +56,7 @@ namespace SafeExamBrowser.Browser
this.appConfig = appConfig;
this.Id = id;
this.isMainInstance = isMainInstance;
this.messageBox = messageBox;
this.logger = logger;
this.settings = settings;
this.text = text;
@ -59,13 +65,18 @@ namespace SafeExamBrowser.Browser
internal void Initialize()
{
var controlLogger = logger.CloneFor($"{nameof(BrowserControl)} {Id}");
var contextMenuHandler = new ContextMenuHandler(settings, text);
var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} {Id}");
var downloadHandler = new DownloadHandler(appConfig, settings, downloadLogger);
var keyboardHandler = new KeyboardHandler();
var lifeSpanHandler = new LifeSpanHandler();
var requestHandler = new RequestHandler(appConfig);
downloadHandler.ConfigurationDownloadRequested += DownloadHandler_ConfigurationDownloadRequested;
keyboardHandler.ReloadRequested += KeyboardHandler_ReloadRequested;
lifeSpanHandler.PopupRequested += LifeSpanHandler_PopupRequested;
control = new BrowserControl(appConfig, settings, downloadHandler, controlLogger, text);
control = new BrowserControl(contextMenuHandler, downloadHandler, keyboardHandler, lifeSpanHandler, requestHandler, settings.StartUrl);
control.AddressChanged += Control_AddressChanged;
control.LoadingStateChanged += Control_LoadingStateChanged;
control.TitleChanged += Control_TitleChanged;
@ -84,8 +95,36 @@ namespace SafeExamBrowser.Browser
logger.Debug("Initialized browser window.");
}
private void HandleReloadRequest()
{
if (settings.AllowReloading && settings.ShowReloadWarning)
{
var result = messageBox.Show(TextKey.MessageBox_ReloadConfirmation, TextKey.MessageBox_ReloadConfirmationTitle, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
if (result == MessageBoxResult.Yes)
{
logger.Debug("The user confirmed reloading the current page...");
control.Reload();
}
else
{
logger.Debug("The user aborted reloading the current page.");
}
}
else if (settings.AllowReloading)
{
logger.Debug("Reloading current page...");
control.Reload();
}
else
{
logger.Debug("Blocked reload attempt, as the user is not allowed to reload web pages.");
}
}
private void Control_AddressChanged(string address)
{
logger.Debug($"Navigated to '{address}'.");
window.UpdateAddress(address);
}
@ -106,7 +145,6 @@ namespace SafeExamBrowser.Browser
{
args.BrowserWindow = window;
logger.Debug($"Forwarding download request for configuration file '{fileName}'.");
ConfigurationDownloadRequested?.Invoke(fileName, args);
}
else
@ -115,6 +153,24 @@ namespace SafeExamBrowser.Browser
}
}
private void KeyboardHandler_ReloadRequested()
{
HandleReloadRequest();
}
private void LifeSpanHandler_PopupRequested(PopupRequestedEventArgs args)
{
if (settings.AllowPopups)
{
logger.Debug($"Forwarding request to open new window for '{args.Url}'...");
PopupRequested?.Invoke(args);
}
else
{
logger.Debug($"Blocked attempt to open new window for '{args.Url}'.");
}
}
private void Window_AddressChanged(string address)
{
logger.Debug($"The user requested to navigate to '{address}'.");
@ -123,19 +179,18 @@ namespace SafeExamBrowser.Browser
private void Window_ReloadRequested()
{
logger.Debug($"The user requested to reload the current page.");
control.Reload();
HandleReloadRequest();
}
private void Window_BackwardNavigationRequested()
{
logger.Debug($"The user requested to navigate backwards.");
logger.Debug($"Navigating forwards...");
control.NavigateBackwards();
}
private void Window_ForwardNavigationRequested()
{
logger.Debug($"The user requested to navigate forwards.");
logger.Debug($"Navigating backwards...");
control.NavigateForwards();
}
}

View file

@ -6,27 +6,20 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface.Browser;
using SafeExamBrowser.Contracts.UserInterface.Browser.Events;
using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.BrowserSettings;
namespace SafeExamBrowser.Browser
{
internal class BrowserControl : ChromiumWebBrowser, IBrowserControl
{
private AppConfig appConfig;
private BrowserSettings settings;
private IContextMenuHandler contextMenuHandler;
private IDownloadHandler downloadHandler;
private ILogger logger;
private IText text;
private IKeyboardHandler keyboardHandler;
private ILifeSpanHandler lifeSpanHandler;
private IRequestHandler requestHandler;
private AddressChangedEventHandler addressChanged;
private LoadingStateChangedEventHandler loadingStateChanged;
@ -51,30 +44,31 @@ namespace SafeExamBrowser.Browser
}
public BrowserControl(
AppConfig appConfig,
BrowserSettings settings,
IContextMenuHandler contextMenuHandler,
IDownloadHandler downloadHandler,
ILogger logger,
IText text) : base(settings.StartUrl)
IKeyboardHandler keyboardHandler,
ILifeSpanHandler lifeSpanHandler,
IRequestHandler requestHandler,
string url) : base(url)
{
this.appConfig = appConfig;
this.contextMenuHandler = contextMenuHandler;
this.downloadHandler = downloadHandler;
this.logger = logger;
this.settings = settings;
this.text = text;
this.keyboardHandler = keyboardHandler;
this.lifeSpanHandler = lifeSpanHandler;
this.requestHandler = requestHandler;
}
public void Initialize()
{
AddressChanged += BrowserControl_AddressChanged;
AddressChanged += (o, args) => addressChanged?.Invoke(args.Address);
LoadingStateChanged += (o, args) => loadingStateChanged?.Invoke(args.IsLoading);
MouseWheel += BrowserControl_MouseWheel;
TitleChanged += (o, args) => titleChanged?.Invoke(args.Title);
DownloadHandler = downloadHandler;
KeyboardHandler = new KeyboardHandler(settings);
MenuHandler = new ContextMenuHandler(settings, text);
RequestHandler = new RequestHandler(appConfig);
KeyboardHandler = keyboardHandler;
LifeSpanHandler = lifeSpanHandler;
MenuHandler = contextMenuHandler;
RequestHandler = requestHandler;
}
public void NavigateBackwards()
@ -89,37 +83,12 @@ namespace SafeExamBrowser.Browser
public void NavigateTo(string address)
{
if (!String.IsNullOrWhiteSpace(address))
{
Load(address);
}
Load(address);
}
public void Reload()
{
GetBrowser().Reload();
}
private void BrowserControl_AddressChanged(object sender, AddressChangedEventArgs args)
{
logger.Debug($"Navigated to '{args.Address}'.");
addressChanged?.Invoke(args.Address);
}
private void BrowserControl_MouseWheel(object sender, MouseEventArgs e)
{
if (settings.AllowPageZoom && ModifierKeys == Keys.Control)
{
var browser = GetBrowser();
browser.GetZoomLevelAsync().ContinueWith(task =>
{
if (task.IsCompleted)
{
browser.SetZoomLevel(task.Result + e.Delta * 0.1);
}
});
}
}
}
}

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2019 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/.
*/
namespace SafeExamBrowser.Browser.Events
{
internal class PopupRequestedEventArgs
{
public string Url { get; set; }
}
}

View file

@ -0,0 +1,12 @@
/*
* Copyright (c) 2019 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/.
*/
namespace SafeExamBrowser.Browser.Events
{
internal delegate void PopupRequestedEventHandler(PopupRequestedEventArgs args);
}

View file

@ -0,0 +1,12 @@
/*
* Copyright (c) 2019 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/.
*/
namespace SafeExamBrowser.Browser.Events
{
internal delegate void ReloadRequestedEventHandler();
}

View file

@ -13,7 +13,7 @@ using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.Browser
namespace SafeExamBrowser.Browser.Handlers
{
/// <remarks>
/// See https://cefsharp.github.io/api/63.0.0/html/T_CefSharp_IContextMenuHandler.htm.
/// See https://cefsharp.github.io/api/67.0.0/html/T_CefSharp_IContextMenuHandler.htm.
/// </remarks>
internal class ContextMenuHandler : IContextMenuHandler
{

View file

@ -19,7 +19,7 @@ using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.Browser
namespace SafeExamBrowser.Browser.Handlers
{
/// <remarks>
/// See https://cefsharp.github.io/api/63.0.0/html/T_CefSharp_IDownloadHandler.htm.
/// See https://cefsharp.github.io/api/67.0.0/html/T_CefSharp_IDownloadHandler.htm.
/// </remarks>
internal class DownloadHandler : IDownloadHandler
{

View file

@ -8,21 +8,16 @@
using System.Windows.Forms;
using CefSharp;
using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.BrowserSettings;
using SafeExamBrowser.Browser.Events;
namespace SafeExamBrowser.Browser.Handlers
{
/// <remarks>
/// See https://cefsharp.github.io/api/63.0.0/html/T_CefSharp_IKeyboardHandler.htm.
/// See https://cefsharp.github.io/api/67.0.0/html/T_CefSharp_IKeyboardHandler.htm.
/// </remarks>
internal class KeyboardHandler : IKeyboardHandler
{
private BrowserSettings settings;
public KeyboardHandler(BrowserSettings settings)
{
this.settings = settings;
}
public event ReloadRequestedEventHandler ReloadRequested;
public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey)
{
@ -31,9 +26,9 @@ namespace SafeExamBrowser.Browser.Handlers
public bool OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut)
{
if (settings.AllowReloading && type == KeyType.KeyUp && windowsKeyCode == (int) Keys.F5)
if (type == KeyType.KeyUp && windowsKeyCode == (int) Keys.F5)
{
browserControl.Reload();
ReloadRequested?.Invoke();
return true;
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2019 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 CefSharp;
using SafeExamBrowser.Browser.Events;
namespace SafeExamBrowser.Browser.Handlers
{
/// <remarks>
/// See https://cefsharp.github.io/api/67.0.0/html/T_CefSharp_ILifeSpanHandler.htm.
/// </remarks>
internal class LifeSpanHandler : ILifeSpanHandler
{
public event PopupRequestedEventHandler PopupRequested;
public bool DoClose(IWebBrowser chromiumWebBrowser, IBrowser browser)
{
return false;
}
public void OnAfterCreated(IWebBrowser chromiumWebBrowser, IBrowser browser)
{
}
public void OnBeforeClose(IWebBrowser chromiumWebBrowser, IBrowser browser)
{
}
public bool OnBeforePopup(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser)
{
var args = new PopupRequestedEventArgs { Url = targetUrl };
newBrowser = default(IWebBrowser);
PopupRequested?.Invoke(args);
return true;
}
}
}

View file

@ -14,7 +14,7 @@ using SafeExamBrowser.Contracts.Configuration;
namespace SafeExamBrowser.Browser.Handlers
{
/// <remarks>
/// See https://cefsharp.github.io/api/63.0.0/html/T_CefSharp_Handler_DefaultRequestHandler.htm.
/// See https://cefsharp.github.io/api/67.0.0/html/T_CefSharp_Handler_DefaultRequestHandler.htm.
/// </remarks>
internal class RequestHandler : DefaultRequestHandler
{

View file

@ -66,6 +66,9 @@
<Compile Include="BrowserApplicationInfo.cs" />
<Compile Include="BrowserApplicationInstance.cs" />
<Compile Include="BrowserInstanceIdentifier.cs" />
<Compile Include="Events\PopupRequestedEventArgs.cs" />
<Compile Include="Events\PopupRequestedEventHandler.cs" />
<Compile Include="Events\ReloadRequestedEventHandler.cs" />
<Compile Include="Handlers\ContextMenuHandler.cs" />
<Compile Include="BrowserControl.cs">
<SubType>Component</SubType>
@ -73,6 +76,7 @@
<Compile Include="BrowserIconResource.cs" />
<Compile Include="Handlers\DownloadHandler.cs" />
<Compile Include="Handlers\KeyboardHandler.cs" />
<Compile Include="Handlers\LifeSpanHandler.cs" />
<Compile Include="Handlers\RequestHandler.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

View file

@ -171,7 +171,7 @@ namespace SafeExamBrowser.Client
private IOperation BuildBrowserOperation()
{
var moduleLogger = new ModuleLogger(logger, "BrowserController");
var browserController = new BrowserApplicationController(configuration.AppConfig, configuration.Settings.Browser, moduleLogger, text, uiFactory);
var browserController = new BrowserApplicationController(configuration.AppConfig, configuration.Settings.Browser, messageBox, moduleLogger, text, uiFactory);
var browserInfo = new BrowserApplicationInfo();
var operation = new BrowserOperation(browserController, browserInfo, logger, Taskbar, uiFactory);

View file

@ -13,6 +13,39 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
{
internal partial class DataMapper
{
private void MapAllowNavigation(Settings settings, object value)
{
if (value is bool allow)
{
settings.Browser.AllowBackwardNavigation = allow;
settings.Browser.AllowForwardNavigation = allow;
}
}
private void MapAllowPageZoom(Settings settings, object value)
{
if (value is bool allow)
{
settings.Browser.AllowPageZoom = allow;
}
}
private void MapAllowPopups(Settings settings, object value)
{
if (value is bool block)
{
settings.Browser.AllowPopups = !block;
}
}
private void MapAllowReload(Settings settings, object value)
{
if (value is bool allow)
{
settings.Browser.AllowReloading = allow;
}
}
private void MapMainWindowMode(Settings settings, object value)
{
const int FULLSCREEN = 1;
@ -23,11 +56,11 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
}
}
private void MapPageZoom(Settings settings, object value)
private void MapShowReloadWarning(Settings settings, object value)
{
if (value is bool enabled)
if (value is bool show)
{
settings.Browser.AllowPageZoom = enabled;
settings.Browser.ShowReloadWarning = show;
}
}

View file

@ -27,12 +27,24 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
{
switch (key)
{
case Keys.Browser.EnablePageZoom:
MapPageZoom(settings, value);
case Keys.Browser.AllowNavigation:
MapAllowNavigation(settings, value);
break;
case Keys.Browser.AllowPageZoom:
MapAllowPageZoom(settings, value);
break;
case Keys.Browser.AllowPopups:
MapAllowPopups(settings, value);
break;
case Keys.Browser.AllowReload:
MapAllowReload(settings, value);
break;
case Keys.Browser.MainWindowMode:
MapMainWindowMode(settings, value);
break;
case Keys.Browser.ShowReloadWarning:
MapShowReloadWarning(settings, value);
break;
case Keys.ConfigurationFile.ConfigurationPurpose:
MapConfigurationMode(settings, value);
break;

View file

@ -20,10 +20,14 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal static class Browser
{
internal const string AllowNavigation = "allowBrowsingBackForward";
internal const string AllowPageZoom = "enableZoomPage";
internal const string AllowPopups = "blockPopUpWindows";
internal const string AllowReload = "browserWindowAllowReload";
internal const string CustomUserAgentDesktop = "browserUserAgentWinDesktopModeCustom";
internal const string CustomUserAgentMobile = "browserUserAgentWinTouchModeCustom";
internal const string EnablePageZoom = "enableZoomPage";
internal const string MainWindowMode = "browserViewMode";
internal const string ShowReloadWarning = "showReloadWarning";
internal const string UserAgentModeDesktop = "browserUserAgentWinDesktopMode";
internal const string UserAgentModeMobile = "browserUserAgentWinTouchMode";
}

View file

@ -17,42 +17,47 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings
public class BrowserSettings
{
/// <summary>
/// Determines whether the user should be allowed to change the URL of a browser window.
/// Determines whether the user will be allowed to change the URL of a browser window.
/// </summary>
public bool AllowAddressBar { get; set; }
/// <summary>
/// Determines whether the user should be allowed to navigate backwards in a browser window.
/// Determines whether the user will be allowed to navigate backwards in a browser window.
/// </summary>
public bool AllowBackwardNavigation { get; set; }
/// <summary>
/// Determines whether the user should be allowed to download configuration files.
/// Determines whether the user will be allowed to download configuration files.
/// </summary>
public bool AllowConfigurationDownloads { get; set; }
/// <summary>
/// Determines whether the user should be allowed to open the developer console of a browser window.
/// Determines whether the user will be allowed to open the developer console of a browser window.
/// </summary>
public bool AllowDeveloperConsole { get; set; }
/// <summary>
/// Determines whether the user should be allowed to download files (excluding configuration files).
/// Determines whether the user will be allowed to download files (excluding configuration files).
/// </summary>
public bool AllowDownloads { get; set; }
/// <summary>
/// Determines whether the user should be allowed to navigate forwards in a browser window.
/// Determines whether the user will be allowed to navigate forwards in a browser window.
/// </summary>
public bool AllowForwardNavigation { get; set; }
/// <summary>
/// Determines whether the user should be allowed to zoom webpages.
/// Determines whether the user will be allowed to zoom webpages.
/// </summary>
public bool AllowPageZoom { get; set; }
/// <summary>
/// Determines whether the user should be allowed to reload webpages.
/// Determines whether popup windows will be opened or not.
/// </summary>
public bool AllowPopups { get; set; }
/// <summary>
/// Determines whether the user will be allowed to reload webpages.
/// </summary>
public bool AllowReloading { get; set; }
@ -62,17 +67,22 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings
public string CustomUserAgent { get; set; }
/// <summary>
/// Determines whether the main browser window should be rendered in fullscreen mode, i.e. without window frame.
/// Determines whether the main browser window will be rendered in fullscreen mode, i.e. without window frame.
/// </summary>
public bool FullScreenMode { get; set; }
/// <summary>
/// The start URL with which a new browser window should be loaded.
/// Determines whether the user will need to confirm every reload attempt.
/// </summary>
public bool ShowReloadWarning { get; set; }
/// <summary>
/// The start URL with which a new browser window will be loaded.
/// </summary>
public string StartUrl { get; set; }
/// <summary>
/// Determines whether a custom user agent should be used for all requests, see <see cref="CustomUserAgent"/>.
/// Determines whether a custom user agent will be used for all requests, see <see cref="CustomUserAgent"/>.
/// </summary>
public bool UseCustomUserAgent { get; set; }
}

View file

@ -42,6 +42,8 @@ namespace SafeExamBrowser.Contracts.I18n
MessageBox_ReconfigurationErrorTitle,
MessageBox_ReconfigurationQuestion,
MessageBox_ReconfigurationQuestionTitle,
MessageBox_ReloadConfirmation,
MessageBox_ReloadConfirmationTitle,
MessageBox_ShutdownError,
MessageBox_ShutdownErrorTitle,
MessageBox_SingleInstance,

View file

@ -13,7 +13,7 @@ namespace SafeExamBrowser.Contracts.Monitoring
/// </summary>
public enum KeyState
{
None = 0,
Unknown = 0,
Pressed,
Released
}

View file

@ -13,7 +13,8 @@ namespace SafeExamBrowser.Contracts.Monitoring
/// </summary>
public enum MouseButton
{
None = 0,
Unknown = 0,
Auxiliary,
Left,
Middle,
Right

View file

@ -84,6 +84,12 @@
<Entry key="MessageBox_ReconfigurationQuestionTitle">
Configuration Detected
</Entry>
<Entry key="MessageBox_ReloadConfirmation">
Would you like to reload the current page?
</Entry>
<Entry key="MessageBox_ReloadConfirmationTitle">
Reload?
</Entry>
<Entry key="MessageBox_ShutdownError">
An unexpected error occurred during the shutdown procedure! Please consult the application log for more information...
</Entry>

View file

@ -27,6 +27,7 @@ namespace SafeExamBrowser.Monitoring.Mouse
{
var block = false;
block |= button == MouseButton.Auxiliary;
block |= button == MouseButton.Middle && !settings.AllowMiddleButton;
block |= button == MouseButton.Right && !settings.AllowRightButton;

View file

@ -5,8 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:fa="http://schemas.fontawesome.io/icons/"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop"
mc:Ignorable="d" Title="BrowserWindow" Background="#FFF0F0F0" Height="500" Width="750" MinHeight="250" MinWidth="250"
WindowState="Maximized" Icon=".\Images\SafeExamBrowser.ico">
mc:Ignorable="d" Title="BrowserWindow" Background="#FFF0F0F0" Height="500" Width="750" MinHeight="250" MinWidth="250" Icon=".\Images\SafeExamBrowser.ico">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>

View file

@ -153,12 +153,26 @@ namespace SafeExamBrowser.UserInterface.Desktop
private void ApplySettings()
{
if (IsMainWindow && settings.FullScreenMode)
if (IsMainWindow)
{
MaxHeight = SystemParameters.WorkArea.Height;
ResizeMode = ResizeMode.NoResize;
WindowState = WindowState.Maximized;
WindowStyle = WindowStyle.None;
if (settings.FullScreenMode)
{
MaxHeight = SystemParameters.WorkArea.Height;
ResizeMode = ResizeMode.NoResize;
WindowState = WindowState.Maximized;
WindowStyle = WindowStyle.None;
}
else
{
WindowState = WindowState.Maximized;
}
}
else
{
Top = 0;
Left = SystemParameters.WorkArea.Width / 2;
Height = SystemParameters.WorkArea.Height;
Width = SystemParameters.WorkArea.Width / 2;
}
UrlTextBox.IsEnabled = settings.AllowAddressBar;

View file

@ -11,17 +11,17 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Templates/Buttons.xaml" />
<ResourceDictionary Source="../Templates/Colors.xaml" />
<ResourceDictionary Source="../Templates/ScrollViewers.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Popup x:Name="InstancePopup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}">
<ScrollViewer x:Name="InstanceScrollViewer" Background="{StaticResource BackgroundBrush}" VerticalScrollBarVisibility="Auto">
<ScrollViewer.Resources>
<s:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">5</s:Double>
</ScrollViewer.Resources>
<StackPanel x:Name="InstanceStackPanel" />
</ScrollViewer>
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="0.75,0.75,0.75,0">
<ScrollViewer x:Name="InstanceScrollViewer" MaxHeight="400" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}">
<StackPanel x:Name="InstanceStackPanel" />
</ScrollViewer>
</Border>
</Popup>
<Button x:Name="Button" Background="{StaticResource BackgroundBrush}" Click="Button_Click" Padding="4" Template="{StaticResource TaskbarButton}" Width="50" />
</Grid>

View file

@ -6,13 +6,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using SafeExamBrowser.Contracts.Core;
using System.Windows.Threading;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
using SafeExamBrowser.Contracts.UserInterface.Taskbar.Events;
using SafeExamBrowser.UserInterface.Desktop.Utilities;
@ -36,13 +39,16 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
public void RegisterInstance(IApplicationInstance instance)
{
var instanceButton = new ApplicationInstanceButton(instance, info);
Dispatcher.Invoke(() =>
{
var instanceButton = new ApplicationInstanceButton(instance, info);
instanceButton.Clicked += (id) => Clicked?.Invoke(id);
instance.Terminated += (id) => Instance_OnTerminated(id, instanceButton);
instanceButton.Clicked += (id) => Clicked?.Invoke(id);
instance.Terminated += (id) => Instance_OnTerminated(id, instanceButton);
instances.Add(instance);
InstanceStackPanel.Children.Add(instanceButton);
instances.Add(instance);
InstanceStackPanel.Children.Add(instanceButton);
});
}
private void InitializeApplicationButton()
@ -53,13 +59,13 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
Button.Content = IconResourceLoader.Load(info.IconResource);
Button.MouseEnter += (o, args) => InstancePopup.IsOpen = instances.Count > 1;
Button.MouseLeave += (o, args) => InstancePopup.IsOpen = InstancePopup.IsMouseOver;
InstancePopup.MouseLeave += (o, args) => InstancePopup.IsOpen = IsMouseOver;
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => InstancePopup.IsOpen = InstancePopup.IsMouseOver));
InstancePopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => InstancePopup.IsOpen = IsMouseOver));
InstancePopup.Opened += (o, args) =>
{
Background = Brushes.LightBlue;
Button.Background = Brushes.LightBlue;
Background = Brushes.LightGray;
Button.Background = Brushes.LightGray;
};
InstancePopup.Closed += (o, args) =>
@ -67,14 +73,6 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
Background = originalBrush;
Button.Background = originalBrush;
};
InstanceStackPanel.SizeChanged += (o, args) =>
{
if (instances.Count > 9)
{
InstanceScrollViewer.MaxHeight = InstanceScrollViewer.ActualHeight;
}
};
}
private void Button_Click(object sender, RoutedEventArgs e)
@ -91,8 +89,11 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
private void Instance_OnTerminated(InstanceIdentifier id, ApplicationInstanceButton instanceButton)
{
instances.Remove(instances.FirstOrDefault(i => i.Id == id));
InstanceStackPanel.Children.Remove(instanceButton);
Dispatcher.BeginInvoke(new Action(() =>
{
instances.Remove(instances.FirstOrDefault(i => i.Id == id));
InstanceStackPanel.Children.Remove(instanceButton);
}));
}
}
}

View file

@ -14,10 +14,10 @@
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Button x:Name="Button" Background="{StaticResource BackgroundBrush}" Click="Button_Click" Height="25" Template="{StaticResource TaskbarButton}">
<Button x:Name="Button" Background="Transparent" Click="Button_Click" Height="40" Padding="10" Template="{StaticResource TaskbarButton}">
<StackPanel Orientation="Horizontal">
<ContentControl x:Name="Icon" HorizontalAlignment="Left" />
<TextBlock x:Name="Text" Padding="5,0,5,0" HorizontalAlignment="Left" VerticalAlignment="Center" />
<ContentControl x:Name="Icon" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0,0,10,0" />
<TextBlock x:Name="Text" HorizontalAlignment="Left" VerticalAlignment="Center" Padding="5" />
</StackPanel>
</Button>
</Grid>

View file

@ -14,7 +14,7 @@
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Button x:Name="Button" Height="40" Padding="10,0" Template="{StaticResource TaskbarButton}">
<Button x:Name="Button" Background="Transparent" Height="40" Padding="10,0" Template="{StaticResource TaskbarButton}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />

View file

@ -17,8 +17,8 @@
</UserControl.Resources>
<Grid>
<Popup x:Name="Popup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}">
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="0.5,0.5,0.5,0">
<ScrollViewer x:Name="LayoutsScrollViewer" Background="{StaticResource BackgroundBrush}" MaxHeight="250" VerticalScrollBarVisibility="Auto">
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="0.75,0.75,0.75,0">
<ScrollViewer x:Name="LayoutsScrollViewer" MaxHeight="250" VerticalScrollBarVisibility="Auto">
<ScrollViewer.Resources>
<s:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">5</s:Double>
</ScrollViewer.Resources>
@ -28,7 +28,11 @@
</Popup>
<Button x:Name="Button" Background="Transparent" Template="{StaticResource TaskbarButton}" Padding="5" Width="40">
<Grid>
<fa:ImageAwesome Panel.ZIndex="1" Foreground="LightGray" Icon="KeyboardOutline" VerticalAlignment="Center" />
<fa:ImageAwesome Panel.ZIndex="1" Foreground="LightGray" Icon="KeyboardOutline" VerticalAlignment="Center">
<fa:ImageAwesome.Effect>
<DropShadowEffect Color="White" BlurRadius="5" Direction="0" Opacity="1" ShadowDepth="0" />
</fa:ImageAwesome.Effect>
</fa:ImageAwesome>
<Viewbox Panel.ZIndex="2" Stretch="Uniform">
<StackPanel Orientation="Vertical">
<TextBlock x:Name="LayoutCultureCode" FontWeight="Bold" TextAlignment="Center" Text="ENG">

View file

@ -8,6 +8,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Media;
using SafeExamBrowser.Contracts.SystemComponents;
@ -62,13 +63,13 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
var originalBrush = Button.Background;
Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen;
Button.MouseLeave += (o, args) => Popup.IsOpen = Popup.IsMouseOver;
Popup.MouseLeave += (o, args) => Popup.IsOpen = IsMouseOver;
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
Popup.Opened += (o, args) =>
{
Background = Brushes.LightBlue;
Button.Background = Brushes.LightBlue;
Background = Brushes.LightGray;
Button.Background = Brushes.LightGray;
};
Popup.Closed += (o, args) =>

View file

@ -15,8 +15,8 @@
</UserControl.Resources>
<Grid>
<Popup x:Name="Popup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}">
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="0.5,0.5,0.5,0" MaxWidth="250" Padding="20,10,20,20">
<Grid>
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="0.75,0.75,0.75,0">
<Grid MaxWidth="250" Margin="20,10,20,20">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />

View file

@ -14,7 +14,7 @@
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Button x:Name="Button" Height="40" Padding="10,0" Template="{StaticResource TaskbarButton}">
<Button x:Name="Button" Background="Transparent" Height="40" Padding="10,0" Template="{StaticResource TaskbarButton}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />

View file

@ -17,8 +17,8 @@
</UserControl.Resources>
<Grid>
<Popup x:Name="Popup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}">
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="0.5,0.5,0.5,0">
<ScrollViewer Background="{StaticResource BackgroundBrush}" MaxHeight="250" VerticalScrollBarVisibility="Auto">
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="0.75,0.75,0.75,0">
<ScrollViewer MaxHeight="250" VerticalScrollBarVisibility="Auto">
<ScrollViewer.Resources>
<s:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">5</s:Double>
</ScrollViewer.Resources>

View file

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
@ -114,13 +115,13 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
SignalStrengthIcon.Child = GetIcon(0);
Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen;
Button.MouseLeave += (o, args) => Popup.IsOpen = Popup.IsMouseOver;
Popup.MouseLeave += (o, args) => Popup.IsOpen = IsMouseOver;
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
Popup.Opened += (o, args) =>
{
Background = Brushes.LightBlue;
Button.Background = Brushes.LightBlue;
Background = Brushes.LightGray;
Button.Background = Brushes.LightGray;
};
Popup.Closed += (o, args) =>

View file

@ -162,6 +162,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Templates\ScrollViewers.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Resource Include="Images\Battery.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>

View file

@ -8,6 +8,13 @@
mc:Ignorable="d"
Title="Taskbar" Background="#FFF0F0F0" Height="40" Width="750" WindowStyle="None" Topmost="True" Visibility="Visible"
ResizeMode="NoResize" Icon="./Images/SafeExamBrowser.ico">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="./Templates/ScrollViewers.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
@ -16,10 +23,8 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0" x:Name="ApplicationScrollViewer" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto">
<ScrollViewer.Resources>
<s:Double x:Key="{x:Static SystemParameters.HorizontalScrollBarHeightKey}">5</s:Double>
</ScrollViewer.Resources>
<ScrollViewer Grid.Column="0" x:Name="ApplicationScrollViewer" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto"
Template="{StaticResource SmallBarScrollViewer}">
<StackPanel x:Name="ApplicationStackPanel" Orientation="Horizontal" />
</ScrollViewer>
<StackPanel Grid.Column="1" x:Name="NotificationStackPanel" Orientation="Horizontal" VerticalAlignment="Stretch" />

View file

@ -0,0 +1,40 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Templates">
<ControlTemplate x:Key="Thumb" TargetType="{x:Type Thumb}">
<Border Background="DarkGray" Height="Auto" Width="Auto" />
</ControlTemplate>
<ControlTemplate x:Key="VerticalScrollBar" TargetType="{x:Type ScrollBar}">
<Track Name="PART_Track" Height="Auto" IsDirectionReversed="True" Width="10">
<Track.Thumb>
<Thumb Template="{StaticResource Thumb}" Focusable="False" Margin="2.5" OverridesDefaultStyle="True" SnapsToDevicePixels="True" />
</Track.Thumb>
</Track>
</ControlTemplate>
<ControlTemplate x:Key="HorizontalScrollBar" TargetType="{x:Type ScrollBar}">
<Track Name="PART_Track" Height="10" IsDirectionReversed="False" Width="Auto">
<Track.Thumb>
<Thumb Template="{StaticResource Thumb}" Focusable="False" Margin="2.5" OverridesDefaultStyle="True" SnapsToDevicePixels="True" />
</Track.Thumb>
</Track>
</ControlTemplate>
<ControlTemplate x:Key="SmallBarScrollViewer" TargetType="{x:Type ScrollViewer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollContentPresenter Grid.Column="0" Grid.Row="0" />
<ScrollBar Name="PART_VerticalScrollBar" Grid.Column="1" Grid.Row="0" OverridesDefaultStyle="True" Value="{TemplateBinding VerticalOffset}"
Maximum="{TemplateBinding ScrollableHeight}" Template="{StaticResource VerticalScrollBar}"
ViewportSize="{TemplateBinding ViewportHeight}" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" />
<ScrollBar Name="PART_HorizontalScrollBar" Grid.Column="0" Grid.Row="1" OverridesDefaultStyle="True" Orientation="Horizontal"
Value="{TemplateBinding HorizontalOffset}" Maximum="{TemplateBinding ScrollableWidth}" Template="{StaticResource HorizontalScrollBar}"
ViewportSize="{TemplateBinding ViewportWidth}" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" />
</Grid>
</ControlTemplate>
</ResourceDictionary>

View file

@ -184,5 +184,23 @@ namespace SafeExamBrowser.WindowsApi.Constants
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646287(v=vs.85).aspx
/// </summary>
internal const int WM_SYSKEYUP = 0x105;
/// <summary>
/// Posted when the user presses the first or second X button while the cursor is in the client area of a window. If the mouse is
/// not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
/// captured the mouse.
///
/// See https://docs.microsoft.com/de-de/windows/desktop/inputdev/wm-xbuttondown.
/// </summary>
internal const int WM_XBUTTONDOWN = 0x20B;
/// <summary>
/// Posted when the user releases the first or second X button while the cursor is in the client area of a window. If the mouse is
/// not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has
/// captured the mouse.
///
/// See https://docs.microsoft.com/de-de/windows/desktop/inputdev/wm-xbuttonup.
/// </summary>
internal const int WM_XBUTTONUP = 0x20C;
}
}

View file

@ -81,7 +81,7 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
case Constant.WM_SYSKEYUP:
return KeyState.Released;
default:
return KeyState.None;
return KeyState.Unknown;
}
}

View file

@ -82,8 +82,11 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
case Constant.WM_RBUTTONDOWN:
case Constant.WM_RBUTTONUP:
return MouseButton.Right;
case Constant.WM_XBUTTONDOWN:
case Constant.WM_XBUTTONUP:
return MouseButton.Auxiliary;
default:
return MouseButton.None;
return MouseButton.Unknown;
}
}
@ -94,13 +97,15 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
case Constant.WM_LBUTTONDOWN:
case Constant.WM_MBUTTONDOWN:
case Constant.WM_RBUTTONDOWN:
case Constant.WM_XBUTTONDOWN:
return KeyState.Pressed;
case Constant.WM_LBUTTONUP:
case Constant.WM_MBUTTONUP:
case Constant.WM_RBUTTONUP:
case Constant.WM_XBUTTONUP:
return KeyState.Released;
default:
return KeyState.None;
return KeyState.Unknown;
}
}
}