diff --git a/PowerToys.sln b/PowerToys.sln index 804a9ac60e..f24422ffec 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -718,6 +718,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRename.FuzzingTest", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShortcutGuide.IndexYmlGenerator", "src\modules\ShortcutGuideV2\ShortcutGuide.IndexYmlGenerator\ShortcutGuide.IndexYmlGenerator\ShortcutGuide.IndexYmlGenerator.csproj", "{D640B00C-9149-4C4F-8B7D-E2B8B2590A0B}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShortcutGuide.CPPProject", "src\modules\ShortcutGuideV2\ShortcutGuide.CPPProject\ShortcutGuide.CPPProject.vcxproj", "{C992FD2C-83B8-4941-9FC1-09730068D8EC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -2632,6 +2634,14 @@ Global {D640B00C-9149-4C4F-8B7D-E2B8B2590A0B}.Release|ARM64.Build.0 = Release|ARM64 {D640B00C-9149-4C4F-8B7D-E2B8B2590A0B}.Release|x64.ActiveCfg = Release|x64 {D640B00C-9149-4C4F-8B7D-E2B8B2590A0B}.Release|x64.Build.0 = Release|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|ARM64.ActiveCfg = Debug|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|ARM64.Build.0 = Debug|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|x64.ActiveCfg = Debug|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|x64.Build.0 = Debug|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|ARM64.ActiveCfg = Release|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|ARM64.Build.0 = Release|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|x64.ActiveCfg = Release|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2908,6 +2918,7 @@ Global {5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2} = {3846508C-77EB-4034-A702-F8BB263C4F79} {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} {D640B00C-9149-4C4F-8B7D-E2B8B2590A0B} = {21275C57-1C20-4F07-8A56-21BFA8363BDB} + {C992FD2C-83B8-4941-9FC1-09730068D8EC} = {21275C57-1C20-4F07-8A56-21BFA8363BDB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj index 045be94f2b..4bb709f180 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj +++ b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj @@ -65,14 +65,12 @@ - - @@ -83,13 +81,9 @@ - - Create - - diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj.filters b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj.filters index 006a6196ef..af14da58f3 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj.filters +++ b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj.filters @@ -18,9 +18,6 @@ - - Header Files - Header Files @@ -51,9 +48,6 @@ Header Files - - Header Files - Header Files @@ -68,9 +62,6 @@ - - Source Files - Source Files @@ -101,9 +92,6 @@ Source Files - - Source Files - Source Files @@ -116,7 +104,79 @@ - + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + Resource Files diff --git a/src/modules/ShortcutGuide/ShortcutGuide/pch.h b/src/modules/ShortcutGuide/ShortcutGuide/pch.h index 9ec2bbcb41..9bff3b54c1 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/pch.h +++ b/src/modules/ShortcutGuide/ShortcutGuide/pch.h @@ -25,5 +25,4 @@ #include #include #include -#include -#include \ No newline at end of file +#include \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.cpp b/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.cpp index 687fc86566..4572f412b3 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.cpp +++ b/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.cpp @@ -1,116 +1,193 @@ #include "pch.h" #include "tasklist_positions.h" +#include +#include +#include +#include -void Tasklist::update() +extern "C" { - // Get HWND of the tasklist - auto tasklist_hwnd = FindWindowA("Shell_TrayWnd", nullptr); - if (!tasklist_hwnd) - return; - tasklist_hwnd = FindWindowExA(tasklist_hwnd, 0, "ReBarWindow32", nullptr); - if (!tasklist_hwnd) - return; - tasklist_hwnd = FindWindowExA(tasklist_hwnd, 0, "MSTaskSwWClass", nullptr); - if (!tasklist_hwnd) - return; - tasklist_hwnd = FindWindowExA(tasklist_hwnd, 0, "MSTaskListWClass", nullptr); - if (!tasklist_hwnd) - return; - if (!automation) - { - winrt::check_hresult(CoCreateInstance(CLSID_CUIAutomation, - nullptr, - CLSCTX_INPROC_SERVER, - IID_IUIAutomation, - automation.put_void())); - winrt::check_hresult(automation->CreateTrueCondition(true_condition.put())); - } - element = nullptr; - winrt::check_hresult(automation->ElementFromHandle(tasklist_hwnd, element.put())); -} + winrt::com_ptr automation; + winrt::com_ptr element; + winrt::com_ptr true_condition; -bool Tasklist::update_buttons(std::vector& buttons) -{ - if (!automation || !element) + // Helper to get the taskbar HWND for the monitor under the cursor + HWND GetTaskbarHwndForCursorMonitor(HMONITOR monitor) { - return false; - } - winrt::com_ptr elements; - if (element->FindAll(TreeScope_Children, true_condition.get(), elements.put()) < 0) - return false; - if (!elements) - return false; - int count; - if (elements->get_Length(&count) < 0) - return false; - winrt::com_ptr child; - std::vector found_buttons; - found_buttons.reserve(count); - for (int i = 0; i < count; ++i) - { - child = nullptr; - if (elements->GetElement(i, child.put()) < 0) - return false; - TasklistButton button; - if (VARIANT var_rect; child->GetCurrentPropertyValue(UIA_BoundingRectanglePropertyId, &var_rect) >= 0) + POINT pt; + if (!GetCursorPos(&pt)) + return nullptr; + + // Find the primary taskbar + HWND primaryTaskbar = FindWindowW(L"Shell_TrayWnd", nullptr); + if (primaryTaskbar) { - if (var_rect.vt == (VT_R8 | VT_ARRAY)) + MONITORINFO mi = { sizeof(mi) }; + if (GetWindowRect(primaryTaskbar, &mi.rcMonitor)) { - LONG pos; - double value; - pos = 0; - SafeArrayGetElement(var_rect.parray, &pos, &value); - button.x = static_cast(value); - pos = 1; - SafeArrayGetElement(var_rect.parray, &pos, &value); - button.y = static_cast(value); - pos = 2; - SafeArrayGetElement(var_rect.parray, &pos, &value); - button.width = static_cast(value); - pos = 3; - SafeArrayGetElement(var_rect.parray, &pos, &value); - button.height = static_cast(value); + HMONITOR primaryMonitor = MonitorFromRect(&mi.rcMonitor, MONITOR_DEFAULTTONEAREST); + if (primaryMonitor == monitor) + return primaryTaskbar; } - VariantClear(&var_rect); + } + + // Find the secondary taskbar(s) + HWND secondaryTaskbar = nullptr; + while ((secondaryTaskbar = FindWindowExW(nullptr, secondaryTaskbar, L"Shell_SecondaryTrayWnd", nullptr)) != nullptr) + { + MONITORINFO mi = { sizeof(mi) }; + RECT rc; + if (GetWindowRect(secondaryTaskbar, &rc)) + { + HMONITOR monitor = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST); + if (monitor == monitor) + return secondaryTaskbar; + } + } + + return nullptr; + } + + __declspec(dllexport) void update(HMONITOR monitor) + { + // Get HWND of the tasklist for the monitor under the cursor + auto taskbar_hwnd = GetTaskbarHwndForCursorMonitor(monitor); + if (!taskbar_hwnd) + return; + + wchar_t class_name[64] = {}; + GetClassNameW(taskbar_hwnd, class_name, 64); + + HWND tasklist_hwnd = nullptr; + + if (wcscmp(class_name, L"Shell_TrayWnd") == 0) + { + // Primary taskbar structure + tasklist_hwnd = FindWindowExW(taskbar_hwnd, 0, L"ReBarWindow32", nullptr); + if (!tasklist_hwnd) + return; + tasklist_hwnd = FindWindowExW(tasklist_hwnd, 0, L"MSTaskSwWClass", nullptr); + if (!tasklist_hwnd) + return; + tasklist_hwnd = FindWindowExW(tasklist_hwnd, 0, L"MSTaskListWClass", nullptr); + if (!tasklist_hwnd) + return; + } + else if (wcscmp(class_name, L"Shell_SecondaryTrayWnd") == 0) + { + // Secondary taskbar structure + HWND workerw = FindWindowExW(taskbar_hwnd, 0, L"WorkerW", nullptr); + if (!workerw) + return; + tasklist_hwnd = FindWindowExW(workerw, 0, L"MSTaskListWClass", nullptr); + if (!tasklist_hwnd) + return; } else + { + // Unknown taskbar type + return; + } + + if (!automation) + { + winrt::check_hresult(CoCreateInstance(CLSID_CUIAutomation, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IUIAutomation, + automation.put_void())); + winrt::check_hresult(automation->CreateTrueCondition(true_condition.put())); + } + element = nullptr; + winrt::check_hresult(automation->ElementFromHandle(tasklist_hwnd, element.put())); + } + + __declspec(dllexport) bool update_buttons(std::vector& buttons) + { + if (!automation || !element) { return false; } - if (BSTR automation_id; child->get_CurrentAutomationId(&automation_id) >= 0) + winrt::com_ptr elements; + if (element->FindAll(TreeScope_Children, true_condition.get(), elements.put()) < 0) + return false; + if (!elements) + return false; + int count; + if (elements->get_Length(&count) < 0) + return false; + winrt::com_ptr child; + std::vector found_buttons; + found_buttons.reserve(count); + for (int i = 0; i < count; ++i) { - button.name = automation_id; - SysFreeString(automation_id); + child = nullptr; + if (elements->GetElement(i, child.put()) < 0) + return false; + TasklistButton button; + if (VARIANT var_rect; child->GetCurrentPropertyValue(UIA_BoundingRectanglePropertyId, &var_rect) >= 0) + { + if (var_rect.vt == (VT_R8 | VT_ARRAY)) + { + LONG pos; + double value; + pos = 0; + SafeArrayGetElement(var_rect.parray, &pos, &value); + button.x = static_cast(value); + pos = 1; + SafeArrayGetElement(var_rect.parray, &pos, &value); + button.y = static_cast(value); + pos = 2; + SafeArrayGetElement(var_rect.parray, &pos, &value); + button.width = static_cast(value); + pos = 3; + SafeArrayGetElement(var_rect.parray, &pos, &value); + button.height = static_cast(value); + } + VariantClear(&var_rect); + } + else + { + return false; + } + if (BSTR automation_id; child->get_CurrentAutomationId(&automation_id) >= 0) + { + wcsncpy_s(button.name, automation_id, _countof(button.name)); + SysFreeString(automation_id); + } + found_buttons.push_back(button); } - found_buttons.push_back(button); + // assign keynums + buttons.clear(); + for (auto& button : found_buttons) + { + if (buttons.empty()) + { + button.keynum = 1; + buttons.push_back(std::move(button)); + } + else + { + if (button.x < buttons.back().x || button.y < buttons.back().y) // skip 2nd row + break; + if (wcsncmp(button.name, buttons.back().name, _countof(button.name)) == 0) + continue; // skip buttons from the same app + button.keynum = buttons.back().keynum + 1; + buttons.push_back(std::move(button)); + if (buttons.back().keynum == 10) + break; // no more than 10 buttons + } + } + return true; } - // assign keynums - buttons.clear(); - for (auto& button : found_buttons) + + __declspec(dllexport) TasklistButton* get_buttons(HMONITOR monitor, int* size) { - if (buttons.empty()) - { - button.keynum = 1; - buttons.push_back(std::move(button)); - } - else - { - if (button.x < buttons.back().x || button.y < buttons.back().y) // skip 2nd row - break; - if (button.name == buttons.back().name) - continue; // skip buttons from the same app - button.keynum = buttons.back().keynum + 1; - buttons.push_back(std::move(button)); - if (buttons.back().keynum == 10) - break; // no more than 10 buttons - } + update(monitor); + static std::vector buttons; + update_buttons(buttons); + *size = static_cast(buttons.size()); + return buttons.data(); } - return true; } -std::vector Tasklist::get_buttons() -{ - std::vector buttons; - update_buttons(buttons); - return buttons; -} \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.h b/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.h index 4ac5bed8a9..cf71ee838f 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.h +++ b/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.h @@ -7,23 +7,10 @@ struct TasklistButton { - std::wstring name; - long x{}; - long y{}; - long width{}; - long height{}; - long keynum{}; -}; - -class Tasklist -{ -public: - void update(); - std::vector get_buttons(); - bool update_buttons(std::vector& buttons); - -private: - winrt::com_ptr automation; - winrt::com_ptr element; - winrt::com_ptr true_condition; + wchar_t name[256]; + int x; + int y; + int width; + int height; + int keynum; }; diff --git a/src/modules/ShortcutGuideV2/ShortcutGuide.CPPProject/ShortcutGuide.CPPProject.vcxproj b/src/modules/ShortcutGuideV2/ShortcutGuide.CPPProject/ShortcutGuide.CPPProject.vcxproj new file mode 100644 index 0000000000..dcfe871e5b --- /dev/null +++ b/src/modules/ShortcutGuideV2/ShortcutGuide.CPPProject/ShortcutGuide.CPPProject.vcxproj @@ -0,0 +1,149 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {c992fd2c-83b8-4941-9fc1-09730068d8ec} + ShortcutGuideCPPProject + 10.0.22621.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\ + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Create + Create + Create + Create + + + + + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuideV2/ShortcutGuide.CPPProject/ShortcutGuide.CPPProject.vcxproj.filters b/src/modules/ShortcutGuideV2/ShortcutGuide.CPPProject/ShortcutGuide.CPPProject.vcxproj.filters new file mode 100644 index 0000000000..da95b2b982 --- /dev/null +++ b/src/modules/ShortcutGuideV2/ShortcutGuide.CPPProject/ShortcutGuide.CPPProject.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Shell.en-US.yml b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Shell.en-US.yml index cf7aa4b240..0e3913b0c5 100644 --- a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Shell.en-US.yml +++ b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Shell.en-US.yml @@ -12,6 +12,21 @@ Shortcuts: Alt: true Keys: - F4 + - Name: Test shortcut + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - K + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - C + - Name: Open shutdown box Description: When no windows are open Shortcut: @@ -29,12 +44,6 @@ Shortcuts: Alt: true Keys: - Esc - - Win: false - Ctrl: false - Shift: false - Alt: true - Keys: - - Esc - Name: Reveal typed password Description: On sign-in screen Shortcut: diff --git a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/DisplayHelper.cs b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/DisplayHelper.cs index 5df8283b8f..a41d0a9b1b 100644 --- a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/DisplayHelper.cs +++ b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/DisplayHelper.cs @@ -19,6 +19,7 @@ namespace ShortcutGuide public static Rect GetWorkAreaForDisplayWithWindow(IntPtr hwnd) { foundMonitorIndex = -1; + monitorIndex = 0; var monitor = MonitorFromWindow(hwnd, (int)MonitorFromWindowDwFlags.MONITOR_DEFAULTTONEAREST); EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnumProc, new LPARAM(monitor)); return MonitorInfo.GetDisplayMonitors()[foundMonitorIndex].RectWork; diff --git a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/Helpers/TaskListPositions.cs b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/Helpers/TaskListPositions.cs deleted file mode 100644 index b408d8e3e2..0000000000 --- a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/Helpers/TaskListPositions.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; - -namespace ShortcutGuide.Helpers -{ - internal sealed class TaskListPositions - { - public static List GetTaskbarIconPositions() - { - throw new NotImplementedException(); - } - - [StructLayout(LayoutKind.Sequential)] - public struct RECT - { - public int Left; - public int Top; - public int Right; - public int Bottom; - } - } -} diff --git a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/NativeMethods.cs b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/NativeMethods.cs index e16b8ffb71..466551d453 100644 --- a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/NativeMethods.cs +++ b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/NativeMethods.cs @@ -26,10 +26,10 @@ internal static partial class NativeMethods internal static partial int SetWindowLongW(IntPtr hWnd, int nIndex, int dwNewLong); [LibraryImport("user32.dll", StringMarshalling = StringMarshalling.Utf16)] - internal static partial int FindWindowA(in string lpClassName, in string? lpWindowName); + internal static partial IntPtr FindWindowA(in string lpClassName, in string? lpWindowName); [LibraryImport("user32.dll", StringMarshalling = StringMarshalling.Utf16)] - internal static partial int FindWindowExA(int hwndParent, int hwndChildAfter, in string lpClassName, in string? lpWindowName); + public static partial IntPtr FindWindowExA(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); [LibraryImport("User32.dll")] internal static partial IntPtr MonitorFromWindow(int hwnd, int dwFlags); @@ -47,6 +47,14 @@ internal static partial class NativeMethods [DllImport("user32.dll", SetLastError = true)] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); + [DllImport("ole32.dll", SetLastError = true)] + public static extern uint CoCreateInstance( + in Guid rclsid, + IntPtr pUnkOuter, + uint dwClsContext, + in Guid riid, + out IntPtr ppv); + public struct POINT { public int X; diff --git a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj index cba7566b17..911ac0502f 100644 --- a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj +++ b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj @@ -60,6 +60,17 @@ + + + tlbimp + 0 + 1 + 944de083-8fb8-45cf-bcb7-c477acb2f897 + 0 + false + true + + @@ -95,6 +106,7 @@ + diff --git a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml.cs b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml.cs index c0440cb735..a1561262a0 100644 --- a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml.cs +++ b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml.cs @@ -28,6 +28,8 @@ namespace ShortcutGuide /// public sealed partial class MainWindow : WindowEx { + public static nint WindowHwnd { get; set; } + private AppWindow _appWindow; private string[] _currentApplicationIds; @@ -41,6 +43,7 @@ namespace ShortcutGuide Title = Resource.ResourceManager.GetString("Title", CultureInfo.InvariantCulture)!; var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this); + WindowHwnd = hwnd; WindowId windowId = Win32Interop.GetWindowIdFromWindow(hwnd); _appWindow = AppWindow.GetFromWindowId(windowId); #if !DEBUG @@ -75,7 +78,9 @@ namespace ShortcutGuide { if (e.WindowActivationState == WindowActivationState.Deactivated) { +#if !DEBUG Environment.Exit(0); +#endif } if (!_setPosition) @@ -96,6 +101,16 @@ namespace ShortcutGuide // Move top of the window to the center of the monitor _appWindow.Move(new PointInt32((int)monitorRect.X, (int)(monitorRect.Y + (int)(monitorRect.Height / 2)))); _setPosition = true; + AppWindow.Changed += (_, a) => + { + if (a.DidPresenterChange) + { + Rect monitorRect = DisplayHelper.GetWorkAreaForDisplayWithWindow(hwnd); + float dpiScale = DpiHelper.GetDPIScaleForWindow((int)hwnd); + this.SetWindowSize(monitorRect.Width / dpiScale, monitorRect.Height / dpiScale / 2); + _appWindow.Move(new PointInt32((int)monitorRect.X, (int)(monitorRect.Y + (int)(monitorRect.Height / 2)))); + } + }; } if (WindowSelector.Items.Count == 0) diff --git a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutView.xaml b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutView.xaml index 803178f318..971809d29b 100644 --- a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutView.xaml +++ b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutView.xaml @@ -38,6 +38,7 @@ + @@ -78,5 +79,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutView.xaml.cs b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutView.xaml.cs index ffbd507880..7ad99dcc3f 100644 --- a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutView.xaml.cs +++ b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutView.xaml.cs @@ -7,13 +7,16 @@ using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Text.Json; using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.UI; +using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Media; -using ShortcutGuide; +using Microsoft.UI.Xaml.Media.Animation; +using Microsoft.UI.Xaml.Shapes; using ShortcutGuide.Models; using Windows.Foundation; @@ -21,6 +24,8 @@ namespace ShortcutGuide { public sealed partial class ShortcutView : Page, INotifyPropertyChanged { + private readonly DispatcherTimer _taskbarUpdateTimer = new() { Interval = TimeSpan.FromMilliseconds(500) }; + private bool _showTaskbarShortcuts = true; private ShortcutFile shortcutList = ManifestInterpreter.GetShortcutsOfApplication(ShortcutPageParameters.CurrentPageName); public ShortcutView() @@ -41,7 +46,7 @@ namespace ShortcutGuide switch (category.SectionName) { case string name when name.StartsWith("", StringComparison.Ordinal): - // Todo: Implement GetTaskbarIconPositions + _showTaskbarShortcuts = true; break; case string name when name.StartsWith('<') && name.EndsWith('>'): break; @@ -69,6 +74,13 @@ namespace ShortcutGuide ShortcutPageParameters.PinnedShortcuts.Add(ShortcutPageParameters.CurrentPageName, []); } + if (_showTaskbarShortcuts) + { + TaskbarIndicators.Visibility = Visibility.Visible; + _taskbarUpdateTimer.Tick += UpdateTaskbarIndicators; + _taskbarUpdateTimer.Start(); + } + OpenOverview(); } catch (Exception) @@ -79,6 +91,59 @@ namespace ShortcutGuide } } + private void UpdateTaskbarIndicators(object? sender, object? e) + { + var buttons = TasklistPositions.GetButtons(); + Canvas[] canvases = [ + TaskbarIndicator1, + TaskbarIndicator2, + TaskbarIndicator3, + TaskbarIndicator4, + TaskbarIndicator5, + TaskbarIndicator6, + TaskbarIndicator7, + TaskbarIndicator8, + TaskbarIndicator9, + TaskbarIndicator10, + ]; + + for (int i = 0; i < canvases.Length; i++) + { + if (i < buttons.Length) + { + canvases[i].Visibility = Visibility.Visible; + Rect workArea = DisplayHelper.GetWorkAreaForDisplayWithWindow(MainWindow.WindowHwnd); + DoubleAnimation animation = new DoubleAnimation + { + To = (buttons[i].X - workArea.Left) / DpiHelper.GetDPIScaleForWindow(MainWindow.WindowHwnd.ToInt32()), + Duration = TimeSpan.FromMilliseconds(500), + }; + + // Create the storyboard + Storyboard storyboard = new Storyboard(); + storyboard.Children.Add(animation); + + // Set the target and property + Storyboard.SetTarget(animation, canvases[i]); + Storyboard.SetTargetProperty(animation, "(Canvas.Left)"); + + // Start the animation + storyboard.Begin(); + + canvases[i].Width = buttons[i].Width / DpiHelper.GetDPIScaleForWindow(MainWindow.WindowHwnd.ToInt32()); + canvases[i].Height = buttons[i].Height / DpiHelper.GetDPIScaleForWindow(MainWindow.WindowHwnd.ToInt32()); + ((Rectangle)canvases[i].Children[0]).Width = buttons[i].Width / DpiHelper.GetDPIScaleForWindow(MainWindow.WindowHwnd.ToInt32()); + ((Rectangle)canvases[i].Children[0]).Height = buttons[i].Height / DpiHelper.GetDPIScaleForWindow(MainWindow.WindowHwnd.ToInt32()); + ((TextBlock)canvases[i].Children[1]).Width = buttons[i].Width / DpiHelper.GetDPIScaleForWindow(MainWindow.WindowHwnd.ToInt32()); + ((TextBlock)canvases[i].Children[1]).Height = buttons[i].Height / DpiHelper.GetDPIScaleForWindow(MainWindow.WindowHwnd.ToInt32()); + } + else + { + canvases[i].Visibility = Visibility.Collapsed; + } + } + } + private void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); diff --git a/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/TasklistPositions.cs b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/TasklistPositions.cs new file mode 100644 index 0000000000..e924348802 --- /dev/null +++ b/src/modules/ShortcutGuideV2/ShortcutGuide.Ui/TasklistPositions.cs @@ -0,0 +1,188 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using UIAutomationClient; + +namespace ShortcutGuide +{ + internal sealed partial class TasklistPositions + { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct TasklistButton + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] + public string Name; + + public int X; + + public int Y; + + public int Width; + + public int Height; + + public int Keynum; + } + + /* + private IUIAutomation? _automation; + private IUIAutomationElement? _element; + private IUIAutomationCondition? _trueCondition; + public void Update() + { + + // Get HWND of the tasklist + var tasklistHwnd = NativeMethods.FindWindowA("Shell_TrayWnd", null); + if (tasklistHwnd == IntPtr.Zero) + { + return; + } + + tasklistHwnd = NativeMethods.FindWindowExA(tasklistHwnd, IntPtr.Zero, "ReBarWindow32", string.Empty); + if (tasklistHwnd == IntPtr.Zero) + { + return; + } + + tasklistHwnd = NativeMethods.FindWindowExA(tasklistHwnd, IntPtr.Zero, "MSTaskSwWClass", string.Empty); + if (tasklistHwnd == IntPtr.Zero) + { + return; + } + + tasklistHwnd = NativeMethods.FindWindowExA(tasklistHwnd, IntPtr.Zero, "MSTaskListWClass", string.Empty); + if (tasklistHwnd == IntPtr.Zero) + { + return; + } + + if (_automation == null) + { + _automation = new CUIAutomation(); + _trueCondition = _automation.CreateTrueCondition(); + } + + _element = null; + _element = _automation.ElementFromHandle(tasklistHwnd); + } + + public bool UpdateButtons(List buttons) + { + if (_automation == null || _element == null) + { + return false; + } + + IUIAutomationElementArray elements = _element.FindAll(TreeScope.TreeScope_Children, _trueCondition); + if (elements == null) + { + return false; + } + + int count = elements.Length; + var foundButtons = new List(count); + + for (int i = 0; i < count; ++i) + { + var child = elements.GetElement(i); + var button = default(TasklistButton); + + object rectObj = child.GetCurrentPropertyValue(30001); + if (rectObj is double[] arr && arr.Length == 4) + { + button.X = (long)arr[0]; + button.Y = (long)arr[1]; + button.Width = (long)arr[2]; + button.Height = (long)arr[3]; + } + else if (rectObj is Windows.Foundation.Rect wrect) + { + button.X = (long)wrect.X; + button.Y = (long)wrect.Y; + button.Width = (long)wrect.Width; + button.Height = (long)wrect.Height; + } + else + { + System.Diagnostics.Debug.WriteLine($"rectObj type: {rectObj?.GetType()} value: {rectObj}"); + continue; // Don't return false, just skip + } + + object nameObj = child.GetCurrentPropertyValue(30011); + button.Name = nameObj as string ?? string.Empty; + + foundButtons.Add(button); + } + + // assign keynums + buttons.Clear(); + foreach (var button in foundButtons) + { + if (buttons.Count == 0) + { + var b = button; + b.KeyNumber = 1; + buttons.Add(b); + } + else + { + var last = buttons[^1]; + if (button.X < last.X || button.Y < last.Y) + { + break; + } + + if (button.Name == last.Name) + { + continue; + } + + var b = button; + b.KeyNumber = last.KeyNumber + 1; + buttons.Add(b); + if (b.KeyNumber == 10) + { + break; + } + } + } + + return true; + }*/ + + [DllImport("ShortcutGuide.CPPProject.dll", EntryPoint = "get_buttons")] + public static extern IntPtr GetTasklistButtons(nint monitor, out int size); + + [LibraryImport("User32.dll")] + private static partial IntPtr MonitorFromWindow(nint hwnd, int dwFlags); + + public static TasklistButton[] GetButtons() + { + var monitor = MonitorFromWindow(MainWindow.WindowHwnd, 0); + IntPtr ptr = GetTasklistButtons(monitor, out int size); + if (ptr == IntPtr.Zero) + { + return []; + } + + if (size <= 0) + { + return []; + } + + TasklistButton[] buttons = new TasklistButton[size]; + IntPtr currentPtr = ptr; + for (int i = 0; i < size; i++) + { + buttons[i] = Marshal.PtrToStructure(currentPtr); + currentPtr += Marshal.SizeOf(); + } + + return buttons; + } + } +}