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 6df4505..c5ea815 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];
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,23 +76,70 @@ static void Main(string[] args)
show = true;
break;
case "-nowait":
+ show = true;
wait = false;
break;
default:
Usage();
return;
}
+
+ args = args.Skip(1).ToArray();
+ }
+
+ if (args.Length < 2)
+ {
+ Usage();
+ return;
}
- bool found = false;
+ 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 = 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
{
- found = TrayIcon.SetPreference(exe, show);
- if (wait && !found)
+ iconState = TrayIcon.SetPreference(exe, show, true);
+
+ if (iconState == false && IsWindows10)
{
- Thread.Sleep(5000);
+ // See if it worked
+ iconState = TrayIcon.SetPreference(exe, show, false);
+ if (iconState == false)
+ {
+ TrayIconUIA.ToggleTrayIcon(iconName);
+ break;
+ }
}
- } while (wait && !found);
+ Thread.Sleep(3000);
+ } while (iconState == null && --attempts > 0);
+
}
///
@@ -102,30 +151,47 @@ 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);
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)
+
+ bool alreadySet = newValue == item.dwUserPref;
+ result = alreadySet;
+
+ if (!alreadySet && update)
{
- Console.WriteLine("Updating {0}", item.pszExeName);
itemOut.dwUserPref = newValue;
if (TrayIcon.UseNew)
@@ -137,13 +203,8 @@ public static bool SetPreference(string exeName, bool alwaysShow)
((ITrayNotifyOld)trayNotify).SetPreference(itemOut);
}
}
- else
- {
- Console.WriteLine("No need to update {0}", item.pszExeName);
- }
}
});
-
if (TrayIcon.UseNew)
{
// Windows 8+
@@ -162,12 +223,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;
}
///
@@ -217,6 +277,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;
}
};