Skip to content
Open
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
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
WASDK_APP_PROJ_PATH: '${{ github.workspace }}\samples\DesktopFlyouts.Wasdk.Sample.App\DesktopFlyouts.Wasdk.Sample.App.csproj'
UWP_APP_PROJ_PATH: '${{ github.workspace }}\samples\DesktopFlyouts.Uwp.Sample.App\DesktopFlyouts.Uwp.Sample.App.csproj'
UNO_APP_PROJ_PATH: '${{ github.workspace }}\samples\DesktopFlyouts.Uno.Sample.App\DesktopFlyouts.Uno.Sample.App.csproj'
UNO_LIB_PROJ_PATH: '${{ github.workspace }}/src/DesktopFlyouts.Uno/DesktopFlyouts.Uno.csproj'
X11_TEST_PROJ_PATH: '${{ github.workspace }}/tests/DesktopFlyouts.X11.Tests/DesktopFlyouts.X11.Tests.csproj'

jobs:
build-wasdk:
Expand Down Expand Up @@ -109,3 +111,33 @@

- name: Build the sample app
run: dotnet build $env:UNO_APP_PROJ_PATH -c $env:CONFIGURATION -a $env:PLATFORM --no-restore

build-test-uno-linux:
runs-on: ubuntu-latest

steps:
- name: Checkout the repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
9.0.x
10.0.x

- name: Restore the Uno library
run: dotnet restore "$UNO_LIB_PROJ_PATH"

- name: Build the Uno library on Linux
run: dotnet build "$UNO_LIB_PROJ_PATH" -c Release --no-restore

- name: Run X11 unit tests
run: dotnet test "$X11_TEST_PROJ_PATH" -c Release

- name: Check changed-file whitespace
if: github.event_name == 'pull_request'
shell: bash
run: git diff --check "$(git merge-base HEAD "origin/$GITHUB_BASE_REF")"..HEAD
Comment on lines +116 to +143
3 changes: 3 additions & 0 deletions DesktopFlyouts.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,8 @@
<Platform Solution="*|x86" Project="x86" />
</Project>
</Folder>
<Folder Name="/tests/">
<Project Path="tests/DesktopFlyouts.X11.Tests/DesktopFlyouts.X11.Tests.csproj" />
</Folder>

</Solution>
2 changes: 1 addition & 1 deletion Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<!-- General properties -->
<Configurations>Debug;Release</Configurations>
<Platforms>x86;x64;arm64</Platforms>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
<RuntimeIdentifiers Condition="'$(MSBuildProjectName)' != 'DesktopFlyouts.Uno' And '$(IsTestProject)' != 'true'">win-x86;win-x64;win-arm64</RuntimeIdentifiers>

</PropertyGroup>
</Project>
3 changes: 3 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.275" />
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.1839" Condition="'$(MSBuildProjectName)' == 'DesktopFlyouts.Wasdk' Or '$(MSBuildProjectName)' == 'DesktopFlyouts.Wasdk.Sample.App'" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
<PackageVersion Include="Tmds.DBus.Protocol" Version="0.94.2" />
<PackageVersion Include="Tmds.DBus.Generator" Version="0.94.2" />
<PackageVersion Include="Svg.Skia" Version="3.2.1" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>

</Project>
7 changes: 7 additions & 0 deletions src/DesktopFlyouts.Shared/DesktopFlyout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1319,7 +1319,11 @@ private void HostWindow_SystemSettingsChanged(object? sender, EventArgs e)
}

