using XFESystem.Core;
using XFESystem.Desktop;
using XFESystem.Kernel;
using Xunit;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
namespace XFESystem.Desktop.Platform.Tests;
public sealed class NativeInputTests
{
[Theory]
[InlineData((byte)0x4F,"End")]
[InlineData((byte)0xCF,"End")]
[InlineData((byte)0x47,"Home")]
[InlineData((byte)0xC7,"Home")]
public void PinnedUsNavigationCodesUseDeclaredEndAndHomeMappings(byte scan,string expected)
{
// Cosmos publishes its pure managed layout implementation only under
// linux-x64. Load that exact pinned binary without loading guest-only
// hardware managers into the Windows test runner.
string packages=Environment.GetEnvironmentVariable("NUGET_PACKAGES")??Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),".nuget","packages");
string path=Path.Combine(packages,"cosmos.kernel.system","3.0.94","runtimes","linux-x64","lib","net10.0","Cosmos.Kernel.System.dll");
var assembly=System.Reflection.Assembly.LoadFrom(path);
var layout=Activator.CreateInstance(assembly.GetType("Cosmos.Kernel.System.Input.Layouts.USStandardLayout",true)!)!;
var convert=assembly.GetType("Cosmos.Kernel.System.Input.KeyboardLayout",true)!.GetMethod("ConvertScanCode",System.Reflection.BindingFlags.Instance|System.Reflection.BindingFlags.NonPublic)!;
var key=convert.Invoke(layout,[scan,false,false,false,false,false]);Assert.NotNull(key);
Assert.Equal(expected,key.GetType().GetProperty("Key")!.GetValue(key)!.ToString());
Assert.Equal('\0',key.GetType().GetProperty("KeyChar")!.GetValue(key));
}
private sealed class Canvas : IDesktopCanvas
{
public int Width => 1280;
public int Height => 800;
public void Fill(uint rgb, int x, int y, int width, int height) { }
public void Line(uint rgb, int x1, int y1, int x2, int y2) { }
public void Text(string text, uint rgb, int x, int y) { }
public void Present() { }
}
private static DesktopSession Session() => new(new Canvas(), new MemoryWorkspace());
private static DesktopKey? Translate(NativeDesktopInput input, DesktopSession session, byte code, bool up = false, bool lost = false)
{
var key = input.Translate(code, up, lost, session, out var character);
Assert.Equal('\0', character);
return key;
}
private static void Route(NativeDesktopInput input, DesktopSession session, byte code, bool up = false, bool lost = false)
{
var key = input.Translate(code, up, lost, session, out var character);
if (key is { } command) session.Key(command, character);
}
private static void Drain()
{
while (NativeKeyboardCapture.TryRead(out _, out _, out _)) { }
}
[Theory]
[InlineData((byte)0x5B)]
[InlineData((byte)0x5C)]
public void BareWindowsKeyOpensStartExactlyOnceAtRelease(byte windows)
{
var input = new NativeDesktopInput(); var desktop = Session();
Assert.Null(Translate(input, desktop, windows));
Assert.Null(Translate(input, desktop, windows));
Assert.Equal(DesktopKey.StartMenu, Translate(input, desktop, windows, up: true));
Assert.Null(Translate(input, desktop, windows, up: true));
}
[Fact]
public void IsolatedWindowsReleaseDoesNotOpenStartMenu()
{
var input = new NativeDesktopInput(); var desktop = Session();
Assert.Null(Translate(input, desktop, 0x5B, up: true));
Assert.Null(Translate(input, desktop, 0x5C, up: true));
}
[Fact]
public void WinEOpensFilesWithoutLeakingCharacterOrStartMenu()
{
var input = new NativeDesktopInput(); var desktop = Session();
var editor = desktop.Open(DesktopApplication.Editor);
Route(input, desktop, 0x5B); Route(input, desktop, 0x12);
Route(input, desktop, 0x12, up: true); Route(input, desktop, 0x5B, up: true);
Assert.Equal(DesktopApplication.Files, desktop.ActiveWindow!.Application);
Assert.Empty(editor.Content); Assert.False(desktop.IsStartMenuOpen);
Route(input, desktop, 0x5B); Route(input, desktop, 0x5B, up: true);
Assert.True(desktop.IsStartMenuOpen);
}
[Theory]
[InlineData((byte)0x20, DesktopKey.ShowDesktop)]
[InlineData((byte)0x4B, DesktopKey.SnapLeft)]
[InlineData((byte)0x4D, DesktopKey.SnapRight)]
[InlineData((byte)0x48, DesktopKey.Maximize)]
[InlineData((byte)0x50, DesktopKey.Restore)]
public void WindowsChordRoutesSpecificDesktopCommand(byte scan, DesktopKey expected)
{
var input = new NativeDesktopInput(); var desktop = Session();
Translate(input, desktop, 0x5B);
Assert.Equal(expected, Translate(input, desktop, scan));
Assert.Null(Translate(input, desktop, scan, up: true));
Assert.Null(Translate(input, desktop, 0x5B, up: true));
}
[Fact]
public void BothWindowsKeysReleaseProducesOneStartCommand()
{
var input = new NativeDesktopInput(); var desktop = Session();
Assert.Null(Translate(input, desktop, 0x5B)); Assert.Null(Translate(input, desktop, 0x5C));
Assert.Null(Translate(input, desktop, 0x5B, up: true));
Assert.Equal(DesktopKey.StartMenu, Translate(input, desktop, 0x5C, up: true));
}
[Fact]
public void WindowsModifierChordDoesNotTriggerBareWindowsMenu()
{
var input = new NativeDesktopInput(); var desktop = Session();
Translate(input, desktop, 0x5B); Translate(input, desktop, 0x2A);
Assert.Null(Translate(input, desktop, 0x5B, up: true));
Translate(input, desktop, 0x2A, up: true);
}
[Fact]
public void AltTabRemainsOneStableCycleUntilAltRelease()
{
var input = new NativeDesktopInput(); var desktop = Session();
var first = desktop.Open(DesktopApplication.Files); var second = desktop.Open(DesktopApplication.Editor);
var third = desktop.Open(DesktopApplication.Terminal);
Route(input, desktop, 0x38);
Route(input, desktop, 0x0F); Assert.Same(second, desktop.ActiveWindow);
Route(input, desktop, 0x0F, up: true); Assert.True(desktop.IsWindowSwitching);
Route(input, desktop, 0x0F); Assert.Same(first, desktop.ActiveWindow);
Route(input, desktop, 0x0F, up: true); Route(input, desktop, 0x0F); Assert.Same(third, desktop.ActiveWindow);
Route(input, desktop, 0x0F, up: true); Route(input, desktop, 0x38, up: true);
Assert.False(desktop.IsWindowSwitching); Assert.Empty(second.Content);
}
[Fact]
public void ShiftAltTabReversesAndWaitsForBothAltReleases()
{
var input = new NativeDesktopInput(); var desktop = Session();
desktop.Open(DesktopApplication.Files); var second = desktop.Open(DesktopApplication.Editor);
desktop.Open(DesktopApplication.Terminal);
Route(input, desktop, 0x38); Route(input, desktop, 0x60); Route(input, desktop, 0x2A);
Assert.Equal(DesktopKey.SwitchWindowBack, Translate(input, desktop, 0x0F));
Route(input, desktop, 0x0F); Route(input, desktop, 0x38, up: true);
Assert.True(desktop.IsWindowSwitching);
Route(input, desktop, 0x60, up: true); Assert.False(desktop.IsWindowSwitching);
Assert.Empty(second.Content);
}
[Fact]
public void CtrlSSavesGuestEditorWithoutTypingS()
{
var storage = new MemoryWorkspace(); var desktop = new DesktopSession(new Canvas(), storage);
var editor = desktop.Open(DesktopApplication.Editor); desktop.Key(DesktopKey.Character, 'X');
var input = new NativeDesktopInput(); Route(input, desktop, 0x1D); Route(input, desktop, 0x1F);
Route(input, desktop, 0x1F, up: true); Route(input, desktop, 0x1D, up: true);
Assert.False(editor.IsDirty); Assert.Equal("X", storage.ReadText(editor.Location));
}
[Fact]
public void CtrlSInFocusedWebEditorReachesDesktopSaveInsteadOfBrowserKeyHandler()
{
var storage=new MemoryWorkspace();var browser=new FocusedWebEditor();
var desktop=new DesktopSession(new Canvas(),storage,browser);
var editor=desktop.Open(DesktopApplication.Editor,SystemPaths.Combine(SystemPaths.Documents,"Hello.cs"));
browser.Model="public class SavedByDesktopShortcut { }";
Assert.True(desktop.HasBrowserContentFocus);
var input=new NativeDesktopInput();
Route(input,desktop,0x1D);Route(input,desktop,0x1F);
Route(input,desktop,0x1F,up:true);Route(input,desktop,0x1D,up:true);
Assert.Equal(browser.Model,storage.ReadText(editor.Location));
Assert.DoesNotContain(browser.Keys,key=>key.Key.Equals("s",StringComparison.OrdinalIgnoreCase));
Assert.Contains("Saved to RAM live",editor.Status,StringComparison.Ordinal);
}
private sealed class FocusedWebEditor : IBrowserService,IInteractiveBrowserFactory,IInteractiveBrowserSession
{
public string Model="";
public List<BrowserKeyboardInput> Keys=[];
public BrowserPage CurrentPage {get;private set;}=new("about:Start","Test","");
public long Revision=>0;
public double DocumentHeight=>0;
public IInteractiveBrowserSession CreatePageSession()=>this;
public BrowserPage Navigate(string address)=>CurrentPage=new(address,"Test","");
public BrowserPage NavigateEditor(string source,string path){Model=source;return CurrentPage=new(path,"Test",source);}
public string? ReadEditorText()=>Model;
public string ExecuteJavaScript(string source)=>"";
public string PreviewMarkdown(string markdown)=>markdown;
public void Paint(IDesktopCanvas canvas,DesktopBounds viewport,double scrollY){}
public bool Pointer(BrowserPointerInput input)=>false;
public bool Key(BrowserKeyboardInput input){Keys.Add(input);return false;}
public bool AdvanceTime(long monotonicMilliseconds)=>false;
}
[Fact]
public void AltF4PreservesUnsavedCloseGuard()
{
var input = new NativeDesktopInput(); var desktop = Session();
var editor = desktop.Open(DesktopApplication.Editor); desktop.Key(DesktopKey.Character, 'X');
Route(input, desktop, 0x38); Route(input, desktop, 0x3E); Route(input, desktop, 0x38, up: true);
Assert.Single(desktop.Windows); Assert.True(editor.IsDirty); Assert.Contains("Unsaved", editor.Status);
}
[Fact]
public void LossResetsModifiersEndsCycleAndRejectsStaleWindowsRelease()
{
var input = new NativeDesktopInput(); var desktop = Session();
desktop.Open(DesktopApplication.Files); desktop.Open(DesktopApplication.Editor);
Route(input, desktop, 0x38); Route(input, desktop, 0x0F); Route(input, desktop, 0x5B);
Assert.True(desktop.IsWindowSwitching);
Route(input, desktop, 0, up: true, lost: true);
Assert.False(desktop.IsWindowSwitching);
Assert.Null(Translate(input, desktop, 0x5B, up: true));
Route(input, desktop, 0x5B); Route(input, desktop, 0x5B, up: true); Assert.True(desktop.IsStartMenuOpen);
}
[Fact]
public void CapturePreservesOrderedMakeAndBreakReports()
{
Drain();
NativeKeyboardCapture.Observe(0x5B, false); NativeKeyboardCapture.Observe(0x12, false);
NativeKeyboardCapture.Observe(0x12, true); NativeKeyboardCapture.Observe(0x5B, true);
(byte Scan, bool Up)[] expected = [(0x5B, false), (0x12, false), (0x12, true), (0x5B, true)];
foreach (var item in expected)
{
Assert.True(NativeKeyboardCapture.TryRead(out var scan, out var up, out var lost));
Assert.Equal(item.Scan, scan); Assert.Equal(item.Up, up); Assert.False(lost);
}
Assert.False(NativeKeyboardCapture.TryRead(out _, out _, out _));
}
[Fact]
public void OverflowDiscardsOldModifierReportsAndSignalsOneReset()
{
Drain(); NativeKeyboardCapture.Observe(0x5B, false);
for (int i = 0; i < 600; i++) NativeKeyboardCapture.Observe(0x12, false);
NativeKeyboardCapture.Observe(0x5B, true);
Assert.True(NativeKeyboardCapture.TryRead(out var scan, out var released, out var lost));
Assert.Equal(0, scan); Assert.True(released); Assert.True(lost);
Assert.False(NativeKeyboardCapture.TryRead(out _, out _, out _));
NativeKeyboardCapture.Observe(0x5B, false); NativeKeyboardCapture.Observe(0x5B, true);
Assert.True(NativeKeyboardCapture.TryRead(out scan, out released, out lost));
Assert.Equal(0x5B, scan); Assert.False(released); Assert.False(lost);
Assert.True(NativeKeyboardCapture.TryRead(out scan, out released, out lost)); Assert.True(released);
}
[Fact]
public async Task ConcurrentWritersNeverPairScancodeWithAnotherWritersReleaseFlag()
{
Drain();
var writers = Enumerable.Range(1, 4).Select(index => Task.Run(() =>
{
for (int i = 0; i < 10000; i++) NativeKeyboardCapture.Observe((byte)index, (index & 1) != 0);
})).ToArray();
var completed = Task.WhenAll(writers);
while (!completed.IsCompleted)
{
while (NativeKeyboardCapture.TryRead(out var scan, out var released, out var lost))
{
if (lost) { Assert.Equal(0, scan); Assert.True(released); continue; }
Assert.InRange(scan, 1, 4); Assert.Equal((scan & 1) != 0, released);
}
await Task.Yield();
}
await completed;
while (NativeKeyboardCapture.TryRead(out var scan, out var released, out var lost))
if (!lost) { Assert.InRange(scan, 1, 4); Assert.Equal((scan & 1) != 0, released); }
NativeKeyboardCapture.Observe(0x7E, false);
Assert.True(NativeKeyboardCapture.TryRead(out var marker, out var markerReleased, out var markerLost));
Assert.Equal(0x7E, marker); Assert.False(markerReleased); Assert.False(markerLost);
}
}
using XFESystem.Core;
using XFESystem.Desktop;
using XFESystem.Kernel;
using Xunit;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
namespace XFESystem.Desktop.Platform.Tests;
public sealed class NativeInputTests
{
[Theory]
[InlineData((byte)0x4F,"End")]
[InlineData((byte)0xCF,"End")]
[InlineData((byte)0x47,"Home")]
[InlineData((byte)0xC7,"Home")]
public void PinnedUsNavigationCodesUseDeclaredEndAndHomeMappings(byte scan,string expected)
{
// Cosmos publishes its pure managed layout implementation only under
// linux-x64. Load that exact pinned binary without loading guest-only
// hardware managers into the Windows test runner.
string packages=Environment.GetEnvironmentVariable("NUGET_PACKAGES")??Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),".nuget","packages");
string path=Path.Combine(packages,"cosmos.kernel.system","3.0.94","runtimes","linux-x64","lib","net10.0","Cosmos.Kernel.System.dll");
var assembly=System.Reflection.Assembly.LoadFrom(path);
var layout=Activator.CreateInstance(assembly.GetType("Cosmos.Kernel.System.Input.Layouts.USStandardLayout",true)!)!;
var convert=assembly.GetType("Cosmos.Kernel.System.Input.KeyboardLayout",true)!.GetMethod("ConvertScanCode",System.Reflection.BindingFlags.Instance|System.Reflection.BindingFlags.NonPublic)!;
var key=convert.Invoke(layout,[scan,false,false,false,false,false]);Assert.NotNull(key);
Assert.Equal(expected,key.GetType().GetProperty("Key")!.GetValue(key)!.ToString());
Assert.Equal('\0',key.GetType().GetProperty("KeyChar")!.GetValue(key));
}
private sealed class Canvas : IDesktopCanvas
{
public int Width => 1280;
public int Height => 800;
public void Fill(uint rgb, int x, int y, int width, int height) { }
public void Line(uint rgb, int x1, int y1, int x2, int y2) { }
public void Text(string text, uint rgb, int x, int y) { }
public void Present() { }
}
private static DesktopSession Session() => new(new Canvas(), new MemoryWorkspace());
private static DesktopKey? Translate(NativeDesktopInput input, DesktopSession session, byte code, bool up = false, bool lost = false)
{
var key = input.Translate(code, up, lost, session, out var character);
Assert.Equal('\0', character);
return key;
}
private static void Route(NativeDesktopInput input, DesktopSession session, byte code, bool up = false, bool lost = false)
{
var key = input.Translate(code, up, lost, session, out var character);
if (key is { } command) session.Key(command, character);
}
private static void Drain()
{
while (NativeKeyboardCapture.TryRead(out _, out _, out _)) { }
}
[Theory]
[InlineData((byte)0x5B)]
[InlineData((byte)0x5C)]
public void BareWindowsKeyOpensStartExactlyOnceAtRelease(byte windows)
{
var input = new NativeDesktopInput(); var desktop = Session();
Assert.Null(Translate(input, desktop, windows));
Assert.Null(Translate(input, desktop, windows));
Assert.Equal(DesktopKey.StartMenu, Translate(input, desktop, windows, up: true));
Assert.Null(Translate(input, desktop, windows, up: true));
}
[Fact]
public void IsolatedWindowsReleaseDoesNotOpenStartMenu()
{
var input = new NativeDesktopInput(); var desktop = Session();
Assert.Null(Translate(input, desktop, 0x5B, up: true));
Assert.Null(Translate(input, desktop, 0x5C, up: true));
}
[Fact]
public void WinEOpensFilesWithoutLeakingCharacterOrStartMenu()
{
var input = new NativeDesktopInput(); var desktop = Session();
var editor = desktop.Open(DesktopApplication.Editor);
Route(input, desktop, 0x5B); Route(input, desktop, 0x12);
Route(input, desktop, 0x12, up: true); Route(input, desktop, 0x5B, up: true);
Assert.Equal(DesktopApplication.Files, desktop.ActiveWindow!.Application);
Assert.Empty(editor.Content); Assert.False(desktop.IsStartMenuOpen);
Route(input, desktop, 0x5B); Route(input, desktop, 0x5B, up: true);
Assert.True(desktop.IsStartMenuOpen);
}
[Theory]
[InlineData((byte)0x20, DesktopKey.ShowDesktop)]
[InlineData((byte)0x4B, DesktopKey.SnapLeft)]
[InlineData((byte)0x4D, DesktopKey.SnapRight)]
[InlineData((byte)0x48, DesktopKey.Maximize)]
[InlineData((byte)0x50, DesktopKey.Restore)]
public void WindowsChordRoutesSpecificDesktopCommand(byte scan, DesktopKey expected)
{
var input = new NativeDesktopInput(); var desktop = Session();
Translate(input, desktop, 0x5B);
Assert.Equal(expected, Translate(input, desktop, scan));
Assert.Null(Translate(input, desktop, scan, up: true));
Assert.Null(Translate(input, desktop, 0x5B, up: true));
}
[Fact]
public void BothWindowsKeysReleaseProducesOneStartCommand()
{
var input = new NativeDesktopInput(); var desktop = Session();
Assert.Null(Translate(input, desktop, 0x5B)); Assert.Null(Translate(input, desktop, 0x5C));
Assert.Null(Translate(input, desktop, 0x5B, up: true));
Assert.Equal(DesktopKey.StartMenu, Translate(input, desktop, 0x5C, up: true));
}
[Fact]
public void WindowsModifierChordDoesNotTriggerBareWindowsMenu()
{
var input = new NativeDesktopInput(); var desktop = Session();
Translate(input, desktop, 0x5B); Translate(input, desktop, 0x2A);
Assert.Null(Translate(input, desktop, 0x5B, up: true));
Translate(input, desktop, 0x2A, up: true);
}
[Fact]
public void AltTabRemainsOneStableCycleUntilAltRelease()
{
var input = new NativeDesktopInput(); var desktop = Session();
var first = desktop.Open(DesktopApplication.Files); var second = desktop.Open(DesktopApplication.Editor);
var third = desktop.Open(DesktopApplication.Terminal);
Route(input, desktop, 0x38);
Route(input, desktop, 0x0F); Assert.Same(second, desktop.ActiveWindow);
Route(input, desktop, 0x0F, up: true); Assert.True(desktop.IsWindowSwitching);
Route(input, desktop, 0x0F); Assert.Same(first, desktop.ActiveWindow);
Route(input, desktop, 0x0F, up: true); Route(input, desktop, 0x0F); Assert.Same(third, desktop.ActiveWindow);
Route(input, desktop, 0x0F, up: true); Route(input, desktop, 0x38, up: true);
Assert.False(desktop.IsWindowSwitching); Assert.Empty(second.Content);
}
[Fact]
public void ShiftAltTabReversesAndWaitsForBothAltReleases()
{
var input = new NativeDesktopInput(); var desktop = Session();
desktop.Open(DesktopApplication.Files); var second = desktop.Open(DesktopApplication.Editor);
desktop.Open(DesktopApplication.Terminal);
Route(input, desktop, 0x38); Route(input, desktop, 0x60); Route(input, desktop, 0x2A);
Assert.Equal(DesktopKey.SwitchWindowBack, Translate(input, desktop, 0x0F));
Route(input, desktop, 0x0F); Route(input, desktop, 0x38, up: true);
Assert.True(desktop.IsWindowSwitching);
Route(input, desktop, 0x60, up: true); Assert.False(desktop.IsWindowSwitching);
Assert.Empty(second.Content);
}
[Fact]
public void CtrlSSavesGuestEditorWithoutTypingS()
{
var storage = new MemoryWorkspace(); var desktop = new DesktopSession(new Canvas(), storage);
var editor = desktop.Open(DesktopApplication.Editor); desktop.Key(DesktopKey.Character, 'X');
var input = new NativeDesktopInput(); Route(input, desktop, 0x1D); Route(input, desktop, 0x1F);
Route(input, desktop, 0x1F, up: true); Route(input, desktop, 0x1D, up: true);
Assert.False(editor.IsDirty); Assert.Equal("X", storage.ReadText(editor.Location));
}
[Fact]
public void CtrlSInFocusedWebEditorReachesDesktopSaveInsteadOfBrowserKeyHandler()
{
var storage=new MemoryWorkspace();var browser=new FocusedWebEditor();
var desktop=new DesktopSession(new Canvas(),storage,browser);
var editor=desktop.Open(DesktopApplication.Editor,SystemPaths.Combine(SystemPaths.Documents,"Hello.cs"));
browser.Model="public class SavedByDesktopShortcut { }";
Assert.True(desktop.HasBrowserContentFocus);
var input=new NativeDesktopInput();
Route(input,desktop,0x1D);Route(input,desktop,0x1F);
Route(input,desktop,0x1F,up:true);Route(input,desktop,0x1D,up:true);
Assert.Equal(browser.Model,storage.ReadText(editor.Location));
Assert.DoesNotContain(browser.Keys,key=>key.Key.Equals("s",StringComparison.OrdinalIgnoreCase));
Assert.Contains("Saved to RAM live",editor.Status,StringComparison.Ordinal);
}
private sealed class FocusedWebEditor : IBrowserService,IInteractiveBrowserFactory,IInteractiveBrowserSession
{
public string Model="";
public List<BrowserKeyboardInput> Keys=[];
public BrowserPage CurrentPage {get;private set;}=new("about:Start","Test","");
public long Revision=>0;
public double DocumentHeight=>0;
public IInteractiveBrowserSession CreatePageSession()=>this;
public BrowserPage Navigate(string address)=>CurrentPage=new(address,"Test","");
public BrowserPage NavigateEditor(string source,string path){Model=source;return CurrentPage=new(path,"Test",source);}
public string? ReadEditorText()=>Model;
public string ExecuteJavaScript(string source)=>"";
public string PreviewMarkdown(string markdown)=>markdown;
public void Paint(IDesktopCanvas canvas,DesktopBounds viewport,double scrollY){}
public bool Pointer(BrowserPointerInput input)=>false;
public bool Key(BrowserKeyboardInput input){Keys.Add(input);return false;}
public bool AdvanceTime(long monotonicMilliseconds)=>false;
}
[Fact]
public void AltF4PreservesUnsavedCloseGuard()
{
var input = new NativeDesktopInput(); var desktop = Session();
var editor = desktop.Open(DesktopApplication.Editor); desktop.Key(DesktopKey.Character, 'X');
Route(input, desktop, 0x38); Route(input, desktop, 0x3E); Route(input, desktop, 0x38, up: true);
Assert.Single(desktop.Windows); Assert.True(editor.IsDirty); Assert.Contains("Unsaved", editor.Status);
}
[Fact]
public void LossResetsModifiersEndsCycleAndRejectsStaleWindowsRelease()
{
var input = new NativeDesktopInput(); var desktop = Session();
desktop.Open(DesktopApplication.Files); desktop.Open(DesktopApplication.Editor);
Route(input, desktop, 0x38); Route(input, desktop, 0x0F); Route(input, desktop, 0x5B);
Assert.True(desktop.IsWindowSwitching);
Route(input, desktop, 0, up: true, lost: true);
Assert.False(desktop.IsWindowSwitching);
Assert.Null(Translate(input, desktop, 0x5B, up: true));
Route(input, desktop, 0x5B); Route(input, desktop, 0x5B, up: true); Assert.True(desktop.IsStartMenuOpen);
}
[Fact]
public void CapturePreservesOrderedMakeAndBreakReports()
{
Drain();
NativeKeyboardCapture.Observe(0x5B, false); NativeKeyboardCapture.Observe(0x12, false);
NativeKeyboardCapture.Observe(0x12, true); NativeKeyboardCapture.Observe(0x5B, true);
(byte Scan, bool Up)[] expected = [(0x5B, false), (0x12, false), (0x12, true), (0x5B, true)];
foreach (var item in expected)
{
Assert.True(NativeKeyboardCapture.TryRead(out var scan, out var up, out var lost));
Assert.Equal(item.Scan, scan); Assert.Equal(item.Up, up); Assert.False(lost);
}
Assert.False(NativeKeyboardCapture.TryRead(out _, out _, out _));
}
[Fact]
public void OverflowDiscardsOldModifierReportsAndSignalsOneReset()
{
Drain(); NativeKeyboardCapture.Observe(0x5B, false);
for (int i = 0; i < 600; i++) NativeKeyboardCapture.Observe(0x12, false);
NativeKeyboardCapture.Observe(0x5B, true);
Assert.True(NativeKeyboardCapture.TryRead(out var scan, out var released, out var lost));
Assert.Equal(0, scan); Assert.True(released); Assert.True(lost);
Assert.False(NativeKeyboardCapture.TryRead(out _, out _, out _));
NativeKeyboardCapture.Observe(0x5B, false); NativeKeyboardCapture.Observe(0x5B, true);
Assert.True(NativeKeyboardCapture.TryRead(out scan, out released, out lost));
Assert.Equal(0x5B, scan); Assert.False(released); Assert.False(lost);
Assert.True(NativeKeyboardCapture.TryRead(out scan, out released, out lost)); Assert.True(released);
}
[Fact]
public async Task ConcurrentWritersNeverPairScancodeWithAnotherWritersReleaseFlag()
{
Drain();
var writers = Enumerable.Range(1, 4).Select(index => Task.Run(() =>
{
for (int i = 0; i < 10000; i++) NativeKeyboardCapture.Observe((byte)index, (index & 1) != 0);
})).ToArray();
var completed = Task.WhenAll(writers);
while (!completed.IsCompleted)
{
while (NativeKeyboardCapture.TryRead(out var scan, out var released, out var lost))
{
if (lost) { Assert.Equal(0, scan); Assert.True(released); continue; }
Assert.InRange(scan, 1, 4); Assert.Equal((scan & 1) != 0, released);
}
await Task.Yield();
}
await completed;
while (NativeKeyboardCapture.TryRead(out var scan, out var released, out var lost))
if (!lost) { Assert.InRange(scan, 1, 4); Assert.Equal((scan & 1) != 0, released); }
NativeKeyboardCapture.Observe(0x7E, false);
Assert.True(NativeKeyboardCapture.TryRead(out var marker, out var markerReleased, out var markerLost));
Assert.Equal(0x7E, marker); Assert.False(markerReleased); Assert.False(markerLost);
}
}