mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-12 23:36:19 +01:00
Merge remote-tracking branch 'upstream/main' into dev/migrie/merge-upstream-again-again
This commit is contained in:
@@ -3,13 +3,13 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-consolidate": {
|
||||
"version": "2.0.0",
|
||||
"version": "4.2.0",
|
||||
"commands": [
|
||||
"dotnet-consolidate"
|
||||
]
|
||||
},
|
||||
"xamlstyler.console": {
|
||||
"version": "3.2404.2",
|
||||
"version": "3.2501.8",
|
||||
"commands": [
|
||||
"xstyler"
|
||||
]
|
||||
|
||||
3
.github/actions/spell-check/allow/code.txt
vendored
3
.github/actions/spell-check/allow/code.txt
vendored
@@ -233,6 +233,9 @@ SWAPBUTTON
|
||||
SYSTEMDOCKED
|
||||
TABLETPC
|
||||
|
||||
# Units
|
||||
nmi
|
||||
|
||||
# MATH
|
||||
|
||||
artanh
|
||||
|
||||
3
.github/actions/spell-check/expect.txt
vendored
3
.github/actions/spell-check/expect.txt
vendored
@@ -513,6 +513,7 @@ flyouts
|
||||
FMask
|
||||
fmtid
|
||||
FOF
|
||||
WANTNUKEWARNING
|
||||
FOFX
|
||||
FOLDERID
|
||||
folderpath
|
||||
@@ -1474,6 +1475,7 @@ SHCNE
|
||||
SHCNF
|
||||
SHCONTF
|
||||
Shcore
|
||||
shellapi
|
||||
SHELLDETAILS
|
||||
SHELLDLL
|
||||
shellex
|
||||
@@ -1880,6 +1882,7 @@ windowssearch
|
||||
windowssettings
|
||||
WINDOWSTYLES
|
||||
WINDOWSTYLESICON
|
||||
winerror
|
||||
WINEVENT
|
||||
winget
|
||||
wingetcreate
|
||||
|
||||
@@ -146,11 +146,6 @@ jobs:
|
||||
Write-Host "##vso[task.setvariable variable=MSBuildCacheParameters]$MSBuildCacheParameters"
|
||||
displayName: Prepare MSBuildCache variables
|
||||
|
||||
- template: steps-ensure-dotnet-version.yml
|
||||
parameters:
|
||||
sdk: true
|
||||
version: '6.0'
|
||||
|
||||
- template: steps-ensure-dotnet-version.yml
|
||||
parameters:
|
||||
sdk: true
|
||||
|
||||
@@ -104,4 +104,4 @@
|
||||
<PackageVersion Include="Microsoft.VariantAssignment.Client" Version="2.4.17140001" />
|
||||
<PackageVersion Include="Microsoft.VariantAssignment.Contract" Version="3.0.16990001" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
58
NOTICE.md
58
NOTICE.md
@@ -9,6 +9,7 @@ This software incorporates material from third parties.
|
||||
- Installer/Runner
|
||||
- Measure tool
|
||||
- Peek
|
||||
- Registry Preview
|
||||
|
||||
## Utility: Color Picker
|
||||
|
||||
@@ -788,6 +789,34 @@ SOFTWARE.
|
||||
|
||||
## Utility: Peek
|
||||
|
||||
### Monaco Editor
|
||||
|
||||
**Source**: https://github.com/Microsoft/monaco-editor
|
||||
|
||||
**Additional third party notifications:** https://github.com/microsoft/monaco-editor/blob/main/ThirdPartyNotices.txt
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 - present Microsoft Corporation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
### The Quite OK Image Format reference decoder
|
||||
|
||||
**Source**: https://github.com/phoboslab/qoi
|
||||
@@ -1331,6 +1360,35 @@ OTHER DEALINGS IN THE SOFTWARE.
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
```
|
||||
|
||||
## Utility: Registry Preview
|
||||
|
||||
### Monaco Editor
|
||||
|
||||
**Source**: https://github.com/Microsoft/monaco-editor
|
||||
|
||||
**Additional third party notifications:** https://github.com/microsoft/monaco-editor/blob/main/ThirdPartyNotices.txt
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 - present Microsoft Corporation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
## NuGet Packages used by PowerToys
|
||||
|
||||
|
||||
@@ -236,6 +236,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getAllowDataDiagnosticsValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetConfiguredNewPlusReplaceVariablesValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredNewPlusReplaceVariablesValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetConfiguredRunAtStartupValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredRunAtStartupValue());
|
||||
|
||||
@@ -65,6 +65,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
static GpoRuleConfigured GetConfiguredNewPlusHideTemplateFilenameExtensionValue();
|
||||
static GpoRuleConfigured GetAllowDataDiagnosticsValue();
|
||||
static GpoRuleConfigured GetConfiguredRunAtStartupValue();
|
||||
static GpoRuleConfigured GetConfiguredNewPlusReplaceVariablesValue();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ namespace PowerToys
|
||||
static GpoRuleConfigured GetConfiguredNewPlusHideTemplateFilenameExtensionValue();
|
||||
static GpoRuleConfigured GetAllowDataDiagnosticsValue();
|
||||
static GpoRuleConfigured GetConfiguredRunAtStartupValue();
|
||||
static GpoRuleConfigured GetConfiguredNewPlusReplaceVariablesValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,17 +11,40 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
public class By
|
||||
{
|
||||
private readonly OpenQA.Selenium.By by;
|
||||
private readonly OpenQA.Selenium.By? by;
|
||||
private readonly bool isAccessibilityId;
|
||||
private readonly string? accessibilityId;
|
||||
|
||||
private By(OpenQA.Selenium.By by)
|
||||
{
|
||||
isAccessibilityId = false;
|
||||
this.by = by;
|
||||
}
|
||||
|
||||
private By(string accessibilityId)
|
||||
{
|
||||
isAccessibilityId = true;
|
||||
this.accessibilityId = accessibilityId;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
// override ToString to return detailed debugging content provided by OpenQA.Selenium.By
|
||||
return this.by.ToString();
|
||||
return this.GetAccessibilityId();
|
||||
}
|
||||
|
||||
public bool GetIsAccessibilityId() => this.isAccessibilityId;
|
||||
|
||||
public string GetAccessibilityId()
|
||||
{
|
||||
if (this.isAccessibilityId)
|
||||
{
|
||||
return this.accessibilityId!;
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.by!.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -31,6 +54,13 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <returns>A By object.</returns>
|
||||
public static By Name(string name) => new By(OpenQA.Selenium.By.Name(name));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a By object using the className attribute.
|
||||
/// </summary>
|
||||
/// <param name="className">The className attribute to search for.</param>
|
||||
/// <returns>A By object.</returns>
|
||||
public static By ClassName(string className) => new By(OpenQA.Selenium.By.ClassName(className));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a By object using the ID attribute.
|
||||
/// </summary>
|
||||
@@ -38,6 +68,13 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <returns>A By object.</returns>
|
||||
public static By Id(string id) => new By(OpenQA.Selenium.By.Id(id));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a By object using the ID attribute.
|
||||
/// </summary>
|
||||
/// <param name="accessibilityId">The ID attribute to search for.</param>
|
||||
/// <returns>A By object.</returns>
|
||||
public static By AccessibilityId(string accessibilityId) => new By(accessibilityId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a By object using the XPath expression.
|
||||
/// </summary>
|
||||
@@ -70,6 +107,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Converts the By object to an OpenQA.Selenium.By object.
|
||||
/// </summary>
|
||||
/// <returns>An OpenQA.Selenium.By object.</returns>
|
||||
internal OpenQA.Selenium.By ToSeleniumBy() => by;
|
||||
internal OpenQA.Selenium.By ToSeleniumBy() => by!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,13 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Drawing;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Xml.Linq;
|
||||
using ABI.Windows.Foundation;
|
||||
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
@@ -63,6 +68,14 @@ namespace Microsoft.PowerToys.UITest
|
||||
get { return this.windowsElement?.Selected ?? false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Rect of the UI element.
|
||||
/// </summary>
|
||||
public Rectangle? Rect
|
||||
{
|
||||
get { return this.windowsElement?.Rect; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AutomationID of the UI element.
|
||||
/// </summary>
|
||||
@@ -138,6 +151,54 @@ namespace Microsoft.PowerToys.UITest
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drag element move offset.
|
||||
/// </summary>
|
||||
/// <param name="offsetX">The offsetX to move.</param>
|
||||
/// <param name="offsetY">The offsetY to move.</param>
|
||||
public void Drag(int offsetX, int offsetY)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
actions.MoveToElement(windowElement).MoveByOffset(10, 10).ClickAndHold(windowElement).MoveByOffset(offsetX, offsetY).Release();
|
||||
actions.Build().Perform();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drag element move to other element.
|
||||
/// </summary>
|
||||
/// <param name="element">Move to this element.</param>
|
||||
public void Drag(Element element)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
actions.MoveToElement(windowElement).ClickAndHold();
|
||||
Assert.IsNotNull(element.windowsElement, "element is null");
|
||||
int dx = (element.windowsElement.Rect.X - windowElement.Rect.X) / 10;
|
||||
int dy = (element.windowsElement.Rect.Y - windowElement.Rect.Y) / 10;
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
actions.MoveByOffset(dx, dy);
|
||||
}
|
||||
|
||||
actions.Release();
|
||||
actions.Build().Perform();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send Key of the element.
|
||||
/// </summary>
|
||||
/// <param name="key">The Key to Send.</param>
|
||||
public void SendKeys(string key)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
windowElement.SendKeys(key);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the attribute value of the UI element.
|
||||
/// </summary>
|
||||
@@ -222,8 +283,16 @@ namespace Microsoft.PowerToys.UITest
|
||||
var foundElements = FindHelper.FindAll<T, AppiumWebElement>(
|
||||
() =>
|
||||
{
|
||||
var elements = this.windowsElement.FindElements(by.ToSeleniumBy());
|
||||
return elements;
|
||||
if (by.GetIsAccessibilityId())
|
||||
{
|
||||
var elements = this.windowsElement.FindElementsByAccessibilityId(by.GetAccessibilityId());
|
||||
return elements;
|
||||
}
|
||||
else
|
||||
{
|
||||
var elements = this.windowsElement.FindElements(by.ToSeleniumBy());
|
||||
return elements;
|
||||
}
|
||||
},
|
||||
this.driver,
|
||||
timeoutMS);
|
||||
|
||||
@@ -17,6 +17,19 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
internal static class FindHelper
|
||||
{
|
||||
public static ReadOnlyCollection<T>? FindAll<T, TW>(Func<IReadOnlyCollection<TW>> findElementsFunc, WindowsDriver<WindowsElement>? driver, int timeoutMS)
|
||||
where T : Element, new()
|
||||
{
|
||||
var items = findElementsFunc();
|
||||
var res = items.Select(item =>
|
||||
{
|
||||
var element = item as WindowsElement;
|
||||
return NewElement<T>(element, driver, timeoutMS);
|
||||
}).Where(item => item.IsMatchingTarget()).ToList();
|
||||
|
||||
return new ReadOnlyCollection<T>(res);
|
||||
}
|
||||
|
||||
public static ReadOnlyCollection<T>? FindAll<T, TW>(Func<ReadOnlyCollection<TW>> findElementsFunc, WindowsDriver<WindowsElement>? driver, int timeoutMS)
|
||||
where T : Element, new()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Xml.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
@@ -98,8 +99,16 @@ namespace Microsoft.PowerToys.UITest
|
||||
var foundElements = FindHelper.FindAll<T, WindowsElement>(
|
||||
() =>
|
||||
{
|
||||
var elements = this.WindowsDriver.FindElements(by.ToSeleniumBy());
|
||||
return elements;
|
||||
if (by.GetIsAccessibilityId())
|
||||
{
|
||||
var elements = this.WindowsDriver.FindElementsByAccessibilityId(by.GetAccessibilityId());
|
||||
return elements;
|
||||
}
|
||||
else
|
||||
{
|
||||
var elements = this.WindowsDriver.FindElements(by.ToSeleniumBy());
|
||||
return elements;
|
||||
}
|
||||
},
|
||||
this.WindowsDriver,
|
||||
timeoutMS);
|
||||
@@ -145,6 +154,39 @@ namespace Microsoft.PowerToys.UITest
|
||||
return this.FindAll<Element>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keyboard Action key.
|
||||
/// </summary>
|
||||
/// <param name="key1">The Keys1 to click.</param>
|
||||
/// <param name="key2">The Keys2 to click.</param>
|
||||
/// <param name="key3">The Keys3 to click.</param>
|
||||
/// <param name="key4">The Keys4 to click.</param>
|
||||
public void KeyboardAction(string key1, string key2 = "", string key3 = "", string key4 = "")
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(key2))
|
||||
{
|
||||
actions.SendKeys(key1);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(key3))
|
||||
{
|
||||
actions.SendKeys(key1).SendKeys(key2);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(key4))
|
||||
{
|
||||
actions.SendKeys(key1).SendKeys(key2).SendKeys(key3);
|
||||
}
|
||||
else
|
||||
{
|
||||
actions.SendKeys(key1).SendKeys(key2).SendKeys(key3).SendKeys(key4);
|
||||
}
|
||||
|
||||
actions.Release();
|
||||
actions.Build().Perform();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches to an existing PowerToys module.
|
||||
/// </summary>
|
||||
@@ -189,5 +231,28 @@ namespace Microsoft.PowerToys.UITest
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates a manual operation on the element.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to perform on the element.</param>
|
||||
/// <param name="msPreAction">The number of milliseconds to wait before the action. Default value is 500 ms</param>
|
||||
/// <param name="msPostAction">The number of milliseconds to wait after the action. Default value is 500 ms</param>
|
||||
protected void PerformAction(Action<Actions, WindowsDriver<WindowsElement>> action, int msPreAction = 500, int msPostAction = 500)
|
||||
{
|
||||
if (msPreAction > 0)
|
||||
{
|
||||
Task.Delay(msPreAction).Wait();
|
||||
}
|
||||
|
||||
var windowsDriver = this.WindowsDriver;
|
||||
Actions actions = new Actions(this.WindowsDriver);
|
||||
action(actions, windowsDriver);
|
||||
|
||||
if (msPostAction > 0)
|
||||
{
|
||||
Task.Delay(msPostAction).Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
@@ -19,15 +20,19 @@ namespace Microsoft.PowerToys.UITest
|
||||
// Default session path is PowerToys settings dashboard
|
||||
private readonly string sessionPath = ModuleConfigData.Instance.GetModulePath(PowerToysModule.PowerToysSettings);
|
||||
|
||||
private string? locationPath;
|
||||
|
||||
private WindowsDriver<WindowsElement> Root { get; set; }
|
||||
|
||||
private WindowsDriver<WindowsElement>? Driver { get; set; }
|
||||
|
||||
private Process? appDriver;
|
||||
|
||||
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "<Pending>")]
|
||||
public SessionHelper(PowerToysModule scope)
|
||||
{
|
||||
this.sessionPath = ModuleConfigData.Instance.GetModulePath(scope);
|
||||
this.locationPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
var winAppDriverProcessInfo = new ProcessStartInfo
|
||||
{
|
||||
@@ -49,17 +54,12 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Initializes the test environment.
|
||||
/// </summary>
|
||||
/// <param name="scope">The PowerToys module to start.</param>
|
||||
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "<Pending>")]
|
||||
public SessionHelper Init()
|
||||
{
|
||||
string? path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
this.StartExe(path + this.sessionPath);
|
||||
this.StartExe(locationPath + this.sessionPath);
|
||||
|
||||
Assert.IsNotNull(this.Driver, $"Failed to initialize the test environment. Driver is null.");
|
||||
|
||||
// Set default timeout to 5 seconds
|
||||
this.Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -68,9 +68,11 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
public void Cleanup()
|
||||
{
|
||||
ExitScopeExe();
|
||||
try
|
||||
{
|
||||
appDriver?.Kill();
|
||||
appDriver?.WaitForExit(); // Optional: Wait for the process to exit
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -87,7 +89,53 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
var opts = new AppiumOptions();
|
||||
opts.AddAdditionalCapability("app", appPath);
|
||||
Console.WriteLine($"appPath: {appPath}");
|
||||
this.Driver = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), opts);
|
||||
|
||||
// Set default timeout to 5 seconds
|
||||
this.Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit a exe.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the application executable.</param>
|
||||
public void ExitExe(string path)
|
||||
{
|
||||
// Exit Exe
|
||||
string exeName = Path.GetFileNameWithoutExtension(path);
|
||||
|
||||
// PowerToys.FancyZonesEditor
|
||||
Process[] processes = Process.GetProcessesByName(exeName);
|
||||
foreach (Process process in processes)
|
||||
{
|
||||
try
|
||||
{
|
||||
process.Kill();
|
||||
process.WaitForExit(); // Optional: Wait for the process to exit
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Fail($"Failed to terminate process {process.ProcessName} (ID: {process.Id}): {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit now exe.
|
||||
/// </summary>
|
||||
public void ExitScopeExe()
|
||||
{
|
||||
ExitExe(sessionPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restarts now exe and takes control of it.
|
||||
/// </summary>
|
||||
public void RestartScopeExe()
|
||||
{
|
||||
ExitExe(sessionPath);
|
||||
StartExe(locationPath + sessionPath);
|
||||
}
|
||||
|
||||
public WindowsDriver<WindowsElement> GetRoot() => this.Root;
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
@@ -31,11 +32,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver());
|
||||
}
|
||||
|
||||
~UITestBase()
|
||||
{
|
||||
this.sessionHelper.Cleanup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the test.
|
||||
/// </summary>
|
||||
@@ -53,6 +49,15 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UnInitializes the test.
|
||||
/// </summary>
|
||||
[TestCleanup]
|
||||
public void TestClean()
|
||||
{
|
||||
this.sessionHelper.Cleanup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element by selector.
|
||||
/// Shortcut for this.Session.Find<T>(by, timeoutMS)
|
||||
@@ -153,5 +158,24 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
return this.Session.FindAll<Element>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restart scope exe.
|
||||
/// </summary>
|
||||
public void RestartScopeExe()
|
||||
{
|
||||
this.sessionHelper.RestartScopeExe();
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver());
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restart scope exe.
|
||||
/// </summary>
|
||||
public void ExitScopeExe()
|
||||
{
|
||||
this.sessionHelper.ExitScopeExe();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ namespace powertoys_gpo
|
||||
const std::wstring POLICY_MWB_DISABLE_USER_DEFINED_IP_MAPPING_RULES = L"MwbDisableUserDefinedIpMappingRules";
|
||||
const std::wstring POLICY_MWB_POLICY_DEFINED_IP_MAPPING_RULES = L"MwbPolicyDefinedIpMappingRules";
|
||||
const std::wstring POLICY_NEW_PLUS_HIDE_TEMPLATE_FILENAME_EXTENSION = L"NewPlusHideTemplateFilenameExtension";
|
||||
const std::wstring POLICY_NEW_PLUS_REPLACE_VARIABLES = L"NewPlusReplaceVariablesInTemplateFilenames";
|
||||
|
||||
// Methods used for reading the registry
|
||||
#pragma region ReadRegistryMethods
|
||||
@@ -620,5 +621,11 @@ namespace powertoys_gpo
|
||||
{
|
||||
return getConfiguredValue(POLICY_NEW_PLUS_HIDE_TEMPLATE_FILENAME_EXTENSION);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getConfiguredNewPlusReplaceVariablesValue()
|
||||
{
|
||||
return getConfiguredValue(POLICY_NEW_PLUS_REPLACE_VARIABLES);
|
||||
}
|
||||
|
||||
#pragma endregion IndividualModuleSettingPolicies
|
||||
}
|
||||
|
||||
@@ -682,5 +682,15 @@
|
||||
<decimal value="0" />
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="NewPlusReplaceVariablesInTemplateFilenames" class="Both" displayName="$(string.NewPlusReplaceVariablesInTemplateFilenames)" explainText="$(string.NewPlusReplaceVariablesInTemplateFilenamesDescription)" key="Software\Policies\PowerToys" valueName="NewPlusReplaceVariablesInTemplateFilenames">
|
||||
<parentCategory ref="NewPlus" />
|
||||
<supportedOn ref="SUPPORTED_POWERTOYS_0_89_0" />
|
||||
<enabledValue>
|
||||
<decimal value="1" />
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<decimal value="0" />
|
||||
</disabledValue>
|
||||
</policy>
|
||||
</policies>
|
||||
</policyDefinitions>
|
||||
|
||||
@@ -227,6 +227,14 @@ If you enable this policy, the setting is enabled and the extension is hidden.
|
||||
If you disable this policy, the setting is disabled and the extension is shown.
|
||||
|
||||
If you don't configure this policy, the user takes control over the setting and can enable or disable it.
|
||||
</string>
|
||||
<string id="NewPlusReplaceVariablesInTemplateFilenamesDescription">This policy configures if supported variables will get replaced in template filenames.
|
||||
|
||||
If you enable this policy, the setting is enabled and supported variables in filenames will get replaced.
|
||||
|
||||
If you disable this policy, the setting is disabled and variables in filenames will not get replaced.
|
||||
|
||||
If you don't configure this policy, the user will be able to control the setting and can enable or disable it.
|
||||
</string>
|
||||
|
||||
<string id="ConfigureAllUtilityGlobalEnabledState">Configure global utility enabled state</string>
|
||||
@@ -291,7 +299,7 @@ If you don't configure this policy, the user takes control over the setting and
|
||||
<string id="MwbPolicyDefinedIpMappingRules">Predefined IP Address mapping rules</string>
|
||||
<string id="NewPlusHideTemplateFilenameExtension">Hide template filename extension</string>
|
||||
<string id="AllowDiagnosticData">Allow sending diagnostic data</string>
|
||||
<string id="ConfigureRunAtStartup">Configure the run at startup setting</string>
|
||||
<string id="ConfigureRunAtStartup">Configure the run at startup setting</string>
|
||||
</stringTable>
|
||||
|
||||
<presentationTable>
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
|
||||
using Common.UI;
|
||||
using HostsEditor.Telemetry;
|
||||
using HostsUILib.Helpers;
|
||||
using HostsUILib.Settings;
|
||||
using HostsUILib.ViewModels;
|
||||
@@ -38,6 +39,8 @@ namespace Hosts
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new HostEditorStartEvent() { TimeStamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() });
|
||||
|
||||
string appLanguage = LanguageHelper.LoadLanguage();
|
||||
if (!string.IsNullOrEmpty(appLanguage))
|
||||
{
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
// 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 Hosts.Helpers;
|
||||
using HostsEditor.Telemetry;
|
||||
using HostsUILib.Helpers;
|
||||
using HostsUILib.Views;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
@@ -44,6 +47,8 @@ namespace Hosts
|
||||
Activated += MainWindow_Activated;
|
||||
|
||||
MainPage = Host.GetService<HostsMainPage>();
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new HostEditorStartFinishEvent() { TimeStamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() });
|
||||
}
|
||||
|
||||
private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
|
||||
|
||||
18
src/modules/Hosts/Hosts/Telemetry/HostEditorStartEvent.cs
Normal file
18
src/modules/Hosts/Hosts/Telemetry/HostEditorStartEvent.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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.Diagnostics.Tracing;
|
||||
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace HostsEditor.Telemetry;
|
||||
|
||||
[EventData]
|
||||
public class HostEditorStartEvent() : EventBase, IEvent
|
||||
{
|
||||
public long TimeStamp { get; set; }
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.Diagnostics.Tracing;
|
||||
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace HostsEditor.Telemetry;
|
||||
|
||||
[EventData]
|
||||
public class HostEditorStartFinishEvent() : EventBase, IEvent
|
||||
{
|
||||
public long TimeStamp { get; set; }
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -153,7 +153,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
string filePath = stringData;
|
||||
|
||||
_ = Common.ImpersonateLoggedOnUserAndDoSomething(() =>
|
||||
_ = Launch.ImpersonateLoggedOnUserAndDoSomething(() =>
|
||||
{
|
||||
if (File.Exists(filePath) || Directory.Exists(filePath))
|
||||
{
|
||||
@@ -579,7 +579,7 @@ namespace MouseWithoutBorders
|
||||
{
|
||||
if (postAct.Equals("desktop", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_ = ImpersonateLoggedOnUserAndDoSomething(() =>
|
||||
_ = Launch.ImpersonateLoggedOnUserAndDoSomething(() =>
|
||||
{
|
||||
savingFolder = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\MouseWithoutBorders\\";
|
||||
|
||||
@@ -696,7 +696,7 @@ namespace MouseWithoutBorders
|
||||
Path.GetFileName(fileName),
|
||||
remoteMachine);
|
||||
|
||||
_ = ImpersonateLoggedOnUserAndDoSomething(() =>
|
||||
_ = Launch.ImpersonateLoggedOnUserAndDoSomething(() =>
|
||||
{
|
||||
ProcessStartInfo startInfo = new();
|
||||
startInfo.UseShellExecute = true;
|
||||
|
||||
@@ -1,277 +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.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
// <summary>
|
||||
// Keyboard/Mouse hook callback implementation.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
using MouseWithoutBorders.Class;
|
||||
using MouseWithoutBorders.Core;
|
||||
using MouseWithoutBorders.Form;
|
||||
|
||||
using Thread = MouseWithoutBorders.Core.Thread;
|
||||
|
||||
namespace MouseWithoutBorders
|
||||
{
|
||||
internal partial class Common
|
||||
{
|
||||
private static readonly DATA KeybdPackage = new();
|
||||
private static readonly DATA MousePackage = new();
|
||||
#pragma warning disable SA1307 // Accessible fields should begin with upper-case names
|
||||
internal static ulong inputEventCount;
|
||||
internal static ulong invalidPackageCount;
|
||||
#pragma warning restore SA1307
|
||||
internal static int MOVE_MOUSE_RELATIVE = 100000;
|
||||
internal static int XY_BY_PIXEL = 300000;
|
||||
|
||||
static Common()
|
||||
{
|
||||
}
|
||||
|
||||
internal static ulong InvalidPackageCount
|
||||
{
|
||||
get => Common.invalidPackageCount;
|
||||
set => Common.invalidPackageCount = value;
|
||||
}
|
||||
|
||||
internal static ulong InputEventCount
|
||||
{
|
||||
get => Common.inputEventCount;
|
||||
set => Common.inputEventCount = value;
|
||||
}
|
||||
|
||||
internal static ulong RealInputEventCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private static Point actualLastPos;
|
||||
private static int myLastX;
|
||||
private static int myLastY;
|
||||
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Dotnet port with style preservation")]
|
||||
internal static void MouseEvent(MOUSEDATA e, int dx, int dy)
|
||||
{
|
||||
try
|
||||
{
|
||||
PaintCount = 0;
|
||||
bool switchByMouseEnabled = IsSwitchingByMouseEnabled();
|
||||
|
||||
if (switchByMouseEnabled && Sk != null && (DesMachineID == MachineID || !Setting.Values.MoveMouseRelatively) && e.dwFlags == WM_MOUSEMOVE)
|
||||
{
|
||||
Point p = MachineStuff.MoveToMyNeighbourIfNeeded(e.X, e.Y, MachineStuff.desMachineID);
|
||||
|
||||
if (!p.IsEmpty)
|
||||
{
|
||||
HasSwitchedMachineSinceLastCopy = true;
|
||||
|
||||
Logger.LogDebug(string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"***** Host Machine: newDesMachineIdEx set = [{0}]. Mouse is now at ({1},{2})",
|
||||
MachineStuff.newDesMachineIdEx,
|
||||
e.X,
|
||||
e.Y));
|
||||
|
||||
myLastX = e.X;
|
||||
myLastY = e.Y;
|
||||
|
||||
PrepareToSwitchToMachine(MachineStuff.newDesMachineIdEx, p);
|
||||
}
|
||||
}
|
||||
|
||||
if (MachineStuff.desMachineID != MachineID && MachineStuff.SwitchLocation.Count <= 0)
|
||||
{
|
||||
MousePackage.Des = MachineStuff.desMachineID;
|
||||
MousePackage.Type = PackageType.Mouse;
|
||||
MousePackage.Md.dwFlags = e.dwFlags;
|
||||
MousePackage.Md.WheelDelta = e.WheelDelta;
|
||||
|
||||
// Relative move
|
||||
if (Setting.Values.MoveMouseRelatively && Math.Abs(dx) >= MOVE_MOUSE_RELATIVE && Math.Abs(dy) >= MOVE_MOUSE_RELATIVE)
|
||||
{
|
||||
MousePackage.Md.X = dx;
|
||||
MousePackage.Md.Y = dy;
|
||||
}
|
||||
else
|
||||
{
|
||||
MousePackage.Md.X = (e.X - MachineStuff.primaryScreenBounds.Left) * 65535 / screenWidth;
|
||||
MousePackage.Md.Y = (e.Y - MachineStuff.primaryScreenBounds.Top) * 65535 / screenHeight;
|
||||
}
|
||||
|
||||
SkSend(MousePackage, null, false);
|
||||
|
||||
if (MousePackage.Md.dwFlags is WM_LBUTTONUP or WM_RBUTTONUP)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
|
||||
NativeMethods.GetCursorPos(ref actualLastPos);
|
||||
|
||||
if (actualLastPos != Common.LastPos)
|
||||
{
|
||||
Logger.LogDebug($"Mouse cursor has moved unexpectedly: Expected: {Common.LastPos}, actual: {actualLastPos}.");
|
||||
Common.LastPos = actualLastPos;
|
||||
}
|
||||
}
|
||||
|
||||
#if SHOW_ON_WINLOGON_EX
|
||||
if (RunOnLogonDesktop && e.dwFlags == WM_RBUTTONUP &&
|
||||
desMachineID == machineID &&
|
||||
e.x > 2 && e.x < 100 && e.y > 2 && e.y < 20)
|
||||
{
|
||||
DoSomethingInUIThread(delegate()
|
||||
{
|
||||
MainForm.HideMenuWhenRunOnLogonDesktop();
|
||||
MainForm.MainMenu.Hide();
|
||||
MainForm.MainMenu.Show(e.x - 5, e.y - 3);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsSwitchingByMouseEnabled()
|
||||
{
|
||||
return (EasyMouseOption)Setting.Values.EasyMouse == EasyMouseOption.Enable || InputHook.EasyMouseKeyDown;
|
||||
}
|
||||
|
||||
internal static void PrepareToSwitchToMachine(ID newDesMachineID, Point desMachineXY)
|
||||
{
|
||||
Logger.LogDebug($"PrepareToSwitchToMachine: newDesMachineID = {newDesMachineID}, desMachineXY = {desMachineXY}");
|
||||
|
||||
if (((GetTick() - MachineStuff.lastJump < 100) && (GetTick() - MachineStuff.lastJump > 0)) || MachineStuff.desMachineID == ID.ALL)
|
||||
{
|
||||
Logger.LogDebug("PrepareToSwitchToMachine: lastJump");
|
||||
return;
|
||||
}
|
||||
|
||||
MachineStuff.lastJump = GetTick();
|
||||
|
||||
string newDesMachineName = MachineStuff.NameFromID(newDesMachineID);
|
||||
|
||||
if (!IsConnectedTo(newDesMachineID))
|
||||
{// Connection lost, cancel switching
|
||||
Logger.LogDebug("No active connection found for " + newDesMachineName);
|
||||
|
||||
// ShowToolTip("No active connection found for [" + newDesMachineName + "]!", 500);
|
||||
}
|
||||
else
|
||||
{
|
||||
MachineStuff.newDesMachineID = newDesMachineID;
|
||||
MachineStuff.SwitchLocation.X = desMachineXY.X;
|
||||
MachineStuff.SwitchLocation.Y = desMachineXY.Y;
|
||||
MachineStuff.SwitchLocation.ResetCount();
|
||||
_ = EvSwitch.Set();
|
||||
|
||||
// PostMessage(mainForm.Handle, WM_SWITCH, IntPtr.Zero, IntPtr.Zero);
|
||||
if (newDesMachineID != DragDrop.DragMachine)
|
||||
{
|
||||
if (!DragDrop.IsDragging && !DragDrop.IsDropping)
|
||||
{
|
||||
if (DragDrop.MouseDown && !RunOnLogonDesktop && !RunOnScrSaverDesktop)
|
||||
{
|
||||
DragDrop.DragDropStep02();
|
||||
}
|
||||
}
|
||||
else if (DragDrop.DragMachine != (ID)1)
|
||||
{
|
||||
DragDrop.ChangeDropMachine();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DragDrop.DragDropStep11();
|
||||
}
|
||||
|
||||
// Change des machine
|
||||
if (MachineStuff.desMachineID != newDesMachineID)
|
||||
{
|
||||
Logger.LogDebug("MouseEvent: Switching to new machine:" + newDesMachineName);
|
||||
|
||||
// Ask current machine to hide the Mouse cursor
|
||||
if (newDesMachineID != ID.ALL && MachineStuff.desMachineID != MachineID)
|
||||
{
|
||||
SendPackage(MachineStuff.desMachineID, PackageType.HideMouse);
|
||||
}
|
||||
|
||||
DesMachineID = newDesMachineID;
|
||||
|
||||
if (MachineStuff.desMachineID == MachineID)
|
||||
{
|
||||
if (GetTick() - clipboardCopiedTime < BIG_CLIPBOARD_DATA_TIMEOUT)
|
||||
{
|
||||
clipboardCopiedTime = 0;
|
||||
Common.GetRemoteClipboard("PrepareToSwitchToMachine");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ask the new active machine to get clipboard data (if the data is too big)
|
||||
SendPackage(MachineStuff.desMachineID, PackageType.MachineSwitched);
|
||||
}
|
||||
|
||||
_ = Interlocked.Increment(ref switchCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SaveSwitchCount()
|
||||
{
|
||||
if (SwitchCount > 0)
|
||||
{
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
Setting.Values.SwitchCount += SwitchCount;
|
||||
_ = Interlocked.Exchange(ref switchCount, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
internal static void KeybdEvent(KEYBDDATA e)
|
||||
{
|
||||
try
|
||||
{
|
||||
PaintCount = 0;
|
||||
if (MachineStuff.desMachineID != MachineStuff.newDesMachineID)
|
||||
{
|
||||
Logger.LogDebug("KeybdEvent: Switching to new machine...");
|
||||
DesMachineID = MachineStuff.newDesMachineID;
|
||||
}
|
||||
|
||||
if (MachineStuff.desMachineID != MachineID)
|
||||
{
|
||||
KeybdPackage.Des = MachineStuff.desMachineID;
|
||||
KeybdPackage.Type = PackageType.Keyboard;
|
||||
KeybdPackage.Kd = e;
|
||||
KeybdPackage.DateTime = GetTick();
|
||||
SkSend(KeybdPackage, null, false);
|
||||
if (KeybdPackage.Kd.dwFlags is WM_KEYUP or WM_SYSKEYUP)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,526 +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.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
using System.Windows.Forms;
|
||||
|
||||
// <summary>
|
||||
// Some other helper methods.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
using Microsoft.Win32;
|
||||
using MouseWithoutBorders.Class;
|
||||
using MouseWithoutBorders.Core;
|
||||
using static System.Windows.Forms.Control;
|
||||
|
||||
namespace MouseWithoutBorders
|
||||
{
|
||||
internal partial class Common
|
||||
{
|
||||
internal const string HELPER_FORM_TEXT = "Mouse without Borders Helper";
|
||||
internal const string HelperProcessName = "PowerToys.MouseWithoutBordersHelper";
|
||||
private static bool signalHelperToExit;
|
||||
private static bool signalWatchDogToExit;
|
||||
internal static long WndProcCounter;
|
||||
|
||||
private static void WatchDogThread()
|
||||
{
|
||||
long oldCounter = WndProcCounter;
|
||||
|
||||
do
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
|
||||
if (signalWatchDogToExit)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (BlockingUI)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
if (WndProcCounter == oldCounter)
|
||||
{
|
||||
Process p = Process.GetCurrentProcess();
|
||||
string procInfo = $"{p.PrivateMemorySize64 / 1024 / 1024}MB, {p.TotalProcessorTime}, {Environment.ProcessorCount}.";
|
||||
string threadStacks = $"{procInfo} {Thread.DumpThreadsStack()}";
|
||||
Logger.TelemetryLogTrace(threadStacks, SeverityLevel.Error);
|
||||
break;
|
||||
}
|
||||
|
||||
oldCounter = WndProcCounter;
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
|
||||
private static void HelperThread()
|
||||
{
|
||||
// SuppressFlow fixes an issue on service mode, where the helper process can't get enough permissions to be started again.
|
||||
// More details can be found on: https://github.com/microsoft/PowerToys/pull/36892
|
||||
using var asyncFlowControl = System.Threading.ExecutionContext.SuppressFlow();
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_ = EvSwitch.WaitOne(); // Switching to another machine?
|
||||
|
||||
if (signalHelperToExit)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (MachineStuff.NewDesMachineID != Common.MachineID && MachineStuff.NewDesMachineID != ID.ALL)
|
||||
{
|
||||
HideMouseCursor(false);
|
||||
Common.MainFormDotEx(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MachineStuff.SwitchLocation.Count > 0)
|
||||
{
|
||||
MachineStuff.SwitchLocation.Count--;
|
||||
|
||||
// When we want to move mouse by pixels, we add 300k to x and y (search for XY_BY_PIXEL for other related code).
|
||||
Logger.LogDebug($"+++++ Moving mouse to {MachineStuff.SwitchLocation.X}, {MachineStuff.SwitchLocation.Y}");
|
||||
|
||||
// MaxXY = 65535 so 100k is safe.
|
||||
if (MachineStuff.SwitchLocation.X > XY_BY_PIXEL - 100000 || MachineStuff.SwitchLocation.Y > XY_BY_PIXEL - 100000)
|
||||
{
|
||||
InputSimulation.MoveMouse(MachineStuff.SwitchLocation.X - XY_BY_PIXEL, MachineStuff.SwitchLocation.Y - XY_BY_PIXEL);
|
||||
}
|
||||
else
|
||||
{
|
||||
InputSimulation.MoveMouseEx(MachineStuff.SwitchLocation.X, MachineStuff.SwitchLocation.Y);
|
||||
}
|
||||
|
||||
Common.MainFormDot();
|
||||
}
|
||||
}
|
||||
|
||||
if (MachineStuff.NewDesMachineID == Common.MachineID)
|
||||
{
|
||||
ReleaseAllKeys();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
|
||||
signalHelperToExit = false;
|
||||
Logger.LogDebug("^^^Helper Thread exiting...^^^");
|
||||
}
|
||||
|
||||
internal static void MainFormDotEx(bool bCheckTS)
|
||||
{
|
||||
Logger.LogDebug("***** MainFormDotEx:");
|
||||
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
int left = MachineStuff.PrimaryScreenBounds.Left + ((MachineStuff.PrimaryScreenBounds.Right - MachineStuff.PrimaryScreenBounds.Left) / 2) - 1;
|
||||
int top = Setting.Values.HideMouse ? 3 : MachineStuff.PrimaryScreenBounds.Top + ((MachineStuff.PrimaryScreenBounds.Bottom - MachineStuff.PrimaryScreenBounds.Top) / 2);
|
||||
|
||||
Common.MainFormVisible = true;
|
||||
|
||||
if (Setting.Values.HideMouse && Setting.Values.StealFocusWhenSwitchingMachine && Common.SendMessageToHelper(0x407, new IntPtr(left), new IntPtr(top), true) == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
/* When user just switches to the Logon desktop, user is actually on the "Windows Default Lock Screen" (LockApp).
|
||||
* If a click is sent to this during switch, it actually triggers a desktop switch on the local machine causing a reconnection affecting the machine switch.
|
||||
* We can detect and skip in this case.
|
||||
* */
|
||||
IntPtr foreGroundWindow = NativeMethods.GetForegroundWindow();
|
||||
string foreGroundWindowText = GetText(foreGroundWindow);
|
||||
|
||||
bool mouseClick = true;
|
||||
|
||||
if (foreGroundWindowText.Equals("Windows Default Lock Screen", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mouseClick = false;
|
||||
}
|
||||
|
||||
// Window title may be localized, check process name:
|
||||
if (mouseClick)
|
||||
{
|
||||
_ = NativeMethods.GetWindowThreadProcessId(foreGroundWindow, out uint pid);
|
||||
|
||||
if (pid > 0)
|
||||
{
|
||||
string foreGroundWindowProcess = Process.GetProcessById((int)pid)?.ProcessName;
|
||||
|
||||
if (foreGroundWindowProcess.Equals("LockApp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mouseClick = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mouseClick)
|
||||
{
|
||||
InputSimulation.MouseClickDotForm(left + 1, top + 1);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomCursor.ShowFakeMouseCursor(int.MinValue, int.MinValue);
|
||||
}
|
||||
|
||||
internal static void MainForm3Pixels()
|
||||
{
|
||||
Logger.LogDebug("***** MainFormDotLarge:");
|
||||
|
||||
DoSomethingInUIThread(
|
||||
() =>
|
||||
{
|
||||
MainForm.Left = MachineStuff.PrimaryScreenBounds.Left + ((MachineStuff.PrimaryScreenBounds.Right - MachineStuff.PrimaryScreenBounds.Left) / 2) - 2;
|
||||
MainForm.Top = Setting.Values.HideMouse ? 3 : MachineStuff.PrimaryScreenBounds.Top + ((MachineStuff.PrimaryScreenBounds.Bottom - MachineStuff.PrimaryScreenBounds.Top) / 2) - 1;
|
||||
MainForm.Width = 3;
|
||||
MainForm.Height = 3;
|
||||
MainForm.Opacity = 0.11D;
|
||||
MainForm.TopMost = true;
|
||||
|
||||
if (Setting.Values.HideMouse)
|
||||
{
|
||||
MainForm.BackColor = Color.Black;
|
||||
MainForm.Show();
|
||||
Common.MainFormVisible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
MainForm.BackColor = Color.White;
|
||||
MainForm.Hide();
|
||||
Common.MainFormVisible = false;
|
||||
}
|
||||
},
|
||||
true);
|
||||
|
||||
CustomCursor.ShowFakeMouseCursor(int.MinValue, int.MinValue);
|
||||
}
|
||||
|
||||
internal static void MainFormDot()
|
||||
{
|
||||
DoSomethingInUIThread(
|
||||
() =>
|
||||
{
|
||||
_ = Common.SendMessageToHelper(0x408, IntPtr.Zero, IntPtr.Zero, false);
|
||||
|
||||
MainForm.Left = MachineStuff.PrimaryScreenBounds.Left + ((MachineStuff.PrimaryScreenBounds.Right - MachineStuff.PrimaryScreenBounds.Left) / 2) - 1;
|
||||
MainForm.Top = Setting.Values.HideMouse ? 3 : MachineStuff.PrimaryScreenBounds.Top + ((MachineStuff.PrimaryScreenBounds.Bottom - MachineStuff.PrimaryScreenBounds.Top) / 2);
|
||||
MainForm.Width = 1;
|
||||
MainForm.Height = 1;
|
||||
MainForm.Opacity = 0.15;
|
||||
MainForm.Hide();
|
||||
Common.MainFormVisible = false;
|
||||
},
|
||||
true);
|
||||
|
||||
CustomCursor.ShowFakeMouseCursor(int.MinValue, int.MinValue);
|
||||
}
|
||||
|
||||
internal static void ToggleIcon()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (toggleIconsIndex < TOGGLE_ICONS_SIZE)
|
||||
{
|
||||
Common.DoSomethingInUIThread(() => Common.MainForm.ChangeIcon(toggleIcons[toggleIconsIndex++]));
|
||||
}
|
||||
else
|
||||
{
|
||||
toggleIconsIndex = 0;
|
||||
toggleIcons = null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RunDDHelper(bool cleanUp = false)
|
||||
{
|
||||
if (Common.RunOnLogonDesktop || Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (cleanUp)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process[] ps = Process.GetProcessesByName(HelperProcessName);
|
||||
foreach (Process p in ps)
|
||||
{
|
||||
p.KillProcess();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
_ = Common.SendMessageToHelper(SharedConst.QUIT_CMD, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Common.IsMyDesktopActive())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Common.IpcChannelCreated)
|
||||
{
|
||||
Logger.TelemetryLogTrace($"{nameof(Common.IpcChannelCreated)} = {Common.IpcChannelCreated}. {Logger.GetStackTrace(new StackTrace())}", SeverityLevel.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MainForm.IsDisposed)
|
||||
{
|
||||
MainForm.NotifyIcon.Visible = false;
|
||||
MainForm.NotifyIcon.Visible = Setting.Values.ShowOriginalUI;
|
||||
}
|
||||
|
||||
IntPtr h = (IntPtr)NativeMethods.FindWindow(null, Common.HELPER_FORM_TEXT);
|
||||
|
||||
if (h.ToInt32() <= 0)
|
||||
{
|
||||
_ = Common.CreateProcessInInputDesktopSession(
|
||||
$"\"{Path.GetDirectoryName(Application.ExecutablePath)}\\{HelperProcessName}.exe\"",
|
||||
string.Empty,
|
||||
Common.GetInputDesktop(),
|
||||
0);
|
||||
|
||||
HasSwitchedMachineSinceLastCopy = true;
|
||||
|
||||
// Common.CreateLowIntegrityProcess("\"" + Path.GetDirectoryName(Application.ExecutablePath) + "\\MouseWithoutBordersHelper.exe\"", string.Empty, 0, false, 0);
|
||||
var processes = Process.GetProcessesByName(HelperProcessName);
|
||||
if (processes?.Length == 0)
|
||||
{
|
||||
Logger.Log("Unable to start helper process.");
|
||||
Common.ShowToolTip("Error starting Mouse Without Borders Helper, clipboard sharing will not work!", 5000, ToolTipIcon.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("Helper process started.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var processes = Process.GetProcessesByName(HelperProcessName);
|
||||
if (processes?.Length > 0)
|
||||
{
|
||||
Logger.Log("Helper process found running.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("Invalid helper process found running.");
|
||||
Common.ShowToolTip("Error finding Mouse Without Borders Helper, clipboard sharing will not work!", 5000, ToolTipIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static int SendMessageToHelper(int msg, IntPtr wparam, IntPtr lparam, bool wait = true, bool log = true)
|
||||
{
|
||||
int h = NativeMethods.FindWindow(null, Common.HELPER_FORM_TEXT);
|
||||
int rv = -1;
|
||||
|
||||
if (h > 0)
|
||||
{
|
||||
rv = wait
|
||||
? (int)NativeMethods.SendMessage((IntPtr)h, msg, wparam, lparam)
|
||||
: NativeMethods.PostMessage((IntPtr)h, msg, wparam, lparam) ? 1 : 0;
|
||||
}
|
||||
|
||||
if (log)
|
||||
{
|
||||
Logger.LogDebug($"SendMessageToHelper: HelperWindow={h}, Return={rv}, msg={msg}, w={wparam.ToInt32()}, l={lparam.ToInt32()}, Post={!wait}");
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
internal static bool IsWindows8AndUp()
|
||||
{
|
||||
return (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor >= 2)
|
||||
|| Environment.OSVersion.Version.Major > 6;
|
||||
}
|
||||
|
||||
internal static string GetMiniLog(IEnumerable<ControlCollection> optionControls)
|
||||
{
|
||||
string log = string.Empty;
|
||||
|
||||
log += "=============================================================================================================================\r\n";
|
||||
log += $"{Application.ProductName} version {Application.ProductVersion}\r\n";
|
||||
|
||||
log += $"{Setting.Values.Username}/{GetDebugInfo(MyKey)}\r\n";
|
||||
log += $"{MachineName}/{MachineID}/{DesMachineID}\r\n";
|
||||
log += $"Id: {Setting.Values.DeviceId}\r\n";
|
||||
log += $"Matrix: {string.Join(",", MachineStuff.MachineMatrix)}\r\n";
|
||||
log += $"McPool: {Setting.Values.MachinePoolString}\r\n";
|
||||
|
||||
log += "\r\nOPTIONS:\r\n";
|
||||
|
||||
foreach (ControlCollection controlCollection in optionControls)
|
||||
{
|
||||
foreach (object c in controlCollection)
|
||||
{
|
||||
if (c is CheckBox checkBox)
|
||||
{
|
||||
log += $"({(checkBox.Checked ? 1 : 0)}) {checkBox.Text}\r\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c is RadioButton radioButton)
|
||||
{
|
||||
log += $"({(radioButton.Checked ? 1 : 0)}) {radioButton.Name}.[{radioButton.Text}]\r\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c is ComboBox comboBox)
|
||||
{
|
||||
log += $"{comboBox.Name} = {comboBox.Text}\r\n";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log += "\r\n";
|
||||
|
||||
SocketStuff sk = Sk;
|
||||
|
||||
if (sk?.TcpSockets != null)
|
||||
{
|
||||
foreach (TcpSk tcp in sk.TcpSockets)
|
||||
{
|
||||
log += $"{Common.MachineName}{(tcp.IsClient ? "=>" : "<=")}{tcp.MachineName}({tcp.MachineId}):{tcp.Status}\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
log += string.Format(CultureInfo.CurrentCulture, "Helper:{0}\r\n", SendMessageToHelper(0x400, IntPtr.Zero, IntPtr.Zero));
|
||||
|
||||
log += Setting.Values.LastPersonalizeLogonScr + "\r\n";
|
||||
log += "Name2IP =\r\n" + Setting.Values.Name2IP + "\r\n";
|
||||
|
||||
log += "Last 10 trace messages:\r\n";
|
||||
|
||||
log += string.Join(Environment.NewLine, Logger.LogCounter.Select(item => $"({item.Value}): {item.Key}").Take(10));
|
||||
|
||||
log += "\r\n=============================================================================================================================";
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
internal static bool GetUserName()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Setting.Values.Username) && !Common.RunOnLogonDesktop)
|
||||
{
|
||||
if (Program.User.Contains("system", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
_ = Common.ImpersonateLoggedOnUserAndDoSomething(() =>
|
||||
{
|
||||
// See: https://stackoverflow.com/questions/19487541/how-to-get-windows-user-name-from-sessionid
|
||||
static string GetUsernameBySessionId(int sessionId)
|
||||
{
|
||||
string username = "SYSTEM";
|
||||
if (NativeMethods.WTSQuerySessionInformation(IntPtr.Zero, sessionId, NativeMethods.WTSInfoClass.WTSUserName, out nint buffer, out int strLen) && strLen > 1)
|
||||
{
|
||||
username = Marshal.PtrToStringAnsi(buffer);
|
||||
NativeMethods.WTSFreeMemory(buffer);
|
||||
|
||||
if (NativeMethods.WTSQuerySessionInformation(IntPtr.Zero, sessionId, NativeMethods.WTSInfoClass.WTSDomainName, out buffer, out strLen) && strLen > 1)
|
||||
{
|
||||
username = @$"{Marshal.PtrToStringAnsi(buffer)}\{username}";
|
||||
NativeMethods.WTSFreeMemory(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
return username;
|
||||
}
|
||||
|
||||
// The most direct way to fetch the username is WindowsIdentity.GetCurrent(true).Name
|
||||
// but GetUserName can run within an ExecutionContext.SuppressFlow block, which creates issues
|
||||
// with WindowsIdentity.GetCurrent.
|
||||
// See: https://stackoverflow.com/questions/76998988/exception-when-using-executioncontext-suppressflow-in-net-7
|
||||
// So we use WTSQuerySessionInformation as a workaround.
|
||||
Setting.Values.Username = GetUsernameBySessionId(Process.GetCurrentProcess().SessionId);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Setting.Values.Username = Program.User;
|
||||
}
|
||||
|
||||
Logger.LogDebug("[Username] = " + Setting.Values.Username);
|
||||
}
|
||||
|
||||
return !string.IsNullOrEmpty(Setting.Values.Username);
|
||||
}
|
||||
|
||||
internal static void ShowOneWayModeMessage()
|
||||
{
|
||||
ToggleShowTopMostMessage(
|
||||
@"
|
||||
Due to Security Controls, a remote device cannot control a SAW device.
|
||||
Please use the keyboard and Mouse from the SAW device.
|
||||
(Press Esc to hide this message)
|
||||
",
|
||||
string.Empty,
|
||||
10);
|
||||
}
|
||||
|
||||
internal static void ApplyCADSetting()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Setting.Values.DisableCAD)
|
||||
{
|
||||
RegistryKey k = Registry.LocalMachine.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System");
|
||||
if (k != null)
|
||||
{
|
||||
k.SetValue("DisableCAD", 1, RegistryValueKind.DWord);
|
||||
k.Close();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RegistryKey k = Registry.LocalMachine.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System");
|
||||
if (k != null)
|
||||
{
|
||||
k.SetValue("DisableCAD", 0, RegistryValueKind.DWord);
|
||||
k.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
internal static void Init()
|
||||
{
|
||||
_ = Common.GetUserName();
|
||||
_ = Helper.GetUserName();
|
||||
Common.GeneratedKey = true;
|
||||
|
||||
try
|
||||
@@ -148,7 +148,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
private static void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
|
||||
{
|
||||
Common.WndProcCounter++;
|
||||
Helper.WndProcCounter++;
|
||||
|
||||
if (e.Mode is PowerModes.Resume or PowerModes.Suspend)
|
||||
{
|
||||
@@ -167,21 +167,21 @@ namespace MouseWithoutBorders
|
||||
watchDogThread.Start();
|
||||
*/
|
||||
|
||||
helper = new Thread(new ThreadStart(HelperThread), "Helper Thread");
|
||||
helper = new Thread(new ThreadStart(Helper.HelperThread), "Helper Thread");
|
||||
helper.SetApartmentState(ApartmentState.STA);
|
||||
helper.Start();
|
||||
}
|
||||
|
||||
private static void AskHelperThreadsToExit(int waitTime)
|
||||
{
|
||||
signalHelperToExit = true;
|
||||
signalWatchDogToExit = true;
|
||||
Helper.signalHelperToExit = true;
|
||||
Helper.signalWatchDogToExit = true;
|
||||
_ = EvSwitch.Set();
|
||||
|
||||
int c = 0;
|
||||
if (helper != null && c < waitTime)
|
||||
{
|
||||
while (signalHelperToExit)
|
||||
while (Helper.signalHelperToExit)
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
@@ -251,7 +251,7 @@ namespace MouseWithoutBorders
|
||||
private static void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
|
||||
{
|
||||
Logger.LogDebug("NetworkAvailabilityEventArgs.IsAvailable: " + e.IsAvailable.ToString(CultureInfo.InvariantCulture));
|
||||
Common.WndProcCounter++;
|
||||
Helper.WndProcCounter++;
|
||||
ScheduleReopenSocketsDueToNetworkChanges(!e.IsAvailable);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,314 +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.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
|
||||
// <summary>
|
||||
// Impersonation.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
using MouseWithoutBorders.Class;
|
||||
using MouseWithoutBorders.Core;
|
||||
|
||||
namespace MouseWithoutBorders
|
||||
{
|
||||
internal partial class Common
|
||||
{
|
||||
internal static bool RunElevated()
|
||||
{
|
||||
return WindowsIdentity.GetCurrent().Owner.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid);
|
||||
}
|
||||
|
||||
internal static bool ImpersonateLoggedOnUserAndDoSomething(Action targetFunc)
|
||||
{
|
||||
if (Common.RunWithNoAdminRight)
|
||||
{
|
||||
targetFunc();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// SuppressFlow fixes an issue on service mode, where WTSQueryUserToken runs successfully once and then fails
|
||||
// on subsequent calls. The reason appears to be an unknown issue with reverting the impersonation,
|
||||
// meaning that subsequent impersonation attempts run as the logged-on user and fail.
|
||||
// This is a workaround.
|
||||
using var asyncFlowControl = System.Threading.ExecutionContext.SuppressFlow();
|
||||
|
||||
uint dwSessionId;
|
||||
IntPtr hUserToken = IntPtr.Zero, hUserTokenDup = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
dwSessionId = (uint)Process.GetCurrentProcess().SessionId;
|
||||
uint rv = NativeMethods.WTSQueryUserToken(dwSessionId, ref hUserToken);
|
||||
var lastError = rv == 0 ? Marshal.GetLastWin32Error() : 0;
|
||||
|
||||
Logger.LogDebug($"{nameof(NativeMethods.WTSQueryUserToken)} returned {rv.ToString(CultureInfo.CurrentCulture)}");
|
||||
|
||||
if (rv == 0)
|
||||
{
|
||||
Logger.Log($"{nameof(NativeMethods.WTSQueryUserToken)} failed with: {lastError}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!NativeMethods.DuplicateToken(hUserToken, (int)NativeMethods.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, ref hUserTokenDup))
|
||||
{
|
||||
Logger.TelemetryLogTrace($"{nameof(NativeMethods.DuplicateToken)} Failed! {Logger.GetStackTrace(new StackTrace())}", SeverityLevel.Warning);
|
||||
_ = NativeMethods.CloseHandle(hUserToken);
|
||||
_ = NativeMethods.CloseHandle(hUserTokenDup);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NativeMethods.ImpersonateLoggedOnUser(hUserTokenDup))
|
||||
{
|
||||
targetFunc();
|
||||
_ = NativeMethods.RevertToSelf();
|
||||
_ = NativeMethods.CloseHandle(hUserToken);
|
||||
_ = NativeMethods.CloseHandle(hUserTokenDup);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("ImpersonateLoggedOnUser Failed!");
|
||||
_ = NativeMethods.CloseHandle(hUserToken);
|
||||
_ = NativeMethods.CloseHandle(hUserTokenDup);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static int CreateProcessInInputDesktopSession(string commandLine, string arg, string desktop, short wShowWindow, bool lowIntegrity = false)
|
||||
|
||||
// As user who runs explorer.exe
|
||||
{
|
||||
if (!Program.User.Contains("system", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
ProcessStartInfo s = new(commandLine, arg);
|
||||
s.WindowStyle = wShowWindow != 0 ? ProcessWindowStyle.Normal : ProcessWindowStyle.Hidden;
|
||||
Process p = Process.Start(s);
|
||||
|
||||
return p == null ? 0 : p.Id;
|
||||
}
|
||||
|
||||
string commandLineWithArg = commandLine + " " + arg;
|
||||
int lastError;
|
||||
int dwSessionId;
|
||||
IntPtr hUserToken = IntPtr.Zero, hUserTokenDup = IntPtr.Zero;
|
||||
|
||||
Logger.LogDebug("CreateProcessInInputDesktopSession called, launching " + commandLineWithArg + " on " + desktop);
|
||||
|
||||
try
|
||||
{
|
||||
dwSessionId = Process.GetCurrentProcess().SessionId;
|
||||
|
||||
// Get the user token used by DuplicateTokenEx
|
||||
lastError = (int)NativeMethods.WTSQueryUserToken((uint)dwSessionId, ref hUserToken);
|
||||
|
||||
NativeMethods.STARTUPINFO si = default;
|
||||
si.cb = Marshal.SizeOf(si);
|
||||
si.lpDesktop = "winsta0\\" + desktop;
|
||||
si.wShowWindow = wShowWindow;
|
||||
|
||||
NativeMethods.SECURITY_ATTRIBUTES sa = default;
|
||||
sa.Length = Marshal.SizeOf(sa);
|
||||
|
||||
if (!NativeMethods.DuplicateTokenEx(hUserToken, NativeMethods.MAXIMUM_ALLOWED, ref sa, (int)NativeMethods.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)NativeMethods.TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
|
||||
{
|
||||
lastError = Marshal.GetLastWin32Error();
|
||||
Logger.Log(string.Format(CultureInfo.CurrentCulture, "DuplicateTokenEx error: {0} Token does not have the privilege.", lastError));
|
||||
_ = NativeMethods.CloseHandle(hUserToken);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (lowIntegrity)
|
||||
{
|
||||
NativeMethods.TOKEN_MANDATORY_LABEL tIL;
|
||||
|
||||
// Low
|
||||
string sIntegritySid = "S-1-16-4096";
|
||||
|
||||
bool rv = NativeMethods.ConvertStringSidToSid(sIntegritySid, out IntPtr pIntegritySid);
|
||||
|
||||
if (!rv)
|
||||
{
|
||||
Logger.Log("ConvertStringSidToSid failed");
|
||||
_ = NativeMethods.CloseHandle(hUserToken);
|
||||
_ = NativeMethods.CloseHandle(hUserTokenDup);
|
||||
return 0;
|
||||
}
|
||||
|
||||
tIL.Label.Attributes = NativeMethods.SE_GROUP_INTEGRITY;
|
||||
tIL.Label.Sid = pIntegritySid;
|
||||
|
||||
rv = NativeMethods.SetTokenInformation(hUserTokenDup, NativeMethods.TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, ref tIL, (uint)Marshal.SizeOf(tIL) + (uint)IntPtr.Size);
|
||||
|
||||
if (!rv)
|
||||
{
|
||||
Logger.Log("SetTokenInformation failed");
|
||||
_ = NativeMethods.CloseHandle(hUserToken);
|
||||
_ = NativeMethods.CloseHandle(hUserTokenDup);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint dwCreationFlags = NativeMethods.NORMAL_PRIORITY_CLASS | NativeMethods.CREATE_NEW_CONSOLE;
|
||||
IntPtr pEnv = IntPtr.Zero;
|
||||
|
||||
if (NativeMethods.CreateEnvironmentBlock(ref pEnv, hUserTokenDup, true))
|
||||
{
|
||||
dwCreationFlags |= NativeMethods.CREATE_UNICODE_ENVIRONMENT;
|
||||
}
|
||||
else
|
||||
{
|
||||
pEnv = IntPtr.Zero;
|
||||
}
|
||||
|
||||
_ = NativeMethods.CreateProcessAsUser(
|
||||
hUserTokenDup, // client's access token
|
||||
null, // file to execute
|
||||
commandLineWithArg, // command line
|
||||
ref sa, // pointer to process SECURITY_ATTRIBUTES
|
||||
ref sa, // pointer to thread SECURITY_ATTRIBUTES
|
||||
false, // handles are not inheritable
|
||||
(int)dwCreationFlags, // creation flags
|
||||
pEnv, // pointer to new environment block
|
||||
null, // name of current directory
|
||||
ref si, // pointer to STARTUPINFO structure
|
||||
out NativeMethods.PROCESS_INFORMATION pi); // receives information about new process
|
||||
|
||||
// GetLastError should be 0
|
||||
int iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
|
||||
Logger.LogDebug("CreateProcessAsUser returned " + iResultOfCreateProcessAsUser.ToString(CultureInfo.CurrentCulture));
|
||||
|
||||
// Close handles task
|
||||
_ = NativeMethods.CloseHandle(hUserToken);
|
||||
_ = NativeMethods.CloseHandle(hUserTokenDup);
|
||||
|
||||
return (iResultOfCreateProcessAsUser == 0) ? (int)pi.dwProcessId : 0;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if CUSTOMIZE_LOGON_SCREEN
|
||||
internal static bool CreateLowIntegrityProcess(string commandLine, string args, int wait, bool killIfTimedOut, long limitedMem, short wShowWindow = 0)
|
||||
{
|
||||
int processId = CreateProcessInInputDesktopSession(commandLine, args, "default", wShowWindow, true);
|
||||
|
||||
if (processId <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wait > 0)
|
||||
{
|
||||
if (limitedMem > 0)
|
||||
{
|
||||
int sec = 0;
|
||||
while (true)
|
||||
{
|
||||
Process p;
|
||||
|
||||
try
|
||||
{
|
||||
if ((p = Process.GetProcessById(processId)) == null)
|
||||
{
|
||||
Logger.Log("Process exited!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
Logger.Log("GetProcessById.ArgumentException");
|
||||
break;
|
||||
}
|
||||
|
||||
if ((!p.HasExited && p.PrivateMemorySize64 > limitedMem) || (++sec > (wait / 1000)))
|
||||
{
|
||||
Logger.Log(string.Format(CultureInfo.CurrentCulture, "Process log (mem): {0}, {1}", sec, p.PrivateMemorySize64));
|
||||
return false;
|
||||
}
|
||||
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Process p;
|
||||
|
||||
if ((p = Process.GetProcessById(processId)) == null)
|
||||
{
|
||||
Logger.Log("Process exited!");
|
||||
}
|
||||
else if (NativeMethods.WaitForSingleObject(p.Handle, wait) != NativeMethods.WAIT_OBJECT_0 && killIfTimedOut)
|
||||
{
|
||||
Logger.Log("Process log (time).");
|
||||
TerminateProcessTree(p.Handle, (uint)processId, -1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void TerminateProcessTree(IntPtr hProcess, uint processID, int exitCode)
|
||||
{
|
||||
if (processID > 0 && hProcess.ToInt32() > 0)
|
||||
{
|
||||
Process[] processes = Process.GetProcesses();
|
||||
int dwSessionId = Process.GetCurrentProcess().SessionId;
|
||||
|
||||
foreach (Process p in processes)
|
||||
{
|
||||
if (p.SessionId == dwSessionId)
|
||||
{
|
||||
NativeMethods.PROCESS_BASIC_INFORMATION processBasicInformation = default;
|
||||
|
||||
try
|
||||
{
|
||||
if (NativeMethods.NtQueryInformationProcess(p.Handle, 0, ref processBasicInformation, (uint)Marshal.SizeOf(processBasicInformation), out uint bytesWritten) >= 0)
|
||||
{// NT_SUCCESS(...)
|
||||
if (processBasicInformation.InheritedFromUniqueProcessId == processID)
|
||||
{
|
||||
TerminateProcessTree(p.Handle, processBasicInformation.UniqueProcessId, exitCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
continue;
|
||||
}
|
||||
catch (Win32Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = NativeMethods.TerminateProcess(hProcess, (IntPtr)exitCode);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -1,161 +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.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.ServiceProcess;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
// <summary>
|
||||
// Service control code.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
using MouseWithoutBorders.Class;
|
||||
using MouseWithoutBorders.Core;
|
||||
|
||||
[module: SuppressMessage("Microsoft.Globalization", "CA1300:SpecifyMessageBoxOptions", Scope = "member", Target = "MouseWithoutBorders.Common.#StartMouseWithoutBordersService()", Justification = "Dotnet port with style preservation")]
|
||||
|
||||
namespace MouseWithoutBorders
|
||||
{
|
||||
internal partial class Common
|
||||
{
|
||||
private static bool shownErrMessage;
|
||||
private static DateTime lastStartServiceTime = DateTime.UtcNow;
|
||||
|
||||
internal static void StartMouseWithoutBordersService(string desktopToRunMouseWithoutBordersOn = null, string startTag1 = "byapp", string startTag2 = null)
|
||||
{
|
||||
// NOTE(@yuyoyuppe): the new flow assumes we run both mwb processes directly from the svc.
|
||||
if (Common.RunWithNoAdminRight || true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Log($"{nameof(StartMouseWithoutBordersService)}: {Logger.GetStackTrace(new StackTrace())}.");
|
||||
|
||||
Task task = Task.Run(() =>
|
||||
{
|
||||
Process[] ps = Process.GetProcessesByName("MouseWithoutBordersSvc");
|
||||
|
||||
if (ps.Length != 0)
|
||||
{
|
||||
if (DateTime.UtcNow - lastStartServiceTime < TimeSpan.FromSeconds(5))
|
||||
{
|
||||
Logger.Log($"{nameof(StartMouseWithoutBordersService)}: Aborted.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Process pp in ps)
|
||||
{
|
||||
Logger.Log(string.Format(CultureInfo.InvariantCulture, "Killing process MouseWithoutBordersSvc {0}.", pp.Id));
|
||||
pp.KillProcess();
|
||||
}
|
||||
}
|
||||
|
||||
lastStartServiceTime = DateTime.UtcNow;
|
||||
ServiceController service = new("MouseWithoutBordersSvc");
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Log("Starting " + service.ServiceName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (!shownErrMessage)
|
||||
{
|
||||
shownErrMessage = true;
|
||||
_ = MessageBox.Show(
|
||||
Application.ProductName + " is not installed yet, please run Setup.exe first!",
|
||||
Application.ProductName,
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
int c = 0;
|
||||
|
||||
while (service.Status != ServiceControllerStatus.Stopped && c++ < 5)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
service = new ServiceController("MouseWithoutBordersSvc");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(desktopToRunMouseWithoutBordersOn))
|
||||
{
|
||||
startTag2 ??= Process.GetCurrentProcess().SessionId.ToString(CultureInfo.InvariantCulture);
|
||||
service.Start(new string[] { startTag1, startTag2 });
|
||||
}
|
||||
else
|
||||
{
|
||||
service.Start(new string[] { desktopToRunMouseWithoutBordersOn });
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
|
||||
// ERROR_SERVICE_ALREADY_RUNNING
|
||||
if (!(shownErrMessage || ((e?.InnerException as Win32Exception)?.NativeErrorCode == 1056)))
|
||||
{
|
||||
shownErrMessage = true;
|
||||
_ = MessageBox.Show(
|
||||
"Cannot start service " + service.ServiceName + ": " + e.Message,
|
||||
Common.BinaryName,
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the task while not blocking the UI thread.
|
||||
do
|
||||
{
|
||||
MMSleep(1);
|
||||
|
||||
if (task.IsCanceled || task.IsCompleted || task.IsFaulted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
|
||||
internal static void StartServiceAndSendLogoffSignal()
|
||||
{
|
||||
try
|
||||
{
|
||||
Process[] p = Process.GetProcessesByName("winlogon");
|
||||
Process me = Process.GetCurrentProcess();
|
||||
string myWinlogon = p?.FirstOrDefault(item => item.SessionId == me.SessionId)?.Id.ToString(CultureInfo.InvariantCulture) ?? null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(myWinlogon))
|
||||
{
|
||||
StartMouseWithoutBordersService(null, "logoff");
|
||||
}
|
||||
else
|
||||
{
|
||||
StartMouseWithoutBordersService(null, "logoff", myWinlogon);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"{nameof(StartServiceAndSendLogoffSignal)}: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,7 +269,7 @@ namespace MouseWithoutBorders
|
||||
if (!Common.RunWithNoAdminRight)
|
||||
{
|
||||
Logger.LogDebug("*** Starting on active Desktop: " + desktopToRunMouseWithoutBordersOn);
|
||||
StartMouseWithoutBordersService(desktopToRunMouseWithoutBordersOn);
|
||||
Service.StartMouseWithoutBordersService(desktopToRunMouseWithoutBordersOn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ namespace MouseWithoutBorders
|
||||
{
|
||||
if (!IsMyDesktopActive() || Common.CurrentProcess.SessionId != NativeMethods.WTSGetActiveConsoleSessionId())
|
||||
{
|
||||
Common.RunDDHelper(true);
|
||||
Helper.RunDDHelper(true);
|
||||
int waitCount = 20;
|
||||
|
||||
while (NativeMethods.WTSGetActiveConsoleSessionId() == 0xFFFFFFFF && waitCount > 0)
|
||||
|
||||
@@ -101,8 +101,10 @@ namespace MouseWithoutBorders
|
||||
private static bool runOnLogonDesktop;
|
||||
private static bool runOnScrSaverDesktop;
|
||||
|
||||
private static int[] toggleIcons;
|
||||
private static int toggleIconsIndex;
|
||||
#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
|
||||
internal static int[] toggleIcons;
|
||||
internal static int toggleIconsIndex;
|
||||
#pragma warning restore SA1307
|
||||
internal const int TOGGLE_ICONS_SIZE = 4;
|
||||
internal const int ICON_ONE = 0;
|
||||
internal const int ICON_ALL = 1;
|
||||
@@ -114,7 +116,9 @@ namespace MouseWithoutBorders
|
||||
internal const int NETWORK_STREAM_BUF_SIZE = 1024 * 1024;
|
||||
internal static readonly EventWaitHandle EvSwitch = new(false, EventResetMode.AutoReset);
|
||||
private static Point lastPos;
|
||||
private static int switchCount;
|
||||
#pragma warning disable SA1307 // Accessible fields should begin with upper-case names
|
||||
internal static int switchCount;
|
||||
#pragma warning restore SA1307
|
||||
private static long lastReconnectByHotKeyTime;
|
||||
private static int tcpPort;
|
||||
private static bool secondOpenSocketTry;
|
||||
@@ -543,7 +547,7 @@ namespace MouseWithoutBorders
|
||||
internal static void SendAwakeBeat()
|
||||
{
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && Common.IsMyDesktopActive() &&
|
||||
Setting.Values.BlockScreenSaver && lastRealInputEventCount != Common.RealInputEventCount)
|
||||
Setting.Values.BlockScreenSaver && lastRealInputEventCount != Event.RealInputEventCount)
|
||||
{
|
||||
SendPackage(ID.ALL, PackageType.Awake);
|
||||
}
|
||||
@@ -552,13 +556,13 @@ namespace MouseWithoutBorders
|
||||
SendHeartBeat();
|
||||
}
|
||||
|
||||
lastInputEventCount = Common.InputEventCount;
|
||||
lastRealInputEventCount = Common.RealInputEventCount;
|
||||
lastInputEventCount = Event.InputEventCount;
|
||||
lastRealInputEventCount = Event.RealInputEventCount;
|
||||
}
|
||||
|
||||
internal static void HumanBeingDetected()
|
||||
{
|
||||
if (lastInputEventCount == Common.InputEventCount)
|
||||
if (lastInputEventCount == Event.InputEventCount)
|
||||
{
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && Common.IsMyDesktopActive())
|
||||
{
|
||||
@@ -566,7 +570,7 @@ namespace MouseWithoutBorders
|
||||
}
|
||||
}
|
||||
|
||||
lastInputEventCount = Common.InputEventCount;
|
||||
lastInputEventCount = Event.InputEventCount;
|
||||
}
|
||||
|
||||
private static void PokeMyself()
|
||||
@@ -581,7 +585,7 @@ namespace MouseWithoutBorders
|
||||
InputSimulation.MoveMouseRelative(-x, -y);
|
||||
Thread.Sleep(50);
|
||||
|
||||
if (lastInputEventCount != Common.InputEventCount)
|
||||
if (lastInputEventCount != Event.InputEventCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -590,8 +594,8 @@ namespace MouseWithoutBorders
|
||||
|
||||
internal static void InitLastInputEventCount()
|
||||
{
|
||||
lastInputEventCount = Common.InputEventCount;
|
||||
lastRealInputEventCount = Common.RealInputEventCount;
|
||||
lastInputEventCount = Event.InputEventCount;
|
||||
lastRealInputEventCount = Event.RealInputEventCount;
|
||||
}
|
||||
|
||||
internal static void SendHello()
|
||||
@@ -665,7 +669,7 @@ namespace MouseWithoutBorders
|
||||
{
|
||||
Common.DoSomethingInUIThread(() =>
|
||||
{
|
||||
if (!DragDrop.MouseDown && Common.SendMessageToHelper(0x401, IntPtr.Zero, IntPtr.Zero) > 0)
|
||||
if (!DragDrop.MouseDown && Helper.SendMessageToHelper(0x401, IntPtr.Zero, IntPtr.Zero) > 0)
|
||||
{
|
||||
Common.MMSleep(0.2);
|
||||
InputSimulation.SendKey(new KEYBDDATA() { wVk = (int)VK.SNAPSHOT });
|
||||
@@ -674,14 +678,14 @@ namespace MouseWithoutBorders
|
||||
Logger.LogDebug("PrepareScreenCapture: SNAPSHOT simulated.");
|
||||
|
||||
_ = NativeMethods.MoveWindow(
|
||||
(IntPtr)NativeMethods.FindWindow(null, Common.HELPER_FORM_TEXT),
|
||||
(IntPtr)NativeMethods.FindWindow(null, Helper.HELPER_FORM_TEXT),
|
||||
MachineStuff.DesktopBounds.Left,
|
||||
MachineStuff.DesktopBounds.Top,
|
||||
MachineStuff.DesktopBounds.Right - MachineStuff.DesktopBounds.Left,
|
||||
MachineStuff.DesktopBounds.Bottom - MachineStuff.DesktopBounds.Top,
|
||||
false);
|
||||
|
||||
_ = Common.SendMessageToHelper(0x406, IntPtr.Zero, IntPtr.Zero, false);
|
||||
_ = Helper.SendMessageToHelper(0x406, IntPtr.Zero, IntPtr.Zero, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -698,7 +702,7 @@ namespace MouseWithoutBorders
|
||||
// {
|
||||
// Process.Start("explorer", "\"" + file + "\"");
|
||||
// });
|
||||
_ = CreateProcessInInputDesktopSession(
|
||||
_ = Launch.CreateProcessInInputDesktopSession(
|
||||
"\"" + Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") +
|
||||
"\"",
|
||||
"\"" + file + "\"",
|
||||
@@ -956,8 +960,8 @@ namespace MouseWithoutBorders
|
||||
{
|
||||
// SwitchToMachine(MachineName.Trim());
|
||||
MachineStuff.NewDesMachineID = DesMachineID = MachineID;
|
||||
MachineStuff.SwitchLocation.X = XY_BY_PIXEL + myLastX;
|
||||
MachineStuff.SwitchLocation.Y = XY_BY_PIXEL + myLastY;
|
||||
MachineStuff.SwitchLocation.X = Event.XY_BY_PIXEL + Event.myLastX;
|
||||
MachineStuff.SwitchLocation.Y = Event.XY_BY_PIXEL + Event.myLastY;
|
||||
MachineStuff.SwitchLocation.ResetCount();
|
||||
EvSwitch.Set();
|
||||
}
|
||||
@@ -1314,7 +1318,7 @@ namespace MouseWithoutBorders
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = ImpersonateLoggedOnUserAndDoSomething(() =>
|
||||
_ = Launch.ImpersonateLoggedOnUserAndDoSomething(() =>
|
||||
{
|
||||
st = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\" + Common.BinaryName;
|
||||
if (!Directory.Exists(st))
|
||||
|
||||
@@ -206,7 +206,7 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
int rv = 1, dx = 0, dy = 0;
|
||||
bool local = false;
|
||||
Common.InputEventCount++;
|
||||
Event.InputEventCount++;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -220,14 +220,14 @@ namespace MouseWithoutBorders.Class
|
||||
}
|
||||
else
|
||||
{
|
||||
Common.RealInputEventCount++;
|
||||
Event.RealInputEventCount++;
|
||||
|
||||
if (MachineStuff.NewDesMachineID == Common.MachineID || MachineStuff.NewDesMachineID == ID.ALL)
|
||||
{
|
||||
local = true;
|
||||
if (Common.MainFormVisible && !DragDrop.IsDropping)
|
||||
{
|
||||
Common.MainFormDot();
|
||||
Helper.MainFormDot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,10 +269,10 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
MachineStuff.SwitchLocation.Count--;
|
||||
|
||||
if (MachineStuff.SwitchLocation.X > Common.XY_BY_PIXEL - 100000 || MachineStuff.SwitchLocation.Y > Common.XY_BY_PIXEL - 100000)
|
||||
if (MachineStuff.SwitchLocation.X > Event.XY_BY_PIXEL - 100000 || MachineStuff.SwitchLocation.Y > Event.XY_BY_PIXEL - 100000)
|
||||
{
|
||||
hookCallbackMouseData.X = MachineStuff.SwitchLocation.X - Common.XY_BY_PIXEL;
|
||||
hookCallbackMouseData.Y = MachineStuff.SwitchLocation.Y - Common.XY_BY_PIXEL;
|
||||
hookCallbackMouseData.X = MachineStuff.SwitchLocation.X - Event.XY_BY_PIXEL;
|
||||
hookCallbackMouseData.Y = MachineStuff.SwitchLocation.Y - Event.XY_BY_PIXEL;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -308,8 +308,8 @@ namespace MouseWithoutBorders.Class
|
||||
hookCallbackMouseData.Y = MachineStuff.PrimaryScreenBounds.Bottom + 1;
|
||||
}
|
||||
|
||||
dx += dx < 0 ? -Common.MOVE_MOUSE_RELATIVE : Common.MOVE_MOUSE_RELATIVE;
|
||||
dy += dy < 0 ? -Common.MOVE_MOUSE_RELATIVE : Common.MOVE_MOUSE_RELATIVE;
|
||||
dx += dx < 0 ? -Event.MOVE_MOUSE_RELATIVE : Event.MOVE_MOUSE_RELATIVE;
|
||||
dy += dy < 0 ? -Event.MOVE_MOUSE_RELATIVE : Event.MOVE_MOUSE_RELATIVE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,13 +336,13 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
private int KeyboardHookProc(int nCode, int wParam, IntPtr lParam)
|
||||
{
|
||||
Common.InputEventCount++;
|
||||
Event.InputEventCount++;
|
||||
if (!RealData)
|
||||
{
|
||||
return NativeMethods.CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
Common.RealInputEventCount++;
|
||||
Event.RealInputEventCount++;
|
||||
|
||||
keyboardHookStruct = LParamToKeyboardHookStruct(lParam);
|
||||
hookCallbackKeybdData.dwFlags = keyboardHookStruct.Flags;
|
||||
|
||||
@@ -223,7 +223,7 @@ namespace MouseWithoutBorders.Class
|
||||
|
||||
if (Common.MainFormVisible && !DragDrop.IsDropping)
|
||||
{
|
||||
Common.MainFormDot();
|
||||
Helper.MainFormDot();
|
||||
}
|
||||
|
||||
return rv;
|
||||
|
||||
@@ -36,6 +36,7 @@ using Newtonsoft.Json;
|
||||
using StreamJsonRpc;
|
||||
|
||||
using Logger = MouseWithoutBorders.Core.Logger;
|
||||
using SettingsHelper = Microsoft.PowerToys.Settings.UI.Library.Utilities.Helper;
|
||||
using Thread = MouseWithoutBorders.Core.Thread;
|
||||
|
||||
[module: SuppressMessage("Microsoft.MSInternal", "CA904:DeclareTypesInMicrosoftOrSystemNamespace", Scope = "namespace", Target = "MouseWithoutBorders", Justification = "Dotnet port with style preservation")]
|
||||
@@ -128,7 +129,7 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
if (args.Length > 2)
|
||||
{
|
||||
Helper.UserLocalAppDataPath = args[2].Trim();
|
||||
SettingsHelper.UserLocalAppDataPath = args[2].Trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +236,7 @@ namespace MouseWithoutBorders.Class
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
Common.Init();
|
||||
Common.WndProcCounter++;
|
||||
Core.Helper.WndProcCounter++;
|
||||
|
||||
var formScreen = new FrmScreen();
|
||||
|
||||
@@ -430,7 +431,7 @@ namespace MouseWithoutBorders.Class
|
||||
Logger.Log(e);
|
||||
}
|
||||
|
||||
Common.StartMouseWithoutBordersService();
|
||||
Service.StartMouseWithoutBordersService();
|
||||
}
|
||||
|
||||
internal static string User { get; set; }
|
||||
|
||||
@@ -33,6 +33,7 @@ using MouseWithoutBorders.Core;
|
||||
using Settings.UI.Library.Attributes;
|
||||
|
||||
using Lock = System.Threading.Lock;
|
||||
using SettingsHelper = Microsoft.PowerToys.Settings.UI.Library.Utilities.Helper;
|
||||
|
||||
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Properties.Setting.Values.#LoadIntSetting(System.String,System.Int32)", Justification = "Dotnet port with style preservation")]
|
||||
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Properties.Setting.Values.#SaveSetting(System.String,System.Object)", Justification = "Dotnet port with style preservation")]
|
||||
@@ -193,7 +194,7 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
_settingsUtils = new SettingsUtils();
|
||||
|
||||
_watcher = Helper.GetFileWatcher("MouseWithoutBorders", "settings.json", () =>
|
||||
_watcher = SettingsHelper.GetFileWatcher("MouseWithoutBorders", "settings.json", () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1684,7 +1684,7 @@ namespace MouseWithoutBorders.Class
|
||||
{
|
||||
string fileName = null;
|
||||
|
||||
if (!Common.ImpersonateLoggedOnUserAndDoSomething(() =>
|
||||
if (!Launch.ImpersonateLoggedOnUserAndDoSomething(() =>
|
||||
{
|
||||
if (!File.Exists(Common.LastDragDropFile))
|
||||
{
|
||||
@@ -1873,7 +1873,7 @@ namespace MouseWithoutBorders.Class
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = Common.ImpersonateLoggedOnUserAndDoSomething(() => { r = SendFileEx(s, ecStream, fileName); });
|
||||
_ = Launch.ImpersonateLoggedOnUserAndDoSomething(() => { r = SendFileEx(s, ecStream, fileName); });
|
||||
}
|
||||
|
||||
return r;
|
||||
@@ -2111,7 +2111,7 @@ namespace MouseWithoutBorders.Class
|
||||
}
|
||||
}
|
||||
|
||||
_ = Common.CreateLowIntegrityProcess(
|
||||
_ = Launch.CreateLowIntegrityProcess(
|
||||
"\"" + Path.GetDirectoryName(Application.ExecutablePath) + "\\MouseWithoutBordersHelper.exe\"",
|
||||
"InternalError" + " \"" + msg + "\"",
|
||||
0,
|
||||
|
||||
@@ -132,7 +132,7 @@ internal static class DragDrop
|
||||
{
|
||||
if (!IsDropping)
|
||||
{
|
||||
IntPtr h = (IntPtr)NativeMethods.FindWindow(null, Common.HELPER_FORM_TEXT);
|
||||
IntPtr h = (IntPtr)NativeMethods.FindWindow(null, Helper.HELPER_FORM_TEXT);
|
||||
if (h.ToInt32() > 0)
|
||||
{
|
||||
_ = Interlocked.Exchange(ref dragDropStep05ExCalledByIpc, 0);
|
||||
@@ -189,7 +189,7 @@ internal static class DragDrop
|
||||
|
||||
if (!IsDropping)
|
||||
{
|
||||
_ = Common.ImpersonateLoggedOnUserAndDoSomething(() =>
|
||||
_ = Launch.ImpersonateLoggedOnUserAndDoSomething(() =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(dragFileName) && (File.Exists(dragFileName) || Directory.Exists(dragFileName)))
|
||||
{
|
||||
|
||||
274
src/modules/MouseWithoutBorders/App/Core/Event.cs
Normal file
274
src/modules/MouseWithoutBorders/App/Core/Event.cs
Normal file
@@ -0,0 +1,274 @@
|
||||
// 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.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using MouseWithoutBorders.Class;
|
||||
|
||||
// <summary>
|
||||
// Keyboard/Mouse hook callback implementation.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal static class Event
|
||||
{
|
||||
private static readonly DATA KeybdPackage = new();
|
||||
private static readonly DATA MousePackage = new();
|
||||
#pragma warning disable SA1307 // Accessible fields should begin with upper-case names
|
||||
internal static ulong inputEventCount;
|
||||
internal static ulong invalidPackageCount;
|
||||
#pragma warning restore SA1307
|
||||
internal static int MOVE_MOUSE_RELATIVE = 100000;
|
||||
internal static int XY_BY_PIXEL = 300000;
|
||||
|
||||
static Event()
|
||||
{
|
||||
}
|
||||
|
||||
internal static ulong InvalidPackageCount
|
||||
{
|
||||
get => Event.invalidPackageCount;
|
||||
set => Event.invalidPackageCount = value;
|
||||
}
|
||||
|
||||
internal static ulong InputEventCount
|
||||
{
|
||||
get => Event.inputEventCount;
|
||||
set => Event.inputEventCount = value;
|
||||
}
|
||||
|
||||
internal static ulong RealInputEventCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private static Point actualLastPos;
|
||||
#pragma warning disable SA1307 // Accessible fields should begin with upper-case names
|
||||
internal static int myLastX;
|
||||
internal static int myLastY;
|
||||
#pragma warning restore SA1307
|
||||
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Dotnet port with style preservation")]
|
||||
internal static void MouseEvent(MOUSEDATA e, int dx, int dy)
|
||||
{
|
||||
try
|
||||
{
|
||||
Common.PaintCount = 0;
|
||||
bool switchByMouseEnabled = IsSwitchingByMouseEnabled();
|
||||
|
||||
if (switchByMouseEnabled && Common.Sk != null && (Common.DesMachineID == Common.MachineID || !Setting.Values.MoveMouseRelatively) && e.dwFlags == Common.WM_MOUSEMOVE)
|
||||
{
|
||||
Point p = MachineStuff.MoveToMyNeighbourIfNeeded(e.X, e.Y, MachineStuff.desMachineID);
|
||||
|
||||
if (!p.IsEmpty)
|
||||
{
|
||||
Common.HasSwitchedMachineSinceLastCopy = true;
|
||||
|
||||
Logger.LogDebug(string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"***** Host Machine: newDesMachineIdEx set = [{0}]. Mouse is now at ({1},{2})",
|
||||
MachineStuff.newDesMachineIdEx,
|
||||
e.X,
|
||||
e.Y));
|
||||
|
||||
myLastX = e.X;
|
||||
myLastY = e.Y;
|
||||
|
||||
PrepareToSwitchToMachine(MachineStuff.newDesMachineIdEx, p);
|
||||
}
|
||||
}
|
||||
|
||||
if (MachineStuff.desMachineID != Common.MachineID && MachineStuff.SwitchLocation.Count <= 0)
|
||||
{
|
||||
MousePackage.Des = MachineStuff.desMachineID;
|
||||
MousePackage.Type = PackageType.Mouse;
|
||||
MousePackage.Md.dwFlags = e.dwFlags;
|
||||
MousePackage.Md.WheelDelta = e.WheelDelta;
|
||||
|
||||
// Relative move
|
||||
if (Setting.Values.MoveMouseRelatively && Math.Abs(dx) >= MOVE_MOUSE_RELATIVE && Math.Abs(dy) >= MOVE_MOUSE_RELATIVE)
|
||||
{
|
||||
MousePackage.Md.X = dx;
|
||||
MousePackage.Md.Y = dy;
|
||||
}
|
||||
else
|
||||
{
|
||||
MousePackage.Md.X = (e.X - MachineStuff.primaryScreenBounds.Left) * 65535 / Common.screenWidth;
|
||||
MousePackage.Md.Y = (e.Y - MachineStuff.primaryScreenBounds.Top) * 65535 / Common.screenHeight;
|
||||
}
|
||||
|
||||
Common.SkSend(MousePackage, null, false);
|
||||
|
||||
if (MousePackage.Md.dwFlags is Common.WM_LBUTTONUP or Common.WM_RBUTTONUP)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
|
||||
NativeMethods.GetCursorPos(ref actualLastPos);
|
||||
|
||||
if (actualLastPos != Common.LastPos)
|
||||
{
|
||||
Logger.LogDebug($"Mouse cursor has moved unexpectedly: Expected: {Common.LastPos}, actual: {actualLastPos}.");
|
||||
Common.LastPos = actualLastPos;
|
||||
}
|
||||
}
|
||||
|
||||
#if SHOW_ON_WINLOGON_EX
|
||||
if (RunOnLogonDesktop && e.dwFlags == WM_RBUTTONUP &&
|
||||
desMachineID == machineID &&
|
||||
e.x > 2 && e.x < 100 && e.y > 2 && e.y < 20)
|
||||
{
|
||||
DoSomethingInUIThread(delegate()
|
||||
{
|
||||
MainForm.HideMenuWhenRunOnLogonDesktop();
|
||||
MainForm.MainMenu.Hide();
|
||||
MainForm.MainMenu.Show(e.x - 5, e.y - 3);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsSwitchingByMouseEnabled()
|
||||
{
|
||||
return (EasyMouseOption)Setting.Values.EasyMouse == EasyMouseOption.Enable || InputHook.EasyMouseKeyDown;
|
||||
}
|
||||
|
||||
internal static void PrepareToSwitchToMachine(ID newDesMachineID, Point desMachineXY)
|
||||
{
|
||||
Logger.LogDebug($"PrepareToSwitchToMachine: newDesMachineID = {newDesMachineID}, desMachineXY = {desMachineXY}");
|
||||
|
||||
if (((Common.GetTick() - MachineStuff.lastJump < 100) && (Common.GetTick() - MachineStuff.lastJump > 0)) || MachineStuff.desMachineID == ID.ALL)
|
||||
{
|
||||
Logger.LogDebug("PrepareToSwitchToMachine: lastJump");
|
||||
return;
|
||||
}
|
||||
|
||||
MachineStuff.lastJump = Common.GetTick();
|
||||
|
||||
string newDesMachineName = MachineStuff.NameFromID(newDesMachineID);
|
||||
|
||||
if (!Common.IsConnectedTo(newDesMachineID))
|
||||
{// Connection lost, cancel switching
|
||||
Logger.LogDebug("No active connection found for " + newDesMachineName);
|
||||
|
||||
// ShowToolTip("No active connection found for [" + newDesMachineName + "]!", 500);
|
||||
}
|
||||
else
|
||||
{
|
||||
MachineStuff.newDesMachineID = newDesMachineID;
|
||||
MachineStuff.SwitchLocation.X = desMachineXY.X;
|
||||
MachineStuff.SwitchLocation.Y = desMachineXY.Y;
|
||||
MachineStuff.SwitchLocation.ResetCount();
|
||||
_ = Common.EvSwitch.Set();
|
||||
|
||||
// PostMessage(mainForm.Handle, WM_SWITCH, IntPtr.Zero, IntPtr.Zero);
|
||||
if (newDesMachineID != DragDrop.DragMachine)
|
||||
{
|
||||
if (!DragDrop.IsDragging && !DragDrop.IsDropping)
|
||||
{
|
||||
if (DragDrop.MouseDown && !Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
DragDrop.DragDropStep02();
|
||||
}
|
||||
}
|
||||
else if (DragDrop.DragMachine != (ID)1)
|
||||
{
|
||||
DragDrop.ChangeDropMachine();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DragDrop.DragDropStep11();
|
||||
}
|
||||
|
||||
// Change des machine
|
||||
if (MachineStuff.desMachineID != newDesMachineID)
|
||||
{
|
||||
Logger.LogDebug("MouseEvent: Switching to new machine:" + newDesMachineName);
|
||||
|
||||
// Ask current machine to hide the Mouse cursor
|
||||
if (newDesMachineID != ID.ALL && MachineStuff.desMachineID != Common.MachineID)
|
||||
{
|
||||
Common.SendPackage(MachineStuff.desMachineID, PackageType.HideMouse);
|
||||
}
|
||||
|
||||
Common.DesMachineID = newDesMachineID;
|
||||
|
||||
if (MachineStuff.desMachineID == Common.MachineID)
|
||||
{
|
||||
if (Common.GetTick() - Common.clipboardCopiedTime < Common.BIG_CLIPBOARD_DATA_TIMEOUT)
|
||||
{
|
||||
Common.clipboardCopiedTime = 0;
|
||||
Common.GetRemoteClipboard("PrepareToSwitchToMachine");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ask the new active machine to get clipboard data (if the data is too big)
|
||||
Common.SendPackage(MachineStuff.desMachineID, PackageType.MachineSwitched);
|
||||
}
|
||||
|
||||
_ = Interlocked.Increment(ref Common.switchCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SaveSwitchCount()
|
||||
{
|
||||
if (Common.SwitchCount > 0)
|
||||
{
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
Setting.Values.SwitchCount += Common.SwitchCount;
|
||||
_ = Interlocked.Exchange(ref Common.switchCount, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
internal static void KeybdEvent(KEYBDDATA e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Common.PaintCount = 0;
|
||||
if (MachineStuff.desMachineID != MachineStuff.newDesMachineID)
|
||||
{
|
||||
Logger.LogDebug("KeybdEvent: Switching to new machine...");
|
||||
Common.DesMachineID = MachineStuff.newDesMachineID;
|
||||
}
|
||||
|
||||
if (MachineStuff.desMachineID != Common.MachineID)
|
||||
{
|
||||
KeybdPackage.Des = MachineStuff.desMachineID;
|
||||
KeybdPackage.Type = PackageType.Keyboard;
|
||||
KeybdPackage.Kd = e;
|
||||
KeybdPackage.DateTime = Common.GetTick();
|
||||
Common.SkSend(KeybdPackage, null, false);
|
||||
if (KeybdPackage.Kd.dwFlags is Common.WM_KEYUP or Common.WM_SYSKEYUP)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
526
src/modules/MouseWithoutBorders/App/Core/Helper.cs
Normal file
526
src/modules/MouseWithoutBorders/App/Core/Helper.cs
Normal file
@@ -0,0 +1,526 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Microsoft.Win32;
|
||||
using MouseWithoutBorders.Class;
|
||||
using static System.Windows.Forms.Control;
|
||||
|
||||
// <summary>
|
||||
// Some other helper methods.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal static class Helper
|
||||
{
|
||||
internal const string HELPER_FORM_TEXT = "Mouse without Borders Helper";
|
||||
internal const string HelperProcessName = "PowerToys.MouseWithoutBordersHelper";
|
||||
#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
|
||||
internal static bool signalHelperToExit;
|
||||
internal static bool signalWatchDogToExit;
|
||||
#pragma warning restore SA1307
|
||||
internal static long WndProcCounter;
|
||||
|
||||
private static void WatchDogThread()
|
||||
{
|
||||
long oldCounter = WndProcCounter;
|
||||
|
||||
do
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
|
||||
if (signalWatchDogToExit)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (Common.BlockingUI)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
if (WndProcCounter == oldCounter)
|
||||
{
|
||||
Process p = Process.GetCurrentProcess();
|
||||
string procInfo = $"{p.PrivateMemorySize64 / 1024 / 1024}MB, {p.TotalProcessorTime}, {Environment.ProcessorCount}.";
|
||||
string threadStacks = $"{procInfo} {Thread.DumpThreadsStack()}";
|
||||
Logger.TelemetryLogTrace(threadStacks, SeverityLevel.Error);
|
||||
break;
|
||||
}
|
||||
|
||||
oldCounter = WndProcCounter;
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
|
||||
internal static void HelperThread()
|
||||
{
|
||||
// SuppressFlow fixes an issue on service mode, where the helper process can't get enough permissions to be started again.
|
||||
// More details can be found on: https://github.com/microsoft/PowerToys/pull/36892
|
||||
using var asyncFlowControl = System.Threading.ExecutionContext.SuppressFlow();
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_ = Common.EvSwitch.WaitOne(); // Switching to another machine?
|
||||
|
||||
if (signalHelperToExit)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (MachineStuff.NewDesMachineID != Common.MachineID && MachineStuff.NewDesMachineID != ID.ALL)
|
||||
{
|
||||
Common.HideMouseCursor(false);
|
||||
Helper.MainFormDotEx(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MachineStuff.SwitchLocation.Count > 0)
|
||||
{
|
||||
MachineStuff.SwitchLocation.Count--;
|
||||
|
||||
// When we want to move mouse by pixels, we add 300k to x and y (search for XY_BY_PIXEL for other related code).
|
||||
Logger.LogDebug($"+++++ Moving mouse to {MachineStuff.SwitchLocation.X}, {MachineStuff.SwitchLocation.Y}");
|
||||
|
||||
// MaxXY = 65535 so 100k is safe.
|
||||
if (MachineStuff.SwitchLocation.X > Event.XY_BY_PIXEL - 100000 || MachineStuff.SwitchLocation.Y > Event.XY_BY_PIXEL - 100000)
|
||||
{
|
||||
InputSimulation.MoveMouse(MachineStuff.SwitchLocation.X - Event.XY_BY_PIXEL, MachineStuff.SwitchLocation.Y - Event.XY_BY_PIXEL);
|
||||
}
|
||||
else
|
||||
{
|
||||
InputSimulation.MoveMouseEx(MachineStuff.SwitchLocation.X, MachineStuff.SwitchLocation.Y);
|
||||
}
|
||||
|
||||
Helper.MainFormDot();
|
||||
}
|
||||
}
|
||||
|
||||
if (MachineStuff.NewDesMachineID == Common.MachineID)
|
||||
{
|
||||
Common.ReleaseAllKeys();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
|
||||
signalHelperToExit = false;
|
||||
Logger.LogDebug("^^^Helper Thread exiting...^^^");
|
||||
}
|
||||
|
||||
internal static void MainFormDotEx(bool bCheckTS)
|
||||
{
|
||||
Logger.LogDebug("***** MainFormDotEx:");
|
||||
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
int left = MachineStuff.PrimaryScreenBounds.Left + ((MachineStuff.PrimaryScreenBounds.Right - MachineStuff.PrimaryScreenBounds.Left) / 2) - 1;
|
||||
int top = Setting.Values.HideMouse ? 3 : MachineStuff.PrimaryScreenBounds.Top + ((MachineStuff.PrimaryScreenBounds.Bottom - MachineStuff.PrimaryScreenBounds.Top) / 2);
|
||||
|
||||
Common.MainFormVisible = true;
|
||||
|
||||
if (Setting.Values.HideMouse && Setting.Values.StealFocusWhenSwitchingMachine && Helper.SendMessageToHelper(0x407, new IntPtr(left), new IntPtr(top), true) == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
/* When user just switches to the Logon desktop, user is actually on the "Windows Default Lock Screen" (LockApp).
|
||||
* If a click is sent to this during switch, it actually triggers a desktop switch on the local machine causing a reconnection affecting the machine switch.
|
||||
* We can detect and skip in this case.
|
||||
* */
|
||||
IntPtr foreGroundWindow = NativeMethods.GetForegroundWindow();
|
||||
string foreGroundWindowText = Common.GetText(foreGroundWindow);
|
||||
|
||||
bool mouseClick = true;
|
||||
|
||||
if (foreGroundWindowText.Equals("Windows Default Lock Screen", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mouseClick = false;
|
||||
}
|
||||
|
||||
// Window title may be localized, check process name:
|
||||
if (mouseClick)
|
||||
{
|
||||
_ = NativeMethods.GetWindowThreadProcessId(foreGroundWindow, out uint pid);
|
||||
|
||||
if (pid > 0)
|
||||
{
|
||||
string foreGroundWindowProcess = Process.GetProcessById((int)pid)?.ProcessName;
|
||||
|
||||
if (foreGroundWindowProcess.Equals("LockApp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mouseClick = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mouseClick)
|
||||
{
|
||||
InputSimulation.MouseClickDotForm(left + 1, top + 1);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomCursor.ShowFakeMouseCursor(int.MinValue, int.MinValue);
|
||||
}
|
||||
|
||||
internal static void MainForm3Pixels()
|
||||
{
|
||||
Logger.LogDebug("***** MainFormDotLarge:");
|
||||
|
||||
Common.DoSomethingInUIThread(
|
||||
() =>
|
||||
{
|
||||
Common.MainForm.Left = MachineStuff.PrimaryScreenBounds.Left + ((MachineStuff.PrimaryScreenBounds.Right - MachineStuff.PrimaryScreenBounds.Left) / 2) - 2;
|
||||
Common.MainForm.Top = Setting.Values.HideMouse ? 3 : MachineStuff.PrimaryScreenBounds.Top + ((MachineStuff.PrimaryScreenBounds.Bottom - MachineStuff.PrimaryScreenBounds.Top) / 2) - 1;
|
||||
Common.MainForm.Width = 3;
|
||||
Common.MainForm.Height = 3;
|
||||
Common.MainForm.Opacity = 0.11D;
|
||||
Common.MainForm.TopMost = true;
|
||||
|
||||
if (Setting.Values.HideMouse)
|
||||
{
|
||||
Common.MainForm.BackColor = Color.Black;
|
||||
Common.MainForm.Show();
|
||||
Common.MainFormVisible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Common.MainForm.BackColor = Color.White;
|
||||
Common.MainForm.Hide();
|
||||
Common.MainFormVisible = false;
|
||||
}
|
||||
},
|
||||
true);
|
||||
|
||||
CustomCursor.ShowFakeMouseCursor(int.MinValue, int.MinValue);
|
||||
}
|
||||
|
||||
internal static void MainFormDot()
|
||||
{
|
||||
Common.DoSomethingInUIThread(
|
||||
() =>
|
||||
{
|
||||
_ = Helper.SendMessageToHelper(0x408, IntPtr.Zero, IntPtr.Zero, false);
|
||||
|
||||
Common.MainForm.Left = MachineStuff.PrimaryScreenBounds.Left + ((MachineStuff.PrimaryScreenBounds.Right - MachineStuff.PrimaryScreenBounds.Left) / 2) - 1;
|
||||
Common.MainForm.Top = Setting.Values.HideMouse ? 3 : MachineStuff.PrimaryScreenBounds.Top + ((MachineStuff.PrimaryScreenBounds.Bottom - MachineStuff.PrimaryScreenBounds.Top) / 2);
|
||||
Common.MainForm.Width = 1;
|
||||
Common.MainForm.Height = 1;
|
||||
Common.MainForm.Opacity = 0.15;
|
||||
Common.MainForm.Hide();
|
||||
Common.MainFormVisible = false;
|
||||
},
|
||||
true);
|
||||
|
||||
CustomCursor.ShowFakeMouseCursor(int.MinValue, int.MinValue);
|
||||
}
|
||||
|
||||
internal static void ToggleIcon()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Common.toggleIconsIndex < Common.TOGGLE_ICONS_SIZE)
|
||||
{
|
||||
Common.DoSomethingInUIThread(() => Common.MainForm.ChangeIcon(Common.toggleIcons[Common.toggleIconsIndex++]));
|
||||
}
|
||||
else
|
||||
{
|
||||
Common.toggleIconsIndex = 0;
|
||||
Common.toggleIcons = null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RunDDHelper(bool cleanUp = false)
|
||||
{
|
||||
if (Common.RunOnLogonDesktop || Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (cleanUp)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process[] ps = Process.GetProcessesByName(HelperProcessName);
|
||||
foreach (Process p in ps)
|
||||
{
|
||||
p.KillProcess();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
_ = Helper.SendMessageToHelper(SharedConst.QUIT_CMD, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Common.IsMyDesktopActive())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Common.IpcChannelCreated)
|
||||
{
|
||||
Logger.TelemetryLogTrace($"{nameof(Common.IpcChannelCreated)} = {Common.IpcChannelCreated}. {Logger.GetStackTrace(new StackTrace())}", SeverityLevel.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Common.MainForm.IsDisposed)
|
||||
{
|
||||
Common.MainForm.NotifyIcon.Visible = false;
|
||||
Common.MainForm.NotifyIcon.Visible = Setting.Values.ShowOriginalUI;
|
||||
}
|
||||
|
||||
IntPtr h = (IntPtr)NativeMethods.FindWindow(null, Helper.HELPER_FORM_TEXT);
|
||||
|
||||
if (h.ToInt32() <= 0)
|
||||
{
|
||||
_ = Launch.CreateProcessInInputDesktopSession(
|
||||
$"\"{Path.GetDirectoryName(Application.ExecutablePath)}\\{HelperProcessName}.exe\"",
|
||||
string.Empty,
|
||||
Common.GetInputDesktop(),
|
||||
0);
|
||||
|
||||
Common.HasSwitchedMachineSinceLastCopy = true;
|
||||
|
||||
// Common.CreateLowIntegrityProcess("\"" + Path.GetDirectoryName(Application.ExecutablePath) + "\\MouseWithoutBordersHelper.exe\"", string.Empty, 0, false, 0);
|
||||
var processes = Process.GetProcessesByName(HelperProcessName);
|
||||
if (processes?.Length == 0)
|
||||
{
|
||||
Logger.Log("Unable to start helper process.");
|
||||
Common.ShowToolTip("Error starting Mouse Without Borders Helper, clipboard sharing will not work!", 5000, ToolTipIcon.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("Helper process started.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var processes = Process.GetProcessesByName(HelperProcessName);
|
||||
if (processes?.Length > 0)
|
||||
{
|
||||
Logger.Log("Helper process found running.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("Invalid helper process found running.");
|
||||
Common.ShowToolTip("Error finding Mouse Without Borders Helper, clipboard sharing will not work!", 5000, ToolTipIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static int SendMessageToHelper(int msg, IntPtr wparam, IntPtr lparam, bool wait = true, bool log = true)
|
||||
{
|
||||
int h = NativeMethods.FindWindow(null, Helper.HELPER_FORM_TEXT);
|
||||
int rv = -1;
|
||||
|
||||
if (h > 0)
|
||||
{
|
||||
rv = wait
|
||||
? (int)NativeMethods.SendMessage((IntPtr)h, msg, wparam, lparam)
|
||||
: NativeMethods.PostMessage((IntPtr)h, msg, wparam, lparam) ? 1 : 0;
|
||||
}
|
||||
|
||||
if (log)
|
||||
{
|
||||
Logger.LogDebug($"SendMessageToHelper: HelperWindow={h}, Return={rv}, msg={msg}, w={wparam.ToInt32()}, l={lparam.ToInt32()}, Post={!wait}");
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
internal static bool IsWindows8AndUp()
|
||||
{
|
||||
return (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor >= 2)
|
||||
|| Environment.OSVersion.Version.Major > 6;
|
||||
}
|
||||
|
||||
internal static string GetMiniLog(IEnumerable<ControlCollection> optionControls)
|
||||
{
|
||||
string log = string.Empty;
|
||||
|
||||
log += "=============================================================================================================================\r\n";
|
||||
log += $"{Application.ProductName} version {Application.ProductVersion}\r\n";
|
||||
|
||||
log += $"{Setting.Values.Username}/{Common.GetDebugInfo(Common.MyKey)}\r\n";
|
||||
log += $"{Common.MachineName}/{Common.MachineID}/{Common.DesMachineID}\r\n";
|
||||
log += $"Id: {Setting.Values.DeviceId}\r\n";
|
||||
log += $"Matrix: {string.Join(",", MachineStuff.MachineMatrix)}\r\n";
|
||||
log += $"McPool: {Setting.Values.MachinePoolString}\r\n";
|
||||
|
||||
log += "\r\nOPTIONS:\r\n";
|
||||
|
||||
foreach (ControlCollection controlCollection in optionControls)
|
||||
{
|
||||
foreach (object c in controlCollection)
|
||||
{
|
||||
if (c is CheckBox checkBox)
|
||||
{
|
||||
log += $"({(checkBox.Checked ? 1 : 0)}) {checkBox.Text}\r\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c is RadioButton radioButton)
|
||||
{
|
||||
log += $"({(radioButton.Checked ? 1 : 0)}) {radioButton.Name}.[{radioButton.Text}]\r\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c is ComboBox comboBox)
|
||||
{
|
||||
log += $"{comboBox.Name} = {comboBox.Text}\r\n";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log += "\r\n";
|
||||
|
||||
SocketStuff sk = Common.Sk;
|
||||
|
||||
if (sk?.TcpSockets != null)
|
||||
{
|
||||
foreach (TcpSk tcp in sk.TcpSockets)
|
||||
{
|
||||
log += $"{Common.MachineName}{(tcp.IsClient ? "=>" : "<=")}{tcp.MachineName}({tcp.MachineId}):{tcp.Status}\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
log += string.Format(CultureInfo.CurrentCulture, "Helper:{0}\r\n", SendMessageToHelper(0x400, IntPtr.Zero, IntPtr.Zero));
|
||||
|
||||
log += Setting.Values.LastPersonalizeLogonScr + "\r\n";
|
||||
log += "Name2IP =\r\n" + Setting.Values.Name2IP + "\r\n";
|
||||
|
||||
log += "Last 10 trace messages:\r\n";
|
||||
|
||||
log += string.Join(Environment.NewLine, Logger.LogCounter.Select(item => $"({item.Value}): {item.Key}").Take(10));
|
||||
|
||||
log += "\r\n=============================================================================================================================";
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
internal static bool GetUserName()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Setting.Values.Username) && !Common.RunOnLogonDesktop)
|
||||
{
|
||||
if (Program.User.Contains("system", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
_ = Launch.ImpersonateLoggedOnUserAndDoSomething(() =>
|
||||
{
|
||||
// See: https://stackoverflow.com/questions/19487541/how-to-get-windows-user-name-from-sessionid
|
||||
static string GetUsernameBySessionId(int sessionId)
|
||||
{
|
||||
string username = "SYSTEM";
|
||||
if (NativeMethods.WTSQuerySessionInformation(IntPtr.Zero, sessionId, NativeMethods.WTSInfoClass.WTSUserName, out nint buffer, out int strLen) && strLen > 1)
|
||||
{
|
||||
username = Marshal.PtrToStringAnsi(buffer);
|
||||
NativeMethods.WTSFreeMemory(buffer);
|
||||
|
||||
if (NativeMethods.WTSQuerySessionInformation(IntPtr.Zero, sessionId, NativeMethods.WTSInfoClass.WTSDomainName, out buffer, out strLen) && strLen > 1)
|
||||
{
|
||||
username = @$"{Marshal.PtrToStringAnsi(buffer)}\{username}";
|
||||
NativeMethods.WTSFreeMemory(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
return username;
|
||||
}
|
||||
|
||||
// The most direct way to fetch the username is WindowsIdentity.GetCurrent(true).Name
|
||||
// but GetUserName can run within an ExecutionContext.SuppressFlow block, which creates issues
|
||||
// with WindowsIdentity.GetCurrent.
|
||||
// See: https://stackoverflow.com/questions/76998988/exception-when-using-executioncontext-suppressflow-in-net-7
|
||||
// So we use WTSQuerySessionInformation as a workaround.
|
||||
Setting.Values.Username = GetUsernameBySessionId(Process.GetCurrentProcess().SessionId);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Setting.Values.Username = Program.User;
|
||||
}
|
||||
|
||||
Logger.LogDebug("[Username] = " + Setting.Values.Username);
|
||||
}
|
||||
|
||||
return !string.IsNullOrEmpty(Setting.Values.Username);
|
||||
}
|
||||
|
||||
internal static void ShowOneWayModeMessage()
|
||||
{
|
||||
Common.ToggleShowTopMostMessage(
|
||||
@"
|
||||
Due to Security Controls, a remote device cannot control a SAW device.
|
||||
Please use the keyboard and Mouse from the SAW device.
|
||||
(Press Esc to hide this message)
|
||||
",
|
||||
string.Empty,
|
||||
10);
|
||||
}
|
||||
|
||||
internal static void ApplyCADSetting()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Setting.Values.DisableCAD)
|
||||
{
|
||||
RegistryKey k = Registry.LocalMachine.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System");
|
||||
if (k != null)
|
||||
{
|
||||
k.SetValue("DisableCAD", 1, RegistryValueKind.DWord);
|
||||
k.Close();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RegistryKey k = Registry.LocalMachine.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System");
|
||||
if (k != null)
|
||||
{
|
||||
k.SetValue("DisableCAD", 0, RegistryValueKind.DWord);
|
||||
k.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
312
src/modules/MouseWithoutBorders/App/Core/Launch.cs
Normal file
312
src/modules/MouseWithoutBorders/App/Core/Launch.cs
Normal file
@@ -0,0 +1,312 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
|
||||
using MouseWithoutBorders.Class;
|
||||
|
||||
// <summary>
|
||||
// Impersonation.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal static class Launch
|
||||
{
|
||||
private static bool RunElevated()
|
||||
{
|
||||
return WindowsIdentity.GetCurrent().Owner.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid);
|
||||
}
|
||||
|
||||
internal static bool ImpersonateLoggedOnUserAndDoSomething(Action targetFunc)
|
||||
{
|
||||
if (Common.RunWithNoAdminRight)
|
||||
{
|
||||
targetFunc();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// SuppressFlow fixes an issue on service mode, where WTSQueryUserToken runs successfully once and then fails
|
||||
// on subsequent calls. The reason appears to be an unknown issue with reverting the impersonation,
|
||||
// meaning that subsequent impersonation attempts run as the logged-on user and fail.
|
||||
// This is a workaround.
|
||||
using var asyncFlowControl = System.Threading.ExecutionContext.SuppressFlow();
|
||||
|
||||
uint dwSessionId;
|
||||
IntPtr hUserToken = IntPtr.Zero, hUserTokenDup = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
dwSessionId = (uint)Process.GetCurrentProcess().SessionId;
|
||||
uint rv = NativeMethods.WTSQueryUserToken(dwSessionId, ref hUserToken);
|
||||
var lastError = rv == 0 ? Marshal.GetLastWin32Error() : 0;
|
||||
|
||||
Logger.LogDebug($"{nameof(NativeMethods.WTSQueryUserToken)} returned {rv.ToString(CultureInfo.CurrentCulture)}");
|
||||
|
||||
if (rv == 0)
|
||||
{
|
||||
Logger.Log($"{nameof(NativeMethods.WTSQueryUserToken)} failed with: {lastError}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!NativeMethods.DuplicateToken(hUserToken, (int)NativeMethods.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, ref hUserTokenDup))
|
||||
{
|
||||
Logger.TelemetryLogTrace($"{nameof(NativeMethods.DuplicateToken)} Failed! {Logger.GetStackTrace(new StackTrace())}", SeverityLevel.Warning);
|
||||
_ = NativeMethods.CloseHandle(hUserToken);
|
||||
_ = NativeMethods.CloseHandle(hUserTokenDup);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NativeMethods.ImpersonateLoggedOnUser(hUserTokenDup))
|
||||
{
|
||||
targetFunc();
|
||||
_ = NativeMethods.RevertToSelf();
|
||||
_ = NativeMethods.CloseHandle(hUserToken);
|
||||
_ = NativeMethods.CloseHandle(hUserTokenDup);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("ImpersonateLoggedOnUser Failed!");
|
||||
_ = NativeMethods.CloseHandle(hUserToken);
|
||||
_ = NativeMethods.CloseHandle(hUserTokenDup);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static int CreateProcessInInputDesktopSession(string commandLine, string arg, string desktop, short wShowWindow, bool lowIntegrity = false)
|
||||
|
||||
// As user who runs explorer.exe
|
||||
{
|
||||
if (!Program.User.Contains("system", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
ProcessStartInfo s = new(commandLine, arg);
|
||||
s.WindowStyle = wShowWindow != 0 ? ProcessWindowStyle.Normal : ProcessWindowStyle.Hidden;
|
||||
Process p = Process.Start(s);
|
||||
|
||||
return p == null ? 0 : p.Id;
|
||||
}
|
||||
|
||||
string commandLineWithArg = commandLine + " " + arg;
|
||||
int lastError;
|
||||
int dwSessionId;
|
||||
IntPtr hUserToken = IntPtr.Zero, hUserTokenDup = IntPtr.Zero;
|
||||
|
||||
Logger.LogDebug("CreateProcessInInputDesktopSession called, launching " + commandLineWithArg + " on " + desktop);
|
||||
|
||||
try
|
||||
{
|
||||
dwSessionId = Process.GetCurrentProcess().SessionId;
|
||||
|
||||
// Get the user token used by DuplicateTokenEx
|
||||
lastError = (int)NativeMethods.WTSQueryUserToken((uint)dwSessionId, ref hUserToken);
|
||||
|
||||
NativeMethods.STARTUPINFO si = default;
|
||||
si.cb = Marshal.SizeOf(si);
|
||||
si.lpDesktop = "winsta0\\" + desktop;
|
||||
si.wShowWindow = wShowWindow;
|
||||
|
||||
NativeMethods.SECURITY_ATTRIBUTES sa = default;
|
||||
sa.Length = Marshal.SizeOf(sa);
|
||||
|
||||
if (!NativeMethods.DuplicateTokenEx(hUserToken, NativeMethods.MAXIMUM_ALLOWED, ref sa, (int)NativeMethods.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)NativeMethods.TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
|
||||
{
|
||||
lastError = Marshal.GetLastWin32Error();
|
||||
Logger.Log(string.Format(CultureInfo.CurrentCulture, "DuplicateTokenEx error: {0} Token does not have the privilege.", lastError));
|
||||
_ = NativeMethods.CloseHandle(hUserToken);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (lowIntegrity)
|
||||
{
|
||||
NativeMethods.TOKEN_MANDATORY_LABEL tIL;
|
||||
|
||||
// Low
|
||||
string sIntegritySid = "S-1-16-4096";
|
||||
|
||||
bool rv = NativeMethods.ConvertStringSidToSid(sIntegritySid, out IntPtr pIntegritySid);
|
||||
|
||||
if (!rv)
|
||||
{
|
||||
Logger.Log("ConvertStringSidToSid failed");
|
||||
_ = NativeMethods.CloseHandle(hUserToken);
|
||||
_ = NativeMethods.CloseHandle(hUserTokenDup);
|
||||
return 0;
|
||||
}
|
||||
|
||||
tIL.Label.Attributes = NativeMethods.SE_GROUP_INTEGRITY;
|
||||
tIL.Label.Sid = pIntegritySid;
|
||||
|
||||
rv = NativeMethods.SetTokenInformation(hUserTokenDup, NativeMethods.TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, ref tIL, (uint)Marshal.SizeOf(tIL) + (uint)IntPtr.Size);
|
||||
|
||||
if (!rv)
|
||||
{
|
||||
Logger.Log("SetTokenInformation failed");
|
||||
_ = NativeMethods.CloseHandle(hUserToken);
|
||||
_ = NativeMethods.CloseHandle(hUserTokenDup);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint dwCreationFlags = NativeMethods.NORMAL_PRIORITY_CLASS | NativeMethods.CREATE_NEW_CONSOLE;
|
||||
IntPtr pEnv = IntPtr.Zero;
|
||||
|
||||
if (NativeMethods.CreateEnvironmentBlock(ref pEnv, hUserTokenDup, true))
|
||||
{
|
||||
dwCreationFlags |= NativeMethods.CREATE_UNICODE_ENVIRONMENT;
|
||||
}
|
||||
else
|
||||
{
|
||||
pEnv = IntPtr.Zero;
|
||||
}
|
||||
|
||||
_ = NativeMethods.CreateProcessAsUser(
|
||||
hUserTokenDup, // client's access token
|
||||
null, // file to execute
|
||||
commandLineWithArg, // command line
|
||||
ref sa, // pointer to process SECURITY_ATTRIBUTES
|
||||
ref sa, // pointer to thread SECURITY_ATTRIBUTES
|
||||
false, // handles are not inheritable
|
||||
(int)dwCreationFlags, // creation flags
|
||||
pEnv, // pointer to new environment block
|
||||
null, // name of current directory
|
||||
ref si, // pointer to STARTUPINFO structure
|
||||
out NativeMethods.PROCESS_INFORMATION pi); // receives information about new process
|
||||
|
||||
// GetLastError should be 0
|
||||
int iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
|
||||
Logger.LogDebug("CreateProcessAsUser returned " + iResultOfCreateProcessAsUser.ToString(CultureInfo.CurrentCulture));
|
||||
|
||||
// Close handles task
|
||||
_ = NativeMethods.CloseHandle(hUserToken);
|
||||
_ = NativeMethods.CloseHandle(hUserTokenDup);
|
||||
|
||||
return (iResultOfCreateProcessAsUser == 0) ? (int)pi.dwProcessId : 0;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if CUSTOMIZE_LOGON_SCREEN
|
||||
internal static bool CreateLowIntegrityProcess(string commandLine, string args, int wait, bool killIfTimedOut, long limitedMem, short wShowWindow = 0)
|
||||
{
|
||||
int processId = CreateProcessInInputDesktopSession(commandLine, args, "default", wShowWindow, true);
|
||||
|
||||
if (processId <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wait > 0)
|
||||
{
|
||||
if (limitedMem > 0)
|
||||
{
|
||||
int sec = 0;
|
||||
while (true)
|
||||
{
|
||||
Process p;
|
||||
|
||||
try
|
||||
{
|
||||
if ((p = Process.GetProcessById(processId)) == null)
|
||||
{
|
||||
Logger.Log("Process exited!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
Logger.Log("GetProcessById.ArgumentException");
|
||||
break;
|
||||
}
|
||||
|
||||
if ((!p.HasExited && p.PrivateMemorySize64 > limitedMem) || (++sec > (wait / 1000)))
|
||||
{
|
||||
Logger.Log(string.Format(CultureInfo.CurrentCulture, "Process log (mem): {0}, {1}", sec, p.PrivateMemorySize64));
|
||||
return false;
|
||||
}
|
||||
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Process p;
|
||||
|
||||
if ((p = Process.GetProcessById(processId)) == null)
|
||||
{
|
||||
Logger.Log("Process exited!");
|
||||
}
|
||||
else if (NativeMethods.WaitForSingleObject(p.Handle, wait) != NativeMethods.WAIT_OBJECT_0 && killIfTimedOut)
|
||||
{
|
||||
Logger.Log("Process log (time).");
|
||||
TerminateProcessTree(p.Handle, (uint)processId, -1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void TerminateProcessTree(IntPtr hProcess, uint processID, int exitCode)
|
||||
{
|
||||
if (processID > 0 && hProcess.ToInt32() > 0)
|
||||
{
|
||||
Process[] processes = Process.GetProcesses();
|
||||
int dwSessionId = Process.GetCurrentProcess().SessionId;
|
||||
|
||||
foreach (Process p in processes)
|
||||
{
|
||||
if (p.SessionId == dwSessionId)
|
||||
{
|
||||
NativeMethods.PROCESS_BASIC_INFORMATION processBasicInformation = default;
|
||||
|
||||
try
|
||||
{
|
||||
if (NativeMethods.NtQueryInformationProcess(p.Handle, 0, ref processBasicInformation, (uint)Marshal.SizeOf(processBasicInformation), out uint bytesWritten) >= 0)
|
||||
{// NT_SUCCESS(...)
|
||||
if (processBasicInformation.InheritedFromUniqueProcessId == processID)
|
||||
{
|
||||
TerminateProcessTree(p.Handle, processBasicInformation.UniqueProcessId, exitCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
continue;
|
||||
}
|
||||
catch (Win32Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = NativeMethods.TerminateProcess(hProcess, (IntPtr)exitCode);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -138,7 +138,7 @@ internal static class Logger
|
||||
Common.PackageSent.ClipboardDragDrop,
|
||||
Common.PackageSent.ClipboardDragDropEnd,
|
||||
Common.PackageSent.ExplorerDragDrop,
|
||||
Common.inputEventCount,
|
||||
Event.inputEventCount,
|
||||
Common.PackageSent.Nil);
|
||||
Log(log);
|
||||
lastPackageSent = Common.PackageSent; // Copy data
|
||||
@@ -161,7 +161,7 @@ internal static class Logger
|
||||
Common.PackageReceived.ClipboardDragDrop,
|
||||
Common.PackageReceived.ClipboardDragDropEnd,
|
||||
Common.PackageReceived.ExplorerDragDrop,
|
||||
Common.invalidPackageCount,
|
||||
Event.invalidPackageCount,
|
||||
Common.PackageReceived.Nil,
|
||||
Receiver.processedPackageCount,
|
||||
Receiver.skippedPackageCount);
|
||||
@@ -197,15 +197,9 @@ internal static class Logger
|
||||
myThreads.Add(t);
|
||||
}
|
||||
|
||||
_ = Logger.PrivateDump(sb, AllLogs, "[Program logs]\r\n===============\r\n", 0, level, false);
|
||||
_ = Logger.PrivateDump(sb, new Common(), "[Other Logs]\r\n===============\r\n", 0, level, false);
|
||||
Logger.DumpType(sb, typeof(Logger), 0, level);
|
||||
sb.AppendLine("[DragDrop]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(DragDrop), 0, level);
|
||||
sb.AppendLine("[MachineStuff]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(MachineStuff), 0, level);
|
||||
sb.AppendLine("[Receiver]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(Receiver), 0, level);
|
||||
Logger.DumpProgramLogs(sb, level);
|
||||
Logger.DumpOtherLogs(sb, level);
|
||||
Logger.DumpStaticTypes(sb, level);
|
||||
|
||||
log = string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
@@ -241,6 +235,36 @@ internal static class Logger
|
||||
}
|
||||
}
|
||||
|
||||
internal static void DumpProgramLogs(StringBuilder sb, int level)
|
||||
{
|
||||
_ = Logger.PrivateDump(sb, AllLogs, "[Program logs]\r\n===============\r\n", 0, level, false);
|
||||
}
|
||||
|
||||
internal static void DumpOtherLogs(StringBuilder sb, int level)
|
||||
{
|
||||
_ = Logger.PrivateDump(sb, new Common(), "[Other Logs]\r\n===============\r\n", 0, level, false);
|
||||
}
|
||||
|
||||
internal static void DumpStaticTypes(StringBuilder sb, int level)
|
||||
{
|
||||
sb.AppendLine($"[{nameof(DragDrop)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(DragDrop), 0, level);
|
||||
sb.AppendLine($"[{nameof(Event)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(Event), 0, level);
|
||||
sb.AppendLine($"[{nameof(Helper)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(Helper), 0, level);
|
||||
sb.AppendLine($"[{nameof(Launch)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(Launch), 0, level);
|
||||
sb.AppendLine($"[{nameof(Logger)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(Logger), 0, level);
|
||||
sb.AppendLine($"[{nameof(MachineStuff)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(MachineStuff), 0, level);
|
||||
sb.AppendLine($"[{nameof(Receiver)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(Receiver), 0, level);
|
||||
sb.AppendLine($"[{nameof(Service)}]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(Service), 0, level);
|
||||
}
|
||||
|
||||
internal static bool PrivateDump(StringBuilder sb, object obj, string objName, int level, int maxLevel, bool stop)
|
||||
{
|
||||
Type t;
|
||||
|
||||
@@ -1018,8 +1018,8 @@ internal static class MachineStuff
|
||||
}
|
||||
|
||||
NewDesMachineID = Common.DesMachineID = id;
|
||||
SwitchLocation.X = Common.XY_BY_PIXEL + primaryScreenBounds.Left + ((primaryScreenBounds.Right - primaryScreenBounds.Left) / 2);
|
||||
SwitchLocation.Y = Common.XY_BY_PIXEL + primaryScreenBounds.Top + ((primaryScreenBounds.Bottom - primaryScreenBounds.Top) / 2);
|
||||
SwitchLocation.X = Event.XY_BY_PIXEL + primaryScreenBounds.Left + ((primaryScreenBounds.Right - primaryScreenBounds.Left) / 2);
|
||||
SwitchLocation.Y = Event.XY_BY_PIXEL + primaryScreenBounds.Top + ((primaryScreenBounds.Bottom - primaryScreenBounds.Top) / 2);
|
||||
SwitchLocation.ResetCount();
|
||||
Common.UpdateMultipleModeIconAndMenu();
|
||||
Common.HideMouseCursor(false);
|
||||
|
||||
@@ -41,12 +41,12 @@ internal static class Receiver
|
||||
{
|
||||
if (package.Type == PackageType.Invalid)
|
||||
{
|
||||
if ((Common.InvalidPackageCount % 100) == 0)
|
||||
if ((Event.InvalidPackageCount % 100) == 0)
|
||||
{
|
||||
Common.ShowToolTip("Invalid packages received!", 1000, ToolTipIcon.Warning, false);
|
||||
}
|
||||
|
||||
Common.InvalidPackageCount++;
|
||||
Event.InvalidPackageCount++;
|
||||
Logger.Log("Invalid packages received!");
|
||||
return false;
|
||||
}
|
||||
@@ -104,7 +104,7 @@ internal static class Receiver
|
||||
{
|
||||
if ((package.Kd.dwFlags & (int)Common.LLKHF.UP) == (int)Common.LLKHF.UP)
|
||||
{
|
||||
Common.ShowOneWayModeMessage();
|
||||
Helper.ShowOneWayModeMessage();
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -133,7 +133,7 @@ internal static class Receiver
|
||||
{
|
||||
if (package.Md.dwFlags is Common.WM_LBUTTONDOWN or Common.WM_RBUTTONDOWN)
|
||||
{
|
||||
Common.ShowOneWayModeMessage();
|
||||
Helper.ShowOneWayModeMessage();
|
||||
}
|
||||
}
|
||||
else if (package.Md.dwFlags is Common.WM_LBUTTONUP or Common.WM_RBUTTONUP)
|
||||
@@ -144,13 +144,13 @@ internal static class Receiver
|
||||
return;
|
||||
}
|
||||
|
||||
if (Math.Abs(package.Md.X) >= Common.MOVE_MOUSE_RELATIVE && Math.Abs(package.Md.Y) >= Common.MOVE_MOUSE_RELATIVE)
|
||||
if (Math.Abs(package.Md.X) >= Event.MOVE_MOUSE_RELATIVE && Math.Abs(package.Md.Y) >= Event.MOVE_MOUSE_RELATIVE)
|
||||
{
|
||||
if (package.Md.dwFlags == Common.WM_MOUSEMOVE)
|
||||
{
|
||||
InputSimulation.MoveMouseRelative(
|
||||
package.Md.X < 0 ? package.Md.X + Common.MOVE_MOUSE_RELATIVE : package.Md.X - Common.MOVE_MOUSE_RELATIVE,
|
||||
package.Md.Y < 0 ? package.Md.Y + Common.MOVE_MOUSE_RELATIVE : package.Md.Y - Common.MOVE_MOUSE_RELATIVE);
|
||||
package.Md.X < 0 ? package.Md.X + Event.MOVE_MOUSE_RELATIVE : package.Md.X - Event.MOVE_MOUSE_RELATIVE,
|
||||
package.Md.Y < 0 ? package.Md.Y + Event.MOVE_MOUSE_RELATIVE : package.Md.Y - Event.MOVE_MOUSE_RELATIVE);
|
||||
_ = NativeMethods.GetCursorPos(ref lastXY);
|
||||
|
||||
Point p = MachineStuff.MoveToMyNeighbourIfNeeded(lastXY.X, lastXY.Y, Common.MachineID);
|
||||
@@ -195,9 +195,9 @@ internal static class Receiver
|
||||
case PackageType.NextMachine:
|
||||
Logger.LogDebug("PackageType.NextMachine received!");
|
||||
|
||||
if (Common.IsSwitchingByMouseEnabled())
|
||||
if (Event.IsSwitchingByMouseEnabled())
|
||||
{
|
||||
Common.PrepareToSwitchToMachine((ID)package.Md.WheelDelta, new Point(package.Md.X, package.Md.Y));
|
||||
Event.PrepareToSwitchToMachine((ID)package.Md.WheelDelta, new Point(package.Md.X, package.Md.Y));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -383,7 +383,7 @@ internal static class Receiver
|
||||
case PackageType.HideMouse:
|
||||
Common.HasSwitchedMachineSinceLastCopy = true;
|
||||
Common.HideMouseCursor(true);
|
||||
Common.MainFormDotEx(false);
|
||||
Helper.MainFormDotEx(false);
|
||||
Common.ReleaseAllKeys();
|
||||
break;
|
||||
|
||||
|
||||
159
src/modules/MouseWithoutBorders/App/Core/Service.cs
Normal file
159
src/modules/MouseWithoutBorders/App/Core/Service.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.ServiceProcess;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using MouseWithoutBorders.Class;
|
||||
|
||||
[module: SuppressMessage("Microsoft.Globalization", "CA1300:SpecifyMessageBoxOptions", Scope = "member", Target = "MouseWithoutBorders.Common.#StartMouseWithoutBordersService()", Justification = "Dotnet port with style preservation")]
|
||||
|
||||
// <summary>
|
||||
// Service control code.
|
||||
// </summary>
|
||||
// <history>
|
||||
// 2008 created by Truong Do (ductdo).
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal static class Service
|
||||
{
|
||||
private static bool shownErrMessage;
|
||||
private static DateTime lastStartServiceTime = DateTime.UtcNow;
|
||||
|
||||
internal static void StartMouseWithoutBordersService(string desktopToRunMouseWithoutBordersOn = null, string startTag1 = "byapp", string startTag2 = null)
|
||||
{
|
||||
// NOTE(@yuyoyuppe): the new flow assumes we run both mwb processes directly from the svc.
|
||||
if (Common.RunWithNoAdminRight || true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Log($"{nameof(StartMouseWithoutBordersService)}: {Logger.GetStackTrace(new StackTrace())}.");
|
||||
|
||||
Task task = Task.Run(() =>
|
||||
{
|
||||
Process[] ps = Process.GetProcessesByName("MouseWithoutBordersSvc");
|
||||
|
||||
if (ps.Length != 0)
|
||||
{
|
||||
if (DateTime.UtcNow - lastStartServiceTime < TimeSpan.FromSeconds(5))
|
||||
{
|
||||
Logger.Log($"{nameof(StartMouseWithoutBordersService)}: Aborted.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Process pp in ps)
|
||||
{
|
||||
Logger.Log(string.Format(CultureInfo.InvariantCulture, "Killing process MouseWithoutBordersSvc {0}.", pp.Id));
|
||||
pp.KillProcess();
|
||||
}
|
||||
}
|
||||
|
||||
lastStartServiceTime = DateTime.UtcNow;
|
||||
ServiceController service = new("MouseWithoutBordersSvc");
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Log("Starting " + service.ServiceName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (!shownErrMessage)
|
||||
{
|
||||
shownErrMessage = true;
|
||||
_ = MessageBox.Show(
|
||||
Application.ProductName + " is not installed yet, please run Setup.exe first!",
|
||||
Application.ProductName,
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
int c = 0;
|
||||
|
||||
while (service.Status != ServiceControllerStatus.Stopped && c++ < 5)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
service = new ServiceController("MouseWithoutBordersSvc");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(desktopToRunMouseWithoutBordersOn))
|
||||
{
|
||||
startTag2 ??= Process.GetCurrentProcess().SessionId.ToString(CultureInfo.InvariantCulture);
|
||||
service.Start(new string[] { startTag1, startTag2 });
|
||||
}
|
||||
else
|
||||
{
|
||||
service.Start(new string[] { desktopToRunMouseWithoutBordersOn });
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(e);
|
||||
|
||||
// ERROR_SERVICE_ALREADY_RUNNING
|
||||
if (!(shownErrMessage || ((e?.InnerException as Win32Exception)?.NativeErrorCode == 1056)))
|
||||
{
|
||||
shownErrMessage = true;
|
||||
_ = MessageBox.Show(
|
||||
"Cannot start service " + service.ServiceName + ": " + e.Message,
|
||||
Common.BinaryName,
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the task while not blocking the UI thread.
|
||||
do
|
||||
{
|
||||
Common.MMSleep(1);
|
||||
|
||||
if (task.IsCanceled || task.IsCompleted || task.IsFaulted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
|
||||
internal static void StartServiceAndSendLogoffSignal()
|
||||
{
|
||||
try
|
||||
{
|
||||
Process[] p = Process.GetProcessesByName("winlogon");
|
||||
Process me = Process.GetCurrentProcess();
|
||||
string myWinlogon = p?.FirstOrDefault(item => item.SessionId == me.SessionId)?.Id.ToString(CultureInfo.InvariantCulture) ?? null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(myWinlogon))
|
||||
{
|
||||
StartMouseWithoutBordersService(null, "logoff");
|
||||
}
|
||||
else
|
||||
{
|
||||
StartMouseWithoutBordersService(null, "logoff", myWinlogon);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"{nameof(StartServiceAndSendLogoffSignal)}: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,8 +63,8 @@ namespace MouseWithoutBorders
|
||||
try
|
||||
{
|
||||
Common.Hook = new InputHook();
|
||||
Common.Hook.MouseEvent += new InputHook.MouseEvHandler(Common.MouseEvent);
|
||||
Common.Hook.KeyboardEvent += new InputHook.KeybdEvHandler(Common.KeybdEvent);
|
||||
Common.Hook.MouseEvent += new InputHook.MouseEvHandler(Event.MouseEvent);
|
||||
Common.Hook.KeyboardEvent += new InputHook.KeybdEvHandler(Event.KeybdEvent);
|
||||
|
||||
Logger.Log("(((((Keyboard/Mouse hooks installed/reinstalled!)))))");
|
||||
/* The hook is called in the context of the thread that installed it.
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
internal void UpdateKeyTextBox()
|
||||
{
|
||||
_ = Common.GetUserName();
|
||||
_ = Helper.GetUserName();
|
||||
textBoxEnc.Text = Common.MyKey;
|
||||
}
|
||||
|
||||
@@ -787,7 +787,7 @@ namespace MouseWithoutBorders
|
||||
{
|
||||
if (!Common.RunWithNoAdminRight)
|
||||
{
|
||||
Common.ApplyCADSetting();
|
||||
Helper.ApplyCADSetting();
|
||||
ShowUpdateMessage();
|
||||
}
|
||||
}
|
||||
@@ -1163,7 +1163,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
private void LinkLabelMiniLog_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
||||
{
|
||||
string miniLog = Common.GetMiniLog(new[] { groupBoxOtherOptions.Controls, groupBoxShortcuts.Controls });
|
||||
string miniLog = Helper.GetMiniLog(new[] { groupBoxOtherOptions.Controls, groupBoxShortcuts.Controls });
|
||||
|
||||
Clipboard.SetText(miniLog);
|
||||
Common.ShowToolTip("Log has been placed in the clipboard.", 30000, ToolTipIcon.Info, false);
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace MouseWithoutBorders
|
||||
NotifyIcon.BalloonTipTitle = Application.ProductName;
|
||||
menuGenDumpFile.Visible = true;
|
||||
|
||||
Common.WndProcCounter++;
|
||||
Helper.WndProcCounter++;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -101,7 +101,7 @@ namespace MouseWithoutBorders
|
||||
}
|
||||
else
|
||||
{
|
||||
Common.StartServiceAndSendLogoffSignal();
|
||||
Service.StartServiceAndSendLogoffSignal();
|
||||
Quit(true, true);
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ namespace MouseWithoutBorders
|
||||
NotifyIcon.Dispose();
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
Common.RunDDHelper(true);
|
||||
Helper.RunDDHelper(true);
|
||||
}
|
||||
|
||||
// Common.UnhookClipboard();
|
||||
@@ -133,7 +133,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
Setting.Values.SwitchCount += Common.SwitchCount;
|
||||
Process me = Process.GetCurrentProcess();
|
||||
Common.WndProcCounter++;
|
||||
Helper.WndProcCounter++;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -142,13 +142,13 @@ namespace MouseWithoutBorders
|
||||
Common.Cleanup();
|
||||
}
|
||||
|
||||
Common.WndProcCounter++;
|
||||
Helper.WndProcCounter++;
|
||||
if (!Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
Common.ReleaseAllKeys();
|
||||
}
|
||||
|
||||
Common.RunDDHelper(true);
|
||||
Helper.RunDDHelper(true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -285,7 +285,7 @@ namespace MouseWithoutBorders
|
||||
helperTimer.Interval = 100;
|
||||
helperTimer.Tick += new EventHandler(HelperTimer_Tick);
|
||||
helperTimer.Start();
|
||||
Common.WndProcCounter++;
|
||||
Helper.WndProcCounter++;
|
||||
|
||||
if (Environment.OSVersion.Version.Major > 6 ||
|
||||
(Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor >= 1))
|
||||
@@ -307,7 +307,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
private void HelperTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
Common.WndProcCounter++;
|
||||
Helper.WndProcCounter++;
|
||||
|
||||
if (busy)
|
||||
{
|
||||
@@ -338,7 +338,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
if (Common.MainFormVisible)
|
||||
{
|
||||
Common.MainFormDot();
|
||||
Helper.MainFormDot();
|
||||
}
|
||||
|
||||
InputSimulation.ResetSystemKeyFlags();
|
||||
@@ -357,7 +357,7 @@ namespace MouseWithoutBorders
|
||||
{
|
||||
if (!Common.SecondOpenSocketTry)
|
||||
{
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && !Common.GetUserName())
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && !Helper.GetUserName())
|
||||
{
|
||||
// While Windows 8 is hybrid-shutting down, user name would be empty (as returned from the .Net API), we should not do anything in this case.
|
||||
Logger.LogDebug("No active user.");
|
||||
@@ -407,7 +407,7 @@ namespace MouseWithoutBorders
|
||||
// Common.ReHookClipboard();
|
||||
}
|
||||
|
||||
Common.RunDDHelper();
|
||||
Helper.RunDDHelper();
|
||||
}
|
||||
|
||||
count = 0;
|
||||
@@ -485,7 +485,7 @@ namespace MouseWithoutBorders
|
||||
// One more time after 1/3 minutes (Sometimes XP has explorer started late)
|
||||
if (count == 600 || count == 1800)
|
||||
{
|
||||
Common.RunDDHelper();
|
||||
Helper.RunDDHelper();
|
||||
}
|
||||
|
||||
if (count == 600)
|
||||
@@ -545,7 +545,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
if (Common.ToggleIcons != null)
|
||||
{
|
||||
Common.ToggleIcon();
|
||||
Helper.ToggleIcon();
|
||||
}
|
||||
|
||||
if (count % 20 == 0)
|
||||
@@ -578,13 +578,13 @@ namespace MouseWithoutBorders
|
||||
}
|
||||
else if ((count % 36005) == 0)
|
||||
{// One hour
|
||||
Common.SaveSwitchCount();
|
||||
Event.SaveSwitchCount();
|
||||
|
||||
int rv = 0;
|
||||
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && Common.IsMyDesktopActive() && (rv = Common.SendMessageToHelper(0x400, IntPtr.Zero, IntPtr.Zero)) <= 0)
|
||||
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && Common.IsMyDesktopActive() && (rv = Helper.SendMessageToHelper(0x400, IntPtr.Zero, IntPtr.Zero)) <= 0)
|
||||
{
|
||||
Logger.TelemetryLogTrace($"{Common.HELPER_FORM_TEXT} not found: {rv}", SeverityLevel.Warning);
|
||||
Logger.TelemetryLogTrace($"{Helper.HELPER_FORM_TEXT} not found: {rv}", SeverityLevel.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -776,7 +776,7 @@ namespace MouseWithoutBorders
|
||||
break;
|
||||
|
||||
case NativeMethods.WM_HIDE_DRAG_DROP:
|
||||
Common.MainFormDot();
|
||||
Helper.MainFormDot();
|
||||
|
||||
/*
|
||||
this.Width = 1;
|
||||
@@ -795,14 +795,14 @@ namespace MouseWithoutBorders
|
||||
break;
|
||||
|
||||
case NativeMethods.WM_HIDE_DD_HELPER:
|
||||
Common.MainForm3Pixels();
|
||||
Helper.MainForm3Pixels();
|
||||
Common.MMSleep(0.2);
|
||||
if (m.WParam.ToInt32() == 1)
|
||||
{
|
||||
InputSimulation.MouseUp(); // A file is being dragged
|
||||
}
|
||||
|
||||
IntPtr h = (IntPtr)NativeMethods.FindWindow(null, Common.HELPER_FORM_TEXT);
|
||||
IntPtr h = (IntPtr)NativeMethods.FindWindow(null, Helper.HELPER_FORM_TEXT);
|
||||
|
||||
if (h.ToInt32() > 0)
|
||||
{
|
||||
@@ -831,7 +831,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
case WM_QUERYENDSESSION:
|
||||
Logger.LogDebug("WM_QUERYENDSESSION...");
|
||||
Common.StartServiceAndSendLogoffSignal();
|
||||
Service.StartServiceAndSendLogoffSignal();
|
||||
break;
|
||||
|
||||
case WM_ENDSESSION:
|
||||
|
||||
@@ -99,6 +99,111 @@ LegalKeyDictionary = Concurrent.ConcurrentDictionary`2[System.String,System.Byte
|
||||
--_budget = ????????????
|
||||
--_growLockArray = True
|
||||
--_comparerIsDefaultForClasses = False
|
||||
initDone = False
|
||||
REOPEN_WHEN_WSAECONNRESET = -10054
|
||||
REOPEN_WHEN_HOTKEY = -10055
|
||||
PleaseReopenSocket = 0
|
||||
ReopenSocketDueToReadError = False
|
||||
<LastResumeSuspendTime>k__BackingField = ????????????
|
||||
--_dateData = ????????????
|
||||
--MinValue = 01/01/0001 00:00:00
|
||||
--MaxValue = 31/12/9999 23:59:59
|
||||
--UnixEpoch = 01/01/1970 00:00:00
|
||||
lastReleaseAllKeysCall = 0
|
||||
PackageSent = MouseWithoutBorders.PackageMonitor
|
||||
--Keyboard = 0
|
||||
--Mouse = 0
|
||||
--Heartbeat = 0
|
||||
--ByeBye = 0
|
||||
--Hello = 0
|
||||
--Matrix = 0
|
||||
--ClipboardText = 0
|
||||
--ClipboardImage = 0
|
||||
--Clipboard = 0
|
||||
--ClipboardDragDrop = 0
|
||||
--ClipboardDragDropEnd = 0
|
||||
--ClipboardAsk = 0
|
||||
--ExplorerDragDrop = 0
|
||||
--Nil = 0
|
||||
PackageReceived = MouseWithoutBorders.PackageMonitor
|
||||
--Keyboard = 0
|
||||
--Mouse = 0
|
||||
--Heartbeat = 0
|
||||
--ByeBye = 0
|
||||
--Hello = 0
|
||||
--Matrix = 0
|
||||
--ClipboardText = 0
|
||||
--ClipboardImage = 0
|
||||
--Clipboard = 0
|
||||
--ClipboardDragDrop = 0
|
||||
--ClipboardDragDropEnd = 0
|
||||
--ClipboardAsk = 0
|
||||
--ExplorerDragDrop = 0
|
||||
--Nil = 0
|
||||
PackageID = 0
|
||||
SensitivePoints = Generic.List`1[Point]
|
||||
--_items = Point[]
|
||||
----System.Drawing.Point[] = Point[]: N/A
|
||||
--_size = 0
|
||||
--_version = 0
|
||||
--s_emptyArray = Point[]
|
||||
----System.Drawing.Point[] = Point[]: N/A
|
||||
p = {X=0,Y=0}
|
||||
--x = 0
|
||||
--y = 0
|
||||
--Empty = {X=0,Y=0}
|
||||
<IpcChannelCreated>k__BackingField = False
|
||||
BIG_CLIPBOARD_DATA_TIMEOUT = 30000
|
||||
MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP = 1048576
|
||||
MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT = 104857600
|
||||
TEXT_HEADER_SIZE = 12
|
||||
DATA_SIZE = 48
|
||||
TEXT_TYPE_SEP = {4CFF57F7-BEDD-43d5-AE8F-27A61E886F2F}
|
||||
TOGGLE_ICONS_SIZE = 4
|
||||
ICON_ONE = 0
|
||||
ICON_ALL = 1
|
||||
ICON_SMALL_CLIPBOARD = 2
|
||||
ICON_BIG_CLIPBOARD = 3
|
||||
ICON_ERROR = 4
|
||||
JUST_GOT_BACK_FROM_SCREEN_SAVER = 9999
|
||||
NETWORK_STREAM_BUF_SIZE = 1048576
|
||||
SymAlBlockSize = 16
|
||||
PW_LENGTH = 16
|
||||
PACKAGE_SIZE = 32
|
||||
PACKAGE_SIZE_EX = 64
|
||||
WP_PACKAGE_SIZE = 6
|
||||
KEYEVENTF_KEYDOWN = 1
|
||||
KEYEVENTF_KEYUP = 2
|
||||
WH_MOUSE = 7
|
||||
WH_KEYBOARD = 2
|
||||
WH_MOUSE_LL = 14
|
||||
WH_KEYBOARD_LL = 13
|
||||
WM_MOUSEMOVE = 512
|
||||
WM_LBUTTONDOWN = 513
|
||||
WM_RBUTTONDOWN = 516
|
||||
WM_MBUTTONDOWN = 519
|
||||
WM_XBUTTONDOWN = 523
|
||||
WM_LBUTTONUP = 514
|
||||
WM_RBUTTONUP = 517
|
||||
WM_MBUTTONUP = 520
|
||||
WM_XBUTTONUP = 524
|
||||
WM_LBUTTONDBLCLK = 515
|
||||
WM_RBUTTONDBLCLK = 518
|
||||
WM_MBUTTONDBLCLK = 521
|
||||
WM_MOUSEWHEEL = 522
|
||||
WM_KEYDOWN = 256
|
||||
WM_KEYUP = 257
|
||||
WM_SYSKEYDOWN = 260
|
||||
WM_SYSKEYUP = 261
|
||||
[DragDrop]
|
||||
===============
|
||||
isDragging = False
|
||||
dragDropStep05ExCalledByIpc = 0
|
||||
isDropping = False
|
||||
dragMachine = NONE
|
||||
<MouseDown>k__BackingField = False
|
||||
[Event]
|
||||
===============
|
||||
KeybdPackage = MouseWithoutBorders.DATA
|
||||
--Type = 0
|
||||
--Id = 0
|
||||
@@ -144,113 +249,15 @@ actualLastPos = {X=0,Y=0}
|
||||
--Empty = {X=0,Y=0}
|
||||
myLastX = 0
|
||||
myLastY = 0
|
||||
[Helper]
|
||||
===============
|
||||
signalHelperToExit = False
|
||||
signalWatchDogToExit = False
|
||||
WndProcCounter = 0
|
||||
initDone = False
|
||||
REOPEN_WHEN_WSAECONNRESET = -10054
|
||||
REOPEN_WHEN_HOTKEY = -10055
|
||||
PleaseReopenSocket = 0
|
||||
ReopenSocketDueToReadError = False
|
||||
<LastResumeSuspendTime>k__BackingField = ????????????
|
||||
--_dateData = ????????????
|
||||
--MinValue = 01/01/0001 00:00:00
|
||||
--MaxValue = 31/12/9999 23:59:59
|
||||
--UnixEpoch = 01/01/1970 00:00:00
|
||||
lastReleaseAllKeysCall = 0
|
||||
PackageSent = MouseWithoutBorders.PackageMonitor
|
||||
--Keyboard = 0
|
||||
--Mouse = 0
|
||||
--Heartbeat = 0
|
||||
--ByeBye = 0
|
||||
--Hello = 0
|
||||
--Matrix = 0
|
||||
--ClipboardText = 0
|
||||
--ClipboardImage = 0
|
||||
--Clipboard = 0
|
||||
--ClipboardDragDrop = 0
|
||||
--ClipboardDragDropEnd = 0
|
||||
--ClipboardAsk = 0
|
||||
--ExplorerDragDrop = 0
|
||||
--Nil = 0
|
||||
PackageReceived = MouseWithoutBorders.PackageMonitor
|
||||
--Keyboard = 0
|
||||
--Mouse = 0
|
||||
--Heartbeat = 0
|
||||
--ByeBye = 0
|
||||
--Hello = 0
|
||||
--Matrix = 0
|
||||
--ClipboardText = 0
|
||||
--ClipboardImage = 0
|
||||
--Clipboard = 0
|
||||
--ClipboardDragDrop = 0
|
||||
--ClipboardDragDropEnd = 0
|
||||
--ClipboardAsk = 0
|
||||
--ExplorerDragDrop = 0
|
||||
--Nil = 0
|
||||
PackageID = 0
|
||||
shownErrMessage = False
|
||||
lastStartServiceTime = ????????????
|
||||
--_dateData = ????????????
|
||||
--MinValue = 01/01/0001 00:00:00
|
||||
--MaxValue = 31/12/9999 23:59:59
|
||||
--UnixEpoch = 01/01/1970 00:00:00
|
||||
SensitivePoints = Generic.List`1[Point]
|
||||
--_items = Point[]
|
||||
----System.Drawing.Point[] = Point[]: N/A
|
||||
--_size = 0
|
||||
--_version = 0
|
||||
--s_emptyArray = Point[]
|
||||
----System.Drawing.Point[] = Point[]: N/A
|
||||
p = {X=0,Y=0}
|
||||
--x = 0
|
||||
--y = 0
|
||||
--Empty = {X=0,Y=0}
|
||||
<IpcChannelCreated>k__BackingField = False
|
||||
BIG_CLIPBOARD_DATA_TIMEOUT = 30000
|
||||
MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP = 1048576
|
||||
MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT = 104857600
|
||||
TEXT_HEADER_SIZE = 12
|
||||
DATA_SIZE = 48
|
||||
TEXT_TYPE_SEP = {4CFF57F7-BEDD-43d5-AE8F-27A61E886F2F}
|
||||
TOGGLE_ICONS_SIZE = 4
|
||||
ICON_ONE = 0
|
||||
ICON_ALL = 1
|
||||
ICON_SMALL_CLIPBOARD = 2
|
||||
ICON_BIG_CLIPBOARD = 3
|
||||
ICON_ERROR = 4
|
||||
JUST_GOT_BACK_FROM_SCREEN_SAVER = 9999
|
||||
NETWORK_STREAM_BUF_SIZE = 1048576
|
||||
SymAlBlockSize = 16
|
||||
PW_LENGTH = 16
|
||||
HELPER_FORM_TEXT = Mouse without Borders Helper
|
||||
HelperProcessName = PowerToys.MouseWithoutBordersHelper
|
||||
PACKAGE_SIZE = 32
|
||||
PACKAGE_SIZE_EX = 64
|
||||
WP_PACKAGE_SIZE = 6
|
||||
KEYEVENTF_KEYDOWN = 1
|
||||
KEYEVENTF_KEYUP = 2
|
||||
WH_MOUSE = 7
|
||||
WH_KEYBOARD = 2
|
||||
WH_MOUSE_LL = 14
|
||||
WH_KEYBOARD_LL = 13
|
||||
WM_MOUSEMOVE = 512
|
||||
WM_LBUTTONDOWN = 513
|
||||
WM_RBUTTONDOWN = 516
|
||||
WM_MBUTTONDOWN = 519
|
||||
WM_XBUTTONDOWN = 523
|
||||
WM_LBUTTONUP = 514
|
||||
WM_RBUTTONUP = 517
|
||||
WM_MBUTTONUP = 520
|
||||
WM_XBUTTONUP = 524
|
||||
WM_LBUTTONDBLCLK = 515
|
||||
WM_RBUTTONDBLCLK = 518
|
||||
WM_MBUTTONDBLCLK = 521
|
||||
WM_MOUSEWHEEL = 522
|
||||
WM_KEYDOWN = 256
|
||||
WM_KEYUP = 257
|
||||
WM_SYSKEYDOWN = 260
|
||||
WM_SYSKEYUP = 261
|
||||
[Launch]
|
||||
===============
|
||||
[Logger]
|
||||
===============
|
||||
AllLogsLock = Lock
|
||||
@@ -319,13 +326,6 @@ MAX_LOG = 10000
|
||||
MaxLogExceptionPerHour = 1000
|
||||
HeaderSENT = Be{0},Ke{1},Mo{2},He{3},Mx{4},Tx{5},Im{6},By{7},Cl{8},Dr{9},De{10},Ed{11},Ie{12},Ni{13}
|
||||
HeaderRECEIVED = Be{0},Ke{1},Mo{2},He{3},Mx{4},Tx{5},Im{6},By{7},Cl{8},Dr{9},De{10},Ed{11},In{12},Ni{13},Pc{14}/{15}
|
||||
[DragDrop]
|
||||
===============
|
||||
isDragging = False
|
||||
dragDropStep05ExCalledByIpc = 0
|
||||
isDropping = False
|
||||
dragMachine = NONE
|
||||
<MouseDown>k__BackingField = False
|
||||
[MachineStuff]
|
||||
===============
|
||||
McMatrixLock = Lock
|
||||
@@ -424,3 +424,11 @@ lastXY = {X=0,Y=0}
|
||||
--x = 0
|
||||
--y = 0
|
||||
--Empty = {X=0,Y=0}
|
||||
[Service]
|
||||
===============
|
||||
shownErrMessage = False
|
||||
lastStartServiceTime = ????????????
|
||||
--_dateData = ????????????
|
||||
--MinValue = 01/01/0001 00:00:00
|
||||
--MaxValue = 31/12/9999 23:59:59
|
||||
--UnixEpoch = 01/01/1970 00:00:00
|
||||
|
||||
@@ -114,18 +114,11 @@ public static class LoggerTests
|
||||
using var streamReader = new StreamReader(stream);
|
||||
var expected = streamReader.ReadToEnd();
|
||||
|
||||
// copied from DumpObjects in Common.Log.cs
|
||||
// copied from DumpObjects in Logger.cs
|
||||
var sb = new StringBuilder(1000000);
|
||||
_ = Logger.PrivateDump(sb, Logger.AllLogs, "[Program logs]\r\n===============\r\n", 0, settingsDumpObjectsLevel, false);
|
||||
_ = Logger.PrivateDump(sb, new Common(), "[Other Logs]\r\n===============\r\n", 0, settingsDumpObjectsLevel, false);
|
||||
sb.AppendLine("[Logger]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(Logger), 0, settingsDumpObjectsLevel);
|
||||
sb.AppendLine("[DragDrop]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(DragDrop), 0, settingsDumpObjectsLevel);
|
||||
sb.AppendLine("[MachineStuff]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(MachineStuff), 0, settingsDumpObjectsLevel);
|
||||
sb.AppendLine("[Receiver]\r\n===============");
|
||||
Logger.DumpType(sb, typeof(Receiver), 0, settingsDumpObjectsLevel);
|
||||
Logger.DumpProgramLogs(sb, settingsDumpObjectsLevel);
|
||||
Logger.DumpOtherLogs(sb, settingsDumpObjectsLevel);
|
||||
Logger.DumpStaticTypes(sb, settingsDumpObjectsLevel);
|
||||
var actual = sb.ToString();
|
||||
|
||||
expected = NormalizeLog(expected);
|
||||
|
||||
@@ -83,6 +83,8 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\constants.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\helpers_filesystem.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\helpers_variables.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\new_utilities.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\settings.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\shell_context_sub_menu.h" />
|
||||
@@ -97,6 +99,7 @@
|
||||
<ClInclude Include="shell_context_menu_win10.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\powerrename\lib\Helpers.cpp" />
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\new_utilities.cpp" />
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\powertoys_module.cpp" />
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\settings.cpp" />
|
||||
|
||||
@@ -57,6 +57,12 @@
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\settings.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\helpers_filesystem.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\helpers_variables.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="pch.cpp">
|
||||
@@ -92,6 +98,9 @@
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\new_utilities.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\powerrename\lib\Helpers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Generated Files\new.rc">
|
||||
|
||||
@@ -32,7 +32,7 @@ IFACEMETHODIMP shell_context_menu_win10::Initialize(PCIDLIST_ABSOLUTE, IDataObje
|
||||
IFACEMETHODIMP shell_context_menu_win10::QueryContextMenu(HMENU menu_handle, UINT menu_index, UINT menu_first_cmd_id, UINT, UINT menu_flags)
|
||||
{
|
||||
if (!NewSettingsInstance().GetEnabled()
|
||||
|| package::IsWin11OrGreater()
|
||||
|| package::IsWin11OrGreater()
|
||||
)
|
||||
{
|
||||
return E_FAIL;
|
||||
@@ -47,7 +47,7 @@ IFACEMETHODIMP shell_context_menu_win10::QueryContextMenu(HMENU menu_handle, UIN
|
||||
{
|
||||
// Create the initial context popup menu containing the list of templates and open templates action
|
||||
int menu_id = menu_first_cmd_id;
|
||||
MENUITEMINFO newplus_main_context_menu_item;
|
||||
MENUITEMINFO newplus_main_context_menu_item = { 0 };
|
||||
HMENU sub_menu_of_templates = CreatePopupMenu();
|
||||
int sub_menu_index = 0;
|
||||
|
||||
@@ -142,7 +142,7 @@ void shell_context_menu_win10::add_open_templates_to_context_menu(HMENU sub_menu
|
||||
wchar_t menu_name_open[256] = { 0 };
|
||||
wcscpy_s(menu_name_open, ARRAYSIZE(menu_name_open), localized_context_menu_item_open_templates.c_str());
|
||||
const auto open_folder_item = Make<template_folder_context_menu_item>(template_folder_root);
|
||||
MENUITEMINFO newplus_menu_item_open_templates;
|
||||
MENUITEMINFO newplus_menu_item_open_templates = { 0 };
|
||||
newplus_menu_item_open_templates.cbSize = sizeof(MENUITEMINFO);
|
||||
newplus_menu_item_open_templates.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID;
|
||||
newplus_menu_item_open_templates.wID = menu_id;
|
||||
@@ -174,7 +174,7 @@ void shell_context_menu_win10::add_open_templates_to_context_menu(HMENU sub_menu
|
||||
|
||||
void shell_context_menu_win10::add_separator_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index)
|
||||
{
|
||||
MENUITEMINFO menu_item_separator;
|
||||
MENUITEMINFO menu_item_separator = { 0 };
|
||||
menu_item_separator.cbSize = sizeof(MENUITEMINFO);
|
||||
menu_item_separator.fMask = MIIM_FTYPE;
|
||||
menu_item_separator.fType = MFT_SEPARATOR;
|
||||
@@ -184,8 +184,11 @@ void shell_context_menu_win10::add_separator_to_context_menu(HMENU sub_menu_of_t
|
||||
void shell_context_menu_win10::add_template_item_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index, newplus::template_item* const template_item, int menu_id, int index)
|
||||
{
|
||||
wchar_t menu_name[256] = { 0 };
|
||||
wcscpy_s(menu_name, ARRAYSIZE(menu_name), template_item->get_menu_title(!utilities::get_newplus_setting_hide_extension(), !utilities::get_newplus_setting_hide_starting_digits()).c_str());
|
||||
MENUITEMINFO newplus_menu_item_template;
|
||||
wcscpy_s(menu_name, ARRAYSIZE(menu_name), template_item->get_menu_title(
|
||||
!utilities::get_newplus_setting_hide_extension(),
|
||||
!utilities::get_newplus_setting_hide_starting_digits(),
|
||||
utilities::get_newplus_setting_resolve_variables()).c_str());
|
||||
MENUITEMINFO newplus_menu_item_template = { 0 };
|
||||
newplus_menu_item_template.cbSize = sizeof(MENUITEMINFO);
|
||||
newplus_menu_item_template.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_DATA;
|
||||
newplus_menu_item_template.wID = menu_id;
|
||||
|
||||
@@ -114,6 +114,8 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="dll_main.h" />
|
||||
<ClInclude Include="helpers_filesystem.h" />
|
||||
<ClInclude Include="helpers_variables.h" />
|
||||
<ClInclude Include="shell_context_menu.h" />
|
||||
<ClInclude Include="shell_context_sub_menu.h" />
|
||||
<ClInclude Include="shell_context_sub_menu_item.h" />
|
||||
@@ -128,6 +130,7 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
|
||||
<ClInclude Include="template_item.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\powerrename\lib\Helpers.cpp" />
|
||||
<ClCompile Include="new_utilities.cpp" />
|
||||
<ClCompile Include="shell_context_menu.cpp" />
|
||||
<ClCompile Include="shell_context_sub_menu.cpp" />
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
<ClCompile Include="new_utilities.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\powerrename\lib\Helpers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="template_folder.h">
|
||||
@@ -75,6 +78,12 @@
|
||||
<ClInclude Include="resource.base.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="helpers_filesystem.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="helpers_variables.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace newplus::constants::non_localizable
|
||||
|
||||
constexpr WCHAR settings_json_key_hide_starting_digits[] = L"HideStartingDigits";
|
||||
|
||||
constexpr WCHAR settings_json_key_replace_variables[] = L"ReplaceVariables";
|
||||
|
||||
constexpr WCHAR settings_json_key_template_location[] = L"TemplateLocation";
|
||||
|
||||
constexpr WCHAR context_menu_package_name[] = L"NewPlusContextMenu";
|
||||
@@ -30,5 +32,5 @@ namespace newplus::constants::non_localizable
|
||||
|
||||
constexpr WCHAR open_templates_icon_dark_resource_relative_path[] = L"\\Assets\\NewPlus\\Open_templates_dark.ico";
|
||||
|
||||
constexpr WCHAR desktop_ini_filename[] = L"desktop.ini";
|
||||
constexpr WCHAR parent_folder_name_variable[] = L"$PARENT_FOLDER_NAME";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "helpers_variables.h"
|
||||
|
||||
namespace newplus::helpers::filesystem
|
||||
{
|
||||
namespace constants::non_localizable
|
||||
{
|
||||
constexpr WCHAR desktop_ini_filename[] = L"desktop.ini";
|
||||
}
|
||||
|
||||
inline bool is_hidden(const std::filesystem::path path)
|
||||
{
|
||||
const std::filesystem::path::string_type name = path.filename();
|
||||
if (name == constants::non_localizable::desktop_ini_filename)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool is_directory(const std::filesystem::path path)
|
||||
{
|
||||
const auto entry = std::filesystem::directory_entry(path);
|
||||
return entry.is_directory();
|
||||
}
|
||||
|
||||
inline std::wstring make_valid_filename(const std::wstring& string, const wchar_t replace_with = L' ')
|
||||
{
|
||||
// replace all non-filename-valid chars with replace_with wchar
|
||||
std::wstring valid_filename = string;
|
||||
|
||||
std::replace_if(valid_filename.begin(), valid_filename.end(), [](wchar_t c) { return c == L'/' || c == L'\\' || c == L':' || c == L'*' || c == L'?' || c == L'"' || c == L'<' || c == L'>' || c == L'|'; }, replace_with);
|
||||
|
||||
return valid_filename;
|
||||
}
|
||||
|
||||
inline std::wstring make_unique_path_name(const std::wstring& initial_path)
|
||||
{
|
||||
std::filesystem::path folder_path(initial_path);
|
||||
std::filesystem::path path_based_on(initial_path);
|
||||
|
||||
int counter = 1;
|
||||
|
||||
while (std::filesystem::exists(folder_path))
|
||||
{
|
||||
std::wstring new_filename = path_based_on.stem().wstring() + L" (" + std::to_wstring(counter) + L")";
|
||||
if (path_based_on.has_extension())
|
||||
{
|
||||
new_filename += path_based_on.extension().wstring();
|
||||
}
|
||||
folder_path = path_based_on.parent_path() / new_filename;
|
||||
counter++;
|
||||
}
|
||||
|
||||
return folder_path.wstring();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
#pragma once
|
||||
|
||||
#include <regex>
|
||||
#include "..\..\powerrename\lib\Helpers.h"
|
||||
#include "helpers_filesystem.h"
|
||||
|
||||
#pragma comment(lib, "Pathcch.lib")
|
||||
|
||||
namespace newplus::helpers::variables
|
||||
{
|
||||
inline std::wstring resolve_an_environment_variable(const std::wstring& string)
|
||||
{
|
||||
std::wstring return_string = string;
|
||||
wchar_t* env_variable = nullptr;
|
||||
|
||||
_wdupenv_s(&env_variable, nullptr, return_string.c_str());
|
||||
|
||||
if (env_variable != nullptr)
|
||||
{
|
||||
return_string = env_variable;
|
||||
free(env_variable);
|
||||
}
|
||||
|
||||
return return_string;
|
||||
}
|
||||
|
||||
inline std::wstring resolve_date_time_variables(const std::wstring& string)
|
||||
{
|
||||
SYSTEMTIME local_now = { 0 };
|
||||
GetLocalTime(&local_now);
|
||||
wchar_t resolved_filename[MAX_PATH] = { 0 };
|
||||
GetDatedFileName(resolved_filename, ARRAYSIZE(resolved_filename), string.c_str(), local_now);
|
||||
|
||||
return resolved_filename;
|
||||
}
|
||||
|
||||
inline std::wstring replace_all_occurrences(const std::wstring& string, const std::wstring& search_for, const std::wstring& replacement)
|
||||
{
|
||||
std::wstring return_string = string;
|
||||
size_t pos = 0;
|
||||
|
||||
while ((pos = return_string.find(search_for, pos)) != std::wstring::npos)
|
||||
{
|
||||
return_string.replace(pos, search_for.length(), replacement);
|
||||
pos += replacement.length();
|
||||
}
|
||||
|
||||
return return_string;
|
||||
}
|
||||
|
||||
inline std::wstring resolve_environment_variables(const std::wstring& string)
|
||||
{
|
||||
// Do case-insensitive string replacement of environment variables being consistent with normal %eNV_VaR% behavior
|
||||
std::wstring return_string = string;
|
||||
const std::wregex reg_expression(L"%([^%]+)%");
|
||||
std::wsmatch match;
|
||||
|
||||
size_t start = 0;
|
||||
while (std::regex_search(return_string.cbegin() + start, return_string.cend(), match, reg_expression))
|
||||
{
|
||||
std::wstring env_var_name = match[1].str();
|
||||
std::wstring env_var_value = resolve_an_environment_variable(env_var_name);
|
||||
if (!env_var_value.empty())
|
||||
{
|
||||
size_t match_position = match.position(0) + start;
|
||||
return_string.replace(match_position, match.length(0), env_var_value);
|
||||
start = match_position + env_var_value.length();
|
||||
}
|
||||
else
|
||||
{
|
||||
start += match.position(0) + match.length(0);
|
||||
}
|
||||
}
|
||||
|
||||
return return_string;
|
||||
}
|
||||
|
||||
inline std::wstring resolve_parent_folder(const std::wstring& string, const std::wstring& parent_folder_name)
|
||||
{
|
||||
// Do case-sensitive string replacement, for consistency on variables designated with $
|
||||
std::wstring result = replace_all_occurrences(string, constants::non_localizable::parent_folder_name_variable, parent_folder_name);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::filesystem::path resolve_variables_in_filename(const std::wstring& filename, const std::wstring& parent_folder_name)
|
||||
{
|
||||
std::wstring result;
|
||||
|
||||
result = resolve_date_time_variables(filename);
|
||||
result = resolve_environment_variables(result);
|
||||
if (!parent_folder_name.empty())
|
||||
{
|
||||
result = resolve_parent_folder(result, parent_folder_name);
|
||||
}
|
||||
result = newplus::helpers::filesystem::make_valid_filename(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::filesystem::path resolve_variables_in_path(const std::filesystem::path& path)
|
||||
{
|
||||
// Need to resolve the whole path top-down (root to leaf), because of the support for $PARENT_FOLDER_NAME
|
||||
std::filesystem::path result;
|
||||
std::wstring previous_section;
|
||||
std::wstring current_section;
|
||||
auto path_section = path.begin();
|
||||
int level = 0;
|
||||
|
||||
while (path_section != path.end())
|
||||
{
|
||||
previous_section = current_section;
|
||||
current_section = path_section->wstring();
|
||||
|
||||
if (level <= 1)
|
||||
{
|
||||
// Up to and including L"x:\\"
|
||||
result /= current_section;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Past L"x:\\", e.g. L"x:\\level1" and beyond
|
||||
result /= resolve_variables_in_filename(current_section, previous_section);
|
||||
}
|
||||
path_section++;
|
||||
level++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void resolve_variables_in_filename_and_rename_files(const std::filesystem::path& path, const bool do_rename = true)
|
||||
{
|
||||
// Depth first recursion, so that we start renaming the leaves, and avoid having to rescan
|
||||
for (const auto& entry : std::filesystem::directory_iterator(path))
|
||||
{
|
||||
if (std::filesystem::is_directory(entry.status()))
|
||||
{
|
||||
resolve_variables_in_filename_and_rename_files(entry.path(), do_rename);
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the actual rename
|
||||
for (const auto& current : std::filesystem::directory_iterator(path))
|
||||
{
|
||||
if (!newplus::helpers::filesystem::is_hidden(current))
|
||||
{
|
||||
const std::filesystem::path resolved_path = resolve_variables_in_path(current.path());
|
||||
|
||||
// Only rename if the filename is actually different
|
||||
const std::wstring non_resolved_leaf = current.path().filename();
|
||||
const std::wstring resolved_leaf = resolved_path.filename();
|
||||
|
||||
if (StrCmpIW(non_resolved_leaf.c_str(), resolved_leaf.c_str()) != 0)
|
||||
{
|
||||
const std::wstring org_name = current.path();
|
||||
const std::wstring new_name = current.path().parent_path() / resolved_leaf;
|
||||
const std::wstring really_new_name = helpers::filesystem::make_unique_path_name(new_name);
|
||||
|
||||
// To aid with testing, only conditionally rename
|
||||
if (do_rename)
|
||||
{
|
||||
std::filesystem::rename(org_name, really_new_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "settings.h"
|
||||
#include "template_item.h"
|
||||
#include "trace.h"
|
||||
#include "helpers_variables.h"
|
||||
|
||||
#pragma comment(lib, "Shlwapi.lib")
|
||||
|
||||
@@ -72,23 +73,6 @@ namespace newplus::utilities
|
||||
return hIcon;
|
||||
}
|
||||
|
||||
inline bool is_hidden(const std::filesystem::path path)
|
||||
{
|
||||
const std::filesystem::path::string_type name = path.filename();
|
||||
if (name == constants::non_localizable::desktop_ini_filename)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool is_directory(const std::filesystem::path path)
|
||||
{
|
||||
const auto entry = std::filesystem::directory_entry(path);
|
||||
return entry.is_directory();
|
||||
}
|
||||
|
||||
inline bool wstring_same_when_comparing_ignore_case(std::wstring stringA, std::wstring stringB)
|
||||
{
|
||||
transform(stringA.begin(), stringA.end(), stringA.begin(), towupper);
|
||||
@@ -97,20 +81,6 @@ namespace newplus::utilities
|
||||
return (stringA == stringB);
|
||||
}
|
||||
|
||||
inline void process_pending_window_messages(HWND window_handle = NULL)
|
||||
{
|
||||
if (window_handle == NULL)
|
||||
{
|
||||
window_handle = GetActiveWindow();
|
||||
}
|
||||
|
||||
MSG current_message;
|
||||
while (PeekMessage(¤t_message, window_handle, NULL, NULL, PM_REMOVE))
|
||||
{
|
||||
DispatchMessage(¤t_message);
|
||||
}
|
||||
}
|
||||
|
||||
inline std::wstring get_new_template_folder_location()
|
||||
{
|
||||
return NewSettingsInstance().GetTemplateLocation();
|
||||
@@ -126,6 +96,11 @@ namespace newplus::utilities
|
||||
return NewSettingsInstance().GetHideStartingDigits();
|
||||
}
|
||||
|
||||
inline bool get_newplus_setting_resolve_variables()
|
||||
{
|
||||
return NewSettingsInstance().GetReplaceVariables();
|
||||
}
|
||||
|
||||
inline void create_folder_if_not_exist(const std::filesystem::path path)
|
||||
{
|
||||
std::filesystem::create_directory(path);
|
||||
@@ -259,6 +234,7 @@ namespace newplus::utilities
|
||||
{
|
||||
ComPtr<IWebBrowserApp> web_browser_app;
|
||||
VARIANT v;
|
||||
VariantInit(&v);
|
||||
V_VT(&v) = VT_I4;
|
||||
V_I4(&v) = i;
|
||||
hr = shell_windows->Item(v, &shell_window);
|
||||
@@ -382,15 +358,30 @@ namespace newplus::utilities
|
||||
std::filesystem::path source_fullpath = template_entry->path;
|
||||
std::filesystem::path target_fullpath = std::wstring(target_path_name);
|
||||
|
||||
// Only append name to target if source is not a directory
|
||||
if (!utilities::is_directory(source_fullpath))
|
||||
// Get target name without starting digits as appropriate
|
||||
const std::wstring target_name = template_entry->get_target_filename(!utilities::get_newplus_setting_hide_starting_digits());
|
||||
|
||||
// Get initial resolved name
|
||||
target_fullpath /= target_name;
|
||||
|
||||
// Expand variables in name of the target path
|
||||
if (utilities::get_newplus_setting_resolve_variables())
|
||||
{
|
||||
target_fullpath.append(template_entry->get_target_filename(!utilities::get_newplus_setting_hide_starting_digits()));
|
||||
target_fullpath = helpers::variables::resolve_variables_in_path(target_fullpath);
|
||||
}
|
||||
|
||||
// Copy file and determine final filename
|
||||
// See if our target already exist, and if so then generate a unique name
|
||||
target_fullpath = helpers::filesystem::make_unique_path_name(target_fullpath);
|
||||
|
||||
// Finally copy file/folder/subfolders
|
||||
std::filesystem::path target_final_fullpath = template_entry->copy_object_to(GetActiveWindow(), target_fullpath);
|
||||
|
||||
// Resolve variables and rename files in newly copied folders and subfolders and files
|
||||
if (utilities::get_newplus_setting_resolve_variables() && helpers::filesystem::is_directory(target_final_fullpath))
|
||||
{
|
||||
helpers::variables::resolve_variables_in_filename_and_rename_files(target_final_fullpath);
|
||||
}
|
||||
|
||||
// Touch all files and set last modified to "now"
|
||||
update_last_write_time(target_final_fullpath);
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ void NewSettings::Save()
|
||||
|
||||
values.add_property(newplus::constants::non_localizable::settings_json_key_hide_file_extension, new_settings.hide_file_extension);
|
||||
values.add_property(newplus::constants::non_localizable::settings_json_key_hide_starting_digits, new_settings.hide_starting_digits);
|
||||
values.add_property(newplus::constants::non_localizable::settings_json_key_replace_variables, new_settings.replace_variables);
|
||||
values.add_property(newplus::constants::non_localizable::settings_json_key_template_location, new_settings.template_location);
|
||||
|
||||
values.save_to_settings_file();
|
||||
@@ -70,6 +71,9 @@ void NewSettings::InitializeWithDefaultSettings()
|
||||
// Currently a similar defaulting logic is also in InitializeWithDefaultSettings in NewViewModel.cs
|
||||
SetHideFileExtension(true);
|
||||
|
||||
// By default Replace Variables is turned off
|
||||
SetReplaceVariables(false);
|
||||
|
||||
SetTemplateLocation(GetTemplateLocationDefaultPath());
|
||||
}
|
||||
|
||||
@@ -139,6 +143,12 @@ void NewSettings::ParseJson()
|
||||
new_settings.hide_starting_digits = hideStartingDigitsValue.value();
|
||||
}
|
||||
|
||||
auto resolveVariables = settings.get_bool_value(newplus::constants::non_localizable::settings_json_key_replace_variables);
|
||||
if (resolveVariables.has_value())
|
||||
{
|
||||
new_settings.replace_variables = resolveVariables.value();
|
||||
}
|
||||
|
||||
GetSystemTimeAsFileTime(&new_settings_last_loaded_timestamp);
|
||||
}
|
||||
|
||||
@@ -163,11 +173,8 @@ bool NewSettings::GetEnabled()
|
||||
|
||||
bool NewSettings::GetHideFileExtension() const
|
||||
{
|
||||
auto gpoSetting = powertoys_gpo::getConfiguredNewPlusHideTemplateFilenameExtensionValue();
|
||||
if (gpoSetting == powertoys_gpo::gpo_rule_configured_enabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const auto gpoSetting = powertoys_gpo::getConfiguredNewPlusHideTemplateFilenameExtensionValue();
|
||||
|
||||
if (gpoSetting == powertoys_gpo::gpo_rule_configured_disabled)
|
||||
{
|
||||
return false;
|
||||
@@ -191,6 +198,23 @@ void NewSettings::SetHideStartingDigits(const bool hide_starting_digits)
|
||||
new_settings.hide_starting_digits = hide_starting_digits;
|
||||
}
|
||||
|
||||
bool NewSettings::GetReplaceVariables() const
|
||||
{
|
||||
const auto gpoSetting = powertoys_gpo::getConfiguredNewPlusReplaceVariablesValue();
|
||||
|
||||
if (gpoSetting == powertoys_gpo::gpo_rule_configured_disabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return new_settings.replace_variables;
|
||||
}
|
||||
|
||||
void NewSettings::SetReplaceVariables(const bool replace_variables)
|
||||
{
|
||||
new_settings.replace_variables = replace_variables;
|
||||
}
|
||||
|
||||
std::wstring NewSettings::GetTemplateLocation() const
|
||||
{
|
||||
return new_settings.template_location;
|
||||
@@ -201,7 +225,7 @@ void NewSettings::SetTemplateLocation(const std::wstring template_location)
|
||||
new_settings.template_location = template_location;
|
||||
}
|
||||
|
||||
std::wstring NewSettings::GetTemplateLocationDefaultPath()
|
||||
std::wstring NewSettings::GetTemplateLocationDefaultPath() const
|
||||
{
|
||||
static const std::wstring default_template_sub_folder_name =
|
||||
GET_RESOURCE_STRING_FALLBACK(
|
||||
|
||||
@@ -12,6 +12,8 @@ public:
|
||||
void SetHideFileExtension(const bool hide_file_extension);
|
||||
bool GetHideStartingDigits() const;
|
||||
void SetHideStartingDigits(const bool hide_starting_digits);
|
||||
bool GetReplaceVariables() const;
|
||||
void SetReplaceVariables(const bool resolve_variables);
|
||||
std::wstring GetTemplateLocation() const;
|
||||
void SetTemplateLocation(const std::wstring template_location);
|
||||
|
||||
@@ -25,12 +27,13 @@ private:
|
||||
bool enabled{ false };
|
||||
bool hide_file_extension{ true };
|
||||
bool hide_starting_digits{ true };
|
||||
bool replace_variables{ true };
|
||||
std::wstring template_location;
|
||||
};
|
||||
|
||||
void RefreshEnabledState();
|
||||
void InitializeWithDefaultSettings();
|
||||
std::wstring GetTemplateLocationDefaultPath();
|
||||
std::wstring GetTemplateLocationDefaultPath() const;
|
||||
|
||||
void Reload();
|
||||
void ParseJson();
|
||||
|
||||
@@ -69,8 +69,21 @@ IFACEMETHODIMP shell_context_menu::GetFlags(_Out_ EXPCMDFLAGS* returned_menu_ite
|
||||
|
||||
IFACEMETHODIMP shell_context_menu::EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** returned_enum_commands)
|
||||
{
|
||||
auto e = Make<shell_context_sub_menu>(site_of_folder);
|
||||
return e->QueryInterface(IID_PPV_ARGS(returned_enum_commands));
|
||||
try
|
||||
{
|
||||
auto e = Make<shell_context_sub_menu>(site_of_folder);
|
||||
return e->QueryInterface(IID_PPV_ARGS(returned_enum_commands));
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
Logger::error("New+ create submenu error: {}", ex.what());
|
||||
return E_FAIL;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error("New+ create submenu error");
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
@@ -80,8 +93,8 @@ IFACEMETHODIMP shell_context_menu::SetSite(_In_ IUnknown* site) noexcept
|
||||
this->site_of_folder = site;
|
||||
return S_OK;
|
||||
}
|
||||
IFACEMETHODIMP shell_context_menu::GetSite(_In_ REFIID riid, _COM_Outptr_ void** returned_site) noexcept
|
||||
IFACEMETHODIMP shell_context_menu::GetSite(_In_ REFIID interface_type, _COM_Outptr_ void** returned_site) noexcept
|
||||
{
|
||||
return this->site_of_folder.CopyTo(riid, returned_site);
|
||||
return this->site_of_folder.CopyTo(interface_type, returned_site);
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
|
||||
#pragma region IObjectWithSite
|
||||
IFACEMETHODIMP SetSite(_In_ IUnknown* site) noexcept;
|
||||
IFACEMETHODIMP GetSite(_In_ REFIID riid, _COM_Outptr_ void** site) noexcept;
|
||||
IFACEMETHODIMP GetSite(_In_ REFIID interface_type, _COM_Outptr_ void** site) noexcept;
|
||||
#pragma endregion
|
||||
|
||||
protected:
|
||||
|
||||
@@ -22,7 +22,8 @@ IFACEMETHODIMP shell_context_sub_menu_item::GetTitle(_In_opt_ IShellItemArray* i
|
||||
{
|
||||
return SHStrDup(this->template_entry->get_menu_title(
|
||||
!utilities::get_newplus_setting_hide_extension(),
|
||||
!utilities::get_newplus_setting_hide_starting_digits()
|
||||
!utilities::get_newplus_setting_hide_starting_digits(),
|
||||
utilities::get_newplus_setting_resolve_variables()
|
||||
).c_str(), title);
|
||||
}
|
||||
|
||||
@@ -95,6 +96,7 @@ IFACEMETHODIMP separator_context_menu_item::GetIcon(_In_opt_ IShellItemArray*, _
|
||||
|
||||
IFACEMETHODIMP separator_context_menu_item::GetFlags(_Out_ EXPCMDFLAGS* returned_flags)
|
||||
{
|
||||
// Separators no longer work on Windows 11 regular context menu. They do still work on the extended context menu.
|
||||
*returned_flags = ECF_ISSEPARATOR;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ void template_folder::rescan_template_folder()
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!utilities::is_hidden(entry.path()))
|
||||
if (!helpers::filesystem::is_hidden(entry.path()))
|
||||
{
|
||||
files.push_back({ entry.path().wstring(), new template_item(entry) });
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "new_utilities.h"
|
||||
#include <cassert>
|
||||
#include <thread>
|
||||
#include <shellapi.h>
|
||||
#include <shlobj_core.h>
|
||||
|
||||
using namespace Microsoft::WRL;
|
||||
@@ -17,7 +16,7 @@ template_item::template_item(const std::filesystem::path entry)
|
||||
path = entry;
|
||||
}
|
||||
|
||||
std::wstring template_item::get_menu_title(const bool show_extension, const bool show_starting_digits) const
|
||||
std::wstring template_item::get_menu_title(const bool show_extension, const bool show_starting_digits, const bool show_resolved_variables) const
|
||||
{
|
||||
std::wstring title = path.filename();
|
||||
|
||||
@@ -27,13 +26,21 @@ std::wstring template_item::get_menu_title(const bool show_extension, const bool
|
||||
title = remove_starting_digits_from_filename(title);
|
||||
}
|
||||
|
||||
if (show_resolved_variables)
|
||||
{
|
||||
title = helpers::variables::resolve_variables_in_filename(title, constants::non_localizable::parent_folder_name_variable);
|
||||
}
|
||||
|
||||
if (show_extension || !path.has_extension())
|
||||
{
|
||||
return title;
|
||||
}
|
||||
|
||||
std::wstring ext = path.extension();
|
||||
title = title.substr(0, title.length() - ext.length());
|
||||
if (!helpers::filesystem::is_directory(path))
|
||||
{
|
||||
std::wstring ext = path.extension();
|
||||
title = title.substr(0, title.length() - ext.length());
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
@@ -53,7 +60,8 @@ std::wstring template_item::get_target_filename(const bool include_starting_digi
|
||||
|
||||
std::wstring template_item::remove_starting_digits_from_filename(std::wstring filename) const
|
||||
{
|
||||
filename.erase(0, min(filename.find_first_not_of(L"0123456789 ."), filename.size()));
|
||||
filename.erase(0, min(filename.find_first_not_of(L"0123456789"), filename.size()));
|
||||
filename.erase(0, min(filename.find_first_not_of(L" ."), filename.size()));
|
||||
|
||||
return filename;
|
||||
}
|
||||
@@ -70,7 +78,7 @@ HICON template_item::get_explorer_icon_handle() const
|
||||
|
||||
std::filesystem::path template_item::copy_object_to(const HWND window_handle, const std::filesystem::path destination) const
|
||||
{
|
||||
// SHFILEOPSTRUCT wants the from and to paths to be terminated with two NULLs,
|
||||
// SHFILEOPSTRUCT wants the from and to paths to be terminated with two NULLs.
|
||||
wchar_t double_terminated_path_from[MAX_PATH + 1] = { 0 };
|
||||
wcsncpy_s(double_terminated_path_from, this->path.c_str(), this->path.wstring().length());
|
||||
double_terminated_path_from[this->path.wstring().length() + 1] = 0;
|
||||
@@ -84,37 +92,16 @@ std::filesystem::path template_item::copy_object_to(const HWND window_handle, co
|
||||
file_operation_params.hwnd = window_handle;
|
||||
file_operation_params.pFrom = double_terminated_path_from;
|
||||
file_operation_params.pTo = double_terminated_path_to;
|
||||
file_operation_params.fFlags = FOF_RENAMEONCOLLISION | FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR | FOF_NOCOPYSECURITYATTRIBS | FOF_WANTMAPPINGHANDLE;
|
||||
file_operation_params.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR | FOF_NOCOPYSECURITYATTRIBS;
|
||||
|
||||
const int result = SHFileOperation(&file_operation_params);
|
||||
|
||||
if (!file_operation_params.hNameMappings)
|
||||
if (result != 0)
|
||||
{
|
||||
// No file name collision on copy
|
||||
if (utilities::is_directory(this->path))
|
||||
{
|
||||
// Append dir for consistency on directory naming inclusion for with and without collision
|
||||
std::filesystem::path with_dir = destination;
|
||||
with_dir /= this->path.filename();
|
||||
return with_dir;
|
||||
}
|
||||
|
||||
return destination;
|
||||
throw std::runtime_error("Failed to copy template");
|
||||
}
|
||||
|
||||
struct file_operation_collision_mapping
|
||||
{
|
||||
int index;
|
||||
SHNAMEMAPPING* mapping;
|
||||
};
|
||||
|
||||
file_operation_collision_mapping* mapping = static_cast<file_operation_collision_mapping*>(file_operation_params.hNameMappings);
|
||||
SHNAMEMAPPING* map = &mapping->mapping[0];
|
||||
std::wstring final_path(map->pszNewPath);
|
||||
|
||||
SHFreeNameMappings(file_operation_params.hNameMappings);
|
||||
|
||||
return final_path;
|
||||
return destination;
|
||||
}
|
||||
|
||||
void template_item::refresh_target(const std::filesystem::path target_final_fullpath) const
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace newplus
|
||||
public:
|
||||
template_item(const std::filesystem::path entry);
|
||||
|
||||
std::wstring get_menu_title(const bool show_extension, const bool show_starting_digits) const;
|
||||
std::wstring get_menu_title(const bool show_extension, const bool show_starting_digits, const bool show_resolved_variables) const;
|
||||
|
||||
std::wstring get_target_filename(const bool include_starting_digits) const;
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
|
||||
using Common.UI;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using WorkspacesEditor.Telemetry;
|
||||
using WorkspacesEditor.Utils;
|
||||
using WorkspacesEditor.ViewModels;
|
||||
|
||||
@@ -36,6 +36,8 @@ namespace WorkspacesEditor
|
||||
|
||||
public App()
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new WorkspacesEditorStartEvent() { TimeStamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() });
|
||||
|
||||
WorkspacesEditorIO = new WorkspacesEditorIO();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using WorkspacesEditor.Telemetry;
|
||||
using WorkspacesEditor.Utils;
|
||||
using WorkspacesEditor.ViewModels;
|
||||
|
||||
@@ -87,6 +88,8 @@ namespace WorkspacesEditor
|
||||
},
|
||||
Application.Current.Dispatcher,
|
||||
cancellationToken.Token);
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new WorkspacesEditorStartFinishEvent() { TimeStamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() });
|
||||
}
|
||||
|
||||
private bool IsEditorInsideVisibleArea()
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.Diagnostics.Tracing;
|
||||
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace WorkspacesEditor.Telemetry;
|
||||
|
||||
[EventData]
|
||||
public class WorkspacesEditorStartEvent() : EventBase, IEvent
|
||||
{
|
||||
public long TimeStamp { get; set; }
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.Diagnostics.Tracing;
|
||||
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace WorkspacesEditor.Telemetry;
|
||||
|
||||
[EventData]
|
||||
public class WorkspacesEditorStartFinishEvent() : EventBase, IEvent
|
||||
{
|
||||
public long TimeStamp { get; set; }
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -52,6 +52,11 @@ namespace SnapshotUtils
|
||||
|
||||
for (const auto window : windows)
|
||||
{
|
||||
if (WindowFilter::FilterPopup(window))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// filter by window rect size
|
||||
RECT rect = WindowUtils::GetWindowRect(window);
|
||||
if (rect.right - rect.left <= 0 || rect.bottom - rect.top <= 0)
|
||||
@@ -93,7 +98,7 @@ namespace SnapshotUtils
|
||||
continue;
|
||||
}
|
||||
|
||||
// fix for the packaged apps that are not caught when minimized, e.g., Settings.
|
||||
// fix for the packaged apps that are not caught when minimized, e.g. Settings, Microsoft ToDo, ...
|
||||
if (processPath.ends_with(NonLocalizable::ApplicationFrameHost))
|
||||
{
|
||||
for (auto otherWindow : windows)
|
||||
@@ -110,11 +115,6 @@ namespace SnapshotUtils
|
||||
}
|
||||
}
|
||||
|
||||
if (WindowFilter::FilterPopup(window))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto data = Utils::Apps::GetApp(processPath, pid, installedApps);
|
||||
if (!data.has_value() || data->name.empty())
|
||||
{
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
#include <WindowProperties/WorkspacesWindowPropertyUtils.h>
|
||||
#include <WorkspacesLib/PwaHelper.h>
|
||||
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const std::wstring ApplicationFrameHost = L"ApplicationFrameHost.exe";
|
||||
}
|
||||
|
||||
namespace PlacementHelper
|
||||
{
|
||||
// When calculating the coordinates difference (== 'distance') between 2 windows, there are additional values added to the real distance
|
||||
@@ -157,6 +162,11 @@ std::optional<WindowWithDistance> WindowArranger::GetNearestWindow(const Workspa
|
||||
|
||||
for (HWND window : m_windowsBefore)
|
||||
{
|
||||
if (WindowFilter::FilterPopup(window))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::find(movedWindows.begin(), movedWindows.end(), window) != movedWindows.end())
|
||||
{
|
||||
continue;
|
||||
@@ -170,6 +180,24 @@ std::optional<WindowWithDistance> WindowArranger::GetNearestWindow(const Workspa
|
||||
|
||||
DWORD pid{};
|
||||
GetWindowThreadProcessId(window, &pid);
|
||||
std::wstring title = WindowUtils::GetWindowTitle(window);
|
||||
|
||||
// fix for the packaged apps that are not caught when minimized, e.g. Settings, Microsoft ToDo, ...
|
||||
if (processPath.ends_with(NonLocalizable::ApplicationFrameHost))
|
||||
{
|
||||
for (auto otherWindow : m_windowsBefore)
|
||||
{
|
||||
DWORD otherPid{};
|
||||
GetWindowThreadProcessId(otherWindow, &otherPid);
|
||||
|
||||
// searching for the window with the same title but different PID
|
||||
if (pid != otherPid && title == WindowUtils::GetWindowTitle(otherWindow))
|
||||
{
|
||||
processPath = get_process_path(otherPid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto data = Utils::Apps::GetApp(processPath, pid, m_installedApps);
|
||||
if (!data.has_value())
|
||||
|
||||
@@ -50,11 +50,6 @@ namespace WindowFilter
|
||||
return false;
|
||||
}
|
||||
|
||||
if (WindowFilter::FilterPopup(window))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!VirtualDesktop::instance().IsWindowOnCurrentDesktop(window))
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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.
|
||||
|
||||
namespace FancyZonesEditorCommon.Data
|
||||
{
|
||||
public enum CustomLayout
|
||||
{
|
||||
Canvas,
|
||||
Grid,
|
||||
}
|
||||
|
||||
public static class CustomLayoutEnumExtension
|
||||
{
|
||||
private const string CanvasJsonTag = "canvas";
|
||||
private const string GridJsonTag = "grid";
|
||||
|
||||
public static string TypeToString(this CustomLayout value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case CustomLayout.Canvas:
|
||||
return CanvasJsonTag;
|
||||
case CustomLayout.Grid:
|
||||
return GridJsonTag;
|
||||
}
|
||||
|
||||
return CanvasJsonTag;
|
||||
}
|
||||
|
||||
public static CustomLayout TypeFromString(string value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case CanvasJsonTag:
|
||||
return CustomLayout.Canvas;
|
||||
case GridJsonTag:
|
||||
return CustomLayout.Grid;
|
||||
}
|
||||
|
||||
return CustomLayout.Canvas;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using ModernWpf.Controls;
|
||||
using OpenQA.Selenium;
|
||||
using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper;
|
||||
|
||||
namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class ApplyLayoutTests : UITestBase
|
||||
{
|
||||
public ApplyLayoutTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
{
|
||||
}
|
||||
|
||||
private static readonly EditorParameters.ParamsWrapper Parameters = new EditorParameters.ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<EditorParameters.NativeMonitorDataWrapper>
|
||||
{
|
||||
new EditorParameters.NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
new EditorParameters.NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-2",
|
||||
MonitorInstanceId = "instance-id-2",
|
||||
MonitorSerialNumber = "serial-number-2",
|
||||
MonitorNumber = 2,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 1920,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly CustomLayouts.CustomLayoutListWrapper CustomLayoutsList = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayouts.CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{E7807D0D-6223-4883-B15B-1F3883944C09}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Custom layout",
|
||||
Info = new CustomLayouts().ToJsonElement(new CustomLayouts.CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 952,
|
||||
RefWidth = 1500,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CustomLayouts.CanvasInfoWrapper.CanvasZoneWrapper> { },
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly LayoutTemplates.TemplateLayoutsListWrapper TemplateLayoutsList = new LayoutTemplates.TemplateLayoutsListWrapper
|
||||
{
|
||||
LayoutTemplates = new List<LayoutTemplates.TemplateLayoutWrapper>
|
||||
{
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Blank.TypeToString(),
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(Parameters));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(TemplateLayoutsList));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(CustomLayoutsList));
|
||||
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper>
|
||||
{
|
||||
new DefaultLayouts.DefaultLayoutWrapper
|
||||
{
|
||||
MonitorConfiguration = MonitorConfigurationType.Horizontal.TypeToString(),
|
||||
Layout = new DefaultLayouts.DefaultLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = true,
|
||||
Spacing = 5,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
},
|
||||
new DefaultLayouts.DefaultLayoutWrapper
|
||||
{
|
||||
MonitorConfiguration = MonitorConfigurationType.Vertical.TypeToString(),
|
||||
Layout = new DefaultLayouts.DefaultLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Custom.TypeToString(),
|
||||
Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
ZoneCount = 0,
|
||||
ShowSpacing = false,
|
||||
Spacing = 0,
|
||||
SensitivityRadius = 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
|
||||
|
||||
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
|
||||
LayoutHotkeys.LayoutHotkeysWrapper layoutHotkeysWrapper = new LayoutHotkeys.LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(layoutHotkeysWrapper));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ApplyCustomLayout()
|
||||
{
|
||||
var layout = CustomLayoutsList.CustomLayouts[0];
|
||||
Assert.IsFalse(Session.Find<Element>(layout.Name).Selected);
|
||||
Session.Find<Element>(layout.Name).Click();
|
||||
|
||||
Assert.IsTrue(Session.Find<Element>(layout.Name).Selected);
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
var data = appliedLayouts.Read(appliedLayouts.File);
|
||||
Assert.AreEqual(Parameters.Monitors.Count, data.AppliedLayouts.Count);
|
||||
Assert.AreEqual(layout.Uuid, data.AppliedLayouts[0].AppliedLayout.Uuid);
|
||||
Assert.AreEqual(Parameters.Monitors[0].MonitorNumber, data.AppliedLayouts[0].Device.MonitorNumber);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ApplyTemplateLayout()
|
||||
{
|
||||
var layoutType = LayoutType.Columns;
|
||||
var layout = TestConstants.TemplateLayoutNames[layoutType];
|
||||
Assert.IsFalse(Session.Find<Element>(layout).Selected);
|
||||
Session.Find<Element>(layout).Click();
|
||||
|
||||
Assert.IsTrue(Session.Find<Element>(layout).Selected);
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
var data = appliedLayouts.Read(appliedLayouts.File);
|
||||
Assert.AreEqual(Parameters.Monitors.Count, data.AppliedLayouts.Count);
|
||||
Assert.AreEqual(layoutType.TypeToString(), data.AppliedLayouts[0].AppliedLayout.Type);
|
||||
Assert.AreEqual(Parameters.Monitors[0].MonitorNumber, data.AppliedLayouts[0].Device.MonitorNumber);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ApplyLayoutsOnEachMonitor()
|
||||
{
|
||||
// apply the layout on the first monitor
|
||||
var firstLayoutType = LayoutType.Columns;
|
||||
var firstLayoutName = TestConstants.TemplateLayoutNames[firstLayoutType];
|
||||
Session.Find<Element>(firstLayoutName).Click();
|
||||
Assert.IsTrue(Session.Find<Element>(firstLayoutName)!.Selected);
|
||||
|
||||
// apply the layout on the second monitor
|
||||
Session.Find<Element>(PowerToys.UITest.By.AccessibilityId("Monitors")).Find<Element>("Monitor 2").Click();
|
||||
var secondLayout = CustomLayoutsList.CustomLayouts[0];
|
||||
Session.Find<Element>(secondLayout.Name).Click();
|
||||
Assert.IsTrue(Session.Find<Element>(secondLayout.Name)!.Selected);
|
||||
|
||||
// verify the layout on the first monitor wasn't changed
|
||||
Session.Find<Element>(PowerToys.UITest.By.AccessibilityId("Monitors")).Find<Element>("Monitor 1").Click();
|
||||
Assert.IsTrue(Session.Find<Element>(firstLayoutName)!.Selected);
|
||||
|
||||
// verify the file
|
||||
var appliedLayouts = new AppliedLayouts();
|
||||
var data = appliedLayouts.Read(appliedLayouts.File);
|
||||
Assert.AreEqual(Parameters.Monitors.Count, data.AppliedLayouts.Count);
|
||||
Assert.AreEqual(firstLayoutType.TypeToString(), data.AppliedLayouts.Find(x => x.Device.MonitorNumber == 1).AppliedLayout.Type);
|
||||
Assert.AreEqual(secondLayout.Uuid, data.AppliedLayouts.Find(x => x.Device.MonitorNumber == 2).AppliedLayout.Uuid);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ApplyTemplateWithDifferentParametersOnEachMonitor()
|
||||
{
|
||||
var layoutType = LayoutType.Columns;
|
||||
var layoutName = TestConstants.TemplateLayoutNames[layoutType];
|
||||
|
||||
// apply the layout on the first monitor, set parameters
|
||||
Session.Find<Element>(layoutName).Click();
|
||||
Session.Find<Element>(layoutName).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.TemplateZoneSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
slider.SendKeys(Keys.Right);
|
||||
var expectedFirstLayoutZoneCount = int.Parse(slider.Text!, CultureInfo.InvariantCulture);
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// apply the layout on the second monitor, set different parameters
|
||||
Session.Find<Element>(PowerToys.UITest.By.AccessibilityId("Monitors")).Find<Element>("Monitor 2").Click();
|
||||
Session.Find<Element>(layoutName).Click();
|
||||
Session.Find<Element>(layoutName).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.TemplateZoneSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Left);
|
||||
var expectedSecondLayoutZoneCount = int.Parse(slider.Text!, CultureInfo.InvariantCulture);
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// verify the layout on the first monitor wasn't changed
|
||||
Session.Find<Element>(PowerToys.UITest.By.AccessibilityId("Monitors")).Find<Element>("Monitor 1").Click();
|
||||
Session.Find<Element>(layoutName).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.TemplateZoneSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
Assert.AreEqual(expectedFirstLayoutZoneCount, int.Parse(slider.Text!, CultureInfo.InvariantCulture));
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// check the file
|
||||
var appliedLayouts = new AppliedLayouts();
|
||||
var data = appliedLayouts.Read(appliedLayouts.File);
|
||||
Assert.AreEqual(Parameters.Monitors.Count, data.AppliedLayouts.Count);
|
||||
Assert.AreEqual(layoutType.TypeToString(), data.AppliedLayouts.Find(x => x.Device.MonitorNumber == 1).AppliedLayout.Type);
|
||||
Assert.AreEqual(expectedFirstLayoutZoneCount, data.AppliedLayouts.Find(x => x.Device.MonitorNumber == 1).AppliedLayout.ZoneCount);
|
||||
Assert.AreEqual(layoutType.TypeToString(), data.AppliedLayouts.Find(x => x.Device.MonitorNumber == 2).AppliedLayout.Type);
|
||||
Assert.AreEqual(expectedSecondLayoutZoneCount, data.AppliedLayouts.Find(x => x.Device.MonitorNumber == 2).AppliedLayout.ZoneCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
// 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.Collections.Generic;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using static FancyZonesEditorCommon.Data.EditorParameters;
|
||||
using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper;
|
||||
|
||||
namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class CopyLayoutTests : UITestBase
|
||||
{
|
||||
public CopyLayoutTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
{
|
||||
}
|
||||
|
||||
private static readonly CustomLayouts.CustomLayoutListWrapper CustomLayouts = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayouts.CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
Type = CustomLayout.Grid.TypeToString(),
|
||||
Name = "Grid custom layout",
|
||||
Info = new CustomLayouts().ToJsonElement(new CustomLayouts.GridInfoWrapper
|
||||
{
|
||||
Rows = 2,
|
||||
Columns = 3,
|
||||
RowsPercentage = new List<int> { 2967, 7033 },
|
||||
ColumnsPercentage = new List<int> { 2410, 6040, 1550 },
|
||||
CellChildMap = new int[][] { [0, 1, 1], [0, 2, 3] },
|
||||
SensitivityRadius = 30,
|
||||
Spacing = 26,
|
||||
ShowSpacing = false,
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly LayoutHotkeys.LayoutHotkeysWrapper Hotkeys = new LayoutHotkeys.LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper>
|
||||
{
|
||||
new LayoutHotkeys.LayoutHotkeyWrapper
|
||||
{
|
||||
LayoutId = CustomLayouts.CustomLayouts[0].Uuid,
|
||||
Key = 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly DefaultLayouts.DefaultLayoutsListWrapper DefaultLayouts = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper>
|
||||
{
|
||||
new DefaultLayouts.DefaultLayoutWrapper
|
||||
{
|
||||
MonitorConfiguration = MonitorConfigurationType.Vertical.TypeToString(),
|
||||
Layout = new DefaultLayouts.DefaultLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Type = "custom",
|
||||
Uuid = CustomLayouts.CustomLayouts[0].Uuid,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 192,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(CustomLayouts));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
LayoutTemplates.TemplateLayoutsListWrapper templateLayoutsListWrapper = new LayoutTemplates.TemplateLayoutsListWrapper
|
||||
{
|
||||
LayoutTemplates = new List<LayoutTemplates.TemplateLayoutWrapper>
|
||||
{
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Blank.TypeToString(),
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(templateLayoutsListWrapper));
|
||||
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(DefaultLayouts));
|
||||
|
||||
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(Hotkeys));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CopyTemplate_FromEditLayoutWindow()
|
||||
{
|
||||
string copiedLayoutName = TestConstants.TemplateLayoutNames[LayoutType.Focus] + " (1)";
|
||||
Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.Focus]).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
ClickCopyLayout();
|
||||
|
||||
// verify the layout is copied
|
||||
Assert.IsNotNull(Session.Find<Element>(copiedLayoutName)); // new name is presented
|
||||
|
||||
// verify the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(CustomLayouts.CustomLayouts.Count + 1, data.CustomLayouts.Count);
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Name == copiedLayoutName));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CopyTemplate_FromContextMenu()
|
||||
{
|
||||
string copiedLayoutName = TestConstants.TemplateLayoutNames[LayoutType.Rows] + " (1)";
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, TestConstants.TemplateLayoutNames[LayoutType.Rows], ElementName.CreateCustomLayout);
|
||||
|
||||
// verify the layout is copied
|
||||
Assert.IsNotNull(Session.Find<Element>(copiedLayoutName)); // new name is presented
|
||||
|
||||
// verify the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(CustomLayouts.CustomLayouts.Count + 1, data.CustomLayouts.Count);
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Name == copiedLayoutName));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CopyTemplate_DefaultLayout()
|
||||
{
|
||||
string copiedLayoutName = TestConstants.TemplateLayoutNames[LayoutType.PriorityGrid] + " (1)";
|
||||
Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.PriorityGrid]).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
ClickCopyLayout();
|
||||
|
||||
// verify the layout is copied
|
||||
Assert.IsNotNull(Session.Find<Element>(copiedLayoutName)); // new name is presented
|
||||
|
||||
// verify the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(CustomLayouts.CustomLayouts.Count + 1, data.CustomLayouts.Count);
|
||||
|
||||
// verify the default layout wasn't changed
|
||||
Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.PriorityGrid]).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
var horizontalDefaultButton = Session.Find<Button>(By.AccessibilityId(AccessibilityId.HorizontalDefaultButtonChecked));
|
||||
Assert.IsNotNull(horizontalDefaultButton);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
Session.Find<Element>(copiedLayoutName).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
horizontalDefaultButton = Session.Find<Button>(By.AccessibilityId(AccessibilityId.HorizontalDefaultButtonUnchecked));
|
||||
Assert.IsNotNull(horizontalDefaultButton);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// verify the default layouts file wasn't changed
|
||||
var defaultLayouts = new DefaultLayouts();
|
||||
var defaultLayoutData = defaultLayouts.Read(defaultLayouts.File);
|
||||
Assert.AreEqual(defaultLayouts.Serialize(DefaultLayouts), defaultLayouts.Serialize(defaultLayoutData));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CopyCustomLayout_FromEditLayoutWindow()
|
||||
{
|
||||
string copiedLayoutName = CustomLayouts.CustomLayouts[0].Name + " (1)";
|
||||
Session.Find<Element>(CustomLayouts.CustomLayouts[0].Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
ClickCopyLayout();
|
||||
|
||||
// verify the layout is copied
|
||||
Assert.IsNotNull(Session.Find<Element>(copiedLayoutName)); // new name is presented
|
||||
|
||||
// verify the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(CustomLayouts.CustomLayouts.Count + 1, data.CustomLayouts.Count);
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Name == copiedLayoutName));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CopyCustomLayout_FromContextMenu()
|
||||
{
|
||||
string copiedLayoutName = CustomLayouts.CustomLayouts[0].Name + " (1)";
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, CustomLayouts.CustomLayouts[0].Name, ElementName.Duplicate);
|
||||
|
||||
// verify the layout is copied
|
||||
Assert.IsNotNull(Session.Find<Element>(copiedLayoutName)); // new name is presented
|
||||
|
||||
// verify the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(CustomLayouts.CustomLayouts.Count + 1, data.CustomLayouts.Count);
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Name == copiedLayoutName));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CopyCustomLayout_DefaultLayout()
|
||||
{
|
||||
string copiedLayoutName = CustomLayouts.CustomLayouts[0].Name + " (1)";
|
||||
Session.Find<Element>(CustomLayouts.CustomLayouts[0].Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
ClickCopyLayout();
|
||||
|
||||
// verify the layout is copied
|
||||
Assert.IsNotNull(Session.Find<Element>(copiedLayoutName)); // new name is presented
|
||||
|
||||
// verify the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(CustomLayouts.CustomLayouts.Count + 1, data.CustomLayouts.Count);
|
||||
|
||||
// verify the default layout wasn't changed
|
||||
Session.Find<Element>(CustomLayouts.CustomLayouts[0].Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
var verticalDefaultButton = Session.Find<Button>(By.AccessibilityId(AccessibilityId.VerticalDefaultButtonChecked));
|
||||
Assert.IsNotNull(verticalDefaultButton);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
Session.Find<Element>(copiedLayoutName).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
verticalDefaultButton = Session.Find<Button>(By.AccessibilityId(AccessibilityId.VerticalDefaultButtonUnchecked));
|
||||
Assert.IsNotNull(verticalDefaultButton);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// verify the default layouts file wasn't changed
|
||||
var defaultLayouts = new DefaultLayouts();
|
||||
var defaultLayoutData = defaultLayouts.Read(defaultLayouts.File);
|
||||
Assert.AreEqual(defaultLayouts.Serialize(DefaultLayouts), defaultLayouts.Serialize(defaultLayoutData));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CopyCustomLayout_Hotkey()
|
||||
{
|
||||
string copiedLayoutName = CustomLayouts.CustomLayouts[0].Name + " (1)";
|
||||
Session.Find<Element>(CustomLayouts.CustomLayouts[0].Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
ClickCopyLayout();
|
||||
|
||||
// verify the layout is copied
|
||||
Assert.IsNotNull(Session.Find<Element>(copiedLayoutName)); // new name is presented
|
||||
|
||||
// verify the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(CustomLayouts.CustomLayouts.Count + 1, data.CustomLayouts.Count);
|
||||
|
||||
// verify the hotkey wasn't changed
|
||||
Session.Find<Element>(CustomLayouts.CustomLayouts[0].Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
var hotkeyComboBox = Session.Find<Element>(By.AccessibilityId(AccessibilityId.HotkeyComboBox));
|
||||
Assert.IsNotNull(hotkeyComboBox);
|
||||
Assert.AreEqual("0", hotkeyComboBox.Text);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
Session.Find<Element>(copiedLayoutName).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
hotkeyComboBox = Session.Find<Element>(By.AccessibilityId(AccessibilityId.HotkeyComboBox));
|
||||
Assert.IsNotNull(hotkeyComboBox);
|
||||
Assert.AreEqual("None", hotkeyComboBox.Text);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// verify the hotkey file wasn't changed
|
||||
var hotkeys = new LayoutHotkeys();
|
||||
var hotkeyData = hotkeys.Read(hotkeys.File);
|
||||
Assert.AreEqual(hotkeys.Serialize(Hotkeys), hotkeys.Serialize(hotkeyData));
|
||||
}
|
||||
|
||||
public void ClickCopyLayout()
|
||||
{
|
||||
if (Session.FindAll<Element>(By.AccessibilityId(AccessibilityId.CopyTemplate)).Count != 0)
|
||||
{
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.CopyTemplate)).Click();
|
||||
}
|
||||
else
|
||||
{
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.DuplicateLayoutButton)).Click();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
// 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.Collections.Generic;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper;
|
||||
|
||||
namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class CreateLayoutTests : UITestBase
|
||||
{
|
||||
public CreateLayoutTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
{
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
// prepare test editor parameters with 2 monitors before launching the editor
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
EditorParameters.ParamsWrapper parameters = new EditorParameters.ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<EditorParameters.NativeMonitorDataWrapper>
|
||||
{
|
||||
new EditorParameters.NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
LayoutTemplates.TemplateLayoutsListWrapper templateLayoutsListWrapper = new LayoutTemplates.TemplateLayoutsListWrapper
|
||||
{
|
||||
LayoutTemplates = new List<LayoutTemplates.TemplateLayoutWrapper>
|
||||
{
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Blank.TypeToString(),
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(templateLayoutsListWrapper));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
CustomLayouts.CustomLayoutListWrapper customLayoutListWrapper = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(customLayoutListWrapper));
|
||||
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
|
||||
|
||||
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
|
||||
LayoutHotkeys.LayoutHotkeysWrapper layoutHotkeysWrapper = new LayoutHotkeys.LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(layoutHotkeysWrapper));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateWithDefaultName()
|
||||
{
|
||||
string name = "Custom layout 1";
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.NewLayoutButton)).Click();
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.PrimaryButton)).Click();
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// verify new layout presented
|
||||
Assert.IsNotNull(Session.Find<Element>(name));
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(1, data.CustomLayouts.Count);
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Name == name));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateWithCustomName()
|
||||
{
|
||||
string name = "Layout Name";
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.NewLayoutButton)).Click();
|
||||
var input = Session.Find<TextBox>(By.ClassName(ClassName.TextBox));
|
||||
Assert.IsNotNull(input);
|
||||
input.SetText(name, true);
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.PrimaryButton)).Click();
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// verify new layout presented
|
||||
Assert.IsNotNull(Session.Find<Element>(name));
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(1, data.CustomLayouts.Count);
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Name == name));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateGrid()
|
||||
{
|
||||
CustomLayout type = CustomLayout.Grid;
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.NewLayoutButton)).Click();
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.GridRadioButton)).Click();
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.PrimaryButton)).Click();
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(1, data.CustomLayouts.Count);
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Type == type.TypeToString()));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateCanvas()
|
||||
{
|
||||
CustomLayout type = CustomLayout.Canvas;
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.NewLayoutButton)).Click();
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.CanvasRadioButton)).Click();
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.PrimaryButton)).Click();
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(1, data.CustomLayouts.Count);
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Type == type.TypeToString()));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CancelGridCreation()
|
||||
{
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.NewLayoutButton)).Click();
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.GridRadioButton)).Click();
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.PrimaryButton)).Click();
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(0, data.CustomLayouts.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CancelCanvasCreation()
|
||||
{
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.NewLayoutButton)).Click();
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.CanvasRadioButton)).Click();
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.PrimaryButton)).Click();
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(0, data.CustomLayouts.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
// 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 FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using static FancyZonesEditorCommon.Data.CustomLayouts;
|
||||
using static FancyZonesEditorCommon.Data.EditorParameters;
|
||||
using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper;
|
||||
|
||||
namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class CustomLayoutsTests : UITestBase
|
||||
{
|
||||
public CustomLayoutsTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
{
|
||||
}
|
||||
|
||||
private static readonly CustomLayoutListWrapper Layouts = new CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
Type = CustomLayout.Grid.TypeToString(),
|
||||
Name = "Grid custom layout",
|
||||
Info = new CustomLayouts().ToJsonElement(new GridInfoWrapper
|
||||
{
|
||||
Rows = 2,
|
||||
Columns = 3,
|
||||
RowsPercentage = new List<int> { 2967, 7033 },
|
||||
ColumnsPercentage = new List<int> { 2410, 6040, 1550 },
|
||||
CellChildMap = new int[][] { [0, 1, 1], [0, 2, 3] },
|
||||
SensitivityRadius = 30,
|
||||
Spacing = 26,
|
||||
ShowSpacing = false,
|
||||
}),
|
||||
},
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{E7807D0D-6223-4883-B15B-1F3883944C09}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Canvas custom layout",
|
||||
Info = new CustomLayouts().ToJsonElement(new CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 952,
|
||||
RefWidth = 1500,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CanvasInfoWrapper.CanvasZoneWrapper>
|
||||
{
|
||||
new CanvasInfoWrapper.CanvasZoneWrapper
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 900,
|
||||
Height = 522,
|
||||
},
|
||||
new CanvasInfoWrapper.CanvasZoneWrapper
|
||||
{
|
||||
X = 900,
|
||||
Y = 0,
|
||||
Width = 600,
|
||||
Height = 750,
|
||||
},
|
||||
new CanvasInfoWrapper.CanvasZoneWrapper
|
||||
{
|
||||
X = 0,
|
||||
Y = 522,
|
||||
Width = 1500,
|
||||
Height = 430,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{F1A94F38-82B6-4876-A653-70D0E882DE2A}",
|
||||
Type = CustomLayout.Grid.TypeToString(),
|
||||
Name = "Grid custom layout spacing enabled",
|
||||
Info = new CustomLayouts().ToJsonElement(new GridInfoWrapper
|
||||
{
|
||||
Rows = 2,
|
||||
Columns = 3,
|
||||
RowsPercentage = new List<int> { 2967, 7033 },
|
||||
ColumnsPercentage = new List<int> { 2410, 6040, 1550 },
|
||||
CellChildMap = new int[][] { [0, 1, 1], [0, 2, 3] },
|
||||
SensitivityRadius = 30,
|
||||
Spacing = 10,
|
||||
ShowSpacing = true,
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(Layouts));
|
||||
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 192,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
LayoutTemplates.TemplateLayoutsListWrapper templateLayoutsListWrapper = new LayoutTemplates.TemplateLayoutsListWrapper
|
||||
{
|
||||
LayoutTemplates = new List<LayoutTemplates.TemplateLayoutWrapper>
|
||||
{
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Blank.TypeToString(),
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(templateLayoutsListWrapper));
|
||||
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
|
||||
|
||||
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
|
||||
LayoutHotkeys.LayoutHotkeysWrapper layoutHotkeysWrapper = new LayoutHotkeys.LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(layoutHotkeysWrapper));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Name_Initialize()
|
||||
{
|
||||
// verify all custom layouts are presented
|
||||
foreach (var layout in Layouts.CustomLayouts)
|
||||
{
|
||||
Assert.IsNotNull(Session.Find<Element>(layout.Name));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Rename_Save()
|
||||
{
|
||||
string newName = "New layout name";
|
||||
var oldName = Layouts.CustomLayouts[0].Name;
|
||||
|
||||
// rename the layout
|
||||
Session.Find<Element>(oldName).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
var input = Session.Find<TextBox>(PowerToys.UITest.By.ClassName(ClassName.TextBox));
|
||||
Assert.IsNotNull(input);
|
||||
input.SetText(newName, true);
|
||||
|
||||
// verify new name
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
if (Session.FindAll<Element>(oldName).Count != 0)
|
||||
{
|
||||
Assert.Fail("Error : previous name isn't presented"); // previous name isn't presented
|
||||
}
|
||||
|
||||
if (Session.FindAll<Element>(newName).Count == 0)
|
||||
{
|
||||
Assert.Fail("Error : new name is presented"); // new name is presented
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Rename_Cancel()
|
||||
{
|
||||
string newName = "New layout name";
|
||||
var oldName = Layouts.CustomLayouts[0].Name;
|
||||
|
||||
// rename the layout
|
||||
Session.Find<Element>(oldName).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
var input = Session.Find<TextBox>(PowerToys.UITest.By.ClassName(ClassName.TextBox));
|
||||
Assert.IsNotNull(input);
|
||||
input.SetText(newName, true);
|
||||
|
||||
// verify new name
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
Assert.IsTrue(Session.FindAll<Element>(oldName).Count == 0);
|
||||
Assert.IsTrue(Session.FindAll<Element>(newName).Count != 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void HighlightDistance_Initialize()
|
||||
{
|
||||
foreach (var layout in Layouts.CustomLayouts)
|
||||
{
|
||||
Session.Find<Element>(layout.Name).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SensitivitySlider));
|
||||
Assert.IsNotNull(slider);
|
||||
var expected = layout.Type == CustomLayout.Canvas.TypeToString() ?
|
||||
new CustomLayouts().CanvasFromJsonElement(layout.Info.GetRawText()).SensitivityRadius :
|
||||
new CustomLayouts().GridFromJsonElement(layout.Info.GetRawText()).SensitivityRadius;
|
||||
Assert.AreEqual($"{expected}", slider.Text);
|
||||
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void HighlightDistance_Save()
|
||||
{
|
||||
var layout = Layouts.CustomLayouts[0];
|
||||
var type = layout.Type;
|
||||
Session.Find<Element>(layout.Name).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SensitivitySlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
|
||||
var value = type == CustomLayout.Canvas.TypeToString() ?
|
||||
new CustomLayouts().CanvasFromJsonElement(layout.Info.GetRawText()).SensitivityRadius :
|
||||
new CustomLayouts().GridFromJsonElement(layout.Info.GetRawText()).SensitivityRadius;
|
||||
var expected = value + 1; // one step right
|
||||
|
||||
Assert.AreEqual($"{expected}", slider.Text);
|
||||
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// verify the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var actual = type == CustomLayout.Canvas.TypeToString() ?
|
||||
new CustomLayouts().CanvasFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == layout.Uuid).Info.GetRawText()).SensitivityRadius :
|
||||
new CustomLayouts().GridFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == layout.Uuid).Info.GetRawText()).SensitivityRadius;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void HighlightDistance_Cancel()
|
||||
{
|
||||
var layout = Layouts.CustomLayouts[0];
|
||||
var type = layout.Type;
|
||||
Session.Find<Element>(layout.Name).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SensitivitySlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
|
||||
var expected = type == CustomLayout.Canvas.TypeToString() ?
|
||||
new CustomLayouts().CanvasFromJsonElement(layout.Info.GetRawText()).SensitivityRadius :
|
||||
new CustomLayouts().GridFromJsonElement(layout.Info.GetRawText()).SensitivityRadius;
|
||||
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// verify the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var actual = type == CustomLayout.Canvas.TypeToString() ?
|
||||
new CustomLayouts().CanvasFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == layout.Uuid).Info.GetRawText()).SensitivityRadius :
|
||||
new CustomLayouts().GridFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == layout.Uuid).Info.GetRawText()).SensitivityRadius;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpaceAroundZones_Initialize()
|
||||
{
|
||||
foreach (var layout in Layouts.CustomLayouts)
|
||||
{
|
||||
if (layout.Type != CustomLayout.Grid.TypeToString())
|
||||
{
|
||||
// only for grid layouts
|
||||
continue;
|
||||
}
|
||||
|
||||
Session.Find<Element>(layout.Name).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
var toggle = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingToggle));
|
||||
Assert.IsNotNull(toggle);
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
|
||||
var spacingEnabled = new CustomLayouts().GridFromJsonElement(layout.Info.GetRawText()).ShowSpacing;
|
||||
Assert.AreEqual(spacingEnabled, slider.Enabled);
|
||||
Assert.AreEqual(spacingEnabled, toggle.Selected);
|
||||
|
||||
var expected = new CustomLayouts().GridFromJsonElement(layout.Info.GetRawText()).Spacing;
|
||||
Assert.AreEqual($"{expected}", slider.Text);
|
||||
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpaceAroundZones_Slider_Save()
|
||||
{
|
||||
var layout = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Grid.TypeToString() && new CustomLayouts().GridFromJsonElement(x.Info.GetRawText()).ShowSpacing);
|
||||
var expected = new CustomLayouts().GridFromJsonElement(layout.Info.GetRawText()).Spacing + 1; // one step right
|
||||
Session.Find<Element>(layout.Name).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
Assert.AreEqual($"{expected}", slider.Text);
|
||||
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// verify the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var actual = new CustomLayouts().GridFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == layout.Uuid).Info.GetRawText()).Spacing;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpaceAroundZones_Slider_Cancel()
|
||||
{
|
||||
var layout = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Grid.TypeToString() && new CustomLayouts().GridFromJsonElement(x.Info.GetRawText()).ShowSpacing);
|
||||
Session.Find<Element>(layout.Name).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
var expected = new CustomLayouts().GridFromJsonElement(layout.Info.GetRawText()).Spacing;
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// verify the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var actual = new CustomLayouts().GridFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == layout.Uuid).Info.GetRawText()).Spacing;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpaceAroundZones_Toggle_Save()
|
||||
{
|
||||
var layout = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Grid.TypeToString());
|
||||
var value = new CustomLayouts().GridFromJsonElement(layout.Info.GetRawText()).ShowSpacing;
|
||||
var expected = !value;
|
||||
Session.Find<Element>(layout.Name).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
var toggle = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingToggle));
|
||||
Assert.IsNotNull(toggle);
|
||||
toggle.Click();
|
||||
Assert.AreEqual(expected, toggle.Selected, "Toggle value not changed");
|
||||
Assert.AreEqual(expected, Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider))?.Enabled);
|
||||
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// verify the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var actual = new CustomLayouts().GridFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == layout.Uuid).Info.GetRawText()).ShowSpacing;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpaceAroundZones_Toggle_Cancel()
|
||||
{
|
||||
var layout = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Grid.TypeToString());
|
||||
var expected = new CustomLayouts().GridFromJsonElement(layout.Info.GetRawText()).ShowSpacing;
|
||||
Session.Find<Element>(layout.Name).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
var toggle = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingToggle));
|
||||
Assert.IsNotNull(toggle);
|
||||
toggle.Click();
|
||||
Assert.AreNotEqual(expected, toggle.Selected, "Toggle value not changed");
|
||||
Assert.AreNotEqual(expected, Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider))?.Enabled);
|
||||
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// verify the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var actual = new CustomLayouts().GridFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == layout.Uuid).Info.GetRawText()).ShowSpacing;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using static FancyZonesEditorCommon.Data.CustomLayouts;
|
||||
using static FancyZonesEditorCommon.Data.DefaultLayouts;
|
||||
using static FancyZonesEditorCommon.Data.EditorParameters;
|
||||
using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper;
|
||||
|
||||
namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class DefaultLayoutsTest : UITestBase
|
||||
{
|
||||
public DefaultLayoutsTest()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
{
|
||||
}
|
||||
|
||||
private static readonly string Vertical = MonitorConfigurationType.Vertical.TypeToString();
|
||||
private static readonly string Horizontal = MonitorConfigurationType.Horizontal.TypeToString();
|
||||
|
||||
private static readonly CustomLayoutListWrapper CustomLayouts = new CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Layout 0",
|
||||
Info = new CustomLayouts().ToJsonElement(new CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 1080,
|
||||
RefWidth = 1920,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CanvasInfoWrapper.CanvasZoneWrapper> { },
|
||||
}),
|
||||
},
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{E7807D0D-6223-4883-B15B-1F3883944C09}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Layout 1",
|
||||
Info = new CustomLayouts().ToJsonElement(new CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 1080,
|
||||
RefWidth = 1920,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CanvasInfoWrapper.CanvasZoneWrapper> { },
|
||||
}),
|
||||
},
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{F1A94F38-82B6-4876-A653-70D0E882DE2A}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Layout 2",
|
||||
Info = new CustomLayouts().ToJsonElement(new CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 1080,
|
||||
RefWidth = 1920,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CanvasInfoWrapper.CanvasZoneWrapper> { },
|
||||
}),
|
||||
},
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{F5FDBC04-0760-4776-9F05-96AAC4AE613F}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Layout 3",
|
||||
Info = new CustomLayouts().ToJsonElement(new CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 1080,
|
||||
RefWidth = 1920,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CanvasInfoWrapper.CanvasZoneWrapper> { },
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly DefaultLayoutsListWrapper Layouts = new DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayoutWrapper>
|
||||
{
|
||||
new DefaultLayoutWrapper
|
||||
{
|
||||
MonitorConfiguration = Horizontal,
|
||||
Layout = new DefaultLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = true,
|
||||
Spacing = 5,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
},
|
||||
new DefaultLayoutWrapper
|
||||
{
|
||||
MonitorConfiguration = Vertical,
|
||||
Layout = new DefaultLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Type = "custom",
|
||||
Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
ZoneCount = 0,
|
||||
ShowSpacing = false,
|
||||
Spacing = 0,
|
||||
SensitivityRadius = 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
var defaultLayouts = new DefaultLayouts();
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(Layouts));
|
||||
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 192,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-2",
|
||||
MonitorInstanceId = "instance-id-2",
|
||||
MonitorSerialNumber = "serial-number-2",
|
||||
MonitorNumber = 2,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 1920,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(CustomLayouts));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
LayoutTemplates.TemplateLayoutsListWrapper templateLayoutsListWrapper = new LayoutTemplates.TemplateLayoutsListWrapper
|
||||
{
|
||||
LayoutTemplates = new List<LayoutTemplates.TemplateLayoutWrapper>
|
||||
{
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Blank.TypeToString(),
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(templateLayoutsListWrapper));
|
||||
|
||||
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
|
||||
LayoutHotkeys.LayoutHotkeysWrapper layoutHotkeysWrapper = new LayoutHotkeys.LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(layoutHotkeysWrapper));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Initialize()
|
||||
{
|
||||
CheckTemplateLayouts(LayoutType.Grid, null);
|
||||
CheckCustomLayouts(string.Empty, CustomLayouts.CustomLayouts[0].Uuid);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Assign_Cancel()
|
||||
{
|
||||
// assign Focus as a default horizontal and vertical layout
|
||||
Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.Focus]).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
Session.Find<Button>(By.AccessibilityId(AccessibilityId.HorizontalDefaultButtonUnchecked)).Click();
|
||||
Session.Find<Button>(By.AccessibilityId(AccessibilityId.VerticalDefaultButtonUnchecked)).Click();
|
||||
|
||||
// cancel
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// check that default layouts weren't changed
|
||||
CheckTemplateLayouts(LayoutType.Grid, null);
|
||||
CheckCustomLayouts(string.Empty, CustomLayouts.CustomLayouts[0].Uuid);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Assign_Save()
|
||||
{
|
||||
// assign Focus as a default horizontal and vertical layout
|
||||
Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.Focus]).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
Session.Find<Button>(By.AccessibilityId(AccessibilityId.HorizontalDefaultButtonUnchecked)).Click();
|
||||
Session.Find<Button>(By.AccessibilityId(AccessibilityId.VerticalDefaultButtonUnchecked)).Click();
|
||||
|
||||
// save
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// check that default layout was changed
|
||||
CheckTemplateLayouts(LayoutType.Focus, LayoutType.Focus);
|
||||
CheckCustomLayouts(string.Empty, string.Empty);
|
||||
}
|
||||
|
||||
private void CheckTemplateLayouts(LayoutType? horizontalDefault, LayoutType? verticalDefault)
|
||||
{
|
||||
foreach (var (key, name) in TestConstants.TemplateLayoutNames)
|
||||
{
|
||||
if (key == LayoutType.Blank)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Session.Find<Element>(name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
bool isCheckedHorizontal = key == horizontalDefault;
|
||||
bool isCheckedVertical = key == verticalDefault;
|
||||
|
||||
Button? horizontalDefaultButton;
|
||||
Button? verticalDefaultButton;
|
||||
|
||||
if (isCheckedHorizontal)
|
||||
{
|
||||
horizontalDefaultButton = Session.Find<Button>(By.AccessibilityId(AccessibilityId.HorizontalDefaultButtonChecked));
|
||||
}
|
||||
else
|
||||
{
|
||||
horizontalDefaultButton = Session.Find<Button>(By.AccessibilityId(AccessibilityId.HorizontalDefaultButtonUnchecked));
|
||||
}
|
||||
|
||||
if (isCheckedVertical)
|
||||
{
|
||||
verticalDefaultButton = Session.Find<Button>(By.AccessibilityId(AccessibilityId.VerticalDefaultButtonChecked));
|
||||
}
|
||||
else
|
||||
{
|
||||
verticalDefaultButton = Session.Find<Button>(By.AccessibilityId(AccessibilityId.VerticalDefaultButtonUnchecked));
|
||||
}
|
||||
|
||||
Assert.IsNotNull(horizontalDefaultButton, "Incorrect horizontal default layout set at " + name);
|
||||
Assert.IsNotNull(verticalDefaultButton, "Incorrect vertical default layout set at " + name);
|
||||
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckCustomLayouts(string horizontalDefaultLayoutUuid, string verticalDefaultLayoutUuid)
|
||||
{
|
||||
foreach (var layout in CustomLayouts.CustomLayouts)
|
||||
{
|
||||
Session.Find<Element>(layout.Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
bool isCheckedHorizontal = layout.Uuid == horizontalDefaultLayoutUuid;
|
||||
bool isCheckedVertical = layout.Uuid == verticalDefaultLayoutUuid;
|
||||
Button? horizontalDefaultButton;
|
||||
Button? verticalDefaultButton;
|
||||
|
||||
if (isCheckedHorizontal)
|
||||
{
|
||||
horizontalDefaultButton = Session.Find<Button>(By.AccessibilityId(AccessibilityId.HorizontalDefaultButtonChecked));
|
||||
}
|
||||
else
|
||||
{
|
||||
horizontalDefaultButton = Session.Find<Button>(By.AccessibilityId(AccessibilityId.HorizontalDefaultButtonUnchecked));
|
||||
}
|
||||
|
||||
if (isCheckedVertical)
|
||||
{
|
||||
verticalDefaultButton = Session.Find<Button>(By.AccessibilityId(AccessibilityId.VerticalDefaultButtonChecked));
|
||||
}
|
||||
else
|
||||
{
|
||||
verticalDefaultButton = Session.Find<Button>(By.AccessibilityId(AccessibilityId.VerticalDefaultButtonUnchecked));
|
||||
}
|
||||
|
||||
Assert.IsNotNull(horizontalDefaultButton, "Incorrect horizontal custom layout set at " + layout.Name);
|
||||
Assert.IsNotNull(verticalDefaultButton, "Incorrect vertical custom layout set at " + layout.Name);
|
||||
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
// 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.Collections.Generic;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using ModernWpf.Controls;
|
||||
using OpenQA.Selenium;
|
||||
using static FancyZonesEditorCommon.Data.CustomLayouts;
|
||||
using static FancyZonesEditorCommon.Data.DefaultLayouts;
|
||||
using static FancyZonesEditorCommon.Data.EditorParameters;
|
||||
using static FancyZonesEditorCommon.Data.LayoutHotkeys;
|
||||
using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper;
|
||||
|
||||
namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class DeleteLayoutTests : UITestBase
|
||||
{
|
||||
public DeleteLayoutTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
{
|
||||
}
|
||||
|
||||
private static readonly CustomLayoutListWrapper CustomLayouts = new CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
Type = CustomLayout.Grid.TypeToString(),
|
||||
Name = "Custom layout 1",
|
||||
Info = new CustomLayouts().ToJsonElement(new CustomLayouts.GridInfoWrapper
|
||||
{
|
||||
Rows = 2,
|
||||
Columns = 3,
|
||||
RowsPercentage = new List<int> { 2967, 7033 },
|
||||
ColumnsPercentage = new List<int> { 2410, 6040, 1550 },
|
||||
CellChildMap = new int[][] { [0, 1, 1], [0, 2, 3] },
|
||||
SensitivityRadius = 30,
|
||||
Spacing = 26,
|
||||
ShowSpacing = false,
|
||||
}),
|
||||
},
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{E7807D0D-6223-4883-B15B-1F3883944C09}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Custom layout 2",
|
||||
Info = new CustomLayouts().ToJsonElement(new CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 952,
|
||||
RefWidth = 1500,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CanvasInfoWrapper.CanvasZoneWrapper>
|
||||
{
|
||||
new CanvasInfoWrapper.CanvasZoneWrapper
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 900,
|
||||
Height = 522,
|
||||
},
|
||||
new CanvasInfoWrapper.CanvasZoneWrapper
|
||||
{
|
||||
X = 900,
|
||||
Y = 0,
|
||||
Width = 600,
|
||||
Height = 750,
|
||||
},
|
||||
new CanvasInfoWrapper.CanvasZoneWrapper
|
||||
{
|
||||
X = 0,
|
||||
Y = 522,
|
||||
Width = 1500,
|
||||
Height = 430,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly DefaultLayouts.DefaultLayoutsListWrapper DefaultLayoutsList = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper>
|
||||
{
|
||||
new DefaultLayoutWrapper
|
||||
{
|
||||
MonitorConfiguration = MonitorConfigurationType.Horizontal.TypeToString(),
|
||||
Layout = new DefaultLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Custom.TypeToString(),
|
||||
Uuid = CustomLayouts.CustomLayouts[1].Uuid,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly LayoutHotkeys.LayoutHotkeysWrapper Hotkeys = new LayoutHotkeys.LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper>
|
||||
{
|
||||
new LayoutHotkeyWrapper
|
||||
{
|
||||
LayoutId = CustomLayouts.CustomLayouts[1].Uuid,
|
||||
Key = 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly ParamsWrapper Parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 192,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(Parameters));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(CustomLayouts));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
LayoutTemplates.TemplateLayoutsListWrapper templateLayoutsListWrapper = new LayoutTemplates.TemplateLayoutsListWrapper
|
||||
{
|
||||
LayoutTemplates = new List<LayoutTemplates.TemplateLayoutWrapper>
|
||||
{
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Blank.TypeToString(),
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(templateLayoutsListWrapper));
|
||||
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(DefaultLayoutsList));
|
||||
|
||||
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(Hotkeys));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
Session.Find<Element>(CustomLayouts.CustomLayouts[0].Name).Click();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DeleteNotAppliedLayout()
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[1].Name;
|
||||
Session.Find<Element>(deletedLayout).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
Session.Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.DeleteLayoutButton)).Click();
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Enter);
|
||||
|
||||
// verify the layout is removed
|
||||
Assert.IsTrue(Session.FindAll<Element>(deletedLayout).Count == 0);
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(CustomLayouts.CustomLayouts.Count - 1, data.CustomLayouts.Count);
|
||||
Assert.IsFalse(data.CustomLayouts.Exists(x => x.Name == deletedLayout));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DeleteAppliedLayout()
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[0].Name;
|
||||
Session.Find<Element>(deletedLayout).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
Session.Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.DeleteLayoutButton)).Click();
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Enter);
|
||||
|
||||
// verify the layout is removed
|
||||
Assert.IsTrue(Session.FindAll<Element>(deletedLayout).Count == 0);
|
||||
|
||||
// verify the empty layout is selected
|
||||
Assert.IsTrue(Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.Blank])!.Selected);
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(CustomLayouts.CustomLayouts.Count - 1, data.CustomLayouts.Count);
|
||||
Assert.IsFalse(data.CustomLayouts.Exists(x => x.Name == deletedLayout));
|
||||
|
||||
var appliedLayouts = new AppliedLayouts();
|
||||
var appliedLayoutsData = appliedLayouts.Read(appliedLayouts.File);
|
||||
Assert.AreEqual(LayoutType.Blank.TypeToString(), appliedLayoutsData.AppliedLayouts.Find(x => x.Device.Monitor == Parameters.Monitors[0].Monitor).AppliedLayout.Type);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CancelDeletion()
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[1].Name;
|
||||
Session.Find<Element>(deletedLayout).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
Session.Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.DeleteLayoutButton)).Click();
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Tab, Keys.Enter);
|
||||
|
||||
// verify the layout is not removed
|
||||
Assert.IsNotNull(Session.Find<Element>(deletedLayout));
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(CustomLayouts.CustomLayouts.Count, data.CustomLayouts.Count);
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Name == deletedLayout));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DeleteFromContextMenu()
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[1].Name;
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, deletedLayout, FancyZonesEditorHelper.ElementName.Delete);
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Enter);
|
||||
|
||||
// verify the layout is removed
|
||||
Assert.IsTrue(Session.FindAll<Element>(deletedLayout).Count == 0);
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
Assert.AreEqual(CustomLayouts.CustomLayouts.Count - 1, data.CustomLayouts.Count);
|
||||
Assert.IsFalse(data.CustomLayouts.Exists(x => x.Name == deletedLayout));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DeleteDefaultLayout()
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[1].Name;
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, deletedLayout, FancyZonesEditorHelper.ElementName.Delete);
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Enter);
|
||||
|
||||
// verify the default layout is reset to the "default" default
|
||||
Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.PriorityGrid]).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
Assert.IsNotNull(Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.HorizontalDefaultButtonChecked)));
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// check the file
|
||||
var defaultLayouts = new DefaultLayouts();
|
||||
var data = defaultLayouts.Read(defaultLayouts.File);
|
||||
string configuration = MonitorConfigurationType.Horizontal.TypeToString();
|
||||
Assert.AreEqual(LayoutType.PriorityGrid.TypeToString(), data.DefaultLayouts.Find(x => x.MonitorConfiguration == configuration).Layout.Type);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DeleteLayoutWithHotkey()
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[1].Name;
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, deletedLayout, FancyZonesEditorHelper.ElementName.Delete);
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Enter);
|
||||
|
||||
// verify the hotkey is available
|
||||
Session.Find<Element>(CustomLayouts.CustomLayouts[0].Name).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
var hotkeyComboBox = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.HotkeyComboBox));
|
||||
Assert.IsNotNull(hotkeyComboBox);
|
||||
hotkeyComboBox.Click();
|
||||
|
||||
var popup = Session.Find<Element>(PowerToys.UITest.By.ClassName(ClassName.Popup));
|
||||
Assert.IsNotNull(popup);
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
popup.Find<Element>($"{i}");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Assert.Fail("Hotkey not found");
|
||||
}
|
||||
|
||||
Session.Find<Button>(ElementName.Cancel).DoubleClick();
|
||||
|
||||
// check the file
|
||||
var hotkeys = new LayoutHotkeys();
|
||||
var data = hotkeys.Read(hotkeys.File);
|
||||
int layoutHotkeyCount = 0;
|
||||
foreach (var layout in data.LayoutHotkeys)
|
||||
{
|
||||
if (layout.Key != -1)
|
||||
{
|
||||
layoutHotkeyCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.AreEqual(0, layoutHotkeyCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,653 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using ModernWpf.Controls;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using static FancyZonesEditorCommon.Data.CustomLayouts;
|
||||
using static FancyZonesEditorCommon.Data.EditorParameters;
|
||||
using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper;
|
||||
|
||||
namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class EditLayoutTests : UITestBase
|
||||
{
|
||||
public EditLayoutTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
{
|
||||
}
|
||||
|
||||
private static readonly CustomLayouts.CustomLayoutListWrapper Layouts = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
Type = CustomLayout.Grid.TypeToString(),
|
||||
Name = "Grid custom layout",
|
||||
Info = new CustomLayouts().ToJsonElement(new GridInfoWrapper
|
||||
{
|
||||
Rows = 2,
|
||||
Columns = 2,
|
||||
RowsPercentage = new List<int> { 5000, 5000 },
|
||||
ColumnsPercentage = new List<int> { 5000, 5000 },
|
||||
CellChildMap = new int[][] { [0, 1], [2, 3] },
|
||||
SensitivityRadius = 30,
|
||||
Spacing = 26,
|
||||
ShowSpacing = false,
|
||||
}),
|
||||
},
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{0EB9BF3E-010E-46D7-8681-1879D1E111E1}",
|
||||
Type = CustomLayout.Grid.TypeToString(),
|
||||
Name = "Grid-9",
|
||||
Info = new CustomLayouts().ToJsonElement(new GridInfoWrapper
|
||||
{
|
||||
Rows = 3,
|
||||
Columns = 3,
|
||||
RowsPercentage = new List<int> { 2333, 3333, 4334 },
|
||||
ColumnsPercentage = new List<int> { 2333, 3333, 4334 },
|
||||
CellChildMap = new int[][] { [0, 1, 2], [3, 4, 5], [6, 7, 8] },
|
||||
SensitivityRadius = 20,
|
||||
Spacing = 3,
|
||||
ShowSpacing = false,
|
||||
}),
|
||||
},
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{E7807D0D-6223-4883-B15B-1F3883944C09}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Canvas custom layout",
|
||||
Info = new CustomLayouts().ToJsonElement(new CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 1040,
|
||||
RefWidth = 1920,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CanvasInfoWrapper.CanvasZoneWrapper>
|
||||
{
|
||||
new CanvasInfoWrapper.CanvasZoneWrapper
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 500,
|
||||
Height = 250,
|
||||
},
|
||||
new CanvasInfoWrapper.CanvasZoneWrapper
|
||||
{
|
||||
X = 500,
|
||||
Y = 0,
|
||||
Width = 1420,
|
||||
Height = 500,
|
||||
},
|
||||
new CanvasInfoWrapper.CanvasZoneWrapper
|
||||
{
|
||||
X = 0,
|
||||
Y = 250,
|
||||
Width = 1920,
|
||||
Height = 500,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 192,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
LayoutTemplates.TemplateLayoutsListWrapper templateLayoutsListWrapper = new LayoutTemplates.TemplateLayoutsListWrapper
|
||||
{
|
||||
LayoutTemplates = new List<LayoutTemplates.TemplateLayoutWrapper>
|
||||
{
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Blank.TypeToString(),
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(templateLayoutsListWrapper));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(Layouts));
|
||||
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
|
||||
|
||||
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
|
||||
LayoutHotkeys.LayoutHotkeysWrapper layoutHotkeysWrapper = new LayoutHotkeys.LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(layoutHotkeysWrapper));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OpenEditMode()
|
||||
{
|
||||
Session.Find<Element>(Layouts.CustomLayouts[0].Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
Session.Find<Button>(By.AccessibilityId(AccessibilityId.EditZonesButton)).Click();
|
||||
Assert.IsNotNull(Session.Find<Element>(ElementName.GridLayoutEditor));
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OpenEditModeFromContextMenu()
|
||||
{
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, Layouts.CustomLayouts[0].Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
Assert.IsNotNull(Session.Find<Element>(ElementName.GridLayoutEditor));
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Canvas_AddZone_Save()
|
||||
{
|
||||
var canvas = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Canvas.TypeToString());
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, canvas.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
Session.Find<Button>(By.AccessibilityId(AccessibilityId.NewZoneButton)).Click();
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var expected = customLayouts.CanvasFromJsonElement(canvas.Info.ToString());
|
||||
var actual = customLayouts.CanvasFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == canvas.Uuid).Info.GetRawText());
|
||||
Assert.AreEqual(expected.Zones.Count + 1, actual.Zones.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Canvas_AddZone_Cancel()
|
||||
{
|
||||
var canvas = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Canvas.TypeToString());
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, canvas.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
Session.Find<Button>(By.AccessibilityId(AccessibilityId.NewZoneButton)).Click();
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var expected = customLayouts.CanvasFromJsonElement(canvas.Info.ToString());
|
||||
var actual = customLayouts.CanvasFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == canvas.Uuid).Info.GetRawText());
|
||||
Assert.AreEqual(expected.Zones.Count, actual.Zones.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Canvas_DeleteZone_Save()
|
||||
{
|
||||
var canvas = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Canvas.TypeToString());
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, canvas.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
FancyZonesEditorHelper.ClickDeleteZone(Session, 1);
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var expected = customLayouts.CanvasFromJsonElement(canvas.Info.ToString());
|
||||
var actual = customLayouts.CanvasFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == canvas.Uuid).Info.GetRawText());
|
||||
Assert.AreEqual(expected.Zones.Count - 1, actual.Zones.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Canvas_DeleteZone_Cancel()
|
||||
{
|
||||
var canvas = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Canvas.TypeToString());
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, canvas.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
FancyZonesEditorHelper.ClickDeleteZone(Session, 1);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var expected = customLayouts.CanvasFromJsonElement(canvas.Info.ToString());
|
||||
var actual = customLayouts.CanvasFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == canvas.Uuid).Info.GetRawText());
|
||||
Assert.AreEqual(expected.Zones.Count, actual.Zones.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Canvas_MoveZone_Save()
|
||||
{
|
||||
int zoneNumber = 1;
|
||||
int xOffset = 100;
|
||||
int yOffset = 100;
|
||||
var canvas = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Canvas.TypeToString());
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, canvas.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
|
||||
FancyZonesEditorHelper.GetZone(Session, zoneNumber, FancyZonesEditorHelper.ClassName.CanvasZone)?.Drag(xOffset, yOffset);
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var expected = customLayouts.CanvasFromJsonElement(canvas.Info.ToString());
|
||||
var actual = customLayouts.CanvasFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == canvas.Uuid).Info.GetRawText());
|
||||
|
||||
// changed zone, exact offset may vary depending on screen resolution
|
||||
Assert.IsTrue(expected.Zones[zoneNumber - 1].X < actual.Zones[zoneNumber - 1].X, $"X: {expected.Zones[zoneNumber - 1].X} > {actual.Zones[zoneNumber - 1].X}");
|
||||
Assert.IsTrue(expected.Zones[zoneNumber - 1].Y < actual.Zones[zoneNumber - 1].Y, $"Y: {expected.Zones[zoneNumber - 1].Y} > {actual.Zones[zoneNumber - 1].Y}");
|
||||
Assert.AreEqual(expected.Zones[zoneNumber - 1].Width, actual.Zones[zoneNumber - 1].Width);
|
||||
Assert.AreEqual(expected.Zones[zoneNumber - 1].Height, actual.Zones[zoneNumber - 1].Height);
|
||||
|
||||
// other zones
|
||||
for (int i = 0; i < expected.Zones.Count; i++)
|
||||
{
|
||||
if (i != zoneNumber - 1)
|
||||
{
|
||||
Assert.AreEqual(expected.Zones[i].X, actual.Zones[i].X);
|
||||
Assert.AreEqual(expected.Zones[i].Y, actual.Zones[i].Y);
|
||||
Assert.AreEqual(expected.Zones[i].Width, actual.Zones[i].Width);
|
||||
Assert.AreEqual(expected.Zones[i].Height, actual.Zones[i].Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Canvas_MoveZone_Cancel()
|
||||
{
|
||||
int zoneNumber = 1;
|
||||
var canvas = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Canvas.TypeToString());
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, canvas.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
|
||||
FancyZonesEditorHelper.GetZone(Session, zoneNumber, FancyZonesEditorHelper.ClassName.CanvasZone)?.Drag(100, 100);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var expected = customLayouts.CanvasFromJsonElement(canvas.Info.ToString());
|
||||
var actual = customLayouts.CanvasFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == canvas.Uuid).Info.GetRawText());
|
||||
for (int i = 0; i < expected.Zones.Count; i++)
|
||||
{
|
||||
Assert.AreEqual(expected.Zones[i].X, actual.Zones[i].X);
|
||||
Assert.AreEqual(expected.Zones[i].Y, actual.Zones[i].Y);
|
||||
Assert.AreEqual(expected.Zones[i].Width, actual.Zones[i].Width);
|
||||
Assert.AreEqual(expected.Zones[i].Height, actual.Zones[i].Height);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Canvas_ResizeZone_Save()
|
||||
{
|
||||
int zoneNumber = 1;
|
||||
int xOffset = 100;
|
||||
int yOffset = 100;
|
||||
var canvas = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Canvas.TypeToString());
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, canvas.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
|
||||
FancyZonesEditorHelper.GetZone(Session, zoneNumber, FancyZonesEditorHelper.ClassName.CanvasZone)?.Find<Element>(By.AccessibilityId(FancyZonesEditorHelper.AccessibilityId.TopRightCorner)).Drag(xOffset, yOffset);
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var expected = customLayouts.CanvasFromJsonElement(canvas.Info.ToString());
|
||||
var actual = customLayouts.CanvasFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == canvas.Uuid).Info.GetRawText());
|
||||
|
||||
// changed zone, exact offset may vary depending on screen resolution
|
||||
Assert.AreEqual(expected.Zones[zoneNumber - 1].X, actual.Zones[zoneNumber - 1].X);
|
||||
Assert.IsTrue(expected.Zones[zoneNumber - 1].Y < actual.Zones[zoneNumber - 1].Y, $"Y: {expected.Zones[zoneNumber - 1].Y} > {actual.Zones[zoneNumber - 1].Y}");
|
||||
Assert.IsTrue(expected.Zones[zoneNumber - 1].Width < actual.Zones[zoneNumber - 1].Width, $"Width: {expected.Zones[zoneNumber - 1].Width} < {actual.Zones[zoneNumber - 1].Width}");
|
||||
Assert.IsTrue(expected.Zones[zoneNumber - 1].Height > actual.Zones[zoneNumber - 1].Height, $"Height: {expected.Zones[zoneNumber - 1].Height} < {actual.Zones[zoneNumber - 1].Height}");
|
||||
|
||||
// other zones
|
||||
for (int i = 0; i < expected.Zones.Count; i++)
|
||||
{
|
||||
if (i != zoneNumber - 1)
|
||||
{
|
||||
Assert.AreEqual(expected.Zones[i].X, actual.Zones[i].X);
|
||||
Assert.AreEqual(expected.Zones[i].Y, actual.Zones[i].Y);
|
||||
Assert.AreEqual(expected.Zones[i].Width, actual.Zones[i].Width);
|
||||
Assert.AreEqual(expected.Zones[i].Height, actual.Zones[i].Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Canvas_ResizeZone_Cancel()
|
||||
{
|
||||
int zoneNumber = 1;
|
||||
int xOffset = 100;
|
||||
int yOffset = 100;
|
||||
var canvas = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Canvas.TypeToString());
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, canvas.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
|
||||
FancyZonesEditorHelper.GetZone(Session, zoneNumber, FancyZonesEditorHelper.ClassName.CanvasZone)?.Find<Element>(By.AccessibilityId(FancyZonesEditorHelper.AccessibilityId.TopRightCorner)).Drag(xOffset, yOffset);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var expected = customLayouts.CanvasFromJsonElement(canvas.Info.ToString());
|
||||
var actual = customLayouts.CanvasFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == canvas.Uuid).Info.GetRawText());
|
||||
|
||||
for (int i = 0; i < expected.Zones.Count; i++)
|
||||
{
|
||||
Assert.AreEqual(expected.Zones[i].X, actual.Zones[i].X);
|
||||
Assert.AreEqual(expected.Zones[i].Y, actual.Zones[i].Y);
|
||||
Assert.AreEqual(expected.Zones[i].Width, actual.Zones[i].Width);
|
||||
Assert.AreEqual(expected.Zones[i].Height, actual.Zones[i].Height);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Grid_SplitZone_Save()
|
||||
{
|
||||
int zoneNumber = 1;
|
||||
var grid = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Grid.TypeToString());
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, grid.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
|
||||
FancyZonesEditorHelper.GetZone(Session, zoneNumber, FancyZonesEditorHelper.ClassName.GridZone)?.Click(); // horizontal split in the middle of the zone
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var expected = customLayouts.GridFromJsonElement(grid.Info.ToString());
|
||||
var actual = customLayouts.GridFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == grid.Uuid).Info.GetRawText());
|
||||
|
||||
// new column added
|
||||
Assert.AreEqual(expected.Columns + 1, actual.Columns);
|
||||
Assert.AreEqual(expected.ColumnsPercentage[0], actual.ColumnsPercentage[0] + actual.ColumnsPercentage[1]);
|
||||
Assert.AreEqual(expected.ColumnsPercentage[1], actual.ColumnsPercentage[2]);
|
||||
|
||||
// rows are not changed
|
||||
Assert.AreEqual(expected.Rows, actual.Rows);
|
||||
for (int i = 0; i < expected.Rows; i++)
|
||||
{
|
||||
Assert.AreEqual(expected.RowsPercentage[i], actual.RowsPercentage[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Grid_SplitZone_Cancel()
|
||||
{
|
||||
int zoneNumber = 1;
|
||||
var grid = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Grid.TypeToString());
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, grid.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
|
||||
FancyZonesEditorHelper.GetZone(Session, zoneNumber, FancyZonesEditorHelper.ClassName.GridZone)?.Click(); // horizontal split in the middle of the zone
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var expected = customLayouts.GridFromJsonElement(grid.Info.ToString());
|
||||
var actual = customLayouts.GridFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == grid.Uuid).Info.GetRawText());
|
||||
|
||||
// columns are not changed
|
||||
Assert.AreEqual(expected.Columns, actual.Columns);
|
||||
for (int i = 0; i < expected.Columns; i++)
|
||||
{
|
||||
Assert.AreEqual(expected.ColumnsPercentage[i], actual.ColumnsPercentage[i]);
|
||||
}
|
||||
|
||||
// rows are not changed
|
||||
Assert.AreEqual(expected.Rows, actual.Rows);
|
||||
for (int i = 0; i < expected.Rows; i++)
|
||||
{
|
||||
Assert.AreEqual(expected.RowsPercentage[i], actual.RowsPercentage[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Grid_MergeZones_Save()
|
||||
{
|
||||
var grid = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Grid.TypeToString());
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, grid.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
|
||||
FancyZonesEditorHelper.MergeGridZones(Session, 1, 2);
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var expected = customLayouts.GridFromJsonElement(grid.Info.ToString());
|
||||
var actual = customLayouts.GridFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == grid.Uuid).Info.GetRawText());
|
||||
|
||||
// columns are not changed
|
||||
Assert.AreEqual(expected.Columns, actual.Columns);
|
||||
for (int i = 0; i < expected.Columns; i++)
|
||||
{
|
||||
Assert.AreEqual(expected.ColumnsPercentage[i], actual.ColumnsPercentage[i]);
|
||||
}
|
||||
|
||||
// rows are not changed
|
||||
Assert.AreEqual(expected.Rows, actual.Rows);
|
||||
for (int i = 0; i < expected.Rows; i++)
|
||||
{
|
||||
Assert.AreEqual(expected.RowsPercentage[i], actual.RowsPercentage[i]);
|
||||
}
|
||||
|
||||
// cells are updated to [0,0][1,2]
|
||||
Assert.IsTrue(actual.CellChildMap[0].SequenceEqual([0, 0]));
|
||||
Assert.IsTrue(actual.CellChildMap[1].SequenceEqual([1, 2]));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Grid_MergeZones_Cancel()
|
||||
{
|
||||
var grid = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Grid.TypeToString());
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, grid.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
|
||||
FancyZonesEditorHelper.MergeGridZones(Session, 1, 2);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var expected = customLayouts.GridFromJsonElement(grid.Info.ToString());
|
||||
var actual = customLayouts.GridFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == grid.Uuid).Info.GetRawText());
|
||||
|
||||
// columns are not changed
|
||||
Assert.AreEqual(expected.Columns, actual.Columns);
|
||||
for (int i = 0; i < expected.Columns; i++)
|
||||
{
|
||||
Assert.AreEqual(expected.ColumnsPercentage[i], actual.ColumnsPercentage[i]);
|
||||
}
|
||||
|
||||
// rows are not changed
|
||||
Assert.AreEqual(expected.Rows, actual.Rows);
|
||||
for (int i = 0; i < expected.Rows; i++)
|
||||
{
|
||||
Assert.AreEqual(expected.RowsPercentage[i], actual.RowsPercentage[i]);
|
||||
}
|
||||
|
||||
// cells are not changed
|
||||
for (int i = 0; i < expected.CellChildMap.Length; i++)
|
||||
{
|
||||
Assert.IsTrue(actual.CellChildMap[i].SequenceEqual(expected.CellChildMap[i]));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Grid_MoveSplitter_Save()
|
||||
{
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 192,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 780,
|
||||
WorkAreaWidth = 1240,
|
||||
MonitorHeight = 780,
|
||||
MonitorWidth = 1240,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
this.RestartScopeExe();
|
||||
|
||||
var grid = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Grid.TypeToString() && x.Name == "Grid-9");
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, grid.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
|
||||
FancyZonesEditorHelper.MoveSplitter(Session, 2, -50, 0);
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var expected = customLayouts.GridFromJsonElement(grid.Info.ToString());
|
||||
var actual = customLayouts.GridFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == grid.Uuid).Info.GetRawText());
|
||||
|
||||
// rows are not changed
|
||||
Assert.AreEqual(expected.Rows, actual.Rows);
|
||||
for (int i = 0; i < expected.Rows; i++)
|
||||
{
|
||||
Assert.AreEqual(expected.RowsPercentage[i], actual.RowsPercentage[i]);
|
||||
}
|
||||
|
||||
// Columns are changed
|
||||
Assert.AreEqual(expected.Columns, actual.Columns);
|
||||
Assert.IsTrue(expected.ColumnsPercentage[0] > actual.ColumnsPercentage[0], $"{expected.ColumnsPercentage[0]} > {actual.ColumnsPercentage[0]}");
|
||||
Assert.IsTrue(expected.ColumnsPercentage[1] < actual.ColumnsPercentage[1], $"{expected.ColumnsPercentage[1]} < {actual.ColumnsPercentage[1]}");
|
||||
Assert.AreEqual(expected.ColumnsPercentage[2], actual.ColumnsPercentage[2], $"{expected.ColumnsPercentage[2]} == {actual.ColumnsPercentage[2]}");
|
||||
|
||||
// cells are not changed
|
||||
for (int i = 0; i < expected.CellChildMap.Length; i++)
|
||||
{
|
||||
Assert.IsTrue(actual.CellChildMap[i].SequenceEqual(expected.CellChildMap[i]));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Grid_MoveSplitter_Cancel()
|
||||
{
|
||||
var grid = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Grid.TypeToString() && x.Name == "Grid-9");
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, grid.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
|
||||
FancyZonesEditorHelper.MoveSplitter(Session, 2, -100, 0);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// check the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
var data = customLayouts.Read(customLayouts.File);
|
||||
var expected = customLayouts.GridFromJsonElement(grid.Info.ToString());
|
||||
var actual = customLayouts.GridFromJsonElement(data.CustomLayouts.Find(x => x.Uuid == grid.Uuid).Info.GetRawText());
|
||||
|
||||
// columns are not changed
|
||||
Assert.AreEqual(expected.Columns, actual.Columns);
|
||||
for (int i = 0; i < expected.Columns; i++)
|
||||
{
|
||||
Assert.AreEqual(expected.ColumnsPercentage[i], actual.ColumnsPercentage[i]);
|
||||
}
|
||||
|
||||
// rows are not changed
|
||||
Assert.AreEqual(expected.Rows, actual.Rows);
|
||||
for (int i = 0; i < expected.Rows; i++)
|
||||
{
|
||||
Assert.AreEqual(expected.RowsPercentage[i], actual.RowsPercentage[i]);
|
||||
}
|
||||
|
||||
// cells are not changed
|
||||
for (int i = 0; i < expected.CellChildMap.Length; i++)
|
||||
{
|
||||
Assert.IsTrue(actual.CellChildMap[i].SequenceEqual(expected.CellChildMap[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Windows.Forms;
|
||||
using System.Xml.Linq;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UITests;
|
||||
using Microsoft.FancyZonesEditor.UITests.Utils;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using ModernWpf.Controls;
|
||||
using Windows.UI;
|
||||
using static FancyZonesEditorCommon.Data.EditorParameters;
|
||||
using static Microsoft.ApplicationInsights.MetricDimensionNames.TelemetryContext;
|
||||
using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper;
|
||||
|
||||
namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class FirstLunchTest : UITestBase
|
||||
{
|
||||
public FirstLunchTest()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
{
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 192, // 200% scaling
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
|
||||
|
||||
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
|
||||
LayoutHotkeys.LayoutHotkeysWrapper layoutHotkeysWrapper = new LayoutHotkeys.LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(layoutHotkeysWrapper));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
CustomLayouts.CustomLayoutListWrapper customLayoutListWrapper = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(customLayoutListWrapper));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
LayoutTemplates.TemplateLayoutsListWrapper templateLayoutsListWrapper = new LayoutTemplates.TemplateLayoutsListWrapper
|
||||
{
|
||||
LayoutTemplates = new List<LayoutTemplates.TemplateLayoutWrapper>
|
||||
{
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Blank.TypeToString(),
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(templateLayoutsListWrapper));
|
||||
|
||||
// verify editor opens without errors
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FirstLaunch()
|
||||
{
|
||||
Session.Find<Element>(By.AccessibilityId(FancyZonesEditorHelper.AccessibilityId.MainWindow)).Click();
|
||||
Assert.IsNotNull(Session.Find<Element>(By.AccessibilityId(FancyZonesEditorHelper.AccessibilityId.MainWindow)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
// 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.Globalization;
|
||||
using System.Linq;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using ModernWpf.Controls;
|
||||
using static FancyZonesEditorCommon.Data.CustomLayouts;
|
||||
using static FancyZonesEditorCommon.Data.EditorParameters;
|
||||
using static FancyZonesEditorCommon.Data.LayoutHotkeys;
|
||||
using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper;
|
||||
|
||||
namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class LayoutHotkeysTests : UITestBase
|
||||
{
|
||||
public LayoutHotkeysTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
{
|
||||
}
|
||||
|
||||
private static readonly CustomLayoutListWrapper CustomLayouts = new CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Layout 0",
|
||||
Info = new CustomLayouts().ToJsonElement(new CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 1080,
|
||||
RefWidth = 1920,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CanvasInfoWrapper.CanvasZoneWrapper> { },
|
||||
}),
|
||||
},
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{E7807D0D-6223-4883-B15B-1F3883944C09}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Layout 1",
|
||||
Info = new CustomLayouts().ToJsonElement(new CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 1080,
|
||||
RefWidth = 1920,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CanvasInfoWrapper.CanvasZoneWrapper> { },
|
||||
}),
|
||||
},
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{F1A94F38-82B6-4876-A653-70D0E882DE2A}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Layout 2",
|
||||
Info = new CustomLayouts().ToJsonElement(new CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 1080,
|
||||
RefWidth = 1920,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CanvasInfoWrapper.CanvasZoneWrapper> { },
|
||||
}),
|
||||
},
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{F5FDBC04-0760-4776-9F05-96AAC4AE613F}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Layout 3",
|
||||
Info = new CustomLayouts().ToJsonElement(new CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 1080,
|
||||
RefWidth = 1920,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CanvasInfoWrapper.CanvasZoneWrapper> { },
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly LayoutHotkeysWrapper Hotkeys = new LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeyWrapper>
|
||||
{
|
||||
new LayoutHotkeyWrapper
|
||||
{
|
||||
LayoutId = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
Key = 0,
|
||||
},
|
||||
new LayoutHotkeyWrapper
|
||||
{
|
||||
LayoutId = "{E7807D0D-6223-4883-B15B-1F3883944C09}",
|
||||
Key = 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 192,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(CustomLayouts));
|
||||
|
||||
var layoutHotkeys = new LayoutHotkeys();
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(Hotkeys));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
LayoutTemplates.TemplateLayoutsListWrapper templateLayoutsListWrapper = new LayoutTemplates.TemplateLayoutsListWrapper
|
||||
{
|
||||
LayoutTemplates = new List<LayoutTemplates.TemplateLayoutWrapper>
|
||||
{
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Blank.TypeToString(),
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(templateLayoutsListWrapper));
|
||||
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Initialize()
|
||||
{
|
||||
foreach (var layout in CustomLayouts.CustomLayouts)
|
||||
{
|
||||
Session.Find<Element>(layout.Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
var hotkeyComboBox = Session.Find<Element>(By.AccessibilityId(AccessibilityId.HotkeyComboBox));
|
||||
Assert.IsNotNull(hotkeyComboBox);
|
||||
|
||||
// verify the selected key
|
||||
string expected = "None";
|
||||
if (Hotkeys.LayoutHotkeys.Any(x => x.LayoutId == layout.Uuid))
|
||||
{
|
||||
expected = $"{Hotkeys.LayoutHotkeys.Find(x => x.LayoutId == layout.Uuid).Key}";
|
||||
}
|
||||
|
||||
Assert.AreEqual($"{expected}", hotkeyComboBox.Text);
|
||||
|
||||
// verify the available values
|
||||
hotkeyComboBox.Click();
|
||||
|
||||
var popup = Session.Find<Element>(By.ClassName(ClassName.Popup));
|
||||
Assert.IsNotNull(popup, "Hotkey combo box wasn't opened");
|
||||
|
||||
try
|
||||
{
|
||||
popup.Find<Element>(expected); // the current value should be available
|
||||
|
||||
// 0 and 1 are assigned, all others should be available
|
||||
for (int i = 2; i < 10; i++)
|
||||
{
|
||||
popup.Find<Element>($"{i}");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Assert.Fail("Hotkey is missed");
|
||||
}
|
||||
|
||||
Session.Find<Button>(ElementName.Cancel).DoubleClick();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Assign_Save()
|
||||
{
|
||||
var layout = CustomLayouts.CustomLayouts[2]; // a layout without assigned hotkey
|
||||
Session.Find<Element>(layout.Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
// assign hotkey
|
||||
const string key = "3";
|
||||
var hotkeyComboBox = Session.Find<Element>(By.AccessibilityId(AccessibilityId.HotkeyComboBox));
|
||||
Assert.IsNotNull(hotkeyComboBox);
|
||||
hotkeyComboBox.Click();
|
||||
|
||||
var popup = Session.Find<Element>(By.ClassName(ClassName.Popup));
|
||||
Assert.IsNotNull(popup);
|
||||
|
||||
popup.Find<Element>($"{key}").Click(); // assign a free hotkey
|
||||
Assert.AreEqual(key, hotkeyComboBox.Text);
|
||||
|
||||
// verify the file
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
var hotkeys = new LayoutHotkeys();
|
||||
var actualData = hotkeys.Read(hotkeys.File);
|
||||
Assert.IsTrue(actualData.LayoutHotkeys.Contains(new LayoutHotkeyWrapper { Key = int.Parse(key, CultureInfo.InvariantCulture), LayoutId = layout.Uuid }));
|
||||
|
||||
// verify the availability
|
||||
Session.Find<Element>(CustomLayouts.CustomLayouts[3].Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
hotkeyComboBox = Session.Find<Element>(By.AccessibilityId(AccessibilityId.HotkeyComboBox));
|
||||
Assert.IsNotNull(hotkeyComboBox);
|
||||
hotkeyComboBox.Click();
|
||||
|
||||
popup = Session.Find<Element>(By.ClassName(ClassName.Popup));
|
||||
Assert.IsNotNull(popup);
|
||||
try
|
||||
{
|
||||
popup.Find<Element>($"{key}"); // verify the key is not available
|
||||
Assert.Fail($"{key} The assigned key is still available for other layouts.");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// key not found as expected
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Assign_Cancel()
|
||||
{
|
||||
var layout = CustomLayouts.CustomLayouts[2]; // a layout without assigned hotkey
|
||||
Session.Find<Element>(layout.Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
// assign a hotkey
|
||||
const string key = "3";
|
||||
var hotkeyComboBox = Session.Find<Element>(By.AccessibilityId(AccessibilityId.HotkeyComboBox));
|
||||
Assert.IsNotNull(hotkeyComboBox);
|
||||
hotkeyComboBox.Click();
|
||||
var popup = Session.Find<Element>(By.ClassName(ClassName.Popup));
|
||||
Assert.IsNotNull(popup);
|
||||
popup.Find<Element>($"{key}").Click();
|
||||
Assert.AreEqual(key, hotkeyComboBox.Text);
|
||||
|
||||
// verify the file
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
var hotkeys = new LayoutHotkeys();
|
||||
var actualData = hotkeys.Read(hotkeys.File);
|
||||
Assert.AreEqual(Hotkeys.ToString(), actualData.ToString());
|
||||
|
||||
// verify the availability
|
||||
Session.Find<Element>(CustomLayouts.CustomLayouts[3].Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
hotkeyComboBox = Session.Find<Element>(By.AccessibilityId(AccessibilityId.HotkeyComboBox));
|
||||
Assert.IsNotNull(hotkeyComboBox);
|
||||
hotkeyComboBox.Click();
|
||||
popup = Session.Find<Element>(By.ClassName(ClassName.Popup));
|
||||
Assert.IsNotNull(popup);
|
||||
try
|
||||
{
|
||||
popup.Find<Element>($"{key}"); // verify the key is available
|
||||
}
|
||||
catch
|
||||
{
|
||||
Assert.Fail("The key is not available for other layouts.");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Assign_AllPossibleValues()
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
string layoutName = $"Layout {i}";
|
||||
Session.Find<Element>(layoutName).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
var hotkeyComboBox = Session.Find<Element>(By.AccessibilityId(AccessibilityId.HotkeyComboBox));
|
||||
Assert.IsNotNull(hotkeyComboBox);
|
||||
hotkeyComboBox.Click();
|
||||
var popup = Session.Find<Element>(By.ClassName(ClassName.Popup));
|
||||
Assert.IsNotNull(popup);
|
||||
popup.Find<Element>($"{i}").Click();
|
||||
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
}
|
||||
|
||||
// check there nothing except None
|
||||
{
|
||||
int layout = 3;
|
||||
string layoutName = $"Layout {layout}";
|
||||
Session.Find<Element>(layoutName).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
var hotkeyComboBox = Session.Find<Element>(By.AccessibilityId(AccessibilityId.HotkeyComboBox));
|
||||
Assert.IsNotNull(hotkeyComboBox);
|
||||
hotkeyComboBox.Click();
|
||||
var popup = Session.Find<Element>(By.ClassName(ClassName.Popup));
|
||||
Assert.IsNotNull(popup);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
popup.Find<Element>($"{i}");
|
||||
Assert.Fail("The assigned key is still available for other layouts.");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
popup.Find<Element>($"None").Click();
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Reset_Save()
|
||||
{
|
||||
var layout = CustomLayouts.CustomLayouts[0]; // a layout with assigned hotkey
|
||||
int assignedKey = Hotkeys.LayoutHotkeys.Find(x => x.LayoutId == layout.Uuid).Key;
|
||||
Session.Find<Element>(layout.Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
const string None = "None";
|
||||
|
||||
// reset the hotkey
|
||||
var hotkeyComboBox = Session.Find<Element>(By.AccessibilityId(AccessibilityId.HotkeyComboBox));
|
||||
Assert.IsNotNull(hotkeyComboBox);
|
||||
hotkeyComboBox.Click();
|
||||
var popup = Session.Find<Element>(By.ClassName(ClassName.Popup));
|
||||
Assert.IsNotNull(popup);
|
||||
popup.Find<Element>(None).Click();
|
||||
Assert.AreEqual(None, hotkeyComboBox.Text);
|
||||
|
||||
// verify the file
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
var hotkeys = new LayoutHotkeys();
|
||||
var actualData = hotkeys.Read(hotkeys.File);
|
||||
Assert.IsFalse(actualData.LayoutHotkeys.Contains(new LayoutHotkeyWrapper { Key = assignedKey, LayoutId = layout.Uuid }));
|
||||
|
||||
// verify the previously assigned key is available
|
||||
Session.Find<Element>(CustomLayouts.CustomLayouts[3].Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
hotkeyComboBox = Session.Find<Element>(By.AccessibilityId(AccessibilityId.HotkeyComboBox));
|
||||
Assert.IsNotNull(hotkeyComboBox);
|
||||
hotkeyComboBox.Click();
|
||||
popup = Session.Find<Element>(By.ClassName(ClassName.Popup));
|
||||
Assert.IsNotNull(popup);
|
||||
try
|
||||
{
|
||||
popup.Find<Element>($"{assignedKey}"); // verify the key is available
|
||||
}
|
||||
catch
|
||||
{
|
||||
Assert.Fail("The key is not available for other layouts.");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Reset_Cancel()
|
||||
{
|
||||
var layout = CustomLayouts.CustomLayouts[0]; // a layout with assigned hotkey
|
||||
int assignedKey = Hotkeys.LayoutHotkeys.Find(x => x.LayoutId == layout.Uuid).Key;
|
||||
Session.Find<Element>(layout.Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
const string None = "None";
|
||||
|
||||
// assign hotkey
|
||||
var hotkeyComboBox = Session.Find<Element>(By.AccessibilityId(AccessibilityId.HotkeyComboBox));
|
||||
Assert.IsNotNull(hotkeyComboBox);
|
||||
hotkeyComboBox.Click();
|
||||
var popup = Session.Find<Element>(By.ClassName(ClassName.Popup));
|
||||
Assert.IsNotNull(popup);
|
||||
popup.Find<Element>(None).Click(); // reset the hotkey
|
||||
Assert.AreEqual(None, hotkeyComboBox.Text);
|
||||
|
||||
// verify the file
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
var hotkeys = new LayoutHotkeys();
|
||||
var actualData = hotkeys.Read(hotkeys.File);
|
||||
Assert.IsTrue(actualData.LayoutHotkeys.Contains(new LayoutHotkeyWrapper { Key = assignedKey, LayoutId = layout.Uuid }));
|
||||
|
||||
// verify the previously assigned key is not available
|
||||
Session.Find<Element>(CustomLayouts.CustomLayouts[3].Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
hotkeyComboBox = Session.Find<Element>(By.AccessibilityId(AccessibilityId.HotkeyComboBox));
|
||||
Assert.IsNotNull(hotkeyComboBox);
|
||||
hotkeyComboBox.Click();
|
||||
popup = Session.Find<Element>(By.ClassName(ClassName.Popup));
|
||||
Assert.IsNotNull(popup);
|
||||
try
|
||||
{
|
||||
popup.Find<Element>($"{assignedKey}"); // verify the key is not available
|
||||
Assert.Fail("The key is still available for other layouts.");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// the key is not available as expected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// 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.Collections.Generic;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UITests;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Windows.UI;
|
||||
using static FancyZonesEditorCommon.Data.EditorParameters;
|
||||
|
||||
namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class NewFancyZonesEditorTest
|
||||
{
|
||||
public NewFancyZonesEditorTest()
|
||||
{
|
||||
// FancyZonesEditorHelper.InitFancyZonesLayout();
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class TestCaseFirstLaunch : UITestBase
|
||||
{
|
||||
public TestCaseFirstLaunch()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
{
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 192, // 200% scaling
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
// files not yet exist
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
|
||||
|
||||
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
|
||||
LayoutHotkeys.LayoutHotkeysWrapper layoutHotkeysWrapper = new LayoutHotkeys.LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(layoutHotkeysWrapper));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
CustomLayouts.CustomLayoutListWrapper customLayoutListWrapper = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(customLayoutListWrapper));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
LayoutTemplates.TemplateLayoutsListWrapper templateLayoutsListWrapper = new LayoutTemplates.TemplateLayoutsListWrapper
|
||||
{
|
||||
LayoutTemplates = new List<LayoutTemplates.TemplateLayoutWrapper>
|
||||
{
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Blank.TypeToString(),
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(templateLayoutsListWrapper));
|
||||
|
||||
// verify editor opens without errors
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FirstLaunch() // verify the session is initialized
|
||||
{
|
||||
Assert.IsNotNull(Session.Find<Element>("FancyZones Layout"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,26 +3,31 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UITests;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper;
|
||||
|
||||
namespace UITests_FancyZonesEditor
|
||||
namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class RunFancyZonesEditorTest
|
||||
public class RunFancyZonesEditorTest : UITestBase
|
||||
{
|
||||
private static FancyZonesEditorSession? _session;
|
||||
private static TestContext? _context;
|
||||
public RunFancyZonesEditorTest()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
{
|
||||
}
|
||||
|
||||
[ClassInitialize]
|
||||
public static void ClassInitialize(TestContext testContext)
|
||||
{
|
||||
_context = testContext;
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
|
||||
// prepare files to launch Editor without errors
|
||||
// prepare test editor parameters with 2 monitors before launching the editor
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
EditorParameters.ParamsWrapper parameters = new EditorParameters.ParamsWrapper
|
||||
{
|
||||
@@ -46,9 +51,25 @@ namespace UITests_FancyZonesEditor
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
new EditorParameters.NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-2",
|
||||
MonitorInstanceId = "instance-id-2",
|
||||
MonitorSerialNumber = "serial-number-2",
|
||||
MonitorNumber = 2,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 1920,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorSession.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
LayoutTemplates.TemplateLayoutsListWrapper templateLayoutsListWrapper = new LayoutTemplates.TemplateLayoutsListWrapper
|
||||
@@ -57,16 +78,16 @@ namespace UITests_FancyZonesEditor
|
||||
{
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.Empty],
|
||||
Type = LayoutType.Blank.TypeToString(),
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.Focus],
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.Rows],
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
@@ -74,7 +95,7 @@ namespace UITests_FancyZonesEditor
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.Columns],
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
@@ -82,7 +103,7 @@ namespace UITests_FancyZonesEditor
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.Grid],
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
@@ -90,7 +111,7 @@ namespace UITests_FancyZonesEditor
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.PriorityGrid],
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
@@ -98,81 +119,118 @@ namespace UITests_FancyZonesEditor
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorSession.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(templateLayoutsListWrapper));
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(templateLayoutsListWrapper));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
CustomLayouts.CustomLayoutListWrapper customLayoutListWrapper = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper> { },
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayouts.CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{E7807D0D-6223-4883-B15B-1F3883944C09}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Custom layout",
|
||||
Info = new CustomLayouts().ToJsonElement(new CustomLayouts.CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 952,
|
||||
RefWidth = 1500,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CustomLayouts.CanvasInfoWrapper.CanvasZoneWrapper> { },
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorSession.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(customLayoutListWrapper));
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(customLayoutListWrapper));
|
||||
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorSession.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
|
||||
|
||||
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
|
||||
LayoutHotkeys.LayoutHotkeysWrapper layoutHotkeysWrapper = new LayoutHotkeys.LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper> { },
|
||||
};
|
||||
FancyZonesEditorSession.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(layoutHotkeysWrapper));
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(layoutHotkeysWrapper));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorSession.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
}
|
||||
|
||||
[ClassCleanup]
|
||||
public static void ClassCleanup()
|
||||
{
|
||||
FancyZonesEditorSession.Files.Restore();
|
||||
_context = null;
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
_session = new FancyZonesEditorSession(_context!);
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
_session?.Close(_context!);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OpenEditorWindow() // verify the session is initialized
|
||||
{
|
||||
Assert.IsNotNull(_session?.Session);
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OpenNewLayoutDialog() // verify the new layout dialog is opened
|
||||
{
|
||||
_session?.Click_CreateNewLayout();
|
||||
Assert.IsNotNull(_session?.Session?.FindElementsByName("Choose layout type")); // check the pane header
|
||||
Session.Find<Button>(By.AccessibilityId(AccessibilityId.NewLayoutButton)).Click();
|
||||
Assert.IsNotNull(Session.Find<Element>("Choose layout type")); // check the pane header
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OpenEditLayoutDialog() // verify the edit layout dialog is opened
|
||||
{
|
||||
_session?.Click_EditLayout(TestConstants.TemplateLayoutNames[Constants.TemplateLayout.Grid]);
|
||||
Assert.IsNotNull(_session?.Session?.FindElementByAccessibilityId("EditLayoutDialogTitle")); // check the pane header
|
||||
Assert.IsNotNull(_session?.Session?.FindElementsByName("Edit 'Grid'")); // verify it's opened for the correct layout
|
||||
Session.Find<Button>(TestConstants.TemplateLayoutNames[LayoutType.Grid]).Click();
|
||||
Assert.IsNotNull(Session.Find<Element>(By.AccessibilityId(FancyZonesEditorHelper.AccessibilityId.DialogTitle))); // check the pane header
|
||||
Assert.IsNotNull(Session.Find<Element>($"Edit '{TestConstants.TemplateLayoutNames[LayoutType.Grid]}'")); // verify it's opened for the correct layout
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OpenEditLayoutDialog_ByContextMenu_TemplateLayout() // verify the edit layout dialog is opened
|
||||
{
|
||||
Session.Find<Button>(TestConstants.TemplateLayoutNames[LayoutType.Grid]).Click(true);
|
||||
var menu = Session.Find<Element>(By.ClassName(ClassName.ContextMenu));
|
||||
menu.Find<Element>(FancyZonesEditorHelper.ElementName.Edit).Click();
|
||||
|
||||
Assert.IsNotNull(Session.Find<Element>(By.AccessibilityId(FancyZonesEditorHelper.AccessibilityId.DialogTitle))); // check the pane header
|
||||
Assert.IsNotNull(Session.Find<Element>($"Edit '{TestConstants.TemplateLayoutNames[LayoutType.Grid]}'")); // verify it's opened for the correct layout
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OpenEditLayoutDialog_ByContextMenu_CustomLayout() // verify the edit layout dialog is opened
|
||||
{
|
||||
string layoutName = "Custom layout";
|
||||
Session.Find<Button>(layoutName).Click(true);
|
||||
var menu = Session.Find<Element>(By.ClassName(ClassName.ContextMenu));
|
||||
menu.Find<Element>(FancyZonesEditorHelper.ElementName.Edit).Click();
|
||||
|
||||
Assert.IsNotNull(Session.Find<Element>(By.AccessibilityId(FancyZonesEditorHelper.AccessibilityId.DialogTitle))); // check the pane header
|
||||
Assert.IsNotNull(Session.Find<Element>($"Edit '{layoutName}'")); // verify it's opened for the correct layout
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OpenContextMenu() // verify the context menu is opened
|
||||
{
|
||||
Assert.IsNotNull(_session?.OpenContextMenu(TestConstants.TemplateLayoutNames[Constants.TemplateLayout.Columns]));
|
||||
Session.Find<Button>(TestConstants.TemplateLayoutNames[LayoutType.Columns]).Click(true);
|
||||
Assert.IsNotNull(Session.Find<Element>(By.ClassName(ClassName.ContextMenu)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ClickMonitor()
|
||||
{
|
||||
Assert.IsNotNull(Session.Find<Element>("Monitor 1"));
|
||||
Assert.IsNotNull(Session.Find<Element>("Monitor 2"));
|
||||
|
||||
// verify that the monitor 1 is selected initially
|
||||
Assert.IsTrue(Session.Find<Element>("Monitor 1").Selected);
|
||||
Assert.IsFalse(Session.Find<Element>("Monitor 2").Selected);
|
||||
|
||||
Session.Find<Element>("Monitor 2").Click();
|
||||
|
||||
// verify that the monitor 2 is selected after click
|
||||
Assert.IsFalse(Session.Find<Element>("Monitor 1").Selected);
|
||||
Assert.IsTrue(Session.Find<Element>("Monitor 2").Selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
// 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.Collections.Generic;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UITests;
|
||||
using Microsoft.FancyZonesEditor.UITests.Utils;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using Windows.UI;
|
||||
using static FancyZonesEditorCommon.Data.AppliedLayouts;
|
||||
using static FancyZonesEditorCommon.Data.DefaultLayouts;
|
||||
using static FancyZonesEditorCommon.Data.EditorParameters;
|
||||
using static FancyZonesEditorCommon.Data.LayoutTemplates;
|
||||
using static Microsoft.ApplicationInsights.MetricDimensionNames.TelemetryContext;
|
||||
using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper;
|
||||
|
||||
namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class TemplateLayoutsTests : UITestBase
|
||||
{
|
||||
private static readonly TemplateLayoutsListWrapper Layouts = new TemplateLayoutsListWrapper
|
||||
{
|
||||
LayoutTemplates = new List<TemplateLayoutWrapper>
|
||||
{
|
||||
new TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Blank.TypeToString(),
|
||||
},
|
||||
new TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
new TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
new TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
new TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
public TemplateLayoutsTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
{
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 192,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(Layouts));
|
||||
|
||||
// Default layouts should match templates
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
DefaultLayoutsListWrapper defaultLayoutsList = new DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayoutWrapper>
|
||||
{
|
||||
new DefaultLayoutWrapper
|
||||
{
|
||||
MonitorConfiguration = MonitorConfigurationType.Vertical.ToString(),
|
||||
Layout = new DefaultLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
},
|
||||
new DefaultLayoutWrapper
|
||||
{
|
||||
MonitorConfiguration = MonitorConfigurationType.Horizontal.ToString(),
|
||||
Layout = new DefaultLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsList));
|
||||
|
||||
// Make sure applied layouts don't replate template settings
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayoutsListWrapper appliedLayoutsList = new AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayoutWrapper>
|
||||
{
|
||||
new AppliedLayoutWrapper
|
||||
{
|
||||
Device = new AppliedLayoutWrapper.DeviceIdWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstance = "instance-id-1",
|
||||
MonitorNumber = 1,
|
||||
SerialNumber = "serial-number-1",
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
},
|
||||
AppliedLayout = new AppliedLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Uuid = "{72409DFC-2B87-469B-AAC4-557273791C26}",
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsList));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
CustomLayouts.CustomLayoutListWrapper customLayoutListWrapper = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(customLayoutListWrapper));
|
||||
|
||||
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
|
||||
LayoutHotkeys.LayoutHotkeysWrapper layoutHotkeysWrapper = new LayoutHotkeys.LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(layoutHotkeysWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ZoneNumber_Cancel()
|
||||
{
|
||||
var type = LayoutType.Rows;
|
||||
var layout = Layouts.LayoutTemplates.Find(x => x.Type == type.TypeToString());
|
||||
var expected = layout.ZoneCount;
|
||||
Session.Find<Button>(TestConstants.TemplateLayoutNames[type]).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.TemplateZoneSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Left);
|
||||
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// verify the file
|
||||
var templateLayouts = new LayoutTemplates();
|
||||
var data = templateLayouts.Read(templateLayouts.File);
|
||||
var actual = data.LayoutTemplates.Find(x => x.Type == type.TypeToString()).ZoneCount;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void HighlightDistance_Initialize()
|
||||
{
|
||||
foreach (var (type, name) in TestConstants.TemplateLayoutNames)
|
||||
{
|
||||
if (type == LayoutType.Blank)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Session.Find<Button>(name).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SensitivitySlider));
|
||||
Assert.IsNotNull(slider);
|
||||
var expected = Layouts.LayoutTemplates.Find(x => x.Type == type.TypeToString()).SensitivityRadius;
|
||||
Assert.AreEqual($"{expected}", slider.Text);
|
||||
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void HighlightDistance_Save()
|
||||
{
|
||||
var type = LayoutType.Focus;
|
||||
var layout = Layouts.LayoutTemplates.Find(x => x.Type == type.TypeToString());
|
||||
var value = layout.SensitivityRadius;
|
||||
Session.Find<Button>(TestConstants.TemplateLayoutNames[type]).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SensitivitySlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
|
||||
var expected = value + 1; // one step right
|
||||
Assert.AreEqual($"{expected}", slider.Text);
|
||||
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// verify the file
|
||||
var templateLayouts = new LayoutTemplates();
|
||||
var data = templateLayouts.Read(templateLayouts.File);
|
||||
var actual = data.LayoutTemplates.Find(x => x.Type == type.TypeToString()).SensitivityRadius;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void HighlightDistance_Cancel()
|
||||
{
|
||||
var type = LayoutType.Focus;
|
||||
var layout = Layouts.LayoutTemplates.Find(x => x.Type == type.TypeToString());
|
||||
var expected = layout.SensitivityRadius;
|
||||
Session.Find<Button>(TestConstants.TemplateLayoutNames[type]).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SensitivitySlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// verify the file
|
||||
var templateLayouts = new LayoutTemplates();
|
||||
var data = templateLayouts.Read(templateLayouts.File);
|
||||
var actual = data.LayoutTemplates.Find(x => x.Type == type.TypeToString()).SensitivityRadius;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpaceAroundZones_Initialize()
|
||||
{
|
||||
foreach (var (type, name) in TestConstants.TemplateLayoutNames)
|
||||
{
|
||||
if (type == LayoutType.Blank || type == LayoutType.Focus)
|
||||
{
|
||||
// only for grid layouts
|
||||
continue;
|
||||
}
|
||||
|
||||
Session.Find<Button>(name).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
|
||||
var spacingEnabled = Layouts.LayoutTemplates.Find(x => x.Type == type.TypeToString()).ShowSpacing;
|
||||
Assert.AreEqual(spacingEnabled, slider.Enabled);
|
||||
|
||||
var expected = Layouts.LayoutTemplates.Find(x => x.Type == type.TypeToString()).Spacing;
|
||||
Assert.AreEqual($"{expected}", slider.Text);
|
||||
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpaceAroundZones_Slider_Save()
|
||||
{
|
||||
var type = LayoutType.PriorityGrid;
|
||||
var layout = Layouts.LayoutTemplates.Find(x => x.Type == type.TypeToString());
|
||||
var expected = layout.Spacing + 1;
|
||||
Session.Find<Button>(TestConstants.TemplateLayoutNames[type]).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
Assert.AreEqual($"{expected}", slider.Text);
|
||||
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// verify the file
|
||||
var templateLayouts = new LayoutTemplates();
|
||||
var data = templateLayouts.Read(templateLayouts.File);
|
||||
var actual = data.LayoutTemplates.Find(x => x.Type == type.TypeToString()).Spacing;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpaceAroundZones_Slider_Cancel()
|
||||
{
|
||||
var type = LayoutType.PriorityGrid;
|
||||
var layout = Layouts.LayoutTemplates.Find(x => x.Type == type.TypeToString());
|
||||
var expected = layout.Spacing;
|
||||
Session.Find<Button>(TestConstants.TemplateLayoutNames[type]).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
Assert.AreEqual($"{expected + 1}", slider.Text);
|
||||
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// verify the file
|
||||
var templateLayouts = new LayoutTemplates();
|
||||
var data = templateLayouts.Read(templateLayouts.File);
|
||||
var actual = data.LayoutTemplates.Find(x => x.Type == type.TypeToString()).Spacing;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpaceAroundZones_Toggle_Save()
|
||||
{
|
||||
var type = LayoutType.PriorityGrid;
|
||||
var layout = Layouts.LayoutTemplates.Find(x => x.Type == type.TypeToString());
|
||||
var expected = !layout.ShowSpacing;
|
||||
Session.Find<Button>(TestConstants.TemplateLayoutNames[type]).Click();
|
||||
|
||||
var toggle = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingToggle));
|
||||
Assert.IsNotNull(toggle);
|
||||
toggle.Click();
|
||||
Assert.AreEqual(expected, toggle.Selected);
|
||||
Assert.AreEqual(expected, Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider))?.Enabled);
|
||||
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// verify the file
|
||||
var templateLayouts = new LayoutTemplates();
|
||||
var data = templateLayouts.Read(templateLayouts.File);
|
||||
var actual = data.LayoutTemplates.Find(x => x.Type == type.TypeToString()).ShowSpacing;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpaceAroundZones_Toggle_Cancel()
|
||||
{
|
||||
var type = LayoutType.PriorityGrid;
|
||||
var layout = Layouts.LayoutTemplates.Find(x => x.Type == type.TypeToString());
|
||||
var expected = layout.ShowSpacing;
|
||||
Session.Find<Button>(TestConstants.TemplateLayoutNames[type]).Click();
|
||||
|
||||
var toggle = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingToggle));
|
||||
Assert.IsNotNull(toggle);
|
||||
toggle.Click();
|
||||
Assert.AreNotEqual(expected, toggle.Selected);
|
||||
Assert.AreNotEqual(expected, Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider))?.Enabled);
|
||||
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// verify the file
|
||||
var templateLayouts = new LayoutTemplates();
|
||||
var data = templateLayouts.Read(templateLayouts.File);
|
||||
var actual = data.LayoutTemplates.Find(x => x.Type == type.TypeToString()).ShowSpacing;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FancyZonesEditor.Models;
|
||||
|
||||
using static FancyZonesEditorCommon.Data.Constants;
|
||||
|
||||
@@ -10,14 +11,14 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
public static class TestConstants
|
||||
{
|
||||
public static readonly Dictionary<TemplateLayout, string> TemplateLayoutNames = new Dictionary<TemplateLayout, string>()
|
||||
public static readonly Dictionary<LayoutType, string> TemplateLayoutNames = new Dictionary<LayoutType, string>()
|
||||
{
|
||||
{ TemplateLayout.Empty, "No layout" },
|
||||
{ TemplateLayout.Focus, "Focus" },
|
||||
{ TemplateLayout.Rows, "Rows" },
|
||||
{ TemplateLayout.Columns, "Columns" },
|
||||
{ TemplateLayout.Grid, "Grid" },
|
||||
{ TemplateLayout.PriorityGrid, "PriorityGrid" },
|
||||
{ LayoutType.Blank, "No layout" },
|
||||
{ LayoutType.Focus, "Focus" },
|
||||
{ LayoutType.Rows, "Rows" },
|
||||
{ LayoutType.Columns, "Columns" },
|
||||
{ LayoutType.Grid, "Grid" },
|
||||
{ LayoutType.PriorityGrid, "Priority Grid" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,846 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Xml.Linq;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UITests;
|
||||
using Microsoft.FancyZonesEditor.UITests.Utils;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Windows.UI;
|
||||
using static FancyZonesEditorCommon.Data.EditorParameters;
|
||||
using static Microsoft.ApplicationInsights.MetricDimensionNames.TelemetryContext;
|
||||
using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper;
|
||||
|
||||
namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class UIInitializeTest : UITestBase
|
||||
{
|
||||
public UIInitializeTest()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
{
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EditorParams_VerifySelectedMonitor()
|
||||
{
|
||||
InitFileData();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = false,
|
||||
},
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-2",
|
||||
MonitorInstanceId = "instance-id-2",
|
||||
MonitorSerialNumber = "serial-number-2",
|
||||
MonitorNumber = 2,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 1920,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
this.RestartScopeExe();
|
||||
|
||||
Session.Find<Element>("Monitor 1").Click();
|
||||
Session.Find<Element>("Monitor 2").Click();
|
||||
Assert.IsFalse(Session.Find<Element>("Monitor 1").Selected);
|
||||
Assert.IsTrue(Session.Find<Element>("Monitor 2").Selected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EditorParams_VerifyMonitorScaling()
|
||||
{
|
||||
InitFileData();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 192, // 200% scaling
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
this.RestartScopeExe();
|
||||
|
||||
Session.Find<Element>("Monitor 1").Click();
|
||||
var monitor = Session.Find<Element>("Monitor 1");
|
||||
var scaling = monitor.Find<Element>(By.AccessibilityId("ScalingText"));
|
||||
Assert.AreEqual("200%", scaling.Text);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EditorParams_VerifyMonitorResolution()
|
||||
{
|
||||
InitFileData();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 192,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
this.RestartScopeExe();
|
||||
|
||||
Session.Find<Element>("Monitor 1").Click();
|
||||
var monitor = Session.Find<Element>("Monitor 1");
|
||||
var resolution = monitor.Find<Element>(By.AccessibilityId("ResolutionText"));
|
||||
Assert.AreEqual("1920 × 1080", resolution.Text);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EditorParams_SpanAcrossMonitors()
|
||||
{
|
||||
InitFileData();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = true,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 192,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
this.RestartScopeExe();
|
||||
|
||||
Session.Find<Element>("Monitor 1").Click();
|
||||
var monitor = Session.Find<Element>("Monitor 1");
|
||||
Assert.IsNotNull(monitor);
|
||||
Assert.IsTrue(monitor.Selected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AppliedLayouts_LayoutsApplied()
|
||||
{
|
||||
InitFileData();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-2",
|
||||
MonitorInstanceId = "instance-id-2",
|
||||
MonitorSerialNumber = "serial-number-2",
|
||||
MonitorNumber = 2,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 1920,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
CustomLayouts.CustomLayoutListWrapper customLayoutListWrapper = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayouts.CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Custom layout 1",
|
||||
Info = new CustomLayouts().ToJsonElement(new CustomLayouts.CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 1080,
|
||||
RefWidth = 1920,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CustomLayouts.CanvasInfoWrapper.CanvasZoneWrapper> { },
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(customLayoutListWrapper));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper>
|
||||
{
|
||||
new AppliedLayouts.AppliedLayoutWrapper
|
||||
{
|
||||
Device = new AppliedLayouts.AppliedLayoutWrapper.DeviceIdWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstance = "instance-id-1",
|
||||
SerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
},
|
||||
AppliedLayout = new AppliedLayouts.AppliedLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Uuid = "{00000000-0000-0000-0000-000000000000}",
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
ZoneCount = 1,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
},
|
||||
new AppliedLayouts.AppliedLayoutWrapper
|
||||
{
|
||||
Device = new AppliedLayouts.AppliedLayoutWrapper.DeviceIdWrapper
|
||||
{
|
||||
Monitor = "monitor-2",
|
||||
MonitorInstance = "instance-id-2",
|
||||
SerialNumber = "serial-number-2",
|
||||
MonitorNumber = 2,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
},
|
||||
AppliedLayout = new AppliedLayouts.AppliedLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Uuid = customLayoutListWrapper.CustomLayouts[0].Uuid,
|
||||
Type = LayoutType.Custom.TypeToString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
this.RestartScopeExe();
|
||||
|
||||
// check layout on monitor 1
|
||||
var layoutOnMonitor1 = Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.Columns]);
|
||||
Assert.IsNotNull(layoutOnMonitor1);
|
||||
Assert.IsTrue(layoutOnMonitor1.Selected);
|
||||
|
||||
// check layout on monitor 2
|
||||
Session.Find<Element>(By.AccessibilityId("Monitors")).Find<Element>("Monitor 2").Click();
|
||||
var layoutOnMonitor2 = Session.Find<Element>(customLayoutListWrapper.CustomLayouts[0].Name);
|
||||
Assert.IsNotNull(layoutOnMonitor2);
|
||||
Assert.IsTrue(layoutOnMonitor2.Selected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AppliedLayouts_CustomLayoutsApplied_LayoutIdNotFound()
|
||||
{
|
||||
InitFileData();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
CustomLayouts.CustomLayoutListWrapper customLayoutListWrapper = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayouts.CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Custom layout 1",
|
||||
Info = new CustomLayouts().ToJsonElement(new CustomLayouts.CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 1080,
|
||||
RefWidth = 1920,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CustomLayouts.CanvasInfoWrapper.CanvasZoneWrapper> { },
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(customLayoutListWrapper));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper>
|
||||
{
|
||||
new AppliedLayouts.AppliedLayoutWrapper
|
||||
{
|
||||
Device = new AppliedLayouts.AppliedLayoutWrapper.DeviceIdWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstance = "instance-id-1",
|
||||
SerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
},
|
||||
AppliedLayout = new AppliedLayouts.AppliedLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Uuid = "{00000000-0000-0000-0000-000000000000}",
|
||||
Type = LayoutType.Custom.TypeToString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
|
||||
var emptyLayout = Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.Blank]);
|
||||
Assert.IsNotNull(emptyLayout);
|
||||
Assert.IsTrue(emptyLayout.Selected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AppliedLayouts_NoLayoutsApplied_CustomDefaultLayout()
|
||||
{
|
||||
InitFileData();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
CustomLayouts.CustomLayoutListWrapper customLayoutListWrapper = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayouts.CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Custom layout 1",
|
||||
Info = new CustomLayouts().ToJsonElement(new CustomLayouts.CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 1080,
|
||||
RefWidth = 1920,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CustomLayouts.CanvasInfoWrapper.CanvasZoneWrapper> { },
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(customLayoutListWrapper));
|
||||
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.RestoreData();
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper>
|
||||
{
|
||||
new DefaultLayouts.DefaultLayoutWrapper
|
||||
{
|
||||
MonitorConfiguration = MonitorConfigurationType.Horizontal.TypeToString(),
|
||||
Layout = new DefaultLayouts.DefaultLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Custom.TypeToString(),
|
||||
Uuid = customLayoutListWrapper.CustomLayouts[0].Uuid,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
Session.Find<Element>(customLayoutListWrapper.CustomLayouts[0].Name).Click();
|
||||
var defaultLayout = Session.Find<Element>(customLayoutListWrapper.CustomLayouts[0].Name);
|
||||
Assert.IsNotNull(defaultLayout);
|
||||
Assert.IsTrue(defaultLayout.Selected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AppliedLayouts_NoLayoutsApplied_TemplateDefaultLayout()
|
||||
{
|
||||
InitFileData();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.RestoreData();
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper>
|
||||
{
|
||||
new DefaultLayouts.DefaultLayoutWrapper
|
||||
{
|
||||
MonitorConfiguration = MonitorConfigurationType.Horizontal.TypeToString(),
|
||||
Layout = new DefaultLayouts.DefaultLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 6,
|
||||
ShowSpacing = true,
|
||||
Spacing = 5,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
|
||||
Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.Grid]).Click();
|
||||
var defaultLayout = Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.Grid]);
|
||||
Assert.IsNotNull(defaultLayout);
|
||||
Assert.IsTrue(defaultLayout.Selected);
|
||||
|
||||
// check the number of zones and spacing
|
||||
Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.Grid]).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
Assert.AreEqual(defaultLayoutsListWrapper.DefaultLayouts[0].Layout.ZoneCount, int.Parse(Session.Find<Element>(By.AccessibilityId(AccessibilityId.TemplateZoneSlider))?.Text!, CultureInfo.InvariantCulture));
|
||||
Assert.AreEqual(defaultLayoutsListWrapper.DefaultLayouts[0].Layout.Spacing, int.Parse(Session.Find<Element>(By.AccessibilityId(AccessibilityId.SpacingSlider))?.Text!, CultureInfo.InvariantCulture));
|
||||
Assert.AreEqual(defaultLayoutsListWrapper.DefaultLayouts[0].Layout.ShowSpacing, Session.Find<Element>(By.AccessibilityId(AccessibilityId.SpacingSlider))?.Enabled);
|
||||
Assert.AreEqual(defaultLayoutsListWrapper.DefaultLayouts[0].Layout.ShowSpacing, Session.Find<Element>(By.AccessibilityId(AccessibilityId.SpacingToggle))?.Selected);
|
||||
Assert.AreEqual(defaultLayoutsListWrapper.DefaultLayouts[0].Layout.SensitivityRadius, int.Parse(Session.Find<Element>(By.AccessibilityId(AccessibilityId.SensitivitySlider))?.Text!, CultureInfo.InvariantCulture));
|
||||
Assert.IsNotNull(Session.Find<Element>(By.AccessibilityId(AccessibilityId.HorizontalDefaultButtonChecked)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AppliedLayouts_VerifyDisconnectedMonitorsLayoutsAreNotChanged()
|
||||
{
|
||||
InitFileData();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper>
|
||||
{
|
||||
new AppliedLayouts.AppliedLayoutWrapper
|
||||
{
|
||||
Device = new AppliedLayouts.AppliedLayoutWrapper.DeviceIdWrapper
|
||||
{
|
||||
Monitor = "monitor-2",
|
||||
MonitorInstance = "instance-id-2",
|
||||
SerialNumber = "serial-number-2",
|
||||
MonitorNumber = 2,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
},
|
||||
AppliedLayout = new AppliedLayouts.AppliedLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Uuid = "{00000000-0000-0000-0000-000000000000}",
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
ZoneCount = 4,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
},
|
||||
new AppliedLayouts.AppliedLayoutWrapper
|
||||
{
|
||||
Device = new AppliedLayouts.AppliedLayoutWrapper.DeviceIdWrapper
|
||||
{
|
||||
Monitor = "monitor-3",
|
||||
MonitorInstance = "instance-id-3",
|
||||
SerialNumber = "serial-number-3",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
},
|
||||
AppliedLayout = new AppliedLayouts.AppliedLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Uuid = "{00000000-0000-0000-0000-000000000000}",
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
ZoneCount = 1,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.Rows]).Click();
|
||||
|
||||
// check the file
|
||||
var data = appliedLayouts.Read(appliedLayouts.File);
|
||||
Assert.AreEqual(parameters.Monitors.Count + appliedLayoutsWrapper.AppliedLayouts.Count, data.AppliedLayouts.Count);
|
||||
|
||||
foreach (var monitor in parameters.Monitors)
|
||||
{
|
||||
Assert.IsNotNull(data.AppliedLayouts.Find(x => x.Device.Monitor == monitor.Monitor));
|
||||
}
|
||||
|
||||
foreach (var layout in appliedLayoutsWrapper.AppliedLayouts)
|
||||
{
|
||||
Assert.IsNotNull(data.AppliedLayouts.Find(x => x.Device.Monitor == layout.Device.Monitor));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AppliedLayouts_VerifyOtherVirtualDesktopsAreNotChanged()
|
||||
{
|
||||
InitFileData();
|
||||
string virtualDesktop1 = "{11111111-1111-1111-1111-111111111111}";
|
||||
string virtualDesktop2 = "{22222222-2222-2222-2222-222222222222}";
|
||||
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = virtualDesktop1,
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper>
|
||||
{
|
||||
new AppliedLayouts.AppliedLayoutWrapper
|
||||
{
|
||||
Device = new AppliedLayouts.AppliedLayoutWrapper.DeviceIdWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstance = "instance-id-1",
|
||||
SerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = virtualDesktop2,
|
||||
},
|
||||
AppliedLayout = new AppliedLayouts.AppliedLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Uuid = "{00000000-0000-0000-0000-000000000000}",
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
ZoneCount = 4,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.Rows]).Click();
|
||||
|
||||
// check the file
|
||||
var data = appliedLayouts.Read(appliedLayouts.File);
|
||||
Assert.AreEqual(parameters.Monitors.Count + appliedLayoutsWrapper.AppliedLayouts.Count, data.AppliedLayouts.Count);
|
||||
Assert.IsNotNull(data.AppliedLayouts.Find(x => x.Device.VirtualDesktop == virtualDesktop1));
|
||||
Assert.IsNotNull(data.AppliedLayouts.Find(x => x.Device.VirtualDesktop == virtualDesktop2));
|
||||
Assert.AreEqual(appliedLayoutsWrapper.AppliedLayouts[0].AppliedLayout.Type, data.AppliedLayouts.Find(x => x.Device.VirtualDesktop == virtualDesktop2).AppliedLayout.Type);
|
||||
Assert.AreEqual(LayoutType.Rows.TypeToString(), data.AppliedLayouts.Find(x => x.Device.VirtualDesktop == virtualDesktop1).AppliedLayout.Type);
|
||||
}
|
||||
|
||||
private void InitFileData()
|
||||
{
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<NativeMonitorDataWrapper>
|
||||
{
|
||||
new NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 192, // 200% scaling
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
|
||||
|
||||
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
|
||||
LayoutHotkeys.LayoutHotkeysWrapper layoutHotkeysWrapper = new LayoutHotkeys.LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(layoutHotkeysWrapper));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
CustomLayouts.CustomLayoutListWrapper customLayoutListWrapper = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(customLayoutListWrapper));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
LayoutTemplates.TemplateLayoutsListWrapper templateLayoutsListWrapper = new LayoutTemplates.TemplateLayoutsListWrapper
|
||||
{
|
||||
LayoutTemplates = new List<LayoutTemplates.TemplateLayoutWrapper>
|
||||
{
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Blank.TypeToString(),
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(templateLayoutsListWrapper));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FancyZonesEditorCommon\FancyZonesEditorCommon.csproj" />
|
||||
<ProjectReference Include="..\editor\FancyZonesEditor\FancyZonesEditor.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,288 @@
|
||||
// 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.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UITests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using ModernWpf.Controls;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
|
||||
namespace Microsoft.FancyZonesEditor.UnitTests.Utils
|
||||
{
|
||||
public class FancyZonesEditorHelper
|
||||
{
|
||||
private static FancyZonesEditorFiles? _files;
|
||||
|
||||
public static FancyZonesEditorFiles Files
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_files == null)
|
||||
{
|
||||
_files = new FancyZonesEditorFiles();
|
||||
}
|
||||
|
||||
return _files;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AccessibilityId
|
||||
{
|
||||
// main window
|
||||
public const string MainWindow = "MainWindow1";
|
||||
public const string Monitors = "Monitors";
|
||||
public const string NewLayoutButton = "NewLayoutButton";
|
||||
|
||||
// layout card
|
||||
public const string EditLayoutButton = "EditLayoutButton";
|
||||
|
||||
// edit layout window: common for template and custom layouts
|
||||
public const string DialogTitle = "EditLayoutDialogTitle";
|
||||
public const string SensitivitySlider = "SensitivityInput";
|
||||
public const string SpacingSlider = "Spacing";
|
||||
public const string SpacingToggle = "spaceAroundSetting";
|
||||
public const string HorizontalDefaultButtonUnchecked = "SetLayoutAsHorizontalDefaultButton";
|
||||
public const string VerticalDefaultButtonUnchecked = "SetLayoutAsVerticalDefaultButton";
|
||||
public const string HorizontalDefaultButtonChecked = "HorizontalDefaultLayoutButton";
|
||||
public const string VerticalDefaultButtonChecked = "VerticalDefaultLayoutButton";
|
||||
|
||||
// edit template layout window
|
||||
public const string CopyTemplate = "createFromTemplateLayoutButton";
|
||||
public const string TemplateZoneSlider = "TemplateZoneCount";
|
||||
|
||||
// edit custom layout window
|
||||
public const string DuplicateLayoutButton = "duplicateLayoutButton";
|
||||
public const string DeleteLayoutButton = "deleteLayoutButton";
|
||||
public const string KeySelectionComboBox = "quickKeySelectionComboBox";
|
||||
public const string EditZonesButton = "editZoneLayoutButton";
|
||||
public const string DeleteTextButton = "DeleteButton";
|
||||
public const string HotkeyComboBox = "quickKeySelectionComboBox";
|
||||
public const string NewZoneButton = "newZoneButton";
|
||||
public const string TopRightCorner = "NEResize";
|
||||
|
||||
// layout creation dialog
|
||||
public const string GridRadioButton = "GridLayoutRadioButton";
|
||||
public const string CanvasRadioButton = "CanvasLayoutRadioButton";
|
||||
|
||||
// confirmation dialog
|
||||
public const string PrimaryButton = "PrimaryButton";
|
||||
public const string SecondaryButton = "SecondaryButton";
|
||||
}
|
||||
|
||||
public static class ElementName
|
||||
{
|
||||
public const string Save = "Save";
|
||||
public const string Cancel = "Cancel";
|
||||
|
||||
// context menu
|
||||
public const string Edit = "Edit";
|
||||
public const string EditZones = "Edit zones";
|
||||
public const string Delete = "Delete";
|
||||
public const string Duplicate = "Duplicate";
|
||||
public const string CreateCustomLayout = "Create custom layout";
|
||||
|
||||
// canvas layout editor
|
||||
public const string CanvasEditorWindow = "Canvas layout editor";
|
||||
|
||||
// grid layout editor
|
||||
public const string GridLayoutEditor = "Grid layout editor";
|
||||
public const string MergeZonesButton = "Merge zones";
|
||||
}
|
||||
|
||||
public static class ClassName
|
||||
{
|
||||
public const string ContextMenu = "ContextMenu";
|
||||
public const string TextBox = "TextBox";
|
||||
public const string Popup = "Popup";
|
||||
|
||||
// layout editor
|
||||
public const string CanvasZone = "CanvasZone";
|
||||
public const string GridZone = "GridZone";
|
||||
public const string Button = "Button";
|
||||
public const string Thumb = "Thumb";
|
||||
}
|
||||
|
||||
public static void ClickContextMenuItem(Session session, string layoutName, string menuItem)
|
||||
{
|
||||
session.Find<Element>(layoutName).Click(true);
|
||||
session.Find<Element>(By.ClassName(ClassName.ContextMenu)).Find<Element>(menuItem).Click();
|
||||
}
|
||||
|
||||
public static Element? GetZone(Session session, int zoneNumber, string zoneClassName)
|
||||
{
|
||||
var zones = session.FindAll<Element>(By.ClassName(zoneClassName));
|
||||
foreach (var zone in zones)
|
||||
{
|
||||
try
|
||||
{
|
||||
zone.Find<Element>(zoneNumber.ToString(CultureInfo.InvariantCulture));
|
||||
Assert.IsNotNull(zone, "zone not found");
|
||||
return zone;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// required number not found in the zone
|
||||
}
|
||||
}
|
||||
|
||||
Assert.IsNotNull(zones, $"zoneClassName : {zoneClassName} not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void MergeGridZones(Session session, int zoneNumber1, int zoneNumber2)
|
||||
{
|
||||
var zone1 = GetZone(session, zoneNumber1, ClassName.GridZone);
|
||||
var zone2 = GetZone(session, zoneNumber2, ClassName.GridZone);
|
||||
Assert.IsNotNull(zone1, "first zone not found");
|
||||
Assert.IsNotNull(zone2, "second zone not found");
|
||||
if (zone1 == null || zone2 == null)
|
||||
{
|
||||
Assert.Fail("zone is null");
|
||||
return;
|
||||
}
|
||||
|
||||
zone1.Drag(zone2);
|
||||
|
||||
session.Find<Element>(ElementName.MergeZonesButton).Click();
|
||||
}
|
||||
|
||||
public static void MoveSplitter(Session session, int index, int xOffset, int yOffset)
|
||||
{
|
||||
var thumbs = session.FindAll<Element>(By.ClassName(ClassName.Thumb));
|
||||
if (thumbs.Count == 0 || index >= thumbs.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
thumbs[index].Drag(xOffset, yOffset);
|
||||
Console.WriteLine($"Moving splitter {index} by ({xOffset}, {yOffset})");
|
||||
}
|
||||
|
||||
public static void ClickDeleteZone(Session session, int zoneNumber)
|
||||
{
|
||||
var zone = FancyZonesEditorHelper.GetZone(session, zoneNumber, ClassName.CanvasZone);
|
||||
Assert.IsNotNull(zone);
|
||||
var button = zone.Find<Button>(By.ClassName(ClassName.Button));
|
||||
Assert.IsNotNull(button);
|
||||
button.Click();
|
||||
}
|
||||
|
||||
public static void InitFancyZonesLayout()
|
||||
{
|
||||
// prepare files to launch Editor without errors
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
EditorParameters.ParamsWrapper parameters = new EditorParameters.ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<EditorParameters.NativeMonitorDataWrapper>
|
||||
{
|
||||
new EditorParameters.NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
LayoutTemplates.TemplateLayoutsListWrapper templateLayoutsListWrapper = new LayoutTemplates.TemplateLayoutsListWrapper
|
||||
{
|
||||
LayoutTemplates = new List<LayoutTemplates.TemplateLayoutWrapper>
|
||||
{
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.Empty],
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.Focus],
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.Rows],
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.Columns],
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.Grid],
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.PriorityGrid],
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(templateLayoutsListWrapper));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
CustomLayouts.CustomLayoutListWrapper customLayoutListWrapper = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(customLayoutListWrapper));
|
||||
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
|
||||
|
||||
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
|
||||
LayoutHotkeys.LayoutHotkeysWrapper layoutHotkeysWrapper = new LayoutHotkeys.LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(layoutHotkeysWrapper));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
## This is for tracking UI-Tests migration progress for FancyZones Editor Module
|
||||
Refer to [release check list] (https://github.com/microsoft/PowerToys/blob/releaseChecklist/doc/releases/tests-checklist-template.md) for all manual tests.
|
||||
|
||||
### Existing Manual Test-cases run by previous PowerToys owner
|
||||
For existing manual test-cases, we will convert them to UI-Tests and run them in CI and Release pipeline
|
||||
|
||||
* Launch Host File Editor:
|
||||
- [ ] Open editor from the settings
|
||||
- [ ] Open editor with a shortcut
|
||||
- [ ] Create a new layout (grid and canvas)
|
||||
- [ ] Duplicate a template and a custom layout
|
||||
- [ ] Delete layout
|
||||
- [ ] Edit templates (number of zones, spacing, distance to highlight adjacent zones). Verify after reopening the editor that saved settings are kept the same.
|
||||
- [ ] Edit canvas layout: zones size and position, create or delete zones.
|
||||
- [ ] Edit grid layout: split, merge, resize zones.
|
||||
- [ ] Check Save and apply and Cancel buttons behavior after editing.
|
||||
- [ ] Assign a layout to each monitor.
|
||||
- [ ] Assign keys to quickly switch layouts (custom layouts only), Win + Ctrl + Alt + number.
|
||||
- [ ] Assign horizontal and vertical default layouts
|
||||
- [ ] Test duplicate layout focus
|
||||
- Select any layout X in 'Templates' or 'Custom' section by click left mouse button
|
||||
- Mouse right button click on any layout Y in 'Templates' or 'Custom' sections
|
||||
- Duplicate it by clicking 'Create custom layout' (Templates section) or 'Duplicate' in 'Custom' section
|
||||
- Expect the layout Y is duplicated
|
||||
|
||||
### Additional UI-Tests cases
|
||||
|
||||
- [ ] Add test data and start → verify data is correct (custom layouts, template layouts, defaults, shortcut keys)
|
||||
|
||||
- [ ] Create a new canvas - verify layout exists
|
||||
|
||||
- [ ] Create a new canvas - cancel - doesn’t exist
|
||||
|
||||
- [ ] Create a new grid - verify the layout exists
|
||||
|
||||
- [ ] Create a new grid - cancel - doesn’t exist
|
||||
|
||||
- [ ] Duplicate template by button (+ check default)
|
||||
|
||||
- [ ] Duplicate template by menu (+ check default)
|
||||
|
||||
- [ ] Duplicate custom by button (+ check shortcut key and default)
|
||||
|
||||
- [ ] Duplicate custom by menu (+ check shortcut key and default)
|
||||
|
||||
- [ ] Delete non-applied layout
|
||||
|
||||
- [ ] Delete applied layout
|
||||
|
||||
- [ ] Delete-cancel
|
||||
|
||||
- [ ] Delete from context menu
|
||||
|
||||
- [ ] Delete: hotkey released
|
||||
|
||||
- [ ] Delete: default layout reset to default-default
|
||||
|
||||
- [ ] Edit template and save
|
||||
|
||||
- [ ] Edit template and cancel
|
||||
|
||||
- [ ] Edit custom and save
|
||||
|
||||
- [ ] Edit custom and cancel
|
||||
|
||||
- [ ] Edit canvas: add zone
|
||||
|
||||
- [ ] Edit canvas: delete zone
|
||||
|
||||
- [ ] Edit canvas: move zone
|
||||
|
||||
- [ ] Edit canvas: resize zone
|
||||
|
||||
- [ ] Edit grid: split zone
|
||||
|
||||
- [ ] Edit grid: merge zones
|
||||
|
||||
- [ ] Edit grid: move splitter
|
||||
|
||||
- [ ] UI Init: assigned layouts selected
|
||||
|
||||
- [ ] UI Init: applied default - check params
|
||||
|
||||
- [ ] UI Init: assigned custom layout, but id not found
|
||||
|
||||
- [ ] Assign the same template but with different params to monitors
|
||||
|
||||
- [ ] Assign layout on each monitor
|
||||
|
||||
- [ ] Assign custom
|
||||
|
||||
- [ ] Assign template
|
||||
|
||||
- [ ] Assign shortcut key and save
|
||||
|
||||
- [ ] Assign shortcut key and cancel
|
||||
|
||||
- [ ] Reset shortcut key and save
|
||||
|
||||
- [ ] Reset shortcut key and cancel
|
||||
|
||||
- [ ] Set default layout + verify both prev and current after reopening
|
||||
|
||||
- [ ] applied-layouts.json keeps info about not connected devices - verify they’re present after closing
|
||||
|
||||
- [ ] applied-layouts.json keeps info about other virtual desktops
|
||||
|
||||
- [ ] first launch without custom-layouts.json, default-layouts.json, layout-hotkeys.json and layout-templates.json
|
||||
@@ -5,13 +5,15 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
using Common.UI;
|
||||
using FancyZoneEditor.Telemetry;
|
||||
using FancyZonesEditor.Utils;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
|
||||
namespace FancyZonesEditor
|
||||
{
|
||||
@@ -56,6 +58,8 @@ namespace FancyZonesEditor
|
||||
|
||||
public App()
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new FancyZonesEditorStartEvent() { TimeStamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() });
|
||||
|
||||
var languageTag = LanguageHelper.LoadLanguage();
|
||||
|
||||
if (!string.IsNullOrEmpty(languageTag))
|
||||
|
||||
@@ -335,6 +335,7 @@
|
||||
</ScrollViewer.DataContext>
|
||||
<Grid>
|
||||
<ui:GridView
|
||||
x:Name="Monitors"
|
||||
HorizontalAlignment="Center"
|
||||
IsItemClickEnabled="True"
|
||||
IsSelectionEnabled="True"
|
||||
|
||||
@@ -13,11 +13,12 @@ using System.Windows.Automation.Peers;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
|
||||
using Common.UI;
|
||||
using FancyZoneEditor.Telemetry;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditor.Utils;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using ModernWpf.Controls;
|
||||
|
||||
namespace FancyZonesEditor
|
||||
@@ -69,6 +70,8 @@ namespace FancyZonesEditor
|
||||
|
||||
// reinit considering work area rect
|
||||
_settings.InitModels();
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new FancyZonesEditorStartFinishEvent() { TimeStamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() });
|
||||
}
|
||||
|
||||
private void BringToFront()
|
||||
@@ -491,6 +494,8 @@ namespace FancyZonesEditor
|
||||
App.FancyZonesEditorIO.SerializeAppliedLayouts();
|
||||
App.FancyZonesEditorIO.SerializeCustomLayouts();
|
||||
App.FancyZonesEditorIO.SerializeDefaultLayouts();
|
||||
App.FancyZonesEditorIO.SerializeLayoutHotkeys();
|
||||
App.FancyZonesEditorIO.SerializeLayoutTemplates();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -328,12 +328,16 @@ namespace FancyZonesEditor.Models
|
||||
// Removes this Layout from the registry and the loaded CustomModels list
|
||||
public void Delete()
|
||||
{
|
||||
var customModels = MainWindowSettingsModel.CustomModels;
|
||||
if (_quickKey != -1)
|
||||
{
|
||||
MainWindowSettingsModel.LayoutHotkeys.FreeKey(QuickKey);
|
||||
foreach (var module in customModels)
|
||||
{
|
||||
module.FirePropertyChanged(nameof(QuickKeysAvailable));
|
||||
}
|
||||
}
|
||||
|
||||
var customModels = MainWindowSettingsModel.CustomModels;
|
||||
int i = customModels.IndexOf(this);
|
||||
if (i != -1)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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.
|
||||
|
||||
namespace FancyZonesEditor.Models
|
||||
{
|
||||
public static class LayoutTypeEnumExtension
|
||||
{
|
||||
private const string BlankJsonTag = "blank";
|
||||
private const string FocusJsonTag = "focus";
|
||||
private const string RowsJsonTag = "rows";
|
||||
private const string ColumnsJsonTag = "columns";
|
||||
private const string GridJsonTag = "grid";
|
||||
private const string PriorityGridJsonTag = "priority-grid";
|
||||
private const string CustomLayoutJsonTag = "custom";
|
||||
|
||||
public static string TypeToString(this LayoutType value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case LayoutType.Blank:
|
||||
return BlankJsonTag;
|
||||
case LayoutType.Focus:
|
||||
return FocusJsonTag;
|
||||
case LayoutType.Rows:
|
||||
return RowsJsonTag;
|
||||
case LayoutType.Columns:
|
||||
return ColumnsJsonTag;
|
||||
case LayoutType.Grid:
|
||||
return GridJsonTag;
|
||||
case LayoutType.PriorityGrid:
|
||||
return PriorityGridJsonTag;
|
||||
}
|
||||
|
||||
return CustomLayoutJsonTag;
|
||||
}
|
||||
|
||||
public static LayoutType TypeFromString(string value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case BlankJsonTag:
|
||||
return LayoutType.Blank;
|
||||
case FocusJsonTag:
|
||||
return LayoutType.Focus;
|
||||
case RowsJsonTag:
|
||||
return LayoutType.Rows;
|
||||
case ColumnsJsonTag:
|
||||
return LayoutType.Columns;
|
||||
case GridJsonTag:
|
||||
return LayoutType.Grid;
|
||||
case PriorityGridJsonTag:
|
||||
return LayoutType.PriorityGrid;
|
||||
}
|
||||
|
||||
return LayoutType.Custom;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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.
|
||||
|
||||
namespace FancyZonesEditor.Models
|
||||
{
|
||||
public static class MonitorConfigurationTypeEnumExtensions
|
||||
{
|
||||
private const string HorizontalJsonTag = "horizontal";
|
||||
private const string VerticalJsonTag = "vertical";
|
||||
|
||||
public static string TypeToString(this MonitorConfigurationType value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case MonitorConfigurationType.Horizontal:
|
||||
return HorizontalJsonTag;
|
||||
case MonitorConfigurationType.Vertical:
|
||||
return VerticalJsonTag;
|
||||
}
|
||||
|
||||
return HorizontalJsonTag;
|
||||
}
|
||||
|
||||
public static MonitorConfigurationType TypeFromString(string value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case HorizontalJsonTag:
|
||||
return MonitorConfigurationType.Horizontal;
|
||||
case VerticalJsonTag:
|
||||
return MonitorConfigurationType.Vertical;
|
||||
}
|
||||
|
||||
return MonitorConfigurationType.Horizontal;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.Diagnostics.Tracing;
|
||||
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace FancyZoneEditor.Telemetry;
|
||||
|
||||
[EventData]
|
||||
public class FancyZonesEditorStartEvent() : EventBase, IEvent
|
||||
{
|
||||
public long TimeStamp { get; set; }
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.Diagnostics.Tracing;
|
||||
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace FancyZoneEditor.Telemetry;
|
||||
|
||||
[EventData]
|
||||
public class FancyZonesEditorStartFinishEvent() : EventBase, IEvent
|
||||
{
|
||||
public long TimeStamp { get; set; }
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -258,14 +258,20 @@ private:
|
||||
for (DWORD i = 0; i < fileCount; i++)
|
||||
{
|
||||
IShellItem* shellItem;
|
||||
psiItemArray->GetItemAt(i, &shellItem);
|
||||
LPWSTR itemName;
|
||||
// Retrieves the entire file system path of the file from its shell item
|
||||
shellItem->GetDisplayName(SIGDN_FILESYSPATH, &itemName);
|
||||
CString fileName(itemName);
|
||||
fileName.Append(_T("\r\n"));
|
||||
// Write the file path into the input stream for image resizer
|
||||
writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR));
|
||||
HRESULT getItemAtResult = psiItemArray->GetItemAt(i, &shellItem);
|
||||
if (SUCCEEDED(getItemAtResult))
|
||||
{
|
||||
LPWSTR itemName;
|
||||
// Retrieves the entire file system path of the file from its shell item
|
||||
HRESULT getDisplayResult = shellItem->GetDisplayName(SIGDN_FILESYSPATH, &itemName);
|
||||
if (SUCCEEDED(getDisplayResult))
|
||||
{
|
||||
CString fileName(itemName);
|
||||
fileName.Append(_T("\r\n"));
|
||||
// Write the file path into the input stream for image resizer
|
||||
writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR));
|
||||
}
|
||||
}
|
||||
}
|
||||
writePipe.Close();
|
||||
}
|
||||
|
||||
@@ -392,8 +392,14 @@ HRESULT __stdcall CContextMenuHandler::GetState(IShellItemArray* psiItemArray, B
|
||||
PERCEIVED type;
|
||||
PERCEIVEDFLAG flag;
|
||||
IShellItem* shellItem;
|
||||
|
||||
//Check extension of first item in the list (the item which is right-clicked on)
|
||||
psiItemArray->GetItemAt(0, &shellItem);
|
||||
HRESULT getItemAtResult = psiItemArray->GetItemAt(0, &shellItem);
|
||||
if(!SUCCEEDED(getItemAtResult)) {
|
||||
// Avoid crashes in the following code.
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
LPTSTR pszPath;
|
||||
// Retrieves the entire file system path of the file from its shell item
|
||||
HRESULT getDisplayResult = shellItem->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user