/// <inheritdoc/>
#if HAS_UNO
public new void Dispose()
#else
public void Dispose()
#endif
{
if (_disposed)
return;
Expand Down Expand Up @@ -1349,6 +1353,9 @@ public void Dispose()
_host?.Dispose();
IsOpen = false;

#if HAS_UNO
base.Dispose();
#endif
GC.SuppressFinalize(this);
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/DesktopFlyouts.Shared/DesktopFlyoutIsland.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ private void DesktopFlyoutIsland_LayoutUpdated(object? sender, object e)

private void UpdateTemplateSettings()
{
#if WASDK && !HAS_UNO
#if WASDK
TemplateSettings.BackdropCornerRadius = new(
GetBackdropCornerRadius(CornerRadius.TopLeft),
GetBackdropCornerRadius(CornerRadius.TopRight),
Expand All @@ -126,12 +126,13 @@ private void UpdateTemplateSettings()
#endif
}

#if WASDK && !HAS_UNO
#if WASDK
private static double GetBackdropCornerRadius(double cornerRadius)
{
return Math.Max(0D, cornerRadius > 0D ? cornerRadius - 1D : 0D);
}

#if !HAS_UNO
internal void UpdateOwnerBackdrop()
{
TemplateSettings.SystemBackdrop = _owner is not null && _owner.TryGetTarget(out var owner)
Expand All @@ -143,6 +144,7 @@ internal void ClearOwnerBackdrop()
{
TemplateSettings.SystemBackdrop = null;
}
#endif
#endif

private static bool AreClose(Size first, Size second)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,22 @@ namespace DesktopFlyouts
/// </remarks>
public partial class DesktopFlyoutIslandTemplateSettings : DependencyObject
{
#if WASDK && !HAS_UNO
#if WASDK
/// <summary>
/// Gets the corner radius used by backdrop elements inside the island template.
/// </summary>
/// <value>The owner island's corner radius reduced for its inner backdrop surface.</value>
[GeneratedDependencyProperty]
public partial CornerRadius BackdropCornerRadius { get; internal set; }

#if !HAS_UNO
/// <summary>
/// Gets the library-created system backdrop used by backdrop elements inside the island template.
/// </summary>
/// <value>The owning flyout's generated system backdrop for this island.</value>
[GeneratedDependencyProperty]
public partial SystemBackdrop? SystemBackdrop { get; internal set; }
#endif
#endif
}
}
4 changes: 3 additions & 1 deletion src/DesktopFlyouts.Shared/DesktopFlyouts.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
<Compile Include="$(MSBuildThisFileDirectory)MouseScrollEventReceivedEventArgs.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\WindowHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\WindowHelpers.X11.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\X11ScreenGeometry.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\X11WindowActivation.cs" />
<Compile Include="$(MSBuildThisFileDirectory)XamlIslandApplication.cs" />
<Compile Include="$(MSBuildThisFileDirectory)XamlIslandHostWindow.cs" />
<Compile Include="$(MSBuildThisFileDirectory)XamlIslandHostWindow.X11.cs" />
Expand All @@ -47,4 +49,4 @@
<AdditionalFiles Include="$(MSBuildThisFileDirectory)NativeMethods.json" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)NativeMethods.txt" />
</ItemGroup>
</Project>
</Project>
3 changes: 2 additions & 1 deletion src/DesktopFlyouts.Shared/DesktopMenuFlyout.Uno.cs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ private static double Clamp(double value, double min, double max)
}

/// <inheritdoc/>
public void Dispose()
public new void Dispose()
{
if (_disposed)
return;
Expand All @@ -332,6 +332,7 @@ public void Dispose()
_host?.Dispose();
IsOpen = false;

base.Dispose();
GC.SuppressFinalize(this);
}
}
Expand Down
1 change: 1 addition & 0 deletions src/DesktopFlyouts.Shared/DesktopMenuFlyout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@


#if UWP
using Windows.Graphics;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Markup;
Expand Down
180 changes: 114 additions & 66 deletions src/DesktopFlyouts.Shared/Helpers/GeneralHelpers.X11.cs
Original file line number Diff line number Diff line change
@@ -1,92 +1,140 @@
#if HAS_UNO
// Real implementation: detects system theme via D-Bus org.freedesktop.portal.Settings.
// Detects the system theme via D-Bus org.freedesktop.portal.Settings.
// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Settings.html

using System;
using System.Threading.Tasks;
using Tmds.DBus.Protocol;
using DesktopFlyouts.DBus;
using Tmds.DBus.Protocol;

namespace DesktopFlyouts;

