Skip to content

Commit a961de4

Browse files
committed
feat: DPI scaling on GTK
1 parent 6245c41 commit a961de4

File tree

8 files changed

+402
-33
lines changed

8 files changed

+402
-33
lines changed

src/SamplesApp/SamplesApp.Skia.Gtk/SamplesApp.Skia.Gtk.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
<PropertyGroup>
1616
<OutputType>Exe</OutputType>
17+
<ApplicationManifest>app.manifest</ApplicationManifest>
1718
</PropertyGroup>
1819

1920
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
3+
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
4+
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
5+
<security>
6+
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
7+
<!-- UAC Manifest Options
8+
If you want to change the Windows User Account Control level replace the
9+
requestedExecutionLevel node with one of the following.
10+
11+
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
12+
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
13+
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
14+
15+
Specifying requestedExecutionLevel element will disable file and registry virtualization.
16+
Remove this element if your application requires this virtualization for backwards
17+
compatibility.
18+
-->
19+
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
20+
</requestedPrivileges>
21+
</security>
22+
</trustInfo>
23+
24+
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
25+
<application>
26+
<!-- A list of the Windows versions that this application has been tested on
27+
and is designed to work with. Uncomment the appropriate elements
28+
and Windows will automatically select the most compatible environment. -->
29+
30+
<!-- Windows Vista -->
31+
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
32+
33+
<!-- Windows 7 -->
34+
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
35+
36+
<!-- Windows 8 -->
37+
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
38+
39+
<!-- Windows 8.1 -->
40+
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
41+
42+
<!-- Windows 10 -->
43+
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
44+
45+
</application>
46+
</compatibility>
47+
48+
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
49+
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
50+
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
51+
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config.
52+
53+
Makes the application long-path aware. See https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
54+
<application xmlns="urn:schemas-microsoft-com:asm.v3">
55+
<windowsSettings>
56+
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
57+
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
58+
</windowsSettings>
59+
</application>
60+
61+
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
62+
<!--
63+
<dependency>
64+
<dependentAssembly>
65+
<assemblyIdentity
66+
type="win32"
67+
name="Microsoft.Windows.Common-Controls"
68+
version="6.0.0.0"
69+
processorArchitecture="*"
70+
publicKeyToken="6595b64144ccf1df"
71+
language="*"
72+
/>
73+
</dependentAssembly>
74+
</dependency>
75+
-->
76+
77+
</assembly>
Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#nullable enable
2+
using System;
23
using System.Runtime.InteropServices;
34
using Gtk;
5+
using Uno.UI.Runtime.Skia.Helpers.Dpi;
46
using Windows.Graphics.Display;
57

68
namespace Uno.UI.Runtime.Skia
@@ -9,11 +11,16 @@ internal class GtkDisplayInformationExtension : IDisplayInformationExtension
911
{
1012
private readonly DisplayInformation _displayInformation;
1113
private readonly Window _window;
14+
private readonly DpiHelper _dpiHelper;
15+
16+
private float? _dpi;
1217

1318
public GtkDisplayInformationExtension(object owner, Gtk.Window window)
1419
{
1520
_displayInformation = (DisplayInformation)owner;
1621
_window = window;
22+
_dpiHelper = new DpiHelper(_window);
23+
_dpiHelper.DpiChanged += OnDpiChanged;
1724
}
1825

1926
public DisplayOrientations CurrentOrientation => DisplayOrientations.Landscape;
@@ -22,38 +29,22 @@ public GtkDisplayInformationExtension(object owner, Gtk.Window window)
2229

2330
public uint ScreenWidthInRawPixels => (uint)_window.Display.GetMonitorAtWindow(_window.Window).Workarea.Width;
2431

25-
public float LogicalDpi
26-
{
27-
get
28-
{
29-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
30-
{
31-
return (float)_window.Screen.Resolution;
32-
}
33-
else
34-
{
35-
return (float)_window.Display.GetMonitorAtWindow(_window.Window).ScaleFactor * 96.0f;
36-
}
37-
}
38-
}
32+
public float LogicalDpi => _dpi ??= GetLogicalDpi();
3933

4034
public double RawPixelsPerViewPixel => LogicalDpi / 96.0f;
4135

4236
public ResolutionScale ResolutionScale => (ResolutionScale)(int)(RawPixelsPerViewPixel * 100.0);
4337

44-
public void StartDpiChanged()
45-
{
46-
_window.ScreenChanged += OnScreenChanged;
47-
}
38+
public void StartDpiChanged() { }
39+
40+
public void StopDpiChanged() { }
4841

49-
private void OnScreenChanged(object o, ScreenChangedArgs args)
42+
private void OnDpiChanged(object sender, EventArgs args)
5043
{
44+
_dpi = GetLogicalDpi();
5145
_displayInformation.NotifyDpiChanged();
5246
}
5347

54-
public void StopDpiChanged()
55-
{
56-
_window.ScreenChanged -= OnScreenChanged;
57-
}
48+
private float GetLogicalDpi() => _dpiHelper.GetLogicalDpi();
5849
}
5950
}

src/Uno.UI.Runtime.Skia.Gtk/GtkHost.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
using Uno.Foundation.Logging;
2525
using Windows.System.Profile.Internal;
2626
using Uno.UI.Runtime.Skia.GTK.System.Profile;
27+
using Uno.UI.Runtime.Skia.Helpers;
28+
using Uno.UI.Runtime.Skia.Helpers.Dpi;
2729

