Browse Source

Added macos input keys

Krzysztof Krysiński 6 months ago
parent
commit
f97cb004b0

+ 1 - 1
src/PixiEditor.MacOs/MacOperatingSystem.cs

@@ -10,7 +10,7 @@ public sealed class MacOperatingSystem : IOperatingSystem
 
     public string AnalyticsId => "macOS";
     
-    public IInputKeys InputKeys { get; }
+    public IInputKeys InputKeys { get; } = new MacOsInputKeys();
     public IProcessUtility ProcessUtility { get; }
     public void OpenUri(string uri)
     {

+ 164 - 0
src/PixiEditor.MacOs/MacOsInputKeys.cs

@@ -0,0 +1,164 @@
+using Avalonia.Input;
+using PixiEditor.OperatingSystem;
+
+namespace PixiEditor.MacOs;
+
+public class MacOsInputKeys : IInputKeys
+{
+    public string GetKeyboardKey(Key key, bool forceInvariant = false)
+    {
+        switch (key)
+        {
+            case Key.LWin: return "\u2318";
+            case Key.RWin: return "\u2318";
+            case Key.LeftCtrl: return "\u2303";
+            case Key.RightCtrl: return "\u2303";
+            case Key.LeftAlt: return "\u2325";
+            case Key.RightAlt: return "\u2325";
+            case Key.LeftShift: return "\u21E7";
+            case Key.RightShift: return "\u21E7";
+            case Key.CapsLock: return "\u21EA";
+            case Key.Escape: return "\u238B";
+            case Key.Return: return "\u23CE";
+            case Key.Back: return "\u232B";
+            case Key.Tab: return "\u21E5";
+        }
+
+        if (key == Key.None) return string.Empty;
+
+        ushort? virtualKeyCode = GetVirtualKeyCode(key);
+        if (virtualKeyCode == null) return string.Empty;
+
+        string result = MacOsInterop.GetSymbolFromKey(virtualKeyCode.Value, 0).ToUpper();
+        if (result.Length == 1 && char.IsControl(result[0])) return key.ToString();
+        
+        return result;
+    }
+
+    public bool ModifierUsesSymbol(KeyModifiers modifier)
+    {
+        return true;
+    }
+
+    private ushort? GetVirtualKeyCode(Key key)
+    {
+        return key switch
+        {
+            Key.A => 0x00,
+            Key.S => 0x01,
+            Key.D => 0x02,
+            Key.F => 0x03,
+            Key.H => 0x04,
+            Key.G => 0x05,
+            Key.Z => 0x06,
+            Key.X => 0x07,
+            Key.C => 0x08,
+            Key.V => 0x09,
+            Key.B => 0x0B,
+            Key.Q => 0x0C,
+            Key.W => 0x0D,
+            Key.E => 0x0E,
+            Key.R => 0x0F,
+            Key.Y => 0x10,
+            Key.T => 0x11,
+            Key.D1 => 0x12,
+            Key.D2 => 0x13,
+            Key.D3 => 0x14,
+            Key.D4 => 0x15,
+            Key.D6 => 0x16,
+            Key.D5 => 0x17,
+            Key.OemPlus => 0x18, // '=',
+            Key.D9 => 0x19,
+            Key.D7 => 0x1A,
+            Key.OemMinus => 0x1B, // '-'
+            Key.D8 => 0x1C,
+            Key.D0 => 0x1D,
+            Key.OemCloseBrackets => 0x1E, // ']'
+            Key.O => 0x1F,
+            Key.U => 0x20,
+            Key.OemOpenBrackets => 0x21, // '['
+            Key.I => 0x22,
+            Key.P => 0x23,
+            Key.Enter => 0x24, // Return
+            Key.L => 0x25,
+            Key.J => 0x26,
+            Key.OemQuotes => 0x27, // "'"
+            Key.K => 0x28,
+            Key.OemSemicolon => 0x29, // ';'
+            Key.OemBackslash => 0x2A, // '\'
+            Key.OemComma => 0x2B, // ','
+            Key.OemQuestion => 0x2C, // '/'
+            Key.N => 0x2D,
+            Key.M => 0x2E,
+            Key.OemPeriod => 0x2F, // '.'
+            Key.Tab => 0x30,
+            Key.Space => 0x31,
+            Key.OemTilde => 0x32 // '~'
+            ,
+            Key.Back => 0x33 // Delete
+            ,
+            Key.Escape => 0x35,
+            Key.LWin => 0x37 // Cmd (Apple)
+            ,
+            Key.LeftShift => 0x38,
+            Key.CapsLock => 0x39,
+            Key.LeftAlt => 0x3A // Option
+            ,
+            Key.LeftCtrl => 0x3B // Control
+            ,
+            Key.RightShift => 0x3C,
+            Key.RightAlt => 0x3D // Right Option
+            ,
+            Key.RightCtrl => 0x3E // Right Control
+            ,
+            Key.F17 => 0x40,
+            Key.VolumeUp => 0x48,
+            Key.VolumeDown => 0x49,
+            Key.VolumeMute => 0x4A,
+            Key.F18 => 0x4F,
+            Key.F19 => 0x50,
+            Key.NumPad0 => 0x52,
+            Key.NumPad1 => 0x53,
+            Key.NumPad2 => 0x54,
+            Key.NumPad3 => 0x55,
+            Key.NumPad4 => 0x56,
+            Key.NumPad5 => 0x57,
+            Key.NumPad6 => 0x58,
+            Key.NumPad7 => 0x59,
+            Key.NumPad8 => 0x5B,
+            Key.NumPad9 => 0x5C,
+            Key.F5 => 0x60,
+            Key.F6 => 0x61,
+            Key.F7 => 0x62,
+            Key.F3 => 0x63,
+            Key.F8 => 0x64,
+            Key.F9 => 0x65,
+            Key.F11 => 0x67,
+            Key.F13 => 0x69,
+            Key.F16 => 0x6A,
+            Key.F14 => 0x6B,
+            Key.F10 => 0x6D,
+            Key.F12 => 0x6F,
+            Key.F15 => 0x71,
+            Key.Help => 0x72,
+            Key.Home => 0x73,
+            Key.PageUp => 0x74,
+            Key.Delete => 0x75 // Below the Help key
+            ,
+            Key.F4 => 0x76,
+            Key.End => 0x77,
+            Key.F2 => 0x78,
+            Key.PageDown => 0x79,
+            Key.F1 => 0x7A,
+            Key.Left => 0x7B // Left Arrow
+            ,
+            Key.Right => 0x7C // Right Arrow
+            ,
+            Key.Down => 0x7D // Down Arrow
+            ,
+            Key.Up => 0x7E // Up Arrow
+            ,
+            _ => null
+        };
+    }
+}

+ 72 - 0
src/PixiEditor.MacOs/MacOsInterop.cs

@@ -0,0 +1,72 @@
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace PixiEditor.MacOs;
+
+internal static class MacOsInterop
+{
+    private const string CoreFoundationLib = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
+    private const string CarbonLib = "/System/Library/Frameworks/Carbon.framework/Carbon";
+    private static readonly IntPtr kTISPropertyInputSourceID = CFStringCreate("TISPropertyUnicodeKeyLayoutData");
+
+    [DllImport(CarbonLib)]
+    private static extern IntPtr TISCopyCurrentKeyboardLayoutInputSource();
+    
+    [DllImport(CarbonLib)]
+    private static extern IntPtr TISGetInputSourceProperty(IntPtr inputSource, IntPtr propertyKey);
+
+    [DllImport(CoreFoundationLib)]
+    private static extern IntPtr CFDataGetBytePtr(IntPtr data);
+
+    [DllImport(CarbonLib)]
+    private static extern short UCKeyTranslate(
+        IntPtr keyLayout,
+        ushort virtualKeyCode,
+        ushort keyAction,
+        UInt32 modifierKeyState,
+        UInt32 keyboardType,
+        uint keyTranslateOptions,
+        ref uint deadKeyState,
+        int maxLength,
+        out int actualLength,
+        ushort[] unicodeString);
+
+    public static string? GetSymbolFromKey(ushort virtualKeyCode, uint modifiers)
+    {
+        IntPtr layout = TISCopyCurrentKeyboardLayoutInputSource();
+        if (layout == IntPtr.Zero)
+            return null;
+
+        
+        IntPtr layoutData = TISGetInputSourceProperty(layout, kTISPropertyInputSourceID);
+        if (layoutData == IntPtr.Zero)
+            return null;
+
+        IntPtr keyLayoutPtr = CFDataGetBytePtr(layoutData);
+        if (keyLayoutPtr == IntPtr.Zero)
+            return null;
+
+        // Translate the key code into a symbol
+        var state = 0u;
+        var output = new ushort[255];
+        UCKeyTranslate(keyLayoutPtr, virtualKeyCode, 3 /* kUCKeyActionDisplay */, modifiers, 0, 0, ref state, output.Length, out var actualLength, output);
+
+        return actualLength > 0 ? UshortArrayToString(output, (int)actualLength) : null;
+    }
+    
+    private static string UshortArrayToString(ushort[] array, int length)
+    {
+        // Convert ushort[] to byte[] for Encoding.Unicode.GetString
+        byte[] bytes = new byte[length * 2];
+        Buffer.BlockCopy(array, 0, bytes, 0, length * 2);
+        return Encoding.Unicode.GetString(bytes, 0, length * 2);
+    }
+    
+    [DllImport(CoreFoundationLib)]
+    private static extern IntPtr CFStringCreateWithCString(IntPtr allocator, string cStr, UInt32 encoding);
+
+    private static IntPtr CFStringCreate(string str)
+    {
+        return CFStringCreateWithCString(IntPtr.Zero, str, 134217984);
+    }
+}

+ 5 - 0
src/PixiEditor.MacOs/todo.md

@@ -0,0 +1,5 @@
+- [x] Input keys
+- [ ] Default shortcuts
+- [ ] Autoupdates
+- [ ] Process handling
+- [ ] Check extensions

+ 2 - 0
src/PixiEditor.OperatingSystem/IInputKeys.cs

@@ -8,4 +8,6 @@ public interface IInputKeys
     ///     Returns the character of the <paramref name="key"/> mapped to the users keyboard layout
     /// </summary>
     public string GetKeyboardKey(Key key, bool forceInvariant = false);
+
+    public bool ModifierUsesSymbol(KeyModifiers modifier);
 }

