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 System.Linq;
using CefSharp; using CefSharp;
using CefSharp.WinForms; using CefSharp.WinForms;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Contracts.Browser; using SafeExamBrowser.Contracts.Browser;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core; using SafeExamBrowser.Contracts.Core;
using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
using SafeExamBrowser.Contracts.UserInterface.Taskbar; using SafeExamBrowser.Contracts.UserInterface.Taskbar;
using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.BrowserSettings; using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.BrowserSettings;
@ -29,6 +31,7 @@ namespace SafeExamBrowser.Browser
private AppConfig appConfig; private AppConfig appConfig;
private IApplicationButton button; private IApplicationButton button;
private IList<IApplicationInstance> instances; private IList<IApplicationInstance> instances;
private IMessageBox messageBox;
private IModuleLogger logger; private IModuleLogger logger;
private BrowserSettings settings; private BrowserSettings settings;
private IText text; private IText text;
@ -39,6 +42,7 @@ namespace SafeExamBrowser.Browser
public BrowserApplicationController( public BrowserApplicationController(
AppConfig appConfig, AppConfig appConfig,
BrowserSettings settings, BrowserSettings settings,
IMessageBox messageBox,
IModuleLogger logger, IModuleLogger logger,
IText text, IText text,
IUserInterfaceFactory uiFactory) IUserInterfaceFactory uiFactory)
@ -46,6 +50,7 @@ namespace SafeExamBrowser.Browser
this.appConfig = appConfig; this.appConfig = appConfig;
this.instances = new List<IApplicationInstance>(); this.instances = new List<IApplicationInstance>();
this.logger = logger; this.logger = logger;
this.messageBox = messageBox;
this.settings = settings; this.settings = settings;
this.text = text; this.text = text;
this.uiFactory = uiFactory; this.uiFactory = uiFactory;
@ -56,7 +61,7 @@ namespace SafeExamBrowser.Browser
var cefSettings = InitializeCefSettings(); var cefSettings = InitializeCefSettings();
var success = Cef.Initialize(cefSettings, true, null); var success = Cef.Initialize(cefSettings, true, null);
logger.Info("Initialized CEF."); logger.Info("Initialized browser engine.");
if (!success) if (!success)
{ {
@ -87,18 +92,20 @@ namespace SafeExamBrowser.Browser
Cef.Shutdown(); 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 id = new BrowserInstanceIdentifier(++instanceIdCounter);
var isMainInstance = instances.Count == 0; var isMainInstance = instances.Count == 0;
var instanceLogger = logger.CloneFor($"BrowserInstance {id}"); 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.Initialize();
instance.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args); instance.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args);
instance.PopupRequested += Instance_PopupRequested;
instance.Terminated += Instance_Terminated; instance.Terminated += Instance_Terminated;
button.RegisterInstance(instance); button.RegisterInstance(instance);
@ -120,9 +127,10 @@ namespace SafeExamBrowser.Browser
UserAgent = settings.UseCustomUserAgent ? settings.CustomUserAgent : string.Empty UserAgent = settings.UseCustomUserAgent ? settings.CustomUserAgent : string.Empty
}; };
logger.Debug($"CEF cache path is '{cefSettings.CachePath}'."); logger.Debug($"Browser cache path: {cefSettings.CachePath}");
logger.Debug($"CEF log file is '{cefSettings.LogFile}'."); logger.Debug($"Browser log file: {cefSettings.LogFile}");
logger.Debug($"CEF log severity is '{cefSettings.LogSeverity}'."); logger.Debug($"Browser log severity: {cefSettings.LogSeverity}");
logger.Debug($"Browser engine version: Chromium {Cef.ChromiumVersion}, CEF {Cef.CefVersion}, CefSharp {Cef.CefSharpVersion}");
return cefSettings; 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) private void Instance_Terminated(InstanceIdentifier id)
{ {
instances.Remove(instances.FirstOrDefault(i => i.Id == 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Browser.Handlers; using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Contracts.Browser; using SafeExamBrowser.Contracts.Browser;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
@ -16,6 +17,7 @@ using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.Browser; using SafeExamBrowser.Contracts.UserInterface.Browser;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
using SafeExamBrowser.Contracts.UserInterface.Windows; using SafeExamBrowser.Contracts.UserInterface.Windows;
namespace SafeExamBrowser.Browser namespace SafeExamBrowser.Browser
@ -26,6 +28,7 @@ namespace SafeExamBrowser.Browser
private IBrowserControl control; private IBrowserControl control;
private IBrowserWindow window; private IBrowserWindow window;
private bool isMainInstance; private bool isMainInstance;
private IMessageBox messageBox;
private IModuleLogger logger; private IModuleLogger logger;
private BrowserSettings settings; private BrowserSettings settings;
private IText text; private IText text;
@ -36,14 +39,16 @@ namespace SafeExamBrowser.Browser
public IWindow Window { get { return window; } } public IWindow Window { get { return window; } }
public event DownloadRequestedEventHandler ConfigurationDownloadRequested; public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event NameChangedEventHandler NameChanged;
public event InstanceTerminatedEventHandler Terminated; public event InstanceTerminatedEventHandler Terminated;
public event NameChangedEventHandler NameChanged;
public event PopupRequestedEventHandler PopupRequested;
public BrowserApplicationInstance( public BrowserApplicationInstance(
AppConfig appConfig, AppConfig appConfig,
BrowserSettings settings, BrowserSettings settings,
InstanceIdentifier id, InstanceIdentifier id,
bool isMainInstance, bool isMainInstance,
IMessageBox messageBox,
IModuleLogger logger, IModuleLogger logger,
IText text, IText text,
IUserInterfaceFactory uiFactory) IUserInterfaceFactory uiFactory)
@ -51,6 +56,7 @@ namespace SafeExamBrowser.Browser
this.appConfig = appConfig; this.appConfig = appConfig;
this.Id = id; this.Id = id;
this.isMainInstance = isMainInstance; this.isMainInstance = isMainInstance;
this.messageBox = messageBox;
this.logger = logger; this.logger = logger;
this.settings = settings; this.settings = settings;
this.text = text; this.text = text;
@ -59,13 +65,18 @@ namespace SafeExamBrowser.Browser
internal void Initialize() internal void Initialize()
{ {
var controlLogger = logger.CloneFor($"{nameof(BrowserControl)} {Id}"); var contextMenuHandler = new ContextMenuHandler(settings, text);
var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} {Id}"); var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} {Id}");
var downloadHandler = new DownloadHandler(appConfig, settings, downloadLogger); var downloadHandler = new DownloadHandler(appConfig, settings, downloadLogger);
var keyboardHandler = new KeyboardHandler();
var lifeSpanHandler = new LifeSpanHandler();
var requestHandler = new RequestHandler(appConfig);
downloadHandler.ConfigurationDownloadRequested += DownloadHandler_ConfigurationDownloadRequested; 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.AddressChanged += Control_AddressChanged;
control.LoadingStateChanged += Control_LoadingStateChanged; control.LoadingStateChanged += Control_LoadingStateChanged;
control.TitleChanged += Control_TitleChanged; control.TitleChanged += Control_TitleChanged;
@ -84,8 +95,36 @@ namespace SafeExamBrowser.Browser
logger.Debug("Initialized browser window."); 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) private void Control_AddressChanged(string address)
{ {
logger.Debug($"Navigated to '{address}'.");
window.UpdateAddress(address); window.UpdateAddress(address);
} }
@ -106,7 +145,6 @@ namespace SafeExamBrowser.Browser
{ {
args.BrowserWindow = window; args.BrowserWindow = window;
logger.Debug($"Forwarding download request for configuration file '{fileName}'."); logger.Debug($"Forwarding download request for configuration file '{fileName}'.");
ConfigurationDownloadRequested?.Invoke(fileName, args); ConfigurationDownloadRequested?.Invoke(fileName, args);
} }
else 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) private void Window_AddressChanged(string address)
{ {
logger.Debug($"The user requested to navigate to '{address}'."); logger.Debug($"The user requested to navigate to '{address}'.");
@ -123,19 +179,18 @@ namespace SafeExamBrowser.Browser
private void Window_ReloadRequested() private void Window_ReloadRequested()
{ {
logger.Debug($"The user requested to reload the current page."); HandleReloadRequest();
control.Reload();
} }
private void Window_BackwardNavigationRequested() private void Window_BackwardNavigationRequested()
{ {
logger.Debug($"The user requested to navigate backwards."); logger.Debug($"Navigating forwards...");
control.NavigateBackwards(); control.NavigateBackwards();
} }
private void Window_ForwardNavigationRequested() private void Window_ForwardNavigationRequested()
{ {
logger.Debug($"The user requested to navigate forwards."); logger.Debug($"Navigating backwards...");
control.NavigateForwards(); control.NavigateForwards();
} }
} }

View file

@ -6,27 +6,20 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using System.Windows.Forms;
using CefSharp; using CefSharp;
using CefSharp.WinForms; 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;
using SafeExamBrowser.Contracts.UserInterface.Browser.Events; using SafeExamBrowser.Contracts.UserInterface.Browser.Events;
using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.BrowserSettings;
namespace SafeExamBrowser.Browser namespace SafeExamBrowser.Browser
{ {
internal class BrowserControl : ChromiumWebBrowser, IBrowserControl internal class BrowserControl : ChromiumWebBrowser, IBrowserControl
{ {
private AppConfig appConfig; private IContextMenuHandler contextMenuHandler;
private BrowserSettings settings;
private IDownloadHandler downloadHandler; private IDownloadHandler downloadHandler;
private ILogger logger; private IKeyboardHandler keyboardHandler;
private IText text; private ILifeSpanHandler lifeSpanHandler;
private IRequestHandler requestHandler;
private AddressChangedEventHandler addressChanged; private AddressChangedEventHandler addressChanged;
private LoadingStateChangedEventHandler loadingStateChanged; private LoadingStateChangedEventHandler loadingStateChanged;
@ -51,30 +44,31 @@ namespace SafeExamBrowser.Browser
} }
public BrowserControl( public BrowserControl(
AppConfig appConfig, IContextMenuHandler contextMenuHandler,
BrowserSettings settings,
IDownloadHandler downloadHandler, IDownloadHandler downloadHandler,
ILogger logger, IKeyboardHandler keyboardHandler,
IText text) : base(settings.StartUrl) ILifeSpanHandler lifeSpanHandler,
IRequestHandler requestHandler,
string url) : base(url)
{ {
this.appConfig = appConfig; this.contextMenuHandler = contextMenuHandler;
this.downloadHandler = downloadHandler; this.downloadHandler = downloadHandler;
this.logger = logger; this.keyboardHandler = keyboardHandler;
this.settings = settings; this.lifeSpanHandler = lifeSpanHandler;
this.text = text; this.requestHandler = requestHandler;
} }
public void Initialize() public void Initialize()
{ {
AddressChanged += BrowserControl_AddressChanged; AddressChanged += (o, args) => addressChanged?.Invoke(args.Address);
LoadingStateChanged += (o, args) => loadingStateChanged?.Invoke(args.IsLoading); LoadingStateChanged += (o, args) => loadingStateChanged?.Invoke(args.IsLoading);
MouseWheel += BrowserControl_MouseWheel;
TitleChanged += (o, args) => titleChanged?.Invoke(args.Title); TitleChanged += (o, args) => titleChanged?.Invoke(args.Title);
DownloadHandler = downloadHandler; DownloadHandler = downloadHandler;
KeyboardHandler = new KeyboardHandler(settings); KeyboardHandler = keyboardHandler;
MenuHandler = new ContextMenuHandler(settings, text); LifeSpanHandler = lifeSpanHandler;
RequestHandler = new RequestHandler(appConfig); MenuHandler = contextMenuHandler;
RequestHandler = requestHandler;
} }
public void NavigateBackwards() public void NavigateBackwards()
@ -89,37 +83,12 @@ namespace SafeExamBrowser.Browser
public void NavigateTo(string address) public void NavigateTo(string address)
{ {
if (!String.IsNullOrWhiteSpace(address)) Load(address);
{
Load(address);
}
} }
public void Reload() public void Reload()
{ {
GetBrowser().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 namespace SafeExamBrowser.Browser.Handlers
{ {
/// <remarks> /// <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> /// </remarks>
internal class ContextMenuHandler : IContextMenuHandler internal class ContextMenuHandler : IContextMenuHandler
{ {

View file

@ -19,7 +19,7 @@ using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.Browser
namespace SafeExamBrowser.Browser.Handlers namespace SafeExamBrowser.Browser.Handlers
{ {
/// <remarks> /// <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> /// </remarks>
internal class DownloadHandler : IDownloadHandler internal class DownloadHandler : IDownloadHandler
{ {

View file

@ -8,21 +8,16 @@
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.BrowserSettings; using SafeExamBrowser.Browser.Events;
namespace SafeExamBrowser.Browser.Handlers namespace SafeExamBrowser.Browser.Handlers
{ {
/// <remarks> /// <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> /// </remarks>
internal class KeyboardHandler : IKeyboardHandler internal class KeyboardHandler : IKeyboardHandler
{ {
private BrowserSettings settings; public event ReloadRequestedEventHandler ReloadRequested;
public KeyboardHandler(BrowserSettings settings)
{
this.settings = settings;
}
public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey) 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) 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; 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 namespace SafeExamBrowser.Browser.Handlers
{ {
/// <remarks> /// <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> /// </remarks>
internal class RequestHandler : DefaultRequestHandler internal class RequestHandler : DefaultRequestHandler
{ {

View file

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

View file

@ -171,7 +171,7 @@ namespace SafeExamBrowser.Client
private IOperation BuildBrowserOperation() private IOperation BuildBrowserOperation()
{ {
var moduleLogger = new ModuleLogger(logger, "BrowserController"); 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 browserInfo = new BrowserApplicationInfo();
var operation = new BrowserOperation(browserController, browserInfo, logger, Taskbar, uiFactory); var operation = new BrowserOperation(browserController, browserInfo, logger, Taskbar, uiFactory);

View file

@ -13,6 +13,39 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
{ {
internal partial class DataMapper 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) private void MapMainWindowMode(Settings settings, object value)
{ {
const int FULLSCREEN = 1; 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) switch (key)
{ {
case Keys.Browser.EnablePageZoom: case Keys.Browser.AllowNavigation:
MapPageZoom(settings, value); 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; break;
case Keys.Browser.MainWindowMode: case Keys.Browser.MainWindowMode:
MapMainWindowMode(settings, value); MapMainWindowMode(settings, value);
break; break;
case Keys.Browser.ShowReloadWarning:
MapShowReloadWarning(settings, value);
break;
case Keys.ConfigurationFile.ConfigurationPurpose: case Keys.ConfigurationFile.ConfigurationPurpose:
MapConfigurationMode(settings, value); MapConfigurationMode(settings, value);
break; break;

View file

@ -20,10 +20,14 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal static class Browser 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 CustomUserAgentDesktop = "browserUserAgentWinDesktopModeCustom";
internal const string CustomUserAgentMobile = "browserUserAgentWinTouchModeCustom"; internal const string CustomUserAgentMobile = "browserUserAgentWinTouchModeCustom";
internal const string EnablePageZoom = "enableZoomPage";
internal const string MainWindowMode = "browserViewMode"; internal const string MainWindowMode = "browserViewMode";
internal const string ShowReloadWarning = "showReloadWarning";
internal const string UserAgentModeDesktop = "browserUserAgentWinDesktopMode"; internal const string UserAgentModeDesktop = "browserUserAgentWinDesktopMode";
internal const string UserAgentModeMobile = "browserUserAgentWinTouchMode"; internal const string UserAgentModeMobile = "browserUserAgentWinTouchMode";
} }

View file

@ -17,42 +17,47 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings
public class BrowserSettings public class BrowserSettings
{ {
/// <summary> /// <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> /// </summary>
public bool AllowAddressBar { get; set; } public bool AllowAddressBar { get; set; }
/// <summary> /// <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> /// </summary>
public bool AllowBackwardNavigation { get; set; } public bool AllowBackwardNavigation { get; set; }
/// <summary> /// <summary>
/// Determines whether the user should be allowed to download configuration files. /// Determines whether the user will be allowed to download configuration files.
/// </summary> /// </summary>
public bool AllowConfigurationDownloads { get; set; } public bool AllowConfigurationDownloads { get; set; }
/// <summary> /// <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> /// </summary>
public bool AllowDeveloperConsole { get; set; } public bool AllowDeveloperConsole { get; set; }
/// <summary> /// <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> /// </summary>
public bool AllowDownloads { get; set; } public bool AllowDownloads { get; set; }
/// <summary> /// <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> /// </summary>
public bool AllowForwardNavigation { get; set; } public bool AllowForwardNavigation { get; set; }
/// <summary> /// <summary>
/// Determines whether the user should be allowed to zoom webpages. /// Determines whether the user will be allowed to zoom webpages.
/// </summary> /// </summary>
public bool AllowPageZoom { get; set; } public bool AllowPageZoom { get; set; }
/// <summary> /// <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> /// </summary>
public bool AllowReloading { get; set; } public bool AllowReloading { get; set; }
@ -62,17 +67,22 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings
public string CustomUserAgent { get; set; } public string CustomUserAgent { get; set; }
/// <summary> /// <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> /// </summary>
public bool FullScreenMode { get; set; } public bool FullScreenMode { get; set; }
/// <summary> /// <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> /// </summary>
public string StartUrl { get; set; } public string StartUrl { get; set; }
/// <summary> /// <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> /// </summary>
public bool UseCustomUserAgent { get; set; } public bool UseCustomUserAgent { get; set; }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -153,12 +153,26 @@ namespace SafeExamBrowser.UserInterface.Desktop
private void ApplySettings() private void ApplySettings()
{ {
if (IsMainWindow && settings.FullScreenMode) if (IsMainWindow)
{ {
MaxHeight = SystemParameters.WorkArea.Height; if (settings.FullScreenMode)
ResizeMode = ResizeMode.NoResize; {
WindowState = WindowState.Maximized; MaxHeight = SystemParameters.WorkArea.Height;
WindowStyle = WindowStyle.None; 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; UrlTextBox.IsEnabled = settings.AllowAddressBar;

View file

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

View file

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

View file

@ -14,10 +14,10 @@
</ResourceDictionary> </ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
<Grid> <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"> <StackPanel Orientation="Horizontal">
<ContentControl x:Name="Icon" HorizontalAlignment="Left" /> <ContentControl x:Name="Icon" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0,0,10,0" />
<TextBlock x:Name="Text" Padding="5,0,5,0" HorizontalAlignment="Left" VerticalAlignment="Center" /> <TextBlock x:Name="Text" HorizontalAlignment="Left" VerticalAlignment="Center" Padding="5" />
</StackPanel> </StackPanel>
</Button> </Button>
</Grid> </Grid>

View file

@ -14,7 +14,7 @@
</ResourceDictionary> </ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
<Grid> <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>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />

View file

@ -17,8 +17,8 @@
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid>
<Popup x:Name="Popup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}"> <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"> <Border Background="LightGray" BorderBrush="Gray" BorderThickness="0.75,0.75,0.75,0">
<ScrollViewer x:Name="LayoutsScrollViewer" Background="{StaticResource BackgroundBrush}" MaxHeight="250" VerticalScrollBarVisibility="Auto"> <ScrollViewer x:Name="LayoutsScrollViewer" MaxHeight="250" VerticalScrollBarVisibility="Auto">
<ScrollViewer.Resources> <ScrollViewer.Resources>
<s:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">5</s:Double> <s:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">5</s:Double>
</ScrollViewer.Resources> </ScrollViewer.Resources>
@ -28,7 +28,11 @@
</Popup> </Popup>
<Button x:Name="Button" Background="Transparent" Template="{StaticResource TaskbarButton}" Padding="5" Width="40"> <Button x:Name="Button" Background="Transparent" Template="{StaticResource TaskbarButton}" Padding="5" Width="40">
<Grid> <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"> <Viewbox Panel.ZIndex="2" Stretch="Uniform">
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<TextBlock x:Name="LayoutCultureCode" FontWeight="Bold" TextAlignment="Center" Text="ENG"> <TextBlock x:Name="LayoutCultureCode" FontWeight="Bold" TextAlignment="Center" Text="ENG">

View file

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

View file

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

View file

@ -14,7 +14,7 @@
</ResourceDictionary> </ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
<Grid> <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>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />

View file

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

View file

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

View file

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

View file

@ -8,6 +8,13 @@
mc:Ignorable="d" mc:Ignorable="d"
Title="Taskbar" Background="#FFF0F0F0" Height="40" Width="750" WindowStyle="None" Topmost="True" Visibility="Visible" Title="Taskbar" Background="#FFF0F0F0" Height="40" Width="750" WindowStyle="None" Topmost="True" Visibility="Visible"
ResizeMode="NoResize" Icon="./Images/SafeExamBrowser.ico"> ResizeMode="NoResize" Icon="./Images/SafeExamBrowser.ico">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="./Templates/ScrollViewers.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
@ -16,10 +23,8 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="40" /> <ColumnDefinition Width="40" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0" x:Name="ApplicationScrollViewer" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto"> <ScrollViewer Grid.Column="0" x:Name="ApplicationScrollViewer" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto"
<ScrollViewer.Resources> Template="{StaticResource SmallBarScrollViewer}">
<s:Double x:Key="{x:Static SystemParameters.HorizontalScrollBarHeightKey}">5</s:Double>
</ScrollViewer.Resources>
<StackPanel x:Name="ApplicationStackPanel" Orientation="Horizontal" /> <StackPanel x:Name="ApplicationStackPanel" Orientation="Horizontal" />
</ScrollViewer> </ScrollViewer>
<StackPanel Grid.Column="1" x:Name="NotificationStackPanel" Orientation="Horizontal" VerticalAlignment="Stretch" /> <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 /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646287(v=vs.85).aspx
/// </summary> /// </summary>
internal const int WM_SYSKEYUP = 0x105; 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: case Constant.WM_SYSKEYUP:
return KeyState.Released; return KeyState.Released;
default: default:
return KeyState.None; return KeyState.Unknown;
} }
} }

View file

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