This commit is contained in:
2026-09-01 10:45:21 -04:00
commit ac32649783
51 changed files with 4837 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
{ inputs, ... }:
{
perSystem =
{
pkgs,
self',
...
}:
{
packages = {
affinity-v3 = pkgs.callPackage ./package.nix {
inherit inputs;
};
default = self'.packages.affinity-v3;
};
};
}
+30
View File
@@ -0,0 +1,30 @@
{
makeDesktopItem,
lib,
affinity-v3 ? null,
}:
{
affinity-v3 = makeDesktopItem rec {
desktopName = "Affinity";
name = "affinity-v3";
exec = "${lib.getExe affinity-v3} %U";
icon = name;
type = "Application";
categories = [ "Graphics" ];
keywords = [
"Graphics"
"2DGraphics"
"RasterGraphics"
"VectorGraphics"
"image"
"editor"
"vector"
"drawing"
];
mimeTypes = [
"application/afphoto"
"application/afdesign"
];
startupWMClass = "affinity.exe";
};
}
+20
View File
@@ -0,0 +1,20 @@
{
fetchurl,
pkgs,
lib,
}:
rec {
icon = fetchurl {
url = "https://web.archive.org/web/20260310003857/https://static.canva.com/domain-assets/affinity/static/images/apple-touch-180x180-1.png";
sha256 = "sha256-KLT9loFiD2y8uNXNzc27DZ4A73yEs6Ntr4h3WULG5HM=";
};
iconPackage = pkgs.runCommand "affinity-v3-icons" { } ''
mkdir -p $out/share/icons/hicolor/256x256/apps
${lib.getExe pkgs.imagemagick} \
${icon} \
-resize 256x256 \
$out/share/icons/hicolor/256x256/apps/affinity-v3.png
'';
}
+50
View File
@@ -0,0 +1,50 @@
{
symlinkJoin,
callPackage,
inputs,
lib,
...
}:
let
wine-packages = callPackage ../wine/packages.nix {
inherit inputs;
};
apl-combined = callPackage ../apl/apl-combined.nix {
src = inputs.plugin-loader-src;
};
prefixBase = callPackage ../prefixWithAffinity.nix {
inherit inputs wine-packages apl-combined;
};
runner = callPackage ../runner/package.nix {
inherit prefixBase inputs wine-packages;
registry-patches = (callPackage ../registry-patches.nix { }).combined;
name = "v3";
};
desktop = callPackage ./desktopItems.nix {
affinity-v3 = runner.package;
};
icons = callPackage ./icons.nix { };
icon-package = icons.iconPackage;
in
symlinkJoin {
name = "Affinity v3";
pname = "affinity-v3";
paths = [
runner.package
desktop.affinity-v3
icon-package
];
meta = {
mainProgram = "affinity-v3";
description = "Affinity v3";
license = lib.licenses.unfree;
homepage = "https://affinity.serif.com/";
platforms = [ "x86_64-linux" ];
};
}
+22
View File
@@ -0,0 +1,22 @@
{ inputs, ... }:
{
perSystem =
{
pkgs,
...
}:
let
makeV2Package =
name:
(pkgs.callPackage ./package.nix {
inherit inputs name;
});
in
{
packages = {
affinity-photo = makeV2Package "Photo";
affinity-designer = makeV2Package "Designer";
affinity-publisher = makeV2Package "Publisher";
};
};
}
+71
View File
@@ -0,0 +1,71 @@
{
makeDesktopItem,
lib,
photo ? null,
publisher ? null,
designer ? null,
}:
{
photo = makeDesktopItem rec {
desktopName = "Affinity Photo 2";
name = "affinity-photo";
exec = "${lib.getExe photo} %U";
icon = name;
type = "Application";
categories = [ "Graphics" ];
keywords = [
"Graphics"
"2DGraphics"
"RasterGraphics"
"image"
"editor"
"vector"
"drawing"
];
mimeTypes = [ "application/afphoto" ];
startupWMClass = "photo.exe";
};
designer = makeDesktopItem rec {
desktopName = "Affinity Designer 2";
name = "affinity-designer";
exec = "${lib.getExe designer} %U";
icon = name;
type = "Application";
categories = [ "Graphics" ];
keywords = [
"Graphics"
"2DGraphics"
"VectorGraphics"
"image"
"editor"
"vector"
"drawing"
];
mimeTypes = [ "application/afdesign" ];
startupWMClass = "designer.exe";
};
publisher = makeDesktopItem rec {
desktopName = "Affinity Publisher 2";
name = "affinity-publisher";
exec = "${lib.getExe publisher} %U";
icon = name;
type = "Application";
categories = [ "Graphics" ];
keywords = [
"Graphics"
"2DGraphics"
"RasterGraphics"
"VectorGraphics"
"image"
"editor"
"vector"
"drawing"
];
mimeTypes = [
"application/afdesign"
"application/afphoto"
# Note that there is no mime type for a `.afpub` file
];
startupWMClass = "publisher.exe";
};
}
+35
View File
@@ -0,0 +1,35 @@
{
fetchurl,
pkgs,
lib,
}:
rec {
icons = {
photo = fetchurl {
url = "https://cdn.serif.com/store/img/logos/affinity-photo-2-020520191502.svg";
sha256 = "04bnf12znp9dgkwpk46c01381cw298gq14ga7j7dwccyl3m556d7";
};
designer = fetchurl {
url = "https://cdn.serif.com/store/img/logos/affinity-designer-2-020520191502.svg";
sha256 = "025j4aq8py2a5r34hx39nlhdmps06z6506h7wxchyxbbz6xz3d11";
};
publisher = fetchurl {
url = "https://cdn.serif.com/store/img/logos/affinity-publisher-2-020520191502.svg";
sha256 = "16ghh3mc2vjhvphbyy9zgan066ccp3xj1ilb6z5lmvsb94dp37aj";
};
};
mkIconPackageFor =
name:
pkgs.runCommand "${name}-icons" { } ''
mkdir -p $out/share/icons/hicolor/{256x256,scalable}/apps
cp ${icons.${lib.toLower name}} \
$out/share/icons/hicolor/scalable/apps/affinity-${lib.toLower name}.svg
${lib.getExe pkgs.inkscape} \
-w 256 -h 256 \
${icons.${lib.toLower name}} \
-o - > $out/share/icons/hicolor/256x256/apps/affinity-${lib.toLower name}.png
'';
}
+59
View File
@@ -0,0 +1,59 @@
{
symlinkJoin,
callPackage,
lib,
name,
inputs,
...
}:
let
wine-packages = callPackage ../wine/packages.nix {
inherit inputs;
};
apl-combined = callPackage ../apl/apl-combined.nix {
src = inputs.plugin-loader-src;
};
prefixBase = callPackage ../prefixWithAffinity.nix {
inherit inputs wine-packages apl-combined;
v3 = false;
};
runner = callPackage ../runner/package.nix {
inherit
prefixBase
inputs
wine-packages
name
;
registry-patches = (callPackage ../registry-patches.nix { }).combined;
};
desktop = callPackage ./desktopItems.nix {
${lib.toLower name} = runner.package;
};
icons = callPackage ./icons.nix { };
icon-package = icons.mkIconPackageFor name;
# last version of affinity v2 released
lastV2Version = "2.6.5";
in
symlinkJoin {
name = "affinity-${lib.toLower name}-${lastV2Version}";
pname = "affinity-${lib.toLower name}";
paths = [
runner.package
desktop.${lib.toLower name}
icon-package
];
meta = {
mainProgram = "affinity-${lib.toLower name}";
description = "Affinity ${name} ${lastV2Version}";
license = lib.licenses.unfree;
homepage = "https://affinity.serif.com/";
platforms = [ "x86_64-linux" ];
};
}
+30
View File
@@ -0,0 +1,30 @@
{
callPackage,
symlinkJoin,
src,
...
}:
let
version = "unstable";
apl = callPackage ./apl.nix {
inherit version src;
};
bootstrap = callPackage ./bootstrap.nix {
inherit version src;
};
in
symlinkJoin {
pname = "apl-combined";
inherit version;
paths = [
apl
bootstrap
];
postBuild = ''
mv $out/lib/AffinityPluginLoader/* $out
rm -rf $out/lib
'';
}
+26
View File
@@ -0,0 +1,26 @@
{
buildDotnetModule,
src,
version,
...
}:
buildDotnetModule rec {
pname = "AffinityPluginLoader";
inherit version src;
projectFile = "AffinityPluginLoader.sln";
patches = [ ./no-login.patch ];
nugetDeps = ./deps.json;
postInstall = ''
pushd $out/lib/${pname}
rm *.pdb
rm *.config
mkdir -p ./apl/plugins
mv ./WineFix.dll ./apl/plugins
'';
}
+37
View File
@@ -0,0 +1,37 @@
{
pkgs,
version,
src,
...
}:
pkgs.pkgsCross.mingwW64.stdenv.mkDerivation {
pname = "affinity-bootstrap";
inherit version;
src = "${src}/AffinityBootstrap";
nativeBuildInputs = [ pkgs.wine64 ];
buildPhase = ''
mkdir -p build
cat << 'EOF' > mscoree.def
LIBRARY mscoree.dll
EXPORTS
CLRCreateInstance
EOF
x86_64-w64-mingw32-dlltool -d mscoree.def -l build/libmscoree.a
$CC -shared -o build/AffinityBootstrap.dll bootstrap.c \
-I${pkgs.wine64}/include/wine/windows \
-Lbuild \
-lole32 -loleaut32 -luuid -lmscoree
'';
installPhase = ''
mkdir -p $out/lib/AffinityPluginLoader
cp build/AffinityBootstrap.dll $out/lib/AffinityPluginLoader
'';
}
+13
View File
@@ -0,0 +1,13 @@
{ inputs, ... }:
{
perSystem =
{
pkgs,
...
}:
{
packages.apl-combined = pkgs.callPackage ./apl-combined.nix {
src = inputs.plugin-loader-src;
};
};
}
+17
View File
@@ -0,0 +1,17 @@
[
{
"pname": "Lib.Harmony",
"version": "2.4.2",
"hash": "sha256-1kWS5TCQRkVZ/OSGEsnKfI3HMROEE3a3qjRV9G/F1Xk="
},
{
"pname": "Microsoft.NETFramework.ReferenceAssemblies",
"version": "1.0.3",
"hash": "sha256-FBoJP5DHZF0QHM0xLm9yd4HJZVQOuSpSKA+VQRpphEE="
},
{
"pname": "Microsoft.NETFramework.ReferenceAssemblies.net48",
"version": "1.0.3",
"hash": "sha256-in40hTjn65E1FpaRFon0nj1PY/i6tRdDK74Vm4sRBKI="
}
]
+589
View File
@@ -0,0 +1,589 @@
From 45ff4e8c5615af1e8f0a41e92580506788a10b24 Mon Sep 17 00:00:00 2001
From: marshmallow <git@althaea.zone>
Date: Mon, 13 Apr 2026 20:11:35 +1000
Subject: [PATCH] Revert "Restore Canva sign-in: Textbox to capture
authorization token (#44)"
This reverts commit 915e1cc5096475f433b8447fc7c6b7cb3b52b7d9.
---
WineFix/Patches/CanvaSignInPatch.cs | 435 -----------------------
WineFix/Patches/MainWindowLoadedPatch.cs | 55 +++
WineFix/WineFix.csproj | 4 -
WineFix/WineFixPlugin.cs | 14 -
4 files changed, 55 insertions(+), 453 deletions(-)
delete mode 100644 WineFix/Patches/CanvaSignInPatch.cs
diff --git a/WineFix/Patches/CanvaSignInPatch.cs b/WineFix/Patches/CanvaSignInPatch.cs
deleted file mode 100644
index 7c05fcb..0000000
--- a/WineFix/Patches/CanvaSignInPatch.cs
+++ /dev/null
@@ -1,435 +0,0 @@
-using System;
-using System.Globalization;
-using System.Linq;
-using System.Reflection;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using HarmonyLib;
-using AffinityPluginLoader.Core;
-
-namespace WineFix.Patches
-{
- /// <summary>
- /// Adds a paste-URL textbox to the Canva sign-in dialog so users can manually
- /// paste the authentication redirect URL from their browser, bypassing the need
- /// for the affinity:// protocol handler which doesn't work under Wine.
- /// </summary>
- public static class CanvaSignInPatch
- {
- private static FieldInfo _authHookField;
- private static object _cloudServicesService;
- private static Border _overlayPanel;
-
- public static void ApplyPatches(Harmony harmony)
- {
- Logger.Info("Applying CanvaSignIn patch...");
-
- var serifAffinity = AppDomain.CurrentDomain.GetAssemblies()
- .FirstOrDefault(a => a.GetName().Name == "Serif.Affinity");
- var serifInterop = AppDomain.CurrentDomain.GetAssemblies()
- .FirstOrDefault(a => a.GetName().Name == "Serif.Interop.Persona");
-
- if (serifAffinity == null || serifInterop == null)
- {
- Logger.Error("Required assemblies not found");
- return;
- }
-
- // Resolve the AuthHookReceived backing field on CloudServicesService
- var cssType = serifInterop.GetType("Serif.Interop.Persona.Services.CloudServicesService");
- _authHookField = cssType?.GetField("<backing_store>AuthHookReceived",
- BindingFlags.NonPublic | BindingFlags.Instance);
-
- if (_authHookField == null)
- {
- // Try the mangled name from decompilation
- _authHookField = cssType?.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
- .FirstOrDefault(f => f.Name.Contains("AuthHookReceived") && f.FieldType == typeof(Action<Uri>));
- }
-
- if (_authHookField == null)
- {
- Logger.Error("Could not find AuthHookReceived backing field");
- return;
- }
-
- // Patch ProductKeyDialog_Loaded to inject our UI
- var dialogType = serifAffinity.GetType("Serif.Affinity.UI.ProductKeyDialog");
- var loadedMethod = dialogType?.GetMethod("ProductKeyDialog_Loaded",
- BindingFlags.NonPublic | BindingFlags.Instance);
-
- if (loadedMethod == null)
- {
- Logger.Error("ProductKeyDialog_Loaded not found");
- return;
- }
-
- harmony.Patch(loadedMethod,
- postfix: new HarmonyMethod(typeof(CanvaSignInPatch), nameof(OnDialogLoaded)));
- Logger.Info("Patched ProductKeyDialog_Loaded");
- }
-
- public static void OnDialogLoaded(object __instance)
- {
- try
- {
- var window = __instance as Window;
- if (window == null) return;
-
- // Get CloudServicesService via Application.Current.GetService<T>()
- var app = Application.Current;
- var getServiceMethod = app.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
- .Where(m => m.Name == "GetService" && m.IsGenericMethod)
- .FirstOrDefault();
-
- if (getServiceMethod != null)
- {
- var cssType = _authHookField.DeclaringType;
- var bound = getServiceMethod.MakeGenericMethod(cssType);
- _cloudServicesService = bound.Invoke(app, null);
- }
-
- // Listen for DataContext property changes to detect state transitions
- var model = ((FrameworkElement)window).DataContext;
- if (model is System.ComponentModel.INotifyPropertyChanged npc)
- {
- npc.PropertyChanged += (s, e) =>
- {
- if (e.PropertyName == "State")
- InjectPasteUI(window);
- };
- }
-
- // Also try immediately in case we're already in SigningIn
- InjectPasteUI(window);
- }
- catch (Exception ex)
- {
- Logger.Error($"OnDialogLoaded failed: {ex.Message}");
- }
- }
-
- private static void InjectPasteUI(Window window)
- {
- try
- {
- var model = ((FrameworkElement)window).DataContext;
- var stateProp = model.GetType().GetProperty("State");
- if (stateProp == null) return;
-
- var state = stateProp.GetValue(model);
- if (state.ToString() == "SigningIn")
- {
- window.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Loaded,
- new Action(() => InjectPasteUIImpl(window)));
- }
- else
- {
- HideOverlay();
- }
- }
- catch (Exception ex)
- {
- Logger.Error($"InjectPasteUI failed: {ex.Message}");
- }
- }
-
- private static void InjectPasteUIImpl(Window window)
- {
- try
- {
- if (_overlayPanel != null)
- {
- _overlayPanel.Visibility = Visibility.Visible;
- return;
- }
-
- var existingContent = window.Content as UIElement;
- if (existingContent == null) return;
-
- // Wrap existing window content in a new Grid so we can overlay on top
- var wrapperGrid = new Grid();
- window.Content = wrapperGrid;
- wrapperGrid.Children.Add(existingContent);
-
- // Build overlay content
- var panel = new StackPanel
- {
- VerticalAlignment = VerticalAlignment.Center,
- Margin = new Thickness(30)
- };
-
- var heading = new TextBlock
- {
- Text = "Running under Wine?",
- FontSize = 14,
- FontWeight = FontWeights.SemiBold,
- Foreground = new SolidColorBrush(Color.FromRgb(0x33, 0x33, 0x33)),
- Margin = new Thickness(0, 0, 0, 8)
- };
-
- var instructions = new TextBlock
- {
- Text = "After signing in, your browser will show a " +
- "\"Launching Affinity\" page. Copy the full URL " +
- "from the address bar and paste it below:",
- Foreground = new SolidColorBrush(Color.FromRgb(0x65, 0x65, 0x65)),
- FontSize = 12,
- TextWrapping = TextWrapping.Wrap,
- Margin = new Thickness(0, 0, 0, 10)
- };
-
- var screenshot = LoadEmbeddedImage("WineFix.Resources.signin.png");
- Image screenshotImage = null;
- if (screenshot != null)
- {
- screenshotImage = new Image
- {
- Source = screenshot,
- Stretch = Stretch.Uniform,
- HorizontalAlignment = HorizontalAlignment.Stretch,
- Margin = new Thickness(0, 0, 0, 12)
- };
- }
-
- var textBox = new TextBox
- {
- Height = 28,
- FontSize = 12,
- VerticalContentAlignment = VerticalAlignment.Center,
- HorizontalAlignment = HorizontalAlignment.Stretch
- };
-
- var placeholder = new TextBlock
- {
- Text = "https://page.service.serif.com/canva-auth-redirect/...",
- Foreground = new SolidColorBrush(Color.FromRgb(0xAA, 0xAA, 0xAA)),
- FontSize = 12,
- IsHitTestVisible = false,
- VerticalAlignment = VerticalAlignment.Center,
- Margin = new Thickness(4, 0, 0, 0)
- };
- var textBoxContainer = new Grid { Height = 28, HorizontalAlignment = HorizontalAlignment.Stretch };
- textBoxContainer.Children.Add(textBox);
- textBoxContainer.Children.Add(placeholder);
- textBox.TextChanged += (s, e) => placeholder.Visibility =
- string.IsNullOrEmpty(textBox.Text) ? Visibility.Visible : Visibility.Collapsed;
- textBox.GotFocus += (s, e) => placeholder.Visibility = Visibility.Collapsed;
- textBox.LostFocus += (s, e) => placeholder.Visibility =
- string.IsNullOrEmpty(textBox.Text) ? Visibility.Visible : Visibility.Collapsed;
-
- var button = new Button
- {
- Content = "Submit",
- Margin = new Thickness(0, 8, 0, 0),
- Padding = new Thickness(0, 8, 0, 8),
- HorizontalAlignment = HorizontalAlignment.Stretch
- };
-
- var errorText = new TextBlock
- {
- Foreground = Brushes.Red,
- FontSize = 11,
- TextWrapping = TextWrapping.Wrap,
- Visibility = Visibility.Collapsed,
- Margin = new Thickness(0, 4, 0, 0)
- };
-
- button.Click += (s, e) => OnSubmitUrl(textBox.Text, errorText);
-
- panel.Children.Add(heading);
- panel.Children.Add(instructions);
- if (screenshotImage != null)
- panel.Children.Add(screenshotImage);
- panel.Children.Add(textBoxContainer);
- panel.Children.Add(button);
- panel.Children.Add(errorText);
-
- // Overlay: right-aligned, 50% width, full height, white background
- _overlayPanel = new Border
- {
- Background = new SolidColorBrush(Color.FromArgb(0xF0, 0xFF, 0xFF, 0xFF)),
- HorizontalAlignment = HorizontalAlignment.Right,
- VerticalAlignment = VerticalAlignment.Stretch,
- Child = panel
- };
- _overlayPanel.SetBinding(FrameworkElement.WidthProperty,
- new Binding("ActualWidth")
- {
- Source = window,
- Converter = new HalfWidthConverter()
- });
-
- wrapperGrid.Children.Add(_overlayPanel);
-
- // Nudge the "Back" button left so it's not covered by the overlay
- var contentControl = FindChild<ContentControl>(existingContent,
- c => c.ContentTemplateSelector != null);
- if (contentControl != null)
- {
- var backButton = FindChild<Button>(contentControl, b =>
- Grid.GetRow(b) == 4 && Grid.GetColumnSpan(b) == 3
- && b.HorizontalAlignment == HorizontalAlignment.Center);
- if (backButton != null)
- backButton.HorizontalAlignment = HorizontalAlignment.Left;
- }
-
- Logger.Info("Injected paste-URL overlay into SigningIn dialog");
- }
- catch (Exception ex)
- {
- Logger.Error($"InjectPasteUIImpl failed: {ex.Message}");
- }
- }
-
- private static void HideOverlay()
- {
- if (_overlayPanel != null)
- _overlayPanel.Visibility = Visibility.Collapsed;
- }
-
- private static void OnSubmitUrl(string input, TextBlock errorText)
- {
- try
- {
- errorText.Visibility = Visibility.Collapsed;
-
- if (string.IsNullOrWhiteSpace(input))
- {
- ShowError(errorText, "Please paste a URL.");
- return;
- }
-
- var affinityUri = ExtractAffinityUri(input.Trim());
- if (affinityUri == null)
- {
- ShowError(errorText, "Could not find an authentication URL. Please paste the full URL from your browser's address bar after signing in.");
- return;
- }
-
- if (_cloudServicesService == null || _authHookField == null)
- {
- ShowError(errorText, "Internal error: cloud services not available.");
- return;
- }
-
- var handler = _authHookField.GetValue(_cloudServicesService) as Action<Uri>;
- if (handler == null)
- {
- ShowError(errorText, "Internal error: auth hook not registered.");
- return;
- }
-
- Logger.Info($"Invoking AuthHookReceived with: {affinityUri}");
- handler.Invoke(affinityUri);
- }
- catch (Exception ex)
- {
- Logger.Error($"OnSubmitUrl failed: {ex}");
- ShowError(errorText, $"Error: {ex.Message}");
- }
- }
-
- /// <summary>
- /// Extracts the affinity:// URI from either:
- /// - A direct affinity://canva/authorize?... URL
- /// - A redirect page URL: https://page.service.serif.com/canva-auth-redirect/?url=affinity%3A%2F%2F...
- /// </summary>
- private static Uri ExtractAffinityUri(string input)
- {
- if (input.StartsWith("affinity://", StringComparison.OrdinalIgnoreCase))
- return new Uri(input);
-
- try
- {
- var uri = new Uri(input);
- var query = uri.Query;
- if (string.IsNullOrEmpty(query)) return null;
-
- var decoded = ParseQueryParam(query, "url");
- if (decoded != null && decoded.StartsWith("affinity://", StringComparison.OrdinalIgnoreCase))
- return new Uri(decoded);
- }
- catch { }
-
- return null;
- }
-
- private static string ParseQueryParam(string query, string name)
- {
- if (query.StartsWith("?")) query = query.Substring(1);
-
- foreach (var part in query.Split('&'))
- {
- var eq = part.IndexOf('=');
- if (eq < 0) continue;
- var key = Uri.UnescapeDataString(part.Substring(0, eq));
- if (string.Equals(key, name, StringComparison.OrdinalIgnoreCase))
- return Uri.UnescapeDataString(part.Substring(eq + 1));
- }
- return null;
- }
-
- private static void ShowError(TextBlock tb, string msg)
- {
- tb.Text = msg;
- tb.Visibility = Visibility.Visible;
- }
-
- private static BitmapImage LoadEmbeddedImage(string resourceName)
- {
- try
- {
- var assembly = typeof(CanvaSignInPatch).Assembly;
- using (var stream = assembly.GetManifestResourceStream(resourceName))
- {
- if (stream == null) return null;
- var bitmap = new BitmapImage();
- bitmap.BeginInit();
- bitmap.CacheOption = BitmapCacheOption.OnLoad;
- bitmap.StreamSource = stream;
- bitmap.EndInit();
- bitmap.Freeze();
- return bitmap;
- }
- }
- catch (Exception ex)
- {
- Logger.Error($"Failed to load embedded image: {ex.Message}");
- return null;
- }
- }
-
- private static T FindChild<T>(DependencyObject parent, Func<T, bool> predicate = null)
- where T : DependencyObject
- {
- var count = VisualTreeHelper.GetChildrenCount(parent);
- for (int i = 0; i < count; i++)
- {
- var child = VisualTreeHelper.GetChild(parent, i);
- if (child is T t && (predicate == null || predicate(t)))
- return t;
- var result = FindChild(child, predicate);
- if (result != null) return result;
- }
- return null;
- }
- }
-
- internal class HalfWidthConverter : IValueConverter
- {
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
- {
- if (value is double d) return d * 0.5;
- return value;
- }
-
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
- }
- }
-}
diff --git a/WineFix/Patches/MainWindowLoadedPatch.cs b/WineFix/Patches/MainWindowLoadedPatch.cs
index 5c658b5..8e32a33 100644
--- a/WineFix/Patches/MainWindowLoadedPatch.cs
+++ b/WineFix/Patches/MainWindowLoadedPatch.cs
@@ -52,10 +52,12 @@ namespace WineFix.Patches
}
// Transpiler that replaces the call to HasPreviousPackageInstalled() with loading 'false'
+ // and also removes the call to base.OnMainWindowLoaded()
public static IEnumerable<CodeInstruction> OnMainWindowLoaded_Transpiler(IEnumerable<CodeInstruction> instructions)
{
var codes = new List<CodeInstruction>(instructions);
bool patchedPackageCheck = false;
+ bool patchedBaseCall = false;
for (int i = 0; i < codes.Count; i++)
{
@@ -97,6 +99,54 @@ namespace WineFix.Patches
patchedPackageCheck = true;
continue;
}
+
+ // Look for: call instance void Serif.Interop.Persona.Application::OnMainWindowLoaded(...)
+ // (Index 22 in the IL - base class is Serif.Interop.Persona.Application, not System.Windows.Application!)
+ if (!patchedBaseCall &&
+ instruction.opcode == OpCodes.Call &&
+ instruction.operand is MethodInfo baseMethod &&
+ baseMethod.Name == "OnMainWindowLoaded" &&
+ baseMethod.DeclaringType != null &&
+ (baseMethod.DeclaringType.FullName == "Serif.Interop.Persona.Application" ||
+ baseMethod.DeclaringType.FullName == "System.Windows.Application"))
+ {
+ Logger.Debug($"Found base.OnMainWindowLoaded call at instruction {i}");
+
+ // The IL sequence is (indices 20-22):
+ // ldarg.0 ; load 'this'
+ // ldarg.1 ; load 'mainWindow'
+ // call base.OnMainWindowLoaded
+ // Replace all three with NOPs, preserving labels
+ if (i >= 2 &&
+ codes[i - 2].opcode == OpCodes.Ldarg_0 &&
+ codes[i - 1].opcode == OpCodes.Ldarg_1)
+ {
+ // Create NOPs but preserve labels
+ var nop1 = new CodeInstruction(OpCodes.Nop);
+ nop1.labels.AddRange(codes[i - 2].labels);
+ codes[i - 2] = nop1;
+
+ var nop2 = new CodeInstruction(OpCodes.Nop);
+ nop2.labels.AddRange(codes[i - 1].labels);
+ codes[i - 1] = nop2;
+
+ var nop3 = new CodeInstruction(OpCodes.Nop);
+ nop3.labels.AddRange(codes[i].labels);
+ codes[i] = nop3;
+
+ Logger.Debug($"Removed base.OnMainWindowLoaded call (indices {i-2} to {i})");
+ patchedBaseCall = true;
+ }
+ else
+ {
+ Logger.Warning($"WARNING: Found base.OnMainWindowLoaded but preceding instructions don't match expected pattern");
+ var nop = new CodeInstruction(OpCodes.Nop);
+ nop.labels.AddRange(codes[i].labels);
+ codes[i] = nop;
+ patchedBaseCall = true;
+ }
+ continue;
+ }
}
if (!patchedPackageCheck)
@@ -104,6 +154,11 @@ namespace WineFix.Patches
Logger.Warning($"WARNING: Could not find HasPreviousPackageInstalled call to patch");
}
+ if (!patchedBaseCall)
+ {
+ Logger.Warning($"WARNING: Could not find base.OnMainWindowLoaded call to patch");
+ }
+
return codes.AsEnumerable();
}
}
diff --git a/WineFix/WineFix.csproj b/WineFix/WineFix.csproj
index b1d3c82..c62939d 100644
--- a/WineFix/WineFix.csproj
+++ b/WineFix/WineFix.csproj
@@ -29,10 +29,6 @@
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
- <Reference Include="System.Xaml" />
- </ItemGroup>
- <ItemGroup>
- <EmbeddedResource Include="Resources\signin.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AffinityPluginLoader\AffinityPluginLoader.csproj">
diff --git a/WineFix/WineFixPlugin.cs b/WineFix/WineFixPlugin.cs
index 4ed9f42..a6ccd09 100644
--- a/WineFix/WineFixPlugin.cs
+++ b/WineFix/WineFixPlugin.cs
@@ -25,10 +25,6 @@ namespace WineFix
{
return new PluginSettingsDefinition(PluginId)
.AddSection("Patches")
- .AddBool(SettingCanvaSignInHelper, "Canva sign-in helper",
- defaultValue: true,
- restartRequired: true,
- description: "Patch the Canva sign-in dialog to include a helper textbox input and instructions to complete Canva sign-in without needing a protocol URL handler.")
.AddBool(BezierRenderingFixKey, "Bezier curves: Fix tool preview path rendering",
defaultValue: true,
restartRequired: true,
@@ -109,16 +105,6 @@ namespace WineFix
{
Logger.Info("Skipping ColorPicker Wayland fix (setting: " + magnifierFix + ")");
}
-
- if (context.Settings.GetEffectiveValue<bool>(SettingCanvaSignInHelper))
- {
- context.Patch("Canva sign-in paste URL fix",
- h => Patches.CanvaSignInPatch.ApplyPatches(h));
- }
- else
- {
- Logger.Info("Skipping Canva sign-in helper (disabled in settings)");
- }
}
}
}
--
2.53.0
+193
View File
@@ -0,0 +1,193 @@
{
linkFarm,
fetchurl,
writeText,
runCommand,
callPackage,
xvfb-run,
lib,
inputs,
wine-packages,
...
}:
let
verbs = [
"tahoma"
"vcrun2022"
#"dotnet20"
"dotnet48"
"corefonts"
"win11"
];
inherit (wine-packages)
wine
winetricks
wineserver
;
dependencies = callPackage ./dependencies.nix { };
winetricksCache = linkFarm "winetricks-cache" [
{
name = "vcrun2022/vc_redist.x64.exe";
path = fetchurl {
urls = [
"https://download.visualstudio.microsoft.com/download/pr/7ebf5fdb-36dc-4145-b0a0-90d3d5990a61/CC0FF0EB1DC3F5188AE6300FAEF32BF5BEEBA4BDD6E8E445A9184072096B713B/VC_redist.x64.exe"
];
hash = "sha256-zA/w6x3D9RiK5jAPrvMr9b7rpL3W6ORFqRhAcglrcTs=";
};
}
{
name = "vcrun2022/vc_redist.x86.exe";
path = fetchurl {
urls = [
"https://download.visualstudio.microsoft.com/download/pr/7ebf5fdb-36dc-4145-b0a0-90d3d5990a61/0C09F2611660441084CE0DF425C51C11E147E6447963C3690F97E0B25C55ED64/VC_redist.x86.exe"
];
hash = "sha256-DAnyYRZgRBCEzg30JcUcEeFH5kR5Y8NpD5fgslxV7WQ=";
};
}
{
name = "dotnet48/ndp48-x86-x64-allos-enu.exe";
path = fetchurl {
urls = [
"https://download.visualstudio.microsoft.com/download/pr/7afca223-55d2-470a-8edc-6a1739ae3252/abd170b4b0ec15ad0222a809b761a036/ndp48-x86-x64-allos-enu.exe"
];
hash = "sha256-lYidbePyBwwHeQrWzyAA0z2aG9/Go4FyWrgqscMU/VM=";
};
}
{
name = "dotnet40/dotNetFx40_Full_x86_x64.exe";
path = fetchurl {
urls = [
"https://download.microsoft.com/download/9/5/A/95A9616B-7A37-4AF6-BC36-D6EA96C8DAAE/dotNetFx40_Full_x86_x64.exe"
];
hash = "sha256-ZeBkJY8uQYgWswT2Rv+eh68QHkyVUqsGS7dNKBw4ZZ8=";
};
}
{
name = "tahoma/IELPKTH.CAB";
path = fetchurl {
url = "https://downloads.sourceforge.net/corefonts/OldFiles/IELPKTH.CAB";
hash = "sha256-wb4/uPAEJXC+duxtqgOpkULIg2fBvIECQLhYJ8cVlho=";
};
}
{
name = "dotnet20/NetFx64.exe";
path = fetchurl {
urls = [
"https://download.microsoft.com/download/9/8/6/98610406-c2b7-45a4-bdc3-9db1b1c5f7e2/NetFx20SP1_x64.exe"
];
hash = "sha256-FzHlPeX0i6rgljZ3JXZg3xMpVJ6BxItNfbf38/Iymqs=";
};
}
];
layer_1 = runCommand "base-prefix-1" { } ''
set -x -e
mkdir -p $out
export WINEPREFIX="$out"
export WINETRICKS_UPDATE_CHECK=0
export WINETRICKS_LATEST_VERSION_CHECK=disabled
mkdir -p /tmp/cache
export XDG_CACHE_HOME="/tmp/cache"
${lib.getExe wine} wineboot --update
# by diffing a registry dump we found that you can disable the file association
# through a registry key.
${lib.getExe wine} regedit /S "${(writeText "file-association-disable.reg" ''
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Wine\DllOverrides]
"winemenubuilder.exe"=""
[HKEY_CURRENT_USER\Software\Wine\FileOpenAssociations]
"Enable"="N"
'').outPath}"
${lib.getExe winetricks} renderer=vulkan
install -D -t "$WINEPREFIX/drive_c/windows/system32/WinMetadata/" ${dependencies}/*.winmd
${lib.getExe wineserver} -w
'';
layer_2 =
runCommand "base-prefix-2"
{
nativeBuildInputs = [
xvfb-run
];
}
''
set -x -e
mkdir -p $out
cp -a ${layer_1}/. $out
chmod -R +w $out
export WINEPREFIX="$out"
mkdir -p /tmp/cache
export XDG_CACHE_HOME="/tmp/cache"
${lib.getExe wine} winecfg -v win7
xvfb-run ${lib.getExe wine} "${dependencies}/MicrosoftEdgeWebView2RuntimeInstallerX64.exe" /silent /install
${lib.getExe wine} winecfg -v win11
${lib.getExe wine} regedit /S "${(writeText "webview2-regedit-changes.reg" ''
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\edgeupdate]
"Start"=dword:00000004
[HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\edgeupdatem]
"Start"=dword:00000004
[HKEY_CURRENT_USER\Software\Wine\AppDefaults]
[HKEY_CURRENT_USER\Software\Wine\AppDefaults\msedgewebview2.exe]
"Version"="win7"
'').outPath}"
# The Edge Update service gets to start before we can deactivate it, so it must be stopped manually
${lib.getExe wine} taskkill /f /im MicrosoftEdgeUpdate.exe
${lib.getExe wineserver} -w
'';
in
runCommand "base-prefix-3"
{
nativeBuildInputs = [
xvfb-run
];
}
''
set -x -e
${lib.strings.toShellVars {
inherit verbs;
}}
mkdir -p /tmp/cache/winetricks/corefonts
mkdir -p $out
cp -a ${layer_2}/. $out
chmod -R +w $out
export WINEPREFIX="$out"
export XDG_CACHE_HOME="/tmp/cache"
export WINETRICKS_UPDATE_CHECK=0
export WINETRICKS_LATEST_VERSION_CHECK=disabled
export WINEDEBUG=-all
export WINEESYNC=0
export WINEFSYNC=0
cp -R ${winetricksCache}/* /tmp/cache/winetricks
cp -R ${inputs.corefonts}/*.exe /tmp/cache/winetricks/corefonts
xvfb-run ${lib.getExe winetricks} -q -f "''${verbs[@]}"
${lib.getExe wineserver} -w
''
+28
View File
@@ -0,0 +1,28 @@
{ inputs, ... }:
{
imports = [
./wine
./affinity
./affinity-v3
./apl
./runner
];
perSystem =
{
pkgs,
wine-packages,
...
}:
{
# this function builds the prefix right up until the affinity sources
# are used. this separation exists for build caching, as the affinity sources
# should never be exposed to a cache
packages.base-prefix-pre-affinity = pkgs.callPackage ./basePrefix.nix {
inherit inputs wine-packages;
};
_module.args = {
};
};
}
+30
View File
@@ -0,0 +1,30 @@
{
fetchurl,
lib,
runCommand,
}:
let
paths = {
"Windows.Services.winmd" = fetchurl {
url = "https://archive.org/download/windows.services-system.winmd/Windows.Services.winmd";
hash = "sha256-l9TRXyyacQFHoVsq6s8zGvJ+SLl4pTNv6yesNksAzdQ=";
};
"Windows.System.winmd" = fetchurl {
url = "https://archive.org/download/windows.services-system.winmd/Windows.System.winmd";
hash = "sha256-EOh5DJXzF+gdqW1Jc4lS1/J2x3hC/SxRkHMzzS4h0TM=";
};
"MicrosoftEdgeWebView2RuntimeInstallerX64.exe" = fetchurl {
url = "https://archive.org/download/microsoft-edge-web-view-2-runtime-installer-v109.0.1518.78/MicrosoftEdgeWebView2RuntimeInstallerX64.exe";
hash = "sha256-8sxJhj4iFALUZk2fqUSkfyJUPaLcs2NDjD5Zh4m5/Vs=";
};
};
in
runCommand "dependencies" { } ''
mkdir -p $out
${lib.concatStringsSep "\n" (
lib.mapAttrsToList (name: content: ''
ln -s ${content} $out/${name}
'') paths
)}
''
+61
View File
@@ -0,0 +1,61 @@
{
callPackage,
fetchzip,
runCommand,
lndir,
zstd,
lib,
inputs,
v3 ? true,
wine-packages,
apl-combined,
...
}:
let
layer_3 = callPackage ./basePrefix.nix {
inherit inputs wine-packages;
};
installers = callPackage ./sources.nix { };
registry-patches = callPackage ./registry-patches.nix { };
vkd3d = fetchzip {
url = "https://github.com/HansKristian-Work/vkd3d-proton/releases/download/v3.0.1/vkd3d-proton-3.0.1.tar.zst";
nativeBuildInputs = [ zstd ];
hash = "sha256-xHJFtVDD/ZHVHF2Fn7TEEX0fMUWJvujNyNt2Xyw9F7o=";
};
inherit (wine-packages) wine wineserver;
in
runCommand "base-prefix-4" { } ''
set -x -e
mkdir -p $out
cp -a ${layer_3}/. $out
chmod -R +w $out
export WINEPREFIX="$out"
cp ${vkd3d}/x64/d3d12.dll "$WINEPREFIX/drive_c/windows/system32"
cp ${vkd3d}/x64/d3d12core.dll "$WINEPREFIX/drive_c/windows/system32"
${lib.getExe wine} regedit /S "${registry-patches.one-vkd3d}"
${lib.optionalString v3 ''
${lib.getExe lndir} ${installers.v3} "$WINEPREFIX/drive_c/Program Files/"
pushd "$WINEPREFIX/drive_c/Program Files/Affinity/Affinity"
cp -r "${apl-combined}/." .
popd
''}
${lib.optionalString (!v3) ''
${lib.getExe lndir} ${installers.photo} "$WINEPREFIX/drive_c/Program Files/"
${lib.getExe lndir} ${installers.designer} "$WINEPREFIX/drive_c/Program Files/"
${lib.getExe lndir} ${installers.publisher} "$WINEPREFIX/drive_c/Program Files/"
''}
${lib.getExe wineserver} -w
rm -rf $WINEPREFIX/drive_c/users/nixbld
''
+21
View File
@@ -0,0 +1,21 @@
{
pkgs,
}:
rec {
# vkd3d was added in revision 10
one-vkd3d =
(pkgs.writeText "vkd3d-regedit-changes.reg" ''
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Wine\DllOverrides]
"d3d12"="native"
"d3d12core"="native"
'').outPath;
combined = pkgs.linkFarm "registry-patches-combined" [
{
name = "one.reg";
path = one-vkd3d;
}
];
}
+30
View File
@@ -0,0 +1,30 @@
{ inputs, ... }:
{
perSystem =
{
pkgs,
wine-packages,
...
}:
let
runner = pkgs.callPackage ./package.nix {
inherit inputs wine-packages;
registry-patches = (pkgs.callPackage ../registry-patches.nix { }).combined;
prefixBase = "/non-functional-runner";
name = "v3";
};
in
{
checks = {
runner = runner.package;
runner-clippy = runner.package-clippy;
};
packages = {
runner = runner.package;
runner-artifacts = runner.package-artifacts;
};
};
}
+88
View File
@@ -0,0 +1,88 @@
{
pkgs,
lib,
inputs,
registry-patches,
wine-packages,
prefixBase,
name,
...
}:
let
craneLib = inputs.crane.mkLib pkgs;
src = craneLib.cleanCargoSource ../..;
commonArgs = {
inherit src;
strictDeps = true;
};
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
fileSetForCrate =
crate:
lib.fileset.toSource {
root = ../..;
fileset = lib.fileset.unions [
../../Cargo.toml
../../Cargo.lock
(craneLib.fileset.commonCargoSources crate)
];
};
env = {
WINE = lib.getExe wine-packages.wine;
WINESERVER = lib.getExe wine-packages.wineserver;
WINETRICKS = lib.getExe wine-packages.winetricks;
FUSE_OVERLAYFS = lib.getExe pkgs.fuse-overlayfs;
GNUTAR = lib.getExe pkgs.gnutar;
ZENITY = lib.getExe pkgs.zenity;
RSYNC = lib.getExe pkgs.rsync;
REGISTRY_PATCHES = registry-patches;
ON_LINUX = inputs.on-linux.outPath;
};
executableName = "affinity-${lib.toLower name}";
executable = craneLib.buildPackage (
commonArgs
// {
inherit cargoArtifacts env;
pname = executableName;
cargoExtraArgs = "-p runner --no-default-features --features ${lib.toLower name}";
src = fileSetForCrate ../../crates/runner;
meta.mainProgram = executableName;
postInstall = ''
mv $out/bin/runner $out/bin/affinity-${lib.toLower name}
'';
}
);
in
{
package =
pkgs.runCommand executableName
{
nativeBuildInputs = [ pkgs.makeWrapper ];
}
''
mkdir -p $out/bin
cp ${lib.getExe executable} $out/bin/${executableName}
wrapProgram $out/bin/${executableName} \
--set "LOWER_DIR" "${prefixBase}" \
'';
package-clippy = craneLib.cargoClippy (
commonArgs
// {
inherit cargoArtifacts env;
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
}
);
package-artifacts = cargoArtifacts;
}
+47
View File
@@ -0,0 +1,47 @@
{
pkgs,
lib,
...
}:
let
mkExtract =
source: segment:
pkgs.runCommand "affinity-extracted-sources"
{
nativeBuildInputs = [
pkgs._7zz
pkgs.msitools
];
meta.license = lib.licenses.unfree;
}
''
7zz e ${source} -tpe "${segment}"
msiextract -C $out ./*
'';
in
{
v3 = mkExtract (pkgs.fetchurl {
url = "https://downloads.affinity.studio/Affinity%20x64.exe";
hash = "sha256-cnPdRZlZrl5Pp6Vr+GkO7br98GrcMDnnJPXcAboxkC4=";
}) ".rsrc/2057/BIN/135";
photo = mkExtract (pkgs.fetchurl {
name = "affinity-photo-msi-2.6.5.exe";
url = "https://archive.org/download/affinity_20251030/affinity-photo-msi-2.6.5.exe";
hash = "sha256-waCX33rWVzZ8AYZWmv2EOqS6ikT6p7s9qRJ+8rDo6Wk=";
}) ".rsrc/2057/BIN/135";
designer = mkExtract (pkgs.fetchurl {
name = "affinity-designer-msi-2.6.5.exe";
url = "https://archive.org/download/affinity_20251030/affinity-designer-msi-2.6.5.exe";
hash = "sha256-b5K1FtsCWCjyn/NC4trB0g9Wv+AfLoDWgaZK2ZGXW0w=";
}) ".rsrc/2057/BIN/135";
publisher = mkExtract (pkgs.fetchurl {
name = "affinity-publisher-msi-2.6.5.exe";
url = "https://archive.org/download/affinity_20251030/affinity-publisher-msi-2.6.5.exe";
hash = "sha256-76ikkAnbAghaSU5YLYLxXiHfAuqVfoEcQK16rHd6+7A=";
}) ".rsrc/2057/BIN/135";
}
+21
View File
@@ -0,0 +1,21 @@
{ inputs, ... }:
{
perSystem =
{
pkgs,
...
}:
let
wine-packages = pkgs.callPackage ./packages.nix {
inherit inputs;
};
in
{
packages.wine = wine-packages.wine;
_module.args = {
inherit (wine-packages) wineUnwrapped;
inherit wine-packages;
};
};
}
+27
View File
@@ -0,0 +1,27 @@
{
stdenv,
callPackage,
pkgs,
inputs,
...
}:
let
wineUnstable = inputs.nixpkgs.legacyPackages.${stdenv.hostPlatform.system}.wineWow64Packages.full;
symlink = callPackage ./symlink.nix { };
wineUnwrapped = symlink {
wine = wineUnstable;
};
wrapWithPrefix = callPackage ./wrapWithPrefix.nix {
inherit wineUnwrapped;
};
in
{
inherit wineUnwrapped;
wine = wrapWithPrefix wineUnwrapped "wine";
winetricks = wrapWithPrefix pkgs.winetricks "winetricks";
wineserver = wrapWithPrefix wineUnwrapped "wineserver";
}
+15
View File
@@ -0,0 +1,15 @@
{ runCommand }:
{ wine }:
runCommand "wine-symlink" { inherit wine; } ''
mkdir -p $out/bin
for f in $wine/bin/*; do
cp -L "$f" $out/bin/
done
ln -s "$wine/bin/wine" $out/bin/wine64
for dir in include lib share; do
ln -s "$wine/$dir" $out
done
patchShebangs $out
''
+21
View File
@@ -0,0 +1,21 @@
{
stdenv,
lib,
pkgs,
wineUnwrapped,
}:
pkg: pname:
stdenv.mkDerivation rec {
name = "affinity-${pname}";
src = ./.;
nativeBuildInputs = [ pkgs.makeWrapper ];
installPhase = ''
makeWrapper ${lib.getExe' pkg pname} $out/bin/${name} \
--set LD_LIBRARY_PATH "${wineUnwrapped}/lib:$LD_LIBRARY_PATH" \
--set WINESERVER "${lib.getExe' wineUnwrapped "wineserver"}" \
--set WINELOADER "${lib.getExe' wineUnwrapped "wine"}" \
--set WINEDLLPATH "${wineUnwrapped}/lib/wine" \
--set WINE "${lib.getExe' wineUnwrapped "wine"}"
'';
meta.mainProgram = name;
}