diff --git a/Directory.Packages.props b/Directory.Packages.props
index 4a488adfdb29..778f17195c87 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -68,14 +68,14 @@
-
+
-
+
@@ -153,4 +153,4 @@
-
+
\ No newline at end of file
diff --git a/src/modules/poweraccent/PowerAccent.Core/NativeMethods.txt b/src/modules/poweraccent/PowerAccent.Core/NativeMethods.txt
index 6aeae331c34e..9de1fe83ca1f 100644
--- a/src/modules/poweraccent/PowerAccent.Core/NativeMethods.txt
+++ b/src/modules/poweraccent/PowerAccent.Core/NativeMethods.txt
@@ -1,6 +1,7 @@
-GetDpiForWindow
GetGUIThreadInfo
GetKeyState
GetMonitorInfo
MonitorFromWindow
-SendInput
\ No newline at end of file
+SendInput
+GetAsyncKeyState
+GetDpiForMonitor
\ No newline at end of file
diff --git a/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs b/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs
index 4811118adbd1..e21ee4b600c3 100644
--- a/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs
+++ b/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs
@@ -5,7 +5,6 @@
using System.Globalization;
using System.Text;
using System.Unicode;
-using System.Windows;
using ManagedCommon;
using PowerAccent.Core.Services;
@@ -27,6 +26,7 @@ public partial class PowerAccent : IDisposable
private string[] _characterDescriptions = Array.Empty();
private int _selectedIndex = -1;
private bool _showUnicodeDescription;
+ private bool _initialShiftState; // Was shift held down when the toolbar was summoned?
public LetterKey[] LetterKeysShowingDescription => _letterKeysShowingDescription;
@@ -95,6 +95,7 @@ private void SetEvents()
private void ShowToolbar(LetterKey letterKey)
{
+ _initialShiftState = WindowsFunctions.IsShiftState();
_visible = true;
_characters = GetCharacters(letterKey);
@@ -240,21 +241,30 @@ private void SendInputAndHideToolbar(InputType inputType)
private void ProcessNextChar(TriggerKey triggerKey, bool shiftPressed)
{
+ // Use an async hardware check as a fallback in case the keyboard hook misses a
+ // quick Shift press. If the popup was opened while holding Shift (e.g., typing a
+ // capital letter), ignore the hardware check so we don't accidentally trigger a
+ // backwards navigation.
+ bool isHardwareShiftPressed = WindowsFunctions.IsShiftState() && !_initialShiftState;
+ shiftPressed = shiftPressed || isHardwareShiftPressed;
+
if (_visible && _selectedIndex == -1)
{
- if (triggerKey == TriggerKey.Left)
+ if (triggerKey == TriggerKey.Space)
{
- _selectedIndex = (_characters.Length / 2) - 1;
+ _selectedIndex = shiftPressed ? (_characters.Length - 1) : 0;
}
-
- if (triggerKey == TriggerKey.Right)
+ else if (_settingService.StartSelectionFromTheLeft)
{
- _selectedIndex = _characters.Length / 2;
+ _selectedIndex = 0;
}
-
- if (triggerKey == TriggerKey.Space || _settingService.StartSelectionFromTheLeft)
+ else if (triggerKey == TriggerKey.Left)
{
- _selectedIndex = 0;
+ _selectedIndex = (_characters.Length / 2) - 1;
+ }
+ else if (triggerKey == TriggerKey.Right)
+ {
+ _selectedIndex = _characters.Length / 2;
}
if (_selectedIndex < 0)
@@ -321,22 +331,47 @@ private void ProcessNextChar(TriggerKey triggerKey, bool shiftPressed)
OnSelectCharacter?.Invoke(_selectedIndex, _characters[_selectedIndex]);
}
+ ///
+ /// Calculates the coordinates at which a window of the specified size should be
+ /// displayed, based on the current display settings and user preferences.
+ ///
+ /// The calculated coordinates take into account the active display's
+ /// location, size, DPI, and the user's configured position preferences.
+ /// The size of the window for which to calculate display
+ /// coordinates.
+ /// A point representing the top-left coordinates where the window should be
+ /// positioned on the active display, in physical/raw coordinates suitable for Win32
+ /// APIs like SetWindowPos.
public Point GetDisplayCoordinates(Size window)
{
(Point Location, Size Size, double Dpi) activeDisplay = WindowsFunctions.GetActiveDisplay();
Rect screen = new(activeDisplay.Location, activeDisplay.Size);
Position position = _settingService.Position;
- /* Debug.WriteLine("Dpi: " + activeDisplay.Dpi); */
-
- return Calculation.GetRawCoordinatesFromPosition(position, screen, window, activeDisplay.Dpi) / activeDisplay.Dpi;
+ return Calculation.GetRawCoordinatesFromPosition(position, screen, window, activeDisplay.Dpi);
}
+ ///
+ /// Gets the maximum width for the toolbar display based on the active screen
+ /// dimensions.
+ ///
+ /// The maximum width in logical pixels, accounting for screen padding.
+ ///
public double GetDisplayMaxWidth()
{
- return WindowsFunctions.GetActiveDisplay().Size.Width - ScreenMinPadding;
+ // Note: activeDisplay.Size.Width is in raw physical pixels.
+ // We divide by DPI to convert to WPF logical pixels (Device-Independent Pixels),
+ // because ScreenMinPadding is a logical pixel value and WPF MaxWidth expects
+ // logical pixels.
+ var activeDisplay = WindowsFunctions.GetActiveDisplay();
+ return (activeDisplay.Size.Width / activeDisplay.Dpi) - ScreenMinPadding;
}
+ ///
+ /// Gets the user-configured position preference for the toolbar display. For example
+ /// .
+ ///
+ /// The preferred location for the toolbar.
public Position GetToolbarPosition()
{
return _settingService.Position;
diff --git a/src/modules/poweraccent/PowerAccent.Core/Tools/WindowsFunctions.cs b/src/modules/poweraccent/PowerAccent.Core/Tools/WindowsFunctions.cs
index a790d27fb593..c4f479c4d2b3 100644
--- a/src/modules/poweraccent/PowerAccent.Core/Tools/WindowsFunctions.cs
+++ b/src/modules/poweraccent/PowerAccent.Core/Tools/WindowsFunctions.cs
@@ -6,6 +6,7 @@
using Windows.Win32;
using Windows.Win32.Graphics.Gdi;
+using Windows.Win32.UI.HiDpi;
using Windows.Win32.UI.Input.KeyboardAndMouse;
using Windows.Win32.UI.WindowsAndMessaging;
@@ -51,36 +52,36 @@ public static void Insert(string s, bool back = false)
Thread.Sleep(1); // Some apps, like Terminal, need a little wait to process the sent backspace or they'll ignore it.
}
- foreach (char c in s)
+ if (s.Length > 0)
{
- // Letter
- var inputsInsert = new INPUT[]
+ var inputsInsert = new INPUT[s.Length * 2];
+ for (int i = 0; i < s.Length; i++)
{
- new INPUT
+ inputsInsert[i * 2] = new INPUT
{
type = INPUT_TYPE.INPUT_KEYBOARD,
Anonymous = new INPUT._Anonymous_e__Union
{
ki = new KEYBDINPUT
{
- wScan = c,
+ wScan = s[i],
dwFlags = KEYBD_EVENT_FLAGS.KEYEVENTF_UNICODE,
},
},
- },
- new INPUT
+ };
+ inputsInsert[(i * 2) + 1] = new INPUT
{
type = INPUT_TYPE.INPUT_KEYBOARD,
Anonymous = new INPUT._Anonymous_e__Union
{
ki = new KEYBDINPUT
{
- wScan = c,
+ wScan = s[i],
dwFlags = KEYBD_EVENT_FLAGS.KEYEVENTF_UNICODE | KEYBD_EVENT_FLAGS.KEYEVENTF_KEYUP,
},
},
- },
- };
+ };
+ }
_ = PInvoke.SendInput(inputsInsert, Marshal.SizeOf());
}
@@ -98,7 +99,13 @@ public static (Point Location, Size Size, double Dpi) GetActiveDisplay()
monitorInfo.cbSize = (uint)Marshal.SizeOf(monitorInfo);
PInvoke.GetMonitorInfo(res, ref monitorInfo);
- double dpi = PInvoke.GetDpiForWindow(guiInfo.hwndActive) / 96d;
+ uint dpiRaw = 96; // Safe default
+ if (PInvoke.GetDpiForMonitor(res, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out uint dpiX, out _) == 0)
+ {
+ dpiRaw = dpiX;
+ }
+
+ double dpi = dpiRaw / 96d;
var location = new Point(monitorInfo.rcWork.left, monitorInfo.rcWork.top);
return (location, monitorInfo.rcWork.Size, dpi);
}
@@ -111,7 +118,7 @@ public static bool IsCapsLockState()
public static bool IsShiftState()
{
- var shift = PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_SHIFT);
+ var shift = PInvoke.GetAsyncKeyState((int)VIRTUAL_KEY.VK_SHIFT);
return shift < 0;
}
}
diff --git a/src/modules/poweraccent/PowerAccent.UI/NativeMethods.txt b/src/modules/poweraccent/PowerAccent.UI/NativeMethods.txt
new file mode 100644
index 000000000000..bff40be3a3b1
--- /dev/null
+++ b/src/modules/poweraccent/PowerAccent.UI/NativeMethods.txt
@@ -0,0 +1,2 @@
+SetWindowPos
+GetSystemMetrics
diff --git a/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj b/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj
index 30111fbdf7b0..b1ce71bd7ab7 100644
--- a/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj
+++ b/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj
@@ -25,6 +25,10 @@
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/src/modules/poweraccent/PowerAccent.UI/Selector.xaml b/src/modules/poweraccent/PowerAccent.UI/Selector.xaml
index 6345a89fbdc8..372b861db59e 100644
--- a/src/modules/poweraccent/PowerAccent.UI/Selector.xaml
+++ b/src/modules/poweraccent/PowerAccent.UI/Selector.xaml
@@ -14,6 +14,7 @@
ResizeMode="NoResize"
ShowInTaskbar="False"
SizeToContent="WidthAndHeight"
+ SizeChanged="Window_SizeChanged"
Visibility="Collapsed"
WindowBackdropType="None"
WindowStyle="None"
@@ -51,16 +52,19 @@
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Background="Transparent"
+ ScrollViewer.HorizontalScrollBarVisibility="Auto"
+ Focusable="False"
IsHitTestVisible="False">