Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions FloatingClock/App.config
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false;Switch.System.Windows.Media.EnableMultiMonitorDisplayClipping=true" />
</runtime>
<startup>

<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/></startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>
3 changes: 2 additions & 1 deletion FloatingClock/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ public partial class App : Application
/// </summary>
private void App_OnExit(object sender, ExitEventArgs e)
{
NetworkTimeProvider.Stop();
MouseHook.UnhookWindowsHookEx(MouseHook._hookID);

}
}
}
}
204 changes: 204 additions & 0 deletions FloatingClock/DisplayHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace FloatingClock
{
/// <summary>
/// Multi-monitor helpers. All rectangles and positions exposed by this class are physical pixels.
/// </summary>
internal static class DisplayHelper
{
private const int EnumCurrentSettings = -1;
private const int MdtEffectiveDpi = 0;
private const uint MonitorDefaultToNearest = 2;

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DevMode
{
private const int CchDeviceName = 32;
private const int CchFormName = 32;

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CchDeviceName)]
public string dmDeviceName;
public ushort dmSpecVersion;
public ushort dmDriverVersion;
public ushort dmSize;
public ushort dmDriverExtra;
public uint dmFields;
public int dmPositionX;
public int dmPositionY;
public uint dmDisplayOrientation;
public uint dmDisplayFixedOutput;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CchFormName)]
public string dmFormName;
public ushort dmLogPixels;
public uint dmBitsPerPel;
public uint dmPelsWidth;
public uint dmPelsHeight;
public uint dmDisplayFlags;
public uint dmDisplayFrequency;
public uint dmICMMethod;
public uint dmICMIntent;
public uint dmMediaType;
public uint dmDitherType;
public uint dmReserved1;
public uint dmReserved2;
public uint dmPanningWidth;
public uint dmPanningHeight;
}

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern bool EnumDisplaySettings(
string deviceName, int modeNum, ref DevMode devMode);

[DllImport("user32.dll")]
private static extern IntPtr MonitorFromPoint(Point pt, uint flags);

[DllImport("shcore.dll")]
private static extern int GetDpiForMonitor(
IntPtr monitor, int dpiType, out uint dpiX, out uint dpiY);

internal static Screen[] GetScreensInFriendlyOrder()
{
return Screen.AllScreens
.OrderByDescending(s => s.Primary)
.ThenBy(s => GetPhysicalBounds(s).Left)
.ThenBy(s => GetPhysicalBounds(s).Top)
.ToArray();
}

internal static Screen FindByDeviceName(string deviceName)
{
if (string.IsNullOrWhiteSpace(deviceName)) return null;
return Screen.AllScreens.FirstOrDefault(s =>
string.Equals(s.DeviceName, deviceName, StringComparison.OrdinalIgnoreCase));
}

internal static Size GetCurrentResolution(Screen screen)
{
DevMode mode;
if (TryGetCurrentMode(screen, out mode))
return new Size((int)mode.dmPelsWidth, (int)mode.dmPelsHeight);

return screen.Bounds.Size;
}

private static bool TryGetCurrentMode(Screen screen, out DevMode mode)
{
mode = new DevMode();
mode.dmSize = (ushort)Marshal.SizeOf(typeof(DevMode));
return EnumDisplaySettings(screen.DeviceName, EnumCurrentSettings, ref mode)
&& mode.dmPelsWidth > 0 && mode.dmPelsHeight > 0;
}

internal static Rectangle GetPhysicalBounds(Screen screen)
{
DevMode mode;
if (!TryGetCurrentMode(screen, out mode)) return screen.Bounds;
return new Rectangle(mode.dmPositionX, mode.dmPositionY,
(int)mode.dmPelsWidth, (int)mode.dmPelsHeight);
}

