Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent crash when monitors change #921

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 38 additions & 14 deletions src/Whim.TestUtils/MonitorTestUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,51 @@

internal static class MonitorTestUtils
{
public static void SetupMultipleMonitors(IInternalContext internalCtx, (RECT Rect, HMONITOR HMonitor)[] monitors)
public static void SetupMultipleMonitors(
IInternalContext internalCtx,
(RECT Rect, HMONITOR HMonitor)[] monitors,
HMONITOR? primaryMonitor = null
)
{
// Get the primary rect.
RECT[] potentialPrimaryMonitors = monitors.Select(m => m.Rect).Where(r => r.left == 0 && r.top == 0).ToArray();
if (potentialPrimaryMonitors.Length != 1)
// Set up the primary monitor.
if (primaryMonitor is HMONITOR primaryMonitorHandle)
{
throw new Exception("No primary monitor found");
internalCtx
.CoreNativeManager.MonitorFromPoint(new Point(0, 0), MONITOR_FROM_FLAGS.MONITOR_DEFAULTTOPRIMARY)
.Returns(primaryMonitorHandle);
}
RECT primaryRect = potentialPrimaryMonitors[0];

// Set up monitor creation.
foreach ((RECT rect, HMONITOR hMonitor) in monitors)
else
{
// Set up the primary monitor, if it's the primary monitor.
if (primaryRect.Equals(rect))
// Get the primary rect.
RECT[] potentialPrimaryMonitors = monitors
.Select(m => m.Rect)
.Where(r => r.left == 0 && r.top == 0)
.ToArray();

if (potentialPrimaryMonitors.Length != 1)
{
internalCtx
.CoreNativeManager.MonitorFromPoint(new Point(0, 0), MONITOR_FROM_FLAGS.MONITOR_DEFAULTTOPRIMARY)
.Returns(hMonitor);
throw new Exception("No primary monitor found");

Check warning on line 36 in src/Whim.TestUtils/MonitorTestUtils.cs

View check run for this annotation

Codecov / codecov/patch

src/Whim.TestUtils/MonitorTestUtils.cs#L36

Added line #L36 was not covered by tests
}

RECT primaryRect = potentialPrimaryMonitors[0];

foreach ((RECT rect, HMONITOR hMonitor) in monitors)
{
if (primaryRect.Equals(rect))
{
internalCtx
.CoreNativeManager.MonitorFromPoint(
new Point(0, 0),
MONITOR_FROM_FLAGS.MONITOR_DEFAULTTOPRIMARY
)
.Returns(hMonitor);
}
}
}

// Set up monitor creation.
foreach ((RECT rect, HMONITOR hMonitor) in monitors)
{
// Set up the fetching of metadata in a multiple monitor setup.
internalCtx
.CoreNativeManager.GetMonitorInfoEx(hMonitor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,13 @@ private static Assert.RaisedEvent<MonitorsChangedEventArgs> DispatchTransformEve
private static void Setup_TryEnqueue(IInternalContext internalCtx) =>
internalCtx.CoreNativeManager.IsStaThread().Returns(_ => true, _ => false);

private static IWorkspace[] SetupWorkspaces_AlreadyAdded(IContext ctx, MutableRootSector rootSector)
/// <summary>
/// Populate the sector with workspaces.
/// </summary>
/// <param name="ctx"></param>
/// <param name="rootSector"></param>
/// <returns></returns>
private static IWorkspace[] PopulateWorkspaces(IContext ctx, MutableRootSector rootSector)
{
Workspace workspace1 = CreateWorkspace(ctx);
Workspace workspace2 = CreateWorkspace(ctx);
Expand All @@ -69,7 +75,13 @@ private static IWorkspace[] SetupWorkspaces_AlreadyAdded(IContext ctx, MutableRo
return new[] { workspace1, workspace2, workspace3 };
}

private static IWorkspace[] SetupWorkspaces_AddWorkspaces(IContext ctx, MutableRootSector rootSector)
/// <summary>
/// Setup the adding of workspaces to the context.
/// </summary>
/// <param name="ctx"></param>
/// <param name="rootSector"></param>
/// <returns></returns>
private static IWorkspace[] SetupAddWorkspaces(IContext ctx, MutableRootSector rootSector)
{
Workspace workspace1 = CreateWorkspace(ctx);
Workspace workspace2 = CreateWorkspace(ctx);
Expand All @@ -96,6 +108,30 @@ private static IWorkspace[] SetupWorkspaces_AddWorkspaces(IContext ctx, MutableR
return new[] { workspace1, workspace2, workspace3 };
}

private static void AssertContainsTransform(IContext ctx, Guid workspaceId, int times = 1)
{
CustomAssert.Contains(
ctx.GetTransforms(),
t => (t as DeactivateWorkspaceTransform) == new DeactivateWorkspaceTransform(workspaceId),
times
);
}

private static void AssertDoesNotContainTransform(IContext ctx, Guid workspaceId)
{
Assert.DoesNotContain(
ctx.GetTransforms(),
t => (t as DeactivateWorkspaceTransform) == new DeactivateWorkspaceTransform(workspaceId)
);
}

private static void AssertPrimaryMonitor(MutableRootSector rootSector, HMONITOR handle)
{
Assert.Equal(handle, rootSector.MonitorSector.PrimaryMonitorHandle);
Assert.Equal(handle, rootSector.MonitorSector.ActiveMonitorHandle);
Assert.Equal(handle, rootSector.MonitorSector.LastWhimActiveMonitorHandle);
}

[Theory, AutoSubstituteData<StoreCustomization>]
internal void WorkspaceSectorNotInitialized(
IContext ctx,
Expand All @@ -104,7 +140,7 @@ MutableRootSector rootSector
)
{
// Given we've populated monitors
SetupWorkspaces_AddWorkspaces(ctx, rootSector);
SetupAddWorkspaces(ctx, rootSector);
SetupMultipleMonitors(internalCtx, new[] { RightMonitorSetup, LeftTopMonitorSetup, LeftBottomMonitorSetup });

rootSector.WorkspaceSector.HasInitialized = false;
Expand All @@ -122,7 +158,7 @@ internal void MonitorsRemoved(IContext ctx, IInternalContext internalCtx, Mutabl
{
// Given we've populated monitors
Setup_TryEnqueue(internalCtx);
IWorkspace[] workspaces = SetupWorkspaces_AlreadyAdded(ctx, rootSector);
IWorkspace[] workspaces = PopulateWorkspaces(ctx, rootSector);

SetupMultipleMonitors(internalCtx, new[] { RightMonitorSetup, LeftTopMonitorSetup, LeftBottomMonitorSetup });

Expand Down Expand Up @@ -150,29 +186,19 @@ internal void MonitorsRemoved(IContext ctx, IInternalContext internalCtx, Mutabl
Assert.Equal(workspaces[2].Id, rootSector.MapSector.MonitorWorkspaceMap[RightMonitorSetup.Handle]);
Assert.DoesNotContain(LeftBottomMonitorSetup.Handle, rootSector.MapSector.MonitorWorkspaceMap.Keys);

Assert.Contains(
ctx.GetTransforms(),
t => (t as DeactivateWorkspaceTransform) == new DeactivateWorkspaceTransform(workspaces[0].Id)
);
AssertContainsTransform(ctx, workspaces[0].Id);
AssertContainsTransform(ctx, workspaces[1].Id, 2);
AssertContainsTransform(ctx, workspaces[2].Id);

CustomAssert.Contains(
ctx.GetTransforms(),
t => (t as DeactivateWorkspaceTransform) == new DeactivateWorkspaceTransform(workspaces[1].Id),
2
);

Assert.Contains(
ctx.GetTransforms(),
t => (t as DeactivateWorkspaceTransform) == new DeactivateWorkspaceTransform(workspaces[2].Id)
);
AssertPrimaryMonitor(rootSector, LeftTopMonitorSetup.Handle);
}

[Theory, AutoSubstituteData<StoreCustomization>]
internal void MonitorsAdded(IContext ctx, IInternalContext internalCtx, MutableRootSector rootSector)
{
// Given we've populated monitors
Setup_TryEnqueue(internalCtx);
IWorkspace[] workspaces = SetupWorkspaces_AlreadyAdded(ctx, rootSector);
IWorkspace[] workspaces = PopulateWorkspaces(ctx, rootSector);

SetupMultipleMonitors(internalCtx, new[] { RightMonitorSetup, LeftTopMonitorSetup });
ctx.Store.Dispatch(new MonitorsChangedTransform());
Expand All @@ -198,27 +224,62 @@ internal void MonitorsAdded(IContext ctx, IInternalContext internalCtx, MutableR
Assert.Equal(workspaces[1].Id, rootSector.MapSector.MonitorWorkspaceMap[RightMonitorSetup.Handle]);
Assert.Equal(workspaces[2].Id, rootSector.MapSector.MonitorWorkspaceMap[LeftBottomMonitorSetup.Handle]);

Assert.Contains(
ctx.GetTransforms(),
t => (t as DeactivateWorkspaceTransform) == new DeactivateWorkspaceTransform(workspaces[0].Id)
);
AssertContainsTransform(ctx, workspaces[0].Id);
AssertContainsTransform(ctx, workspaces[1].Id);
AssertDoesNotContainTransform(ctx, workspaces[2].Id);

Assert.Contains(
ctx.GetTransforms(),
t => (t as DeactivateWorkspaceTransform) == new DeactivateWorkspaceTransform(workspaces[1].Id)
AssertPrimaryMonitor(rootSector, LeftTopMonitorSetup.Handle);
}

[Theory, AutoSubstituteData<StoreCustomization>]
internal void MonitorsAdded_PrimaryChanged(IContext ctx, IInternalContext internalCtx, MutableRootSector rootSector)
{
// Given we've populated monitors
Setup_TryEnqueue(internalCtx);
IWorkspace[] workspaces = PopulateWorkspaces(ctx, rootSector);

SetupMultipleMonitors(internalCtx, new[] { RightMonitorSetup, LeftTopMonitorSetup });
ctx.Store.Dispatch(new MonitorsChangedTransform());

// When a monitor is added, and the new monitor changes to become the primary monitor
Setup_TryEnqueue(internalCtx);
SetupMultipleMonitors(
internalCtx,
new[] { RightMonitorSetup, LeftTopMonitorSetup, LeftBottomMonitorSetup },
LeftBottomMonitorSetup.Handle
);

Assert.DoesNotContain(
ctx.GetTransforms(),
t => (t as DeactivateWorkspaceTransform) == new DeactivateWorkspaceTransform(workspaces[2].Id)
// Then the resulting event will have:
// - two monitors added (the new one and the previous primary as a non-primary)
// - one monitor removed (the previous primary as a non-primary)
var raisedEvent = DispatchTransformEvent(
ctx,
rootSector,
new[] { workspaces[0].Id, workspaces[1].Id, workspaces[2].Id }
);

Assert.Equal(2, raisedEvent.Arguments.AddedMonitors.Count());
Assert.Single(raisedEvent.Arguments.RemovedMonitors);
Assert.Single(raisedEvent.Arguments.UnchangedMonitors);

Assert.Equal(3, rootSector.MapSector.MonitorWorkspaceMap.Count);

Assert.Equal(workspaces[0].Id, rootSector.MapSector.MonitorWorkspaceMap[LeftTopMonitorSetup.Handle]);
Assert.Equal(workspaces[1].Id, rootSector.MapSector.MonitorWorkspaceMap[RightMonitorSetup.Handle]);
Assert.Equal(workspaces[2].Id, rootSector.MapSector.MonitorWorkspaceMap[LeftBottomMonitorSetup.Handle]);

AssertContainsTransform(ctx, workspaces[0].Id, 2);
AssertContainsTransform(ctx, workspaces[1].Id);
AssertDoesNotContainTransform(ctx, workspaces[2].Id);

AssertPrimaryMonitor(rootSector, LeftBottomMonitorSetup.Handle);
}

[Theory, AutoSubstituteData<StoreCustomization>]
internal void MonitorsAdded_Initialization(IContext ctx, IInternalContext internalCtx, MutableRootSector rootSector)
{
// Given we have no monitors
IWorkspace[] workspaces = SetupWorkspaces_AlreadyAdded(ctx, rootSector);
IWorkspace[] workspaces = PopulateWorkspaces(ctx, rootSector);

// When we add monitors
SetupMultipleMonitors(internalCtx, new[] { RightMonitorSetup, LeftTopMonitorSetup });
Expand All @@ -234,6 +295,8 @@ internal void MonitorsAdded_Initialization(IContext ctx, IInternalContext intern
Assert.Equal(workspaces[1].Id, rootSector.MapSector.MonitorWorkspaceMap[RightMonitorSetup.Handle]);

Assert.DoesNotContain(ctx.GetTransforms(), t => t is DeactivateWorkspaceTransform);

AssertPrimaryMonitor(rootSector, LeftTopMonitorSetup.Handle);
}

[Theory, AutoSubstituteData<StoreCustomization>]
Expand All @@ -244,7 +307,7 @@ MutableRootSector rootSector
)
{
// Given we have no monitors
IWorkspace[] workspaces = SetupWorkspaces_AddWorkspaces(ctx, rootSector);
IWorkspace[] workspaces = SetupAddWorkspaces(ctx, rootSector);

// When we add monitors
SetupMultipleMonitors(internalCtx, new[] { RightMonitorSetup, LeftTopMonitorSetup });
Expand All @@ -260,14 +323,16 @@ MutableRootSector rootSector
Assert.Equal(workspaces[1].Id, rootSector.MapSector.MonitorWorkspaceMap[RightMonitorSetup.Handle]);

Assert.DoesNotContain(ctx.GetTransforms(), t => t is DeactivateWorkspaceTransform);

AssertPrimaryMonitor(rootSector, LeftTopMonitorSetup.Handle);
}

[Theory, AutoSubstituteData<StoreCustomization>]
internal void MonitorsUnchanged(IContext ctx, IInternalContext internalCtx, MutableRootSector rootSector)
{
// Given there are no changes in the monitors.
Setup_TryEnqueue(internalCtx);
IWorkspace[] workspaces = SetupWorkspaces_AlreadyAdded(ctx, rootSector);
IWorkspace[] workspaces = PopulateWorkspaces(ctx, rootSector);

SetupMultipleMonitors(internalCtx, new[] { RightMonitorSetup, LeftTopMonitorSetup, LeftBottomMonitorSetup });

Expand All @@ -292,19 +357,10 @@ internal void MonitorsUnchanged(IContext ctx, IInternalContext internalCtx, Muta
Assert.Equal(workspaces[2].Id, rootSector.MapSector.MonitorWorkspaceMap[RightMonitorSetup.Handle]);
Assert.Equal(workspaces[1].Id, rootSector.MapSector.MonitorWorkspaceMap[LeftBottomMonitorSetup.Handle]);

Assert.DoesNotContain(
ctx.GetTransforms(),
t => (t as DeactivateWorkspaceTransform) == new DeactivateWorkspaceTransform(workspaces[0].Id)
);

Assert.DoesNotContain(
ctx.GetTransforms(),
t => (t as DeactivateWorkspaceTransform) == new DeactivateWorkspaceTransform(workspaces[1].Id)
);
AssertDoesNotContainTransform(ctx, workspaces[0].Id);
AssertDoesNotContainTransform(ctx, workspaces[1].Id);
AssertDoesNotContainTransform(ctx, workspaces[2].Id);

Assert.DoesNotContain(
ctx.GetTransforms(),
t => (t as DeactivateWorkspaceTransform) == new DeactivateWorkspaceTransform(workspaces[2].Id)
);
AssertPrimaryMonitor(rootSector, LeftTopMonitorSetup.Handle);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Diagnostics.CodeAnalysis;

namespace Whim.TestUtils;
namespace Whim.Tests;

[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")]
public class LayoutEngineCustomActionTransformTests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ MutableRootSector mutableRootSector

// Get the new monitors.
ImmutableArray<IMonitor> previousMonitors = sector.Monitors;

sector.Monitors = MonitorUtils.GetCurrentMonitors(internalCtx);
UpdateMonitorSector(ctx, internalCtx, mutableRootSector);

List<IMonitor> unchangedMonitors = new();
List<IMonitor> removedMonitors = new();
Expand Down Expand Up @@ -78,6 +77,24 @@ MutableRootSector mutableRootSector
return Unit.Result;
}

private static void UpdateMonitorSector(IContext ctx, IInternalContext internalCtx, MutableRootSector rootSector)
{
MonitorSector sector = rootSector.MonitorSector;

sector.Monitors = MonitorUtils.GetCurrentMonitors(internalCtx);

foreach (IMonitor m in sector.Monitors)
{
if (m.IsPrimary)
{
sector.PrimaryMonitorHandle = m.Handle;
sector.ActiveMonitorHandle = m.Handle;
sector.LastWhimActiveMonitorHandle = m.Handle;
break;
}
}
}

private static void UpdateMapSector(
IContext ctx,
MutableRootSector rootSector,
Expand Down
Loading