+ 6 - 1
src/PixiEditor.Windows/WindowsInputKeys.cs

@@ -19,7 +19,7 @@ public class WindowsInputKeys : IInputKeys
     private static nint? invariantLayout;
 
     /// <summary>
-    /// Returns the charcter of the <paramref name="key"/> mapped to the users keyboard layout
+    /// Returns the character of the <paramref name="key"/> mapped to the users keyboard layout
     /// </summary>
     public string GetKeyboardKey(Key key, bool forceInvariant = false) => key switch
     {
@@ -32,6 +32,11 @@ public class WindowsInputKeys : IInputKeys
         _ => GetMappedKey(key, forceInvariant),
     };
 
+    public bool ModifierUsesSymbol(KeyModifiers modifier)
+    {
+        return false;
+    }
+
     private static string GetMappedKey(Key key, bool forceInvariant)
     {
         int virtualKey = KeyInterop.VirtualKeyFromKey(key);

+ 45 - 0
src/PixiEditor.sln

@@ -164,6 +164,8 @@ Global
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Steam|x64.Build.0 = Release|Any CPU
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.DevRelease|x64.ActiveCfg = Debug|Any CPU
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.DevRelease|x64.Build.0 = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|ARM64.Build.0 = Release|Any CPU
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x64.ActiveCfg = Debug|x64
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x64.Build.0 = Debug|x64
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x64.Deploy.0 = Debug|x64
@@ -198,6 +200,8 @@ Global
 		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.DevRelease|x64.Build.0 = Debug|Any CPU
 		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.DevSteam|x64.ActiveCfg = Release|Any CPU
 		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.DevSteam|x64.Build.0 = Release|Any CPU
+		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.Release|ARM64.Build.0 = Release|Any CPU
 		{510ED47C-2455-4DCE-A561-1074725E1236}.Debug|x64.ActiveCfg = Debug|x64
 		{510ED47C-2455-4DCE-A561-1074725E1236}.Debug|x64.Build.0 = Debug|x64
 		{510ED47C-2455-4DCE-A561-1074725E1236}.DevRelease|x64.ActiveCfg = DevRelease|x64
@@ -226,6 +230,8 @@ Global
 		{294FD171-9536-474C-A679-83F0266275FB}.Steam|x64.Build.0 = Steam|Any CPU
 		{294FD171-9536-474C-A679-83F0266275FB}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{294FD171-9536-474C-A679-83F0266275FB}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{294FD171-9536-474C-A679-83F0266275FB}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{294FD171-9536-474C-A679-83F0266275FB}.Release|ARM64.Build.0 = Release|Any CPU
 		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Debug|x64.Build.0 = Debug|Any CPU
 		{758DF7DF-A8B1-4409-B79A-018E542B7251}.DevRelease|x64.ActiveCfg = DevRelease|Any CPU
@@ -242,6 +248,8 @@ Global
 		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Debug|ARM64.Build.0 = Debug|Any CPU
 		{758DF7DF-A8B1-4409-B79A-018E542B7251}.DevSteam|x64.ActiveCfg = DevRelease|Any CPU
 		{758DF7DF-A8B1-4409-B79A-018E542B7251}.DevSteam|x64.Build.0 = DevRelease|Any CPU
+		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Release|ARM64.Build.0 = Release|Any CPU
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Debug|x64.Build.0 = Debug|Any CPU
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.MSIX Debug|x64.ActiveCfg = Debug|Any CPU
@@ -258,6 +266,8 @@ Global
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.DevRelease|x64.Build.0 = Debug|Any CPU
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.DevSteam|x64.ActiveCfg = Release|Any CPU
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.DevSteam|x64.Build.0 = Release|Any CPU
+		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Release|ARM64.Build.0 = Release|Any CPU
 		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Debug|x64.Build.0 = Debug|Any CPU
 		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.MSIX Debug|x64.ActiveCfg = Debug|Any CPU
@@ -274,6 +284,8 @@ Global
 		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.DevRelease|x64.Build.0 = Debug|Any CPU
 		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.DevSteam|x64.ActiveCfg = Release|Any CPU
 		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.DevSteam|x64.Build.0 = Release|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Release|ARM64.Build.0 = Release|Any CPU
 		{9BCD0764-9C16-4A2A-B153-C676FEF38887}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{9BCD0764-9C16-4A2A-B153-C676FEF38887}.Debug|x64.Build.0 = Debug|Any CPU
 		{9BCD0764-9C16-4A2A-B153-C676FEF38887}.DevRelease|x64.ActiveCfg = Debug|Any CPU
@@ -290,6 +302,8 @@ Global
 		{9BCD0764-9C16-4A2A-B153-C676FEF38887}.Debug|ARM64.Build.0 = Debug|Any CPU
 		{9BCD0764-9C16-4A2A-B153-C676FEF38887}.DevSteam|x64.ActiveCfg = Release|Any CPU
 		{9BCD0764-9C16-4A2A-B153-C676FEF38887}.DevSteam|x64.Build.0 = Release|Any CPU
+		{9BCD0764-9C16-4A2A-B153-C676FEF38887}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{9BCD0764-9C16-4A2A-B153-C676FEF38887}.Release|ARM64.Build.0 = Release|Any CPU
 		{2BDEB8C6-F22D-43EA-A309-B3387A803689}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{2BDEB8C6-F22D-43EA-A309-B3387A803689}.Debug|x64.Build.0 = Debug|Any CPU
 		{2BDEB8C6-F22D-43EA-A309-B3387A803689}.DevRelease|x64.ActiveCfg = Debug|Any CPU
@@ -306,6 +320,8 @@ Global
 		{2BDEB8C6-F22D-43EA-A309-B3387A803689}.Debug|ARM64.Build.0 = Debug|Any CPU
 		{2BDEB8C6-F22D-43EA-A309-B3387A803689}.DevSteam|x64.ActiveCfg = Release|Any CPU
 		{2BDEB8C6-F22D-43EA-A309-B3387A803689}.DevSteam|x64.Build.0 = Release|Any CPU
+		{2BDEB8C6-F22D-43EA-A309-B3387A803689}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{2BDEB8C6-F22D-43EA-A309-B3387A803689}.Release|ARM64.Build.0 = Release|Any CPU
 		{8EF48E6C-8219-4EE2-87C6-5176D8D092E6}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{8EF48E6C-8219-4EE2-87C6-5176D8D092E6}.Debug|x64.Build.0 = Debug|Any CPU
 		{8EF48E6C-8219-4EE2-87C6-5176D8D092E6}.DevRelease|x64.ActiveCfg = Debug|Any CPU
@@ -333,6 +349,8 @@ Global
 		{7A12C96B-8B5C-45E1-9EF6-0B1DA7F270DE}.Steam|x64.ActiveCfg = Debug|Any CPU
 		{7A12C96B-8B5C-45E1-9EF6-0B1DA7F270DE}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{7A12C96B-8B5C-45E1-9EF6-0B1DA7F270DE}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{7A12C96B-8B5C-45E1-9EF6-0B1DA7F270DE}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{7A12C96B-8B5C-45E1-9EF6-0B1DA7F270DE}.Release|ARM64.Build.0 = Release|Any CPU
 		{1249EE2B-EB0D-411C-B311-53A7A22B7743}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{1249EE2B-EB0D-411C-B311-53A7A22B7743}.Debug|x64.Build.0 = Debug|Any CPU
 		{1249EE2B-EB0D-411C-B311-53A7A22B7743}.DevRelease|x64.ActiveCfg = Debug|Any CPU
@@ -349,6 +367,8 @@ Global
 		{1249EE2B-EB0D-411C-B311-53A7A22B7743}.Debug|ARM64.Build.0 = Debug|Any CPU
 		{1249EE2B-EB0D-411C-B311-53A7A22B7743}.DevSteam|x64.ActiveCfg = Release|Any CPU
 		{1249EE2B-EB0D-411C-B311-53A7A22B7743}.DevSteam|x64.Build.0 = Release|Any CPU
+		{1249EE2B-EB0D-411C-B311-53A7A22B7743}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{1249EE2B-EB0D-411C-B311-53A7A22B7743}.Release|ARM64.Build.0 = Release|Any CPU
 		{FA98BFA6-2E83-41C6-9102-76875B261F51}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{FA98BFA6-2E83-41C6-9102-76875B261F51}.Debug|x64.Build.0 = Debug|Any CPU
 		{FA98BFA6-2E83-41C6-9102-76875B261F51}.DevRelease|x64.ActiveCfg = Debug|Any CPU
@@ -365,6 +385,8 @@ Global
 		{FA98BFA6-2E83-41C6-9102-76875B261F51}.Steam|x64.Build.0 = Release|Any CPU
 		{FA98BFA6-2E83-41C6-9102-76875B261F51}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{FA98BFA6-2E83-41C6-9102-76875B261F51}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{FA98BFA6-2E83-41C6-9102-76875B261F51}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{FA98BFA6-2E83-41C6-9102-76875B261F51}.Release|ARM64.Build.0 = Release|Any CPU
 		{16519035-0FF4-456F-B3F0-0ACA16E6920C}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{16519035-0FF4-456F-B3F0-0ACA16E6920C}.Debug|x64.Build.0 = Debug|Any CPU
 		{16519035-0FF4-456F-B3F0-0ACA16E6920C}.DevRelease|x64.ActiveCfg = Debug|Any CPU
@@ -381,6 +403,8 @@ Global
 		{16519035-0FF4-456F-B3F0-0ACA16E6920C}.Steam|x64.Build.0 = Release|Any CPU
 		{16519035-0FF4-456F-B3F0-0ACA16E6920C}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{16519035-0FF4-456F-B3F0-0ACA16E6920C}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{16519035-0FF4-456F-B3F0-0ACA16E6920C}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{16519035-0FF4-456F-B3F0-0ACA16E6920C}.Release|ARM64.Build.0 = Release|Any CPU
 		{3DF64622-87E3-4870-B694-05D565251BB9}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{3DF64622-87E3-4870-B694-05D565251BB9}.Debug|x64.Build.0 = Debug|Any CPU
 		{3DF64622-87E3-4870-B694-05D565251BB9}.DevRelease|x64.ActiveCfg = Debug|Any CPU
@@ -429,6 +453,8 @@ Global
 		{8F4FFC91-BE9F-4476-A372-FBD952865F15}.Steam|x64.Build.0 = Release|Any CPU
 		{8F4FFC91-BE9F-4476-A372-FBD952865F15}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{8F4FFC91-BE9F-4476-A372-FBD952865F15}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{8F4FFC91-BE9F-4476-A372-FBD952865F15}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{8F4FFC91-BE9F-4476-A372-FBD952865F15}.Release|ARM64.Build.0 = Release|Any CPU
 		{71907779-F1D1-4AA6-BA11-E990DB089841}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{71907779-F1D1-4AA6-BA11-E990DB089841}.Debug|x64.Build.0 = Debug|Any CPU
 		{71907779-F1D1-4AA6-BA11-E990DB089841}.DevRelease|x64.ActiveCfg = Debug|Any CPU
@@ -461,6 +487,8 @@ Global
 		{B30622ED-9177-4930-8E64-2B2352D4D8DC}.Steam|x64.Build.0 = Release|Any CPU
 		{B30622ED-9177-4930-8E64-2B2352D4D8DC}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{B30622ED-9177-4930-8E64-2B2352D4D8DC}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{B30622ED-9177-4930-8E64-2B2352D4D8DC}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{B30622ED-9177-4930-8E64-2B2352D4D8DC}.Release|ARM64.Build.0 = Release|Any CPU
 		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Debug|x64.Build.0 = Debug|Any CPU
 		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.DevRelease|x64.ActiveCfg = Debug|Any CPU
@@ -477,6 +505,8 @@ Global
 		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Steam|x64.Build.0 = Release|Any CPU
 		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Release|ARM64.Build.0 = Release|Any CPU
 		{5848FCF1-E127-4CE3-8A25-F37032819F8D}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{5848FCF1-E127-4CE3-8A25-F37032819F8D}.Debug|x64.Build.0 = Debug|Any CPU
 		{5848FCF1-E127-4CE3-8A25-F37032819F8D}.DevRelease|x64.ActiveCfg = Debug|Any CPU
@@ -493,6 +523,8 @@ Global
 		{5848FCF1-E127-4CE3-8A25-F37032819F8D}.Steam|x64.Build.0 = Release|Any CPU
 		{5848FCF1-E127-4CE3-8A25-F37032819F8D}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{5848FCF1-E127-4CE3-8A25-F37032819F8D}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{5848FCF1-E127-4CE3-8A25-F37032819F8D}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{5848FCF1-E127-4CE3-8A25-F37032819F8D}.Release|ARM64.Build.0 = Release|Any CPU
 		{5E8F82CF-F48A-40B2-99E3-9BBB8725866A}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{5E8F82CF-F48A-40B2-99E3-9BBB8725866A}.Debug|x64.Build.0 = Debug|Any CPU
 		{5E8F82CF-F48A-40B2-99E3-9BBB8725866A}.DevRelease|x64.ActiveCfg = Debug|Any CPU
@@ -509,6 +541,8 @@ Global
 		{5E8F82CF-F48A-40B2-99E3-9BBB8725866A}.Steam|x64.Build.0 = Release|Any CPU
 		{5E8F82CF-F48A-40B2-99E3-9BBB8725866A}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{5E8F82CF-F48A-40B2-99E3-9BBB8725866A}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{5E8F82CF-F48A-40B2-99E3-9BBB8725866A}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{5E8F82CF-F48A-40B2-99E3-9BBB8725866A}.Release|ARM64.Build.0 = Release|Any CPU
 		{9FCCD0CF-FF76-4638-A712-803EFBBC641F}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{9FCCD0CF-FF76-4638-A712-803EFBBC641F}.Debug|x64.Build.0 = Debug|Any CPU
 		{9FCCD0CF-FF76-4638-A712-803EFBBC641F}.DevRelease|x64.ActiveCfg = Debug|Any CPU
@@ -525,6 +559,8 @@ Global
 		{9FCCD0CF-FF76-4638-A712-803EFBBC641F}.Steam|x64.Build.0 = Release|Any CPU
 		{9FCCD0CF-FF76-4638-A712-803EFBBC641F}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{9FCCD0CF-FF76-4638-A712-803EFBBC641F}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{9FCCD0CF-FF76-4638-A712-803EFBBC641F}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{9FCCD0CF-FF76-4638-A712-803EFBBC641F}.Release|ARM64.Build.0 = Release|Any CPU
 		{DA3AF3CC-43B2-4871-BDEC-CBE9222A8269}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{DA3AF3CC-43B2-4871-BDEC-CBE9222A8269}.Debug|x64.Build.0 = Debug|Any CPU
 		{DA3AF3CC-43B2-4871-BDEC-CBE9222A8269}.DevRelease|x64.ActiveCfg = Debug|Any CPU
@@ -557,6 +593,8 @@ Global
 		{E46F2824-3CDA-40CB-AA57-8A4387E6B188}.Steam|x64.Build.0 = Release|Any CPU
 		{E46F2824-3CDA-40CB-AA57-8A4387E6B188}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{E46F2824-3CDA-40CB-AA57-8A4387E6B188}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{E46F2824-3CDA-40CB-AA57-8A4387E6B188}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{E46F2824-3CDA-40CB-AA57-8A4387E6B188}.Release|ARM64.Build.0 = Release|Any CPU
 		{AE200ADC-9E85-4275-A373-E975CD6D518C}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{AE200ADC-9E85-4275-A373-E975CD6D518C}.Debug|x64.Build.0 = Debug|Any CPU
 		{AE200ADC-9E85-4275-A373-E975CD6D518C}.DevRelease|x64.ActiveCfg = Debug|Any CPU
@@ -571,6 +609,7 @@ Global
 		{AE200ADC-9E85-4275-A373-E975CD6D518C}.Release|x64.Build.0 = Release|Any CPU
 		{AE200ADC-9E85-4275-A373-E975CD6D518C}.Steam|x64.ActiveCfg = Release|Any CPU
 		{AE200ADC-9E85-4275-A373-E975CD6D518C}.Steam|x64.Build.0 = Release|Any CPU
+		{AE200ADC-9E85-4275-A373-E975CD6D518C}.Release|ARM64.ActiveCfg = Release|Any CPU
 		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.Debug|x64.ActiveCfg = Debug|x64
 		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.Debug|x64.Build.0 = Debug|x64
 		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.Steam|x64.ActiveCfg = Steam|x64
@@ -583,6 +622,8 @@ Global
 		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.DevRelease|x64.Build.0 = DevRelease|x64
 		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.DevSteam|x64.ActiveCfg = DevSteam|x64
 		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.DevSteam|x64.Build.0 = DevSteam|x64
+		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.Release|ARM64.ActiveCfg = Release|arm64
+		{F2E992CA-12E3-49F3-B16F-2CEF5B191493}.Release|ARM64.Build.0 = Release|arm64
 		{D72E70F3-BF37-432F-B78B-5B247C873852}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{D72E70F3-BF37-432F-B78B-5B247C873852}.Debug|ARM64.Build.0 = Debug|Any CPU
 		{D72E70F3-BF37-432F-B78B-5B247C873852}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -701,6 +742,8 @@ Global
 		{7AEE19FA-A4F8-4ACA-9E39-401AA1F603C2}.Steam|x64.Build.0 = Release|Any CPU
 		{7AEE19FA-A4F8-4ACA-9E39-401AA1F603C2}.Debug|ARM64.ActiveCfg = Debug|Any CPU
 		{7AEE19FA-A4F8-4ACA-9E39-401AA1F603C2}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{7AEE19FA-A4F8-4ACA-9E39-401AA1F603C2}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{7AEE19FA-A4F8-4ACA-9E39-401AA1F603C2}.Release|ARM64.Build.0 = Release|Any CPU
 		{2B4A9926-0532-4C59-9289-37775A7499A4}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{2B4A9926-0532-4C59-9289-37775A7499A4}.Debug|x64.Build.0 = Debug|Any CPU
 		{2B4A9926-0532-4C59-9289-37775A7499A4}.Debug|ARM64.ActiveCfg = Debug|Any CPU
@@ -821,6 +864,8 @@ Global
 		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.DevRelease|x64.Build.0 = Debug|Any CPU
 		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|x64.ActiveCfg = Release|Any CPU
 		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|x64.Build.0 = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|ARM64.Build.0 = Release|Any CPU
 		{786E1F87-4A10-493E-88BD-3F2461DBFCA0}.Debug|x64.ActiveCfg = Debug|Any CPU
 		{786E1F87-4A10-493E-88BD-3F2461DBFCA0}.Debug|x64.Build.0 = Debug|Any CPU
 		{786E1F87-4A10-493E-88BD-3F2461DBFCA0}.Debug|ARM64.ActiveCfg = Debug|Any CPU

+ 17 - 0
src/PixiEditor/Helpers/InputKeyHelpers.cs

@@ -10,4 +10,21 @@ internal static class InputKeyHelpers
     /// </summary>
     public static string GetKeyboardKey(Key key, bool forceInvariant = false) =>
         IOperatingSystem.Current.InputKeys.GetKeyboardKey(key, forceInvariant);
+
+    public static bool ModifierUsesSymbol(KeyModifiers modifier)
+    {
+        return IOperatingSystem.Current.InputKeys.ModifierUsesSymbol(modifier);
+    }
+
+    public static Key ModifierToKey(KeyModifiers modifier)
+    {
+        return modifier switch
+        {
+            KeyModifiers.Alt => Key.LeftAlt,
+            KeyModifiers.Control => Key.LeftCtrl,
+            KeyModifiers.Shift => Key.LeftShift,
+            KeyModifiers.Meta => Key.LWin,
+            _ => Key.None
+        };
+    }
 }

