diff --git a/src/Files.App (Package)/Package.appxmanifest b/src/Files.App (Package)/Package.appxmanifest index a01a91162889..883bc279294d 100644 --- a/src/Files.App (Package)/Package.appxmanifest +++ b/src/Files.App (Package)/Package.appxmanifest @@ -16,7 +16,7 @@ + Version="4.0.11.0" /> Files - Dev diff --git a/src/Files.App.CsWin32/Files.App.CsWin32.csproj b/src/Files.App.CsWin32/Files.App.CsWin32.csproj index 8a2e431be6e7..d3a5a5198de2 100644 --- a/src/Files.App.CsWin32/Files.App.CsWin32.csproj +++ b/src/Files.App.CsWin32/Files.App.CsWin32.csproj @@ -18,4 +18,9 @@ + + + + + diff --git a/src/Files.App.CsWin32/IStorageProviderQuotaUI.cs b/src/Files.App.CsWin32/IStorageProviderQuotaUI.cs index 20f9dc08d55b..c7cd816b77fc 100644 --- a/src/Files.App.CsWin32/IStorageProviderQuotaUI.cs +++ b/src/Files.App.CsWin32/IStorageProviderQuotaUI.cs @@ -1,29 +1,19 @@ // Copyright (c) Files Community // Licensed under the MIT License. +using Files.Shared.Attributes; using System; -using System.Runtime.CompilerServices; using Windows.Win32.Foundation; namespace Windows.Win32.System.WinRT { public unsafe partial struct IStorageProviderQuotaUI : IComIID { -#pragma warning disable CS0649 // Field 'field' is never assigned to, and will always have its default value 'value' - private void** lpVtbl; -#pragma warning restore CS0649 // Field 'field' is never assigned to, and will always have its default value 'value' + [GeneratedVTableFunction(Index = 6)] + public partial HRESULT GetQuotaTotalInBytes(ulong* value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public HRESULT GetQuotaTotalInBytes(ulong* value) - { - return (HRESULT)((delegate* unmanaged[MemberFunction])(lpVtbl[6]))((IStorageProviderQuotaUI*)Unsafe.AsPointer(ref this), value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public HRESULT GetQuotaUsedInBytes(ulong* value) - { - return (HRESULT)((delegate* unmanaged[MemberFunction])(lpVtbl[8]))((IStorageProviderQuotaUI*)Unsafe.AsPointer(ref this), value); - } + [GeneratedVTableFunction(Index = 8)] + public partial HRESULT GetQuotaUsedInBytes(ulong* value); [GuidRVAGen.Guid("BA6295C3-312E-544F-9FD5-1F81B21F3649")] public static partial ref readonly Guid Guid { get; } diff --git a/src/Files.App.CsWin32/IStorageProviderStatusUI.cs b/src/Files.App.CsWin32/IStorageProviderStatusUI.cs index 1d778bc008ea..3aa60c4255f9 100644 --- a/src/Files.App.CsWin32/IStorageProviderStatusUI.cs +++ b/src/Files.App.CsWin32/IStorageProviderStatusUI.cs @@ -1,23 +1,16 @@ // Copyright (c) Files Community // Licensed under the MIT License. +using Files.Shared.Attributes; using System; -using System.Runtime.CompilerServices; using Windows.Win32.Foundation; namespace Windows.Win32.System.WinRT { public unsafe partial struct IStorageProviderStatusUI : IComIID { -#pragma warning disable CS0649 // Field 'field' is never assigned to, and will always have its default value 'value' - private void** lpVtbl; -#pragma warning restore CS0649 // Field 'field' is never assigned to, and will always have its default value 'value' - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public HRESULT GetQuotaUI(IStorageProviderQuotaUI** result) - { - return (HRESULT)((delegate* unmanaged[MemberFunction])lpVtbl[14])((IStorageProviderStatusUI*)Unsafe.AsPointer(ref this), result); - } + [GeneratedVTableFunction(Index = 14)] + public partial HRESULT GetQuotaUI(IStorageProviderQuotaUI** result); [GuidRVAGen.Guid("D6B6A758-198D-5B80-977F-5FF73DA33118")] public static partial ref readonly Guid Guid { get; } diff --git a/src/Files.App.CsWin32/IStorageProviderStatusUISource.cs b/src/Files.App.CsWin32/IStorageProviderStatusUISource.cs index caf7aa291bf3..9f4ede28194f 100644 --- a/src/Files.App.CsWin32/IStorageProviderStatusUISource.cs +++ b/src/Files.App.CsWin32/IStorageProviderStatusUISource.cs @@ -1,23 +1,16 @@ // Copyright (c) Files Community // Licensed under the MIT License. +using Files.Shared.Attributes; using System; -using System.Runtime.CompilerServices; using Windows.Win32.Foundation; namespace Windows.Win32.System.WinRT { public unsafe partial struct IStorageProviderStatusUISource : IComIID { -#pragma warning disable CS0649 // Field 'field' is never assigned to, and will always have its default value 'value' - private void** lpVtbl; -#pragma warning restore CS0649 // Field 'field' is never assigned to, and will always have its default value 'value' - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public HRESULT GetStatusUI(IStorageProviderStatusUI** result) - { - return (HRESULT)((delegate* unmanaged[MemberFunction])lpVtbl[6])((IStorageProviderStatusUISource*)Unsafe.AsPointer(ref this), result); - } + [GeneratedVTableFunction(Index = 6)] + public partial HRESULT GetStatusUI(IStorageProviderStatusUI** result); [GuidRVAGen.Guid("A306C249-3D66-5E70-9007-E43DF96051FF")] public static partial ref readonly Guid Guid { get; } diff --git a/src/Files.App.CsWin32/IStorageProviderStatusUISourceFactory.cs b/src/Files.App.CsWin32/IStorageProviderStatusUISourceFactory.cs index 9ab9495766d7..4065c1800c10 100644 --- a/src/Files.App.CsWin32/IStorageProviderStatusUISourceFactory.cs +++ b/src/Files.App.CsWin32/IStorageProviderStatusUISourceFactory.cs @@ -1,23 +1,16 @@ // Copyright (c) Files Community // Licensed under the MIT License. +using Files.Shared.Attributes; using System; -using System.Runtime.CompilerServices; using Windows.Win32.Foundation; namespace Windows.Win32.System.WinRT { public unsafe partial struct IStorageProviderStatusUISourceFactory : IComIID { -#pragma warning disable CS0649 // Field 'field' is never assigned to, and will always have its default value 'value' - private void** lpVtbl; -#pragma warning restore CS0649 // Field 'field' is never assigned to, and will always have its default value 'value' - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public HRESULT GetStatusUISource(nint syncRootId, IStorageProviderStatusUISource** result) - { - return (HRESULT)((delegate* unmanaged[MemberFunction])lpVtbl[6])((IStorageProviderStatusUISourceFactory*)Unsafe.AsPointer(ref this), syncRootId, result); - } + [GeneratedVTableFunction(Index = 6)] + public partial HRESULT GetStatusUISource(nint syncRootId, IStorageProviderStatusUISource** result); [GuidRVAGen.Guid("12E46B74-4E5A-58D1-A62F-0376E8EE7DD8")] public static partial ref readonly Guid Guid { get; } diff --git a/src/Files.App.CsWin32/NativeMethods.txt b/src/Files.App.CsWin32/NativeMethods.txt index a758d0ef4c51..3e0a28cb0dd9 100644 --- a/src/Files.App.CsWin32/NativeMethods.txt +++ b/src/Files.App.CsWin32/NativeMethods.txt @@ -251,9 +251,7 @@ WinVerifyTrust FileTimeToSystemTime FileTimeToLocalFileTime SystemTimeToFileTime -CRYPTOAPI_BLOB CMSG_SIGNER_INFO -SignDataHandle CRYPT_ATTRIBUTE FILETIME CRYPT_BIT_BLOB @@ -266,6 +264,5 @@ CATALOG_INFO WINTRUST_FILE_INFO WINTRUST_DATA HCERTSTORE -HCRYPTMSG CERT_QUERY_ENCODING_TYPE CertGetNameString diff --git a/src/Files.App/Files.App.csproj b/src/Files.App/Files.App.csproj index 872cd58d1cd9..06a1a9a4f0e3 100644 --- a/src/Files.App/Files.App.csproj +++ b/src/Files.App/Files.App.csproj @@ -24,6 +24,7 @@ true true true + false Debug;Release Files.App.Server;Microsoft.UI.Content.ContentExternalOutputLink;Microsoft.UI.Content.IContentExternalOutputLink bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ diff --git a/src/Files.App/Strings/hr-HR/Resources.resw b/src/Files.App/Strings/hr-HR/Resources.resw index c7a8b9de46b2..5dcc668f3314 100644 --- a/src/Files.App/Strings/hr-HR/Resources.resw +++ b/src/Files.App/Strings/hr-HR/Resources.resw @@ -166,7 +166,7 @@ Preskoči - Select all + Odaberi sve Obrnuti izbor @@ -184,7 +184,7 @@ Očisti sve stavke - Unesite putanju ili upišite ">" kao bi otvorili paletu naredbi + Unesite putanju za navigaciju ili upišite „>” za otvaranje palete naredbi Traži @@ -196,7 +196,7 @@ Ukloni ovu stavku - GitHub Repozitorij + GitHub repozitorij O aplikaciji @@ -283,7 +283,7 @@ Izbriši - Pin to Sidebar + Prikvači na alatnu traku Brzi pristup Dobrodošli u Files! @@ -352,7 +352,7 @@ Postavi kao pozadinu zaključanog zaslona - Set as app background + Postavi kao pozadinu aplikacije Ovu stavku je nemoguće izbrisati @@ -906,10 +906,10 @@ Bitrata kodiranja - Audio Encoding Bitrate + Brzina kodiranja zvuka - Video Encoding Bitrate + Brzina kodiranja videa Kompresija @@ -1020,10 +1020,10 @@ Ne - Prikaži zaštićene Sistemske Datoteke + Prikaz zaštićenih sistemskih datoteka - Sync layout and sorting preferences across directories + Sinkroniziraj raspored i postavke sortiranja između direktorija Prilagodi kontekstni izbornik desnog klika @@ -1059,7 +1059,7 @@ Novo okno - Open in new pane + Otvori u novom panelu Datoteke i mape @@ -1119,7 +1119,7 @@ Naziv stavke - Unpin from Sidebar + Otkvači s alatne trake Brzi pristup Mreža @@ -1134,13 +1134,13 @@ Odabrano okno za pregled datoteke - Feedback + Povratne informacije Dostupno kada je online - Documentation + Dokumentacija Dostupno izvan mreže @@ -1203,7 +1203,7 @@ Mapiranje mrežnog pogona - Pinned + Prikvačeno Biblioteke @@ -1413,7 +1413,7 @@ {0} stavki - Reopen tab + Ponovno otvori karticu Preimenuj @@ -1446,7 +1446,7 @@ Dopusti - Deny + Odbij Potpuna kontrola @@ -1455,10 +1455,10 @@ Popis sadržaja mape - Modify + Izmjena - Permissions for {0} + Dozvole za {0} Čitanje i izvršavanje @@ -1485,7 +1485,7 @@ Nepoznati vlasnik - Extract archive + Raspakiranje arhive Otvori odredišnu mapu pri završetku @@ -1740,10 +1740,10 @@ Cards - Otvori mape u novoj kartici + Otvaranje mapa u novoj kartici - Status center + Status centar Stupac datum stvaranja @@ -1773,16 +1773,16 @@ Oznaka datoteke - Otvori datoteke jednim klikom + Otvaranje stavki jednim klikom Putanja do mape - Izvezi postavke + Izvoz postavki - Uvezi postavke + Uvoz postavki Nije moguće uvesti postavke. Datoteka postavki je oštećena. @@ -1818,16 +1818,16 @@ Ništa - On Windows login + Prilikom prijave u Windows - On this program start + Prilikom pokretanja ove aplikacije Sustav - System (Enhanced) + Sustav (napredno) 16-bit (65536) color @@ -1836,19 +1836,19 @@ 8-bit (256) color - Disable full-screen optimizations + Onemogući optimizacije punog zaslona - Run in 640 x 480 screen resolution + Pokreni u rezoluciji zaslona 640 x 480 - Override high DPI scaling behavior + Zamijeni ponašanje skaliranja pri visokom DPI-u - Reduced color mode + Način rada s manje boja - Register this program for restart + Registriraj ovu aplikaciju za ponovno pokretanje Start window @@ -1866,28 +1866,28 @@ Pokreni kao administrator - Run compatibility troubleshooter + Pokreni alat za rješavanje problema s kompatibilnošću - Use DPI settings of the main monitor + Koristi DPI postavke glavnog monitora - Do not adjust DPI + Ne prilagođavaj DPI - Do not override DPI + Ne mijenjaj DPI Način kompatibilnosti - No reduced color + Bez smanjenog broja boja Third party libraries - Ova opcija izmjenjuje registry i može imati nepredviđene posljedice na vašem uređaju. Nastavite na vlastitu odgovornost. + Ova opcija mijenja sistemski registar i može imati neočekivane posljedice na vaš uređaj. Nastavite na vlastitu odgovornost. The flatten operations are permanent and not recommended. Continue at your own risk. @@ -1896,16 +1896,16 @@ Stvaranje biblioteke - Enter Library name + Unesite naziv biblioteke - Izračunaj veličinu mape + Izračunavanje veličine mapa Nedavno korištene datoteke - Otvori Files kod pokretanja Windowsa + Otvori Files pri pokretanju Windowsa Atributi: @@ -2007,13 +2007,13 @@ Prilagođeno - Set as desktop slideshow + Postavi kao dijaprojekciju na radnoj površini - Size all columns to fit + Prilagodi širinu svih stupaca - Automatically choose the best layout + Prilagodljivi raspored Adaptive (Ctrl+Shift+7) @@ -2022,10 +2022,10 @@ Ctrl+Shift+7 - Show alternate data streams + Prikaz alternativnih tokova podataka - Behaviors + Ponašanja Hello! @@ -2046,13 +2046,13 @@ Dismiss - Set as background + Postavi kao pozadinu Postavi kao pozadinu radne površine - Set as default + Postavi kao zadano Stupac datum promjene @@ -2070,16 +2070,16 @@ Stupac vrsta - Lockscreen + Zaključani zaslon - Open folders with a single click in the Columns Layout + Otvaranje mapa jednim klikom u prikazu stupaca - Opening items + Otvaranje stavki - Compress + Komprimiraj Move all contents from subfolders into the selected location @@ -2097,7 +2097,7 @@ Show flatten options - Select files and folders when hovering over them + Odabir datoteka i mapa prilikom prelaska pokazivača iznad njih Are you sure you want to restore all items in the recycle bin? @@ -2115,7 +2115,7 @@ Restore All Items - Restore + Vrati Shortcut cannot be opened @@ -2139,10 +2139,10 @@ Path - Sorting and grouping + Sortiranje i grupiranje - Create archive + Stvori arhivu Napravi @@ -2151,31 +2151,31 @@ Create {0} - Enter a name + Unesite naziv - Compression level + Razina kompresije Ultra - High + Visoka - Normal + Normalna - Low + Niska - Fast + Brza Ništa - Splitting size + Veličina dijeljenja Do not split @@ -2193,7 +2193,7 @@ Blu-ray - Encryption + Šifriranje Lozinka @@ -2205,16 +2205,16 @@ Sync status column - Group by + Grupiraj po Sortiraj po - Group in descending order + Grupiraj silaznim redom - Sort in descending order + Sortiraj silaznim redom Create a new shortcut @@ -2226,13 +2226,13 @@ Enter the location of the item: - Creates a shortcut + Stvara prečac Recently used files is currently disabled in Windows File Explorer. - Edit settings file + Uredi datoteku postavki Open settings file in your default editor @@ -2256,7 +2256,7 @@ Postavke - Double click on a blank space to go up one directory + Dvostruki klik na prazno mjesto za odlazak u nadređenu mapu View more @@ -2292,28 +2292,28 @@ Network folder error - App version + Verzija aplikacije - Windows version + Windows verzija - Always + Uvijek - Permanent deletion only + Samo za trajno brisanje - Never + Nikad Tag color - New tag + Nova oznaka - Create new tag + Izradi novu oznaku Loading... @@ -2322,7 +2322,7 @@ Stavke izbornika desnog klika - File extensions + Datotečni nastavci Oblik @@ -2346,7 +2346,7 @@ Width - Apply this action to all conflicting items + Primijeni ovu radnju na sve stavke u sukobu Open in Windows Terminal @@ -2355,16 +2355,16 @@ Open in Windows Terminal as administrator - Save + Spremi Multiselect - Reorder sidebar items + Promijeni redoslijed stavki bočne trake - Hashes + Hashevi An error occurred during the calculation @@ -2421,7 +2421,7 @@ Toggle full screen mode - Enter compact overlay mode + Uđi u kompaktni prikaz Exit compact overlay mode @@ -2490,7 +2490,7 @@ Create new shortcut to any item - Empty the contents of Recycle Bin + Isprazni koš za smeće Open "Format Drive" menu for selected item @@ -2634,10 +2634,10 @@ Switch to list view - List + Popis - Grid + Mreža Switch to grid view @@ -2980,13 +2980,13 @@ Toggle selection - Show warning when changing file extensions + Prikaz upozorenja prilikom promjene ekstenzija datoteka This PC - Recycle Bin + Koš za smeće Untagged @@ -3025,7 +3025,7 @@ Are you sure you want to move these files without their properties? - Show checkboxes when selecting items + Prikaz potvrdnih okvira prilikom odabira stavki Edit path in the OmniBar @@ -3055,7 +3055,7 @@ Redo the last file operation - Location: + Lokacija: Group items by month @@ -3079,7 +3079,7 @@ Godina - Group by date unit + Grupiraj po vremenskoj jedinici Group items by day of date created @@ -3148,16 +3148,16 @@ Invalid branch name - Create branch + Stvori granu - Create branch + Stvori granu - Based on + Na temelju - Switch to new branch + Prebaci na novu granu Create a folder with the currently selected {0, plural, one {item} other {items}} @@ -3175,13 +3175,13 @@ Open File Explorer properties window - Locals + Lokalne - Remotes + Udaljene - Translate on Crowdin + Prevedi na Crowdin-u Fetch @@ -3199,7 +3199,7 @@ Run git pull - Preview + Pretpregled Git status @@ -3215,22 +3215,22 @@ Text indicating that multiple selected files have different metadata values. - Author + Autor - Date committed + Datum commita - Commit message + Poruka commita - Commit SHA + SHA commita Git - Acrylic + Akril Mica @@ -3366,7 +3366,7 @@ Turn on BitLocker - Manage BitLocker + Uključi BitLocker The following items are too large to be copied to this drive @@ -3384,7 +3384,7 @@ Due to a limitation with Windows and WinAppSdk, drag and drop isn't available when running Files as admin. If you wish to use drag and drop, you can work around this limitation by opening UAC (User Account Control) from the Start Menu and selecting the third level, and restarting Windows. Otherwise, you can keep using Files without drag & drop. - Leave app running in the background when the window is closed + Ostavi aplikaciju pokrenutu u pozadini prilikom zatvaranja prozora Plava @@ -3463,7 +3463,7 @@ One of the custom color themes - Clear completed + Čišćenje završeno Name: @@ -3734,7 +3734,7 @@ Unblock downloaded file - No ongoing file operations + Nema aktivnih operacija s datotekama The option to open Files on Windows Startup is not available due to your system settings or group policy. To re-enable, open the startup page in Task Manager. @@ -3749,16 +3749,16 @@ Failed to open the settings file - Delete Git branch + Izbriši Git granu - Are you sure you want to permanently delete "{0}" branch? + Jeste li sigurni da želite trajno izbrisati granu "{0}"? - Connected to GitHub + Povezano s GitHubom - Logout + Odjava Connect to GitHub @@ -3776,7 +3776,7 @@ Extract selected {0, plural, one {archive} other {archives}} here for single-item or to new folder for multi-item - Extract here (Smart) + Raspakiraj ovdje (Pametno) Sortiraj datoteke i mape zajedno @@ -3812,7 +3812,7 @@ Failed to share items - Scroll to previous folder when navigating up + Pomicanje na prethodnu mapu prilikom navigacije prema gore Open new window @@ -3836,7 +3836,7 @@ Where did Files go? - Questions & discussions + Pitanja i rasprave Compact @@ -3891,25 +3891,25 @@ Used to describe layout sizes - Details view + Prikaz detalja - Layout type + Vrsta rasporeda Radnje - Commands + Naredbe - Add command + Dodaj naredbu - Restore defaults + Vrati zadano - Choose an action + Odaberite radnju Are you sure you want to restore the default key bindings? This action cannot be undone. diff --git a/src/Files.App/Strings/ja-JP/Resources.resw b/src/Files.App/Strings/ja-JP/Resources.resw index 3343ff1d6965..d7fac53c3eda 100644 --- a/src/Files.App/Strings/ja-JP/Resources.resw +++ b/src/Files.App/Strings/ja-JP/Resources.resw @@ -1107,10 +1107,10 @@ 絞り込みヘッダーの表示/非表示を切り替える - Toggle dual pane + デュアルペインの切り替え - Toggle dual pane mode + デュアルペインモードを切り替え プレビューを利用できません @@ -4012,7 +4012,7 @@ This is the friendly name for bitmap files. - Image Files + 画像ファイル This is the friendly name for image files. @@ -4050,13 +4050,13 @@ 縦に分割 - Split pane vertically + ペインを垂直に分割 横に分割 - Split pane horizontally + ペインを水平に分割 縦に配置 diff --git a/src/Files.Core.SourceGenerator/Data/ParameterTypeNamePair.cs b/src/Files.Core.SourceGenerator/Data/ParameterTypeNamePair.cs new file mode 100644 index 000000000000..84bedd868162 --- /dev/null +++ b/src/Files.Core.SourceGenerator/Data/ParameterTypeNamePair.cs @@ -0,0 +1,7 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.Core.SourceGenerator.Data +{ + internal record ParameterTypeNamePair(string FullyQualifiedTypeName, string ValueName); +} diff --git a/src/Files.Core.SourceGenerator/Data/VTableFunctionInfo.cs b/src/Files.Core.SourceGenerator/Data/VTableFunctionInfo.cs new file mode 100644 index 000000000000..cecabac72122 --- /dev/null +++ b/src/Files.Core.SourceGenerator/Data/VTableFunctionInfo.cs @@ -0,0 +1,15 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.Core.SourceGenerator.Data +{ + internal record VTableFunctionInfo( + string FullyQualifiedParentTypeName, + string ParentTypeNamespace, + string ParentTypeName, + bool IsReturnTypeVoid, + string Name, + string ReturnTypeName, + int Index, + EquatableArray Parameters); +} diff --git a/src/Files.Core.SourceGenerator/Files.Core.SourceGenerator.csproj b/src/Files.Core.SourceGenerator/Files.Core.SourceGenerator.csproj index 385420e9720a..3feb86b8465e 100644 --- a/src/Files.Core.SourceGenerator/Files.Core.SourceGenerator.csproj +++ b/src/Files.Core.SourceGenerator/Files.Core.SourceGenerator.csproj @@ -9,6 +9,8 @@ AnyCPU true Debug;Release + true + true diff --git a/src/Files.Core.SourceGenerator/Generators/VTableFunctionGenerator.cs b/src/Files.Core.SourceGenerator/Generators/VTableFunctionGenerator.cs new file mode 100644 index 000000000000..2a761cf1a483 --- /dev/null +++ b/src/Files.Core.SourceGenerator/Generators/VTableFunctionGenerator.cs @@ -0,0 +1,115 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.Core.SourceGenerator.Generators +{ + [Generator(LanguageNames.CSharp)] + internal class VTableFunctionGenerator : IIncrementalGenerator + { + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var sources = context.SyntaxProvider.ForAttributeWithMetadataName( + "Files.Shared.Attributes.GeneratedVTableFunctionAttribute", + static (node, token) => + { + token.ThrowIfCancellationRequested(); + + // Check if the method has partial modifier and is public or internal (and not static) + if (node is not MethodDeclarationSyntax { AttributeLists.Count: > 0 } method || + !method.Modifiers.Any(SyntaxKind.PartialKeyword) || + !(method.Modifiers.Any(SyntaxKind.PublicKeyword) || method.Modifiers.Any(SyntaxKind.InternalKeyword)) || + method.Modifiers.Any(SyntaxKind.StaticKeyword)) + return false; + + // Check if the type containing the method has partial modifier and is a struct + if (node.Parent is not TypeDeclarationSyntax { Keyword.RawKind: (int)SyntaxKind.StructKeyword, Modifiers: { } modifiers } || + !modifiers.Any(SyntaxKind.PartialKeyword)) + return false; + + return true; + }, + static (context, token) => + { + token.ThrowIfCancellationRequested(); + + var fullyQualifiedParentTypeName = context.TargetSymbol.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var structNamespace = context.TargetSymbol.ContainingType.ContainingNamespace.ToString(); + var structName = context.TargetSymbol.ContainingType.Name; + var methodSymbol = (IMethodSymbol)context.TargetSymbol; + var isReturnTypeVoid = methodSymbol.ReturnsVoid; + var functionName = methodSymbol.Name; + var returnTypeName = methodSymbol.ReturnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var parameters = methodSymbol.Parameters.Select(x => new ParameterTypeNamePair(x.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), x.Name)); + var index = (int)context.Attributes[0].NamedArguments.FirstOrDefault(x => x.Key.Equals("Index")).Value.Value!; + + return new VTableFunctionInfo(fullyQualifiedParentTypeName, structNamespace, structName, isReturnTypeVoid, functionName, returnTypeName, index, new(parameters.ToImmutableArray())); + }) + .Where(static item => item is not null) + .Collect() + .Select((items, token) => + { + token.ThrowIfCancellationRequested(); + + return items.GroupBy(source => source.FullyQualifiedParentTypeName, StringComparer.OrdinalIgnoreCase).ToImmutableArray(); + }); + + + context.RegisterSourceOutput(sources, (context, sources) => + { + foreach (var source in sources) + { + var fileName = $"{source.ToImmutableArray().ElementAt(0).ParentTypeNamespace}.{source.ToImmutableArray().ElementAt(0).ParentTypeName}_VTableFunctions.g.cs"; + var generatedCSharpCode = GenerateVtableFunctionsForStruct(source.ToImmutableArray()); + + context.AddSource(fileName, generatedCSharpCode); + } + }); + } + + private string GenerateVtableFunctionsForStruct(ImmutableArray sources) + { + StringBuilder builder = new(); + + builder.AppendLine($"// "); + builder.AppendLine(); + builder.AppendLine($"using global::System.Runtime.CompilerServices;"); + builder.AppendLine(); + builder.AppendLine($"#pragma warning disable"); + builder.AppendLine(); + + builder.AppendLine($"namespace {sources.ElementAt(0).ParentTypeNamespace};"); + builder.AppendLine(); + + builder.AppendLine($"public unsafe partial struct {sources.ElementAt(0).ParentTypeName}"); + builder.AppendLine($"{{"); + + builder.AppendLine($" private void** lpVtbl;"); + builder.AppendLine(); + + var sourceIndex = 0; + var sourceCount = sources.Count(); + + foreach (var source in sources) + { + var returnTypeName = source.IsReturnTypeVoid ? "void" : "int"; + + builder.AppendLine($" [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]"); + + builder.AppendLine($" public partial {source.ReturnTypeName} {source.Name}({string.Join(", ", source.Parameters.Select(x => $"{x.FullyQualifiedTypeName} {x.ValueName}"))})"); + builder.AppendLine($" {{"); + builder.AppendLine($" return ({source.ReturnTypeName})((delegate* unmanaged[MemberFunction]<{sources.ElementAt(0).FullyQualifiedParentTypeName}*, {string.Join(", ", source.Parameters.Select(x => $"{x.FullyQualifiedTypeName}"))}, {returnTypeName}>)(lpVtbl[{source.Index}]))"); + builder.AppendLine($" (({sources.ElementAt(0).FullyQualifiedParentTypeName}*)global::System.Runtime.CompilerServices.Unsafe.AsPointer(ref this), {string.Join(", ", source.Parameters.Select(x => $"{x.ValueName}"))});"); + builder.AppendLine($" }}"); + + if (sourceIndex < sourceCount - 1) + builder.AppendLine(); + + sourceIndex++; + } + + builder.AppendLine($"}}"); + + return builder.ToString(); + } + } +} diff --git a/src/Files.Core.SourceGenerator/Properties/launchSettings.json b/src/Files.Core.SourceGenerator/Properties/launchSettings.json new file mode 100644 index 000000000000..2804cd5078a4 --- /dev/null +++ b/src/Files.Core.SourceGenerator/Properties/launchSettings.json @@ -0,0 +1,20 @@ +{ + "profiles": { + "Files.App.CsWin32": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\Files.App.CsWin32\\Files.App.CsWin32.csproj" + }, + "Files.App.Controls": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\Files.App.Controls\\Files.App.Controls.csproj" + }, + "Files.App.Server": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\Files.App.Server\\Files.App.Server.csproj" + }, + "Files.App": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\Files.App\\Files.App.csproj" + } + } +} \ No newline at end of file diff --git a/src/Files.Core.SourceGenerator/Utilities/EquatableArray.cs b/src/Files.Core.SourceGenerator/Utilities/EquatableArray.cs new file mode 100644 index 000000000000..c0ad88cbcfd1 --- /dev/null +++ b/src/Files.Core.SourceGenerator/Utilities/EquatableArray.cs @@ -0,0 +1,129 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using System.Collections; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Files.Core.SourceGenerator.Utilities; + +/// +/// An immutable array wrapper that supports value-based equality comparison. +/// +/// +/// For the reference implementation, see EquatableArray{T}@ComputeSharp +/// +/// +/// +internal readonly struct EquatableArray(ImmutableArray array) : IEquatable>, IEnumerable + where T : IEquatable +{ + private readonly T[]? array = ImmutableCollectionsMarshal.AsArray(array); + + public ref readonly T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref AsImmutableArray().ItemRef(index); + } + + public bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => AsImmutableArray().IsEmpty; + } + + public bool IsDefaultOrEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => AsImmutableArray().IsDefaultOrEmpty; + } + + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => AsImmutableArray().Length; + } + + public bool Equals(EquatableArray array) + { + return AsSpan().SequenceEqual(array.AsSpan()); + } + + public override bool Equals(object? obj) + { + return obj is EquatableArray array && Equals(this, array); + } + + public override unsafe int GetHashCode() + { + if (this.array is not T[] array) + return 0; + + HashCode hashCode = default; + + if (typeof(T) == typeof(byte)) + { + ReadOnlySpan span = array; + ref T r0 = ref MemoryMarshal.GetReference(span); + ref byte r1 = ref Unsafe.As(ref r0); + + fixed (byte* p = &r1) + { + ReadOnlySpan bytes = new(p, span.Length); + + hashCode.AddBytes(bytes); + } + } + else + { + foreach (T item in array) + hashCode.Add(item); + } + + return hashCode.ToHashCode(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImmutableArray AsImmutableArray() + { + return ImmutableCollectionsMarshal.AsImmutableArray(this.array); + } + + public static EquatableArray FromImmutableArray(ImmutableArray array) + { + return new(array); + } + + public ReadOnlySpan AsSpan() + { + return AsImmutableArray().AsSpan(); + } + + public T[] ToArray() + { + return [.. AsImmutableArray()]; + } + + public ImmutableArray.Enumerator GetEnumerator() + { + return AsImmutableArray().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)AsImmutableArray()).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)AsImmutableArray()).GetEnumerator(); + } + + public static implicit operator EquatableArray(ImmutableArray array) => FromImmutableArray(array); + + public static implicit operator ImmutableArray(EquatableArray array) => array.AsImmutableArray(); + + public static bool operator ==(EquatableArray left, EquatableArray right) => left.Equals(right); + + public static bool operator !=(EquatableArray left, EquatableArray right) => !left.Equals(right); +} diff --git a/src/Files.Core.SourceGenerator/Utilities/HashCode.cs b/src/Files.Core.SourceGenerator/Utilities/HashCode.cs new file mode 100644 index 000000000000..530daf5c1b02 --- /dev/null +++ b/src/Files.Core.SourceGenerator/Utilities/HashCode.cs @@ -0,0 +1,354 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Cryptography; + +#pragma warning disable CS0809 +#pragma warning disable CA1066 + +namespace System +{ + // xxHash32 is used for the hash code. + // https://github.com/Cyan4973/xxHash + // Polyfill System.HashCode + public struct HashCode + { + private static readonly uint s_seed = GenerateGlobalSeed(); + + private const uint Prime1 = 2654435761U; + private const uint Prime2 = 2246822519U; + private const uint Prime3 = 3266489917U; + private const uint Prime4 = 668265263U; + private const uint Prime5 = 374761393U; + + private uint _v1, _v2, _v3, _v4; + private uint _queue1, _queue2, _queue3; + private uint _length; + + private static unsafe uint GenerateGlobalSeed() + { + byte[] bytes = new byte[4]; + + RandomNumberGenerator.Create().GetBytes(bytes); + + return BitConverter.ToUInt32(bytes, 0); + } + + public static int Combine(T1 value1) + { + // Provide a way of diffusing bits from something with a limited + // input hash space. For example, many enums only have a few + // possible hashes, only using the bottom few bits of the code. Some + // collections are built on the assumption that hashes are spread + // over a larger space, so diffusing the bits may help the + // collection work more efficiently. + + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + + uint hash = MixEmptyState(); + hash += 4; + + hash = QueueRound(hash, hc1); + + hash = MixFinal(hash); + return (int)hash; + } + + public static int Combine(T1 value1, T2 value2) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + + uint hash = MixEmptyState(); + hash += 8; + + hash = QueueRound(hash, hc1); + hash = QueueRound(hash, hc2); + + hash = MixFinal(hash); + return (int)hash; + } + + public static int Combine(T1 value1, T2 value2, T3 value3) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + + uint hash = MixEmptyState(); + hash += 12; + + hash = QueueRound(hash, hc1); + hash = QueueRound(hash, hc2); + hash = QueueRound(hash, hc3); + + hash = MixFinal(hash); + return (int)hash; + } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + hash += 16; + + hash = MixFinal(hash); + return (int)hash; + } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + hash += 20; + + hash = QueueRound(hash, hc5); + + hash = MixFinal(hash); + return (int)hash; + } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + uint hc6 = (uint)(value6?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + hash += 24; + + hash = QueueRound(hash, hc5); + hash = QueueRound(hash, hc6); + + hash = MixFinal(hash); + return (int)hash; + } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + uint hc6 = (uint)(value6?.GetHashCode() ?? 0); + uint hc7 = (uint)(value7?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + hash += 28; + + hash = QueueRound(hash, hc5); + hash = QueueRound(hash, hc6); + hash = QueueRound(hash, hc7); + + hash = MixFinal(hash); + return (int)hash; + } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + uint hc6 = (uint)(value6?.GetHashCode() ?? 0); + uint hc7 = (uint)(value7?.GetHashCode() ?? 0); + uint hc8 = (uint)(value8?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + v1 = Round(v1, hc5); + v2 = Round(v2, hc6); + v3 = Round(v3, hc7); + v4 = Round(v4, hc8); + + uint hash = MixState(v1, v2, v3, v4); + hash += 32; + + hash = MixFinal(hash); + return (int)hash; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) + { + v1 = s_seed + Prime1 + Prime2; + v2 = s_seed + Prime2; + v3 = s_seed; + v4 = s_seed - Prime1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Round(uint hash, uint input) + { + return RotateLeft(hash + input * Prime2, 13) * Prime1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint QueueRound(uint hash, uint queuedValue) + { + return RotateLeft(hash + queuedValue * Prime3, 17) * Prime4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixState(uint v1, uint v2, uint v3, uint v4) + { + return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18); + } + + private static uint MixEmptyState() + { + return s_seed + Prime5; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixFinal(uint hash) + { + hash ^= hash >> 15; + hash *= Prime2; + hash ^= hash >> 13; + hash *= Prime3; + hash ^= hash >> 16; + return hash; + } + + public void Add(T value) + { + Add(value?.GetHashCode() ?? 0); + } + + public void Add(T value, IEqualityComparer? comparer) + { + Add(value is null ? 0 : (comparer?.GetHashCode(value) ?? value.GetHashCode())); + } + + public void AddBytes(ReadOnlySpan value) + { + ref byte pos = ref MemoryMarshal.GetReference(value); + ref byte end = ref Unsafe.Add(ref pos, value.Length); + + while ((nint)Unsafe.ByteOffset(ref pos, ref end) >= sizeof(int)) + { + Add(Unsafe.ReadUnaligned(ref pos)); + pos = ref Unsafe.Add(ref pos, sizeof(int)); + } + + while (Unsafe.IsAddressLessThan(ref pos, ref end)) + { + Add((int)pos); + pos = ref Unsafe.Add(ref pos, 1); + } + } + + private void Add(int value) + { + uint val = (uint)value; + uint previousLength = _length++; + uint position = previousLength % 4; + + if (position == 0) + _queue1 = val; + else if (position == 1) + _queue2 = val; + else if (position == 2) + _queue3 = val; + else + { + if (previousLength == 3) + Initialize(out _v1, out _v2, out _v3, out _v4); + + _v1 = Round(_v1, _queue1); + _v2 = Round(_v2, _queue2); + _v3 = Round(_v3, _queue3); + _v4 = Round(_v4, val); + } + } + + public int ToHashCode() + { + uint length = _length; + uint position = length % 4; + uint hash = length < 4 ? MixEmptyState() : MixState(_v1, _v2, _v3, _v4); + + hash += length * 4; + + if (position > 0) + { + hash = QueueRound(hash, _queue1); + + if (position > 1) + { + hash = QueueRound(hash, _queue2); + if (position > 2) + hash = QueueRound(hash, _queue3); + } + } + + hash = MixFinal(hash); + return (int)hash; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint RotateLeft(uint value, int offset) + { + return (value << offset) | (value >> (32 - offset)); + } + + [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => throw new NotSupportedException(); + + [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => throw new NotSupportedException(); + } +} diff --git a/src/Files.Shared/Attributes/GeneratedVTableFunctionAttribute.cs b/src/Files.Shared/Attributes/GeneratedVTableFunctionAttribute.cs new file mode 100644 index 000000000000..109a72b7ada4 --- /dev/null +++ b/src/Files.Shared/Attributes/GeneratedVTableFunctionAttribute.cs @@ -0,0 +1,12 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using System; + +namespace Files.Shared.Attributes; + +[AttributeUsage(AttributeTargets.Method, Inherited = false)] +public sealed class GeneratedVTableFunctionAttribute : Attribute +{ + public required int Index { get; init; } +}