SEBWIN-312: Implemented thumbnails of open windows for desktop taskview.

This commit is contained in:
dbuechel 2019-12-05 11:54:43 +01:00
parent 018e596905
commit a93678d5d7
17 changed files with 329 additions and 93 deletions

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 System;
using SafeExamBrowser.Applications.Contracts.Events; using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Applications.Contracts.Resources.Icons; using SafeExamBrowser.Applications.Contracts.Resources.Icons;
@ -16,6 +17,11 @@ namespace SafeExamBrowser.Applications.Contracts
/// </summary> /// </summary>
public interface IApplicationWindow public interface IApplicationWindow
{ {
/// <summary>
/// The native handle of the window.
/// </summary>
IntPtr Handle { get; }
/// <summary> /// <summary>
/// The icon of the window. /// The icon of the window.
/// </summary> /// </summary>

View file

@ -18,7 +18,7 @@ namespace SafeExamBrowser.Applications
{ {
private INativeMethods nativeMethods; private INativeMethods nativeMethods;
internal IntPtr Handle { get; } public IntPtr Handle { get; }
public IconResource Icon { get; private set; } public IconResource Icon { get; private set; }
public string Title { get; private set; } public string Title { get; private set; }

View file

@ -51,6 +51,7 @@ namespace SafeExamBrowser.Browser
internal int Id { get; } internal int Id { get; }
public IntPtr Handle { get; private set; }
public IconResource Icon { get; private set; } public IconResource Icon { get; private set; }
public string Title { get; private set; } public string Title { get; private set; }
@ -178,6 +179,8 @@ namespace SafeExamBrowser.Browser
window.UpdateZoomLevel(CalculateZoomPercentage()); window.UpdateZoomLevel(CalculateZoomPercentage());
window.Show(); window.Show();
Handle = window.Handle;
logger.Debug("Initialized browser window."); logger.Debug("Initialized browser window.");
} }

View file

@ -71,7 +71,7 @@ namespace SafeExamBrowser.Client
private IRuntimeProxy runtimeProxy; private IRuntimeProxy runtimeProxy;
private ISystemInfo systemInfo; private ISystemInfo systemInfo;
private ITaskbar taskbar; private ITaskbar taskbar;
private ITaskView taskView; private ITaskView taskview;
private IText text; private IText text;
private ITextResource textResource; private ITextResource textResource;
private IUserInterfaceFactory uiFactory; private IUserInterfaceFactory uiFactory;
@ -95,7 +95,7 @@ namespace SafeExamBrowser.Client
uiFactory = BuildUserInterfaceFactory(); uiFactory = BuildUserInterfaceFactory();
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), ModuleLogger(nameof(RuntimeProxy)), Interlocutor.Client); runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), ModuleLogger(nameof(RuntimeProxy)), Interlocutor.Client);
taskbar = BuildTaskbar(); taskbar = BuildTaskbar();
taskView = BuildTaskView(); taskview = BuildTaskView();
var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory))); var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
var applicationMonitor = new ApplicationMonitor(TWO_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, 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 moduleLogger = ModuleLogger(nameof(BrowserApplication));
var browser = new BrowserApplication(context.AppConfig, context.Settings.Browser, messageBox, moduleLogger, text, uiFactory); 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; context.Browser = browser;
@ -258,7 +258,7 @@ namespace SafeExamBrowser.Client
powerSupply, powerSupply,
systemInfo, systemInfo,
taskbar, taskbar,
taskView, taskview,
text, text,
uiFactory, uiFactory,
wirelessAdapter); wirelessAdapter);
@ -311,7 +311,7 @@ namespace SafeExamBrowser.Client
case UserInterfaceMode.Mobile: case UserInterfaceMode.Mobile:
return new Mobile.TaskView(); return new Mobile.TaskView();
default: default:
return new Desktop.TaskView(); return new Desktop.Taskview();
} }
} }

View file