+ 17 - 8
src/PixiEditor/Models/Input/KeyCombination.cs

@@ -18,6 +18,7 @@ public record struct KeyCombination(Key Key, KeyModifiers Modifiers)
     public KeyGesture ToKeyGesture() => new(Key, Modifiers);
 
     public KeyGesture Gesture => ToKeyGesture();
+    
 
     private string ToString(bool forceInvariant, bool showNone)
     {
@@ -27,23 +28,31 @@ public record struct KeyCombination(Key Key, KeyModifiers Modifiers)
         {
             if (modifier == KeyModifiers.None) continue;
 
-            string key = modifier switch
+            string key;
+            if (InputKeyHelpers.ModifierUsesSymbol(modifier))
             {
-                KeyModifiers.Control => new LocalizedString("CTRL_KEY"),
-                KeyModifiers.Shift => new LocalizedString("SHIFT_KEY"),
-                KeyModifiers.Alt => new LocalizedString("ALT_KEY"),
-                _ => modifier.ToString()
-            };
+                key = InputKeyHelpers.GetKeyboardKey(InputKeyHelpers.ModifierToKey(modifier), forceInvariant);
+            }
+            else
+            {
+                key = modifier switch
+                {
+                    KeyModifiers.Control => new LocalizedString("CTRL_KEY"),
+                    KeyModifiers.Shift => new LocalizedString("SHIFT_KEY"),
+                    KeyModifiers.Alt => new LocalizedString("ALT_KEY"),
+                    _ => modifier.ToString()
+                };
+            }
 
             builder.Append($"{key}+");
         }
