mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-09 20:57:22 +02:00
Merge branch 'microsoft/main' into dev/seraphima/tests/29246-fancyzones-tests-initial-step
This commit is contained in:
@@ -394,15 +394,6 @@ void FancyZones::WindowCreated(HWND window) noexcept
|
||||
return;
|
||||
}
|
||||
|
||||
// Hotfix
|
||||
// Avoid automatically moving popup windows, as they can be just popup menus.
|
||||
bool isPopup = FancyZonesWindowUtils::IsPopupWindow(window);
|
||||
bool hasThickFrame = FancyZonesWindowUtils::HasThickFrame(window);
|
||||
if (isPopup && !hasThickFrame)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid already stamped (zoned) windows
|
||||
const bool isZoned = !FancyZonesWindowProperties::RetrieveZoneIndexProperty(window).empty();
|
||||
if (isZoned)
|
||||
@@ -1005,41 +996,42 @@ void FancyZones::RefreshLayouts() noexcept
|
||||
|
||||
bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept
|
||||
{
|
||||
auto window = GetForegroundWindow();
|
||||
if (!FancyZonesSettings::settings().overrideSnapHotkeys)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto window = GetForegroundWindow();
|
||||
if (!FancyZonesWindowProcessing::IsProcessable(window))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FancyZonesSettings::settings().overrideSnapHotkeys)
|
||||
HMONITOR monitor = WorkAreaKeyFromWindow(window);
|
||||
|
||||
auto workArea = m_workAreaConfiguration.GetWorkArea(monitor);
|
||||
if (!workArea)
|
||||
{
|
||||
HMONITOR monitor = WorkAreaKeyFromWindow(window);
|
||||
Logger::error(L"No work area for processing snap hotkey");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto workArea = m_workAreaConfiguration.GetWorkArea(monitor);
|
||||
if (!workArea)
|
||||
const auto& layout = workArea->GetLayout();
|
||||
if (!layout)
|
||||
{
|
||||
Logger::error(L"No layout for processing snap hotkey");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (layout->Zones().size() > 0)
|
||||
{
|
||||
if (vkCode == VK_UP || vkCode == VK_DOWN)
|
||||
{
|
||||
Logger::error(L"No work area for processing snap hotkey");
|
||||
return false;
|
||||
return FancyZonesSettings::settings().moveWindowsBasedOnPosition;
|
||||
}
|
||||
|
||||
const auto& layout = workArea->GetLayout();
|
||||
if (!layout)
|
||||
else
|
||||
{
|
||||
Logger::error(L"No layout for processing snap hotkey");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (layout->Zones().size() > 0)
|
||||
{
|
||||
if (vkCode == VK_UP || vkCode == VK_DOWN)
|
||||
{
|
||||
return FancyZonesSettings::settings().moveWindowsBasedOnPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
<ClInclude Include="ModuleConstants.h" />
|
||||
<ClInclude Include="MonitorUtils.h" />
|
||||
<ClInclude Include="WorkAreaConfiguration.h" />
|
||||
<ClInclude Include="NotificationUtil.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="Generated Files/resource.h" />
|
||||
<None Include="resource.base.h" />
|
||||
|
||||
@@ -153,9 +153,6 @@
|
||||
<ClInclude Include="HighlightedZones.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="NotificationUtil.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HighlightedZones.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -5,52 +5,69 @@
|
||||
#include <FancyZonesLib/VirtualDesktop.h>
|
||||
#include <FancyZonesLib/WindowUtils.h>
|
||||
|
||||
bool FancyZonesWindowProcessing::IsProcessable(HWND window) noexcept
|
||||
FancyZonesWindowProcessing::ProcessabilityType FancyZonesWindowProcessing::DefineWindowType(HWND window) noexcept
|
||||
{
|
||||
const bool isSplashScreen = FancyZonesWindowUtils::IsSplashScreen(window);
|
||||
if (isSplashScreen)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool windowMinimized = IsIconic(window);
|
||||
if (windowMinimized)
|
||||
{
|
||||
return false;
|
||||
return ProcessabilityType::Minimized;
|
||||
}
|
||||
|
||||
const bool standard = FancyZonesWindowUtils::IsStandardWindow(window);
|
||||
if (!standard)
|
||||
auto style = GetWindowLong(window, GWL_STYLE);
|
||||
auto exStyle = GetWindowLong(window, GWL_EXSTYLE);
|
||||
|
||||
if (!FancyZonesWindowUtils::HasStyle(style, WS_VISIBLE))
|
||||
{
|
||||
return false;
|
||||
return ProcessabilityType::NotVisible;
|
||||
}
|
||||
|
||||
// popup could be the window we don't want to snap: start menu, notification popup, tray window, etc.
|
||||
// also, popup could be the windows we want to snap disregarding the "allowSnapPopupWindows" setting, e.g. Telegram
|
||||
bool isPopup = FancyZonesWindowUtils::IsPopupWindow(window) && !FancyZonesWindowUtils::HasThickFrameAndMinimizeMaximizeButtons(window);
|
||||
if (isPopup && !FancyZonesSettings::settings().allowSnapPopupWindows)
|
||||
if (FancyZonesWindowUtils::HasStyle(exStyle, WS_EX_TOOLWINDOW))
|
||||
{
|
||||
return false;
|
||||
return ProcessabilityType::ToolWindow;
|
||||
}
|
||||
|
||||
if (!FancyZonesWindowUtils::IsRoot(window))
|
||||
{
|
||||
// child windows such as buttons, combo boxes, etc.
|
||||
return ProcessabilityType::NonRootWindow;
|
||||
}
|
||||
|
||||
bool isPopup = FancyZonesWindowUtils::HasStyle(style, WS_POPUP);
|
||||
bool hasThickFrame = FancyZonesWindowUtils::HasStyle(style, WS_THICKFRAME);
|
||||
bool hasCaption = FancyZonesWindowUtils::HasStyle(style, WS_CAPTION);
|
||||
bool hasMinimizeMaximizeButtons = FancyZonesWindowUtils::HasStyle(style, WS_MINIMIZEBOX) || FancyZonesWindowUtils::HasStyle(style, WS_MAXIMIZEBOX);
|
||||
if (isPopup && !(hasThickFrame && (hasCaption || hasMinimizeMaximizeButtons)))
|
||||
{
|
||||
// popup windows we want to snap: e.g. Calculator, Telegram
|
||||
// popup windows we don't want to snap: start menu, notification popup, tray window, etc.
|
||||
// WS_CAPTION, WS_MINIMIZEBOX, WS_MAXIMIZEBOX are used for filtering out menus,
|
||||
// e.g., in Edge "Running as admin" menu when creating a new PowerToys issue.
|
||||
return ProcessabilityType::NonProcessablePopupWindow;
|
||||
}
|
||||
|
||||
// allow child windows
|
||||
auto hasOwner = FancyZonesWindowUtils::HasVisibleOwner(window);
|
||||
if (hasOwner && !FancyZonesSettings::settings().allowSnapChildWindows)
|
||||
{
|
||||
return false;
|
||||
return ProcessabilityType::ChildWindow;
|
||||
}
|
||||
|
||||
if (FancyZonesWindowUtils::IsExcluded(window))
|
||||
{
|
||||
return false;
|
||||
return ProcessabilityType::Excluded;
|
||||
}
|
||||
|
||||
// Switch between virtual desktops results with posting same windows messages that also indicate
|
||||
// creation of new window. We need to check if window being processed is on currently active desktop.
|
||||
if (!VirtualDesktop::instance().IsWindowOnCurrentDesktop(window))
|
||||
{
|
||||
return false;
|
||||
return ProcessabilityType::NotCurrentVirtualDesktop;
|
||||
}
|
||||
|
||||
return true;
|
||||
return ProcessabilityType::Processable;
|
||||
}
|
||||
|
||||
bool FancyZonesWindowProcessing::IsProcessable(HWND window) noexcept
|
||||
{
|
||||
return DefineWindowType(window) == ProcessabilityType::Processable;
|
||||
}
|
||||
|
||||
@@ -2,5 +2,20 @@
|
||||
|
||||
namespace FancyZonesWindowProcessing
|
||||
{
|
||||
enum class ProcessabilityType
|
||||
{
|
||||
Processable = 0,
|
||||
SplashScreen,
|
||||
Minimized,
|
||||
ToolWindow,
|
||||
NotVisible,
|
||||
NonRootWindow,
|
||||
NonProcessablePopupWindow,
|
||||
ChildWindow,
|
||||
Excluded,
|
||||
NotCurrentVirtualDesktop
|
||||
};
|
||||
|
||||
ProcessabilityType DefineWindowType(HWND window) noexcept;
|
||||
bool IsProcessable(HWND window) noexcept;
|
||||
}
|
||||
@@ -360,7 +360,7 @@ namespace MonitorUtils
|
||||
if (GetMonitorInfo(monitor, &destMi))
|
||||
{
|
||||
RECT newPosition = FitOnScreen(placement.rcNormalPosition, originMi.rcWork, destMi.rcWork);
|
||||
FancyZonesWindowUtils::SizeWindowToRect(window, newPosition);
|
||||
FancyZonesWindowUtils::SizeWindowToRect(window, newPosition, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <common/notifications/notifications.h>
|
||||
#include <common/notifications/dont_show_again.h>
|
||||
#include <common/utils/resources.h>
|
||||
|
||||
namespace FancyZonesNotifications
|
||||
{
|
||||
// Non-Localizable strings
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const wchar_t FancyZonesRunAsAdminInfoPage[] = L"https://aka.ms/powertoysDetectedElevatedHelp";
|
||||
const wchar_t ToastNotificationButtonUrl[] = L"powertoys://cant_drag_elevated_disable/";
|
||||
}
|
||||
|
||||
inline void WarnIfElevationIsRequired()
|
||||
{
|
||||
using namespace notifications;
|
||||
using namespace NonLocalizable;
|
||||
|
||||
static bool warning_shown = false;
|
||||
if (!warning_shown && !is_toast_disabled(CantDragElevatedDontShowAgainRegistryPath, CantDragElevatedDisableIntervalInDays))
|
||||
{
|
||||
std::vector<action_t> actions = {
|
||||
link_button{ GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED_LEARN_MORE), FancyZonesRunAsAdminInfoPage },
|
||||
link_button{ GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED_DIALOG_DONT_SHOW_AGAIN), ToastNotificationButtonUrl }
|
||||
};
|
||||
show_toast_with_activations(GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED),
|
||||
GET_RESOURCE_STRING(IDS_FANCYZONES),
|
||||
{},
|
||||
std::move(actions));
|
||||
warning_shown = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,7 +215,7 @@
|
||||
</data>
|
||||
<data name="FancyZones" xml:space="preserve">
|
||||
<value>FancyZones</value>
|
||||
<comment>FancyZone is a product name, keep as is.</comment>
|
||||
<comment>FancyZones is a product name, keep as is.</comment>
|
||||
</data>
|
||||
<data name="Cant_Drag_Elevated" xml:space="preserve">
|
||||
<value>We've detected an application running with administrator privileges. This will prevent certain interactions with these applications.</value>
|
||||
@@ -243,19 +243,19 @@
|
||||
</data>
|
||||
<data name="FancyZones_Data_Error" xml:space="preserve">
|
||||
<value>FancyZones persisted data path not found. Please report the bug to</value>
|
||||
<comment>"Report bug to" will have a URL after. FancyZone is a product name, keep as is.</comment>
|
||||
<comment>"Report bug to" will have a URL after. FancyZones is a product name, keep as is.</comment>
|
||||
</data>
|
||||
<data name="FancyZones_Editor_Launch_Error" xml:space="preserve">
|
||||
<value>The FancyZones editor failed to start. Please report the bug to</value>
|
||||
<comment>"Report bug to" will have a URL after. FancyZone is a product name, keep as is.</comment>
|
||||
<comment>"Report bug to" will have a URL after. FancyZones is a product name, keep as is.</comment>
|
||||
</data>
|
||||
<data name="FancyZones_Settings_Load_Error" xml:space="preserve">
|
||||
<value>Failed to load the FancyZones settings. Default settings will be used.</value>
|
||||
<comment>FancyZone is a product name, keep as is.</comment>
|
||||
<comment>FancyZones is a product name, keep as is.</comment>
|
||||
</data>
|
||||
<data name="FancyZones_Settings_Save_Error" xml:space="preserve">
|
||||
<value>Failed to save the FancyZones settings. Please retry again later, if the problem persists report the bug to</value>
|
||||
<comment>"Report bug to" will have a URL after. FancyZone is a product name, keep as is.</comment>
|
||||
<comment>"Report bug to" will have a URL after. FancyZones is a product name, keep as is.</comment>
|
||||
</data>
|
||||
<data name="Setting_Description_QuickLayoutSwitch" xml:space="preserve">
|
||||
<value>Enable quick layout switch</value>
|
||||
|
||||
@@ -33,7 +33,6 @@ namespace NonLocalizable
|
||||
const wchar_t ShowOnAllMonitorsID[] = L"fancyzones_show_on_all_monitors";
|
||||
const wchar_t SpanZonesAcrossMonitorsID[] = L"fancyzones_span_zones_across_monitors";
|
||||
const wchar_t MakeDraggedWindowTransparentID[] = L"fancyzones_makeDraggedWindowTransparent";
|
||||
const wchar_t AllowPopupWindowSnapID[] = L"fancyzones_allowPopupWindowSnap";
|
||||
const wchar_t AllowChildWindowSnapID[] = L"fancyzones_allowChildWindowSnap";
|
||||
const wchar_t DisableRoundCornersOnSnapping[] = L"fancyzones_disableRoundCornersOnSnap";
|
||||
|
||||
@@ -127,7 +126,6 @@ void FancyZonesSettings::LoadSettings()
|
||||
SetBoolFlag(values, NonLocalizable::WindowSwitchingToggleID, SettingId::WindowSwitching, m_settings.windowSwitching);
|
||||
SetBoolFlag(values, NonLocalizable::SystemThemeID, SettingId::SystemTheme, m_settings.systemTheme);
|
||||
SetBoolFlag(values, NonLocalizable::ShowZoneNumberID, SettingId::ShowZoneNumber, m_settings.showZoneNumber);
|
||||
SetBoolFlag(values, NonLocalizable::AllowPopupWindowSnapID, SettingId::AllowSnapPopupWindows, m_settings.allowSnapPopupWindows);
|
||||
SetBoolFlag(values, NonLocalizable::AllowChildWindowSnapID, SettingId::AllowSnapChildWindows, m_settings.allowSnapChildWindows);
|
||||
SetBoolFlag(values, NonLocalizable::DisableRoundCornersOnSnapping, SettingId::DisableRoundCornersOnSnapping, m_settings.disableRoundCorners);
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ struct Settings
|
||||
bool makeDraggedWindowTransparent = true;
|
||||
bool systemTheme = true;
|
||||
bool showZoneNumber = true;
|
||||
bool allowSnapPopupWindows = false;
|
||||
bool allowSnapChildWindows = false;
|
||||
bool disableRoundCorners = false;
|
||||
std::wstring zoneColor = L"#AACDFF";
|
||||
|
||||
@@ -32,7 +32,6 @@ enum class SettingId
|
||||
NextTabHotkey,
|
||||
PrevTabHotkey,
|
||||
ExcludedApps,
|
||||
AllowSnapPopupWindows,
|
||||
AllowSnapChildWindows,
|
||||
DisableRoundCornersOnSnapping,
|
||||
};
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <FancyZonesLib/FancyZonesData/AppZoneHistory.h>
|
||||
#include <FancyZonesLib/FancyZonesWindowProcessing.h>
|
||||
#include <FancyZonesLib/FancyZonesWindowProperties.h>
|
||||
#include <FancyZonesLib/NotificationUtil.h>
|
||||
#include <FancyZonesLib/Settings.h>
|
||||
#include <FancyZonesLib/WindowUtils.h>
|
||||
#include <FancyZonesLib/WorkArea.h>
|
||||
@@ -12,6 +11,7 @@
|
||||
#include <FancyZonesLib/trace.h>
|
||||
|
||||
#include <common/utils/elevation.h>
|
||||
#include <common/notifications/NotificationUtil.h>
|
||||
|
||||
WindowMouseSnap::WindowMouseSnap(HWND window, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas) :
|
||||
m_window(window),
|
||||
@@ -20,8 +20,6 @@ WindowMouseSnap::WindowMouseSnap(HWND window, const std::unordered_map<HMONITOR,
|
||||
m_snappingMode(false)
|
||||
{
|
||||
m_windowProperties.hasNoVisibleOwner = !FancyZonesWindowUtils::HasVisibleOwner(m_window);
|
||||
m_windowProperties.isStandardWindow = FancyZonesWindowUtils::IsStandardWindow(m_window) &&
|
||||
(!FancyZonesWindowUtils::IsPopupWindow(m_window) || FancyZonesSettings::settings().allowSnapPopupWindows);
|
||||
}
|
||||
|
||||
WindowMouseSnap::~WindowMouseSnap()
|
||||
@@ -31,16 +29,15 @@ WindowMouseSnap::~WindowMouseSnap()
|
||||
|
||||
std::unique_ptr<WindowMouseSnap> WindowMouseSnap::Create(HWND window, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas)
|
||||
{
|
||||
if (!FancyZonesWindowProcessing::IsProcessable(window) ||
|
||||
FancyZonesWindowUtils::IsCursorTypeIndicatingSizeEvent())
|
||||
if (FancyZonesWindowUtils::IsCursorTypeIndicatingSizeEvent() || !FancyZonesWindowProcessing::IsProcessable(window))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!is_process_elevated() && FancyZonesWindowUtils::IsProcessOfWindowElevated(window))
|
||||
if (!is_process_elevated() && IsProcessOfWindowElevated(window))
|
||||
{
|
||||
// Notifies user if unable to drag elevated window
|
||||
FancyZonesNotifications::WarnIfElevationIsRequired();
|
||||
notifications::WarnIfElevationIsRequired(GET_RESOURCE_STRING(IDS_FANCYZONES), GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED), GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED_LEARN_MORE), GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED_DIALOG_DONT_SHOW_AGAIN));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -111,13 +108,8 @@ void WindowMouseSnap::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, bo
|
||||
void WindowMouseSnap::MoveSizeEnd()
|
||||
{
|
||||
if (m_snappingMode)
|
||||
{
|
||||
const bool hasNoVisibleOwner = !FancyZonesWindowUtils::HasVisibleOwner(m_window);
|
||||
const bool isStandardWindow = FancyZonesWindowUtils::IsStandardWindow(m_window);
|
||||
|
||||
if ((isStandardWindow == false && hasNoVisibleOwner == true &&
|
||||
m_windowProperties.isStandardWindow == true && m_windowProperties.hasNoVisibleOwner == true) ||
|
||||
FancyZonesWindowUtils::IsWindowMaximized(m_window))
|
||||
{
|
||||
if (FancyZonesWindowUtils::IsWindowMaximized(m_window))
|
||||
{
|
||||
// Abort the zoning, this is a Chromium based tab that is merged back with an existing window
|
||||
// or if the window is maximized by Windows when the cursor hits the screen top border
|
||||
|
||||
@@ -24,8 +24,6 @@ private:
|
||||
|
||||
struct WindowProperties
|
||||
{
|
||||
// True if from the styles the window looks like a standard window
|
||||
bool isStandardWindow = false;
|
||||
// True if the window is a top-level window that does not have a visible owner
|
||||
bool hasNoVisibleOwner = false;
|
||||
// Properties to restore after dragging
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const wchar_t PowerToysAppFZEditor[] = L"POWERTOYS.FANCYZONESEDITOR.EXE";
|
||||
const wchar_t SplashClassName[] = L"MsoSplash";
|
||||
const char SplashClassName[] = "MsoSplash";
|
||||
const wchar_t CoreWindow[] = L"Windows.UI.Core.CoreWindow";
|
||||
const wchar_t SearchUI[] = L"SearchUI.exe";
|
||||
const wchar_t SystemAppsFolder[] = L"SYSTEMAPPS";
|
||||
@@ -122,17 +122,6 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
bool FancyZonesWindowUtils::IsSplashScreen(HWND window)
|
||||
{
|
||||
wchar_t className[MAX_PATH];
|
||||
if (GetClassName(window, className, MAX_PATH) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return wcscmp(NonLocalizable::SplashClassName, className) == 0;
|
||||
}
|
||||
|
||||
bool FancyZonesWindowUtils::IsWindowMaximized(HWND window) noexcept
|
||||
{
|
||||
WINDOWPLACEMENT placement{};
|
||||
@@ -164,71 +153,9 @@ bool FancyZonesWindowUtils::HasVisibleOwner(HWND window) noexcept
|
||||
return rect.top != rect.bottom && rect.left != rect.right;
|
||||
}
|
||||
|
||||
bool FancyZonesWindowUtils::IsStandardWindow(HWND window)
|
||||
bool FancyZonesWindowUtils::IsRoot(HWND window) noexcept
|
||||
{
|
||||
// True if from the styles the window looks like a standard window
|
||||
|
||||
if (GetAncestor(window, GA_ROOT) != window)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto style = GetWindowLong(window, GWL_STYLE);
|
||||
auto exStyle = GetWindowLong(window, GWL_EXSTYLE);
|
||||
|
||||
bool isToolWindow = (exStyle & WS_EX_TOOLWINDOW) == WS_EX_TOOLWINDOW;
|
||||
bool isVisible = (style & WS_VISIBLE) == WS_VISIBLE;
|
||||
if (isToolWindow || !isVisible)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FancyZonesWindowUtils::IsPopupWindow(HWND window) noexcept
|
||||
{
|
||||
auto style = GetWindowLong(window, GWL_STYLE);
|
||||
return ((style & WS_POPUP) == WS_POPUP);
|
||||
}
|
||||
|
||||
bool FancyZonesWindowUtils::HasThickFrame(HWND window) noexcept
|
||||
{
|
||||
auto style = GetWindowLong(window, GWL_STYLE);
|
||||
return ((style & WS_THICKFRAME) == WS_THICKFRAME);
|
||||
}
|
||||
|
||||
bool FancyZonesWindowUtils::HasThickFrameAndMinimizeMaximizeButtons(HWND window) noexcept
|
||||
{
|
||||
auto style = GetWindowLong(window, GWL_STYLE);
|
||||
return ((style & WS_THICKFRAME) == WS_THICKFRAME && (style & WS_MINIMIZEBOX) == WS_MINIMIZEBOX && (style & WS_MAXIMIZEBOX) == WS_MAXIMIZEBOX);
|
||||
}
|
||||
|
||||
bool FancyZonesWindowUtils::IsProcessOfWindowElevated(HWND window)
|
||||
{
|
||||
DWORD pid = 0;
|
||||
GetWindowThreadProcessId(window, &pid);
|
||||
if (!pid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
wil::unique_handle hProcess{ OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
|
||||
FALSE,
|
||||
pid) };
|
||||
|
||||
wil::unique_handle token;
|
||||
|
||||
if (OpenProcessToken(hProcess.get(), TOKEN_QUERY, &token))
|
||||
{
|
||||
TOKEN_ELEVATION elevation;
|
||||
DWORD size;
|
||||
if (GetTokenInformation(token.get(), TokenElevation, &elevation, sizeof(elevation), &size))
|
||||
{
|
||||
return elevation.TokenIsElevated != 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return GetAncestor(window, GA_ROOT) == window;
|
||||
}
|
||||
|
||||
bool FancyZonesWindowUtils::IsExcluded(HWND window)
|
||||
@@ -248,12 +175,12 @@ bool FancyZonesWindowUtils::IsExcluded(HWND window)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FancyZonesWindowUtils::IsExcludedByUser(const HWND& hwnd, std::wstring& processPath) noexcept
|
||||
bool FancyZonesWindowUtils::IsExcludedByUser(const HWND& hwnd, const std::wstring& processPath) noexcept
|
||||
{
|
||||
return (check_excluded_app(hwnd, processPath, FancyZonesSettings::settings().excludedAppsArray));
|
||||
}
|
||||
|
||||
bool FancyZonesWindowUtils::IsExcludedByDefault(const HWND& hwnd, std::wstring& processPath) noexcept
|
||||
bool FancyZonesWindowUtils::IsExcludedByDefault(const HWND& hwnd, const std::wstring& processPath) noexcept
|
||||
{
|
||||
static std::vector<std::wstring> defaultExcludedFolders = { NonLocalizable::SystemAppsFolder };
|
||||
if (find_folder_in_path(processPath, defaultExcludedFolders))
|
||||
@@ -261,9 +188,14 @@ bool FancyZonesWindowUtils::IsExcludedByDefault(const HWND& hwnd, std::wstring&
|
||||
return true;
|
||||
}
|
||||
|
||||
std::array<char, 256> class_name;
|
||||
GetClassNameA(hwnd, class_name.data(), static_cast<int>(class_name.size()));
|
||||
if (is_system_window(hwnd, class_name.data()))
|
||||
std::array<char, 256> className;
|
||||
GetClassNameA(hwnd, className.data(), static_cast<int>(className.size()));
|
||||
if (is_system_window(hwnd, className.data()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(NonLocalizable::SplashClassName, className.data()) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -294,7 +226,7 @@ void FancyZonesWindowUtils::SwitchToWindow(HWND window) noexcept
|
||||
}
|
||||
}
|
||||
|
||||
void FancyZonesWindowUtils::SizeWindowToRect(HWND window, RECT rect) noexcept
|
||||
void FancyZonesWindowUtils::SizeWindowToRect(HWND window, RECT rect, BOOL snapZone) noexcept
|
||||
{
|
||||
WINDOWPLACEMENT placement{};
|
||||
::GetWindowPlacement(window, &placement);
|
||||
@@ -306,8 +238,15 @@ void FancyZonesWindowUtils::SizeWindowToRect(HWND window, RECT rect) noexcept
|
||||
::GetWindowPlacement(window, &placement);
|
||||
}
|
||||
|
||||
BOOL maximizeLater = false;
|
||||
if (IsWindowVisible(window))
|
||||
{
|
||||
// If is not snap zone then need keep maximize state (move to active monitor)
|
||||
if (!snapZone && placement.showCmd == SW_SHOWMAXIMIZED)
|
||||
{
|
||||
maximizeLater = true;
|
||||
}
|
||||
|
||||
// Do not restore minimized windows. We change their placement though so they restore to the correct zone.
|
||||
if ((placement.showCmd != SW_SHOWMINIMIZED) &&
|
||||
(placement.showCmd != SW_MINIMIZE))
|
||||
@@ -335,6 +274,12 @@ void FancyZonesWindowUtils::SizeWindowToRect(HWND window, RECT rect) noexcept
|
||||
Logger::error(L"SetWindowPlacement failed, {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
|
||||
// make sure window is moved to the correct monitor before maximize.
|
||||
if (maximizeLater)
|
||||
{
|
||||
placement.showCmd = SW_SHOWMAXIMIZED;
|
||||
}
|
||||
|
||||
// Do it again, allowing Windows to resize the window and set correct scaling
|
||||
// This fixes Issue #365
|
||||
result = ::SetWindowPlacement(window, &placement);
|
||||
|
||||
@@ -15,21 +15,23 @@
|
||||
|
||||
namespace FancyZonesWindowUtils
|
||||
{
|
||||
bool IsSplashScreen(HWND window);
|
||||
bool IsWindowMaximized(HWND window) noexcept;
|
||||
bool HasVisibleOwner(HWND window) noexcept;
|
||||
bool IsStandardWindow(HWND window);
|
||||
bool IsPopupWindow(HWND window) noexcept;
|
||||
bool HasThickFrame(HWND window) noexcept;
|
||||
bool HasThickFrameAndMinimizeMaximizeButtons(HWND window) noexcept;
|
||||
bool IsRoot(HWND window) noexcept;
|
||||
|
||||
constexpr bool HasStyle(LONG style, LONG styleToCheck) noexcept
|
||||
{
|
||||
return ((style & styleToCheck) == styleToCheck);
|
||||
}
|
||||
|
||||
bool IsProcessOfWindowElevated(HWND window); // If HWND is already dead, we assume it wasn't elevated
|
||||
|
||||
bool IsExcluded(HWND window);
|
||||
bool IsExcludedByUser(const HWND& hwnd, std::wstring& processPath) noexcept;
|
||||
bool IsExcludedByDefault(const HWND& hwnd, std::wstring& processPath) noexcept;
|
||||
bool IsExcludedByUser(const HWND& hwnd, const std::wstring& processPath) noexcept;
|
||||
bool IsExcludedByDefault(const HWND& hwnd, const std::wstring& processPath) noexcept;
|
||||
|
||||
void SwitchToWindow(HWND window) noexcept;
|
||||
void SizeWindowToRect(HWND window, RECT rect) noexcept; // Parameter rect must be in screen coordinates (e.g. obtained from GetWindowRect)
|
||||
void SizeWindowToRect(HWND window, RECT rect, BOOL snapZone = true) noexcept; // Parameter rect must be in screen coordinates (e.g. obtained from GetWindowRect)
|
||||
void SaveWindowSizeAndOrigin(HWND window) noexcept;
|
||||
void RestoreWindowSize(HWND window) noexcept;
|
||||
void RestoreWindowOrigin(HWND window) noexcept;
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
#define SpanZonesAcrossMonitorsKey "SpanZonesAcrossMonitors"
|
||||
#define MakeDraggedWindowTransparentKey "MakeDraggedWindowTransparent"
|
||||
#define AllowSnapChildWindows "AllowSnapChildWindows"
|
||||
#define AllowSnapPopupWindows "AllowSnapPopupWindows"
|
||||
#define DisableRoundCornersOnSnapping "DisableRoundCornersOnSnapping"
|
||||
#define ZoneColorKey "ZoneColor"
|
||||
#define ZoneBorderColorKey "ZoneBorderColor"
|
||||
@@ -323,7 +322,6 @@ void Trace::SettingsTelemetry(const Settings& settings) noexcept
|
||||
TraceLoggingBoolean(settings.spanZonesAcrossMonitors, SpanZonesAcrossMonitorsKey),
|
||||
TraceLoggingBoolean(settings.makeDraggedWindowTransparent, MakeDraggedWindowTransparentKey),
|
||||
TraceLoggingBoolean(settings.allowSnapChildWindows, AllowSnapChildWindows),
|
||||
TraceLoggingBoolean(settings.allowSnapPopupWindows, AllowSnapPopupWindows),
|
||||
TraceLoggingBoolean(settings.disableRoundCorners, DisableRoundCornersOnSnapping),
|
||||
TraceLoggingWideString(settings.zoneColor.c_str(), ZoneColorKey),
|
||||
TraceLoggingWideString(settings.zoneBorderColor.c_str(), ZoneBorderColorKey),
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
<ClCompile Include="Util.Spec.cpp" />
|
||||
<ClCompile Include="Util.cpp" />
|
||||
<ClCompile Include="WindowKeyboardSnap.Spec.cpp" />
|
||||
<ClCompile Include="WindowProcessingTests.Spec.cpp" />
|
||||
<ClCompile Include="WorkArea.Spec.cpp" />
|
||||
<ClCompile Include="WorkAreaIdTests.Spec.cpp" />
|
||||
<ClCompile Include="Zone.Spec.cpp" />
|
||||
|
||||
@@ -66,6 +66,9 @@
|
||||
<ClCompile Include="WindowKeyboardSnap.Spec.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WindowProcessingTests.Spec.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h">
|
||||
|
||||
@@ -8,8 +8,7 @@ namespace Mocks
|
||||
class HwndCreator
|
||||
{
|
||||
public:
|
||||
HwndCreator(const std::wstring& title = L"");
|
||||
|
||||
HwndCreator(const std::wstring& title, const std::wstring& className, DWORD exStyle, DWORD style, HWND parentWindow);
|
||||
~HwndCreator();
|
||||
|
||||
HWND operator()(HINSTANCE hInst);
|
||||
@@ -20,23 +19,30 @@ namespace Mocks
|
||||
inline HINSTANCE getHInstance() const { return m_hInst; }
|
||||
inline const std::wstring& getTitle() const { return m_windowTitle; }
|
||||
inline const std::wstring& getWindowClassName() const { return m_windowClassName; }
|
||||
inline DWORD getExStyle() const noexcept { return m_exStyle; }
|
||||
inline DWORD getStyle() const noexcept { return m_style; }
|
||||
inline HWND getParentWindow() const noexcept { return m_parentWindow; }
|
||||
|
||||
private:
|
||||
std::wstring m_windowTitle;
|
||||
std::wstring m_windowClassName;
|
||||
DWORD m_exStyle{ 0 };
|
||||
DWORD m_style{ 0 };
|
||||
HWND m_parentWindow{ NULL };
|
||||
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_conditionVar;
|
||||
bool m_conditionFlag;
|
||||
HANDLE m_thread;
|
||||
|
||||
HINSTANCE m_hInst;
|
||||
HINSTANCE m_hInst{};
|
||||
HWND m_hWnd;
|
||||
};
|
||||
|
||||
HWND WindowCreate(HINSTANCE hInst)
|
||||
HWND WindowCreate(HINSTANCE hInst, const std::wstring& title /*= L""*/, const std::wstring& className /*= L""*/,
|
||||
DWORD exStyle, DWORD style, HWND parentWindow)
|
||||
{
|
||||
return HwndCreator()(hInst);
|
||||
return HwndCreator(title, className, exStyle, style, parentWindow)(hInst);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,8 +91,21 @@ DWORD WINAPI ThreadProc(LPVOID lpParam)
|
||||
|
||||
if (RegisterDLLWindowClass(creator->getWindowClassName().c_str(), creator) != 0)
|
||||
{
|
||||
auto hWnd = CreateWindowEx(0, creator->getWindowClassName().c_str(), creator->getTitle().c_str(), WS_EX_APPWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 10, 10, nullptr, nullptr, creator->getHInstance(), NULL);
|
||||
SetWindowPos(hWnd, HWND_TOPMOST, 10, 10, 100, 100, SWP_SHOWWINDOW);
|
||||
auto hWnd = CreateWindowEx(creator->getExStyle(),
|
||||
creator->getWindowClassName().c_str(),
|
||||
creator->getTitle().c_str(),
|
||||
creator->getStyle(),
|
||||
CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
creator->getParentWindow(),
|
||||
nullptr,
|
||||
creator->getHInstance(),
|
||||
NULL);
|
||||
|
||||
ShowWindow(hWnd, SW_SHOW);
|
||||
// wait after ShowWindow to make sure that it's finished and shown
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
creator->setHwnd(hWnd);
|
||||
creator->setCondition(true);
|
||||
|
||||
@@ -95,8 +114,6 @@ DWORD WINAPI ThreadProc(LPVOID lpParam)
|
||||
TranslateMessage(&messages);
|
||||
DispatchMessage(&messages);
|
||||
}
|
||||
|
||||
creator->setHwnd(hWnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -108,8 +125,16 @@ DWORD WINAPI ThreadProc(LPVOID lpParam)
|
||||
|
||||
namespace Mocks
|
||||
{
|
||||
HwndCreator::HwndCreator(const std::wstring& title) :
|
||||
m_windowTitle(title), m_windowClassName(std::to_wstring(++s_classId)), m_conditionFlag(false), m_thread(nullptr), m_hInst(HINSTANCE{}), m_hWnd(nullptr)
|
||||
HwndCreator::HwndCreator(const std::wstring& title, const std::wstring& className, DWORD exStyle, DWORD style, HWND parentWindow) :
|
||||
m_windowTitle(title),
|
||||
m_windowClassName(className.empty() ? std::to_wstring(++s_classId) : className),
|
||||
m_exStyle(exStyle),
|
||||
m_style(style),
|
||||
m_parentWindow(parentWindow),
|
||||
m_conditionFlag(false),
|
||||
m_thread(nullptr),
|
||||
m_hInst(HINSTANCE{}),
|
||||
m_hWnd(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,8 @@ namespace Mocks
|
||||
return reinterpret_cast<HINSTANCE>(++s_nextInstance);
|
||||
}
|
||||
|
||||
HWND WindowCreate(HINSTANCE hInst);
|
||||
HWND WindowCreate(HINSTANCE hInst, const std::wstring& title = L"", const std::wstring& className = L""
|
||||
, DWORD exStyle = 0, DWORD style = 0, HWND parentWindow = nullptr);
|
||||
}
|
||||
|
||||
namespace Helpers
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <FancyZonesLib/FancyZonesWindowProcessing.h>
|
||||
#include <FancyZonesLib/Settings.h>
|
||||
#include <FancyZonesLib/WindowUtils.h>
|
||||
|
||||
#include "Util.h"
|
||||
|
||||
#include <CppUnitTestLogger.h>
|
||||
|
||||
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||
|
||||
namespace Microsoft
|
||||
{
|
||||
namespace VisualStudio
|
||||
{
|
||||
namespace CppUnitTestFramework
|
||||
{
|
||||
template<>
|
||||
std::wstring ToString<FancyZonesWindowProcessing::ProcessabilityType>(const FancyZonesWindowProcessing::ProcessabilityType& type)
|
||||
{
|
||||
return std::to_wstring((int)type);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace FancyZonesUnitTests
|
||||
{
|
||||
TEST_CLASS (WindowProcessingUnitTests)
|
||||
{
|
||||
HINSTANCE hInst{};
|
||||
|
||||
TEST_METHOD_CLEANUP(CleanUp)
|
||||
{
|
||||
FancyZonesSettings::instance().SetSettings(Settings{});
|
||||
}
|
||||
|
||||
TEST_METHOD (MinimizedWindow)
|
||||
{
|
||||
HWND window = Mocks::WindowCreate(hInst);
|
||||
ShowWindow(window, SW_MINIMIZE);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // let ShowWindow finish
|
||||
Assert::IsTrue(IsIconic(window));
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Minimized, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
|
||||
TEST_METHOD (ToolWindow)
|
||||
{
|
||||
HWND window = Mocks::WindowCreate(hInst, L"", L"", WS_EX_TOOLWINDOW);
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::ToolWindow, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
|
||||
TEST_METHOD (InvisibleWindow)
|
||||
{
|
||||
HWND window = Mocks::WindowCreate(hInst);
|
||||
ShowWindow(window, SW_HIDE);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // let ShowWindow finish
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::NotVisible, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
|
||||
TEST_METHOD(NonRootWindow)
|
||||
{
|
||||
HWND rootWindow = Mocks::WindowCreate(hInst, L"RootWindow", L"", 0, WS_TILEDWINDOW | WS_CLIPCHILDREN);
|
||||
Assert::IsTrue(FancyZonesWindowUtils::IsRoot(rootWindow));
|
||||
|
||||
HWND window = CreateWindow(WC_COMBOBOX, TEXT(""), CBS_DROPDOWN | CBS_HASSTRINGS | WS_CHILD | WS_OVERLAPPED | WS_VISIBLE, 0, 0, 10, 10, rootWindow, NULL, hInst, NULL);
|
||||
Assert::IsFalse(FancyZonesWindowUtils::IsRoot(window));
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::NonRootWindow, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
|
||||
TEST_METHOD (Popup_App)
|
||||
{
|
||||
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, WS_TILEDWINDOW | WS_POPUP);
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Processable, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
|
||||
TEST_METHOD (Popup_Menu)
|
||||
{
|
||||
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, WS_POPUP | WS_TILED | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::NonProcessablePopupWindow, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
|
||||
TEST_METHOD (Popup_MenuEdge)
|
||||
{
|
||||
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, WS_POPUP | WS_TILED | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_THICKFRAME | WS_SIZEBOX);
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::NonProcessablePopupWindow, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
|
||||
TEST_METHOD (Popup_Calculator)
|
||||
{
|
||||
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, WS_BORDER | WS_CLIPSIBLINGS | WS_DLGFRAME | WS_GROUP | WS_POPUP | WS_POPUPWINDOW | WS_SIZEBOX | WS_TABSTOP | WS_TILEDWINDOW);
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Processable, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
|
||||
TEST_METHOD (Popup_CalculatorTopmost)
|
||||
{
|
||||
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, WS_BORDER | WS_CAPTION | WS_CLIPSIBLINGS | WS_DLGFRAME | WS_OVERLAPPED | WS_POPUP | WS_POPUPWINDOW | WS_SIZEBOX | WS_SYSMENU | WS_THICKFRAME);
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Processable, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
|
||||
TEST_METHOD(Popup_FacebookMessenger)
|
||||
{
|
||||
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, WS_GROUP | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_POPUP | WS_TABSTOP | WS_THICKFRAME);
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Processable, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
|
||||
TEST_METHOD (ChildWindow_OptionDisabled)
|
||||
{
|
||||
FancyZonesSettings::instance().SetSettings(Settings{ .allowSnapChildWindows = false });
|
||||
HWND parentWindow = Mocks::WindowCreate(hInst, L"", L"", 0, WS_TILEDWINDOW);
|
||||
if (!IsWindowVisible(parentWindow))
|
||||
{
|
||||
// skip the test if the parent window isn't visible.
|
||||
// test can run locally, but will fail in CI because of the configuration
|
||||
return;
|
||||
}
|
||||
|
||||
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, 0, parentWindow);
|
||||
Assert::IsTrue(IsWindowVisible(window), L"Child window not visible");
|
||||
Assert::IsTrue(FancyZonesWindowUtils::HasVisibleOwner(window), L"Child window doesn't have visible owner");
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::ChildWindow, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
|
||||
TEST_METHOD (ChildWindow_OptionEnabled)
|
||||
{
|
||||
FancyZonesSettings::instance().SetSettings(Settings{ .allowSnapChildWindows = true });
|
||||
HWND parentWindow = Mocks::WindowCreate(hInst, L"", L"", 0, WS_TILEDWINDOW);
|
||||
if (!IsWindowVisible(parentWindow))
|
||||
{
|
||||
// skip the test if the parent window isn't visible.
|
||||
// test can run locally, but will fail in CI because of the configuration
|
||||
return;
|
||||
}
|
||||
|
||||
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, 0, parentWindow);
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Processable, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
|
||||
TEST_METHOD (ExcludedApp_ByDefault)
|
||||
{
|
||||
// set class from the excluded list
|
||||
HWND window = Mocks::WindowCreate(hInst, L"", L"SysListView32");
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Excluded, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
|
||||
TEST_METHOD (ExcludedApp_ByDefault_SplashScreen)
|
||||
{
|
||||
HWND window = Mocks::WindowCreate(hInst, L"", L"MsoSplash");
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Excluded, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
|
||||
TEST_METHOD (ExcludedApp_ByUser)
|
||||
{
|
||||
// case sensitive, should be uppercase
|
||||
FancyZonesSettings::instance().SetSettings(Settings{ .excludedAppsArray = { L"TEST_EXCLUDED" } });
|
||||
|
||||
// exclude by window title
|
||||
HWND window = Mocks::WindowCreate(hInst, L"Test_Excluded");
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Excluded, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsFalse(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
|
||||
TEST_METHOD (ProcessableWindow)
|
||||
{
|
||||
HWND window = Mocks::WindowCreate(hInst, L"", L"", 0, WS_TILEDWINDOW);
|
||||
|
||||
Assert::AreEqual(FancyZonesWindowProcessing::ProcessabilityType::Processable, FancyZonesWindowProcessing::DefineWindowType(window));
|
||||
Assert::IsTrue(FancyZonesWindowProcessing::IsProcessable(window));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -249,7 +249,7 @@ namespace FancyZonesUnitTests
|
||||
AppliedLayouts::instance().ApplyLayout(m_workAreaId, layout);
|
||||
|
||||
const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
|
||||
const auto window = Mocks::WindowCreate(m_hInst);
|
||||
const auto window = Mocks::WindowCreate(m_hInst, L"", L"", 0, WS_THICKFRAME);
|
||||
|
||||
SetWindowPos(window, nullptr, 150, 150, 450, 550, SWP_SHOWWINDOW);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows10.0.20348.0</TargetFramework>
|
||||
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
@@ -74,8 +74,8 @@
|
||||
TabIndex="2" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Click="OnSaveApplyTemplate"
|
||||
Content="{x:Static props:Resources.Save_Apply}"
|
||||
Click="OnSave"
|
||||
Content="{x:Static props:Resources.Save}"
|
||||
Style="{StaticResource AccentButtonStyle}"
|
||||
TabIndex="1" />
|
||||
</Grid>
|
||||
|
||||
@@ -237,7 +237,7 @@ namespace FancyZonesEditor
|
||||
private SnappyHelperBase snappyX;
|
||||
private SnappyHelperBase snappyY;
|
||||
|
||||
private SnappyHelperBase NewMagneticSnapper(bool isX, ResizeMode mode)
|
||||
private SnappyHelperMagnetic NewMagneticSnapper(bool isX, ResizeMode mode)
|
||||
{
|
||||
Rect workingArea = App.Overlay.WorkArea;
|
||||
int screenAxisOrigin = (int)(isX ? workingArea.Left : workingArea.Top);
|
||||
@@ -245,7 +245,7 @@ namespace FancyZonesEditor
|
||||
return new SnappyHelperMagnetic(Model.Zones, ZoneIndex, isX, mode, screenAxisOrigin, screenAxisSize);
|
||||
}
|
||||
|
||||
private SnappyHelperBase NewNonMagneticSnapper(bool isX, ResizeMode mode)
|
||||
private SnappyHelperNonMagnetic NewNonMagneticSnapper(bool isX, ResizeMode mode)
|
||||
{
|
||||
Rect workingArea = App.Overlay.WorkArea;
|
||||
int screenAxisOrigin = (int)(isX ? workingArea.Left : workingArea.Top);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Windows.Automation.Peers;
|
||||
using System.Windows.Controls;
|
||||
|
||||
@@ -10,6 +11,8 @@ namespace FancyZonesEditor.Controls
|
||||
{
|
||||
internal sealed class CustomSliderAutomationPeer : SliderAutomationPeer
|
||||
{
|
||||
private static readonly CompositeFormat CustomSliderAnnounce = System.Text.CompositeFormat.Parse(Properties.Resources.Custom_slider_announce);
|
||||
|
||||
private string name = string.Empty;
|
||||
|
||||
public CustomSliderAutomationPeer(Slider owner)
|
||||
@@ -29,7 +32,7 @@ namespace FancyZonesEditor.Controls
|
||||
|
||||
string announce = string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
Properties.Resources.Custom_slider_announce,
|
||||
CustomSliderAnnounce,
|
||||
name,
|
||||
element.Minimum,
|
||||
element.Maximum,
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace FancyZonesEditor
|
||||
EditingLayout = editingLayout;
|
||||
}
|
||||
|
||||
protected void OnSaveApplyTemplate(object sender, RoutedEventArgs e)
|
||||
protected void OnSave(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
@@ -31,15 +31,8 @@ namespace FancyZonesEditor
|
||||
|
||||
EditingLayout.Persist();
|
||||
|
||||
MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings;
|
||||
settings.SetAppliedModel(EditingLayout);
|
||||
App.Overlay.Monitors[App.Overlay.CurrentDesktop].SetLayoutSettings(EditingLayout);
|
||||
|
||||
App.FancyZonesEditorIO.SerializeLayoutTemplates();
|
||||
App.FancyZonesEditorIO.SerializeCustomLayouts();
|
||||
App.FancyZonesEditorIO.SerializeAppliedLayouts();
|
||||
App.FancyZonesEditorIO.SerializeDefaultLayouts();
|
||||
App.FancyZonesEditorIO.SerializeLayoutHotkeys();
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
|
||||
<!-- SelfContained=true requires RuntimeIdentifier to be set -->
|
||||
<PropertyGroup Condition="'$(Platform)'=='x64'">
|
||||
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Platform)'=='ARM64'">
|
||||
<RuntimeIdentifier>win10-arm64</RuntimeIdentifier>
|
||||
<RuntimeIdentifier>win-arm64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}</ProjectGuid>
|
||||
<TargetFramework>net7.0-windows10.0.20348.0</TargetFramework>
|
||||
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
|
||||
@@ -72,8 +72,8 @@
|
||||
TabIndex="1" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Click="OnSaveApplyTemplate"
|
||||
Content="{x:Static props:Resources.Save_Apply}"
|
||||
Click="OnSave"
|
||||
Content="{x:Static props:Resources.Save}"
|
||||
Style="{StaticResource AccentButtonStyle}"
|
||||
TabIndex="0" />
|
||||
</Grid>
|
||||
|
||||
@@ -6,6 +6,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Automation.Peers;
|
||||
@@ -36,6 +37,10 @@ namespace FancyZonesEditor
|
||||
|
||||
private bool haveTriedToGetFocusAlready;
|
||||
|
||||
private static readonly CompositeFormat EditTemplate = System.Text.CompositeFormat.Parse(Properties.Resources.Edit_Template);
|
||||
private static readonly CompositeFormat PixelValue = System.Text.CompositeFormat.Parse(Properties.Resources.Pixel_Value);
|
||||
private static readonly CompositeFormat TemplateZoneCountValue = System.Text.CompositeFormat.Parse(Properties.Resources.Template_Zone_Count_Value);
|
||||
|
||||
public int WrapPanelItemSize { get; set; } = DefaultWrapPanelItemSize;
|
||||
|
||||
public MainWindow(bool spanZonesAcrossMonitors, Rect workArea)
|
||||
@@ -335,7 +340,7 @@ namespace FancyZonesEditor
|
||||
App.Overlay.StartEditing(_settings.SelectedModel);
|
||||
|
||||
Keyboard.ClearFocus();
|
||||
EditLayoutDialogTitle.Text = string.Format(CultureInfo.CurrentCulture, Properties.Resources.Edit_Template, ((LayoutModel)dataContext).Name);
|
||||
EditLayoutDialogTitle.Text = string.Format(CultureInfo.CurrentCulture, EditTemplate, ((LayoutModel)dataContext).Name);
|
||||
await EditLayoutDialog.ShowAsync();
|
||||
}
|
||||
|
||||
@@ -567,7 +572,7 @@ namespace FancyZonesEditor
|
||||
FrameworkElementAutomationPeer.FromElement(SensitivityInput) as SliderAutomationPeer;
|
||||
string activityId = "sliderValueChanged";
|
||||
|
||||
string value = string.Format(CultureInfo.CurrentCulture, Properties.Resources.Pixel_Value, SensitivityInput.Value);
|
||||
string value = string.Format(CultureInfo.CurrentCulture, PixelValue, SensitivityInput.Value);
|
||||
|
||||
if (peer != null && value != null)
|
||||
{
|
||||
@@ -588,7 +593,7 @@ namespace FancyZonesEditor
|
||||
FrameworkElementAutomationPeer.FromElement(TemplateZoneCount) as SliderAutomationPeer;
|
||||
string activityId = "templateZoneCountValueChanged";
|
||||
|
||||
string value = string.Format(CultureInfo.CurrentCulture, Properties.Resources.Template_Zone_Count_Value, TemplateZoneCount.Value);
|
||||
string value = string.Format(CultureInfo.CurrentCulture, TemplateZoneCountValue, TemplateZoneCount.Value);
|
||||
|
||||
if (peer != null && value != null)
|
||||
{
|
||||
@@ -609,7 +614,7 @@ namespace FancyZonesEditor
|
||||
FrameworkElementAutomationPeer.FromElement(Spacing) as SliderAutomationPeer;
|
||||
string activityId = "spacingValueChanged";
|
||||
|
||||
string value = string.Format(CultureInfo.CurrentCulture, Properties.Resources.Pixel_Value, Spacing.Value);
|
||||
string value = string.Format(CultureInfo.CurrentCulture, PixelValue, Spacing.Value);
|
||||
|
||||
if (peer != null && value != null)
|
||||
{
|
||||
|
||||
@@ -43,12 +43,12 @@ namespace FancyZonesEditor.Models
|
||||
|
||||
public bool SelectKey(string key, string uuid)
|
||||
{
|
||||
if (!SelectedKeys.ContainsKey(key))
|
||||
if (!SelectedKeys.TryGetValue(key, out string value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SelectedKeys[key] == uuid)
|
||||
if (value == uuid)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -377,6 +377,7 @@ namespace FancyZonesEditor.Models
|
||||
public void Persist()
|
||||
{
|
||||
PersistData();
|
||||
FirePropertyChanged(nameof(PersistData));
|
||||
}
|
||||
|
||||
public void LayoutHotkeys_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
|
||||
@@ -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.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
@@ -231,7 +232,7 @@ namespace FancyZonesEditor
|
||||
{
|
||||
foreach (LayoutModel model in CustomModels)
|
||||
{
|
||||
if (model.Uuid == currentApplied.ZonesetUuid.ToUpperInvariant())
|
||||
if (string.Equals(model.Uuid, currentApplied.ZonesetUuid, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// found match
|
||||
foundModel = model;
|
||||
|
||||
@@ -20,10 +20,7 @@ namespace FancyZonesEditor.Utils
|
||||
|
||||
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
|
||||
{
|
||||
if (execute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(execute));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(execute);
|
||||
|
||||
_execute = execute;
|
||||
_canExecute = canExecute;
|
||||
|
||||
@@ -20,10 +20,7 @@ namespace FancyZonesEditor.Utils
|
||||
|
||||
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
|
||||
{
|
||||
if (execute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(execute));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(execute);
|
||||
|
||||
_execute = execute;
|
||||
_canExecute = canExecute;
|
||||
|
||||
Reference in New Issue
Block a user