@ -20,7 +20,7 @@ namespace SafeExamBrowser.Client.Operations
private IActionCenter actionCenter; private IActionCenter actionCenter;
private ILogger logger; private ILogger logger;
private ITaskbar taskbar; private ITaskbar taskbar;
private ITaskView taskView; private ITaskView taskview;
private IUserInterfaceFactory uiFactory; private IUserInterfaceFactory uiFactory;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } } public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
@ -31,13 +31,13 @@ namespace SafeExamBrowser.Client.Operations
ClientContext context, ClientContext context,
ILogger logger, ILogger logger,
ITaskbar taskbar, ITaskbar taskbar,
ITaskView taskView, ITaskView taskview,
IUserInterfaceFactory uiFactory) : base(context) IUserInterfaceFactory uiFactory) : base(context)
{ {
this.actionCenter = actionCenter; this.actionCenter = actionCenter;
this.logger = logger; this.logger = logger;
this.taskbar = taskbar; this.taskbar = taskbar;
this.taskView = taskView; this.taskview = taskview;
this.uiFactory = uiFactory; this.uiFactory = uiFactory;
} }
@ -58,7 +58,7 @@ namespace SafeExamBrowser.Client.Operations
taskbar.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.Taskbar), true); taskbar.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.Taskbar), true);
} }
taskView.Add(Context.Browser); taskview.Add(Context.Browser);
return OperationResult.Success; return OperationResult.Success;
} }

View file

@ -35,7 +35,7 @@ namespace SafeExamBrowser.Client.Operations
private IPowerSupply powerSupply; private IPowerSupply powerSupply;
private ISystemInfo systemInfo; private ISystemInfo systemInfo;
private ITaskbar taskbar; private ITaskbar taskbar;
private ITaskView taskView; private ITaskView taskview;
private IText text; private IText text;
private IUserInterfaceFactory uiFactory; private IUserInterfaceFactory uiFactory;
private IWirelessAdapter wirelessAdapter; private IWirelessAdapter wirelessAdapter;
@ -56,7 +56,7 @@ namespace SafeExamBrowser.Client.Operations
IPowerSupply powerSupply, IPowerSupply powerSupply,
ISystemInfo systemInfo, ISystemInfo systemInfo,
ITaskbar taskbar, ITaskbar taskbar,
ITaskView taskView, ITaskView taskview,
IText text, IText text,
IUserInterfaceFactory uiFactory, IUserInterfaceFactory uiFactory,
IWirelessAdapter wirelessAdapter) : base(context) IWirelessAdapter wirelessAdapter) : base(context)
@ -73,7 +73,7 @@ namespace SafeExamBrowser.Client.Operations
this.systemInfo = systemInfo; this.systemInfo = systemInfo;
this.text = text; this.text = text;
this.taskbar = taskbar; this.taskbar = taskbar;
this.taskView = taskView; this.taskview = taskview;
this.uiFactory = uiFactory; this.uiFactory = uiFactory;
this.wirelessAdapter = wirelessAdapter; this.wirelessAdapter = wirelessAdapter;
} }
@ -116,7 +116,7 @@ namespace SafeExamBrowser.Client.Operations
if (Context.Settings.Keyboard.AllowAltTab && activator is ITaskViewActivator taskViewActivator) if (Context.Settings.Keyboard.AllowAltTab && activator is ITaskViewActivator taskViewActivator)
{ {
taskView.Register(taskViewActivator); taskview.Register(taskViewActivator);
taskViewActivator.Start(); taskViewActivator.Start();
} }
@ -177,7 +177,7 @@ namespace SafeExamBrowser.Client.Operations
foreach (var application in Context.Applications) foreach (var application in Context.Applications)
{ {
taskView.Add(application); taskview.Add(application);
} }
} }

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 System;
using SafeExamBrowser.Applications.Contracts.Resources.Icons; using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.UserInterface.Contracts.Browser.Events; using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Contracts.Windows;
@ -26,6 +27,11 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser
/// Enables the forward navigation button. /// Enables the forward navigation button.
/// </summary> /// </summary>
bool CanNavigateForwards { set; } bool CanNavigateForwards { set; }
/// <summary>
/// The native handle of the window.
/// </summary>
IntPtr Handle { get; }
/// <summary> /// <summary>
/// Event fired when the user changed the URL. /// Event fired when the user changed the URL.

View file