-
+        
         if (Key != Key.None || showNone)
         {
             builder.Append(InputKeyHelpers.GetKeyboardKey(Key, forceInvariant));
         }
 
-        builder.Append('‎'); // left-to-right marker ensures WPF does not reverse the string when using punctuations as key
+        builder.Append('‎'); // left-to-right marker ensures Avalonia does not reverse the string when using punctuations as key
         return builder.ToString();
     }
 

+ 7 - 0
src/PixiEditor/ViewModels/SubViewModels/UpdateViewModel.cs

@@ -14,6 +14,7 @@ using PixiEditor.Helpers;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.IO;
+using PixiEditor.OperatingSystem;
 using PixiEditor.Platform;
 using PixiEditor.UpdateModule;
 
@@ -72,6 +73,11 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
 
     public async Task<bool> CheckForUpdate()
     {
+        if(IOperatingSystem.Current.Name != "Windows")
+        {
+            return false;
+        }
+        
         bool updateAvailable = await UpdateChecker.CheckUpdateAvailable();
         if(!UpdateChecker.LatestReleaseInfo.WasDataFetchSuccessful || string.IsNullOrEmpty(UpdateChecker.LatestReleaseInfo.TagName))
         {
@@ -82,6 +88,7 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
         bool autoUpdateFailed = CheckAutoupdateFailed();
         bool updateFileDoesNotExists = !File.Exists(
             Path.Join(UpdateDownloader.DownloadLocation, $"update-{UpdateChecker.LatestReleaseInfo.TagName}.zip"));
+        
         bool updateExeDoesNotExists = !File.Exists(
             Path.Join(UpdateDownloader.DownloadLocation, $"update-{UpdateChecker.LatestReleaseInfo.TagName}.exe"));
         if (updateAvailable && (updateFileDoesNotExists && updateExeDoesNotExists) || autoUpdateFailed)

+ 4 - 4
src/PixiEditor/Views/Shortcuts/KeyCombinationBox.axaml.cs

@@ -1,6 +1,7 @@
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Input;
+using Avalonia.Input.Raw;
 using Avalonia.Interactivity;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Models.Input;
@@ -60,7 +61,6 @@ internal partial class KeyCombinationBox : UserControl
     {
         e.Handled = true;
 
-        //TODO: e.Key == Key.System ? e.SystemKey : e.Key was here, but SystemKey is not available in Avalonia
         if (GetModifier(e.Key) is { } modifier)
         {
             currentCombination = new(currentCombination.Key, currentCombination.Modifiers | modifier);
@@ -78,15 +78,14 @@ internal partial class KeyCombinationBox : UserControl
     {
         e.Handled = true;
 
-        //TODO: There was (e.Key == Key.System ? e.SystemKey : e.Key), but SystemKey is not available in Avalonia
         if (GetModifier(e.Key) is { } modifier)
         {
-            currentCombination = new(currentCombination.Key, currentCombination.Modifiers ^ modifier);
+            currentCombination = currentCombination with { Modifiers = currentCombination.Modifiers ^ modifier };
             UpdateText();
         }
         else
         {
-            KeyCombination = new(e.Key, currentCombination.Modifiers);
+            KeyCombination = currentCombination with { Key = e.Key };
             focusGrid.Focus();
         }
 
@@ -173,6 +172,7 @@ internal partial class KeyCombinationBox : UserControl
         Key.LeftCtrl or Key.RightCtrl => KeyModifiers.Control,
         Key.LeftAlt or Key.RightAlt => KeyModifiers.Alt,
         Key.LeftShift or Key.RightShift => KeyModifiers.Shift,
+        Key.LWin or Key.RWin => KeyModifiers.Meta,
         _ => null
     };
 }