diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs
index 86902dba..3b6d191a 100644
--- a/SafeExamBrowser.Client/CompositionRoot.cs
+++ b/SafeExamBrowser.Client/CompositionRoot.cs
@@ -303,6 +303,7 @@ namespace SafeExamBrowser.Client
context.Activators.Add(new ActionCenterKeyboardActivator(ModuleLogger(nameof(ActionCenterKeyboardActivator)), nativeMethods));
context.Activators.Add(new ActionCenterTouchActivator(ModuleLogger(nameof(ActionCenterTouchActivator)), nativeMethods));
+ context.Activators.Add(new TaskbarKeyboardActivator(ModuleLogger(nameof(TaskbarKeyboardActivator)), nativeMethods));
context.Activators.Add(new TaskviewKeyboardActivator(ModuleLogger(nameof(TaskviewKeyboardActivator)), nativeMethods));
context.Activators.Add(new TerminationActivator(ModuleLogger(nameof(TerminationActivator)), nativeMethods));
diff --git a/SafeExamBrowser.Client/Operations/ShellOperation.cs b/SafeExamBrowser.Client/Operations/ShellOperation.cs
index c548b618..76a64bd4 100644
--- a/SafeExamBrowser.Client/Operations/ShellOperation.cs
+++ b/SafeExamBrowser.Client/Operations/ShellOperation.cs
@@ -118,6 +118,12 @@ namespace SafeExamBrowser.Client.Operations
{
terminationActivator.Start();
}
+
+ if (Context.Settings.Taskbar.EnableTaskbar && activator is ITaskbarActivator taskbarActivator)
+ {
+ taskbar.Register(taskbarActivator);
+ taskbarActivator.Start();
+ }
}
}
diff --git a/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj b/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj
index 5eb5ab83..8e05824e 100644
--- a/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj
+++ b/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj
@@ -87,6 +87,7 @@
+
diff --git a/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskbar.cs b/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskbar.cs
index 0dd67e8a..1a22cb12 100644
--- a/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskbar.cs
+++ b/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskbar.cs
@@ -57,6 +57,11 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell
///
void Close();
+ ///
+ /// Puts the focus on the taskbar.
+ ///
+ void Focus(bool forward = true);
+
///
/// Returns the absolute height of the taskbar (i.e. in physical pixels).
///
@@ -72,14 +77,14 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell
///
void InitializeText(IText text);
+ ///
+ /// Registers the specified activator for the taskbar.
+ ///
+ void Register(ITaskbarActivator activator);
+
///
/// Shows the taskbar.
///
void Show();
-
- ///
- /// Puts the focus on the taskbar.
- ///
- void Focus(bool forward = true);
}
}
diff --git a/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskbarActivator.cs b/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskbarActivator.cs
new file mode 100644
index 00000000..4a9fd222
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskbarActivator.cs
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2022 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 SafeExamBrowser.UserInterface.Contracts.Shell.Events;
+
+namespace SafeExamBrowser.UserInterface.Contracts.Shell
+{
+ ///
+ /// A module which can be used to activate the .
+ ///
+ public interface ITaskbarActivator : IActivator
+ {
+ ///
+ /// Fired when the taskbar should be activated (i.e. put into focus).
+ ///
+ event ActivatorEventHandler Activated;
+ }
+}
diff --git a/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml.cs
index 2d4f4126..fbde4baf 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml.cs
@@ -79,11 +79,11 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
internal BrowserWindow(IBrowserControl browserControl, BrowserSettings settings, bool isMainWindow, IText text, ILogger logger)
{
+ this.browserControl = browserControl;
this.isMainWindow = isMainWindow;
+ this.logger = logger;
this.settings = settings;
this.text = text;
- this.logger = logger;
- this.browserControl = browserControl;
InitializeComponent();
InitializeBrowserWindow(browserControl);
@@ -214,12 +214,15 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
if (e.Key == Key.Tab)
{
var hasShift = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
+
if (Toolbar.IsKeyboardFocusWithin && hasShift)
{
var firstActiveElementInToolbar = Toolbar.PredictFocus(FocusNavigationDirection.Right);
- if (firstActiveElementInToolbar is System.Windows.UIElement)
+
+ if (firstActiveElementInToolbar is UIElement)
{
- var control = firstActiveElementInToolbar as System.Windows.UIElement;
+ var control = firstActiveElementInToolbar as UIElement;
+
if (control.IsKeyboardFocusWithin)
{
this.LoseFocusRequested?.Invoke(false);
@@ -257,6 +260,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
{
var hasCtrl = (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control;
var hasShift = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
+
if (BrowserControlHost.IsFocused && hasCtrl)
{
if (Findbar.Visibility == Visibility.Hidden || hasShift)
@@ -273,6 +277,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
var focusedElement = FocusManager.GetFocusedElement(this);
var focusedControl = focusedElement as System.Windows.Controls.Control;
var prevFocusedControl = tabKeyDownFocusElement as System.Windows.Controls.Control;
+
if (focusedControl != null && prevFocusedControl != null)
{
//var commonAncestor = focusedControl.FindCommonVisualAncestor(prevFocusedControl);
@@ -452,14 +457,14 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
var javascript = @"
if (typeof __SEB_focusElement === 'undefined') {
__SEB_focusElement = function (forward) {
- var items = [].map
- .call(document.body.querySelectorAll(['input', 'select', 'a[href]', 'textarea', 'button', '[tabindex]']), function(el, i) { return { el, i } })
- .filter(function(e) { return e.el.tabIndex >= 0 && !e.el.disabled && e.el.offsetParent; })
- .sort(function(a,b) { return a.el.tabIndex === b.el.tabIndex ? a.i - b.i : (a.el.tabIndex || 9E9) - (b.el.tabIndex || 9E9); })
- var item = items[forward ? 1 : items.length - 1];
- if (item && item.focus && typeof item.focus !== 'function')
- throw ('item.focus is not a function, ' + typeof item.focus)
- setTimeout(function () { item && item.focus && item.focus(); }, 20);
+ var items = [].map
+ .call(document.body.querySelectorAll(['input', 'select', 'a[href]', 'textarea', 'button', '[tabindex]']), function(el, i) { return { el, i } })
+ .filter(function(e) { return e.el.tabIndex >= 0 && !e.el.disabled && e.el.offsetParent; })
+ .sort(function(a,b) { return a.el.tabIndex === b.el.tabIndex ? a.i - b.i : (a.el.tabIndex || 9E9) - (b.el.tabIndex || 9E9); })
+ var item = items[forward ? 1 : items.length - 1];
+ if (item && item.focus && typeof item.focus !== 'function')
+ throw ('item.focus is not a function, ' + typeof item.focus)
+ setTimeout(function () { item && item.focus && item.focus(); }, 20);
}
}";
this.browserControl.ExecuteJavascript(javascript, result =>
@@ -593,7 +598,7 @@ if (typeof __SEB_focusElement === 'undefined') {
public void FocusToolbar(bool forward)
{
- this.Dispatcher.BeginInvoke((Action)(async () =>
+ this.Dispatcher.BeginInvoke((Action) (async () =>
{
this.Activate();
await Task.Delay(50);
@@ -613,7 +618,7 @@ if (typeof __SEB_focusElement === 'undefined') {
public void FocusBrowser()
{
- this.Dispatcher.BeginInvoke((Action)(async () =>
+ this.Dispatcher.BeginInvoke((Action) (async () =>
{
this.FocusToolbar(false);
await Task.Delay(100);
@@ -630,7 +635,7 @@ if (typeof __SEB_focusElement === 'undefined') {
public void FocusAddressBar()
{
- this.Dispatcher.BeginInvoke((Action)(async () =>
+ this.Dispatcher.BeginInvoke((Action) (() =>
{
this.UrlTextBox.Focus();
}));
diff --git a/SafeExamBrowser.UserInterface.Desktop/Windows/Taskbar.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Windows/Taskbar.xaml.cs
index f4a184d2..78ad173b 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Windows/Taskbar.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Desktop/Windows/Taskbar.xaml.cs
@@ -20,8 +20,9 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
{
internal partial class Taskbar : Window, ITaskbar
{
+ private readonly ILogger logger;
+
private bool allowClose;
- private ILogger logger;
private bool isQuitButtonFocusedAtKeyDown;
private bool isFirstChildFocusedAtKeyDown;
@@ -82,6 +83,23 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
Dispatcher.Invoke(base.Close);
}
+ public void Focus(bool forward)
+ {
+ Dispatcher.BeginInvoke((Action) (() =>
+ {
+ Activate();
+
+ if (forward)
+ {
+ SetFocusWithin(ApplicationStackPanel.Children[0]);
+ }
+ else
+ {
+ QuitButton.Focus();
+ }
+ }));
+ }
+
public int GetAbsoluteHeight()
{
return Dispatcher.Invoke(() =>
@@ -121,6 +139,13 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
});
}
+ private void InitializeTaskbar()
+ {
+ Closing += Taskbar_Closing;
+ Loaded += (o, args) => InitializeBounds();
+ QuitButton.Clicked += QuitButton_Clicked;
+ }
+
public void InitializeText(IText text)
{
Dispatcher.Invoke(() =>
@@ -131,11 +156,21 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
});
}
+ public void Register(ITaskbarActivator activator)
+ {
+ activator.Activated += Activator_Activated;
+ }
+
public new void Show()
{
Dispatcher.Invoke(base.Show);
}
+ private void Activator_Activated()
+ {
+ (this as ITaskbar).Focus(true);
+ }
+
private void QuitButton_Clicked(CancelEventArgs args)
{
QuitButtonClicked?.Invoke(args);
@@ -160,17 +195,10 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
}
}
- private void InitializeTaskbar()
- {
- Closing += Taskbar_Closing;
- Loaded += (o, args) => InitializeBounds();
- QuitButton.Clicked += QuitButton_Clicked;
- }
-
private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
- this.isQuitButtonFocusedAtKeyDown = this.QuitButton.IsKeyboardFocusWithin;
- this.isFirstChildFocusedAtKeyDown = this.ApplicationStackPanel.Children[0].IsKeyboardFocusWithin;
+ isQuitButtonFocusedAtKeyDown = QuitButton.IsKeyboardFocusWithin;
+ isFirstChildFocusedAtKeyDown = ApplicationStackPanel.Children[0].IsKeyboardFocusWithin;
}
private void Window_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
@@ -178,36 +206,20 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
if (e.Key == System.Windows.Input.Key.Tab)
{
var shift = System.Windows.Input.Keyboard.IsKeyDown(System.Windows.Input.Key.LeftShift);
- if (!shift && this.ApplicationStackPanel.Children[0].IsKeyboardFocusWithin && this.isQuitButtonFocusedAtKeyDown)
+ if (!shift && ApplicationStackPanel.Children[0].IsKeyboardFocusWithin && isQuitButtonFocusedAtKeyDown)
{
- this.LoseFocusRequested?.Invoke(true);
+ LoseFocusRequested?.Invoke(true);
e.Handled = true;
}
- else if (shift && this.QuitButton.IsKeyboardFocusWithin && this.isFirstChildFocusedAtKeyDown)
+ else if (shift && QuitButton.IsKeyboardFocusWithin && isFirstChildFocusedAtKeyDown)
{
- this.LoseFocusRequested?.Invoke(false);
+ LoseFocusRequested?.Invoke(false);
e.Handled = true;
}
}
- this.isQuitButtonFocusedAtKeyDown = false;
- this.isFirstChildFocusedAtKeyDown = false;
- }
-
- void ITaskbar.Focus(bool forward)
- {
- this.Dispatcher.BeginInvoke((Action)(() =>
- {
- base.Activate();
- if (forward)
- {
- this.SetFocusWithin(this.ApplicationStackPanel.Children[0]);
- }
- else
- {
- this.QuitButton.Focus();
- }
- }));
+ isQuitButtonFocusedAtKeyDown = false;
+ isFirstChildFocusedAtKeyDown = false;
}
private bool SetFocusWithin(UIElement uIElement)
@@ -215,15 +227,17 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
if (uIElement.Focusable)
{
uIElement.Focus();
+
return true;
}
if (uIElement is System.Windows.Controls.Panel)
{
var panel = uIElement as System.Windows.Controls.Panel;
+
for (var i = 0; i < panel.Children.Count; i++)
{
- if (this.SetFocusWithin(panel.Children[i]))
+ if (SetFocusWithin(panel.Children[i]))
{
return true;
}
@@ -235,9 +249,10 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
{
var control = uIElement as System.Windows.Controls.ContentControl;
var content = control.Content as UIElement;
+
if (content != null)
{
- return this.SetFocusWithin(content);
+ return SetFocusWithin(content);
}
}
diff --git a/SafeExamBrowser.UserInterface.Mobile/Windows/BrowserWindow.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Windows/BrowserWindow.xaml.cs
index 8110d4d9..5569fb06 100644
--- a/SafeExamBrowser.UserInterface.Mobile/Windows/BrowserWindow.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Mobile/Windows/BrowserWindow.xaml.cs
@@ -33,12 +33,12 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
internal partial class BrowserWindow : Window, IBrowserWindow
{
private readonly bool isMainWindow;
+ private readonly ILogger logger;
private readonly BrowserSettings settings;
private readonly IText text;
private WindowClosedEventHandler closed;
private WindowClosingEventHandler closing;
- private ILogger logger;
private WindowSettings WindowSettings
{
@@ -54,7 +54,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
public event ActionRequestedEventHandler DeveloperConsoleRequested;
public event FindRequestedEventHandler FindRequested;
public event ActionRequestedEventHandler ForwardNavigationRequested;
- public event LoseFocusRequestedEventHandler LoseFocusRequested;
+ public event LoseFocusRequestedEventHandler LoseFocusRequested { add { } remove { } }
public event ActionRequestedEventHandler HomeNavigationRequested;
public event ActionRequestedEventHandler ReloadRequested;
public event ActionRequestedEventHandler ZoomInRequested;
@@ -76,9 +76,9 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
internal BrowserWindow(IBrowserControl browserControl, BrowserSettings settings, bool isMainWindow, IText text, ILogger logger)
{
this.isMainWindow = isMainWindow;
+ this.logger = logger;
this.settings = settings;
this.text = text;
- this.logger = logger;
InitializeComponent();
InitializeBrowserWindow(browserControl);
diff --git a/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml.cs
index 9ddfee07..beeaff73 100644
--- a/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml.cs
@@ -19,8 +19,8 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
{
internal partial class Taskbar : Window, ITaskbar
{
+ private readonly ILogger logger;
private bool allowClose;
- private ILogger logger;
public bool ShowClock
{
@@ -32,7 +32,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
set { Dispatcher.Invoke(() => QuitButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed); }
}
- public event LoseFocusRequestedEventHandler LoseFocusRequested;
+ public event LoseFocusRequestedEventHandler LoseFocusRequested { add { } remove { } }
public event QuitButtonClickedEventHandler QuitButtonClicked;
internal Taskbar(ILogger logger)
@@ -79,6 +79,20 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
Dispatcher.Invoke(base.Close);
}
+ public void Focus(bool fromTop)
+ {
+ Activate();
+
+ if (fromTop)
+ {
+ ApplicationStackPanel.Children[0].Focus();
+ }
+ else
+ {
+ QuitButton.Focus();
+ }
+ }
+
public int GetAbsoluteHeight()
{
return Dispatcher.Invoke(() =>
@@ -118,6 +132,13 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
});
}
+ private void InitializeTaskbar()
+ {
+ Closing += Taskbar_Closing;
+ Loaded += (o, args) => InitializeBounds();
+ QuitButton.Clicked += QuitButton_Clicked;
+ }
+
public void InitializeText(IText text)
{
Dispatcher.Invoke(() =>
@@ -126,11 +147,21 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
});
}
+ public void Register(ITaskbarActivator activator)
+ {
+ activator.Activated += Activator_Activated;
+ }
+
public new void Show()
{
Dispatcher.Invoke(base.Show);
}
+ private void Activator_Activated()
+ {
+ (this as ITaskbar).Focus(true);
+ }
+
private void QuitButton_Clicked(CancelEventArgs args)
{
QuitButtonClicked?.Invoke(args);
@@ -154,25 +185,5 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
e.Cancel = true;
}
}
-
- private void InitializeTaskbar()
- {
- Closing += Taskbar_Closing;
- Loaded += (o, args) => InitializeBounds();
- QuitButton.Clicked += QuitButton_Clicked;
- }
-
- void ITaskbar.Focus(bool fromTop)
- {
- base.Activate();
- if (fromTop)
- {
- this.ApplicationStackPanel.Children[0].Focus();
- }
- else
- {
- this.QuitButton.Focus();
- }
- }
}
}
diff --git a/SafeExamBrowser.UserInterface.Shared/Activators/TaskbarKeyboardActivator.cs b/SafeExamBrowser.UserInterface.Shared/Activators/TaskbarKeyboardActivator.cs
new file mode 100644
index 00000000..3bf8e743
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Shared/Activators/TaskbarKeyboardActivator.cs
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2022 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+using System.Windows.Input;
+using SafeExamBrowser.Logging.Contracts;
+using SafeExamBrowser.UserInterface.Contracts.Shell;
+using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
+using SafeExamBrowser.WindowsApi.Contracts;
+using SafeExamBrowser.WindowsApi.Contracts.Events;
+
+namespace SafeExamBrowser.UserInterface.Shared.Activators
+{
+ public class TaskbarKeyboardActivator : KeyboardActivator, ITaskbarActivator
+ {
+ private readonly ILogger logger;
+ private bool leftWindows;
+
+ public event ActivatorEventHandler Activated;
+
+ public TaskbarKeyboardActivator(ILogger logger, INativeMethods nativeMethods) : base(nativeMethods)
+ {
+ this.logger = logger;
+ }
+
+ protected override bool Process(Key key, KeyModifier modifier, KeyState state)
+ {
+ var changed = false;
+ var pressed = state == KeyState.Pressed;
+
+ if (key == Key.LWin)
+ {
+ changed = leftWindows != pressed;
+ leftWindows = pressed;
+ }
+
+ if (leftWindows && changed)
+ {
+ logger.Debug("Detected activation sequence for taskbar.");
+ Activated?.Invoke();
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj b/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj
index 3ea7e132..064fbe30 100644
--- a/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj
+++ b/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj
@@ -66,6 +66,7 @@
+