@ -12,6 +12,7 @@ using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using SafeExamBrowser.Applications.Contracts.Resources.Icons; 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 CanNavigateBackwards { set => Dispatcher.Invoke(() => BackwardButton.IsEnabled = value); }
public bool CanNavigateForwards { set => Dispatcher.Invoke(() => ForwardButton.IsEnabled = value); } public bool CanNavigateForwards { set => Dispatcher.Invoke(() => ForwardButton.IsEnabled = value); }
public IntPtr Handle { get; private set; }
public event AddressChangedEventHandler AddressChanged; public event AddressChangedEventHandler AddressChanged;
public event ActionRequestedEventHandler BackwardNavigationRequested; public event ActionRequestedEventHandler BackwardNavigationRequested;
@ -157,6 +159,8 @@ namespace SafeExamBrowser.UserInterface.Desktop
private void BrowserWindow_Loaded(object sender, RoutedEventArgs e) private void BrowserWindow_Loaded(object sender, RoutedEventArgs e)
{ {
Handle = new WindowInteropHelper(this).Handle;
if (isMainWindow) if (isMainWindow)
{ {
WindowUtility.DisableCloseButtonFor(this); WindowUtility.DisableCloseButtonFor(this);

View file

@ -1,4 +1,4 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.TaskViewWindowControl" <UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.TaskviewWindowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -11,11 +11,20 @@
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
<Grid Margin="5" Height="150" Width="250"> <Grid Margin="5" Height="175" Width="250">
<Border Name="Indicator" Background="{StaticResource BackgroundTransparentEmphasisBrush}" BorderThickness="0" Visibility="Hidden" /> <Border Name="Indicator" Background="{StaticResource BackgroundTransparentEmphasisBrush}" BorderThickness="0" Visibility="Hidden" />
<StackPanel HorizontalAlignment="Center" Orientation="Vertical" VerticalAlignment="Center"> <Grid Margin="5">
<ContentControl Name="Icon" MaxWidth="40" /> <Grid.ColumnDefinitions>
<TextBlock Name="Title" Margin="10" Foreground="{StaticResource PrimaryTextBrush}" TextTrimming="CharacterEllipsis" /> <ColumnDefinition Width="Auto" />
</StackPanel> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" Grid.Column="0" Name="Icon" Margin="0,0,5,0" Height="16" />
<TextBlock Grid.Row="0" Grid.Column="1" Name="Title" Foreground="{StaticResource PrimaryTextBrush}" VerticalAlignment="Center" TextTrimming="CharacterEllipsis" />
<ContentControl Grid.Row="1" Grid.ColumnSpan="2" Name="Placeholder" Margin="0,5,0,0" />
</Grid>
</Grid> </Grid>
</UserControl> </UserControl>

View file

@ -6,21 +6,26 @@
* 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; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Threading;
using SafeExamBrowser.Applications.Contracts; using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Resources.Icons; using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.UserInterface.Shared.Utilities; using SafeExamBrowser.UserInterface.Shared.Utilities;
namespace SafeExamBrowser.UserInterface.Desktop.Controls namespace SafeExamBrowser.UserInterface.Desktop.Controls
{ {
public partial class TaskViewWindowControl : UserControl public partial class TaskviewWindowControl : UserControl
{ {
private Taskview taskview;
private IntPtr thumbnail;
private IApplicationWindow window; private IApplicationWindow window;
public TaskViewWindowControl(IApplicationWindow window) public TaskviewWindowControl(IApplicationWindow window, Taskview taskview)
{ {
this.window = window; this.window = window;
this.taskview = taskview;
InitializeComponent(); InitializeComponent();
InitializeControl(); InitializeControl();
@ -33,35 +38,128 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
internal void Deselect() internal void Deselect()
{ {
Icon.MaxWidth = 40;
Indicator.Visibility = Visibility.Hidden; Indicator.Visibility = Visibility.Hidden;
Title.FontWeight = FontWeights.Normal; Title.FontWeight = FontWeights.Normal;
} }
internal void Destroy()
{
if (thumbnail != IntPtr.Zero)
{
Thumbnail.DwmUnregisterThumbnail(thumbnail);
}
}
internal void Select() internal void Select()
{ {
Icon.MaxWidth = 50;
Indicator.Visibility = Visibility.Visible; Indicator.Visibility = Visibility.Visible;
Title.FontWeight = FontWeights.SemiBold; 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() private void InitializeControl()
{ {
Icon.Content = IconResourceLoader.Load(window.Icon); Icon.Content = IconResourceLoader.Load(window.Icon);
IsVisibleChanged += TaskViewWindowControl_IsVisibleChanged;
Loaded += (o, args) => Update();
Title.Text = window.Title; Title.Text = window.Title;
window.IconChanged += Window_IconChanged;
window.IconChanged += Instance_IconChanged; window.TitleChanged += Window_TitleChanged;
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));
} }
} }
} }

View file

@ -142,8 +142,8 @@
<Compile Include="Controls\TaskbarWirelessNetworkControl.xaml.cs"> <Compile Include="Controls\TaskbarWirelessNetworkControl.xaml.cs">
<DependentUpon>TaskbarWirelessNetworkControl.xaml</DependentUpon> <DependentUpon>TaskbarWirelessNetworkControl.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Controls\TaskViewWindowControl.xaml.cs"> <Compile Include="Controls\TaskviewWindowControl.xaml.cs">
<DependentUpon>TaskViewWindowControl.xaml</DependentUpon> <DependentUpon>TaskviewWindowControl.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="LockScreen.xaml.cs"> <Compile Include="LockScreen.xaml.cs">
<DependentUpon>LockScreen.xaml</DependentUpon> <DependentUpon>LockScreen.xaml</DependentUpon>
@ -161,8 +161,8 @@
<Compile Include="SplashScreen.xaml.cs"> <Compile Include="SplashScreen.xaml.cs">
<DependentUpon>SplashScreen.xaml</DependentUpon> <DependentUpon>SplashScreen.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="TaskView.xaml.cs"> <Compile Include="Taskview.xaml.cs">
<DependentUpon>TaskView.xaml</DependentUpon> <DependentUpon>Taskview.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="UserInterfaceFactory.cs" /> <Compile Include="UserInterfaceFactory.cs" />
<Compile Include="ViewModels\DateTimeViewModel.cs" /> <Compile Include="ViewModels\DateTimeViewModel.cs" />
@ -317,7 +317,7 @@
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Resource> </Resource>
<Page Include="Controls\TaskViewWindowControl.xaml"> <Page Include="Controls\TaskviewWindowControl.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
@ -325,7 +325,7 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="TaskView.xaml"> <Page Include="Taskview.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>

View file

@ -1,11 +1,11 @@
<Window x:Class="SafeExamBrowser.UserInterface.Desktop.TaskView" <Window x:Class="SafeExamBrowser.UserInterface.Desktop.Taskview"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop" xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop"
mc:Ignorable="d" AllowsTransparency="True" Background="{DynamicResource BackgroundTransparentBrush}" BorderBrush="DodgerBlue" BorderThickness="1" mc:Ignorable="d" AllowsTransparency="True" Background="{DynamicResource BackgroundTransparentBrush}" BorderBrush="DodgerBlue" BorderThickness="1"
Title="TaskView" Topmost="True" Height="450" SizeToContent="WidthAndHeight" Width="800" WindowStartupLocation="CenterScreen" WindowStyle="None"> Title="Taskview" Topmost="True" Height="450" SizeToContent="WidthAndHeight" Width="800" WindowStartupLocation="CenterScreen" WindowStyle="None">
<Window.Resources> <Window.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>

View file

@ -11,24 +11,28 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Interop;
using SafeExamBrowser.Applications.Contracts; using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
using SafeExamBrowser.UserInterface.Desktop.Controls; using SafeExamBrowser.UserInterface.Desktop.Controls;
namespace SafeExamBrowser.UserInterface.Desktop namespace SafeExamBrowser.UserInterface.Desktop
{ {
public partial class TaskView : Window, ITaskView public partial class Taskview : Window, ITaskView
{ {
private IList<IApplication> applications; private IList<IApplication> applications;
private LinkedListNode<TaskViewWindowControl> current; private LinkedListNode<TaskviewWindowControl> current;
private LinkedList<TaskViewWindowControl> controls; private LinkedList<TaskviewWindowControl> controls;
public TaskView() internal IntPtr Handle { get; private set; }
public Taskview()
{ {
applications = new List<IApplication>(); applications = new List<IApplication>();
controls = new LinkedList<TaskViewWindowControl>(); controls = new LinkedList<TaskviewWindowControl>();
InitializeComponent(); InitializeComponent();
InitializeTaskview();
} }
public void Add(IApplication application) public void Add(IApplication application)
@ -66,9 +70,21 @@ namespace SafeExamBrowser.UserInterface.Desktop
private void ActivateAndHide() private void ActivateAndHide()
{ {
Activate(); if (IsVisible)
current?.Value.Activate(); {
Hide(); Activate();
current?.Value.Activate();
Hide();
}
}
private void InitializeTaskview()
{
Loaded += (o, args) =>
{
Handle = new WindowInteropHelper(this).Handle;
Update();
};
} }
private void SelectNext() private void SelectNext()
@ -106,31 +122,37 @@ namespace SafeExamBrowser.UserInterface.Desktop
private void Update() private void Update()
{ {
var windows = new Stack<IApplicationWindow>(); ClearTaskview();
LoadControls();
UpdateLocation();
}
foreach (var application in applications) private void ClearTaskview()
{
foreach (var control in controls)
{ {
foreach (var window in application.GetWindows()) control.Destroy();
{
windows.Push(window);
}
} }
var max = Math.Ceiling(Math.Sqrt(windows.Count));
controls.Clear(); controls.Clear();
Rows.Children.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 }; var row = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center };
Rows.Children.Add(row); 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 window = windows.Pop();
var control = new TaskViewWindowControl(window); var control = new TaskviewWindowControl(window, this);
controls.AddLast(control); controls.AddLast(control);
row.Children.Add(control); row.Children.Add(control);
@ -139,16 +161,36 @@ namespace SafeExamBrowser.UserInterface.Desktop
current = controls.First; current = controls.First;
current?.Value.Select(); current?.Value.Select();
}
UpdateLayout(); private void UpdateLocation()
{
if (controls.Any())
{
UpdateLayout();
Left = (SystemParameters.WorkArea.Width - Width) / 2 + SystemParameters.WorkArea.Left; Left = (SystemParameters.WorkArea.Width - Width) / 2 + SystemParameters.WorkArea.Left;
Top = (SystemParameters.WorkArea.Height - Height) / 2 + SystemParameters.WorkArea.Top; Top = (SystemParameters.WorkArea.Height - Height) / 2 + SystemParameters.WorkArea.Top;
}
if (!controls.Any()) else
{ {
Hide(); Hide();
} }
} }
private Stack<IApplicationWindow> GetAllWindows()
{
var stack = new Stack<IApplicationWindow>();
foreach (var application in applications)
{
foreach (var window in application.GetWindows())
{
stack.Push(window);
}
}
return stack;
}
} }
} }

View file

@ -12,6 +12,7 @@ using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using SafeExamBrowser.Applications.Contracts.Resources.Icons; 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 CanNavigateBackwards { set => Dispatcher.Invoke(() => BackwardButton.IsEnabled = value); }
public bool CanNavigateForwards { set => Dispatcher.Invoke(() => ForwardButton.IsEnabled = value); } public bool CanNavigateForwards { set => Dispatcher.Invoke(() => ForwardButton.IsEnabled = value); }
public IntPtr Handle { get; private set; }
public event AddressChangedEventHandler AddressChanged; public event AddressChangedEventHandler AddressChanged;
public event ActionRequestedEventHandler BackwardNavigationRequested; public event ActionRequestedEventHandler BackwardNavigationRequested;
@ -157,6 +159,8 @@ namespace SafeExamBrowser.UserInterface.Mobile
private void BrowserWindow_Loaded(object sender, RoutedEventArgs e) private void BrowserWindow_Loaded(object sender, RoutedEventArgs e)
{ {
Handle = new WindowInteropHelper(this).Handle;
if (isMainWindow) if (isMainWindow)
{ {
WindowUtility.DisableCloseButtonFor(this); WindowUtility.DisableCloseButtonFor(this);

View file

@ -73,6 +73,7 @@
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Utilities\IconResourceLoader.cs" /> <Compile Include="Utilities\IconResourceLoader.cs" />
<Compile Include="Utilities\Thumbnail.cs" />
<Compile Include="Utilities\VisualExtensions.cs" /> <Compile Include="Utilities\VisualExtensions.cs" />
<Compile Include="Utilities\WindowUtility.cs" /> <Compile Include="Utilities\WindowUtility.cs" />
</ItemGroup> </ItemGroup>

View file

@ -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
{
/// <remarks>
/// See https://docs.microsoft.com/en-us/windows/win32/dwm/thumbnail-ovw.
/// </remarks>
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;
}
}
}

View file

@ -72,29 +72,6 @@ namespace SafeExamBrowser.WindowsApi
return process; 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) public bool TryGetById(int id, out IProcess process)
{ {
var raw = System.Diagnostics.Process.GetProcesses().FirstOrDefault(p => p.Id == id); 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})"); 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);
}
} }
} }