From c209354850a9ffae1c4d8f40e5dd68b038e7be46 Mon Sep 17 00:00:00 2001 From: Lex Li Date: Sat, 17 Oct 2026 11:53:58 -0400 Subject: [PATCH] Refactor the Linux port to prepare for a cross-platform desktop head Extract the OS-neutral desktop code into a new Files.Platform.Posix project that references only Files.Platform.Abstractions, and rename the Linux-prefixed types, files and namespaces to Desktop*/Posix* so the Linux head is one implementation of a shared desktop layer instead of the layer itself. * Add a FilesDesktopOS selector (defaults to Linux) and gate the desktop Wayland/X11 targets on it. Linux behaviour is unchanged: the same Uno packages, the same define, the same output. * Move the shared POSIX primitives (stat, enumeration, watching, file operations, permissions, archives, launching, icons, previews, search and settings) into Files.Platform.Posix; Files.Platform.Linux keeps the Linux-only services and references it. * Route the root/elevation environment applied when starting a process through a Posix-level hook that the Linux assembly installs, and move the safe "open beneath a directory handle" primitive into the Posix native layer. * Gather the app layer's Linux entry points behind DesktopPlatform (DI registration, user directories, drag file list, display scale, FileManager1) and move the Linux startup, single instance and Uno host code from Program.cs into Program.Linux.cs. * Let CredentialsHelpers use any non-Windows ISecretStore. * Fix the tests left stale by the move and restore the /usr/lib/linuxfiles helper, state and cache paths that the packaging, polkit policy and launchers still use. Co-Authored-By: Claude Sonnet 5.5 --- Directory.Build.props | 7 +- Files.slnx | 5 + ...inuxHomeFolder.cs => DesktopHomeFolder.cs} | 6 +- .../Actions/Open/OpenSettingsFileAction.cs | 2 +- .../Actions/Open/OpenTerminalAction.cs | 6 +- .../Actions/Open/OpenTerminalAsAdminAction.cs | 2 +- .../Open/OpenTerminalFromHomeAction.cs | 2 +- .../Open/OpenTerminalFromSidebarAction.cs | 2 +- src/Files.App/App.xaml | 2 +- src/Files.App/Constants.cs | 3 +- ...pers.Linux.cs => HotKeyHelpers.Desktop.cs} | 0 .../Data/Contexts/HomePage/HomePageContext.cs | 4 +- ...ontentPageContextFlyoutFactory.Desktop.cs} | 12 +- ...entPageContextFlyoutFactory.RootActions.cs | 2 +- .../ContentPageContextFlyoutFactory.cs | 6 +- .../PropertiesNavigationItemsFactory.cs | 2 +- ...s => ShellContextFlyoutFactory.Desktop.cs} | 6 +- .../Factories/ShellContextFlyoutHelper.cs | 2 +- src/Files.App/Data/Items/DriveItem.cs | 4 +- .../SelectedItemsPropertiesViewModel.cs | 10 +- ...WithDialog.cs => DesktopOpenWithDialog.cs} | 6 +- src/Files.App/Files.App.csproj | 25 ++- .../Helpers/Application/AppLifecycleHelper.cs | 4 +- ... => AppToastNotificationHelper.Desktop.cs} | 0 src/Files.App/Helpers/CredentialsHelpers.cs | 10 +- ...s => LayoutPreferencesDatabase.Desktop.cs} | 0 .../{LinuxAppIcons.cs => DesktopAppIcons.cs} | 4 +- ...nux.cs => ArchiveOpenTempStore.Desktop.cs} | 0 ....Linux.cs => NavigationHelpers.Desktop.cs} | 118 +++++------ .../Helpers/Navigation/NavigationHelpers.cs | 18 +- src/Files.App/Helpers/RootActionsHelper.cs | 3 +- src/Files.App/MainWindow.Linux.cs | 64 +++--- src/Files.App/MainWindow.xaml.cs | 2 +- .../Platforms/Desktop/App.Desktop.cs | 10 +- .../Platforms/Desktop/DesktopPlatform.cs | 51 +++++ .../Platforms/Desktop/Program.Linux.cs | 136 +++++++++++++ src/Files.App/Platforms/Desktop/Program.cs | 98 +-------- .../Desktop/Services/DesktopFileDragHelper.cs | 2 +- .../Desktop/Services/LinuxIconHelper.cs | 2 +- ...del.Linux.cs => ShellViewModel.Desktop.cs} | 186 +++++++++--------- src/Files.App/Platforms/Desktop/WindowEx.cs | 2 +- .../Services/App/AppResourcesService.Linux.cs | 16 +- .../Services/App/AppResourcesService.cs | 18 +- .../Services/App/AppThemeModeService.cs | 7 +- .../Services/Git/LibGit2NativeResolver.cs | 2 +- ...Linux.cs => CachedSizeProvider.Desktop.cs} | 0 .../Services/Storage/StorageDevicesService.cs | 12 +- .../Storage/StorageNetworkService.Linux.cs | 20 +- .../Services/Storage/StorageNetworkService.cs | 6 +- src/Files.App/Strings/en-US/Resources.resw | 140 ++++++------- src/Files.App/Styles/AdwaitaResources.xaml | 4 +- ...opResources.xaml => DesktopResources.xaml} | 10 +- .../FilePreviews/PreviewTextRenderer.cs | 2 +- .../UserControls/NavigationToolbar.xaml.cs | 2 +- .../StatusCenter/StatusCenter.xaml | 2 +- .../{TabBar.Linux.cs => TabBar.Desktop.cs} | 34 ++-- src/Files.App/UserControls/TabBar/TabBar.xaml | 10 +- .../UserControls/TabBar/TabBar.xaml.cs | 4 +- src/Files.App/UserControls/Toolbar.xaml.cs | 2 +- ...e.Linux.cs => FileTagsDatabase.Desktop.cs} | 0 ...per.Linux.cs => FileTagsHelper.Desktop.cs} | 0 ...DriveCatalog.cs => DesktopDriveCatalog.cs} | 14 +- ...cher.cs => DesktopStorageDeviceWatcher.cs} | 8 +- .../DefaultSettingsSerializer.cs | 2 +- ...lpers.Linux.cs => DriveHelpers.Desktop.cs} | 28 +-- ...per.Linux.cs => FontFileHelper.Desktop.cs} | 2 +- ...ux.cs => StorageFileExtensions.Desktop.cs} | 0 ...rs.Linux.cs => SyncRootHelpers.Desktop.cs} | 0 ...Tests.cs => DesktopStorageHistoryTests.cs} | 8 +- .../History/Tests/HistoryTestHelpers.cs | 2 +- ...ions.cs => DesktopFilesystemOperations.cs} | 6 +- ...ux.cs => FileOperationsHelpers.Desktop.cs} | 0 ...Linux.cs => FileSizeCalculator.Desktop.cs} | 0 .../Operations/FilesystemOperationsFactory.cs | 2 +- ...earch.Linux.cs => FolderSearch.Desktop.cs} | 20 +- .../Utils/Storage/Search/FolderSearch.cs | 2 +- src/Files.App/ViewModels/MainPageViewModel.cs | 2 +- ...FileMetadata.cs => DesktopFileMetadata.cs} | 2 +- ...tcutHelper.cs => DesktopShortcutHelper.cs} | 4 +- .../Properties/Items/BaseProperties.cs | 12 +- .../Properties/Items/CombinedProperties.cs | 4 +- .../Properties/Items/DriveProperties.cs | 4 +- .../Properties/Items/FileProperties.cs | 26 +-- .../Properties/Items/FolderProperties.cs | 18 +- .../ViewModels/Settings/AboutViewModel.cs | 2 +- .../ViewModels/Settings/AdvancedViewModel.cs | 2 +- .../Settings/AppearanceViewModel.cs | 31 ++- .../ViewModels/Settings/GeneralViewModel.cs | 2 +- src/Files.App/ViewModels/ShellViewModel.cs | 10 +- .../NavigationToolbarViewModel.cs | 2 +- .../Previews/ArchiveListingPreview.cs | 2 +- .../UserControls/Previews/BasePreviewModel.cs | 2 +- .../Previews/ImagePreviewViewModel.cs | 2 +- .../Previews/MediaMetadataPreviewViewModel.cs | 2 +- .../UserControls/SidebarViewModel.cs | 6 +- .../Widgets/DrivesWidgetViewModel.cs | 6 +- .../NetworkLocationsWidgetViewModel.cs | 6 +- .../Views/Layouts/BaseGroupableLayoutPage.cs | 10 +- ...age.Linux.cs => BaseLayoutPage.Desktop.cs} | 16 +- src/Files.App/Views/Layouts/BaseLayoutPage.cs | 10 +- ...Source.cs => DesktopGroupedItemsSource.cs} | 4 +- ...rapPanel.cs => DesktopGroupedWrapPanel.cs} | 2 +- .../Views/Layouts/DetailsLayoutPage.xaml.cs | 42 ++-- .../Views/Layouts/GridLayoutPage.xaml.cs | 2 +- .../Views/Properties/GeneralPage.xaml.cs | 4 +- .../Views/Settings/AdvancedPage.xaml | 8 +- .../Views/Settings/AppearancePage.xaml | 30 +-- src/Files.App/Views/Shells/BaseShellPage.cs | 2 +- .../Appearance/IAppearanceSupport.cs | 40 ++++ .../INetworkLocationService.cs | 2 +- ...iles.Platform.Linux.ElevationHelper.csproj | 5 +- .../HelperEngine.cs | 1 + .../Program.cs | 1 + .../AppDataServiceCollectionExtensions.cs | 5 +- .../Elevation/HelperAuthorization.cs | 2 +- .../Elevation/PkexecElevationService.cs | 4 +- .../Files.Platform.Linux.csproj | 2 + src/Files.Platform.Linux/Gvfs/GioRunner.cs | 2 +- .../Gvfs/GvfsNameParser.cs | 2 +- .../Gvfs/GvfsNetworkLocationService.cs | 2 +- .../LaunchingServiceCollectionExtensions.cs | 9 +- .../Launching/RootTerminalResolver.cs | 2 +- .../Launching/TracedProcess.cs | 21 -- .../Launching/TracedProcessRegistration.cs | 28 +++ .../Mime/MimeServiceCollectionExtensions.cs | 4 +- .../Native/ElevationNative.cs | 47 +---- .../PlatformServiceCollectionExtensions.cs | 19 +- .../Previews/PreviewFile.cs | 55 ------ .../Theme/GtkNamedColors.cs | 2 +- .../Theme/LinuxColorScheme.cs | 2 +- .../Theme/LinuxSystemAppearanceService.cs | 2 +- .../Theme/SystemAppearancePalette.cs | 4 +- .../Thumbnails/ExternalThumbnailers.cs | 2 +- .../Trash/LinuxTrashOptions.cs | 4 +- .../Trash/LinuxTrashService.cs | 4 +- .../VolumesServiceCollectionExtensions.cs | 2 +- .../Windowing/CompositorScaleSettings.cs | 2 +- .../Windowing/CursorSettingsResolver.cs | 2 +- .../Windowing/LinuxAppearanceSupport.cs | 27 +++ .../Windowing/X11AppearanceSupport.cs | 2 - .../Windowing/X11WindowChrome.cs | 11 +- .../AppData/PosixLocalSettingsStore.cs} | 12 +- .../Archives/ArchivePathValidator.cs | 6 +- .../Archives/ArchiveSource.cs | 4 +- .../ArchivesServiceCollectionExtensions.cs | 8 +- .../Archives/ISevenZipRunner.cs | 6 +- .../Archives/PosixArchiveService.Create.cs} | 6 +- .../Archives/PosixArchiveService.Extract.cs} | 12 +- .../PosixArchiveService.OpenEntry.cs} | 4 +- .../Archives/PosixArchiveService.Preview.cs} | 6 +- .../Archives/PosixArchiveService.cs} | 10 +- .../Elevation/RootActionMode.cs | 2 +- .../Elevation/RootActionsAvailability.cs | 4 +- .../EnumerationServiceCollectionExtensions.cs | 6 +- .../Enumeration/FileSystemEntryReader.cs | 4 +- .../Enumeration/PosixFileSystemEnumerator.cs} | 4 +- .../FileOperations/FileNameGenerator.cs | 2 +- .../FileOperations/FileOperationContext.cs | 6 +- .../FileOperations/FileOperationErrors.cs | 2 +- ...leOperationsServiceCollectionExtensions.cs | 6 +- .../FileOperations/FileSystemEntry.cs | 4 +- .../FileOperations/MountTable.cs | 22 ++- .../PosixFileOperationsHooks.cs} | 4 +- .../PosixFileOperationsService.Copy.cs} | 6 +- .../PosixFileOperationsService.Move.cs} | 6 +- .../PosixFileOperationsService.cs} | 14 +- .../FileStatServiceCollectionExtensions.cs | 6 +- .../FileStat/PosixFileStatService.cs} | 6 +- .../Files.Platform.Posix.csproj | 30 +++ .../Icons/CachingTypefaceProvider.cs | 2 +- .../Icons/IconColorScheme.cs | 2 +- .../Icons/SvgRasterizer.cs | 2 +- .../Launching/DesktopExecExpander.cs | 2 +- .../Launching/DisplaySanitizer.cs | 2 +- .../Launching/IExecutableLocator.cs | 2 +- .../Launching/IProcessStarter.cs | 4 +- .../Launching/OpenDecision.cs | 4 +- .../Launching/PosixExecutableService.cs} | 4 +- .../Launching/PosixTemplatesService.cs} | 6 +- .../Launching/ServiceMenuLaunchPlan.cs | 8 +- .../Launching/TracedProcess.cs | 26 +++ .../Mime/DesktopEntryDisplay.cs | 6 +- .../Mime/DesktopEntryParser.cs | 2 +- .../Mime/MimeHierarchy.cs | 2 +- .../Mime/ParsedFileCache.cs | 4 +- .../Mime/PosixServiceMenuService.cs} | 12 +- .../Mime/ServiceMenuParser.cs | 4 +- .../Mime/XdgDirectories.cs | 2 +- .../Native/FileLockNative.cs | 25 ++- .../Native/FileOwnershipInspector.cs | 2 +- .../Native/PosixFileOperationsNative.cs | 24 ++- .../Native/PosixNative.Darwin.cs | 136 +++++++++++++ .../Native/PosixNative.cs | 43 ++-- .../Native/PosixPermissionsNative.cs | 10 +- .../Native/PosixSecureOpenNative.cs | 76 +++++++ .../Native/ProcessIdentityNative.cs | 2 +- .../Native/TrustedNativeDirectory.cs | 2 +- .../PermissionsServiceCollectionExtensions.cs | 10 +- .../PosixFileAttributesService.cs} | 6 +- .../PosixFilePermissionsService.cs} | 10 +- .../Permissions/PosixNameDatabase.cs | 2 +- .../PosixPermissionsFileStatService.cs} | 6 +- .../PlatformServiceCollectionExtensions.cs | 50 +++++ .../PosixGlobalUsings.props | 25 +++ .../Previews/BoundedFileReader.cs | 2 +- .../Previews/MediaPreviewInput.Snapshot.cs | 2 +- .../Previews/MediaPreviewInput.cs | 2 +- .../Previews/PreviewEntryName.cs | 2 +- .../Previews/PreviewFile.cs | 76 +++++++ .../Previews/PreviewReadStream.cs | 2 +- .../Properties/AssemblyInfo.cs | 10 + .../Search/GitDirectoryResolver.cs | 4 +- .../Search/PosixFileSearchService.cs} | 12 +- .../Thumbnails/FontThumbnailRenderer.cs | 12 +- .../Volumes/HeadlessDriveFixture.cs | 4 +- .../Watching/FolderChangeBatch.cs | 2 +- .../Watching/FolderChangeReconciler.cs | 2 +- .../Watching/PosixFolderWatcher.cs} | 6 +- .../Watching/PosixFolderWatcherFactory.cs} | 12 +- .../WatchingServiceCollectionExtensions.cs | 6 +- src/Files.Platform.Posix/WindowTitle.cs | 26 +++ .../AppData/AppDataTests.cs | 20 +- .../Archives/ArchiveBrowsingTests.cs | 6 +- .../Archives/ArchiveServiceTests.cs | 33 ++-- ...s.cs => PosixFileSystemEnumeratorTests.cs} | 12 +- .../FileOperations/CopyTests.cs | 4 +- .../FileOperations/DeleteRenameCreateTests.cs | 10 +- .../FileOperations/FdCompatibilityTests.cs | 14 +- .../FileOperations/FdMoveTests.cs | 32 +-- .../FileOperations/FdRenameFollowUpTests.cs | 26 +-- .../FileOperations/FileNameGeneratorTests.cs | 2 +- .../FileOperations/HardeningTests.cs | 22 +-- .../FileOperations/MoveQuarantineTests.cs | 18 +- .../FileOperations/MoveTests.cs | 10 +- ...eTests.cs => PosixFileStatServiceTests.cs} | 6 +- .../Files.Platform.Tests.csproj | 6 +- .../Launching/ExecutableServiceTests.cs | 4 +- .../Launching/OpenDecisionTests.cs | 4 +- .../Launching/TemplatesServiceTests.cs | 12 +- .../Listing/FolderListingHelperTests.cs | 6 +- .../Mime/MimeHierarchyTests.cs | 2 +- .../Mime/ServiceMenuTests.cs | 30 +-- .../Permissions/FileAttributesTests.cs | 6 +- .../Permissions/PermissionsTests.cs | 8 +- .../Previews/PreviewHardeningTests.cs | 6 +- .../Previews/PreviewTests.cs | 2 +- ...ests.cs => PosixFileSearchServiceTests.cs} | 46 ++--- .../CachingJsonSettingsDatabaseTests.cs | 4 +- .../SystemIntegration/RootActionModeTests.cs | 2 +- .../Tags/FileTagsDatabaseTests.cs | 6 +- .../Tags/FileTagsHelperTests.cs | 2 +- .../Trash/FdRestoreCompatibilityTests.cs | 10 +- .../Trash/FdRestoreFollowUpTests.cs | 8 +- .../Trash/LinuxTrashHardeningTests.cs | 2 +- .../Trash/LinuxTrashServiceTests.cs | 4 +- .../Files.Platform.Tests/Volumes/GvfsTests.cs | 2 +- ...herTests.cs => PosixFolderWatcherTests.cs} | 16 +- 257 files changed, 1862 insertions(+), 1263 deletions(-) rename src/Files.App.Storage/Legacy/HomeFolder/{LinuxHomeFolder.cs => DesktopHomeFolder.cs} (92%) rename src/Files.App/Data/Commands/HotKey/{HotKeyHelpers.Linux.cs => HotKeyHelpers.Desktop.cs} (100%) rename src/Files.App/Data/Factories/{ContentPageContextFlyoutFactory.Linux.cs => ContentPageContextFlyoutFactory.Desktop.cs} (93%) rename src/Files.App/Data/Factories/{ShellContextFlyoutFactory.Linux.cs => ShellContextFlyoutFactory.Desktop.cs} (90%) rename src/Files.App/Dialogs/{LinuxOpenWithDialog.cs => DesktopOpenWithDialog.cs} (93%) rename src/Files.App/Helpers/Application/{AppToastNotificationHelper.Linux.cs => AppToastNotificationHelper.Desktop.cs} (100%) rename src/Files.App/Helpers/Layout/{LayoutPreferencesDatabase.Linux.cs => LayoutPreferencesDatabase.Desktop.cs} (100%) rename src/Files.App/Helpers/MenuFlyout/{LinuxAppIcons.cs => DesktopAppIcons.cs} (96%) rename src/Files.App/Helpers/Navigation/{ArchiveOpenTempStore.Linux.cs => ArchiveOpenTempStore.Desktop.cs} (100%) rename src/Files.App/Helpers/Navigation/{NavigationHelpers.Linux.cs => NavigationHelpers.Desktop.cs} (68%) create mode 100644 src/Files.App/Platforms/Desktop/DesktopPlatform.cs create mode 100644 src/Files.App/Platforms/Desktop/Program.Linux.cs rename src/Files.App/Platforms/Desktop/ViewModels/{ShellViewModel.Linux.cs => ShellViewModel.Desktop.cs} (84%) rename src/Files.App/Services/SizeProvider/{CachedSizeProvider.Linux.cs => CachedSizeProvider.Desktop.cs} (100%) rename src/Files.App/Styles/{LinuxDesktopResources.xaml => DesktopResources.xaml} (97%) rename src/Files.App/UserControls/TabBar/{TabBar.Linux.cs => TabBar.Desktop.cs} (52%) rename src/Files.App/Utils/FileTags/{FileTagsDatabase.Linux.cs => FileTagsDatabase.Desktop.cs} (100%) rename src/Files.App/Utils/FileTags/{FileTagsHelper.Linux.cs => FileTagsHelper.Desktop.cs} (100%) rename src/Files.App/Utils/Global/{LinuxDriveCatalog.cs => DesktopDriveCatalog.cs} (94%) rename src/Files.App/Utils/Global/{LinuxStorageDeviceWatcher.cs => DesktopStorageDeviceWatcher.cs} (94%) rename src/Files.App/Utils/Storage/Helpers/{DriveHelpers.Linux.cs => DriveHelpers.Desktop.cs} (95%) rename src/Files.App/Utils/Storage/Helpers/{FontFileHelper.Linux.cs => FontFileHelper.Desktop.cs} (92%) rename src/Files.App/Utils/Storage/Helpers/{StorageFileExtensions.Linux.cs => StorageFileExtensions.Desktop.cs} (100%) rename src/Files.App/Utils/Storage/Helpers/{SyncRootHelpers.Linux.cs => SyncRootHelpers.Desktop.cs} (100%) rename src/Files.App/Utils/Storage/History/Tests/{LinuxStorageHistoryTests.cs => DesktopStorageHistoryTests.cs} (98%) rename src/Files.App/Utils/Storage/Operations/{LinuxFilesystemOperations.cs => DesktopFilesystemOperations.cs} (98%) rename src/Files.App/Utils/Storage/Operations/{FileOperationsHelpers.Linux.cs => FileOperationsHelpers.Desktop.cs} (100%) rename src/Files.App/Utils/Storage/Operations/{FileSizeCalculator.Linux.cs => FileSizeCalculator.Desktop.cs} (100%) rename src/Files.App/Utils/Storage/Search/{FolderSearch.Linux.cs => FolderSearch.Desktop.cs} (83%) rename src/Files.App/ViewModels/Properties/{LinuxFileMetadata.cs => DesktopFileMetadata.cs} (99%) rename src/Files.App/ViewModels/Properties/{LinuxShortcutHelper.cs => DesktopShortcutHelper.cs} (94%) rename src/Files.App/Views/Layouts/{BaseLayoutPage.Linux.cs => BaseLayoutPage.Desktop.cs} (56%) rename src/Files.App/Views/Layouts/{LinuxGroupedItemsSource.cs => DesktopGroupedItemsSource.cs} (96%) rename src/Files.App/Views/Layouts/{LinuxGroupedWrapPanel.cs => DesktopGroupedWrapPanel.cs} (98%) create mode 100644 src/Files.Platform.Abstractions/Appearance/IAppearanceSupport.cs rename src/Files.Platform.Abstractions/{Gvfs => Network}/INetworkLocationService.cs (98%) delete mode 100644 src/Files.Platform.Linux/Launching/TracedProcess.cs create mode 100644 src/Files.Platform.Linux/Launching/TracedProcessRegistration.cs delete mode 100644 src/Files.Platform.Linux/Previews/PreviewFile.cs create mode 100644 src/Files.Platform.Linux/Windowing/LinuxAppearanceSupport.cs rename src/{Files.Platform.Linux/AppData/LinuxLocalSettingsStore.cs => Files.Platform.Posix/AppData/PosixLocalSettingsStore.cs} (94%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Archives/ArchivePathValidator.cs (97%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Archives/ArchiveSource.cs (99%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Archives/ArchivesServiceCollectionExtensions.cs (77%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Archives/ISevenZipRunner.cs (95%) rename src/{Files.Platform.Linux/Archives/LinuxArchiveService.Create.cs => Files.Platform.Posix/Archives/PosixArchiveService.Create.cs} (98%) rename src/{Files.Platform.Linux/Archives/LinuxArchiveService.Extract.cs => Files.Platform.Posix/Archives/PosixArchiveService.Extract.cs} (97%) rename src/{Files.Platform.Linux/Archives/LinuxArchiveService.OpenEntry.cs => Files.Platform.Posix/Archives/PosixArchiveService.OpenEntry.cs} (97%) rename src/{Files.Platform.Linux/Archives/LinuxArchiveService.Preview.cs => Files.Platform.Posix/Archives/PosixArchiveService.Preview.cs} (94%) rename src/{Files.Platform.Linux/Archives/LinuxArchiveService.cs => Files.Platform.Posix/Archives/PosixArchiveService.cs} (96%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Elevation/RootActionMode.cs (96%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Elevation/RootActionsAvailability.cs (91%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Enumeration/EnumerationServiceCollectionExtensions.cs (78%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Enumeration/FileSystemEntryReader.cs (95%) rename src/{Files.Platform.Linux/Enumeration/LinuxFileSystemEnumerator.cs => Files.Platform.Posix/Enumeration/PosixFileSystemEnumerator.cs} (98%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/FileOperations/FileNameGenerator.cs (98%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/FileOperations/FileOperationContext.cs (94%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/FileOperations/FileOperationErrors.cs (98%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/FileOperations/FileOperationsServiceCollectionExtensions.cs (79%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/FileOperations/FileSystemEntry.cs (98%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/FileOperations/MountTable.cs (77%) rename src/{Files.Platform.Linux/FileOperations/LinuxFileOperationsHooks.cs => Files.Platform.Posix/FileOperations/PosixFileOperationsHooks.cs} (96%) rename src/{Files.Platform.Linux/FileOperations/LinuxFileOperationsService.Copy.cs => Files.Platform.Posix/FileOperations/PosixFileOperationsService.Copy.cs} (99%) rename src/{Files.Platform.Linux/FileOperations/LinuxFileOperationsService.Move.cs => Files.Platform.Posix/FileOperations/PosixFileOperationsService.Move.cs} (99%) rename src/{Files.Platform.Linux/FileOperations/LinuxFileOperationsService.cs => Files.Platform.Posix/FileOperations/PosixFileOperationsService.cs} (97%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/FileStat/FileStatServiceCollectionExtensions.cs (65%) rename src/{Files.Platform.Linux/FileStat/LinuxFileStatService.cs => Files.Platform.Posix/FileStat/PosixFileStatService.cs} (96%) create mode 100644 src/Files.Platform.Posix/Files.Platform.Posix.csproj rename src/{Files.Platform.Linux => Files.Platform.Posix}/Icons/CachingTypefaceProvider.cs (98%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Icons/IconColorScheme.cs (98%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Icons/SvgRasterizer.cs (99%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Launching/DesktopExecExpander.cs (99%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Launching/DisplaySanitizer.cs (98%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Launching/IExecutableLocator.cs (97%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Launching/IProcessStarter.cs (97%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Launching/OpenDecision.cs (98%) rename src/{Files.Platform.Linux/Launching/LinuxExecutableService.cs => Files.Platform.Posix/Launching/PosixExecutableService.cs} (89%) rename src/{Files.Platform.Linux/Launching/LinuxTemplatesService.cs => Files.Platform.Posix/Launching/PosixTemplatesService.cs} (95%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Launching/ServiceMenuLaunchPlan.cs (89%) create mode 100644 src/Files.Platform.Posix/Launching/TracedProcess.cs rename src/{Files.Platform.Linux => Files.Platform.Posix}/Mime/DesktopEntryDisplay.cs (97%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Mime/DesktopEntryParser.cs (99%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Mime/MimeHierarchy.cs (98%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Mime/ParsedFileCache.cs (95%) rename src/{Files.Platform.Linux/Mime/LinuxServiceMenuService.cs => Files.Platform.Posix/Mime/PosixServiceMenuService.cs} (92%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Mime/ServiceMenuParser.cs (98%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Mime/XdgDirectories.cs (98%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Native/FileLockNative.cs (77%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Native/FileOwnershipInspector.cs (97%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Native/PosixFileOperationsNative.cs (92%) create mode 100644 src/Files.Platform.Posix/Native/PosixNative.Darwin.cs rename src/{Files.Platform.Linux => Files.Platform.Posix}/Native/PosixNative.cs (91%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Native/PosixPermissionsNative.cs (93%) create mode 100644 src/Files.Platform.Posix/Native/PosixSecureOpenNative.cs rename src/{Files.Platform.Linux => Files.Platform.Posix}/Native/ProcessIdentityNative.cs (97%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Native/TrustedNativeDirectory.cs (99%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Permissions/PermissionsServiceCollectionExtensions.cs (67%) rename src/{Files.Platform.Linux/Permissions/LinuxFileAttributesService.cs => Files.Platform.Posix/Permissions/PosixFileAttributesService.cs} (94%) rename src/{Files.Platform.Linux/Permissions/LinuxFilePermissionsService.cs => Files.Platform.Posix/Permissions/PosixFilePermissionsService.cs} (95%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Permissions/PosixNameDatabase.cs (98%) rename src/{Files.Platform.Linux/Permissions/LinuxFileStatService.cs => Files.Platform.Posix/Permissions/PosixPermissionsFileStatService.cs} (95%) create mode 100644 src/Files.Platform.Posix/PlatformServiceCollectionExtensions.cs create mode 100644 src/Files.Platform.Posix/PosixGlobalUsings.props rename src/{Files.Platform.Linux => Files.Platform.Posix}/Previews/BoundedFileReader.cs (96%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Previews/MediaPreviewInput.Snapshot.cs (99%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Previews/MediaPreviewInput.cs (99%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Previews/PreviewEntryName.cs (96%) create mode 100644 src/Files.Platform.Posix/Previews/PreviewFile.cs rename src/{Files.Platform.Linux => Files.Platform.Posix}/Previews/PreviewReadStream.cs (98%) create mode 100644 src/Files.Platform.Posix/Properties/AssemblyInfo.cs rename src/{Files.Platform.Linux => Files.Platform.Posix}/Search/GitDirectoryResolver.cs (98%) rename src/{Files.Platform.Linux/Search/LinuxFileSearchService.cs => Files.Platform.Posix/Search/PosixFileSearchService.cs} (95%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Thumbnails/FontThumbnailRenderer.cs (92%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Volumes/HeadlessDriveFixture.cs (98%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Watching/FolderChangeBatch.cs (98%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Watching/FolderChangeReconciler.cs (98%) rename src/{Files.Platform.Linux/Watching/LinuxFolderWatcher.cs => Files.Platform.Posix/Watching/PosixFolderWatcher.cs} (98%) rename src/{Files.Platform.Linux/Watching/LinuxFolderWatcherFactory.cs => Files.Platform.Posix/Watching/PosixFolderWatcherFactory.cs} (65%) rename src/{Files.Platform.Linux => Files.Platform.Posix}/Watching/WatchingServiceCollectionExtensions.cs (78%) create mode 100644 src/Files.Platform.Posix/WindowTitle.cs rename tests/Files.Platform.Tests/Enumeration/{LinuxFileSystemEnumeratorTests.cs => PosixFileSystemEnumeratorTests.cs} (94%) rename tests/Files.Platform.Tests/FileStat/{LinuxFileStatServiceTests.cs => PosixFileStatServiceTests.cs} (96%) rename tests/Files.Platform.Tests/Search/{LinuxFileSearchServiceTests.cs => PosixFileSearchServiceTests.cs} (90%) rename tests/Files.Platform.Tests/Watching/{LinuxFolderWatcherTests.cs => PosixFolderWatcherTests.cs} (92%) 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 @@ - - -