Browse Source

Some shortcut displaying stuff and processes

Krzysztof Krysiński 6 months ago
parent
commit
a655c2663c

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

@@ -11,15 +11,15 @@ public sealed class MacOperatingSystem : IOperatingSystem
     public string AnalyticsId => "macOS";
     
     public IInputKeys InputKeys { get; } = new MacOsInputKeys();
-    public IProcessUtility ProcessUtility { get; }
+    public IProcessUtility ProcessUtility { get; } = new MacOsProcessUtility();
     public void OpenUri(string uri)
     {
-        throw new NotImplementedException();
+        ProcessUtility.ShellExecute(uri);
     }
 
     public void OpenFolder(string path)
     {
-        throw new NotImplementedException();
+        ProcessUtility.ShellExecute(Path.GetDirectoryName(path));
     }
 
     public bool HandleNewInstance(Dispatcher? dispatcher, Action<string> openInExistingAction, IApplicationLifetime lifetime)

+ 57 - 0
src/PixiEditor.MacOs/MacOsProcessUtility.cs

@@ -0,0 +1,57 @@
+using System.Diagnostics;
+using PixiEditor.OperatingSystem;
+
+namespace PixiEditor.MacOs;
+
+internal class MacOsProcessUtility : IProcessUtility
+{
+    public Process RunAsAdmin(string path)
+    {
+        return RunAsAdmin(path, true);
+    }
+
+    public Process RunAsAdmin(string path, bool createWindow)
+    {
+        ProcessStartInfo startInfo = new ProcessStartInfo
+        {
+            FileName = path,
+            Verb = "runas",
+            UseShellExecute = createWindow,
+            CreateNoWindow = !createWindow,
+            RedirectStandardOutput = !createWindow,
+            RedirectStandardError = !createWindow,
+            WindowStyle = createWindow ? ProcessWindowStyle.Normal : ProcessWindowStyle.Hidden
+        };
+
+        Process p = new Process();
+        p.StartInfo = startInfo;
+
+        p.Start();
+        return p;
+    }
+
+    public bool IsRunningAsAdministrator()
+    {
+        return Environment.IsPrivilegedProcess;
+    }
+
+    public static void ShellExecute(string url)
+    {
+        Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true, });
+    }
+
+    void IProcessUtility.ShellExecute(string url)
+    {
+        ShellExecute(url);
+    }
+
+    public static void ShellExecute(string url, string args)
+    {
+        Process.Start(new ProcessStartInfo { FileName = url, Arguments = args, UseShellExecute = true, });
+    }
+
+    public static void ShellExecuteEV(string path) => ShellExecute(Environment.ExpandEnvironmentVariables(path));
+
+    public static void ShellExecuteEV(string path, string args) =>
+        ShellExecute(Environment.ExpandEnvironmentVariables(path), args);
+}

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

@@ -1,5 +1,9 @@
 - [x] Input keys
 - [ ] Default shortcuts
+- [ ] Single instance
+- [ ] File associations
 - [ ] Autoupdates
 - [ ] Process handling
-- [ ] Check extensions
+- [ ] Check if extensions work
+- [ ] Native menu
+- [ ] Performance fixes

+ 4 - 0
src/PixiEditor.OperatingSystem/IOperatingSystem.cs

@@ -13,6 +13,10 @@ public interface IOperatingSystem
 
     public IInputKeys InputKeys { get; }
     public IProcessUtility ProcessUtility { get; }
+    public bool IsMacOs => Name == "MacOS";
+    public bool IsWindows => Name == "Windows";
+    public bool IsLinux => Name == "Linux";
+    public bool IsMiscellaneous => !IsMacOs && !IsWindows && !IsLinux;
 
     public static void RegisterOS(IOperatingSystem operatingSystem)
     {

+ 55 - 10
src/PixiEditor/Helpers/Converters/KeyToStringConverter.cs

@@ -1,23 +1,68 @@
 using System.Globalization;
+using System.Text;
 using Avalonia.Input;
 using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.Extensions.Helpers;
+using PixiEditor.Models.Input;
+using PixiEditor.OperatingSystem;
 
 namespace PixiEditor.Helpers.Converters;
 
 internal class KeyToStringConverter
     : SingleInstanceConverter<KeyToStringConverter>
 {
-    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
-        value switch
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        return value switch
         {
-            Key key => (object)InputKeyHelpers.GetKeyboardKey(key),
-            KeyModifiers modifier => modifier switch
-            {
-                KeyModifiers.Control => new LocalizedString("CTRL_KEY"),
-                KeyModifiers.Shift => new LocalizedString("SHIFT_KEY"),
-                KeyModifiers.Alt => new LocalizedString("ALT_KEY"),
-                _ => modifier.ToString()
-            },
+            Key key => ConvertKey(key),
+            KeyModifiers modifier => ConvertModifier(modifier),
+            KeyGesture gesture => ConvertKeyCombination(gesture),
             _ => string.Empty
         };
+    }
+
+    private static string ConvertKey(Key key)
+    {
+        return InputKeyHelpers.GetKeyboardKey(key);
+    }
+
+    private static string ConvertModifier(KeyModifiers modifier)
+    {
+        if (IOperatingSystem.Current.InputKeys.ModifierUsesSymbol(modifier))
+        {
+            return InputKeyHelpers.GetKeyboardKey(InputKeyHelpers.ModifierToKey(modifier));
+        }
+
+        return modifier switch
+        {
+            KeyModifiers.Control => "CTRL_KEY",
+            KeyModifiers.Shift => "SHIFT_KEY",
+            KeyModifiers.Alt => "ALT_KEY",
+            _ => modifier.ToString()
+        };
+    }
+
+    private string ConvertKeyCombination(KeyGesture value)
+    {
+        var flags = value.KeyModifiers.GetFlags().OrderByDescending(x => x != KeyModifiers.Alt);
+        var builder = new StringBuilder();
+        
+        foreach (var modifier in flags)
+        {
+            if (modifier == KeyModifiers.None) continue;
+
+            string mod = ConvertModifier(modifier);
+
+            builder.Append($"{mod}+");
+        }
+        
+        if (value.Key != Key.None)
+        {
+            builder.Append(ConvertKey(value.Key));
+        }
+        
+        builder.Append('‎'); // left-to-right marker ensures Avalonia does not reverse the string when using punctuations as key
+        return builder.ToString();
+    }
 }

