From a93678d5d703c667926cf3498fedb30970b10716 Mon Sep 17 00:00:00 2001 From: dbuechel Date: Thu, 5 Dec 2019 11:54:43 +0100 Subject: [PATCH] SEBWIN-312: Implemented thumbnails of open windows for desktop taskview. --- .../IApplicationWindow.cs | 6 + .../ExternalApplicationWindow.cs | 2 +- .../BrowserApplicationInstance.cs | 3 + SafeExamBrowser.Client/CompositionRoot.cs | 10 +- .../Operations/BrowserOperation.cs | 8 +- .../Operations/ShellOperation.cs | 10 +- .../Browser/IBrowserWindow.cs | 6 + .../BrowserWindow.xaml.cs | 4 + .../Controls/TaskViewWindowControl.xaml | 21 ++- .../Controls/TaskViewWindowControl.xaml.cs | 132 +++++++++++++++--- ...feExamBrowser.UserInterface.Desktop.csproj | 12 +- .../TaskView.xaml | 4 +- .../TaskView.xaml.cs | 90 ++++++++---- .../BrowserWindow.xaml.cs | 4 + ...afeExamBrowser.UserInterface.Shared.csproj | 1 + .../Utilities/Thumbnail.cs | 63 +++++++++ SafeExamBrowser.WindowsApi/ProcessFactory.cs | 46 +++--- 17 files changed, 329 insertions(+), 93 deletions(-) create mode 100644 SafeExamBrowser.UserInterface.Shared/Utilities/Thumbnail.cs diff --git a/SafeExamBrowser.Applications.Contracts/IApplicationWindow.cs b/SafeExamBrowser.Applications.Contracts/IApplicationWindow.cs index 5ac1deb7..818c7ad6 100644 --- a/SafeExamBrowser.Applications.Contracts/IApplicationWindow.cs +++ b/SafeExamBrowser.Applications.Contracts/IApplicationWindow.cs @@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; using SafeExamBrowser.Applications.Contracts.Events; using SafeExamBrowser.Applications.Contracts.Resources.Icons; @@ -16,6 +17,11 @@ namespace SafeExamBrowser.Applications.Contracts /// public interface IApplicationWindow { + /// + /// The native handle of the window. + /// + IntPtr Handle { get; } + /// /// The icon of the window. /// diff --git a/SafeExamBrowser.Applications/ExternalApplicationWindow.cs b/SafeExamBrowser.Applications/ExternalApplicationWindow.cs index 13489c98..6d6c5456 100644 --- a/SafeExamBrowser.Applications/ExternalApplicationWindow.cs +++ b/SafeExamBrowser.Applications/ExternalApplicationWindow.cs @@ -18,7 +18,7 @@ namespace SafeExamBrowser.Applications { private INativeMethods nativeMethods; - internal IntPtr Handle { get; } + public IntPtr Handle { get; } public IconResource Icon { get; private set; } public string Title { get; private set; } diff --git a/SafeExamBrowser.Browser/BrowserApplicationInstance.cs b/SafeExamBrowser.Browser/BrowserApplicationInstance.cs index 64aa7b90..0e7fdee5 100644 --- a/SafeExamBrowser.Browser/BrowserApplicationInstance.cs +++ b/SafeExamBrowser.Browser/BrowserApplicationInstance.cs @@ -51,6 +51,7 @@ namespace SafeExamBrowser.Browser internal int Id { get; } + public IntPtr Handle { get; private set; } public IconResource Icon { get; private set; } public string Title { get; private set; } @@ -178,6 +179,8 @@ namespace SafeExamBrowser.Browser window.UpdateZoomLevel(CalculateZoomPercentage()); window.Show(); + Handle = window.Handle; + logger.Debug("Initialized browser window."); } diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index 52bc5453..51056978 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -71,7 +71,7 @@ namespace SafeExamBrowser.Client private IRuntimeProxy runtimeProxy; private ISystemInfo systemInfo; private ITaskbar taskbar; - private ITaskView taskView; + private ITaskView taskview; private IText text; private ITextResource textResource; private IUserInterfaceFactory uiFactory; @@ -95,7 +95,7 @@ namespace SafeExamBrowser.Client uiFactory = BuildUserInterfaceFactory(); runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), ModuleLogger(nameof(RuntimeProxy)), Interlocutor.Client); taskbar = BuildTaskbar(); - taskView = BuildTaskView(); + taskview = BuildTaskView(); var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory))); var applicationMonitor = new ApplicationMonitor(TWO_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, processFactory); @@ -199,7 +199,7 @@ namespace SafeExamBrowser.Client { var moduleLogger = ModuleLogger(nameof(BrowserApplication)); var browser = new BrowserApplication(context.AppConfig, context.Settings.Browser, messageBox, moduleLogger, text, uiFactory); - var operation = new BrowserOperation(actionCenter, context, logger, taskbar, taskView, uiFactory); + var operation = new BrowserOperation(actionCenter, context, logger, taskbar, taskview, uiFactory); context.Browser = browser; @@ -258,7 +258,7 @@ namespace SafeExamBrowser.Client powerSupply, systemInfo, taskbar, - taskView, + taskview, text, uiFactory, wirelessAdapter); @@ -311,7 +311,7 @@ namespace SafeExamBrowser.Client case UserInterfaceMode.Mobile: return new Mobile.TaskView(); default: - return new Desktop.TaskView(); + return new Desktop.Taskview(); } } diff --git a/SafeExamBrowser.Client/Operations/BrowserOperation.cs b/SafeExamBrowser.Client/Operations/BrowserOperation.cs index eeda4df5..f62e85ff 100644 --- a/SafeExamBrowser.Client/Operations/BrowserOperation.cs +++ b/SafeExamBrowser.Client/Operations/BrowserOperation.cs @@ -20,7 +20,7 @@ namespace SafeExamBrowser.Client.Operations private IActionCenter actionCenter; private ILogger logger; private ITaskbar taskbar; - private ITaskView taskView; + private ITaskView taskview; private IUserInterfaceFactory uiFactory; public override event ActionRequiredEventHandler ActionRequired { add { } remove { } } @@ -31,13 +31,13 @@ namespace SafeExamBrowser.Client.Operations ClientContext context, ILogger logger, ITaskbar taskbar, - ITaskView taskView, + ITaskView taskview, IUserInterfaceFactory uiFactory) : base(context) { this.actionCenter = actionCenter; this.logger = logger; this.taskbar = taskbar; - this.taskView = taskView; + this.taskview = taskview; this.uiFactory = uiFactory; } @@ -58,7 +58,7 @@ namespace SafeExamBrowser.Client.Operations taskbar.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.Taskbar), true); } - taskView.Add(Context.Browser); + taskview.Add(Context.Browser); return OperationResult.Success; } diff --git a/SafeExamBrowser.Client/Operations/ShellOperation.cs b/SafeExamBrowser.Client/Operations/ShellOperation.cs index 7ba8cae1..4577ef45 100644 --- a/SafeExamBrowser.Client/Operations/ShellOperation.cs +++ b/SafeExamBrowser.Client/Operations/ShellOperation.cs @@ -35,7 +35,7 @@ namespace SafeExamBrowser.Client.Operations private IPowerSupply powerSupply; private ISystemInfo systemInfo; private ITaskbar taskbar; - private ITaskView taskView; + private ITaskView taskview; private IText text; private IUserInterfaceFactory uiFactory; private IWirelessAdapter wirelessAdapter; @@ -56,7 +56,7 @@ namespace SafeExamBrowser.Client.Operations IPowerSupply powerSupply, ISystemInfo systemInfo, ITaskbar taskbar, - ITaskView taskView, + ITaskView taskview, IText text, IUserInterfaceFactory uiFactory, IWirelessAdapter wirelessAdapter) : base(context) @@ -73,7 +73,7 @@ namespace SafeExamBrowser.Client.Operations this.systemInfo = systemInfo; this.text = text; this.taskbar = taskbar; - this.taskView = taskView; + this.taskview = taskview; this.uiFactory = uiFactory; this.wirelessAdapter = wirelessAdapter; } @@ -116,7 +116,7 @@ namespace SafeExamBrowser.Client.Operations if (Context.Settings.Keyboard.AllowAltTab && activator is ITaskViewActivator taskViewActivator) { - taskView.Register(taskViewActivator); + taskview.Register(taskViewActivator); taskViewActivator.Start(); } @@ -177,7 +177,7 @@ namespace SafeExamBrowser.Client.Operations foreach (var application in Context.Applications) { - taskView.Add(application); + taskview.Add(application); } } diff --git a/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserWindow.cs b/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserWindow.cs index 858ab9f0..d51bc5c2 100644 --- a/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserWindow.cs +++ b/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserWindow.cs @@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; using SafeExamBrowser.Applications.Contracts.Resources.Icons; using SafeExamBrowser.UserInterface.Contracts.Browser.Events; using SafeExamBrowser.UserInterface.Contracts.Windows; @@ -26,6 +27,11 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser /// Enables the forward navigation button. /// bool CanNavigateForwards { set; } + + /// + /// The native handle of the window. + /// + IntPtr Handle { get; } /// /// Event fired when the user changed the URL. diff --git a/SafeExamBrowser.UserInterface.Desktop/BrowserWindow.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/BrowserWindow.xaml.cs index 537a9e6e..210f12f0 100644 --- a/SafeExamBrowser.UserInterface.Desktop/BrowserWindow.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/BrowserWindow.xaml.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Controls.Primitives; using System.Windows.Input; +using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; using SafeExamBrowser.Applications.Contracts.Resources.Icons; @@ -40,6 +41,7 @@ namespace SafeExamBrowser.UserInterface.Desktop public bool CanNavigateBackwards { set => Dispatcher.Invoke(() => BackwardButton.IsEnabled = value); } public bool CanNavigateForwards { set => Dispatcher.Invoke(() => ForwardButton.IsEnabled = value); } + public IntPtr Handle { get; private set; } public event AddressChangedEventHandler AddressChanged; public event ActionRequestedEventHandler BackwardNavigationRequested; @@ -157,6 +159,8 @@ namespace SafeExamBrowser.UserInterface.Desktop private void BrowserWindow_Loaded(object sender, RoutedEventArgs e) { + Handle = new WindowInteropHelper(this).Handle; + if (isMainWindow) { WindowUtility.DisableCloseButtonFor(this); diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskViewWindowControl.xaml b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskViewWindowControl.xaml index 1c5353f3..4a71b36c 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskViewWindowControl.xaml +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskViewWindowControl.xaml @@ -1,4 +1,4 @@ - - + - - - - + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskViewWindowControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskViewWindowControl.xaml.cs index 34ad9fee..d99409ea 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskViewWindowControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskViewWindowControl.xaml.cs @@ -6,21 +6,26 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; using System.Windows; using System.Windows.Controls; +using System.Windows.Threading; using SafeExamBrowser.Applications.Contracts; using SafeExamBrowser.Applications.Contracts.Resources.Icons; using SafeExamBrowser.UserInterface.Shared.Utilities; namespace SafeExamBrowser.UserInterface.Desktop.Controls { - public partial class TaskViewWindowControl : UserControl + public partial class TaskviewWindowControl : UserControl { + private Taskview taskview; + private IntPtr thumbnail; private IApplicationWindow window; - public TaskViewWindowControl(IApplicationWindow window) + public TaskviewWindowControl(IApplicationWindow window, Taskview taskview) { this.window = window; + this.taskview = taskview; InitializeComponent(); InitializeControl(); @@ -33,35 +38,128 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls internal void Deselect() { - Icon.MaxWidth = 40; Indicator.Visibility = Visibility.Hidden; Title.FontWeight = FontWeights.Normal; } + internal void Destroy() + { + if (thumbnail != IntPtr.Zero) + { + Thumbnail.DwmUnregisterThumbnail(thumbnail); + } + } + internal void Select() { - Icon.MaxWidth = 50; Indicator.Visibility = Visibility.Visible; Title.FontWeight = FontWeights.SemiBold; } + internal void Update() + { + if (!IsLoaded || !IsVisible) + { + return; + } + + if (thumbnail == IntPtr.Zero && taskview.Handle != IntPtr.Zero && window.Handle != IntPtr.Zero) + { + Thumbnail.DwmRegisterThumbnail(taskview.Handle, window.Handle, out thumbnail); + } + + if (thumbnail != IntPtr.Zero) + { + Thumbnail.DwmQueryThumbnailSourceSize(thumbnail, out var size); + + var destination = CalculatePhysicalDestination(size); + var properties = new Thumbnail.Properties + { + Destination = destination, + Flags = Thumbnail.DWM_TNP_RECTDESTINATION | Thumbnail.DWM_TNP_VISIBLE, + Visible = true + }; + + Thumbnail.DwmUpdateThumbnailProperties(thumbnail, ref properties); + } + } + + private void TaskViewWindowControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if (e.NewValue as bool? == true) + { + Update(); + } + } + + private void Window_TitleChanged(string title) + { + Dispatcher.InvokeAsync(() => Title.Text = title); + } + + private void Window_IconChanged(IconResource icon) + { + Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(window.Icon)); + } + + private Thumbnail.Rectangle CalculatePhysicalDestination(Thumbnail.Size size) + { + var controlToTaskview = TransformToVisual(taskview); + var placeholderToControl = Placeholder.TransformToVisual(this); + var placeholderLeft = placeholderToControl.Transform(new Point(0, 0)).X; + var placeholderTop = placeholderToControl.Transform(new Point(0, 0)).Y; + var placeholderRight = placeholderToControl.Transform(new Point(Placeholder.ActualWidth, 0)).X; + var placeholderBottom = placeholderToControl.Transform(new Point(0, Placeholder.ActualHeight)).Y; + + var physicalBounds = new Thumbnail.Rectangle + { + Left = (int) Math.Round(this.TransformToPhysical(controlToTaskview.Transform(new Point(placeholderLeft, 0)).X, 0).X), + Top = (int) Math.Round(this.TransformToPhysical(0, controlToTaskview.Transform(new Point(0, placeholderTop)).Y).Y), + Right = (int) Math.Round(this.TransformToPhysical(controlToTaskview.Transform(new Point(placeholderRight, 0)).X, 0).X), + Bottom = (int) Math.Round(this.TransformToPhysical(0, controlToTaskview.Transform(new Point(0, placeholderBottom)).Y).Y) + }; + + var scaleFactor = default(double); + var thumbnailHeight = default(double); + var thumbnailWidth = default(double); + var maxWidth = (double) physicalBounds.Right - physicalBounds.Left; + var maxHeight = (double) physicalBounds.Bottom - physicalBounds.Top; + var placeholderRatio = maxWidth / maxHeight; + var windowRatio = (double) size.X / size.Y; + + if (windowRatio < placeholderRatio) + { + thumbnailHeight = maxHeight; + scaleFactor = thumbnailHeight / size.Y; + thumbnailWidth = size.X * scaleFactor; + } + else + { + thumbnailWidth = maxWidth; + scaleFactor = thumbnailWidth / size.X; + thumbnailHeight = size.Y * scaleFactor; + } + + var widthDifference = maxWidth - thumbnailWidth; + var heightDifference = maxHeight - thumbnailHeight; + + return new Thumbnail.Rectangle + { + Left = (int) Math.Round(physicalBounds.Left + (widthDifference / 2)), + Top = (int) Math.Round(physicalBounds.Top + (heightDifference / 2)), + Right = (int) Math.Round(physicalBounds.Right - (widthDifference / 2)), + Bottom = (int) Math.Round(physicalBounds.Bottom - (heightDifference / 2)) + }; + } + private void InitializeControl() { Icon.Content = IconResourceLoader.Load(window.Icon); + IsVisibleChanged += TaskViewWindowControl_IsVisibleChanged; + Loaded += (o, args) => Update(); Title.Text = window.Title; - - window.IconChanged += Instance_IconChanged; - window.TitleChanged += Instance_TitleChanged; - } - - private void Instance_TitleChanged(string title) - { - Dispatcher.InvokeAsync(() => Title.Text = title); - } - - private void Instance_IconChanged(IconResource icon) - { - Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(window.Icon)); + window.IconChanged += Window_IconChanged; + window.TitleChanged += Window_TitleChanged; } } } diff --git a/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj b/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj index f5b2bb3d..d86976d3 100644 --- a/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj +++ b/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj @@ -142,8 +142,8 @@ TaskbarWirelessNetworkControl.xaml - - TaskViewWindowControl.xaml + + TaskviewWindowControl.xaml LockScreen.xaml @@ -161,8 +161,8 @@ SplashScreen.xaml - - TaskView.xaml + + Taskview.xaml @@ -317,7 +317,7 @@ MSBuild:Compile Designer - + Designer MSBuild:Compile @@ -325,7 +325,7 @@ Designer MSBuild:Compile - + Designer MSBuild:Compile diff --git a/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml b/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml index 3103e6c8..1a61fe1f 100644 --- a/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml +++ b/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml @@ -1,11 +1,11 @@ - + Title="Taskview" Topmost="True" Height="450" SizeToContent="WidthAndHeight" Width="800" WindowStartupLocation="CenterScreen" WindowStyle="None"> diff --git a/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs index 35479681..1bea115c 100644 --- a/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs @@ -11,24 +11,28 @@ using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; +using System.Windows.Interop; using SafeExamBrowser.Applications.Contracts; using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Desktop.Controls; namespace SafeExamBrowser.UserInterface.Desktop { - public partial class TaskView : Window, ITaskView + public partial class Taskview : Window, ITaskView { private IList applications; - private LinkedListNode current; - private LinkedList controls; + private LinkedListNode current; + private LinkedList controls; - public TaskView() + internal IntPtr Handle { get; private set; } + + public Taskview() { applications = new List(); - controls = new LinkedList(); + controls = new LinkedList(); InitializeComponent(); + InitializeTaskview(); } public void Add(IApplication application) @@ -66,9 +70,21 @@ namespace SafeExamBrowser.UserInterface.Desktop private void ActivateAndHide() { - Activate(); - current?.Value.Activate(); - Hide(); + if (IsVisible) + { + Activate(); + current?.Value.Activate(); + Hide(); + } + } + + private void InitializeTaskview() + { + Loaded += (o, args) => + { + Handle = new WindowInteropHelper(this).Handle; + Update(); + }; } private void SelectNext() @@ -106,31 +122,37 @@ namespace SafeExamBrowser.UserInterface.Desktop private void Update() { - var windows = new Stack(); + ClearTaskview(); + LoadControls(); + UpdateLocation(); + } - foreach (var application in applications) + private void ClearTaskview() + { + foreach (var control in controls) { - foreach (var window in application.GetWindows()) - { - windows.Push(window); - } + control.Destroy(); } - var max = Math.Ceiling(Math.Sqrt(windows.Count)); - controls.Clear(); Rows.Children.Clear(); + } - for (var rowCount = 0; rowCount < max && windows.Any(); rowCount++) + private void LoadControls() + { + var windows = GetAllWindows(); + var maxColumns = Math.Ceiling(Math.Sqrt(windows.Count)); + + while (windows.Any()) { var row = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center }; Rows.Children.Add(row); - for (var columnIndex = 0; columnIndex < max && windows.Any(); columnIndex++) + for (var column = 0; column < maxColumns && windows.Any(); column++) { var window = windows.Pop(); - var control = new TaskViewWindowControl(window); + var control = new TaskviewWindowControl(window, this); controls.AddLast(control); row.Children.Add(control); @@ -139,16 +161,36 @@ namespace SafeExamBrowser.UserInterface.Desktop current = controls.First; current?.Value.Select(); + } - UpdateLayout(); + private void UpdateLocation() + { + if (controls.Any()) + { + UpdateLayout(); - Left = (SystemParameters.WorkArea.Width - Width) / 2 + SystemParameters.WorkArea.Left; - Top = (SystemParameters.WorkArea.Height - Height) / 2 + SystemParameters.WorkArea.Top; - - if (!controls.Any()) + Left = (SystemParameters.WorkArea.Width - Width) / 2 + SystemParameters.WorkArea.Left; + Top = (SystemParameters.WorkArea.Height - Height) / 2 + SystemParameters.WorkArea.Top; + } + else { Hide(); } } + + private Stack GetAllWindows() + { + var stack = new Stack(); + + foreach (var application in applications) + { + foreach (var window in application.GetWindows()) + { + stack.Push(window); + } + } + + return stack; + } } } diff --git a/SafeExamBrowser.UserInterface.Mobile/BrowserWindow.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/BrowserWindow.xaml.cs index f8c282ed..90c3220c 100644 --- a/SafeExamBrowser.UserInterface.Mobile/BrowserWindow.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/BrowserWindow.xaml.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Controls.Primitives; using System.Windows.Input; +using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; using SafeExamBrowser.Applications.Contracts.Resources.Icons; @@ -40,6 +41,7 @@ namespace SafeExamBrowser.UserInterface.Mobile public bool CanNavigateBackwards { set => Dispatcher.Invoke(() => BackwardButton.IsEnabled = value); } public bool CanNavigateForwards { set => Dispatcher.Invoke(() => ForwardButton.IsEnabled = value); } + public IntPtr Handle { get; private set; } public event AddressChangedEventHandler AddressChanged; public event ActionRequestedEventHandler BackwardNavigationRequested; @@ -157,6 +159,8 @@ namespace SafeExamBrowser.UserInterface.Mobile private void BrowserWindow_Loaded(object sender, RoutedEventArgs e) { + Handle = new WindowInteropHelper(this).Handle; + if (isMainWindow) { WindowUtility.DisableCloseButtonFor(this); diff --git a/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj b/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj index 92e0291e..4f3471b5 100644 --- a/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj +++ b/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj @@ -73,6 +73,7 @@ Code + diff --git a/SafeExamBrowser.UserInterface.Shared/Utilities/Thumbnail.cs b/SafeExamBrowser.UserInterface.Shared/Utilities/Thumbnail.cs new file mode 100644 index 00000000..ac10f139 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Shared/Utilities/Thumbnail.cs @@ -0,0 +1,63 @@ +/* + * 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 System; +using System.Runtime.InteropServices; + +namespace SafeExamBrowser.UserInterface.Shared.Utilities +{ + /// + /// See https://docs.microsoft.com/en-us/windows/win32/dwm/thumbnail-ovw. + /// + public static class Thumbnail + { + public const int DWM_TNP_VISIBLE = 0x8; + public const int DWM_TNP_OPACITY = 0x4; + public const int DWM_TNP_RECTDESTINATION = 0x1; + public const int S_OK = 0; + + [DllImport("dwmapi.dll")] + public static extern int DwmQueryThumbnailSourceSize(IntPtr thumbnail, out Size size); + + [DllImport("dwmapi.dll")] + public static extern int DwmRegisterThumbnail(IntPtr destinationWindow, IntPtr sourceWindow, out IntPtr thumbnail); + + [DllImport("dwmapi.dll")] + public static extern int DwmUnregisterThumbnail(IntPtr thumbnail); + + [DllImport("dwmapi.dll")] + public static extern int DwmUpdateThumbnailProperties(IntPtr thumbnail, ref Properties properties); + + [StructLayout(LayoutKind.Sequential)] + public struct Properties + { + public int Flags; + public Rectangle Destination; + public Rectangle Source; + public byte Opacity; + public bool Visible; + public bool SourceClientAreaOnly; + } + + [StructLayout(LayoutKind.Sequential)] + public struct Size + { + public int X; + public int Y; + } + + [StructLayout(LayoutKind.Sequential)] + public struct Rectangle + { + public int Left; + public int Top; + public int Right; + public int Bottom; + } + } +} diff --git a/SafeExamBrowser.WindowsApi/ProcessFactory.cs b/SafeExamBrowser.WindowsApi/ProcessFactory.cs index 238bbc10..4a7cd6fc 100644 --- a/SafeExamBrowser.WindowsApi/ProcessFactory.cs +++ b/SafeExamBrowser.WindowsApi/ProcessFactory.cs @@ -72,29 +72,6 @@ namespace SafeExamBrowser.WindowsApi return process; } - private System.Diagnostics.Process StartOnDesktop(string path, params string[] args) - { - var commandLine = $"{'"' + path + '"'} {string.Join(" ", args)}"; - var processInfo = new PROCESS_INFORMATION(); - var startupInfo = new STARTUPINFO(); - - startupInfo.cb = Marshal.SizeOf(startupInfo); - startupInfo.lpDesktop = StartupDesktop?.Name; - - var success = Kernel32.CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, true, Constant.NORMAL_PRIORITY_CLASS, IntPtr.Zero, null, ref startupInfo, ref processInfo); - - if (success) - { - return System.Diagnostics.Process.GetProcessById(processInfo.dwProcessId); - } - - var errorCode = Marshal.GetLastWin32Error(); - - logger.Error($"Failed to start process '{path}' on desktop '{StartupDesktop}'! Error code: {errorCode}."); - - throw new Win32Exception(errorCode); - } - public bool TryGetById(int id, out IProcess process) { var raw = System.Diagnostics.Process.GetProcesses().FirstOrDefault(p => p.Id == id); @@ -183,5 +160,28 @@ namespace SafeExamBrowser.WindowsApi { return logger.CloneFor($"{nameof(Process)} '{name}' ({process.Id})"); } + + private System.Diagnostics.Process StartOnDesktop(string path, params string[] args) + { + var commandLine = $"{'"' + path + '"'} {string.Join(" ", args)}"; + var processInfo = new PROCESS_INFORMATION(); + var startupInfo = new STARTUPINFO(); + + startupInfo.cb = Marshal.SizeOf(startupInfo); + startupInfo.lpDesktop = StartupDesktop?.Name; + + var success = Kernel32.CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, true, Constant.NORMAL_PRIORITY_CLASS, IntPtr.Zero, null, ref startupInfo, ref processInfo); + + if (success) + { + return System.Diagnostics.Process.GetProcessById(processInfo.dwProcessId); + } + + var errorCode = Marshal.GetLastWin32Error(); + + logger.Error($"Failed to start process '{path}' on desktop '{StartupDesktop}'! Error code: {errorCode}."); + + throw new Win32Exception(errorCode); + } } }