namespace DesktopFlyouts
internal static class GeneralHelpers
{
internal static class GeneralHelpers
private const string Service = "org.freedesktop.portal.Desktop";
private const string ObjectPath = "/org/freedesktop/portal/desktop";

private static readonly object SyncRoot = new();
private static bool _isTaskbarLight;
private static Task? _initializationTask;
private static DBusConnection? _connection;
private static IDisposable? _settingsWatch;

static GeneralHelpers()
{
private const string Service = "org.freedesktop.portal.Desktop";
private const string ObjectPath = "/org/freedesktop/portal/desktop";
AppDomain.CurrentDomain.ProcessExit += (_, _) => DisposeResources();
}

internal static event EventHandler? SystemSettingsChanged;

internal static bool IsTaskbarLight()
{
ThrowHelper.ThrowIfNotLinux();

lock (SyncRoot)
{
_initializationTask ??= InitializeAsync();
return _isTaskbarLight;
}
}

private static bool? _isTaskbarLight;
private static readonly object _lock = new();
private static bool _initialized;
internal static bool IsTaskbarColorPrevalenceEnabled()
{
// Linux desktop environments do not have a Windows-style "accent color on taskbar" setting.
return false;
}

internal static bool IsTaskbarLight()
private static async Task InitializeAsync()
{
DBusConnection? connection = null;
IDisposable? settingsWatch = null;
try
{
ThrowHelper.ThrowIfNotLinux();
var sessionAddress = DBusAddress.Session;
if (sessionAddress is null)
return;

connection = new DBusConnection(sessionAddress);
await connection.ConnectAsync();

if (!_initialized)
var desktopService = new DBusService(connection, Service);
var settings = desktopService.CreateSettings(ObjectPath);
if (await settings.GetVersionAsync() < 2)
return;

var result = await settings.ReadOneAsync(
"org.freedesktop.appearance",
"color-scheme");
UpdateTheme(result.GetUInt32());

settingsWatch = await settings.WatchSettingChangedAsync(tuple =>
{
_ = InitAsync().ConfigureAwait(false);
}
if (tuple is { Namespace: "org.freedesktop.appearance", Key: "color-scheme" })
UpdateTheme(tuple.Value.GetUInt32());
});

lock (_lock)
lock (SyncRoot)
{
return _isTaskbarLight ?? false;
_connection = connection;
_settingsWatch = settingsWatch;
connection = null;
settingsWatch = null;
}
}

internal static bool IsTaskbarColorPrevalenceEnabled()
catch
{
// Linux desktop environments do not have a Windows-style "accent color on taskbar" setting.
return false;
// D-Bus or the portal is unavailable. Keep the dark fallback for this process.
}
finally
{
settingsWatch?.Dispose();
connection?.Dispose();
}
}

private static async Task InitAsync()
private static void UpdateTheme(uint colorScheme)
{
// 0 = no preference, 1 = dark, 2 = light.
var isLight = colorScheme != 1;
var changed = false;
lock (SyncRoot)
{
try
if (_isTaskbarLight != isLight)
{
var sessionAddress = DBusAddress.Session;
if (sessionAddress is null)
return;

var connection = new DBusConnection(sessionAddress);
await connection.ConnectAsync();

var desktopService = new DBusService(connection, Service);
var settings = desktopService.CreateSettings(ObjectPath);

var version = await settings.GetVersionAsync();
if (version < 2)
return;

var result = await settings.ReadOneAsync("org.freedesktop.appearance", "color-scheme");
var colorScheme = result.GetUInt32();
// 0 = no preference, 1 = dark, 2 = light
lock (_lock)
{
_isTaskbarLight = colorScheme != 1;
_initialized = true;
}

_ = settings.WatchSettingChangedAsync(tuple =>
{
if (tuple is { Namespace: "org.freedesktop.appearance", Key: "color-scheme" })
{
lock (_lock)
{
_isTaskbarLight = tuple.Value.GetUInt32() != 1;
}
}
});
}
catch
{
// D-Bus not available or portal not present; fall back to default (dark).
lock (_lock)
{
_isTaskbarLight = false;
_initialized = true;
}
_isTaskbarLight = isLight;
changed = true;
}
}

if (changed)
SystemSettingsChanged?.Invoke(null, EventArgs.Empty);
}

private static void DisposeResources()
{
IDisposable? settingsWatch;
DBusConnection? connection;
lock (SyncRoot)
{
settingsWatch = _settingsWatch;
connection = _connection;
_settingsWatch = null;
_connection = null;
}

try
{
settingsWatch?.Dispose();
}
catch
{
}

try
{
connection?.Dispose();
}
catch
{
}
}
}
#endif
Loading