+ 26 - 4
src/PixiEditor/Models/Commands/CommandController.cs

@@ -3,6 +3,7 @@ using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Threading.Tasks;
+using Avalonia.Input;
 using Avalonia.Media;
 using Microsoft.Extensions.DependencyInjection;
 using Newtonsoft.Json;
@@ -18,6 +19,7 @@ using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Input;
 using PixiEditor.Models.Structures;
+using PixiEditor.OperatingSystem;
 using CommandAttribute = PixiEditor.Models.Commands.Attributes.Commands.Command;
 
 namespace PixiEditor.Models.Commands;
@@ -255,8 +257,8 @@ internal class CommandController
                                 Description = attribute.Description,
                                 Icon = attribute.Icon,
                                 IconEvaluator = xIcon,
-                                DefaultShortcut = attribute.GetShortcut(),
-                                Shortcut = GetShortcut(name, attribute.GetShortcut(), template),
+                                DefaultShortcut = AdjustForOS(attribute.GetShortcut()),
+                                Shortcut = GetShortcut(name, AdjustForOS(attribute.GetShortcut()), template),
                                 Parameter = basic.Parameter,
                                 MenuItemPath = basic.MenuItemPath,
                                 MenuItemOrder = basic.MenuItemOrder,
@@ -300,8 +302,8 @@ internal class CommandController
                                 DisplayName = menu.DisplayName,
                                 Description = menu.DisplayName,
                                 IconEvaluator = IconEvaluator.Default,
-                                DefaultShortcut = menu.GetShortcut(),
-                                Shortcut = GetShortcut(name, attribute.GetShortcut(), template)
+                                DefaultShortcut = AdjustForOS(menu.GetShortcut()),
+                                Shortcut = GetShortcut(name, AdjustForOS(attribute.GetShortcut()), template)
                             };
                         
                         Commands.Add(command);
@@ -364,8 +366,28 @@ internal class CommandController
             return command;
 
             void CommandAction(object x) => CommandMethodInvoker(method, name, instance, x, parameters, attribute.AnalyticsTrack);
+        }
+
+        KeyCombination AdjustForOS(KeyCombination combination)
+        {
+            if (IOperatingSystem.Current.IsMacOs)
+            {
+                KeyCombination newCombination = combination;
+                if (combination.Modifiers.HasFlag(KeyModifiers.Control))
+                {
+                    newCombination.Modifiers &= ~KeyModifiers.Control;
+                    newCombination.Modifiers |= KeyModifiers.Meta;
+                }
 
+                if (combination.Key == Key.Delete)
+                {
+                    newCombination.Key = Key.Back;
+                }
+                
+                return newCombination;
+            }
             
+            return combination;
         }
     }
 

+ 11 - 10
src/PixiEditor/Models/Controllers/InputDevice/KeyboardInputFilter.cs

@@ -55,19 +55,20 @@ internal class KeyboardInputFilter
             Key convKey = ConvertRightKeys(key);
             bool raiseConverted = UpdateKeyState(convKey, KeyStates.None, converterdKeyboardState);
 
-            var (shift, ctrl, alt) = GetModifierStates();
-            OnAnyKeyUp?.Invoke(this, new FilteredKeyEventArgs(key, key, KeyStates.None, false, shift, ctrl, alt));
+            var (shift, ctrl, alt, meta) = GetModifierStates();
+            OnAnyKeyUp?.Invoke(this, new FilteredKeyEventArgs(key, key, KeyStates.None, false, shift, ctrl, alt, meta));
             if (raiseConverted)
