From f19f284d956c26a8d99475a131f07a78d7d02c00 Mon Sep 17 00:00:00 2001 From: dbuechel Date: Fri, 29 Nov 2019 14:59:54 +0100 Subject: [PATCH] SEBWIN-312: Implemented basic window handling for external applications. Reverted wrong cleanup logic for native handles. --- .../InstanceIdentifier.cs | 41 ------- ...eExamBrowser.Applications.Contracts.csproj | 1 - .../ApplicationFactory.cs | 6 +- .../ApplicationInstanceIdentifier.cs | 42 ------- .../Events/InstanceTerminatedEventHandler.cs | 12 ++ .../ExternalApplication.cs | 26 ++++- .../ExternalApplicationInstance.cs | 106 ++++++++++++++++-- .../ExternalApplicationWindow.cs | 52 +++++++++ .../SafeExamBrowser.Applications.csproj | 3 +- SafeExamBrowser.Browser/BrowserApplication.cs | 9 +- .../BrowserApplicationInstance.cs | 12 +- .../BrowserInstanceIdentifier.cs | 42 ------- .../Events/InstanceTerminatedEventHandler.cs | 2 +- .../SafeExamBrowser.Browser.csproj | 1 - SafeExamBrowser.Client/CompositionRoot.cs | 2 +- .../TaskView.xaml.cs | 2 +- .../TaskView.xaml.cs | 2 +- .../INativeMethods.cs | 7 +- SafeExamBrowser.WindowsApi/NativeMethods.cs | 46 +++++--- SafeExamBrowser.WindowsApi/Process.cs | 50 --------- 20 files changed, 243 insertions(+), 221 deletions(-) delete mode 100644 SafeExamBrowser.Applications.Contracts/InstanceIdentifier.cs delete mode 100644 SafeExamBrowser.Applications/ApplicationInstanceIdentifier.cs create mode 100644 SafeExamBrowser.Applications/Events/InstanceTerminatedEventHandler.cs create mode 100644 SafeExamBrowser.Applications/ExternalApplicationWindow.cs delete mode 100644 SafeExamBrowser.Browser/BrowserInstanceIdentifier.cs diff --git a/SafeExamBrowser.Applications.Contracts/InstanceIdentifier.cs b/SafeExamBrowser.Applications.Contracts/InstanceIdentifier.cs deleted file mode 100644 index 07f3979d..00000000 --- a/SafeExamBrowser.Applications.Contracts/InstanceIdentifier.cs +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.Applications.Contracts -{ - /// - /// Defines an identifier which uniquely identifies an instance in the context of an application. - /// - public abstract class InstanceIdentifier - { - /// - /// Determines whether two identifiers are equal (i.e. whether they identify the same application instance). - /// - public static bool operator ==(InstanceIdentifier a, InstanceIdentifier b) => Equals(a, b); - - /// - /// Determines whether two identifiers are different (i.e. whether they identify different application instances). - /// - public static bool operator !=(InstanceIdentifier a, InstanceIdentifier b) => !Equals(a, b); - - /// - /// Indicates whether the given object is an identifier for the same application instance. - /// - public abstract override bool Equals(object other); - - /// - /// Returns a hash code for the identifier. - /// - public abstract override int GetHashCode(); - - /// - /// Returns a human-readable string representation of the identifier. - /// - public abstract override string ToString(); - } -} diff --git a/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj b/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj index add43a26..f2e84263 100644 --- a/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj +++ b/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj @@ -62,7 +62,6 @@ - diff --git a/SafeExamBrowser.Applications/ApplicationFactory.cs b/SafeExamBrowser.Applications/ApplicationFactory.cs index 99b6db3c..9e5549b8 100644 --- a/SafeExamBrowser.Applications/ApplicationFactory.cs +++ b/SafeExamBrowser.Applications/ApplicationFactory.cs @@ -21,11 +21,13 @@ namespace SafeExamBrowser.Applications public class ApplicationFactory : IApplicationFactory { private IModuleLogger logger; + private INativeMethods nativeMethods; private IProcessFactory processFactory; - public ApplicationFactory(IModuleLogger logger, IProcessFactory processFactory) + public ApplicationFactory(IModuleLogger logger, INativeMethods nativeMethods, IProcessFactory processFactory) { this.logger = logger; + this.nativeMethods = nativeMethods; this.processFactory = processFactory; } @@ -65,7 +67,7 @@ namespace SafeExamBrowser.Applications { var icon = new IconResource { Type = IconResourceType.Embedded, Uri = new Uri(executablePath) }; var info = new ApplicationInfo { AutoStart = settings.AutoStart, Icon = icon, Name = settings.DisplayName, Tooltip = settings.Description ?? settings.DisplayName }; - var application = new ExternalApplication(executablePath, info, logger.CloneFor(settings.DisplayName), processFactory); + var application = new ExternalApplication(executablePath, info, logger.CloneFor(settings.DisplayName), nativeMethods, processFactory); return application; } diff --git a/SafeExamBrowser.Applications/ApplicationInstanceIdentifier.cs b/SafeExamBrowser.Applications/ApplicationInstanceIdentifier.cs deleted file mode 100644 index 68e21581..00000000 --- a/SafeExamBrowser.Applications/ApplicationInstanceIdentifier.cs +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 SafeExamBrowser.Applications.Contracts; - -namespace SafeExamBrowser.Applications -{ - internal class ApplicationInstanceIdentifier : InstanceIdentifier - { - internal int ProcessId { get; private set; } - - public ApplicationInstanceIdentifier(int processId) - { - ProcessId = processId; - } - - public override bool Equals(object other) - { - if (other is ApplicationInstanceIdentifier id) - { - return ProcessId == id.ProcessId; - } - - return false; - } - - public override int GetHashCode() - { - return ProcessId.GetHashCode(); - } - - public override string ToString() - { - return $"({ProcessId})"; - } - } -} diff --git a/SafeExamBrowser.Applications/Events/InstanceTerminatedEventHandler.cs b/SafeExamBrowser.Applications/Events/InstanceTerminatedEventHandler.cs new file mode 100644 index 00000000..499a2119 --- /dev/null +++ b/SafeExamBrowser.Applications/Events/InstanceTerminatedEventHandler.cs @@ -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.Applications.Events +{ + internal delegate void InstanceTerminatedEventHandler(int id); +} diff --git a/SafeExamBrowser.Applications/ExternalApplication.cs b/SafeExamBrowser.Applications/ExternalApplication.cs index df8985a0..0e3e3e4c 100644 --- a/SafeExamBrowser.Applications/ExternalApplication.cs +++ b/SafeExamBrowser.Applications/ExternalApplication.cs @@ -18,8 +18,11 @@ namespace SafeExamBrowser.Applications { internal class ExternalApplication : IApplication { + private int instanceIdCounter = default(int); + private string executablePath; private IModuleLogger logger; + private INativeMethods nativeMethods; private IList instances; private IProcessFactory processFactory; @@ -27,18 +30,24 @@ namespace SafeExamBrowser.Applications public ApplicationInfo Info { get; } - internal ExternalApplication(string executablePath, ApplicationInfo info, IModuleLogger logger, IProcessFactory processFactory) + internal ExternalApplication( + string executablePath, + ApplicationInfo info, + IModuleLogger logger, + INativeMethods nativeMethods, + IProcessFactory processFactory) { this.executablePath = executablePath; this.Info = info; this.logger = logger; + this.nativeMethods = nativeMethods; this.instances = new List(); this.processFactory = processFactory; } public IEnumerable GetWindows() { - return Enumerable.Empty(); + return instances.SelectMany(i => i.GetWindows()); } public void Initialize() @@ -53,10 +62,13 @@ namespace SafeExamBrowser.Applications logger.Info("Starting application..."); var process = processFactory.StartNew(executablePath); - var id = new ApplicationInstanceIdentifier(process.Id); - var instance = new ExternalApplicationInstance(Info.Icon, id, logger.CloneFor($"{Info.Name} {id}"), process); + var id = ++instanceIdCounter; + var instanceLogger = logger.CloneFor($"{Info.Name} Instance #{id}"); + var instance = new ExternalApplicationInstance(Info.Icon, id, instanceLogger, nativeMethods, process); instance.Initialize(); + instance.Terminated += Instance_Terminated; + instance.WindowsChanged += () => WindowsChanged?.Invoke(); instances.Add(instance); } catch (Exception e) @@ -65,6 +77,12 @@ namespace SafeExamBrowser.Applications } } + private void Instance_Terminated(int id) + { + instances.Remove(instances.First(i => i.Id == id)); + WindowsChanged?.Invoke(); + } + public void Terminate() { if (instances.Any()) diff --git a/SafeExamBrowser.Applications/ExternalApplicationInstance.cs b/SafeExamBrowser.Applications/ExternalApplicationInstance.cs index 90241a86..786c9b3d 100644 --- a/SafeExamBrowser.Applications/ExternalApplicationInstance.cs +++ b/SafeExamBrowser.Applications/ExternalApplicationInstance.cs @@ -6,7 +6,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Timers; using SafeExamBrowser.Applications.Contracts; +using SafeExamBrowser.Applications.Contracts.Events; +using SafeExamBrowser.Applications.Events; using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.WindowsApi.Contracts; @@ -15,22 +21,42 @@ namespace SafeExamBrowser.Applications { internal class ExternalApplicationInstance { - private IconResource icon; - private InstanceIdentifier id; - private ILogger logger; - private IProcess process; + private readonly object @lock = new object(); - internal ExternalApplicationInstance(IconResource icon, InstanceIdentifier id, ILogger logger, IProcess process) + private IconResource icon; + private ILogger logger; + private INativeMethods nativeMethods; + private IProcess process; + private Timer timer; + private IList windows; + + internal int Id { get; } + + internal event InstanceTerminatedEventHandler Terminated; + internal event WindowsChangedEventHandler WindowsChanged; + + internal ExternalApplicationInstance(IconResource icon, int id, ILogger logger, INativeMethods nativeMethods, IProcess process) { this.icon = icon; - this.id = id; + this.Id = id; this.logger = logger; + this.nativeMethods = nativeMethods; this.process = process; + this.windows = new List(); + } + + internal IEnumerable GetWindows() + { + lock (@lock) + { + return new List(windows); + } } internal void Initialize() { - process.Terminated += Process_Terminated; + InitializeEvents(); + logger.Info("Initialized application instance."); } internal void Terminate() @@ -42,7 +68,7 @@ namespace SafeExamBrowser.Applications if (!terminated) { - process.Terminated -= Process_Terminated; + FinalizeEvents(); for (var attempt = 0; attempt < MAX_ATTEMPTS && !terminated; attempt++) { @@ -68,7 +94,69 @@ namespace SafeExamBrowser.Applications private void Process_Terminated(int exitCode) { logger.Info($"Application instance has terminated with exit code {exitCode}."); - // TODO: Terminated?.Invoke(Id); -> Remove from application! + FinalizeEvents(); + Terminated?.Invoke(Id); + } + + private void Timer_Elapsed(object sender, ElapsedEventArgs e) + { + var changed = false; + var openWindows = nativeMethods.GetOpenWindows(); + + lock (@lock) + { + var closedWindows = windows.Where(w => openWindows.All(ow => ow != w.Handle)).ToList(); + var openedWindows = openWindows.Where(ow => windows.All(w => w.Handle != ow) && BelongsToInstance(ow)); + + foreach (var window in closedWindows) + { + changed = true; + windows.Remove(window); + } + + foreach (var window in openedWindows) + { + changed = true; + windows.Add(new ExternalApplicationWindow(icon, nativeMethods, window)); + } + + foreach (var window in windows) + { + window.Update(); + } + } + + if (changed) + { + logger.Error("WINDOWS CHANGED!"); + WindowsChanged?.Invoke(); + } + + timer.Start(); + } + + private bool BelongsToInstance(IntPtr window) + { + return nativeMethods.GetProcessIdFor(window) == process.Id; + } + + private void InitializeEvents() + { + const int ONE_SECOND = 1000; + + process.Terminated += Process_Terminated; + + timer = new Timer(ONE_SECOND); + timer.Elapsed += Timer_Elapsed; + timer.Start(); + } + + private void FinalizeEvents() + { + timer.Elapsed -= Timer_Elapsed; + timer.Stop(); + + process.Terminated -= Process_Terminated; } } } diff --git a/SafeExamBrowser.Applications/ExternalApplicationWindow.cs b/SafeExamBrowser.Applications/ExternalApplicationWindow.cs new file mode 100644 index 00000000..b5194bb3 --- /dev/null +++ b/SafeExamBrowser.Applications/ExternalApplicationWindow.cs @@ -0,0 +1,52 @@ +/* + * 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 SafeExamBrowser.Applications.Contracts; +using SafeExamBrowser.Applications.Contracts.Events; +using SafeExamBrowser.Core.Contracts; +using SafeExamBrowser.WindowsApi.Contracts; + +namespace SafeExamBrowser.Applications +{ + internal class ExternalApplicationWindow : IApplicationWindow + { + private INativeMethods nativeMethods; + + internal IntPtr Handle { get; } + public IconResource Icon { get; } + public string Title { get; private set; } + + public event IconChangedEventHandler IconChanged { add { } remove { } } + public event TitleChangedEventHandler TitleChanged; + + internal ExternalApplicationWindow(IconResource icon, INativeMethods nativeMethods, IntPtr handle) + { + this.Handle = handle; + this.Icon = icon; + this.nativeMethods = nativeMethods; + } + + public void Activate() + { + nativeMethods.ActivateWindow(Handle); + } + + internal void Update() + { + var title = nativeMethods.GetWindowTitle(Handle); + var hasChanged = Title?.Equals(title, StringComparison.Ordinal) != true; + + if (hasChanged) + { + Title = title; + TitleChanged?.Invoke(title); + } + } + } +} diff --git a/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj b/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj index ea3676c1..546a58e2 100644 --- a/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj +++ b/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj @@ -55,9 +55,10 @@ - + + diff --git a/SafeExamBrowser.Browser/BrowserApplication.cs b/SafeExamBrowser.Browser/BrowserApplication.cs index d2c57e2b..1b6578be 100644 --- a/SafeExamBrowser.Browser/BrowserApplication.cs +++ b/SafeExamBrowser.Browser/BrowserApplication.cs @@ -112,9 +112,9 @@ namespace SafeExamBrowser.Browser private void CreateNewInstance(string url = null) { - var id = new BrowserInstanceIdentifier(++instanceIdCounter); + var id = ++instanceIdCounter; var isMainInstance = instances.Count == 0; - var instanceLogger = logger.CloneFor($"BrowserInstance {id}"); + var instanceLogger = logger.CloneFor($"Browser Instance #{id}"); var startUrl = url ?? settings.StartUrl; var instance = new BrowserApplicationInstance(appConfig, settings, id, isMainInstance, messageBox, instanceLogger, text, uiFactory, startUrl); @@ -157,10 +157,9 @@ namespace SafeExamBrowser.Browser CreateNewInstance(args.Url); } - private void Instance_Terminated(InstanceIdentifier id) + private void Instance_Terminated(int id) { - instances.Remove(instances.FirstOrDefault(i => i.Id == id)); - logger.Info($"Browser instance {id} was terminated."); + instances.Remove(instances.First(i => i.Id == id)); WindowsChanged?.Invoke(); } diff --git a/SafeExamBrowser.Browser/BrowserApplicationInstance.cs b/SafeExamBrowser.Browser/BrowserApplicationInstance.cs index 9a01d5b7..74e2ed63 100644 --- a/SafeExamBrowser.Browser/BrowserApplicationInstance.cs +++ b/SafeExamBrowser.Browser/BrowserApplicationInstance.cs @@ -49,7 +49,7 @@ namespace SafeExamBrowser.Browser get { return isMainInstance ? settings.MainWindow : settings.AdditionalWindow; } } - internal BrowserInstanceIdentifier Id { get; private set; } + internal int Id { get; } public IconResource Icon { get; private set; } public string Title { get; private set; } @@ -64,7 +64,7 @@ namespace SafeExamBrowser.Browser public BrowserApplicationInstance( AppConfig appConfig, BrowserSettings settings, - BrowserInstanceIdentifier id, + int id, bool isMainInstance, IMessageBox messageBox, IModuleLogger logger, @@ -166,7 +166,7 @@ namespace SafeExamBrowser.Browser private void InitializeWindow() { window = uiFactory.CreateBrowserWindow(control, settings, isMainInstance); - window.Closing += () => Terminated?.Invoke(Id); + window.Closing += Window_Closing; window.AddressChanged += Window_AddressChanged; window.BackwardNavigationRequested += Window_BackwardNavigationRequested; window.DeveloperConsoleRequested += Window_DeveloperConsoleRequested; @@ -326,6 +326,12 @@ namespace SafeExamBrowser.Browser control.NavigateBackwards(); } + private void Window_Closing() + { + logger.Info($"Instance has terminated."); + Terminated?.Invoke(Id); + } + private void Window_DeveloperConsoleRequested() { logger.Debug("Showing developer console..."); diff --git a/SafeExamBrowser.Browser/BrowserInstanceIdentifier.cs b/SafeExamBrowser.Browser/BrowserInstanceIdentifier.cs deleted file mode 100644 index f9e2cdc1..00000000 --- a/SafeExamBrowser.Browser/BrowserInstanceIdentifier.cs +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 SafeExamBrowser.Applications.Contracts; - -namespace SafeExamBrowser.Browser -{ - internal class BrowserInstanceIdentifier : InstanceIdentifier - { - internal int Value { get; private set; } - - public BrowserInstanceIdentifier(int id) - { - Value = id; - } - - public override bool Equals(object other) - { - if (other is BrowserInstanceIdentifier id) - { - return Value == id.Value; - } - - return false; - } - - public override int GetHashCode() - { - return Value.GetHashCode(); - } - - public override string ToString() - { - return $"#{Value}"; - } - } -} diff --git a/SafeExamBrowser.Browser/Events/InstanceTerminatedEventHandler.cs b/SafeExamBrowser.Browser/Events/InstanceTerminatedEventHandler.cs index afbc35e6..7975dfdc 100644 --- a/SafeExamBrowser.Browser/Events/InstanceTerminatedEventHandler.cs +++ b/SafeExamBrowser.Browser/Events/InstanceTerminatedEventHandler.cs @@ -8,5 +8,5 @@ namespace SafeExamBrowser.Browser.Events { - internal delegate void InstanceTerminatedEventHandler(BrowserInstanceIdentifier id); + internal delegate void InstanceTerminatedEventHandler(int id); } diff --git a/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj b/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj index 943d6f1d..af07210f 100644 --- a/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj +++ b/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj @@ -63,7 +63,6 @@ - diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index a11ee56b..386d4957 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -98,7 +98,7 @@ namespace SafeExamBrowser.Client taskView = BuildTaskView(); var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory))); - var applicationFactory = new ApplicationFactory(ModuleLogger(nameof(ApplicationFactory)), processFactory); + var applicationFactory = new ApplicationFactory(ModuleLogger(nameof(ApplicationFactory)), nativeMethods, processFactory); var applicationMonitor = new ApplicationMonitor(TWO_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, new ProcessFactory(ModuleLogger(nameof(ProcessFactory)))); var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo); var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods); diff --git a/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs index 1e3ad581..35479681 100644 --- a/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs @@ -145,7 +145,7 @@ namespace SafeExamBrowser.UserInterface.Desktop Left = (SystemParameters.WorkArea.Width - Width) / 2 + SystemParameters.WorkArea.Left; Top = (SystemParameters.WorkArea.Height - Height) / 2 + SystemParameters.WorkArea.Top; - if (!windows.Any()) + if (!controls.Any()) { Hide(); } diff --git a/SafeExamBrowser.UserInterface.Mobile/TaskView.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/TaskView.xaml.cs index 123447f9..a633f039 100644 --- a/SafeExamBrowser.UserInterface.Mobile/TaskView.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/TaskView.xaml.cs @@ -145,7 +145,7 @@ namespace SafeExamBrowser.UserInterface.Mobile Left = (SystemParameters.WorkArea.Width - Width) / 2 + SystemParameters.WorkArea.Left; Top = (SystemParameters.WorkArea.Height - Height) / 2 + SystemParameters.WorkArea.Top; - if (!windows.Any()) + if (!controls.Any()) { Hide(); } diff --git a/SafeExamBrowser.WindowsApi.Contracts/INativeMethods.cs b/SafeExamBrowser.WindowsApi.Contracts/INativeMethods.cs index c0769970..6ed3e422 100644 --- a/SafeExamBrowser.WindowsApi.Contracts/INativeMethods.cs +++ b/SafeExamBrowser.WindowsApi.Contracts/INativeMethods.cs @@ -17,6 +17,11 @@ namespace SafeExamBrowser.WindowsApi.Contracts /// public interface INativeMethods { + /// + /// Brings the window with the given handle to the foreground and activates it. + /// + void ActivateWindow(IntPtr handle); + /// /// Deregisters a previously registered keyboard hook. /// @@ -86,7 +91,7 @@ namespace SafeExamBrowser.WindowsApi.Contracts string GetWallpaperPath(); /// - /// Retrieves the title of the specified window, or an empty string, if the given window does not have a title. + /// Retrieves the title of the window with the given handle, or an empty string if the given window does not have a title. /// string GetWindowTitle(IntPtr window); diff --git a/SafeExamBrowser.WindowsApi/NativeMethods.cs b/SafeExamBrowser.WindowsApi/NativeMethods.cs index dbb3cb91..c84ab429 100644 --- a/SafeExamBrowser.WindowsApi/NativeMethods.cs +++ b/SafeExamBrowser.WindowsApi/NativeMethods.cs @@ -24,34 +24,50 @@ namespace SafeExamBrowser.WindowsApi { public class NativeMethods : INativeMethods { - private ConcurrentBag KeyboardHooks = new ConcurrentBag(); - private ConcurrentBag MouseHooks = new ConcurrentBag(); - private ConcurrentBag SystemHooks = new ConcurrentBag(); + private ConcurrentDictionary KeyboardHooks = new ConcurrentDictionary(); + private ConcurrentDictionary MouseHooks = new ConcurrentDictionary(); + private ConcurrentDictionary SystemHooks = new ConcurrentDictionary(); /// /// Upon finalization, unregister all active system events and hooks... /// ~NativeMethods() { - foreach (var hook in SystemHooks) + foreach (var hook in SystemHooks.Values) { hook.Detach(); } - foreach (var hook in KeyboardHooks) + foreach (var hook in KeyboardHooks.Values) { hook.Detach(); } - foreach (var hook in MouseHooks) + foreach (var hook in MouseHooks.Values) { hook.Detach(); } } + public void ActivateWindow(IntPtr handle) + { + var placement = new WINDOWPLACEMENT(); + + User32.BringWindowToTop(handle); + User32.SetForegroundWindow(handle); + + placement.length = Marshal.SizeOf(placement); + User32.GetWindowPlacement(handle, ref placement); + + if (placement.showCmd == (int) ShowWindowCommand.ShowMinimized) + { + User32.ShowWindowAsync(handle, (int) ShowWindowCommand.Restore); + } + } + public void DeregisterKeyboardHook(Guid hookId) { - var hook = KeyboardHooks.FirstOrDefault(h => h.Id == hookId); + var hook = KeyboardHooks.Values.FirstOrDefault(h => h.Id == hookId); if (hook != null) { @@ -62,13 +78,13 @@ namespace SafeExamBrowser.WindowsApi throw new Win32Exception(Marshal.GetLastWin32Error()); } - KeyboardHooks.TryTake(out _); + KeyboardHooks.TryRemove(hookId, out _); } } public void DeregisterMouseHook(Guid hookId) { - var hook = MouseHooks.FirstOrDefault(h => h.Id == hookId); + var hook = MouseHooks.Values.FirstOrDefault(h => h.Id == hookId); if (hook != null) { @@ -79,13 +95,13 @@ namespace SafeExamBrowser.WindowsApi throw new Win32Exception(Marshal.GetLastWin32Error()); } - MouseHooks.TryTake(out _); + MouseHooks.TryRemove(hookId, out _); } } public void DeregisterSystemEventHook(Guid hookId) { - var hook = SystemHooks.FirstOrDefault(h => h.Id == hookId); + var hook = SystemHooks.Values.FirstOrDefault(h => h.Id == hookId); if (hook != null) { @@ -96,7 +112,7 @@ namespace SafeExamBrowser.WindowsApi throw new Win32Exception(Marshal.GetLastWin32Error()); } - SystemHooks.TryTake(out _); + SystemHooks.TryRemove(hookId, out _); } } @@ -254,7 +270,7 @@ namespace SafeExamBrowser.WindowsApi hook.Attach(); hookId = hook.Id; - KeyboardHooks.Add(hook); + KeyboardHooks[hookId] = hook; hookReadyEvent.Set(); while (true) @@ -283,7 +299,7 @@ namespace SafeExamBrowser.WindowsApi hook.Attach(); hookId = hook.Id; - MouseHooks.Add(hook); + MouseHooks[hookId] = hook; hookReadyEvent.Set(); while (true) @@ -321,7 +337,7 @@ namespace SafeExamBrowser.WindowsApi hook.Attach(); hookId = hook.Id; - SystemHooks.Add(hook); + SystemHooks[hookId] = hook; hookReadyEvent.Set(); hook.AwaitDetach(); }); diff --git a/SafeExamBrowser.WindowsApi/Process.cs b/SafeExamBrowser.WindowsApi/Process.cs index 39255541..889cb5d9 100644 --- a/SafeExamBrowser.WindowsApi/Process.cs +++ b/SafeExamBrowser.WindowsApi/Process.cs @@ -11,12 +11,9 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Management; -using System.Runtime.InteropServices; using SafeExamBrowser.Logging.Contracts; -using SafeExamBrowser.WindowsApi.Constants; using SafeExamBrowser.WindowsApi.Contracts; using SafeExamBrowser.WindowsApi.Contracts.Events; -using SafeExamBrowser.WindowsApi.Types; namespace SafeExamBrowser.WindowsApi { @@ -68,34 +65,6 @@ namespace SafeExamBrowser.WindowsApi this.namesInitialized = true; } - public bool TryActivate() - { - try - { - var success = true; - var placement = new WINDOWPLACEMENT(); - - success &= User32.BringWindowToTop(process.MainWindowHandle); - success &= User32.SetForegroundWindow(process.MainWindowHandle); - - placement.length = Marshal.SizeOf(placement); - User32.GetWindowPlacement(process.MainWindowHandle, ref placement); - - if (placement.showCmd == (int) ShowWindowCommand.ShowMinimized) - { - success &= User32.ShowWindowAsync(process.MainWindowHandle, (int) ShowWindowCommand.Restore); - } - - return success; - } - catch (Exception e) - { - logger.Error("Failed to activate process!", e); - } - - return false; - } - public bool TryClose(int timeout_ms = 0) { try @@ -124,25 +93,6 @@ namespace SafeExamBrowser.WindowsApi return false; } - public bool TryGetWindowTitle(out string title) - { - title = default(string); - - try - { - process.Refresh(); - title = process.MainWindowTitle; - - return true; - } - catch (Exception e) - { - logger.Error("Failed to retrieve title of main window!", e); - } - - return false; - } - public bool TryKill(int timeout_ms = 0) { try