From d6ffac0af707e1fceedc4bd35c1fb3bc724a70fe Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 8 Jan 2018 21:33:03 +0000 Subject: [PATCH 1/4] GPII-2554: Made the tray icon show also if re-installing --- setup/trayicon.cs | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/setup/trayicon.cs b/setup/trayicon.cs index c0044be..871c8c2 100644 --- a/setup/trayicon.cs +++ b/setup/trayicon.cs @@ -63,7 +63,7 @@ static void Main(string[] args) return; } - string exe = args[0]; + string exe = args[0].ToLowerInvariant(); bool show = true; bool wait = true; @@ -86,15 +86,33 @@ static void Main(string[] args) } } - bool found = false; - do + // Wait for the process to start. The icon needs to be shown in order to configure it. + bool running = false; + int attempts = 60; + while (wait && !running) { - found = TrayIcon.SetPreference(exe, show); - if (wait && !found) + running = Process.GetProcesses().Any(p => { - Thread.Sleep(5000); + try + { + return p.MainModule.FileName.ToLowerInvariant().Contains(exe); + } + catch (System.ComponentModel.Win32Exception) + { + return false; + } + }); + + if (--attempts < 0) + { + break; } - } while (wait && !found); + + // Sleep even if running, to give the application a chance to show the icon. + Thread.Sleep(5000); + } + + TrayIcon.SetPreference(exe, show); } /// @@ -122,12 +140,13 @@ public static bool SetPreference(string exeName, bool alwaysShow) INotificationCB callback = new NotificationCallback(item => { + Console.WriteLine(item.pszExeName + "=" + item.dwUserPref + ": " + item.guidItem.ToString()); if (item.pszExeName.IndexOf(exeName, StringComparison.OrdinalIgnoreCase) >= 0) { NOTIFYITEM_OUT itemOut = new NOTIFYITEM_OUT(item); uint newValue = (bool)alwaysShow ? NOTIFYITEM.AlwaysShow : NOTIFYITEM.Hide; found = true; - if (itemOut.dwUserPref != newValue) + if (true || itemOut.dwUserPref != newValue) { Console.WriteLine("Updating {0}", item.pszExeName); itemOut.dwUserPref = newValue; From 0c3ba4e6f646e319bbee37dc2a6cc0852f790b49 Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 9 Jan 2018 10:18:11 +0000 Subject: [PATCH 2/4] GPII-2554: Always configure the icon. --- setup/trayicon.cs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/setup/trayicon.cs b/setup/trayicon.cs index 871c8c2..d84bd84 100644 --- a/setup/trayicon.cs +++ b/setup/trayicon.cs @@ -146,23 +146,17 @@ public static bool SetPreference(string exeName, bool alwaysShow) NOTIFYITEM_OUT itemOut = new NOTIFYITEM_OUT(item); uint newValue = (bool)alwaysShow ? NOTIFYITEM.AlwaysShow : NOTIFYITEM.Hide; found = true; - if (true || itemOut.dwUserPref != newValue) - { - Console.WriteLine("Updating {0}", item.pszExeName); - itemOut.dwUserPref = newValue; - if (TrayIcon.UseNew) - { - ((ITrayNotifyNew)trayNotify).SetPreference(itemOut); - } - else - { - ((ITrayNotifyOld)trayNotify).SetPreference(itemOut); - } + Console.WriteLine("Updating {0}", item.pszExeName); + itemOut.dwUserPref = newValue; + + if (TrayIcon.UseNew) + { + ((ITrayNotifyNew)trayNotify).SetPreference(itemOut); } else { - Console.WriteLine("No need to update {0}", item.pszExeName); + ((ITrayNotifyOld)trayNotify).SetPreference(itemOut); } } }); From bf3c739fb7853cf36e8fa5f80f05f95a661a1855 Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 23 Jan 2018 15:08:22 +0000 Subject: [PATCH 3/4] GPII-2554: Increased chances of the icon being locked, thanks to @JavierJF's UI Automation. --- setup/Product.wxs | 2 +- setup/setup.msbuild | 16 ++- setup/trayicon-uia.cs | 284 ++++++++++++++++++++++++++++++++++++++++++ setup/trayicon.cs | 117 ++++++++++------- 4 files changed, 370 insertions(+), 49 deletions(-) create mode 100644 setup/trayicon-uia.cs diff --git a/setup/Product.wxs b/setup/Product.wxs index 2fbd30d..e3ad969 100644 --- a/setup/Product.wxs +++ b/setup/Product.wxs @@ -352,7 +352,7 @@ - + diff --git a/setup/setup.msbuild b/setup/setup.msbuild index 991cdf7..692b21a 100644 --- a/setup/setup.msbuild +++ b/setup/setup.msbuild @@ -41,10 +41,22 @@ + + + + + + True + + + - + + + + diff --git a/setup/trayicon-uia.cs b/setup/trayicon-uia.cs new file mode 100644 index 0000000..d466284 --- /dev/null +++ b/setup/trayicon-uia.cs @@ -0,0 +1,284 @@ +/* + * trayicon-uia.cs + * Copyright 2018 Raising the Floor - International + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * The R&D leading to these results received funding from the + * Department of Education - Grant H421A150005 (GPII-APCP). However, + * these results do not necessarily represent the policy of the + * Department of Education, and you should not assume endorsement by the + * Federal Government. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +namespace TrayIconApplication +{ + class TrayIconUIA + { + private const int SWP_NOOWNERZORDER = 0x200; + private const int SWP_NOACTIVATE = 0x10; + private const int SWP_NOSIZE = 0x1; + private const int SWP_NOZORDER = 0x4; + private const int SWP_HIDEWINDOW = 0x80; + private const uint WM_CLOSE = 0x10; + + [DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + [DllImport("user32.dll")] + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); + [DllImport("user32.dll")] + private static extern bool EnumWindows(WindowEnumProc lpEnumFunc, IntPtr lParam); + [DllImport("user32.dll")] + private static extern bool EnumChildWindows(IntPtr hWndParent, WindowEnumProc lpEnumFunc, IntPtr lParam); + [DllImport("user32.dll")] + private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); + [DllImport("user32.dll")] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + [DllImport("user32.dll")] + private static extern int SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); + [DllImport("user32.dll")] + private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); + + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int Left, Top, Right, Bottom; + } + + public delegate bool WindowEnumProc(IntPtr hwnd, IntPtr lparam); + + private static Process GetWindowProcess(IntPtr hwnd) + { + int pid; + GetWindowThreadProcessId(hwnd, out pid); + var p = Process.GetProcessById(pid); + return p; + } + + /// + /// Gets the UWP process that owns the given window. + /// + /// If the process owning the window is ApplicationFrameHost, then return the process that + /// owns a child window with a class name of "Windows.UI.Core.CoreWindow". + /// + /// The window to check. + /// The process that owns the Window. + [System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions] + private static Process GetUwpWindowProcess(IntPtr hwnd) + { + int n = 0; + + Process process = GetWindowProcess(hwnd); + + if (process != null && process.ProcessName == "ApplicationFrameHost") + { + Process uwpProcess = null; + EnumChildWindows(hwnd, (hwndChild, lp) => + { + StringBuilder cls = new StringBuilder(255); + GetClassName(hwndChild, cls, cls.Capacity); + if (cls.ToString() == "Windows.UI.Core.CoreWindow") + { + Process otherProcess = GetWindowProcess(hwndChild); + if (otherProcess.Id != process.Id) + { + uwpProcess = otherProcess; + } + } + return uwpProcess == null; + }, IntPtr.Zero); + + return uwpProcess; + } + else + { + return null; + } + } + + /// + /// Finds a Window of a UWP process. + /// + /// The process name. + /// The window handle, or 0 if not found. + private static IntPtr FindWindowByProcess(string processName) + { + IntPtr result = IntPtr.Zero; + + EnumWindows((hwnd, lp) => + { + Process process = GetUwpWindowProcess(hwnd); + + if (process != null && process.ProcessName == processName) + { + result = hwnd; + return false; + } + else + { + return true; + } + }, IntPtr.Zero); + + return result; + } + + public static void ToggleTrayIcon(string appToLock) + { + Console.WriteLine(":: Starting ms-settings."); + Process.Start("ms-settings:taskbar"); + + + // Wait for the window to appear. + IntPtr hwnd = IntPtr.Zero; + for (int n = 0; n < 50; n++) + { + hwnd = FindWindowByProcess("SystemSettings"); + + if (hwnd != IntPtr.Zero) + { + Console.WriteLine("found settings window"); + break; + } + Thread.Sleep(100); + } + + RECT windowRect = new RECT(); + if (hwnd != IntPtr.Zero) + { + // Move the window off screen. + GetWindowRect(hwnd, out windowRect); + SetWindowPos(hwnd, IntPtr.Zero, -windowRect.Right, windowRect.Top, 0, 0, SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER); + } + + PerformUIA(appToLock); + + if (hwnd != IntPtr.Zero) + { + // Put the window back, and hide it. + SetWindowPos(hwnd, IntPtr.Zero, windowRect.Left, windowRect.Top, 0, 0, SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER | SWP_HIDEWINDOW); + SendMessage(hwnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); + } + } + + /// Toggles the tray icon. + /// The name of the icon. + private static void PerformUIA(string appToLock) { + + Console.WriteLine(":: Initializing UI Automation."); + + // The first step in calling UIA, is getting a CUIAutomation object. + var _automation = new CUIAutomation(); + + // The properties are used from the actual constant values here: + // "https://msdn.microsoft.com/en-us/library/windows/desktop/ee684017(v=vs.85)." + int _propertyName = 30005; + int _propertyAutomationId = 30011; + int _propertyClassName = 30012; + + // The patterns are used from the actual constant values here: + // "https://msdn.microsoft.com/en-us/library/windows/desktop/ee671195(v=vs.85).aspx" + int _invokePattern = 10000; + int _tooglePattern = 10015; + + Console.WriteLine(":: Getting root node."); + // The first step in calling UIA, is getting a CUIAutomation object. + IUIAutomationElement rootElement = _automation.GetRootElement(); + + IUIAutomationCondition settingsWindow = + _automation.CreatePropertyCondition( + _propertyAutomationId, + "Settings" + ); + + IUIAutomationCondition waitSettingsOpenning = + _automation.CreatePropertyCondition( + _propertyAutomationId, + "SystemSettings_Taskbar_Lock_ToggleSwitch" + ); + + Console.WriteLine(":: Waiting for 'ms-settings' app to be ready."); + IUIAutomationElement settingsOpennedMark = rootElement.FindFirst(TreeScope.TreeScope_Subtree, waitSettingsOpenning); + while (settingsOpennedMark == null) + { + Thread.Sleep(200); + settingsOpennedMark = rootElement.FindFirst(TreeScope.TreeScope_Subtree, waitSettingsOpenning); + } + + var p = settingsOpennedMark.GetCachedParent(); + + Console.WriteLine(":: 'ms-settings' app oppened sucessfully."); + + // Triggers the first link to the list of possible applications icons. + // SystemSettings_Taskbar_SelectIconsToAppearOnTaskbar_HyperlinkButton + + IUIAutomationCondition searchTaskBarIconsLink = + _automation.CreatePropertyCondition( + _propertyAutomationId, + "SystemSettings_Taskbar_SelectIconsToAppearOnTaskbar_HyperlinkButton" + ); + + Console.WriteLine(":: Openning 'ms-settings' app section with icon toogling."); + IUIAutomationElement selectIconsLink = rootElement.FindFirst(TreeScope.TreeScope_Subtree, searchTaskBarIconsLink); + if (selectIconsLink == null) + { + Console.WriteLine("Error: No link to icon section found."); + return; + } + var invokeSelecIcons = (IUIAutomationInvokePattern)selectIconsLink.GetCurrentPattern(_invokePattern); + invokeSelecIcons.Invoke(); + + IUIAutomationCondition changedToSelectIconMode = + _automation.CreatePropertyCondition( + _propertyAutomationId, + "SystemSettings_Notifications_ShowIconsOnTaskbar_ToggleSwitch" + ); + + Console.WriteLine(":: Waiting for 'ms-settings' to be in icon toggling section."); + var markFound = rootElement.FindFirst(TreeScope.TreeScope_Subtree, changedToSelectIconMode); + while (markFound == null) + { + Thread.Sleep(1000); + markFound = rootElement.FindFirst(TreeScope.TreeScope_Subtree, changedToSelectIconMode); + } + + IUIAutomationCondition searchGPIIAppInIconListName = + _automation.CreatePropertyCondition( + _propertyName, + appToLock + ); + + IUIAutomationCondition searchGPIIAppInIconListClass = + _automation.CreatePropertyCondition( + _propertyClassName, + "ToggleSwitch" + ); + + var searchGPIITriggerButton = + _automation.CreateAndCondition(searchGPIIAppInIconListName, searchGPIIAppInIconListClass); + + Console.WriteLine(":: Finding GPII app icon switch."); + Thread.Sleep(100); + var gpiiIconLockButton = rootElement.FindFirst(TreeScope.TreeScope_Subtree, searchGPIITriggerButton); + if (gpiiIconLockButton == null) + { + Console.WriteLine("Error: No application with specified name found."); + Console.ReadKey(); + return; + } + var triggerLockButton = (IUIAutomationTogglePattern)gpiiIconLockButton.GetCurrentPattern(_tooglePattern); + Console.WriteLine(":: Toogling GPII app icon switch."); + triggerLockButton.Toggle(); + } + } +} diff --git a/setup/trayicon.cs b/setup/trayicon.cs index f97dd28..5613d45 100644 --- a/setup/trayicon.cs +++ b/setup/trayicon.cs @@ -28,17 +28,20 @@ namespace TrayIconApplication using System.Linq; using System.Threading; using System.Runtime.InteropServices; + using Microsoft.Win32; + using System.Collections.Generic; public class TrayIcon { static void Usage() { Console.WriteLine("\nShow or hide notification icons for app.exe"); - Console.WriteLine("Usage: {0} app.exe [-nowait] [-show|-hide]", Process.GetCurrentProcess().ProcessName); + Console.WriteLine("Usage: {0} [-show|-hide] [-nowait] app.exe Icon", Process.GetCurrentProcess().ProcessName); Console.WriteLine(" app.exe The executable name that provides the icon."); + Console.WriteLine(" Icon The icon name."); Console.WriteLine(" -show Keep the icon visible (default)."); Console.WriteLine(" -hide Hide the icon."); - Console.WriteLine(" -nowait Quit imediately if the icon hasn't been registered,"); + Console.WriteLine(" -nowait Quit immediately if the icon hasn't been registered,"); Console.WriteLine(" otherwise keep retrying for 5 minutes."); Console.Write("\nPress a key"); Console.ReadKey(); @@ -59,13 +62,12 @@ static void Main(string[] args) ShowWindow(GetConsoleWindow(), 0); } - string exe = args[0].ToLowerInvariant(); bool show = true; bool wait = true; - foreach (string arg in args.Skip(1)) + while (args[0][0] == '-') { - switch (arg.ToLowerInvariant()) + switch (args[0]) { case "-hide": show = false; @@ -74,41 +76,47 @@ static void Main(string[] args) show = true; break; case "-nowait": - wait = false; + show = true; break; default: Usage(); return; } + + args = args.Skip(1).ToArray(); + } + + if (args.Length < 2) + { + Usage(); + return; } + string exe = args[0].ToLowerInvariant(); + string iconName = string.Join(" ", args.Skip(1).ToArray()); + // Wait for the process to start. The icon needs to be shown in order to configure it. bool running = false; - int attempts = 60; - while (wait && !running) + int attempts = 100; + + bool? iconState = null; + do { - running = Process.GetProcesses().Any(p => + iconState = TrayIcon.SetPreference(exe, show, true); + + if (iconState == false && IsWindows10) { - try + // See if it worked + iconState = TrayIcon.SetPreference(exe, show, false); + if (iconState == false) { - return p.MainModule.FileName.ToLowerInvariant().Contains(exe); - } - catch (System.ComponentModel.Win32Exception) - { - return false; + TrayIconUIA.ToggleTrayIcon(iconName); + break; } - }); - - if (--attempts < 0) - { - break; } + Thread.Sleep(1000); + } while (iconState == null && --attempts > 0); - // Sleep even if running, to give the application a chance to show the icon. - Thread.Sleep(5000); - } - - TrayIcon.SetPreference(exe, show); } /// @@ -120,43 +128,60 @@ private static bool UseNew get { return Environment.OSVersion.Version >= new Version(6, 2); } } + private static bool IsWindows10 + { + get + { + return Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion") + .GetValue("ProductName").ToString().Contains("Windows 10"); + } + } + /// /// Sets the visibility preference of an item. /// /// The executable name. /// true to always show the icon. - /// true if an icon by a matching executable was found. - public static bool SetPreference(string exeName, bool alwaysShow) + /// true to apply the setting. + /// + /// true a matching icon was already in the desired state, false if not, or null if there is no icon. + /// + public static bool? SetPreference(string exeName, bool alwaysShow, bool update) { - bool found = false; + bool? result = null; CTrayNotify trayNotify = null; + INotificationCB callback = null; + Thread t = null; try { + uint newValue = (bool)alwaysShow ? NOTIFYITEM.AlwaysShow : NOTIFYITEM.Hide; trayNotify = new CTrayNotify(); - - INotificationCB callback = new NotificationCallback(item => + + callback = new NotificationCallback(item => { - Console.WriteLine(item.pszExeName + "=" + item.dwUserPref + ": " + item.guidItem.ToString()); + Console.WriteLine(item.pszExeName + "=" + item.dwUserPref); if (item.pszExeName.IndexOf(exeName, StringComparison.OrdinalIgnoreCase) >= 0) { NOTIFYITEM_OUT itemOut = new NOTIFYITEM_OUT(item); - uint newValue = (bool)alwaysShow ? NOTIFYITEM.AlwaysShow : NOTIFYITEM.Hide; - found = true; - Console.WriteLine("Updating {0}", item.pszExeName); - itemOut.dwUserPref = newValue; + bool alreadySet = newValue == item.dwUserPref; + result = alreadySet; - if (TrayIcon.UseNew) - { - ((ITrayNotifyNew)trayNotify).SetPreference(itemOut); - } - else + if (!alreadySet && update) { - ((ITrayNotifyOld)trayNotify).SetPreference(itemOut); + itemOut.dwUserPref = newValue; + + if (TrayIcon.UseNew) + { + ((ITrayNotifyNew)trayNotify).SetPreference(itemOut); + } + else + { + ((ITrayNotifyOld)trayNotify).SetPreference(itemOut); + } } } }); - if (TrayIcon.UseNew) { // Windows 8+ @@ -175,12 +200,11 @@ public static bool SetPreference(string exeName, bool alwaysShow) { if (trayNotify != null) { - Marshal.ReleaseComObject(trayNotify); - trayNotify = null; + //Marshal.ReleaseComObject(trayNotify); + //trayNotify = null; } } - - return found; + return result; } /// @@ -230,6 +254,7 @@ public NOTIFYITEM_OUT(NOTIFYITEM notifyItem) this.dwUserPref = notifyItem.dwUserPref; this.uID = notifyItem.uID; this.guidItem = notifyItem.guidItem; + //this.guidItem = IntPtr.Zero; this.uID2 = notifyItem.uID2; } }; From f0ad8b08395176f0ee23bc0d38aa3ff599dc4ba2 Mon Sep 17 00:00:00 2001 From: ste Date: Thu, 25 Jan 2018 16:30:37 +0000 Subject: [PATCH 4/4] GPII-2554: Waiting for GPII to start, before locking the icon. --- setup/trayicon.cs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/setup/trayicon.cs b/setup/trayicon.cs index 5613d45..c5ea815 100644 --- a/setup/trayicon.cs +++ b/setup/trayicon.cs @@ -77,6 +77,7 @@ static void Main(string[] args) break; case "-nowait": show = true; + wait = false; break; default: Usage(); @@ -98,6 +99,28 @@ static void Main(string[] args) // Wait for the process to start. The icon needs to be shown in order to configure it. bool running = false; int attempts = 100; + while (wait && !running) + { + running = Process.GetProcesses().Any(p => + { + try + { + return p.MainModule.FileName.ToLowerInvariant().Contains(exe); + } + catch (System.ComponentModel.Win32Exception) + { + return false; + } + }); + + if (--attempts < 0) + { + break; + } + + // Sleep even if running, to give the application a chance to show the icon. + Thread.Sleep(1000); + } bool? iconState = null; do @@ -114,7 +137,7 @@ static void Main(string[] args) break; } } - Thread.Sleep(1000); + Thread.Sleep(3000); } while (iconState == null && --attempts > 0); } @@ -156,7 +179,7 @@ private static bool IsWindows10 { uint newValue = (bool)alwaysShow ? NOTIFYITEM.AlwaysShow : NOTIFYITEM.Hide; trayNotify = new CTrayNotify(); - + callback = new NotificationCallback(item => { Console.WriteLine(item.pszExeName + "=" + item.dwUserPref);