internal static Rectangle GetPhysicalWorkingArea(Screen screen)
{
Rectangle physical = GetPhysicalBounds(screen);
Rectangle logical = screen.Bounds;
Rectangle work = screen.WorkingArea;
if (logical.Width <= 0 || logical.Height <= 0) return physical;

double sx = physical.Width / (double)logical.Width;
double sy = physical.Height / (double)logical.Height;
int left = physical.Left + (int)Math.Round((work.Left - logical.Left) * sx);
int top = physical.Top + (int)Math.Round((work.Top - logical.Top) * sy);
int right = physical.Left + (int)Math.Round((work.Right - logical.Left) * sx);
int bottom = physical.Top + (int)Math.Round((work.Bottom - logical.Top) * sy);
return Rectangle.FromLTRB(left, top, right, bottom);
}

internal static uint GetEffectiveDpi(Screen screen)
{
// 在 System-DPI-aware 的旧版 WPF 中 Screen.Bounds 会被缩放,显示模式仍是物理像素。
// 先用二者比例恢复目标屏 DPI,避免把主屏 DPI 错套到其它屏幕。
Size physical = GetCurrentResolution(screen);
if (screen.Bounds.Width > 0)
{
double ratio = physical.Width / (double)screen.Bounds.Width;
if (ratio > 1.01)
return (uint)Math.Round(96.0 * ratio);
}

try
{
var center = new Point(
screen.Bounds.Left + screen.Bounds.Width / 2,
screen.Bounds.Top + screen.Bounds.Height / 2);
IntPtr monitor = MonitorFromPoint(center, MonitorDefaultToNearest);
uint x, y;
if (monitor != IntPtr.Zero && GetDpiForMonitor(monitor, MdtEffectiveDpi, out x, out y) == 0 && x > 0)
return x;
}
catch (DllNotFoundException) { }
catch (EntryPointNotFoundException) { }

return 96;
}

internal static int GetScalePercent(Screen screen)
{
return (int)Math.Round(GetEffectiveDpi(screen) * 100.0 / 96.0);
}

internal static string FriendlyDeviceName(Screen screen)
{
string name = screen.DeviceName ?? "Display";
const string prefix = @"\\.\DISPLAY";
return name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
? "Display " + name.Substring(prefix.Length)
: name;
}

internal static Rectangle GetVirtualBounds()
{
var screens = Screen.AllScreens;
if (screens.Length == 0) return Rectangle.Empty;

int left = screens.Min(s => GetPhysicalBounds(s).Left);
int top = screens.Min(s => GetPhysicalBounds(s).Top);
int right = screens.Max(s => GetPhysicalBounds(s).Right);
int bottom = screens.Max(s => GetPhysicalBounds(s).Bottom);
return Rectangle.FromLTRB(left, top, right, bottom);
}

internal static Screen FindByPhysicalPoint(Point point)
{
Screen nearest = null;
long nearestDistance = long.MaxValue;
foreach (Screen screen in Screen.AllScreens)
{
Rectangle bounds = GetPhysicalBounds(screen);
if (bounds.Contains(point)) return screen;

int dx = point.X < bounds.Left ? bounds.Left - point.X
: point.X >= bounds.Right ? point.X - bounds.Right + 1 : 0;
int dy = point.Y < bounds.Top ? bounds.Top - point.Y
: point.Y >= bounds.Bottom ? point.Y - bounds.Bottom + 1 : 0;
long distance = (long)dx * dx + (long)dy * dy;
if (distance < nearestDistance)
{
nearestDistance = distance;
nearest = screen;
}
}
return nearest ?? Screen.PrimaryScreen;
}
}
}
6 changes: 5 additions & 1 deletion FloatingClock/FloatingClock.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>clock.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
Expand Down Expand Up @@ -71,6 +72,8 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="HotKey.cs" />
<Compile Include="DisplayHelper.cs" />
<Compile Include="NetworkTimeProvider.cs" />
<Compile Include="OptionsWindow.cs" />
<Compile Include="Skins.cs" />
<Compile Include="MainWindow.xaml.cs">
Expand Down Expand Up @@ -104,6 +107,7 @@
<AppDesigner Include="Properties\" />
</ItemGroup>
<ItemGroup>
<None Include="app.manifest" />
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
Expand All @@ -119,4 +123,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>
Loading