244 lines
7.4 KiB
C#
244 lines
7.4 KiB
C#
using System.Windows;
|
|
using System.Windows.Interop;
|
|
using CursorLang.Interop;
|
|
using CursorLang.Tests.Infrastructure;
|
|
|
|
namespace CursorLang.Tests.Interop;
|
|
|
|
/// <summary>
|
|
/// The Win32 wrappers: what is checked is that the calls are put together
|
|
/// right — structures of the expected size, flags in place, and the answers
|
|
/// of the system read correctly.
|
|
/// </summary>
|
|
public sealed class NativeWrappersTests
|
|
{
|
|
[Fact]
|
|
public void The_cursor_position_is_read()
|
|
{
|
|
PopupWindowNative.Point cursor = PopupWindowNative.GetCursorPosition();
|
|
|
|
// The virtual screen may run into negative coordinates, but not beyond
|
|
// reason: a misread structure would give garbage
|
|
Assert.InRange(cursor.X, -32_000, 32_000);
|
|
Assert.InRange(cursor.Y, -32_000, 32_000);
|
|
}
|
|
|
|
[Fact]
|
|
public void The_scale_of_the_monitor_under_the_cursor_is_positive()
|
|
{
|
|
double scale = PopupWindowNative.GetScaleAt(PopupWindowNative.GetCursorPosition());
|
|
|
|
Assert.InRange(scale, 0.5, 8.0);
|
|
}
|
|
|
|
[Fact]
|
|
public void The_work_area_of_the_active_monitor_is_not_empty()
|
|
{
|
|
(PopupWindowNative.Rect work, double scale) = PopupWindowNative.GetActiveMonitorWorkArea();
|
|
|
|
Assert.True(work.Right > work.Left);
|
|
Assert.True(work.Bottom > work.Top);
|
|
Assert.InRange(scale, 0.5, 8.0);
|
|
}
|
|
|
|
[Fact]
|
|
public void The_window_bounds_are_read_from_the_system()
|
|
{
|
|
Sta.Run(() =>
|
|
{
|
|
using var window = new HandleWindow();
|
|
|
|
PopupWindowNative.Rect? bounds = WindowPlacementNative.TryGetBounds(window.Handle);
|
|
|
|
Assert.NotNull(bounds);
|
|
Assert.True(bounds.Value.Right > bounds.Value.Left);
|
|
Assert.True(bounds.Value.Bottom > bounds.Value.Top);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void A_window_that_does_not_exist_has_no_bounds()
|
|
{
|
|
Assert.Null(WindowPlacementNative.TryGetBounds(IntPtr.Zero));
|
|
}
|
|
|
|
[Fact]
|
|
public void A_window_is_moved_to_the_given_point()
|
|
{
|
|
Sta.Run(() =>
|
|
{
|
|
using var window = new HandleWindow();
|
|
|
|
PopupWindowNative.MoveTo(window.Handle, 120, 90);
|
|
|
|
PopupWindowNative.Rect bounds = WindowPlacementNative.TryGetBounds(window.Handle)!.Value;
|
|
Assert.Equal(120, bounds.Left);
|
|
Assert.Equal(90, bounds.Top);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Moving_does_not_change_the_window_size()
|
|
{
|
|
Sta.Run(() =>
|
|
{
|
|
using var window = new HandleWindow();
|
|
|
|
PopupWindowNative.Rect before = WindowPlacementNative.TryGetBounds(window.Handle)!.Value;
|
|
PopupWindowNative.MoveTo(window.Handle, 200, 150);
|
|
PopupWindowNative.Rect after = WindowPlacementNative.TryGetBounds(window.Handle)!.Value;
|
|
|
|
Assert.Equal(before.Right - before.Left, after.Right - after.Left);
|
|
Assert.Equal(before.Bottom - before.Top, after.Bottom - after.Top);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void The_window_bounds_are_set_as_a_whole()
|
|
{
|
|
Sta.Run(() =>
|
|
{
|
|
using var window = new HandleWindow();
|
|
|
|
PopupWindowNative.SetBounds(window.Handle, 60, 70, 320, 240);
|
|
|
|
PopupWindowNative.Rect bounds = WindowPlacementNative.TryGetBounds(window.Handle)!.Value;
|
|
Assert.Equal(60, bounds.Left);
|
|
Assert.Equal(70, bounds.Top);
|
|
Assert.Equal(380, bounds.Right);
|
|
Assert.Equal(310, bounds.Bottom);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void The_work_area_is_found_by_the_rectangle_of_the_window()
|
|
{
|
|
Sta.Run(() =>
|
|
{
|
|
using var window = new HandleWindow();
|
|
PopupWindowNative.Rect bounds = WindowPlacementNative.TryGetBounds(window.Handle)!.Value;
|
|
|
|
PopupWindowNative.Rect? work = WindowPlacementNative.TryGetWorkAreaNear(bounds);
|
|
|
|
Assert.NotNull(work);
|
|
Assert.True(work.Value.Right > work.Value.Left);
|
|
Assert.True(work.Value.Bottom > work.Value.Top);
|
|
});
|
|
}
|
|
|
|
// The nearest monitor is picked, so an area is found even for a point far off screen
|
|
[Fact]
|
|
public void For_a_rectangle_off_every_screen_the_nearest_monitor_is_taken()
|
|
{
|
|
var far = new PopupWindowNative.Rect { Left = 30_000, Top = 30_000, Right = 30_100, Bottom = 30_100 };
|
|
|
|
PopupWindowNative.Rect? work = WindowPlacementNative.TryGetWorkAreaNear(far);
|
|
|
|
Assert.NotNull(work);
|
|
Assert.True(work.Value.Right > work.Value.Left);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_window_becomes_invisible_to_the_focus_and_the_switcher()
|
|
{
|
|
Sta.Run(() =>
|
|
{
|
|
using var window = new HandleWindow();
|
|
|
|
PopupWindowNative.MakePassive(window.Handle);
|
|
|
|
// Checked through the same wrapper: the style has to stick and not
|
|
// be reset by a repeated call
|
|
PopupWindowNative.MakePassive(window.Handle);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void The_layout_of_the_foreground_window_is_read()
|
|
{
|
|
int localeId = KeyboardLayoutNative.GetActiveLocaleId();
|
|
|
|
// The low word of the HKL is the locale identifier, and it is never zero
|
|
Assert.NotEqual(0, localeId);
|
|
Assert.InRange(localeId, 1, 0xFFFF);
|
|
}
|
|
|
|
[Fact]
|
|
public void The_layout_is_read_for_any_window()
|
|
{
|
|
int localeId = KeyboardLayoutNative.GetLocaleIdOf(KeyboardLayoutNative.GetForegroundWindow());
|
|
|
|
Assert.InRange(localeId, 0, 0xFFFF);
|
|
}
|
|
|
|
[Fact]
|
|
public void The_input_state_of_the_foreground_is_read()
|
|
{
|
|
bool received = ForegroundInputNative.TryGetInfo(out ForegroundInputNative.GuiThreadInfo info);
|
|
|
|
if (received)
|
|
{
|
|
// The structure size is filled in by the wrapper itself, and it has
|
|
// to match what Windows expects
|
|
Assert.Equal(System.Runtime.InteropServices.Marshal.SizeOf<ForegroundInputNative.GuiThreadInfo>(),
|
|
info.cbSize);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void The_right_to_show_a_window_is_given_away_without_errors()
|
|
{
|
|
ForegroundPermissionNative.GrantToAnyProcess();
|
|
}
|
|
|
|
[Fact]
|
|
public void The_window_title_bar_is_repainted()
|
|
{
|
|
Sta.Run(() =>
|
|
{
|
|
using var window = new HandleWindow();
|
|
|
|
WindowThemeNative.SetDarkTitleBar(window.Handle, isDark: true);
|
|
WindowThemeNative.SetDarkTitleBar(window.Handle, isDark: false);
|
|
});
|
|
}
|
|
|
|
// The window may be gone by the time of the repaint
|
|
[Fact]
|
|
public void Repainting_a_window_that_does_not_exist_passes_silently()
|
|
{
|
|
WindowThemeNative.SetDarkTitleBar(IntPtr.Zero, isDark: true);
|
|
}
|
|
|
|
[Fact]
|
|
public void The_package_flag_is_computed_once_and_does_not_change()
|
|
{
|
|
bool first = PackageIdentityNative.IsPackaged;
|
|
|
|
Assert.Equal(first, PackageIdentityNative.IsPackaged);
|
|
}
|
|
|
|
/// <summary>A window with a created handle that never appears on screen.</summary>
|
|
private sealed class HandleWindow : IDisposable
|
|
{
|
|
private readonly Window _window;
|
|
|
|
internal HandleWindow()
|
|
{
|
|
_window = new Window
|
|
{
|
|
Width = 300,
|
|
Height = 200,
|
|
ShowInTaskbar = false,
|
|
WindowStartupLocation = WindowStartupLocation.Manual,
|
|
};
|
|
|
|
Handle = new WindowInteropHelper(_window).EnsureHandle();
|
|
}
|
|
|
|
internal IntPtr Handle { get; }
|
|
|
|
public void Dispose() => _window.Close();
|
|
}
|
|
}
|