Desktop Backends
.NET MAUI ships no first-party macOS (AppKit) or Linux head. dotnet/maui-labs provides both — a native AppKit backend and a GTK4 backend — and Shiny MAUI Hosting supports them.
| Backend | TFM | Shiny package |
|---|---|---|
| macOS (AppKit) | net10.0-macos |
Shiny.Extensions.MauiHosting |
| Linux (GTK4) | net10.0 |
Shiny.Extensions.MauiHosting.Linux |
Why Linux needs its own package
Section titled “Why Linux needs its own package”On both heads Microsoft.Maui.Essentials resolves its platform-neutral net10.0 asset, where every static
member throws NotImplementedInReferenceAssembly. The two backends fix that differently:
- macOS —
Microsoft.Maui.Platforms.MacOS.Essentialsreflects AppKit implementations into the private backing fields behindAppInfo.Current,DeviceInfo.Current,Browser.Defaultand the rest, so onceAddMacOSEssentials()has run the ordinary static APIs work.IAppSupportandIAppStoreneed no changes, which is why macOS lives in the main package. - Linux —
AddLinuxGtk4Essentials()only redirects five statics (Preferences,FilePicker,SecureStorage,Clipboard,MediaPicker).AppInfo.Version,DeviceInfo.ModelandBrowser.OpenAsyncstill throw. The Linux implementations therefore resolve the Essentials interfaces from the container, which is where the GTK4 backend registers them — a different implementation, hence a different package.
macOS (AppKit)
Section titled “macOS (AppKit)”-
Reference the backend alongside the hosting package:
<PropertyGroup><TargetFramework>net10.0-macos</TargetFramework><OutputType>Exe</OutputType><UseMaui>true</UseMaui><SingleProject>true</SingleProject><SupportedOSPlatformVersion>14.0</SupportedOSPlatformVersion></PropertyGroup><ItemGroup><PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" /><PackageReference Include="Microsoft.Maui.Platforms.MacOS" Version="*-*" /><PackageReference Include="Shiny.Extensions.MauiHosting" Version="*" /></ItemGroup>Microsoft.Maui.Platforms.MacOS.Essentialsarrives transitively — thenet10.0-macosasset ofShiny.Extensions.MauiHostingdepends on it. -
Write the entry point. There is no MAUI single-project generated
Mainon-macos:Main.cs using AppKit;public static class MainClass{static void Main(string[] args){NSApplication.Init();NSApplication.SharedApplication.Delegate = new MauiMacOSApp();NSApplication.Main(args);}}// MauiMacOSApp.csusing Foundation;using Microsoft.Maui.Platforms.MacOS.Platform;[Register("MauiMacOSApp")]public class MauiMacOSApp : MacOSMauiApplication{protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();} -
Register as usual:
using Microsoft.Maui.Platforms.MacOS.Hosting;using Shiny;var builder = MauiApp.CreateBuilder();builder.UseMauiAppMacOS<App>().AddInfrastructureModules(new MyModule()).AddAppSupport() // calls AddMacOSEssentials() for you.AddAppStore(opts => opts.AppleAppId = "1234567890").AddStartupService();return builder.Build();
MacOSMauiApplication sets IPlatformApplication.Current before it builds the MAUI app, so ShinyHost
and IMauiModule.Use behave exactly as on the other heads.
| Capability | AppKit behaviour |
|---|---|
IAppSupport device info |
MAUI Essentials, backed by the AppKit implementations |
| Culture / time-zone changes | NSNotificationCenter — NSLocale.CurrentLocaleDidChangeNotification and NSSystemTimeZoneDidChangeNotification |
| Orientation | DeviceDisplay.MainDisplayInfoChanged (NSApplication.DidChangeScreenParametersNotification). SetOrientation/ResetOrientation return false — AppKit windows don’t rotate |
IAppStore |
iTunes Search API scoped to entity=macSoftware; macappstore:// deep links; RequestReview uses StoreKit.AppStore.RequestReview against the key window’s NSViewController (macOS 14+) |
IStartupService |
SMAppService.MainApp — works with or without the Essentials wiring |
Linux (GTK4)
Section titled “Linux (GTK4)”There is no -linux TFM, so a GTK4 head is a plain net10.0 project sitting next to your shared MAUI code.
-
Reference the backend and the Linux hosting package:
<PropertyGroup><TargetFramework>net10.0</TargetFramework><OutputType>Exe</OutputType></PropertyGroup><ItemGroup><PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" /><PackageReference Include="Microsoft.Maui.Platforms.Linux.Gtk4" Version="*-*" /><PackageReference Include="Shiny.Extensions.MauiHosting.Linux" Version="*" /></ItemGroup>GTK 4.12+ has to be installed on the machine (
libgtk-4-devand friends). -
Write the entry point:
using Microsoft.Maui.Platforms.Linux.Gtk4.Platform;public class Program : GtkMauiApplication{protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();public static void Main(string[] args) => new Program().Run(args);} -
Register with the Linux extension methods:
using Microsoft.Maui.Platforms.Linux.Gtk4.Hosting;using Shiny;var builder = MauiApp.CreateBuilder();builder.UseMauiAppLinuxGtk4<App>().AddInfrastructureModules(new MyModule()).AddLinuxAppSupport() // IAppSupport over the GTK4 Essentials services.AddLinuxAppStore("org.example.MyApp") // IAppStore over Flatpak / Snap.AddStartupService(); // XDG autostart, from the base packagereturn builder.Build();
| Capability | Linux behaviour |
|---|---|
| Device info, browser, map | IAppInfo, IDeviceInfo, IBrowser and IMap registered by AddLinuxGtk4Essentials() (browser and map launch through xdg-open) |
| Time-zone changes | FileSystemWatcher on /etc/localtime, which systemd-timedated replaces when the zone changes. Falls back to a 30-second poll when the watch can’t be created |
| Culture changes | 30-second poll. A Linux locale switch only applies to the next login, so a running process never sees one from the OS |
| Orientation | Read from the GDK monitor geometry. SetOrientation/ResetOrientation return false |
IStartupService |
The base package’s ~/.config/autostart/{Identifier}.desktop entry — no Linux-specific registration needed |
App store lookups
Section titled “App store lookups”LinuxAppStore covers the two packaging formats that publish an updatable version to a store. Anything
else — a tarball, a distro package, dotnet run — has no store to ask, and GetCurrent() returns null.
| Format | Installed version | Published version |
|---|---|---|
| Flatpak | flatpak info <id> |
flatpak remote-info <origin> <id>, against the remote the app was installed from |
| Snap | snap list <name> |
snap info <name>, reading the channel the install tracks |
OpenStore() and OpenReviewPage() both hand appstream://<LinuxAppId> to xdg-open, which GNOME
Software, Plasma Discover and the Snap Store all handle — software centres show reviews on the app’s own
page, so there’s no separate review destination. RequestReview() falls back to OpenReviewPage(); no
Linux software centre offers an in-app prompt.
Inside a Flatpak sandbox the flatpak CLI lives on the host, so commands are forwarded with
flatpak-spawn --host. Every command failure is treated as “can’t tell” and surfaces as null or false
rather than an exception.
Samples
Section titled “Samples”The extensions repo has a runnable head for each backend —
samples/Sample.Maui.MacOS and samples/Sample.Maui.Linux — both driving the same shared sample app as
the mobile heads.