-                OnConvertedKeyUp?.Invoke(this, new FilteredKeyEventArgs(key, key, KeyStates.None, false, shift, ctrl, alt));
+                OnConvertedKeyUp?.Invoke(this, new FilteredKeyEventArgs(key, key, KeyStates.None, false, shift, ctrl, alt, meta));
         }
     }
 
-    private (bool shift, bool ctrl, bool alt) GetModifierStates()
+    private (bool shift, bool ctrl, bool alt, bool meta) GetModifierStates()
     {
-        bool shift = converterdKeyboardState.TryGetValue(Key.LeftShift, out KeyStates shiftKey) ? shiftKey == KeyStates.Down : false;
-        bool ctrl = converterdKeyboardState.TryGetValue(Key.LeftCtrl, out KeyStates ctrlKey) ? ctrlKey == KeyStates.Down : false;
-        bool alt = converterdKeyboardState.TryGetValue(Key.LeftAlt, out KeyStates altKey) ? altKey == KeyStates.Down : false;
-        return (shift, ctrl, alt);
+        bool shift = converterdKeyboardState.TryGetValue(Key.LeftShift, out KeyStates shiftKey) && shiftKey == KeyStates.Down;
+        bool ctrl = converterdKeyboardState.TryGetValue(Key.LeftCtrl, out KeyStates ctrlKey) && ctrlKey == KeyStates.Down;
+        bool alt = converterdKeyboardState.TryGetValue(Key.LeftAlt, out KeyStates altKey) && altKey == KeyStates.Down;
+        bool meta = converterdKeyboardState.TryGetValue(Key.LWin, out KeyStates metaKey) && metaKey == KeyStates.Down;
+        return (shift, ctrl, alt, meta);
     }
 
     public void KeyDownInlet(KeyEventArgs args)
@@ -107,7 +108,7 @@ internal class KeyboardInputFilter
         bool isRepeat = isRepeatFromArgs && !keyWasUpdated;
         if (!isRepeat && !keyWasUpdated)
             return;
-        var (shift, ctrl, alt) = GetModifierStates();
-        eventToRaise?.Invoke(this, new FilteredKeyEventArgs(key, key, newKeyState, isRepeat, shift, ctrl, alt));
+        var (shift, ctrl, alt, meta) = GetModifierStates();
+        eventToRaise?.Invoke(this, new FilteredKeyEventArgs(key, key, newKeyState, isRepeat, shift, ctrl, alt, meta));
     }
 }

+ 3 - 1
src/PixiEditor/Models/Events/FilteredKeyEventArgs.cs

@@ -5,7 +5,7 @@ namespace PixiEditor.Models.Events;
 internal class FilteredKeyEventArgs : EventArgs
 {
     public FilteredKeyEventArgs(
-        Key unfilteredKey, Key key, KeyStates state, bool isRepeat, bool isShiftDown, bool isCtrlDown, bool isAltDown)
+        Key unfilteredKey, Key key, KeyStates state, bool isRepeat, bool isShiftDown, bool isCtrlDown, bool isAltDown, bool isMetaDown)
     {
         UnfilteredKey = unfilteredKey;
         Key = key;
@@ -19,6 +19,8 @@ internal class FilteredKeyEventArgs : EventArgs
             modifiers |= KeyModifiers.Control;
         if (isAltDown)
             modifiers |= KeyModifiers.Alt;
+        if (isMetaDown)
+            modifiers |= KeyModifiers.Meta;
         Modifiers = modifiers;
     }
 

+ 2 - 1
src/PixiEditor/Views/Main/MainTitleBar.axaml

@@ -10,6 +10,7 @@
              xmlns:input="clr-namespace:PixiEditor.Views.Input;assembly=PixiEditor.UI.Common"
              xmlns:menu="clr-namespace:PixiEditor.ViewModels.Menu"
              xmlns:main="clr-namespace:PixiEditor.Views.Main"
+             xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              x:DataType="menu:MenuBarViewModel"
              x:Class="PixiEditor.Views.Main.MainTitleBar">
@@ -60,7 +61,7 @@
                 </Interaction.Behaviors>
                 <Grid Margin="5,0" VerticalAlignment="Center">
                     <TextBlock ui:Translator.Key="SEARCH" />
-                    <TextBlock Text="{xaml:ShortcutBinding PixiEditor.Search.Toggle}"
+                    <TextBlock Text="{xaml:ShortcutBinding PixiEditor.Search.Toggle, Converter={converters:KeyToStringConverter}}"
                                HorizontalAlignment="Right" />
                 </Grid>
             </Border>

+ 1 - 1
src/PixiEditor/Views/Shortcuts/ShortcutHint.axaml

@@ -14,7 +14,7 @@
                 <DataTemplate DataType="{x:Type KeyModifiers}">
                     <Border Classes="KeyBorder">
                         <TextBlock
-                            ui:Translator.LocalizedString="{Binding Converter={converters:KeyToStringConverter}}"
+                            ui:Translator.Key="{Binding Converter={converters:KeyToStringConverter}}"
                             Classes="KeyBorderText" TextAlignment="Center" MinWidth="33" />
                     </Border>
                 </DataTemplate>