2830
namespace Uno.UI.Runtime.Skia
2931
{
@@ -53,6 +55,7 @@ public GtkHost(Func<WUX.Application> appBuilder, string[] args)
5355

5456
public void Run()
5557
{
58+
DpiHelper.Initialize();
5659
Gtk.Application.Init();
5760
SetupTheme();
5861

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Runtime.InteropServices;
4+
using Gtk;
5+
using Uno.Helpers;
6+
using Uno.UI.Runtime.Skia.Helpers.Windows;
7+
8+
namespace Uno.UI.Runtime.Skia.Helpers.Dpi
9+
{
10+
public class DpiHelper
11+
{
12+
private readonly Window _window;
13+
private readonly StartStopEventWrapper<EventHandler> _dpiChangedWrapper;
14+
15+
private float? _dpi;
16+
17+
public DpiHelper(Window window)
18+
{
19+
_window = window;
20+
_dpiChangedWrapper = new(StartDpiChanged, StopDpiChanged);
21+
}
22+
23+
public event EventHandler DpiChanged
24+
{
25+
add => _dpiChangedWrapper.AddHandler(value);
26+
remove => _dpiChangedWrapper.RemoveHandler(value);
27+
}
28+
29+
public static void Initialize()
30+
{
31+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
32+
{
33+
WindowsDpiHelper.SetupDpiAwareness();
34+
}
35+
}
36+
37+
public float GetLogicalDpi() => _dpi ??= GetNativeDpi();
38+
39+
private void StartDpiChanged()
40+
{
41+
_window.AddEvents((int)Gdk.EventType.Configure);
42+
_window.Screen.MonitorsChanged += OnMonitorsChanged;
43+
_window.ScreenChanged += OnScreenChanged;
44+
_window.SizeAllocated += OnWindowSizeAllocated;
45+
_window.ConfigureEvent += OnWindowConfigure;
46+
_window.Screen.SizeChanged += OnScreenSizeChanged;
47+
}
48+
49+
private void StopDpiChanged()
50+
{
51+
_window.Screen.MonitorsChanged -= OnMonitorsChanged;
52+
_window.ScreenChanged -= OnScreenChanged;
53+
_window.SizeAllocated -= OnWindowSizeAllocated;
54+
_window.ConfigureEvent -= OnWindowConfigure;
55+
_window.Screen.SizeChanged -= OnScreenSizeChanged;
56+
}
57+
58+
private void OnWindowConfigure(object o, ConfigureEventArgs args) => CheckDpiUpdate();
59+
60+
private void OnWindowSizeAllocated(object o, SizeAllocatedArgs args) => CheckDpiUpdate();
61+
62+
private void OnMonitorsChanged(object sender, EventArgs e) => CheckDpiUpdate();
63+
64+
private void OnScreenChanged(object o, ScreenChangedArgs args) => CheckDpiUpdate();
65+
66+
private void OnScreenSizeChanged(object sender, EventArgs e) => CheckDpiUpdate();
67+
68+
private void CheckDpiUpdate()
69+
{
70+
var dpi = GetNativeDpi();
71+
if (dpi != _dpi)
72+
{
73+
_dpi = dpi;
74+
_dpiChangedWrapper.Event?.Invoke(this, EventArgs.Empty);
75+
}
76+
}
77+
78+
private float GetNativeDpi()
79+
{
80+
float dpi;
81+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
82+
{
83+
if (_window.Window != null)
84+
{
85+
dpi = DpiUtilities.GetDpiForWindow(DpiUtilities.GetWin32Hwnd(_window.Window));
86+
}
87+
else
88+
{
89+
dpi = 96.0f; // GDK Window not initialized yet, default to 96 DPI.
90+
}
91+
}
92+
else
93+
{
94+
dpi = _window.Display.GetMonitorAtWindow(_window.Window).ScaleFactor * 96.0f;
95+
}
96+
return dpi;
97+
}
98+
}
99+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Runtime.InteropServices;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace Uno.UI.Runtime.Skia.Helpers
9+
{
10+
// Borrowed from https://stackoverflow.com/questions/43537990/wpf-clickonce-dpi-awareness-per-monitor-v2
11+
public class WindowsDpiHelper
12+
{
13+
private WindowsDpiHelper()
14+
{ }
15+
16+
[DllImport("user32.dll", SetLastError = true)]
17+
private static extern bool SetProcessDpiAwarenessContext(int dpiFlag);
18+
19+
[DllImport("SHCore.dll", SetLastError = true)]
20+
private static extern bool SetProcessDpiAwareness(PROCESS_DPI_AWARENESS awareness);
21+
22+
[DllImport("user32.dll")]
23+
private static extern bool SetProcessDPIAware();
24+
25+
private enum PROCESS_DPI_AWARENESS
26+
{
27+
Process_DPI_Unaware = 0,
28+
Process_System_DPI_Aware = 1,
29+
Process_Per_Monitor_DPI_Aware = 2
30+
}
31+
32+
private enum DPI_AWARENESS_CONTEXT
33+
{
34+
DPI_AWARENESS_CONTEXT_UNAWARE = 16,
35+
DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = 17,
36+
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = 18,
37+
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 34
38+
}
39+
40+
public static void SetupDpiAwareness()
41+
{
42+
if (Environment.OSVersion.Version >= new Version(6, 3, 0)) // Windows 8.1
43+
{
44+
if (Environment.OSVersion.Version >= new Version(10, 0, 15063)) // Windows 10 1703
45+
{
46+
SetProcessDpiAwarenessContext((int)DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
47+
}
48+
else
49+
{
50+
SetProcessDpiAwareness(PROCESS_DPI_AWARENESS.Process_Per_Monitor_DPI_Aware);
51+
}
52+
}
53+
else
54+
{
55+
SetProcessDPIAware();
56+
}
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)