diff --git a/Directory.Build.props b/Directory.Build.props index 5ce44b5859c3..c802b771d62d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,13 +5,16 @@ net10.0 $(BaseTargetFramework)-desktop + + Linux + $(DesktopTargetFramework) $(DesktopTargetFramework) - true + true false - true + true preview nullable diff --git a/Files.slnx b/Files.slnx index f55a46d960a1..00bc1588e772 100644 --- a/Files.slnx +++ b/Files.slnx @@ -44,6 +44,11 @@ + + + + + diff --git a/src/Files.App.Storage/Legacy/HomeFolder/LinuxHomeFolder.cs b/src/Files.App.Storage/Legacy/HomeFolder/DesktopHomeFolder.cs similarity index 92% rename from src/Files.App.Storage/Legacy/HomeFolder/LinuxHomeFolder.cs rename to src/Files.App.Storage/Legacy/HomeFolder/DesktopHomeFolder.cs index 1a9efb937cf3..a0093bc46b6d 100644 --- a/src/Files.App.Storage/Legacy/HomeFolder/LinuxHomeFolder.cs +++ b/src/Files.App.Storage/Legacy/HomeFolder/DesktopHomeFolder.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 using Files.Platform.Abstractions; -using Files.Platform.Abstractions.Gvfs; +using Files.Platform.Abstractions.Network; using Files.Platform.Abstractions.Recent; using Files.Platform.Abstractions.Volumes; using OwlCore.Storage.System.IO; @@ -10,7 +10,7 @@ namespace Files.App.Storage.Storables { - public sealed class LinuxHomeFolder : IHomeFolder + public sealed class DesktopHomeFolder : IHomeFolder { private readonly IUserDirectories directories; private readonly IVolumeService volumes; @@ -20,7 +20,7 @@ public sealed class LinuxHomeFolder : IHomeFolder public string Id => "Home"; public string Name => "Home"; - public LinuxHomeFolder(IUserDirectories directories, IVolumeService volumes, IRecentFilesStore recentFiles, INetworkLocationService network) + public DesktopHomeFolder(IUserDirectories directories, IVolumeService volumes, IRecentFilesStore recentFiles, INetworkLocationService network) { this.directories = directories; this.volumes = volumes; diff --git a/src/Files.App/Actions/Open/OpenSettingsFileAction.cs b/src/Files.App/Actions/Open/OpenSettingsFileAction.cs index 94007495e874..a832a4fc7eeb 100644 --- a/src/Files.App/Actions/Open/OpenSettingsFileAction.cs +++ b/src/Files.App/Actions/Open/OpenSettingsFileAction.cs @@ -32,7 +32,7 @@ public async Task ExecuteAsync(object? parameter = null) try { var settingsJsonFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri($"ms-appdata:///local/{Constants.LocalSettings.SettingsFolderName}/{Constants.LocalSettings.UserSettingsFileName}")); - await NavigationHelpers.OpenFileLinuxAsync(settingsJsonFile.Path); + await NavigationHelpers.OpenFileDesktopAsync(settingsJsonFile.Path); } catch (Exception ex) { diff --git a/src/Files.App/Actions/Open/OpenTerminalAction.cs b/src/Files.App/Actions/Open/OpenTerminalAction.cs index 13d9113ab050..5e602ffbf4eb 100644 --- a/src/Files.App/Actions/Open/OpenTerminalAction.cs +++ b/src/Files.App/Actions/Open/OpenTerminalAction.cs @@ -13,7 +13,7 @@ internal partial class OpenTerminalAction : ObservableObject, IAction public virtual string Label - => Strings.OpenTerminalLinux.GetLocalizedResource(); + => Strings.OpenTerminalDesktop.GetLocalizedResource(); public virtual string Description => Strings.OpenTerminalDescription.GetLocalizedResource(); @@ -46,10 +46,10 @@ public Task ExecuteAsync(object? parameter = null) if (paths.Length is 0) return Task.CompletedTask; - return OpenLinuxTerminalsAsync(paths); + return OpenDesktopTerminalsAsync(paths); } - private static async Task OpenLinuxTerminalsAsync(string[] paths) + private static async Task OpenDesktopTerminalsAsync(string[] paths) { var launcher = Ioc.Default.GetRequiredService(); foreach (var path in paths) diff --git a/src/Files.App/Actions/Open/OpenTerminalAsAdminAction.cs b/src/Files.App/Actions/Open/OpenTerminalAsAdminAction.cs index a367a6a2832f..da3874f9758a 100644 --- a/src/Files.App/Actions/Open/OpenTerminalAsAdminAction.cs +++ b/src/Files.App/Actions/Open/OpenTerminalAsAdminAction.cs @@ -7,7 +7,7 @@ namespace Files.App.Actions internal sealed partial class OpenTerminalAsAdminAction : OpenTerminalAction { public override string Label - => Strings.OpenTerminalAsAdminLinux.GetLocalizedResource(); + => Strings.OpenTerminalAsAdminDesktop.GetLocalizedResource(); public override string Description => Strings.OpenTerminalAsAdminDescription.GetLocalizedResource(); diff --git a/src/Files.App/Actions/Open/OpenTerminalFromHomeAction.cs b/src/Files.App/Actions/Open/OpenTerminalFromHomeAction.cs index bce11d9f8ca8..510a61d45637 100644 --- a/src/Files.App/Actions/Open/OpenTerminalFromHomeAction.cs +++ b/src/Files.App/Actions/Open/OpenTerminalFromHomeAction.cs @@ -9,7 +9,7 @@ internal sealed partial class OpenTerminalFromHomeAction : OpenTerminalAction private IHomePageContext HomePageContext { get; } = Ioc.Default.GetRequiredService(); public override string Label - => Strings.OpenTerminalLinux.GetLocalizedResource(); + => Strings.OpenTerminalDesktop.GetLocalizedResource(); public override string Description => Strings.OpenTerminalDescription.GetLocalizedResource(); diff --git a/src/Files.App/Actions/Open/OpenTerminalFromSidebarAction.cs b/src/Files.App/Actions/Open/OpenTerminalFromSidebarAction.cs index 56c4a16facf5..a06c0eeb8174 100644 --- a/src/Files.App/Actions/Open/OpenTerminalFromSidebarAction.cs +++ b/src/Files.App/Actions/Open/OpenTerminalFromSidebarAction.cs @@ -9,7 +9,7 @@ internal sealed partial class OpenTerminalFromSidebarAction : OpenTerminalAction private ISidebarContext SidebarContext { get; } = Ioc.Default.GetRequiredService(); public override string Label - => Strings.OpenTerminalLinux.GetLocalizedResource(); + => Strings.OpenTerminalDesktop.GetLocalizedResource(); public override string Description => Strings.OpenTerminalDescription.GetLocalizedResource(); diff --git a/src/Files.App/App.xaml b/src/Files.App/App.xaml index f349ebdf88c6..ba18ac25fd0c 100644 --- a/src/Files.App/App.xaml +++ b/src/Files.App/App.xaml @@ -181,7 +181,7 @@ - + diff --git a/src/Files.App/Constants.cs b/src/Files.App/Constants.cs index 2b7dd68f1cae..6b0598399c2c 100644 --- a/src/Files.App/Constants.cs +++ b/src/Files.App/Constants.cs @@ -208,13 +208,12 @@ public static class DragAndDrop public static class UserEnvironmentPaths { - private static readonly Files.Platform.Linux.LinuxUserDirectories _userDirectories = new(); + private static readonly IUserDirectories _userDirectories = DesktopPlatform.CreateUserDirectories(); public static readonly string DesktopPath = _userDirectories.Desktop; public static readonly string DownloadsPath = _userDirectories.Downloads; - // XDG data home (~/.local/share) public static readonly string LocalAppDataPath = Environment.GetEnvironmentVariable("XDG_DATA_HOME") is { Length: > 0 } dataHome && System.IO.Path.IsPathRooted(dataHome) ? dataHome : System.IO.Path.Combine(_userDirectories.Home, ".local", "share"); diff --git a/src/Files.App/Data/Commands/HotKey/HotKeyHelpers.Linux.cs b/src/Files.App/Data/Commands/HotKey/HotKeyHelpers.Desktop.cs similarity index 100% rename from src/Files.App/Data/Commands/HotKey/HotKeyHelpers.Linux.cs rename to src/Files.App/Data/Commands/HotKey/HotKeyHelpers.Desktop.cs diff --git a/src/Files.App/Data/Contexts/HomePage/HomePageContext.cs b/src/Files.App/Data/Contexts/HomePage/HomePageContext.cs index 20145cb3bbec..695e50b9098c 100644 --- a/src/Files.App/Data/Contexts/HomePage/HomePageContext.cs +++ b/src/Files.App/Data/Contexts/HomePage/HomePageContext.cs @@ -16,11 +16,11 @@ public sealed partial class HomePageContext : ObservableObject, IHomePageContext private static IHomeFolder CreateHomeFolder() { - return new LinuxHomeFolder( + return new DesktopHomeFolder( Ioc.Default.GetRequiredService(), Ioc.Default.GetRequiredService(), Ioc.Default.GetRequiredService(), - Ioc.Default.GetRequiredService()); + Ioc.Default.GetRequiredService()); } private WidgetCardItem? rightClickedItem = null; diff --git a/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.Linux.cs b/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.Desktop.cs similarity index 93% rename from src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.Linux.cs rename to src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.Desktop.cs index 9a99b13d307b..34d51a64833c 100644 --- a/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.Linux.cs +++ b/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.Desktop.cs @@ -4,8 +4,8 @@ using Files.Platform.Abstractions; using Files.Platform.Abstractions.Launching; using Files.Platform.Abstractions.Mime; -using Files.Platform.Linux.Launching; -using Files.Platform.Linux.Mime; +using Files.Platform.Posix.Launching; +using Files.Platform.Posix.Mime; using Files.App.ViewModels.Layouts; using Microsoft.Extensions.Logging; @@ -31,7 +31,7 @@ public static Task> GetItemContextCommandsW }, cancellationToken); } - private static Task> GetLinuxItemContextCommandsAsync( + private static Task> GetDesktopItemContextCommandsAsync( string? workingDir, List selectedItems, bool shiftPressed, bool showOpenMenu, CancellationToken cancellationToken) { var shellItems = ShellContextFlyoutFactory.GetShellContextmenuAsync(shiftPressed: shiftPressed, showOpenMenu: showOpenMenu, @@ -41,7 +41,7 @@ private static Task> GetLinuxItemContextCom return Task.Run(async () => { var items = await shellItems.ConfigureAwait(false); - if (!includeServiceMenus || !targets.All(LinuxServiceMenuService.IsLocalFileSystemTarget)) + if (!includeServiceMenus || !targets.All(PosixServiceMenuService.IsLocalFileSystemTarget)) return items; try @@ -57,7 +57,7 @@ private static Task> GetLinuxItemContextCom Text = DisplaySanitizer.Field(action.Application.Name), Glyph = "\xE756", ShowInSearchPage = true, - Command = new AsyncRelayCommand(() => NavigationHelpers.RunServiceMenuLinuxAsync(captured, targets)), + Command = new AsyncRelayCommand(() => NavigationHelpers.RunServiceMenuDesktopAsync(captured, targets)), }; if (string.IsNullOrWhiteSpace(action.Submenu)) items.Add(item); @@ -88,7 +88,7 @@ private static Task> GetLinuxItemContextCom /// /// Builds the "New" submenu from : Folder, Text Document and the files in ~/Templates. /// - internal static List GetLinuxNewItemItems(bool canCreateFileInPage, ContextMenuCommandSnapshot? createFolder = null, CancellationToken cancellationToken = default) + internal static List GetDesktopNewItemItems(bool canCreateFileInPage, ContextMenuCommandSnapshot? createFolder = null, CancellationToken cancellationToken = default) { var list = new List() { diff --git a/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.RootActions.cs b/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.RootActions.cs index 5058fb9648cd..7e915263623c 100644 --- a/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.RootActions.cs +++ b/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.RootActions.cs @@ -47,7 +47,7 @@ internal static ContextMenuFlyoutItemViewModel GetRootActionsItem(List RootActionsHelper.OpenTerminalAsync(terminalTarget)), }); } diff --git a/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.cs b/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.cs index 703e8746a110..d4fdf05a6b91 100644 --- a/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.cs +++ b/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.cs @@ -33,7 +33,7 @@ public static List GetItemContextCommandsWithout public static Task> GetItemContextShellCommandsAsync(string? workingDir, List selectedItems, bool shiftPressed, bool showOpenMenu, CancellationToken cancellationToken) { - return GetLinuxItemContextCommandsAsync(workingDir, selectedItems, shiftPressed, showOpenMenu, cancellationToken); + return GetDesktopItemContextCommandsAsync(workingDir, selectedItems, shiftPressed, showOpenMenu, cancellationToken); } public static List Filter(List items, List selectedItems, bool shiftPressed, CurrentInstanceViewModel currentInstanceViewModel, bool removeOverflowMenu = true) @@ -370,7 +370,7 @@ private static List BuildBaseItemMenuItems(Conte ThemedIconStyle = state.Commands[Commands.AddItem].Glyph.ThemedIconStyle }, Text = state.Commands[Commands.AddItem].Label, - Items = GetLinuxNewItemItems(state.CanCreateFileInPage, state.Commands[Commands.CreateFolder], cancellationToken), + Items = GetDesktopNewItemItems(state.CanCreateFileInPage, state.Commands[Commands.CreateFolder], cancellationToken), ShowItem = !state.ItemsSelected, ShowInFtpPage = true }, @@ -878,7 +878,7 @@ private static ContextMenuBuildState CaptureMenuState(BaseLayoutViewModel comman public static List GetNewItemItems(BaseLayoutViewModel commandsViewModel, bool canCreateFileInPage) { - return GetLinuxNewItemItems(canCreateFileInPage); + return GetDesktopNewItemItems(canCreateFileInPage); } } } diff --git a/src/Files.App/Data/Factories/PropertiesNavigationItemsFactory.cs b/src/Files.App/Data/Factories/PropertiesNavigationItemsFactory.cs index 138c3b65796b..8d113ad848e9 100644 --- a/src/Files.App/Data/Factories/PropertiesNavigationItemsFactory.cs +++ b/src/Files.App/Data/Factories/PropertiesNavigationItemsFactory.cs @@ -66,7 +66,7 @@ public static ObservableCollection Initialize(object i { var isShortcut = listedItem.IsShortcut; if (OperatingSystem.IsLinux() && !isShortcut) - isShortcut = LinuxShortcutHelper.IsShortcutLike(listedItem.ItemPath); + isShortcut = DesktopShortcutHelper.IsShortcutLike(listedItem.ItemPath); var isLibrary = listedItem.IsLibrary; var fileExt = listedItem.FileExtension; var isFolder = listedItem.PrimaryItemAttribute == Windows.Storage.StorageItemTypes.Folder; diff --git a/src/Files.App/Data/Factories/ShellContextFlyoutFactory.Linux.cs b/src/Files.App/Data/Factories/ShellContextFlyoutFactory.Desktop.cs similarity index 90% rename from src/Files.App/Data/Factories/ShellContextFlyoutFactory.Linux.cs rename to src/Files.App/Data/Factories/ShellContextFlyoutFactory.Desktop.cs index 0e178b806e70..87e857ebea24 100644 --- a/src/Files.App/Data/Factories/ShellContextFlyoutFactory.Linux.cs +++ b/src/Files.App/Data/Factories/ShellContextFlyoutFactory.Desktop.cs @@ -14,7 +14,7 @@ public static partial class ShellContextFlyoutFactory /// Linux has no shell extension menu. The only entry produced is the "Open with" submenu for a single file, /// shaped like the Windows "openas" entry so the existing layout code swaps it in. /// - private static Task> GetLinuxContextMenuAsync(List? selectedItems, CancellationToken cancellationToken) + private static Task> GetDesktopContextMenuAsync(List? selectedItems, CancellationToken cancellationToken) { var path = selectedItems is { Count: 1 } && selectedItems[0].PrimaryItemAttribute == Windows.Storage.StorageItemTypes.File @@ -61,7 +61,7 @@ private static async Task LoadOpenWithItemsAsync(ContextMenuFlyoutItemViewModel items.Add(new ContextMenuFlyoutItemViewModel { Text = captured.Id == defaultApp?.Id ? $"{captured.Name} ({Strings.Default.GetLocalizedResource()})" : captured.Name, - BitmapIcon = await LinuxAppIcons.GetBitmapAsync(captured), + BitmapIcon = await DesktopAppIcons.GetBitmapAsync(captured), Command = new AsyncRelayCommand(() => launcher.OpenWithAsync(captured, [path])), }); } @@ -72,7 +72,7 @@ private static async Task LoadOpenWithItemsAsync(ContextMenuFlyoutItemViewModel items.Add(new ContextMenuFlyoutItemViewModel { Text = Strings.ChooseAnotherApp.GetLocalizedResource() + "…", - Command = new AsyncRelayCommand(() => LinuxOpenWithDialog.ShowAsync(path)), + Command = new AsyncRelayCommand(() => DesktopOpenWithDialog.ShowAsync(path)), }); } } diff --git a/src/Files.App/Data/Factories/ShellContextFlyoutHelper.cs b/src/Files.App/Data/Factories/ShellContextFlyoutHelper.cs index 19e2c91743b6..b1c83c1adfef 100644 --- a/src/Files.App/Data/Factories/ShellContextFlyoutHelper.cs +++ b/src/Files.App/Data/Factories/ShellContextFlyoutHelper.cs @@ -24,7 +24,7 @@ public static partial class ShellContextFlyoutFactory public static async Task> GetShellContextmenuAsync(bool showOpenMenu, bool shiftPressed, string? workingDirectory, List? selectedItems, CancellationToken cancellationToken) { // No shell extensions on Linux: skip the Win32 shell menu entirely (no COM, no delay) - return await GetLinuxContextMenuAsync(selectedItems, cancellationToken); + return await GetDesktopContextMenuAsync(selectedItems, cancellationToken); } diff --git a/src/Files.App/Data/Items/DriveItem.cs b/src/Files.App/Data/Items/DriveItem.cs index 30c5fcabf67c..a1bb213aa2e4 100644 --- a/src/Files.App/Data/Items/DriveItem.cs +++ b/src/Files.App/Data/Items/DriveItem.cs @@ -257,7 +257,7 @@ public static async Task CreateFromPropertiesAsync(StorageFolder root // The label already is the display name (volume label, " Volume" or "File System") item.Text = !string.IsNullOrWhiteSpace(label) ? label : root.DisplayName; if (string.IsNullOrWhiteSpace(item.Text) && root.Path is "/") - item.Text = Strings.LinuxFileSystemRoot.GetLocalizedResource(); + item.Text = Strings.DesktopFileSystemRoot.GetLocalizedResource(); item.Type = type; item.MenuOptions = new ContextMenuOptions { @@ -312,7 +312,7 @@ public async Task UpdatePropertiesAsync() var info = await Task.Run(() => { - if (Files.Platform.Linux.Volumes.HeadlessDriveFixture.Find(Path) is { } synthetic) + if (Files.Platform.Posix.Volumes.HeadlessDriveFixture.Find(Path) is { } synthetic) return (Total: (long)synthetic.TotalBytes, Free: (long)synthetic.FreeBytes, Format: synthetic.FileSystem); var driveInfo = new SystemIO.DriveInfo(Path ?? throw new InvalidOperationException("The drive path has not been initialized.")); diff --git a/src/Files.App/Data/Models/SelectedItemsPropertiesViewModel.cs b/src/Files.App/Data/Models/SelectedItemsPropertiesViewModel.cs index 1eab604f6c57..6ede6ccd572f 100644 --- a/src/Files.App/Data/Models/SelectedItemsPropertiesViewModel.cs +++ b/src/Files.App/Data/Models/SelectedItemsPropertiesViewModel.cs @@ -570,18 +570,18 @@ public bool IsSelectedItemShortcut } } - private bool isLinuxOpenTargetAvailable; - public bool IsLinuxOpenTargetAvailable + private bool isDesktopOpenTargetAvailable; + public bool IsDesktopOpenTargetAvailable { - get => isLinuxOpenTargetAvailable; + get => isDesktopOpenTargetAvailable; set { - if (SetProperty(ref isLinuxOpenTargetAvailable, value)) + if (SetProperty(ref isDesktopOpenTargetAvailable, value)) OnPropertyChanged(nameof(IsOpenTargetVisible)); } } - public bool IsOpenTargetVisible => IsSelectedItemShortcut || IsLinuxOpenTargetAvailable; + public bool IsOpenTargetVisible => IsSelectedItemShortcut || IsDesktopOpenTargetAvailable; private string? desktopEntryName; public string? DesktopEntryName diff --git a/src/Files.App/Dialogs/LinuxOpenWithDialog.cs b/src/Files.App/Dialogs/DesktopOpenWithDialog.cs similarity index 93% rename from src/Files.App/Dialogs/LinuxOpenWithDialog.cs rename to src/Files.App/Dialogs/DesktopOpenWithDialog.cs index 9d23da40eee5..2b8e38cef218 100644 --- a/src/Files.App/Dialogs/LinuxOpenWithDialog.cs +++ b/src/Files.App/Dialogs/DesktopOpenWithDialog.cs @@ -12,7 +12,7 @@ namespace Files.App.Dialogs /// /// Lets the user choose an installed application to open a file with, optionally making it the default. /// - internal static class LinuxOpenWithDialog + internal static class DesktopOpenWithDialog { public static async Task ShowAsync(string path) { @@ -36,7 +36,7 @@ public static async Task ShowAsync(string path) _ = LoadIconAsync(image, app); } - var always = new CheckBox { Content = Strings.LinuxAlwaysUseApp.GetLocalizedResource() }; + var always = new CheckBox { Content = Strings.DesktopAlwaysUseApp.GetLocalizedResource() }; var content = new StackPanel { Spacing = 12, MinWidth = 320 }; content.Children.Add(new TextBlock { Text = Path.GetFileName(path), TextTrimming = TextTrimming.CharacterEllipsis }); content.Children.Add(list); @@ -67,7 +67,7 @@ private static async Task LoadIconAsync(Image image, DesktopApplication app) { try { - image.Source = await LinuxAppIcons.GetBitmapAsync(app, 20); + image.Source = await DesktopAppIcons.GetBitmapAsync(app, 20); } catch (Exception ex) when (ex is not OperationCanceledException) { diff --git a/src/Files.App/Files.App.csproj b/src/Files.App/Files.App.csproj index 578b1e23fcd4..0b467e87ee36 100644 --- a/src/Files.App/Files.App.csproj +++ b/src/Files.App/Files.App.csproj @@ -88,11 +88,15 @@ - + + + + + $(DefineConstants);UNO_WAYLAND @@ -102,7 +106,7 @@ - + @@ -115,6 +119,21 @@ + + + + + + + + + + + + + + + $(DefaultItemExcludes);Assets/Libraries/**;Assets/FilesOpenDialog/**;Assets/WSL/**;Assets/tile-0-*.png;Assets/AppTiles/**/*.png @@ -128,9 +147,9 @@ - + diff --git a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs index 9cd693844b82..3c9ab8800c4e 100644 --- a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs +++ b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs @@ -316,9 +316,7 @@ public static IServiceProvider ConfigureHost(AppModel appModel) services // Platform services - .AddLinuxPlatform() - .AddSingleton(_ => Program.SingleInstance ?? new Files.Platform.Linux.Instance.LinuxSingleInstanceService()) - .AddLinuxSystemIntegration() + .AddDesktopPlatform() // Settings services .AddSingleton() .AddSingleton(sp => new AppearanceSettingsService(((UserSettingsService)sp.GetRequiredService()).GetSharingContext())) diff --git a/src/Files.App/Helpers/Application/AppToastNotificationHelper.Linux.cs b/src/Files.App/Helpers/Application/AppToastNotificationHelper.Desktop.cs similarity index 100% rename from src/Files.App/Helpers/Application/AppToastNotificationHelper.Linux.cs rename to src/Files.App/Helpers/Application/AppToastNotificationHelper.Desktop.cs diff --git a/src/Files.App/Helpers/CredentialsHelpers.cs b/src/Files.App/Helpers/CredentialsHelpers.cs index ba48ea348d84..058f909143f0 100644 --- a/src/Files.App/Helpers/CredentialsHelpers.cs +++ b/src/Files.App/Helpers/CredentialsHelpers.cs @@ -5,12 +5,12 @@ namespace Files.App.Helpers { internal sealed class CredentialsHelpers { - // Linux: the Secret Service (libsecret backend) through ISecretStore; PasswordVault does not exist there - private static ISecretStore? LinuxStore => OperatingSystem.IsLinux() ? Ioc.Default.GetService() : null; + // Desktop heads: the platform secret store (Secret Service on Linux) through ISecretStore; PasswordVault does not exist there + private static ISecretStore? PlatformStore => OperatingSystem.IsWindows() ? null : Ioc.Default.GetService(); public static void SavePassword(string resourceName, string username, string password) { - if (LinuxStore is { } store) + if (PlatformStore is { } store) { store.Save(resourceName, username, password); return; @@ -25,7 +25,7 @@ public static void SavePassword(string resourceName, string username, string pas // Remove saved credentials from the vault public static void DeleteSavedPassword(string resourceName, string username) { - if (LinuxStore is { } store) + if (PlatformStore is { } store) { store.Delete(resourceName, username); return; @@ -39,7 +39,7 @@ public static void DeleteSavedPassword(string resourceName, string username) public static string GetPassword(string resourceName, string username) { - if (LinuxStore is { } store) + if (PlatformStore is { } store) return store.Get(resourceName, username) ?? string.Empty; try diff --git a/src/Files.App/Helpers/Layout/LayoutPreferencesDatabase.Linux.cs b/src/Files.App/Helpers/Layout/LayoutPreferencesDatabase.Desktop.cs similarity index 100% rename from src/Files.App/Helpers/Layout/LayoutPreferencesDatabase.Linux.cs rename to src/Files.App/Helpers/Layout/LayoutPreferencesDatabase.Desktop.cs diff --git a/src/Files.App/Helpers/MenuFlyout/LinuxAppIcons.cs b/src/Files.App/Helpers/MenuFlyout/DesktopAppIcons.cs similarity index 96% rename from src/Files.App/Helpers/MenuFlyout/LinuxAppIcons.cs rename to src/Files.App/Helpers/MenuFlyout/DesktopAppIcons.cs index ab1aed0f5acc..2b5750e9d2c8 100644 --- a/src/Files.App/Helpers/MenuFlyout/LinuxAppIcons.cs +++ b/src/Files.App/Helpers/MenuFlyout/DesktopAppIcons.cs @@ -3,7 +3,7 @@ using Files.Platform.Abstractions.Icons; using Files.Platform.Abstractions.Mime; -using Files.Platform.Linux.Icons; +using Files.Platform.Posix.Icons; using Microsoft.UI.Xaml.Media.Imaging; using System.Collections.Concurrent; using System.IO; @@ -13,7 +13,7 @@ namespace Files.App.Helpers /// /// Loads application icons (from the icon theme) for menus and dialogs. /// - internal static class LinuxAppIcons + internal static class DesktopAppIcons { private static readonly ConcurrentDictionary<(string Name, uint Size), byte[]?> cache = new(); diff --git a/src/Files.App/Helpers/Navigation/ArchiveOpenTempStore.Linux.cs b/src/Files.App/Helpers/Navigation/ArchiveOpenTempStore.Desktop.cs similarity index 100% rename from src/Files.App/Helpers/Navigation/ArchiveOpenTempStore.Linux.cs rename to src/Files.App/Helpers/Navigation/ArchiveOpenTempStore.Desktop.cs diff --git a/src/Files.App/Helpers/Navigation/NavigationHelpers.Linux.cs b/src/Files.App/Helpers/Navigation/NavigationHelpers.Desktop.cs similarity index 68% rename from src/Files.App/Helpers/Navigation/NavigationHelpers.Linux.cs rename to src/Files.App/Helpers/Navigation/NavigationHelpers.Desktop.cs index 951c5746066f..df71f4ef67de 100644 --- a/src/Files.App/Helpers/Navigation/NavigationHelpers.Linux.cs +++ b/src/Files.App/Helpers/Navigation/NavigationHelpers.Desktop.cs @@ -5,8 +5,8 @@ using Files.Platform.Abstractions.Archives; using Files.Platform.Abstractions.Launching; using Files.Platform.Abstractions.Mime; -using Files.Platform.Linux.Launching; -using Files.Platform.Linux.Mime; +using Files.Platform.Posix.Launching; +using Files.Platform.Posix.Mime; using Files.Shared.Helpers; using Microsoft.Extensions.Logging; using System.Globalization; @@ -20,14 +20,14 @@ namespace Files.App.Helpers /// public static partial class NavigationHelpers { - private static ILauncherService LinuxLauncher => Ioc.Default.GetRequiredService(); + private static ILauncherService DesktopLauncher => Ioc.Default.GetRequiredService(); // A double click can raise the open command twice; a second navigation to the archive would need two Back presses private static readonly InFlightOpenGate archiveOpenGate = new(); - private static IMimeTypeService LinuxMimeTypes => Ioc.Default.GetRequiredService(); + private static IMimeTypeService DesktopMimeTypes => Ioc.Default.GetRequiredService(); - private static async Task OpenPathLinuxAsync(string path, IShellPage associatedInstance, bool openViaApplicationPicker, IEnumerable? selectItems, bool forceOpenInNewTab) + private static async Task OpenPathDesktopAsync(string path, IShellPage associatedInstance, bool openViaApplicationPicker, IEnumerable? selectItems, bool forceOpenInNewTab) { try { @@ -66,7 +66,7 @@ private static async Task OpenPathLinuxAsync(string path, IShellPage assoc if (isArchiveRoot || resolved.Item is not OwlCore.Storage.IFile member) { - await DialogDisplayHelper.ShowDialogAsync(Strings.LinuxOpenFailedTitle.GetLocalizedResource(), Strings.LinuxOpenFailedText.GetLocalizedFormatResource(DisplaySanitizer.Field(path))); + await DialogDisplayHelper.ShowDialogAsync(Strings.DesktopOpenFailedTitle.GetLocalizedResource(), Strings.DesktopOpenFailedText.GetLocalizedFormatResource(DisplaySanitizer.Field(path))); return false; } @@ -76,9 +76,9 @@ private static async Task OpenPathLinuxAsync(string path, IShellPage assoc if (temporaryPath is null) return false; - var openedMember = await OpenFileLinuxAsync(temporaryPath, openViaApplicationPicker); + var openedMember = await OpenFileDesktopAsync(temporaryPath, openViaApplicationPicker); if (!openedMember) - await DialogDisplayHelper.ShowDialogAsync(Strings.LinuxOpenFailedTitle.GetLocalizedResource(), Strings.LinuxOpenFailedText.GetLocalizedFormatResource(DisplaySanitizer.Field(path))); + await DialogDisplayHelper.ShowDialogAsync(Strings.DesktopOpenFailedTitle.GetLocalizedResource(), Strings.DesktopOpenFailedText.GetLocalizedFormatResource(DisplaySanitizer.Field(path))); return openedMember; } @@ -91,9 +91,9 @@ private static async Task OpenPathLinuxAsync(string path, IShellPage assoc return false; } - var opened = await OpenFileLinuxAsync(path, openViaApplicationPicker); + var opened = await OpenFileDesktopAsync(path, openViaApplicationPicker); if (!opened) - await DialogDisplayHelper.ShowDialogAsync(Strings.LinuxOpenFailedTitle.GetLocalizedResource(), Strings.LinuxOpenFailedText.GetLocalizedFormatResource(DisplaySanitizer.Field(path))); + await DialogDisplayHelper.ShowDialogAsync(Strings.DesktopOpenFailedTitle.GetLocalizedResource(), Strings.DesktopOpenFailedText.GetLocalizedFormatResource(DisplaySanitizer.Field(path))); return opened; } @@ -135,22 +135,22 @@ private static async Task OpenPathLinuxAsync(string path, IShellPage assoc catch (Exception ex) { App.Logger.LogWarning(ex, "Failed to extract archive member {Name}", DisplaySanitizer.Field(name)); - await DialogDisplayHelper.ShowDialogAsync(Strings.LinuxOpenFailedTitle.GetLocalizedResource(), Strings.LinuxOpenFailedText.GetLocalizedFormatResource(DisplaySanitizer.Field(name))); + await DialogDisplayHelper.ShowDialogAsync(Strings.DesktopOpenFailedTitle.GetLocalizedResource(), Strings.DesktopOpenFailedText.GetLocalizedFormatResource(DisplaySanitizer.Field(name))); return null; } } internal static async Task OpenPreviewFileAsync(string path) { - if (!await OpenFilesLinuxAsync([path])) - await DialogDisplayHelper.ShowDialogAsync(Strings.LinuxOpenFailedTitle.GetLocalizedResource(), Strings.LinuxOpenFailedText.GetLocalizedFormatResource(DisplaySanitizer.Field(path))); + if (!await OpenFilesDesktopAsync([path])) + await DialogDisplayHelper.ShowDialogAsync(Strings.DesktopOpenFailedTitle.GetLocalizedResource(), Strings.DesktopOpenFailedText.GetLocalizedFormatResource(DisplaySanitizer.Field(path))); } /// /// Opens several files. Files that need a gate (executables, launchers) are opened one by one through - /// ; plain documents are passed to their default applications together. + /// ; plain documents are passed to their default applications together. /// - private static async Task OpenFilesLinuxAsync(IEnumerable paths) + private static async Task OpenFilesDesktopAsync(IEnumerable paths) { var list = paths.ToList(); @@ -158,8 +158,8 @@ private static async Task OpenFilesLinuxAsync(IEnumerable paths) if (list.Count > Constants.Actions.MaxSelectedItems) { var confirmed = await DialogDisplayHelper.ShowDialogAsync( - Strings.LinuxOpenManyTitle.GetLocalizedFormatResource(list.Count), - Strings.LinuxOpenManyText.GetLocalizedResource(), + Strings.DesktopOpenManyTitle.GetLocalizedFormatResource(list.Count), + Strings.DesktopOpenManyText.GetLocalizedResource(), Strings.Open.GetLocalizedResource(), Strings.Cancel.GetLocalizedResource()); @@ -168,7 +168,7 @@ private static async Task OpenFilesLinuxAsync(IEnumerable paths) } var success = true; - var plain = new List<(string Path, LinuxOpenPlan Plan)>(); + var plain = new List<(string Path, DesktopOpenPlan Plan)>(); foreach (var path in list) { var plan = await PlanAsync(path); @@ -184,7 +184,7 @@ private static async Task OpenFilesLinuxAsync(IEnumerable paths) success = false; if (verified.Count > 0) - success &= await LinuxLauncher.OpenAsync(verified); + success &= await DesktopLauncher.OpenAsync(verified); return success; } @@ -193,19 +193,19 @@ private static async Task OpenFilesLinuxAsync(IEnumerable paths) /// Everything decided about a file before any dialog: the resolved target, its identity, the decision and the /// parsed .desktop entry. The same entry object is shown in the dialog and launched. /// - private sealed record LinuxOpenPlan(OpenAction Action, string Target, FileIdentity? Identity, DesktopEntryParser.Entry? Entry, IReadOnlyList? Argv = null, IReadOnlyList>? Invocations = null) + private sealed record DesktopOpenPlan(OpenAction Action, string Target, FileIdentity? Identity, DesktopEntryParser.Entry? Entry, IReadOnlyList? Argv = null, IReadOnlyList>? Invocations = null) { public bool StillValid() => Identity is { } id && id.StillMatches(Target); } - private static async Task PlanAsync(string path) + private static async Task PlanAsync(string path) { var target = ResolveFinalTarget(path); var identity = FileIdentity.TryCapture(target); if (identity is null) - return new LinuxOpenPlan(OpenAction.Refuse, target, null, null); + return new DesktopOpenPlan(OpenAction.Refuse, target, null, null); - var mime = await LinuxMimeTypes.GetMimeTypeAsync(path); + var mime = await DesktopMimeTypes.GetMimeTypeAsync(path); var hasExec = (identity.Value.Mode & 0b001_001_001) != 0; // Read the bytes once; the identity must be unchanged afterwards so the bytes belong to that version @@ -242,14 +242,14 @@ private static async Task PlanAsync(string path) } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { - return new LinuxOpenPlan(OpenAction.Refuse, target, null, null); + return new DesktopOpenPlan(OpenAction.Refuse, target, null, null); } if (!identity.Value.StillMatches(target)) - return new LinuxOpenPlan(OpenAction.Refuse, target, null, null); + return new DesktopOpenPlan(OpenAction.Refuse, target, null, null); var action = OpenDecision.Decide(desktop, desktop == DesktopState.Valid && IsInApplicationsDirectory(target), hasExec, OpenDecision.Sniff(head), mime); - return new LinuxOpenPlan(action, target, identity, entry, argv); + return new DesktopOpenPlan(action, target, identity, entry, argv); } /// @@ -270,10 +270,10 @@ private static string ResolveFinalTarget(string path) return path; } - internal static async Task OpenFileLinuxAsync(string path, bool openViaApplicationPicker = false) + internal static async Task OpenFileDesktopAsync(string path, bool openViaApplicationPicker = false) { if (openViaApplicationPicker) - return await LinuxOpenWithDialog.ShowAsync(path); + return await DesktopOpenWithDialog.ShowAsync(path); return await ExecutePlanAsync(path, await PlanAsync(path)); } @@ -282,7 +282,7 @@ internal static async Task OpenFileLinuxAsync(string path, bool openViaApp /// Runs an executable with the dropped items as arguments. The complete argv (target and every item) is shown and /// exactly that argv is run; anything that is not a confirmable executable, or too large to show in full, is refused. /// - internal static async Task RunWithItemsLinuxAsync(string executablePath, IReadOnlyList arguments) + internal static async Task RunWithItemsDesktopAsync(string executablePath, IReadOnlyList arguments) { var plan = await PlanAsync(executablePath); var argv = new List(arguments.Count + 1) { plan.Target }; @@ -290,23 +290,23 @@ internal static async Task RunWithItemsLinuxAsync(string executablePath, I if (!OpenDecision.NeedsRunConfirmation(plan.Action) || DisplaySanitizer.FullArguments(argv) is not { } lines) { - await DialogDisplayHelper.ShowDialogAsync(Strings.LinuxOpenRefusedTitle.GetLocalizedResource(), Strings.LinuxOpenRefusedText.GetLocalizedFormatResource(DisplaySanitizer.Field(plan.Target))); + await DialogDisplayHelper.ShowDialogAsync(Strings.DesktopOpenRefusedTitle.GetLocalizedResource(), Strings.DesktopOpenRefusedText.GetLocalizedFormatResource(DisplaySanitizer.Field(plan.Target))); return false; } var confirmed = await DialogDisplayHelper.ShowDialogAsync( - Strings.LinuxRunExecutableTitle.GetLocalizedFormatResource(DisplaySanitizer.Field(Path.GetFileName(plan.Target), 60)), - Strings.LinuxWouldRun.GetLocalizedResource() + "\n" + string.Join('\n', lines), + Strings.DesktopRunExecutableTitle.GetLocalizedFormatResource(DisplaySanitizer.Field(Path.GetFileName(plan.Target), 60)), + Strings.DesktopWouldRun.GetLocalizedResource() + "\n" + string.Join('\n', lines), Strings.Run.GetLocalizedResource(), Strings.Cancel.GetLocalizedResource()); if (!confirmed) return true; - return plan.StillValid() ? await LinuxLauncher.RunExecutableAsync(argv[0], argv.Skip(1).ToList(), Path.GetDirectoryName(argv[0])) : false; + return plan.StillValid() ? await DesktopLauncher.RunExecutableAsync(argv[0], argv.Skip(1).ToList(), Path.GetDirectoryName(argv[0])) : false; } - internal static async Task RunServiceMenuLinuxAsync(ServiceMenuAction action, IReadOnlyList targets) + internal static async Task RunServiceMenuDesktopAsync(ServiceMenuAction action, IReadOnlyList targets) { var culture = CultureInfo.CurrentUICulture; var tooManyInvocations = false; @@ -314,17 +314,17 @@ internal static async Task RunServiceMenuLinuxAsync(ServiceMenuAction acti if (servicePlan is null) { var message = tooManyInvocations - ? Strings.LinuxServiceMenuTooManyTargetsText.GetLocalizedFormatResource(ServiceMenuLaunchPlan.MaxPerTargetLaunches) - : Strings.LinuxServiceMenuRefusedText.GetLocalizedResource(); - await DialogDisplayHelper.ShowDialogAsync(Strings.LinuxServiceMenuTitle.GetLocalizedFormatResource(DisplaySanitizer.Field(action.Application.Name, 60)), message); + ? Strings.DesktopServiceMenuTooManyTargetsText.GetLocalizedFormatResource(ServiceMenuLaunchPlan.MaxPerTargetLaunches) + : Strings.DesktopServiceMenuRefusedText.GetLocalizedResource(); + await DialogDisplayHelper.ShowDialogAsync(Strings.DesktopServiceMenuTitle.GetLocalizedFormatResource(DisplaySanitizer.Field(action.Application.Name, 60)), message); return false; } - var plan = new LinuxOpenPlan(OpenAction.LaunchDesktopConfirm, action.Application.DesktopFilePath, servicePlan.Identity, + var plan = new DesktopOpenPlan(OpenAction.LaunchDesktopConfirm, action.Application.DesktopFilePath, servicePlan.Identity, servicePlan.Entry, Invocations: servicePlan.Invocations); return await ExecutePlanAsync(plan.Target, plan); } - private static async Task ExecutePlanAsync(string path, LinuxOpenPlan plan) + private static async Task ExecutePlanAsync(string path, DesktopOpenPlan plan) { var target = plan.Target; var workingDirectory = Path.GetDirectoryName(target); @@ -334,38 +334,38 @@ private static async Task ExecutePlanAsync(string path, LinuxOpenPlan plan async Task ChangedAsync() { App.Logger.LogWarning("File changed before it was opened: {Path}", target); - await DialogDisplayHelper.ShowDialogAsync(Strings.LinuxFileChangedTitle.GetLocalizedResource(), Strings.LinuxFileChangedText.GetLocalizedFormatResource(DisplaySanitizer.Field(target))); + await DialogDisplayHelper.ShowDialogAsync(Strings.DesktopFileChangedTitle.GetLocalizedResource(), Strings.DesktopFileChangedText.GetLocalizedFormatResource(DisplaySanitizer.Field(target))); return false; } switch (plan.Action) { case OpenAction.Refuse: - await DialogDisplayHelper.ShowDialogAsync(Strings.LinuxOpenRefusedTitle.GetLocalizedResource(), Strings.LinuxOpenRefusedText.GetLocalizedFormatResource(DisplaySanitizer.Field(target))); + await DialogDisplayHelper.ShowDialogAsync(Strings.DesktopOpenRefusedTitle.GetLocalizedResource(), Strings.DesktopOpenRefusedText.GetLocalizedFormatResource(DisplaySanitizer.Field(target))); return false; case OpenAction.RunBinaryWithConfirm: { var confirmed = await DialogDisplayHelper.ShowDialogAsync( - Strings.LinuxRunExecutableTitle.GetLocalizedFormatResource(DisplaySanitizer.Field(Path.GetFileName(target), 60)), - Strings.LinuxRunBinaryText.GetLocalizedFormatResource(DisplaySanitizer.Escape(target)), + Strings.DesktopRunExecutableTitle.GetLocalizedFormatResource(DisplaySanitizer.Field(Path.GetFileName(target), 60)), + Strings.DesktopRunBinaryText.GetLocalizedFormatResource(DisplaySanitizer.Escape(target)), Strings.Run.GetLocalizedResource(), Strings.Cancel.GetLocalizedResource()); if (!confirmed) return true; - return plan.StillValid() ? await LinuxLauncher.RunExecutableAsync(target, null, workingDirectory) : await ChangedAsync(); + return plan.StillValid() ? await DesktopLauncher.RunExecutableAsync(target, null, workingDirectory) : await ChangedAsync(); } case OpenAction.RunScriptWithConfirm: { var dialog = new DynamicDialog(new DynamicDialogViewModel() { - TitleText = Strings.LinuxRunExecutableTitle.GetLocalizedFormatResource(DisplaySanitizer.Field(Path.GetFileName(target), 60)), - SubtitleText = Strings.LinuxRunExecutableText.GetLocalizedFormatResource(DisplaySanitizer.Escape(target)), + TitleText = Strings.DesktopRunExecutableTitle.GetLocalizedFormatResource(DisplaySanitizer.Field(Path.GetFileName(target), 60)), + SubtitleText = Strings.DesktopRunExecutableText.GetLocalizedFormatResource(DisplaySanitizer.Escape(target)), PrimaryButtonText = Strings.Run.GetLocalizedResource(), - SecondaryButtonText = Strings.LinuxDisplayFile.GetLocalizedResource(), + SecondaryButtonText = Strings.DesktopDisplayFile.GetLocalizedResource(), CloseButtonText = Strings.Cancel.GetLocalizedResource(), DynamicButtons = DynamicDialogButtons.Primary | DynamicDialogButtons.Secondary | DynamicDialogButtons.Cancel }); @@ -380,7 +380,7 @@ async Task ChangedAsync() switch (OpenDecision.Resolve(plan.Action, choice)) { case FollowUp.RunExact: - return plan.StillValid() ? await LinuxLauncher.RunExecutableAsync(target, null, workingDirectory) : await ChangedAsync(); + return plan.StillValid() ? await DesktopLauncher.RunExecutableAsync(target, null, workingDirectory) : await ChangedAsync(); case FollowUp.DisplayAsText: return plan.StillValid() ? await DisplayAsTextAsync(target) : await ChangedAsync(); default: @@ -402,13 +402,13 @@ async Task ChangedAsync() return true; } if (!plan.StillValid()) return await ChangedAsync(); - success &= await LinuxLauncher.RunCommandAsync(argv, application.RunInTerminal); + success &= await DesktopLauncher.RunCommandAsync(argv, application.RunInTerminal); } return success; } default: - return plan.StillValid() ? await LinuxLauncher.OpenAsync([path]) : await ChangedAsync(); + return plan.StillValid() ? await DesktopLauncher.OpenAsync([path]) : await ChangedAsync(); } } @@ -421,11 +421,11 @@ private static async Task DisplayAsTextAsync(string target) var editor = await registry.GetDefaultApplicationAsync("text/plain") ?? (await registry.GetApplicationsForMimeTypeAsync("text/plain")).FirstOrDefault(); if (editor is null) { - await DialogDisplayHelper.ShowDialogAsync(Strings.LinuxOpenFailedTitle.GetLocalizedResource(), Strings.LinuxOpenFailedText.GetLocalizedFormatResource(DisplaySanitizer.Field(target))); + await DialogDisplayHelper.ShowDialogAsync(Strings.DesktopOpenFailedTitle.GetLocalizedResource(), Strings.DesktopOpenFailedText.GetLocalizedFormatResource(DisplaySanitizer.Field(target))); return false; } - return await LinuxLauncher.OpenWithAsync(editor, [target]); + return await DesktopLauncher.OpenWithAsync(editor, [target]); } private static async Task ShowLauncherConfirmationAsync(string target, string claimedName, IReadOnlyList argv, bool inTerminal, bool isServiceMenu = false) @@ -433,14 +433,14 @@ private static async Task ShowLauncherConfirmationAsync(string target, str // What will run must be shown in full; if it cannot be, it does not run if (DisplaySanitizer.FullArguments(argv) is not { } lines) { - await DialogDisplayHelper.ShowDialogAsync(Strings.LinuxOpenRefusedTitle.GetLocalizedResource(), Strings.LinuxCommandTooLong.GetLocalizedResource()); + await DialogDisplayHelper.ShowDialogAsync(Strings.DesktopOpenRefusedTitle.GetLocalizedResource(), Strings.DesktopCommandTooLong.GetLocalizedResource()); return false; } var panel = new Microsoft.UI.Xaml.Controls.StackPanel { Spacing = 8 }; - panel.Children.Add(new Microsoft.UI.Xaml.Controls.TextBlock { Text = Strings.LinuxFileLabel.GetLocalizedResource() + " " + DisplaySanitizer.Escape(target), TextWrapping = Microsoft.UI.Xaml.TextWrapping.Wrap, IsTextSelectionEnabled = true }); - panel.Children.Add(new Microsoft.UI.Xaml.Controls.TextBlock { Text = Strings.LinuxClaimsToBe.GetLocalizedResource() + " " + DisplaySanitizer.Field(claimedName), TextWrapping = Microsoft.UI.Xaml.TextWrapping.Wrap }); - panel.Children.Add(new Microsoft.UI.Xaml.Controls.TextBlock { Text = Strings.LinuxWouldRun.GetLocalizedResource() + (inTerminal ? " (" + Strings.LinuxInTerminal.GetLocalizedResource() + ")" : string.Empty) }); + panel.Children.Add(new Microsoft.UI.Xaml.Controls.TextBlock { Text = Strings.DesktopFileLabel.GetLocalizedResource() + " " + DisplaySanitizer.Escape(target), TextWrapping = Microsoft.UI.Xaml.TextWrapping.Wrap, IsTextSelectionEnabled = true }); + panel.Children.Add(new Microsoft.UI.Xaml.Controls.TextBlock { Text = Strings.DesktopClaimsToBe.GetLocalizedResource() + " " + DisplaySanitizer.Field(claimedName), TextWrapping = Microsoft.UI.Xaml.TextWrapping.Wrap }); + panel.Children.Add(new Microsoft.UI.Xaml.Controls.TextBlock { Text = Strings.DesktopWouldRun.GetLocalizedResource() + (inTerminal ? " (" + Strings.DesktopInTerminal.GetLocalizedResource() + ")" : string.Empty) }); panel.Children.Add(new Microsoft.UI.Xaml.Controls.ScrollViewer { MaxHeight = 240, @@ -457,9 +457,9 @@ private static async Task ShowLauncherConfirmationAsync(string target, str var dialog = new DynamicDialog(new DynamicDialogViewModel() { TitleText = isServiceMenu - ? Strings.LinuxServiceMenuTitle.GetLocalizedFormatResource(DisplaySanitizer.Field(claimedName, 60)) - : Strings.LinuxUntrustedLauncherTitle.GetLocalizedFormatResource(DisplaySanitizer.Field(Path.GetFileName(target), 60)), - SubtitleText = (isServiceMenu ? Strings.LinuxServiceMenuConfirmationText : Strings.LinuxUntrustedLauncherText).GetLocalizedResource(), + ? Strings.DesktopServiceMenuTitle.GetLocalizedFormatResource(DisplaySanitizer.Field(claimedName, 60)) + : Strings.DesktopUntrustedLauncherTitle.GetLocalizedFormatResource(DisplaySanitizer.Field(Path.GetFileName(target), 60)), + SubtitleText = (isServiceMenu ? Strings.DesktopServiceMenuConfirmationText : Strings.DesktopUntrustedLauncherText).GetLocalizedResource(), DisplayControl = panel, PrimaryButtonText = Strings.Run.GetLocalizedResource(), CloseButtonText = Strings.Cancel.GetLocalizedResource(), diff --git a/src/Files.App/Helpers/Navigation/NavigationHelpers.cs b/src/Files.App/Helpers/Navigation/NavigationHelpers.cs index cb42f0e63189..51598da4043c 100644 --- a/src/Files.App/Helpers/Navigation/NavigationHelpers.cs +++ b/src/Files.App/Helpers/Navigation/NavigationHelpers.cs @@ -386,9 +386,9 @@ await SafetyExtensions.IgnoreExceptions(async () => : navigationArg == MainPageViewModel.SelectedTabItem?.NavigationParameter?.NavigationParameter; if (isCurrent) { - var title = $"{windowTitle} - {(OperatingSystem.IsLinux() ? Strings.LinuxAppDisplayName.GetLocalizedResource() : "Files")}"; - title = Files.Platform.Linux.Windowing.X11WindowChrome.SanitizeTitle(MainWindow.FormatRootModeTitle(title)); - MainWindow.Instance.UpdateLinuxWindowTitle(title); + var title = $"{windowTitle} - {(OperatingSystem.IsLinux() ? Strings.DesktopAppDisplayName.GetLocalizedResource() : "Files")}"; + title = Files.Platform.Posix.WindowTitle.Sanitize(MainWindow.FormatRootModeTitle(title)); + MainWindow.Instance.UpdateWindowTitle(title); MainWindow.Instance.AppWindow.Title = title; } }); @@ -425,7 +425,7 @@ public static Task OpenTabInNewWindowAsync(string tabArgs, int? dropX = nu } // The Linux app has one window per process, so a new window is a second process that skips single instance forwarding - private static Task LaunchNewLinuxInstance() + private static Task LaunchNewDesktopInstance() { try { @@ -442,7 +442,7 @@ private static Task LaunchNewLinuxInstance() startInfo.Environment["FILES_NO_SINGLE_INSTANCE"] = "1"; startInfo.ArgumentList.Add("--new-window"); - if (Files.Platform.Linux.Elevation.RootActionMode.IsRequested(Program.LaunchArguments)) + if (Files.Platform.Posix.Elevation.RootActionMode.IsRequested(Program.LaunchArguments)) startInfo.ArgumentList.Add("--root"); System.Diagnostics.Process.Start(startInfo)?.Dispose(); } @@ -467,7 +467,7 @@ public static void OpenInSecondaryPane(IShellPage associatedInstance, ListedItem public static Task LaunchNewWindowAsync() { if (OperatingSystem.IsLinux()) - return LaunchNewLinuxInstance(); + return LaunchNewDesktopInstance(); return Launcher.LaunchUriAsync(new Uri("files-dev:?window=")).AsTask(); } @@ -499,7 +499,7 @@ public static async Task OpenSelectedItemsAsync(IShellPage associatedInstance, b !Ioc.Default.GetRequiredService().IsArchiveFileName(x.GetRequiredPath())) ) { - opened = await OpenFilesLinuxAsync(selectedItems.Select(x => x.GetRequiredPath())); + opened = await OpenFilesDesktopAsync(selectedItems.Select(x => x.GetRequiredPath())); } if (opened) @@ -534,7 +534,7 @@ public static async Task OpenItemsWithExecutableAsync(IShellPage associatedInsta if (workingDirectory.StartsWith(Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.Ordinal)) return; - await RunWithItemsLinuxAsync(executablePath, items.Select(item => item.Path).ToArray()); + await RunWithItemsDesktopAsync(executablePath, items.Select(item => item.Path).ToArray()); } /// @@ -573,7 +573,7 @@ public static async Task OpenPath(string path, IShellPage associatedInstan return true; } - return await OpenPathLinuxAsync(path, associatedInstance, openViaApplicationPicker, selectItems, forceOpenInNewTab); + return await OpenPathDesktopAsync(path, associatedInstance, openViaApplicationPicker, selectItems, forceOpenInNewTab); } diff --git a/src/Files.App/Helpers/RootActionsHelper.cs b/src/Files.App/Helpers/RootActionsHelper.cs index 6a2fd57b5e73..c8daab9e2508 100644 --- a/src/Files.App/Helpers/RootActionsHelper.cs +++ b/src/Files.App/Helpers/RootActionsHelper.cs @@ -4,8 +4,9 @@ using Files.Platform.Abstractions.Clipboard; using Files.Platform.Abstractions.Launching; using Files.Platform.Abstractions.Elevation; +using Files.Platform.Posix.Elevation; +using Files.Platform.Posix.Launching; using Files.Platform.Linux.Elevation; -using Files.Platform.Linux.Launching; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using System.IO; diff --git a/src/Files.App/MainWindow.Linux.cs b/src/Files.App/MainWindow.Linux.cs index 5e33cabff562..764faf9f44ee 100644 --- a/src/Files.App/MainWindow.Linux.cs +++ b/src/Files.App/MainWindow.Linux.cs @@ -16,13 +16,13 @@ namespace Files.App { public sealed partial class MainWindow { - private X11WindowChrome? _linuxChrome; + private X11WindowChrome? _desktopChrome; private PropertyChangedEventHandler? _appearanceChanged; private EventHandler? _systemAppearanceChanged; private bool _chromeEventsAttached; private int _dragStart; private int _titleHeight; - private string? _linuxWindowTitle; + private string? _desktopWindowTitle; #if UNO_WAYLAND private bool _waylandWindow; // The Wayland host implements caption regions, move and maximize itself (InputNonClientPointerSource, presenter). @@ -32,35 +32,35 @@ public sealed partial class MainWindow #endif public bool UseClientSideDecorations => OperatingSystem.IsLinux() && Ioc.Default.GetRequiredService().Get("UseClientSideDecorations", true); - public bool HasClientSideDecorations => (_linuxChrome?.SupportsClientSideDecorations == true || IsWaylandWindow) && UseClientSideDecorations; - public bool IsLinuxWindowMaximized => _linuxChrome?.IsMaximized ?? + public bool HasClientSideDecorations => (_desktopChrome?.SupportsClientSideDecorations == true || IsWaylandWindow) && UseClientSideDecorations; + public bool IsWindowMaximized => _desktopChrome?.IsMaximized ?? (IsWaylandWindow && AppWindow.Presenter is OverlappedPresenter { State: OverlappedPresenterState.Maximized }); - public event EventHandler? LinuxChromeChanged; + public event EventHandler? ChromeChanged; internal static string FormatRootModeTitle(string title) => title + (RootActionsAvailability.Mode.Indicator switch { - RootModeIndicator.RootMode => Strings.LinuxRootModeSuffix.GetLocalizedResource(), - RootModeIndicator.RunningAsRoot => Strings.LinuxRunningAsRootSuffix.GetLocalizedResource(), + RootModeIndicator.RootMode => Strings.DesktopRootModeSuffix.GetLocalizedResource(), + RootModeIndicator.RunningAsRoot => Strings.DesktopRunningAsRootSuffix.GetLocalizedResource(), _ => string.Empty, }); - public void InitializeLinuxChrome() + public void InitializeChrome() { if (!OperatingSystem.IsLinux()) return; var nativeWindow = Uno.UI.Xaml.WindowHelper.GetNativeWindow(this); - if (_linuxChrome is null && nativeWindow is X11NativeWindow native) - _linuxChrome = X11WindowChrome.TryCreate((nuint)native.WindowId); + if (_desktopChrome is null && nativeWindow is X11NativeWindow native) + _desktopChrome = X11WindowChrome.TryCreate((nuint)native.WindowId); #if UNO_WAYLAND // Only the X11 and Wayland hosts are registered, so any other native window is the Wayland surface. _waylandWindow = nativeWindow is not null and not X11NativeWindow; #endif if (!HasClientSideDecorations) - _linuxChrome?.HideRegions(); - if (_linuxWindowTitle is not null) - _linuxChrome?.SetTitle(_linuxWindowTitle); + _desktopChrome?.HideRegions(); + if (_desktopWindowTitle is not null) + _desktopChrome?.SetTitle(_desktopWindowTitle); ExtendsContentIntoTitleBar = HasClientSideDecorations; if (AppWindow.Presenter is OverlappedPresenter presenter) @@ -71,62 +71,62 @@ public void InitializeLinuxChrome() _chromeEventsAttached = true; AppWindow.Changed += (_, _) => { - UpdateLinuxChromeRegions(_dragStart, _titleHeight); - LinuxChromeChanged?.Invoke(this, EventArgs.Empty); + UpdateChromeRegions(_dragStart, _titleHeight); + ChromeChanged?.Invoke(this, EventArgs.Empty); }; Closed += (_, _) => { Ioc.Default.GetRequiredService().PropertyChanged -= _appearanceChanged; Ioc.Default.GetRequiredService().Changed -= _systemAppearanceChanged; X11AppearanceSupport.Changed -= _systemAppearanceChanged; - _linuxChrome?.Dispose(); _linuxChrome = null; + _desktopChrome?.Dispose(); _desktopChrome = null; }; var appearance = Ioc.Default.GetRequiredService(); _appearanceChanged = (_, e) => { if (e.PropertyName is nameof(IAppearanceSettingsService.WindowOpacity) or nameof(IAppearanceSettingsService.BackdropMode) or nameof(IAppearanceSettingsService.BackgroundOpacity)) - DispatcherQueue.TryEnqueue(ApplyLinuxWindowOpacity); + DispatcherQueue.TryEnqueue(ApplyDesktopWindowOpacity); }; appearance.PropertyChanged += _appearanceChanged; - _systemAppearanceChanged = (_, _) => DispatcherQueue.TryEnqueue(ApplyLinuxWindowOpacity); + _systemAppearanceChanged = (_, _) => DispatcherQueue.TryEnqueue(ApplyDesktopWindowOpacity); Ioc.Default.GetRequiredService().Changed += _systemAppearanceChanged; X11AppearanceSupport.Changed += _systemAppearanceChanged; } - ApplyLinuxWindowOpacity(); + ApplyDesktopWindowOpacity(); Ioc.Default.GetRequiredService().ApplyResources(); - LinuxChromeChanged?.Invoke(this, EventArgs.Empty); + ChromeChanged?.Invoke(this, EventArgs.Empty); } - private void ApplyLinuxWindowOpacity() + private void ApplyDesktopWindowOpacity() { var appearance = Ioc.Default.GetRequiredService(); var highContrast = Ioc.Default.GetRequiredService().Current.HighContrast == true; var support = X11AppearanceSupport.Current; - _linuxChrome?.SetWindowOpacity(!highContrast && support.WindowOpacity == OpacitySupport.Supported ? appearance.WindowOpacity : 1); + _desktopChrome?.SetWindowOpacity(!highContrast && support.WindowOpacity == OpacitySupport.Supported ? appearance.WindowOpacity : 1); var mode = support.Resolve(appearance.BackdropMode, highContrast); Uno.UI.Xaml.WindowHelper.SetBackground(this, new SolidColorBrush(mode == BackdropMode.Solid ? (appearance.AppThemeMode == "Dark" || appearance.AppThemeMode == "Default" && Files.Platform.Linux.Theme.LinuxColorScheme.IsDark ? Colors.Black : Colors.White) : Colors.Transparent)); - _linuxChrome?.SetBlur(mode == BackdropMode.Blur); + _desktopChrome?.SetBlur(mode == BackdropMode.Blur); } public void SetClientSideDecorations(bool enabled) { Ioc.Default.GetRequiredService().Set("UseClientSideDecorations", enabled); - InitializeLinuxChrome(); + InitializeChrome(); } - public void UpdateLinuxWindowTitle(string title) + public void UpdateWindowTitle(string title) { // Uno 6.7 can leave the native title unchanged after setting AppWindow.Title. - _linuxWindowTitle = title; - _linuxChrome?.SetTitle(title); + _desktopWindowTitle = title; + _desktopChrome?.SetTitle(title); } - public void ToggleLinuxWindowMaximize() + public void ToggleWindowMaximize() { - if (_linuxChrome is not null) - _linuxChrome.ToggleMaximize(); + if (_desktopChrome is not null) + _desktopChrome.ToggleMaximize(); else if (IsWaylandWindow && AppWindow.Presenter is OverlappedPresenter presenter) { if (presenter.State == OverlappedPresenterState.Maximized) @@ -136,7 +136,7 @@ public void ToggleLinuxWindowMaximize() } } - public void UpdateLinuxChromeRegions(int dragStart, int titleHeight) + public void UpdateChromeRegions(int dragStart, int titleHeight) { _dragStart = dragStart; _titleHeight = titleHeight; @@ -155,7 +155,7 @@ public void UpdateLinuxChromeRegions(int dragStart, int titleHeight) ]); return; } - _linuxChrome?.SetRegions((int)(root.ActualWidth * scale), (int)(root.ActualHeight * scale), + _desktopChrome?.SetRegions((int)(root.ActualWidth * scale), (int)(root.ActualHeight * scale), (int)(dragStart * scale), (int)((root.ActualWidth - dragStart - 138) * scale), (int)(titleHeight * scale), Math.Max(1, (int)(5 * scale))); } diff --git a/src/Files.App/MainWindow.xaml.cs b/src/Files.App/MainWindow.xaml.cs index dd1f36d56784..8fc7ebf6fd60 100644 --- a/src/Files.App/MainWindow.xaml.cs +++ b/src/Files.App/MainWindow.xaml.cs @@ -35,7 +35,7 @@ public MainWindow() : base(416, 316) InitializeComponent(); ExtendsContentIntoTitleBar = UseClientSideDecorations; - Title = OperatingSystem.IsLinux() ? Strings.LinuxAppDisplayName.GetLocalizedResource() : "Files"; + Title = OperatingSystem.IsLinux() ? Strings.DesktopAppDisplayName.GetLocalizedResource() : "Files"; Title = FormatRootModeTitle(Title); AppWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent; AppWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent; diff --git a/src/Files.App/Platforms/Desktop/App.Desktop.cs b/src/Files.App/Platforms/Desktop/App.Desktop.cs index d68f80b46dfc..3ddac90196e3 100644 --- a/src/Files.App/Platforms/Desktop/App.Desktop.cs +++ b/src/Files.App/Platforms/Desktop/App.Desktop.cs @@ -102,15 +102,7 @@ private void StartInstanceRequestListener() try { var settings = Ioc.Default.GetRequiredService().GeneralSettingsService; - var fileManager = Ioc.Default.GetRequiredService(); - - async Task Apply() - { - if (settings.UseAsDefaultFileManager) - await fileManager.StartAsync(); - else - await fileManager.StopAsync(); - } + async Task Apply() => await DesktopPlatform.SetDefaultFileManagerAsync(settings.UseAsDefaultFileManager); await Apply(); settings.PropertyChanged += async (_, e) => diff --git a/src/Files.App/Platforms/Desktop/DesktopPlatform.cs b/src/Files.App/Platforms/Desktop/DesktopPlatform.cs new file mode 100644 index 000000000000..ffd0dd2a37c1 --- /dev/null +++ b/src/Files.App/Platforms/Desktop/DesktopPlatform.cs @@ -0,0 +1,51 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using Files.Platform.Linux.Instance; +using Microsoft.Extensions.DependencyInjection; +using Windows.ApplicationModel.DataTransfer; + +namespace Files.App +{ + /// + /// The single place where the shared app layer binds to the desktop head's platform assembly. + /// + internal static class DesktopPlatform + { + /// + /// Creates the user directories before the DI container exists (used by static path constants). + /// + public static IUserDirectories CreateUserDirectories() + => new Files.Platform.Linux.LinuxUserDirectories(); + + /// + /// Adds the file list of a drag to in the format other applications of the desktop read. + /// + public static void SetDraggedFiles(DataPackage data, IReadOnlyList paths) + => data.SetData(Files.Platform.Linux.Clipboard.ClipboardFormats.UriList, Files.Platform.Linux.Clipboard.ClipboardFormats.BuildUriList(paths)); + + /// + /// Gets the effective display scale used to size windows from physical pixels. + /// + public static double GetDisplayScale() + => Files.Platform.Linux.Windowing.DisplayScaleResolver.GetEffectiveScale(); + + /// + /// Starts or stops the desktop's file manager service (FileManager1 on Linux). + /// + public static Task SetDefaultFileManagerAsync(bool enabled) + { + var fileManager = Ioc.Default.GetRequiredService(); + return enabled ? fileManager.StartAsync() : fileManager.StopAsync(); + } + + /// + /// Registers the platform and system integration services of the desktop head. + /// + public static IServiceCollection AddDesktopPlatform(this IServiceCollection services) + => services + .AddLinuxPlatform() + .AddSingleton(_ => Program.SingleInstance ?? new LinuxSingleInstanceService()) + .AddLinuxSystemIntegration(); + } +} diff --git a/src/Files.App/Platforms/Desktop/Program.Linux.cs b/src/Files.App/Platforms/Desktop/Program.Linux.cs new file mode 100644 index 000000000000..ed69c76ae083 --- /dev/null +++ b/src/Files.App/Platforms/Desktop/Program.Linux.cs @@ -0,0 +1,136 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using Files.Platform.Abstractions.Instance; +using Files.Platform.Linux.Elevation; +using Files.Platform.Linux.Instance; +using Files.Platform.Linux.Windowing; +using Uno.UI.Hosting; + +namespace Files.App +{ + /// + /// The Linux (Wayland/X11) part of the entry point: startup environment, single instance and the Uno host. + /// + internal sealed partial class Program + { + /// + /// Gets the single instance service this process owns. Null when single instance handling is disabled. + /// + internal static LinuxSingleInstanceService? SingleInstance { get; private set; } + + private static bool TryRunPlatformHelper(string[] args, out int exitCode) + => Files.Platform.Linux.Previews.HtmlSnapshotHelper.TryRunHelper(args, out exitCode); + + /// + /// Applies the root startup environment. Returns false when the process must not continue. + /// + private static bool TryApplyStartupEnvironment(string[] args) + { + try + { + using var trace = Files.Platform.Abstractions.Diagnostics.PerformanceTrace.Begin("startup.root-environment"); + RootStartupEnvironment.Apply(); + } + catch (System.IO.IOException) + { + Console.Error.WriteLine("[Files] Refusing root startup: root's account and settings directories must be exclusively controlled by root."); + return false; + } + RootActionsAvailability.Configure(args); + return true; + } + + private static void InitializePlatform() + { + // Selawik weights (Regular/Semibold/Bold/Light) are matched by family name + weight through fontconfig + Files.Platform.Linux.Native.FontConfigNative.RegisterFontDirectory(System.IO.Path.Combine(AppContext.BaseDirectory, "Assets", "Fonts", "Linux")); + + // Uno reads Xft.dpi itself; this covers sessions that only export GDK/Qt scale variables + Files.Platform.Linux.Windowing.DisplayScaleResolver.ApplyToProcess(); + } + + /// + /// Returns false when the command line was forwarded to the running instance. + /// + private static bool TryBecomePrimaryInstance(string[] args) + { + // Root-mode launches use a separate process so they never change or forward into an ordinary window. + // Ordinary second launches forward through D-Bus (socket fallback). + // FILES_NO_SINGLE_INSTANCE=1 skips this (for running several instances side by side while developing). + var noSingleInstance = !RootActionsAvailability.Mode.UseSingleInstance || Environment.GetEnvironmentVariable("FILES_NO_SINGLE_INSTANCE") == "1"; + + // Keep the flag from leaking into apps and terminals launched from this instance + Environment.SetEnvironmentVariable("FILES_NO_SINGLE_INSTANCE", null); + + if (noSingleInstance) + return true; + + var singleInstance = new LinuxSingleInstanceService(); + var request = new InstanceRequest(InstanceRequestKind.CommandLine, Environment.CurrentDirectory, args); + + bool isPrimary; + try + { + using var trace = Files.Platform.Abstractions.Diagnostics.PerformanceTrace.Begin("startup.single-instance"); + isPrimary = singleInstance.TryBecomePrimaryAsync(request).GetAwaiter().GetResult(); + } + catch (Exception ex) when (ex is System.IO.IOException or InvalidOperationException or System.Net.Sockets.SocketException or UnauthorizedAccessException) + { + Console.Error.WriteLine($"[Files] Single instance check failed, continuing: {ex.Message}"); + isPrimary = true; + } + + if (!isPrimary) + { + Console.Error.WriteLine("[Files] Forwarded the command line to the running instance."); + return false; + } + + SingleInstance = singleInstance; + return true; + } + + /// + /// Builds the Uno host for the display server and runs it until the app exits. + /// + private static void RunHost() + { + // Software GL (llvmpipe) is slower than Uno's software renderer; FILES_RENDERER=software|opengl|gles|vulkan overrides + var (renderingBackend, reason) = RenderingBackendSelector.Resolve(Environment.GetEnvironmentVariable, RenderingBackendSelector.HasGpuDevice); + Console.Error.WriteLine($"[Files] Rendering backend: {renderingBackend} ({reason ?? "default"})"); + + using (var trace = Files.Platform.Abstractions.Diagnostics.PerformanceTrace.Begin("startup.cursor")) + CursorSettingsResolver.ApplyToProcess(); + + var hostBuilder = UnoPlatformHostBuilder.Create() + .App(() => new App()); +#if UNO_WAYLAND + // Preferred when WAYLAND_DISPLAY is set; UNO_SKIA_HOST=x11|wayland|auto overrides the choice + hostBuilder = hostBuilder.UseWayland(); +#endif + var host = hostBuilder + .UseX11(x11 => x11.RenderingBackend(renderingBackend switch + { + RenderingBackendChoice.Software => X11RenderingBackend.Software, + RenderingBackendChoice.OpenGL => X11RenderingBackend.OpenGL, + RenderingBackendChoice.OpenGLES => X11RenderingBackend.OpenGLES, + RenderingBackendChoice.Vulkan => X11RenderingBackend.Vulkan, + _ => X11RenderingBackend.Default, + })) + .Build(); + + using var scaleCancellation = new CancellationTokenSource(); + var scaleWatcher = DisplayScaleResolver.WatchCompositorScaleAsync(scaleCancellation.Token); + try + { + host.Run(); + } + finally + { + scaleCancellation.Cancel(); + scaleWatcher.GetAwaiter().GetResult(); + } + } + } +} diff --git a/src/Files.App/Platforms/Desktop/Program.cs b/src/Files.App/Platforms/Desktop/Program.cs index d2fdcf03eb65..a2de0e4fc56c 100644 --- a/src/Files.App/Platforms/Desktop/Program.cs +++ b/src/Files.App/Platforms/Desktop/Program.cs @@ -1,12 +1,7 @@ // Copyright (c) Files Community // Licensed under the MIT License. -using Files.Platform.Abstractions.Instance; -using Files.Platform.Linux.Instance; -using Files.Platform.Linux.Elevation; -using Files.Platform.Linux.Windowing; using System.Text; -using Uno.UI.Hosting; namespace Files.App { @@ -14,7 +9,7 @@ namespace Files.App /// Represents the entry point of the Files app on Uno Platform (Skia desktop, Wayland/X11). /// Replaces Program.cs, which is built for Windows only (WinAppSDK AppInstance redirection). /// - internal sealed class Program + internal sealed partial class Program { /// /// Gets or sets the semaphore a parked (background) instance waits on. Unused on desktop for now. @@ -31,105 +26,30 @@ internal sealed class Program /// public static string[] LaunchArguments { get; private set; } = []; - /// - /// Gets the single instance service this process owns. Null when single instance handling is disabled. - /// - internal static LinuxSingleInstanceService? SingleInstance { get; private set; } - [STAThread] public static int Main(string[] args) { - if (Files.Platform.Linux.Previews.HtmlSnapshotHelper.TryRunHelper(args, out var helperExitCode)) return helperExitCode; - try - { - using var trace = Files.Platform.Abstractions.Diagnostics.PerformanceTrace.Begin("startup.root-environment"); - RootStartupEnvironment.Apply(); - } - catch (System.IO.IOException) - { - Console.Error.WriteLine("[Files] Refusing root startup: root's account and settings directories must be exclusively controlled by root."); + if (TryRunPlatformHelper(args, out var helperExitCode)) + return helperExitCode; + + if (!TryApplyStartupEnvironment(args)) return 1; - } - RootActionsAvailability.Configure(args); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - // Selawik weights (Regular/Semibold/Bold/Light) are matched by family name + weight through fontconfig - Files.Platform.Linux.Native.FontConfigNative.RegisterFontDirectory(System.IO.Path.Combine(AppContext.BaseDirectory, "Assets", "Fonts", "Linux")); + InitializePlatform(); LaunchArguments = args; - // Uno reads Xft.dpi itself; this covers sessions that only export GDK/Qt scale variables - Files.Platform.Linux.Windowing.DisplayScaleResolver.ApplyToProcess(); - - // Root-mode launches use a separate process so they never change or forward into an ordinary window. - // Ordinary second launches forward through D-Bus (socket fallback). - // FILES_NO_SINGLE_INSTANCE=1 skips this (for running several instances side by side while developing). - var noSingleInstance = !RootActionsAvailability.Mode.UseSingleInstance || Environment.GetEnvironmentVariable("FILES_NO_SINGLE_INSTANCE") == "1"; - - // Keep the flag from leaking into apps and terminals launched from this instance - Environment.SetEnvironmentVariable("FILES_NO_SINGLE_INSTANCE", null); - - if (!noSingleInstance) - { - var singleInstance = new LinuxSingleInstanceService(); - var request = new InstanceRequest(InstanceRequestKind.CommandLine, Environment.CurrentDirectory, args); - - bool isPrimary; - try - { - using var trace = Files.Platform.Abstractions.Diagnostics.PerformanceTrace.Begin("startup.single-instance"); - isPrimary = singleInstance.TryBecomePrimaryAsync(request).GetAwaiter().GetResult(); - } - catch (Exception ex) when (ex is System.IO.IOException or InvalidOperationException or System.Net.Sockets.SocketException or UnauthorizedAccessException) - { - Console.Error.WriteLine($"[Files] Single instance check failed, continuing: {ex.Message}"); - isPrimary = true; - } - - if (!isPrimary) - { - Console.Error.WriteLine("[Files] Forwarded the command line to the running instance."); - return 0; - } - - SingleInstance = singleInstance; - } - - // Software GL (llvmpipe) is slower than Uno's software renderer; FILES_RENDERER=software|opengl|gles|vulkan overrides - var (renderingBackend, reason) = RenderingBackendSelector.Resolve(Environment.GetEnvironmentVariable, RenderingBackendSelector.HasGpuDevice); - Console.Error.WriteLine($"[Files] Rendering backend: {renderingBackend} ({reason ?? "default"})"); - - using (var trace = Files.Platform.Abstractions.Diagnostics.PerformanceTrace.Begin("startup.cursor")) - CursorSettingsResolver.ApplyToProcess(); - - var hostBuilder = UnoPlatformHostBuilder.Create() - .App(() => new App()); -#if UNO_WAYLAND - // Preferred when WAYLAND_DISPLAY is set; UNO_SKIA_HOST=x11|wayland|auto overrides the choice - hostBuilder = hostBuilder.UseWayland(); -#endif - var host = hostBuilder - .UseX11(x11 => x11.RenderingBackend(renderingBackend switch - { - RenderingBackendChoice.Software => X11RenderingBackend.Software, - RenderingBackendChoice.OpenGL => X11RenderingBackend.OpenGL, - RenderingBackendChoice.OpenGLES => X11RenderingBackend.OpenGLES, - RenderingBackendChoice.Vulkan => X11RenderingBackend.Vulkan, - _ => X11RenderingBackend.Default, - })) - .Build(); + if (!TryBecomePrimaryInstance(args)) + return 0; - using var scaleCancellation = new CancellationTokenSource(); - var scaleWatcher = DisplayScaleResolver.WatchCompositorScaleAsync(scaleCancellation.Token); try { - host.Run(); + RunHost(); } finally { - scaleCancellation.Cancel(); - scaleWatcher.GetAwaiter().GetResult(); FileTagsHelper.DrainPendingWritesAsync().GetAwaiter().GetResult(); } diff --git a/src/Files.App/Platforms/Desktop/Services/DesktopFileDragHelper.cs b/src/Files.App/Platforms/Desktop/Services/DesktopFileDragHelper.cs index 8690a1100632..380278dcb2f8 100644 --- a/src/Files.App/Platforms/Desktop/Services/DesktopFileDragHelper.cs +++ b/src/Files.App/Platforms/Desktop/Services/DesktopFileDragHelper.cs @@ -106,7 +106,7 @@ public static void StartDrag(DataPackage data, IEnumerable paths) var existing = ExistingPaths(paths); if (existing.Count > 0) - data.SetData(Files.Platform.Linux.Clipboard.ClipboardFormats.UriList, Files.Platform.Linux.Clipboard.ClipboardFormats.BuildUriList(existing)); + DesktopPlatform.SetDraggedFiles(data, existing); } /// diff --git a/src/Files.App/Platforms/Desktop/Services/LinuxIconHelper.cs b/src/Files.App/Platforms/Desktop/Services/LinuxIconHelper.cs index 7edce704d6dc..8bb392b2d75b 100644 --- a/src/Files.App/Platforms/Desktop/Services/LinuxIconHelper.cs +++ b/src/Files.App/Platforms/Desktop/Services/LinuxIconHelper.cs @@ -149,7 +149,7 @@ internal static IconColorScheme CurrentColorScheme try { byte[]? data; - using (var stream = Files.Platform.Linux.Previews.PreviewFile.OpenRead(iconPath)) + using (var stream = Files.Platform.Posix.Previews.PreviewFile.OpenRead(iconPath)) data = DesktopEntryDisplay.ReadBounded(stream, MaxIconFileSize); if (data is null) diff --git a/src/Files.App/Platforms/Desktop/ViewModels/ShellViewModel.Linux.cs b/src/Files.App/Platforms/Desktop/ViewModels/ShellViewModel.Desktop.cs similarity index 84% rename from src/Files.App/Platforms/Desktop/ViewModels/ShellViewModel.Linux.cs rename to src/Files.App/Platforms/Desktop/ViewModels/ShellViewModel.Desktop.cs index ae540f5f385f..77822b2ea1cb 100644 --- a/src/Files.App/Platforms/Desktop/ViewModels/ShellViewModel.Linux.cs +++ b/src/Files.App/Platforms/Desktop/ViewModels/ShellViewModel.Desktop.cs @@ -8,9 +8,9 @@ using Files.Platform.Abstractions.Enumeration; using Files.Platform.Abstractions.Trash; using Files.Platform.Abstractions.Watching; -using Files.Platform.Linux.Enumeration; -using Files.Platform.Linux.Mime; -using Files.Platform.Linux.Watching; +using Files.Platform.Posix.Enumeration; +using Files.Platform.Posix.Mime; +using Files.Platform.Posix.Watching; using Files.Shared.Helpers; using Microsoft.Extensions.Logging; using OwlCore.Storage; @@ -27,15 +27,15 @@ namespace Files.App.ViewModels /// public sealed partial class ShellViewModel { - private Files.Platform.Abstractions.Watching.IFolderWatcher? _linuxWatcher; - public bool IsLinuxWatcherPolling => _linuxWatcher?.IsPolling == true; - private CancellationTokenSource? _linuxRefreshDebounce; - private readonly List _linuxRepositoryWatchers = []; - private Action? _linuxTrashUnsubscribe; - private CancellationTokenSource? _linuxGitDebounce; - private readonly FolderChangeBatch _linuxChangeBatch = new(); - private CancellationTokenSource? _linuxChangeCts; - private int _linuxChangePending; + private Files.Platform.Abstractions.Watching.IFolderWatcher? _desktopWatcher; + public bool IsDesktopWatcherPolling => _desktopWatcher?.IsPolling == true; + private CancellationTokenSource? _desktopRefreshDebounce; + private readonly List _desktopRepositoryWatchers = []; + private Action? _desktopTrashUnsubscribe; + private CancellationTokenSource? _desktopGitDebounce; + private readonly FolderChangeBatch _desktopChangeBatch = new(); + private CancellationTokenSource? _desktopChangeCts; + private int _desktopChangePending; // The listed location as a storable; set for archive folders, which have no WinRT equivalent private StorableWithPath? currentStorable; @@ -43,14 +43,14 @@ public sealed partial class ShellViewModel /// /// Lists into filesAndFolders. Returns 3 on success and -1 on failure. /// - private async Task EnumerateLinuxFolderAsync(string path, CancellationToken cancellationToken, LibraryItem? library) + private async Task EnumerateDesktopFolderAsync(string path, CancellationToken cancellationToken, LibraryItem? library) { if (path.StartsWith(Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.Ordinal)) - return await EnumerateLinuxTrashAsync(path, cancellationToken); + return await EnumerateDesktopTrashAsync(path, cancellationToken); currentStorable = null; if (FileExtensionHelpers.IsZipPath(path, isArchiveFileName: Ioc.Default.GetRequiredService().IsArchiveFileName)) - return await EnumerateLinuxArchiveAsync(path, cancellationToken, library); + return await EnumerateDesktopArchiveAsync(path, cancellationToken, library); if (!Directory.Exists(path)) { @@ -116,7 +116,7 @@ await Task.Run(async () => if (entry.IsHidden && !includeHidden) continue; - var item = CreateLinuxListedItem(entry); + var item = CreateDesktopListedItem(entry); try { item.PreloadedIconData = await iconCache.GetIconAsync(item.ItemPath, item.FileExtension, entry.IsDirectory, iconSize); } catch (Exception ex) { App.Logger.LogWarning(ex, "Could not load icon for {Path}", item.ItemPath); } pending.Add(item); @@ -144,7 +144,7 @@ await Task.Run(async () => await ApplyFilesAndFoldersChangesAsync(); // Uno does not raise the container update callbacks that normally trigger this, so the status columns are filled in here - _ = LoadLinuxRepositoryPropertiesAsync(cancellationToken); + _ = LoadDesktopRepositoryPropertiesAsync(cancellationToken); // desktop.ini based customization has no Linux equivalent; the Windows services are stubs _ = dispatcherQueue.EnqueueOrInvokeAsync(CheckForSolutionFile, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low); @@ -166,7 +166,7 @@ await Task.Run(async () => /// Lists an archive root or a folder inside an archive through (read-only, no watcher). /// Returns 5 on success and -1 on failure. /// - private async Task EnumerateLinuxArchiveAsync(string path, CancellationToken cancellationToken, LibraryItem? library) + private async Task EnumerateDesktopArchiveAsync(string path, CancellationToken cancellationToken, LibraryItem? library) { HasNoWatcher = true; @@ -283,7 +283,7 @@ await Task.Run(async () => return 5; } - private async Task LoadLinuxRepositoryPropertiesAsync(CancellationToken cancellationToken) + private async Task LoadDesktopRepositoryPropertiesAsync(CancellationToken cancellationToken) { if (!IsValidGitDirectory || EnabledGitProperties is GitProperties.None) return; @@ -304,12 +304,12 @@ private async Task LoadLinuxRepositoryPropertiesAsync(CancellationToken cancella } // Items inside a repository use the Git item type so the status/commit columns can be filled in on demand - private ListedItem NewLinuxItem() => IsValidGitDirectory ? new GitItem() : new ListedItem(null); + private ListedItem NewDesktopItem() => IsValidGitDirectory ? new GitItem() : new ListedItem(null); /// /// Lists the freedesktop.org trash (trash:///) through . Returns 4 on success. /// - private async Task EnumerateLinuxTrashAsync(string path, CancellationToken cancellationToken) + private async Task EnumerateDesktopTrashAsync(string path, CancellationToken cancellationToken) { currentStorageFolder = null; HasNoWatcher = false; @@ -327,7 +327,7 @@ private async Task EnumerateLinuxTrashAsync(string path, CancellationToken FileSizeBytes = 0, }; - var items = await BuildLinuxTrashItemsAsync(cancellationToken); + var items = await BuildDesktopTrashItemsAsync(cancellationToken); if (cancellationToken.IsCancellationRequested) return -1; @@ -342,7 +342,7 @@ private async Task EnumerateLinuxTrashAsync(string path, CancellationToken return 4; } - private async Task> BuildLinuxTrashItemsAsync(CancellationToken cancellationToken) + private async Task> BuildDesktopTrashItemsAsync(CancellationToken cancellationToken) { var trashed = await StorageTrashBinService.GetAllRecycleBinFoldersAsync(); var iconCache = Ioc.Default.GetRequiredService(); @@ -392,15 +392,15 @@ private async Task> BuildLinuxTrashItemsAsync(CancellationToken // Diffs the trash against the listed items by trash path and applies only the additions and removals, so the // selection and scroll position survive. Falls back to a full apply when grouping or a filter is active. - private async Task RefreshLinuxTrashIncrementallyAsync() + private async Task RefreshDesktopTrashIncrementallyAsync() { if (isDisposed || WorkingDirectory != Constants.UserEnvironmentPaths.RecycleBinPath) return; - var cts = _linuxRefreshDebounce; + var cts = _desktopRefreshDebounce; var token = cts?.Token ?? CancellationToken.None; - var fresh = await BuildLinuxTrashItemsAsync(token); + var fresh = await BuildDesktopTrashItemsAsync(token); if (token.IsCancellationRequested) return; @@ -448,15 +448,15 @@ await dispatcherQueue.EnqueueOrInvokeAsync(async () => }); } - private async Task RefreshLinuxTrashAfterChangeAsync() + private async Task RefreshDesktopTrashAfterChangeAsync() { - _linuxRefreshDebounce?.Cancel(); - var cts = _linuxRefreshDebounce = new CancellationTokenSource(); + _desktopRefreshDebounce?.Cancel(); + var cts = _desktopRefreshDebounce = new CancellationTokenSource(); try { await Task.Delay(300, cts.Token); - await RefreshLinuxTrashIncrementallyAsync(); + await RefreshDesktopTrashIncrementallyAsync(); } catch (OperationCanceledException) { @@ -468,17 +468,17 @@ private async Task RefreshLinuxTrashAfterChangeAsync() } // Trash changes are pushed by ITrashService.Watcher rather than by a folder watcher on trash:/// - private void WatchForLinuxTrashChanges() + private void WatchForDesktopTrashChanges() { if (isDisposed) return; - CloseLinuxWatcher(); + CloseDesktopWatcher(); try { var notifier = Ioc.Default.GetRequiredService().Watcher; - void OnChanged(object? s, EventArgs e) => _ = RefreshLinuxTrashAfterChangeAsync(); + void OnChanged(object? s, EventArgs e) => _ = RefreshDesktopTrashAfterChangeAsync(); notifier.ItemAdded += OnChanged; notifier.ItemDeleted += OnChanged; @@ -487,7 +487,7 @@ private void WatchForLinuxTrashChanges() notifier.RefreshRequested += OnChanged; notifier.StartWatcher(); - _linuxTrashUnsubscribe = () => + _desktopTrashUnsubscribe = () => { notifier.ItemAdded -= OnChanged; notifier.ItemDeleted -= OnChanged; @@ -502,7 +502,7 @@ private void WatchForLinuxTrashChanges() } } - private ListedItem CreateLinuxListedItem(FileSystemEntryInfo entry) + private ListedItem CreateDesktopListedItem(FileSystemEntryInfo entry) { var opacity = entry.IsHidden ? Constants.UI.DimItemOpacity : 1d; var modified = entry.LastWriteTimeUtc.ToLocalTime(); @@ -511,7 +511,7 @@ private ListedItem CreateLinuxListedItem(FileSystemEntryInfo entry) if (entry.IsDirectory) { - var folder = NewLinuxItem(); + var folder = NewDesktopItem(); folder.PrimaryItemAttribute = StorageItemTypes.Folder; folder.ItemNameRaw = entry.Name; folder.ItemDateModifiedReal = modified; @@ -524,7 +524,7 @@ private ListedItem CreateLinuxListedItem(FileSystemEntryInfo entry) folder.ItemPath = entry.FullPath; folder.FileSize = null; folder.FileSizeBytes = 0; - ApplyLinuxLinkInfo(folder, entry); + ApplyDesktopLinkInfo(folder, entry); return folder; } @@ -537,7 +537,7 @@ private ListedItem CreateLinuxListedItem(FileSystemEntryInfo entry) itemType = !string.IsNullOrEmpty(localizedType) ? localizedType : extension.Trim('.') + " " + itemType; } - var item = NewLinuxItem(); + var item = NewDesktopItem(); item.PrimaryItemAttribute = StorageItemTypes.File; item.FileExtension = extension; item.IsHiddenItem = entry.IsHidden; @@ -552,7 +552,7 @@ private ListedItem CreateLinuxListedItem(FileSystemEntryInfo entry) item.ItemPath = entry.FullPath; item.FileSize = entry.Length.ToSizeString(); item.FileSizeBytes = entry.Length; - ApplyLinuxLinkInfo(item, entry); + ApplyDesktopLinkInfo(item, entry); // Display only: the name comes from the strictly parsed entry; opening goes through the launcher confirmation if (!entry.IsBrokenSymlink && DesktopEntryDisplay.IsDesktopFile(entry.Name) && @@ -571,21 +571,21 @@ private static void ApplyLinkInfoCore(ListedItem item, string? linkTarget, bool item.SymLinkTarget = linkTarget; item.IsBrokenSymLink = broken; if (broken) - item.ItemType = Strings.LinuxBrokenLink.GetLocalizedResource(); + item.ItemType = Strings.DesktopBrokenLink.GetLocalizedResource(); } - private static void ApplyLinuxLinkInfo(ListedItem item, FileSystemEntryInfo entry) + private static void ApplyDesktopLinkInfo(ListedItem item, FileSystemEntryInfo entry) { if (entry.IsSymlink) ApplyLinkInfoCore(item, entry.LinkTarget ?? string.Empty, entry.IsBrokenSymlink); } - private void WatchForLinuxFolderChanges(string path) + private void WatchForDesktopFolderChanges(string path) { if (isDisposed) return; - CloseLinuxWatcher(); + CloseDesktopWatcher(); try { @@ -601,11 +601,11 @@ private void WatchForLinuxFolderChanges(string path) void OnPath(object? s, FolderChangeEventArgs e) { if (string.Equals(e.FullPath, folder, StringComparison.Ordinal)) - _linuxChangeBatch.RequestFullRefresh(); + _desktopChangeBatch.RequestFullRefresh(); else - _linuxChangeBatch.Add(e.FullPath); + _desktopChangeBatch.Add(e.FullPath); - ScheduleLinuxChanges(folder); + ScheduleDesktopChanges(folder); } watcherInstance.Created += OnPath; @@ -613,17 +613,17 @@ void OnPath(object? s, FolderChangeEventArgs e) watcherInstance.Changed += OnPath; watcherInstance.Renamed += (_, e) => { - _linuxChangeBatch.AddRename(e.OldFullPath, e.FullPath); - ScheduleLinuxChanges(folder); + _desktopChangeBatch.AddRename(e.OldFullPath, e.FullPath); + ScheduleDesktopChanges(folder); }; watcherInstance.RescanRequired += (_, _) => { - _linuxChangeBatch.RequestFullRefresh(); - ScheduleLinuxChanges(folder); + _desktopChangeBatch.RequestFullRefresh(); + ScheduleDesktopChanges(folder); }; watcherInstance.Start(); - _linuxWatcher = watcherInstance; + _desktopWatcher = watcherInstance; } catch (Exception ex) { @@ -632,10 +632,10 @@ void OnPath(object? s, FolderChangeEventArgs e) } // Used by the trash, whose changes always reload the whole listing - private async Task RefreshAfterLinuxChangeAsync() + private async Task RefreshAfterDesktopChangeAsync() { - _linuxRefreshDebounce?.Cancel(); - var cts = _linuxRefreshDebounce = new CancellationTokenSource(); + _desktopRefreshDebounce?.Cancel(); + var cts = _desktopRefreshDebounce = new CancellationTokenSource(); try { @@ -648,21 +648,21 @@ private async Task RefreshAfterLinuxChangeAsync() } // The first event of a burst opens a short window; everything arriving inside it is applied together - private void ScheduleLinuxChanges(string folder) + private void ScheduleDesktopChanges(string folder) { - if (Interlocked.Exchange(ref _linuxChangePending, 1) == 1) + if (Interlocked.Exchange(ref _desktopChangePending, 1) == 1) return; var cts = new CancellationTokenSource(); - Interlocked.Exchange(ref _linuxChangeCts, cts)?.Cancel(); + Interlocked.Exchange(ref _desktopChangeCts, cts)?.Cancel(); _ = Task.Run(async () => { try { await Task.Delay(150, cts.Token); - Interlocked.Exchange(ref _linuxChangePending, 0); - await ApplyLinuxChangesAsync(folder, cts.Token); + Interlocked.Exchange(ref _desktopChangePending, 0); + await ApplyDesktopChangesAsync(folder, cts.Token); } catch (OperationCanceledException) { @@ -714,9 +714,9 @@ private bool TryApplyIncrementalDisplayChanges(List desired) /// Applies a burst of watcher events as add, remove, rename and update operations on the existing items. Falls back to a /// full reload when events were lost or the burst was too large. /// - private async Task ApplyLinuxChangesAsync(string folder, CancellationToken cancellationToken) + private async Task ApplyDesktopChangesAsync(string folder, CancellationToken cancellationToken) { - var snapshot = _linuxChangeBatch.Drain(); + var snapshot = _desktopChangeBatch.Drain(); if (cancellationToken.IsCancellationRequested || isDisposed) return; @@ -778,7 +778,7 @@ private async Task ApplyLinuxChangesAsync(string folder, CancellationToken cance case FolderChangeKind.Rename: var renamed = items[op.Path]; var target = entries[op.NewPath!]; - if (CanRenameLinuxItemInPlace(renamed, target)) + if (CanRenameDesktopItemInPlace(renamed, target)) { await dispatcherQueue.EnqueueOrInvokeAsync(() => { @@ -793,7 +793,7 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() => { filesAndFolders.Remove(renamed); removedAny = true; - if (await TryCreateLinuxItemAsync(target, iconCache, iconSize) is { } recreated) + if (await TryCreateDesktopItemAsync(target, iconCache, iconSize) is { } recreated) added.Add(recreated); } break; @@ -801,7 +801,7 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() => case FolderChangeKind.Update: var existing = items[op.Path]; var fresh = entries[op.Path]; - if (IsSameLinuxItemKind(existing, fresh)) + if (IsSameDesktopItemKind(existing, fresh)) { await dispatcherQueue.EnqueueOrInvokeAsync(() => { @@ -821,13 +821,13 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() => { filesAndFolders.Remove(existing); removedAny = true; - if (await TryCreateLinuxItemAsync(fresh, iconCache, iconSize) is { } replaced) + if (await TryCreateDesktopItemAsync(fresh, iconCache, iconSize) is { } replaced) added.Add(replaced); } break; case FolderChangeKind.Add: - if (await TryCreateLinuxItemAsync(entries[op.Path], iconCache, iconSize) is { } created) + if (await TryCreateDesktopItemAsync(entries[op.Path], iconCache, iconSize) is { } created) added.Add(created); break; } @@ -851,11 +851,11 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() => } } - private async Task TryCreateLinuxItemAsync(FileSystemEntryInfo entry, IIconCacheService iconCache, uint iconSize) + private async Task TryCreateDesktopItemAsync(FileSystemEntryInfo entry, IIconCacheService iconCache, uint iconSize) { try { - var item = CreateLinuxListedItem(entry); + var item = CreateDesktopListedItem(entry); try { item.PreloadedIconData = await iconCache.GetIconAsync(item.ItemPath, item.FileExtension, entry.IsDirectory, iconSize); } catch (Exception ex) { App.Logger.LogWarning(ex, "Could not load icon for {Path}", item.ItemPath); } return item; } @@ -866,27 +866,27 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() => } // A rename keeps the item (and its position) only when nothing derived from the name changes - private static bool CanRenameLinuxItemInPlace(ListedItem item, FileSystemEntryInfo target) - => IsSameLinuxItemKind(item, target) && + private static bool CanRenameDesktopItemInPlace(ListedItem item, FileSystemEntryInfo target) + => IsSameDesktopItemKind(item, target) && string.Equals(Path.GetExtension(item.ItemPath), Path.GetExtension(target.Name), StringComparison.OrdinalIgnoreCase) && item.IsHiddenItem == target.IsHidden; - private static bool IsSameLinuxItemKind(ListedItem item, FileSystemEntryInfo entry) + private static bool IsSameDesktopItemKind(ListedItem item, FileSystemEntryInfo entry) => item.IsFolder == entry.IsDirectory && item.IsBrokenSymLink == entry.IsBrokenSymlink && item.SymLinkTarget == (entry.IsSymlink ? entry.LinkTarget ?? string.Empty : null) && item.DisplayNameOverride is null; // Raises GitDirectoryUpdated when the repository metadata changes (commit, checkout, stage, fetch) so the status columns refresh - private void WatchForLinuxRepositoryChanges() + private void WatchForDesktopRepositoryChanges() { if (isDisposed || string.IsNullOrEmpty(GitDirectory)) return; - DisposeLinuxRepositoryWatchers(); + DisposeDesktopRepositoryWatchers(); // .git may be a gitfile (linked worktree, submodule); HEAD and the index live in the git dir, refs in the common dir - if (Files.Platform.Linux.Search.GitDirectoryResolver.Resolve(GitDirectory) is not var (gitDir, commonDir)) + if (Files.Platform.Posix.Search.GitDirectoryResolver.Resolve(GitDirectory) is not var (gitDir, commonDir)) return; try @@ -912,7 +912,7 @@ void OnChanged(string root, FolderChangeEventArgs e) if (e.Name.EndsWith(".lock", StringComparison.Ordinal) || relative.StartsWith("objects/", StringComparison.Ordinal) || relative.Contains("/objects/", StringComparison.Ordinal)) return; - ScheduleLinuxGitRefresh(); + ScheduleDesktopGitRefresh(); } foreach (var (path, recursive) in targets.DistinctBy(t => t.Path)) @@ -927,7 +927,7 @@ void OnChanged(string root, FolderChangeEventArgs e) watcherInstance.Changed += (_, e) => OnChanged(root, e); watcherInstance.Renamed += (_, e) => OnChanged(root, e); watcherInstance.Start(); - _linuxRepositoryWatchers.Add(watcherInstance); + _desktopRepositoryWatchers.Add(watcherInstance); } } catch (Exception ex) @@ -937,22 +937,22 @@ void OnChanged(string root, FolderChangeEventArgs e) } // Several watchers fire for one Git command; coalesce them into one reload - private void ScheduleLinuxGitRefresh() + private void ScheduleDesktopGitRefresh() { var cts = new CancellationTokenSource(); - Interlocked.Exchange(ref _linuxGitDebounce, cts)?.Cancel(); + Interlocked.Exchange(ref _desktopGitDebounce, cts)?.Cancel(); _ = Task.Delay(TimeSpan.FromMilliseconds(400), cts.Token).ContinueWith( _ => dispatcherQueue.EnqueueOrInvokeAsync(async () => { - await ReloadLinuxGitPropertiesAsync(); + await ReloadDesktopGitPropertiesAsync(); GitDirectoryUpdated?.Invoke(null, null!); }), cts.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); } // The Git columns are loaded once per item; after the repository changed they are stale, so reset the flags and reload - private async Task ReloadLinuxGitPropertiesAsync() + private async Task ReloadDesktopGitPropertiesAsync() { if (isDisposed || !IsValidGitDirectory) return; @@ -976,24 +976,24 @@ private async Task ReloadLinuxGitPropertiesAsync() } } - private void DisposeLinuxRepositoryWatchers() + private void DisposeDesktopRepositoryWatchers() { - foreach (var w in _linuxRepositoryWatchers) + foreach (var w in _desktopRepositoryWatchers) w.Dispose(); - _linuxRepositoryWatchers.Clear(); + _desktopRepositoryWatchers.Clear(); } - private void CloseLinuxWatcher() + private void CloseDesktopWatcher() { - DisposeLinuxRepositoryWatchers(); - Interlocked.Exchange(ref _linuxGitDebounce, null)?.Cancel(); - _linuxRefreshDebounce?.Cancel(); - Interlocked.Exchange(ref _linuxChangeCts, null)?.Cancel(); - Interlocked.Exchange(ref _linuxChangePending, 0); - _linuxChangeBatch.Drain(); - _linuxWatcher?.Dispose(); - _linuxWatcher = null; - Interlocked.Exchange(ref _linuxTrashUnsubscribe, null)?.Invoke(); + DisposeDesktopRepositoryWatchers(); + Interlocked.Exchange(ref _desktopGitDebounce, null)?.Cancel(); + _desktopRefreshDebounce?.Cancel(); + Interlocked.Exchange(ref _desktopChangeCts, null)?.Cancel(); + Interlocked.Exchange(ref _desktopChangePending, 0); + _desktopChangeBatch.Drain(); + _desktopWatcher?.Dispose(); + _desktopWatcher = null; + Interlocked.Exchange(ref _desktopTrashUnsubscribe, null)?.Invoke(); } } } diff --git a/src/Files.App/Platforms/Desktop/WindowEx.cs b/src/Files.App/Platforms/Desktop/WindowEx.cs index 2620a7cd8548..ab980929d7d3 100644 --- a/src/Files.App/Platforms/Desktop/WindowEx.cs +++ b/src/Files.App/Platforms/Desktop/WindowEx.cs @@ -97,7 +97,7 @@ private void ApplyDefaultSize() { var work = DisplayArea.Primary.WorkArea; // AppWindow sizes are physical pixels; the caps are logical - var scale = Files.Platform.Linux.Windowing.DisplayScaleResolver.GetEffectiveScale(); + var scale = DesktopPlatform.GetDisplayScale(); var width = Math.Clamp((int)(work.Width * 0.8), Math.Min((int)(MinWidth * scale), work.Width), (int)(1600 * scale)); var height = Math.Clamp((int)(work.Height * 0.8), Math.Min((int)(MinHeight * scale), work.Height), (int)(1000 * scale)); AppWindow.Resize(new Windows.Graphics.SizeInt32 { Width = width, Height = height }); diff --git a/src/Files.App/Services/App/AppResourcesService.Linux.cs b/src/Files.App/Services/App/AppResourcesService.Linux.cs index a38771791c40..d38d1fef8ea4 100644 --- a/src/Files.App/Services/App/AppResourcesService.Linux.cs +++ b/src/Files.App/Services/App/AppResourcesService.Linux.cs @@ -16,21 +16,21 @@ namespace Files.App.Services public sealed partial class ResourcesService { private readonly Dictionary _manualColors = new(); - private readonly HashSet _linuxOverrides = new(); + private readonly HashSet _desktopOverrides = new(); private ISystemAppearanceService SystemAppearance => Ioc.Default.GetRequiredService(); private bool _refreshing; private static readonly string[] SurfaceKeys = ["App.Theme.BackgroundBrush", "App.Theme.AddressBar.BackgroundBrush", "App.Theme.Toolbar.BackgroundBrush", "App.Theme.Sidebar.BackgroundBrush", "App.Theme.FileArea.BackgroundBrush", "App.Theme.FileArea.SecondaryBackgroundBrush", "App.Theme.InfoPane.BackgroundBrush", "ToolbarBackgroundBrush", "TabViewItemHeaderBackgroundSelected"]; - private void InitializeLinuxAppearance() + private void InitializeDesktopAppearance() { if (!OperatingSystem.IsLinux()) return; SystemAppearance.Changed += (_, _) => MainWindow.Instance.DispatcherQueue.TryEnqueue(ApplyResources); X11AppearanceSupport.Changed += (_, _) => MainWindow.Instance.DispatcherQueue.TryEnqueue(ApplyResources); AppThemeModeService.AppThemeModeChanged += (_, _) => { - if (!_refreshing) UpdateLinuxAppearanceResources(); + if (!_refreshing) UpdateDesktopAppearanceResources(); }; _ = RefreshSystemAppearanceAsync(); } @@ -41,7 +41,7 @@ private async Task RefreshSystemAppearanceAsync() catch (OperationCanceledException) { } } - private void SetLinuxManualColor(string key, Color color) + private void SetDesktopManualColor(string key, Color color) { if (color.A == 0) _manualColors.Remove(key); else _manualColors[key] = color; @@ -58,15 +58,15 @@ private void SetLinuxManualColor(string key, Color color) return null; } - private void UpdateLinuxAppearanceResources() + private void UpdateDesktopAppearanceResources() { if (!OperatingSystem.IsLinux() || _refreshing) return; _refreshing = true; try { var resources = Application.Current.Resources; - foreach (var key in _linuxOverrides) resources.Remove(key); - _linuxOverrides.Clear(); + foreach (var key in _desktopOverrides) resources.Remove(key); + _desktopOverrides.Clear(); var settings = UserSettingsService.AppearanceSettingsService; var system = SystemAppearance.Current; var dark = AppThemeModeService.AppThemeMode == ElementTheme.Dark || @@ -125,7 +125,7 @@ private void UpdateLinuxAppearanceResources() { resources[key] = key.StartsWith("SystemAccentColor", StringComparison.Ordinal) || key == "App.Theme.FillColorAttention" ? (object)color : new SolidColorBrush(color); - _linuxOverrides.Add(key); + _desktopOverrides.Add(key); } } finally { _refreshing = false; } diff --git a/src/Files.App/Services/App/AppResourcesService.cs b/src/Files.App/Services/App/AppResourcesService.cs index dd99b84a2c2b..5ab6155a51e8 100644 --- a/src/Files.App/Services/App/AppResourcesService.cs +++ b/src/Files.App/Services/App/AppResourcesService.cs @@ -19,7 +19,7 @@ public ResourcesService() { UpdateAppearanceResources(); #if HAS_UNO - InitializeLinuxAppearance(); + InitializeDesktopAppearance(); #endif UserSettingsService.AppearanceSettingsService.PropertyChanged += AppearanceSettingsService_PropertyChanged; @@ -102,7 +102,7 @@ public void SetAppThemeBackgroundColor(Color appThemeBackgroundColor) #if HAS_UNO if (OperatingSystem.IsLinux()) { - SetLinuxManualColor("App.Theme.BackgroundBrush", appThemeBackgroundColor); + SetDesktopManualColor("App.Theme.BackgroundBrush", appThemeBackgroundColor); return; } #endif @@ -115,7 +115,7 @@ public void SetAppThemeAddressBarBackgroundColor(Color appThemeAddressBarBackgro #if HAS_UNO if (OperatingSystem.IsLinux()) { - SetLinuxManualColor("App.Theme.AddressBar.BackgroundBrush", appThemeAddressBarBackgroundColor); + SetDesktopManualColor("App.Theme.AddressBar.BackgroundBrush", appThemeAddressBarBackgroundColor); return; } #endif @@ -131,7 +131,7 @@ public void SetAppThemeToolbarBackgroundColor(Color appThemeToolbarBackgroundCol #if HAS_UNO if (OperatingSystem.IsLinux()) { - SetLinuxManualColor("App.Theme.Toolbar.BackgroundBrush", appThemeToolbarBackgroundColor); + SetDesktopManualColor("App.Theme.Toolbar.BackgroundBrush", appThemeToolbarBackgroundColor); return; } #endif @@ -144,7 +144,7 @@ public void SetAppThemeSidebarBackgroundColor(Color appThemeSidebarBackgroundCol #if HAS_UNO if (OperatingSystem.IsLinux()) { - SetLinuxManualColor("App.Theme.Sidebar.BackgroundBrush", appThemeSidebarBackgroundColor); + SetDesktopManualColor("App.Theme.Sidebar.BackgroundBrush", appThemeSidebarBackgroundColor); return; } #endif @@ -157,7 +157,7 @@ public void SetAppThemeFileAreaBackgroundColor(Color appThemeFileAreaBackgroundC #if HAS_UNO if (OperatingSystem.IsLinux()) { - SetLinuxManualColor("App.Theme.FileArea.BackgroundBrush", appThemeFileAreaBackgroundColor); + SetDesktopManualColor("App.Theme.FileArea.BackgroundBrush", appThemeFileAreaBackgroundColor); return; } #endif @@ -170,7 +170,7 @@ public void SetAppThemeFileAreaSecondaryBackgroundColor(Color appThemeFileAreaSe #if HAS_UNO if (OperatingSystem.IsLinux()) { - SetLinuxManualColor("App.Theme.FileArea.SecondaryBackgroundBrush", appThemeFileAreaSecondaryBackgroundColor); + SetDesktopManualColor("App.Theme.FileArea.SecondaryBackgroundBrush", appThemeFileAreaSecondaryBackgroundColor); return; } #endif @@ -183,7 +183,7 @@ public void SetAppThemeInfoPaneBackgroundColor(Color appThemeInfoPaneBackgroundC #if HAS_UNO if (OperatingSystem.IsLinux()) { - SetLinuxManualColor("App.Theme.InfoPane.BackgroundBrush", appThemeInfoPaneBackgroundColor); + SetDesktopManualColor("App.Theme.InfoPane.BackgroundBrush", appThemeInfoPaneBackgroundColor); return; } #endif @@ -206,7 +206,7 @@ public void SetScrollInertiaEnabled(bool enableScrollInertia) public void ApplyResources() { #if HAS_UNO - UpdateLinuxAppearanceResources(); + UpdateDesktopAppearanceResources(); #endif AppThemeModeService.ApplyResources(); } diff --git a/src/Files.App/Services/App/AppThemeModeService.cs b/src/Files.App/Services/App/AppThemeModeService.cs index 6c6009cbc81c..2465b6135919 100644 --- a/src/Files.App/Services/App/AppThemeModeService.cs +++ b/src/Files.App/Services/App/AppThemeModeService.cs @@ -65,9 +65,10 @@ public AppThemeModeService() UISettings.ColorValuesChanged += UISettings_ColorValuesChanged; #if HAS_UNO - Files.Platform.Linux.Theme.LinuxColorScheme.Initialize(); - Files.Platform.Linux.Theme.LinuxColorScheme.Changed += async (_, _) => + var appearance = Ioc.Default.GetRequiredService(); + appearance.Changed += async (_, _) => await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => SetAppThemeMode()); + _ = appearance.RefreshAsync(); #endif } @@ -78,7 +79,7 @@ private static ElementTheme ResolveRootTheme(ElementTheme theme) { #if HAS_UNO if (theme is ElementTheme.Default) - return Files.Platform.Linux.Theme.LinuxColorScheme.IsDark ? ElementTheme.Dark : ElementTheme.Light; + return Ioc.Default.GetService()?.Current.IsDark == true ? ElementTheme.Dark : ElementTheme.Light; #endif return theme; } diff --git a/src/Files.App/Services/Git/LibGit2NativeResolver.cs b/src/Files.App/Services/Git/LibGit2NativeResolver.cs index 4789103c75d1..b2d87ca3ea1a 100644 --- a/src/Files.App/Services/Git/LibGit2NativeResolver.cs +++ b/src/Files.App/Services/Git/LibGit2NativeResolver.cs @@ -1,7 +1,7 @@ // Copyright (c) Files Community // Licensed under the MIT License. -using Files.Platform.Linux.Native; +using Files.Platform.Posix.Native; using LibGit2Sharp; using System.IO; using System.Reflection; diff --git a/src/Files.App/Services/SizeProvider/CachedSizeProvider.Linux.cs b/src/Files.App/Services/SizeProvider/CachedSizeProvider.Desktop.cs similarity index 100% rename from src/Files.App/Services/SizeProvider/CachedSizeProvider.Linux.cs rename to src/Files.App/Services/SizeProvider/CachedSizeProvider.Desktop.cs diff --git a/src/Files.App/Services/Storage/StorageDevicesService.cs b/src/Files.App/Services/Storage/StorageDevicesService.cs index 1d4e93f27195..c15769fb84ea 100644 --- a/src/Files.App/Services/Storage/StorageDevicesService.cs +++ b/src/Files.App/Services/Storage/StorageDevicesService.cs @@ -12,19 +12,19 @@ public sealed class RemovableDrivesService : IRemovableDrivesService { public IStorageDeviceWatcher CreateWatcher() { - return new LinuxStorageDeviceWatcher(); + return new DesktopStorageDeviceWatcher(); } public async IAsyncEnumerable GetDrivesAsync() { // Mounted drives (mountinfo + UDisks2 labels), volumes that are not mounted yet and MTP devices (GVfs) - var entries = await LinuxDriveCatalog.EnumerateAsync().ConfigureAwait(false); - var linuxPending = entries.Select(entry => entry.CreateAsync()).ToList(); + var entries = await DesktopDriveCatalog.EnumerateAsync().ConfigureAwait(false); + var desktopPending = entries.Select(entry => entry.CreateAsync()).ToList(); - await foreach (var completed in Task.WhenEach(linuxPending)) + await foreach (var completed in Task.WhenEach(desktopPending)) { - if (await completed is { } linuxItem) - yield return linuxItem; + if (await completed is { } desktopItem) + yield return desktopItem; } } diff --git a/src/Files.App/Services/Storage/StorageNetworkService.Linux.cs b/src/Files.App/Services/Storage/StorageNetworkService.Linux.cs index ddbc1a86f134..3e6b9d4375d4 100644 --- a/src/Files.App/Services/Storage/StorageNetworkService.Linux.cs +++ b/src/Files.App/Services/Storage/StorageNetworkService.Linux.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using Files.App.Dialogs; -using Files.Platform.Abstractions.Gvfs; +using Files.Platform.Abstractions.Network; using Files.Platform.Linux.Gvfs; using Microsoft.Extensions.Logging; using Microsoft.UI.Xaml.Controls; @@ -25,7 +25,7 @@ public sealed partial class NetworkService private static bool IsNetworkKind(GvfsMount mount) => mount.Kind is not (GvfsMountKind.Mtp or GvfsMountKind.Gphoto); - private void StartLinuxWatching() + private void StartPlatformWatching() { if (Gvfs is not { } gvfs) return; @@ -50,8 +50,8 @@ await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () => App.Logger.LogInformation("Watching GVfs mounts in {Directory} ({Count} now)", NetworkRootPath, gvfs.GetMounts().Count); } - private IEnumerable GetLinuxNetworkItems() - => (Gvfs?.GetMounts() ?? []).Where(IsNetworkKind).Select(m => (IFolder)LinuxDriveCatalog.CreateNetworkItem(m)); + private IEnumerable GetPlatformNetworkItems() + => (Gvfs?.GetMounts() ?? []).Where(IsNetworkKind).Select(m => (IFolder)DesktopDriveCatalog.CreateNetworkItem(m)); /// public Task GetNetworkAvailabilityAsync() @@ -85,7 +85,7 @@ public async Task OpenMapNetworkDriveDialogAsync() DynamicDialog? dialog = null; var input = new TextBox { - PlaceholderText = Strings.LinuxConnectToServerPlaceholder.GetLocalizedResource(), + PlaceholderText = Strings.DesktopConnectToServerPlaceholder.GetLocalizedResource(), MinWidth = 320d, }; @@ -99,9 +99,9 @@ public async Task OpenMapNetworkDriveDialogAsync() dialog = new DynamicDialog(new DynamicDialogViewModel() { - TitleText = Strings.LinuxConnectToServerTitle.GetLocalizedResource(), - SubtitleText = Strings.LinuxConnectToServerSubtitle.GetLocalizedResource(), - PrimaryButtonText = Strings.LinuxConnectToServerButton.GetLocalizedResource(), + TitleText = Strings.DesktopConnectToServerTitle.GetLocalizedResource(), + SubtitleText = Strings.DesktopConnectToServerSubtitle.GetLocalizedResource(), + PrimaryButtonText = Strings.DesktopConnectToServerButton.GetLocalizedResource(), CloseButtonText = Strings.Cancel.GetLocalizedResource(), DisplayControl = new StackPanel { Children = { input } }, DynamicButtons = DynamicDialogButtons.Primary | DynamicDialogButtons.Cancel, @@ -121,8 +121,8 @@ public async Task OpenMapNetworkDriveDialogAsync() if (!await gvfs.ConnectAsync(uri)) { await DialogDisplayHelper.ShowDialogAsync( - Strings.LinuxConnectToServerTitle.GetLocalizedResource(), - string.Format(Strings.LinuxConnectToServerFailed.GetLocalizedResource(), uri)); + Strings.DesktopConnectToServerTitle.GetLocalizedResource(), + string.Format(Strings.DesktopConnectToServerFailed.GetLocalizedResource(), uri)); } } diff --git a/src/Files.App/Services/Storage/StorageNetworkService.cs b/src/Files.App/Services/Storage/StorageNetworkService.cs index 21edc24e61f4..886181f2db27 100644 --- a/src/Files.App/Services/Storage/StorageNetworkService.cs +++ b/src/Files.App/Services/Storage/StorageNetworkService.cs @@ -48,20 +48,20 @@ public NetworkService() lock (_Computers) _Computers.Add(networkItem); - StartLinuxWatching(); + StartPlatformWatching(); } /// public async Task> GetComputersAsync() { // Connected GVfs locations (smb, sftp, ftp, dav, nfs...); LINUX-TODO(network-discovery): browse the LAN with Avahi - return await Task.FromResult(GetLinuxNetworkItems()); + return await Task.FromResult(GetPlatformNetworkItems()); } /// public async Task> GetShortcutsAsync() { - return await Task.FromResult(GetLinuxNetworkItems()); + return await Task.FromResult(GetPlatformNetworkItems()); } /// diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index cced72f02c60..9987b4738393 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -4813,37 +4813,37 @@ New text document - + Always use this app for this file type - + Run "{0}"? - + This is an executable text file ({0}). Do you want to run it or display its contents? - + Display - + Launch "{0}"? - + This launcher is not installed and not trusted. Only run launchers from sources you trust. - + This is an executable program ({0}). Only run programs from sources you trust. - + Open {0} files? - + Opening many files at once may be slow or unintended. - + Cannot open file - + No application could open {0}. @@ -4859,16 +4859,16 @@ The archive could not be extracted - + File changed - + {0} changed after it was checked, so it was not opened. - + Cannot open file - + {0} is an executable-looking or invalid file that cannot be opened safely. @@ -4877,73 +4877,73 @@ ... and {0} more entries - + Mount - + Unmount - + Disconnect - + Connect to server - + Enter the address of a server, for example smb://server/share, sftp://user@host/, ftp://host/ or davs://host/path. Saved credentials from your desktop keyring are used; passwords cannot be typed here. - + smb://server/share - + Connect - + Could not connect to {0}. Check the address, and make sure the gio tool and the GVfs services are installed. - + Problem with the device - + You are not authorized to do this. - + This drive is not mounted. - + File: - + Claims to be: - + Would run: - + in a terminal - + The command is too long to be shown in full, so it will not be run. - + Use Files as default file manager - + Let other applications reveal files in Files (org.freedesktop.FileManager1). - + Open in terminal - + Show option to open folders in the terminal - + Open in terminal as administrator {0} more folders… - + Broken link @@ -4970,20 +4970,20 @@ "{0}" could not be installed. Make sure it is a valid font file. Shown when installing a font on Linux. - + LinuxFiles Display name of the Linux port, shown in the window title and About page on Linux. - + Show window controls in the tab bar - + Minimize - + Maximize - + Restore @@ -4993,7 +4993,7 @@ Browse the contents of the selected archive like a folder - + File System @@ -5042,25 +5042,25 @@ Preparing... - + This service-menu action will run the command shown below on your selection. Only run actions from sources you trust. - + This service-menu action has changed, is too large, or uses an unsupported command. Shell commands and embedded file substitutions are not supported. - + Run service-menu action “{0}”? - + This service-menu action runs a separate command for each item. Select at most {0} items and try again. - + Open in terminal as root - + (Root mode) - + (Running as root) @@ -5075,64 +5075,64 @@ Makes the whole window see-through. Requires a compositing window manager. - + Files - + System (GTK/portal) - + Colour source - + Use Files colours, Adwaita, or available system colours. Missing GTK colours use the app palette. Your light/dark and custom colour choices are preserved. - + Window background - + Solid - + Transparent - + Blur - + A translucent background keeps text and icons opaque. Blur requires the KWin X11 blur effect. Renderer support may vary. - + Use an opaque background. Background transparency and KWin blur depend on the compositor. - + This compositor or bridge does not advertise the requested effect, or high contrast is enabled. Using a solid background. - + Background opacity - + Opacity of the background tint in Transparent or Blur mode. Text, icons and popups stay opaque. - + xwayland-satellite does not forward whole-window opacity. This control is unavailable. - + Whole-window opacity support is unknown for this compositor or bridge. This control is unavailable. - + No X11 compositing manager was detected. Whole-window opacity is unavailable. - + Fades the entire window, including text and icons. Requires compositor support. - + High contrast keeps the window opaque. Your opacity choice is retained. - + Detect display scale from the compositor (experimental) - + Requires a restart. Uses the focused compositor output scale (niri, sway or Hyprland), KDE display scale, or GNOME text scale when no display or toolkit scale is configured. Scale changes require a restart. May double-scale on some setups. diff --git a/src/Files.App/Styles/AdwaitaResources.xaml b/src/Files.App/Styles/AdwaitaResources.xaml index 8aae1e23a1f1..c844bfccbca8 100644 --- a/src/Files.App/Styles/AdwaitaResources.xaml +++ b/src/Files.App/Styles/AdwaitaResources.xaml @@ -39,7 +39,7 @@ - + @@ -197,7 +197,7 @@ - + diff --git a/src/Files.App/Styles/LinuxDesktopResources.xaml b/src/Files.App/Styles/DesktopResources.xaml similarity index 97% rename from src/Files.App/Styles/LinuxDesktopResources.xaml rename to src/Files.App/Styles/DesktopResources.xaml index 6f0df36a58ca..c483f2daf881 100644 --- a/src/Files.App/Styles/LinuxDesktopResources.xaml +++ b/src/Files.App/Styles/DesktopResources.xaml @@ -1,6 +1,6 @@ @@ -8,8 +8,8 @@ ms-appx:///Assets/Fonts/Linux/selawk.ttf#Selawik ms-appx:///Uno.Fonts.Fluent/Fonts/uno-fluentui-assets.ttf#Symbols - ms-appx:///Uno.Fonts.Fluent/Fonts/uno-fluentui-assets.ttf#Symbols - DejaVu Sans Mono, Liberation Mono, monospace + ms-appx:///Uno.Fonts.Fluent/Fonts/uno-fluentui-assets.ttf#Symbols + DejaVu Sans Mono, Liberation Mono, monospace @@ -291,7 +291,7 @@ - + @@ -324,7 +324,7 @@ - + diff --git a/src/Files.App/UserControls/FilePreviews/PreviewTextRenderer.cs b/src/Files.App/UserControls/FilePreviews/PreviewTextRenderer.cs index aa05680be9e4..68b8276e7f0e 100644 --- a/src/Files.App/UserControls/FilePreviews/PreviewTextRenderer.cs +++ b/src/Files.App/UserControls/FilePreviews/PreviewTextRenderer.cs @@ -33,7 +33,7 @@ public static class PreviewTextRenderer public static FontFamily GetMonospaceFont() { - if (Application.Current?.Resources.TryGetValue("Files.Linux.MonospaceFontFamily", out var value) == true && value is FontFamily family) + if (Application.Current?.Resources.TryGetValue("Files.Desktop.MonospaceFontFamily", out var value) == true && value is FontFamily family) return family; return new FontFamily("Consolas"); diff --git a/src/Files.App/UserControls/NavigationToolbar.xaml.cs b/src/Files.App/UserControls/NavigationToolbar.xaml.cs index 84d89ed5a41e..8d9b062c81a7 100644 --- a/src/Files.App/UserControls/NavigationToolbar.xaml.cs +++ b/src/Files.App/UserControls/NavigationToolbar.xaml.cs @@ -361,7 +361,7 @@ async Task AddItemsAsync(string header, IAsyncEnumerable storabl var path = storable.Id; var flyoutItem = new MenuFlyoutItem() { - Text = path is "/" ? Strings.LinuxFileSystemRoot.GetLocalizedResource() : string.IsNullOrEmpty(storable.Name) ? path : storable.Name, + Text = path is "/" ? Strings.DesktopFileSystemRoot.GetLocalizedResource() : string.IsNullOrEmpty(storable.Name) ? path : storable.Name, Icon = new FontIcon() { Glyph = "\uE8B7" }, // As a placeholder }; e.Flyout.Items.Add(flyoutItem); diff --git a/src/Files.App/UserControls/StatusCenter/StatusCenter.xaml b/src/Files.App/UserControls/StatusCenter/StatusCenter.xaml index 2b8dc04d305c..336b52d0556c 100644 --- a/src/Files.App/UserControls/StatusCenter/StatusCenter.xaml +++ b/src/Files.App/UserControls/StatusCenter/StatusCenter.xaml @@ -19,7 +19,7 @@ - + diff --git a/src/Files.App/UserControls/TabBar/TabBar.Linux.cs b/src/Files.App/UserControls/TabBar/TabBar.Desktop.cs similarity index 52% rename from src/Files.App/UserControls/TabBar/TabBar.Linux.cs rename to src/Files.App/UserControls/TabBar/TabBar.Desktop.cs index 55d3eecb60be..0bf595e628e5 100644 --- a/src/Files.App/UserControls/TabBar/TabBar.Linux.cs +++ b/src/Files.App/UserControls/TabBar/TabBar.Desktop.cs @@ -10,35 +10,35 @@ namespace Files.App.UserControls.TabBar { public sealed partial class TabBar { - private void InitializeLinuxCaptionButtons() + private void InitializeCaptionButtons() { if (!OperatingSystem.IsLinux()) return; - MainWindow.Instance.InitializeLinuxChrome(); - MainWindow.Instance.LinuxChromeChanged -= LinuxChromeChanged; - MainWindow.Instance.LinuxChromeChanged += LinuxChromeChanged; - Unloaded -= LinuxCaptionButtons_Unloaded; - Unloaded += LinuxCaptionButtons_Unloaded; - UpdateLinuxCaptionButtons(); + MainWindow.Instance.InitializeChrome(); + MainWindow.Instance.ChromeChanged -= OnChromeChanged; + MainWindow.Instance.ChromeChanged += OnChromeChanged; + Unloaded -= CaptionButtons_Unloaded; + Unloaded += CaptionButtons_Unloaded; + UpdateCaptionButtons(); _ = NavigationHelpers.UpdateInstancePropertiesAsync(Items.ElementAtOrDefault(App.AppModel.TabStripSelectedIndex)?.NavigationParameter?.NavigationParameter); } - private void LinuxCaptionButtons_Unloaded(object sender, RoutedEventArgs e) - => MainWindow.Instance.LinuxChromeChanged -= LinuxChromeChanged; + private void CaptionButtons_Unloaded(object sender, RoutedEventArgs e) + => MainWindow.Instance.ChromeChanged -= OnChromeChanged; - private void LinuxChromeChanged(object? sender, EventArgs e) => UpdateLinuxCaptionButtons(); + private void OnChromeChanged(object? sender, EventArgs e) => UpdateCaptionButtons(); - private void UpdateLinuxCaptionButtons() + private void UpdateCaptionButtons() { var window = MainWindow.Instance; - LinuxCaptionButtons.Visibility = window.HasClientSideDecorations ? Visibility.Visible : Visibility.Collapsed; + CaptionButtons.Visibility = window.HasClientSideDecorations ? Visibility.Visible : Visibility.Collapsed; ClientSideDecorationsMenuItem.Visibility = Visibility.Visible; ClientSideDecorationsMenuItem.IsChecked = window.UseClientSideDecorations; - var maximizeLabel = (window.IsLinuxWindowMaximized ? "LinuxRestoreWindow" : "LinuxMaximizeWindow").GetLocalizedResource(); - AutomationProperties.SetName(LinuxMaximizeButton, maximizeLabel); - ToolTipService.SetToolTip(LinuxMaximizeButton, maximizeLabel); - LinuxMaximizeIcon.Glyph = window.IsLinuxWindowMaximized ? "\uE923" : "\uE922"; + var maximizeLabel = (window.IsWindowMaximized ? "DesktopRestoreWindow" : "DesktopMaximizeWindow").GetLocalizedResource(); + AutomationProperties.SetName(MaximizeButton, maximizeLabel); + ToolTipService.SetToolTip(MaximizeButton, maximizeLabel); + MaximizeIcon.Glyph = window.IsWindowMaximized ? "\uE923" : "\uE922"; UpdateTitleBarInsets(); } @@ -55,7 +55,7 @@ private void MinimizeWindow_Click(object sender, RoutedEventArgs e) private void MaximizeWindow_Click(object sender, RoutedEventArgs e) { - MainWindow.Instance.ToggleLinuxWindowMaximize(); + MainWindow.Instance.ToggleWindowMaximize(); } private void CloseWindow_Click(object sender, RoutedEventArgs e) => MainWindow.Instance.Close(); diff --git a/src/Files.App/UserControls/TabBar/TabBar.xaml b/src/Files.App/UserControls/TabBar/TabBar.xaml index 7e14e387b3a9..c61c937ed0c9 100644 --- a/src/Files.App/UserControls/TabBar/TabBar.xaml +++ b/src/Files.App/UserControls/TabBar/TabBar.xaml @@ -39,7 +39,7 @@ @@ -256,12 +256,12 @@ - - -