• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

luttje / Key2Joy / 6603144428

22 Oct 2023 10:10AM UTC coverage: 43.896% (-0.2%) from 44.094%
6603144428

Pull #54

github

web-flow
Merge 6b56375be into 6b38fe9a4
Pull Request #54: Feature/rework override default

765 of 2403 branches covered (0.0%)

Branch coverage included in aggregate %.

97 of 97 new or added lines in 10 files covered. (100.0%)

3902 of 8229 relevant lines covered (47.42%)

20421.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.57
/Core/Key2Joy.Core/Key2JoyManager.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Diagnostics;
4
using System.IO;
5
using System.Linq;
6
using System.Reflection;
7
using CommonServiceLocator;
8
using Key2Joy.Config;
9
using Key2Joy.Contracts.Mapping.Actions;
10
using Key2Joy.Contracts.Mapping.Triggers;
11
using Key2Joy.Interop;
12
using Key2Joy.Interop.Commands;
13
using Key2Joy.LowLevelInput.SimulatedGamePad;
14
using Key2Joy.LowLevelInput.XInput;
15
using Key2Joy.Mapping;
16
using Key2Joy.Mapping.Actions.Logic;
17
using Key2Joy.Mapping.Triggers.Keyboard;
18
using Key2Joy.Mapping.Triggers.Mouse;
19
using Key2Joy.Plugins;
20
using Key2Joy.Util;
21

22
namespace Key2Joy;
23

24
public delegate bool AppCommandRunner(AppCommand command);
25

26
public class Key2JoyManager : IKey2JoyManager
27
{
28
    /// <summary>
29
    /// Directory where plugins are located
30
    /// </summary>
31
    public const string PluginsDirectory = "Plugins";
32

33
    public event EventHandler<StatusChangedEventArgs> StatusChanged;
34

35
    public static Key2JoyManager instance;
36

37
    public static Key2JoyManager Instance
38
    {
39
        get
40
        {
41
            if (instance == null)
6!
42
            {
43
                throw new Exception("Key2JoyManager not initialized using InitSafely yet!");
×
44
            }
45

46
            return instance;
6✔
47
        }
48
    }
49

50
    /// <summary>
51
    /// Trigger listeners that should explicitly loaded. This ensures that they're available for scripts
52
    /// even if no mapping option is mapped to be triggered by it.
53
    /// </summary>
54
    public IList<AbstractTriggerListener> ExplicitTriggerListeners { get; set; }
8✔
55

56
    private const string READY_MESSAGE = "Key2Joy is ready";
57
    private static AppCommandRunner commandRunner;
58
    private MappingProfile armedProfile;
59
    private IHaveHandleAndInvoke handleAndInvoker;
60

61
    private Key2JoyManager()
2✔
62
    { }
2✔
63

64
    /// <summary>
65
    /// Ensures Key2Joy is running and ready to accept commands as long as the main loop does not end.
66
    /// </summary>
67
    /// <param name="commandRunner"></param>
68
    /// <param name="mainLoop"></param>
69
    /// <param name="configManager">Optionally a custom config manager (probably only useful for unit testing)</param>
70
    public static void InitSafely(AppCommandRunner commandRunner, Action<PluginSet> mainLoop, IConfigManager configManager = null)
71
    {
72
        // Setup dependency injection and services
73
        var serviceLocator = new DependencyServiceLocator();
2✔
74
        ServiceLocator.SetLocatorProvider(() => serviceLocator);
10✔
75

76
        instance = new Key2JoyManager
2✔
77
        {
2✔
78
            ExplicitTriggerListeners = new List<AbstractTriggerListener>()
2✔
79
            {
2✔
80
                // Always add these listeners so scripts can ask them if stuff has happened.
2✔
81
                KeyboardTriggerListener.Instance,
2✔
82
                MouseButtonTriggerListener.Instance,
2✔
83
                MouseMoveTriggerListener.Instance
2✔
84
            }
2✔
85
        };
2✔
86
        serviceLocator.Register<IKey2JoyManager>(instance);
2✔
87

88
#pragma warning disable IDE0001 // Simplify Names
89
        serviceLocator.Register<IConfigManager>(configManager ??= new ConfigManager());
2!
90
#pragma warning restore IDE0001 // Simplify Names
91

92
        var gamePadService = new SimulatedGamePadService();
2✔
93
        serviceLocator.Register<ISimulatedGamePadService>(gamePadService);
2✔
94

95
        var xInputService = new XInputService();
2✔
96
        serviceLocator.Register<IXInputService>(xInputService);
2✔
97

98
        var commandRepository = new CommandRepository();
2✔
99
        serviceLocator.Register<ICommandRepository>(commandRepository);
2✔
100

101
        // Load plugins
102
        var pluginDirectoriesPaths = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
2✔
103
        pluginDirectoriesPaths = Path.Combine(pluginDirectoriesPaths, PluginsDirectory);
2✔
104

105
        PluginSet plugins = new(pluginDirectoriesPaths);
2✔
106
        plugins.LoadAll();
2✔
107
        plugins.RefreshPluginTypes();
2✔
108

109
        foreach (var loadState in plugins.AllPluginLoadStates.Values)
8✔
110
        {
111
            if (loadState.LoadState == PluginLoadStates.FailedToLoad)
2!
112
            {
113
                System.Windows.MessageBox.Show(
×
114
                    $"One of your plugins located at {loadState.AssemblyPath} failed to load. This was the error: " +
×
115
                    loadState.LoadErrorMessage,
×
116
                    "Failed to load plugin!",
×
117
                    System.Windows.MessageBoxButton.OK,
×
118
                    System.Windows.MessageBoxImage.Warning
×
119
                );
×
120
            }
121
        }
122

123
        Key2JoyManager.commandRunner = commandRunner;
2✔
124

125
        var interopServer = new InteropServer(instance, commandRepository);
2✔
126

127
        try
128
        {
129
            interopServer.RestartListening();
2✔
130
            mainLoop(plugins);
2✔
131
        }
2✔
132
        finally
133
        {
134
            interopServer.StopListening();
2✔
135
            gamePadService.ShutDown();
2✔
136
        }
2✔
137
    }
2✔
138

139
    // Run the event on the same thread as the main control/form
140
    public void CallOnUiThread(Action action) => this.handleAndInvoker.Invoke(action);
×
141

142
    internal static bool RunAppCommand(AppCommand command) => commandRunner != null && commandRunner(command);
×
143

144
    public void SetHandlerWithInvoke(IHaveHandleAndInvoke handleAndInvoker)
145
    {
146
        this.handleAndInvoker = handleAndInvoker;
×
147

148
        Console.WriteLine(READY_MESSAGE);
×
149
    }
×
150

151
    public bool GetIsArmed(MappingProfile profile = null)
152
    {
153
        if (profile == null)
×
154
        {
155
            return this.armedProfile != null;
×
156
        }
157

158
        return this.armedProfile == profile;
×
159
    }
160

161
    /// <inheritdoc/>
162
    public void ArmMappings(MappingProfile profile)
163
    {
164
        this.armedProfile = profile;
2✔
165

166
        var allListeners = new List<AbstractTriggerListener>();
2✔
167
        allListeners.AddRange(this.ExplicitTriggerListeners);
2✔
168

169
        var allActions = (IList<AbstractAction>)profile.MappedOptions.Select(m => m.Action).ToList();
4✔
170

171
        var xInputService = ServiceLocator.Current.GetInstance<IXInputService>();
2✔
172
        // We must recognize physical devices before any simulated ones are added.
173
        // Otherwise we wont be able to tell the difference.
174
        xInputService.RecognizePhysicalDevices();
2✔
175
        xInputService.StartPolling();
2✔
176

177
        try
178
        {
179
            foreach (var mappedOption in profile.MappedOptions)
8✔
180
            {
181
                if (mappedOption.Trigger == null)
2✔
182
                {
183
                    continue;
184
                }
185

186
                var listener = mappedOption.Trigger.GetTriggerListener();
2✔
187

188
                if (!allListeners.Contains(listener))
2✔
189
                {
190
                    allListeners.Add(listener);
2✔
191
                }
192

193
                mappedOption.Action.OnStartListening(listener, ref allActions);
2✔
194
                listener.AddMappedOption(mappedOption);
2✔
195
            }
196

197
            var allListenersForSharing = (IList<AbstractTriggerListener>)allListeners;
2✔
198

199
            foreach (var listener in allListeners)
8✔
200
            {
201
                listener.StartListening(ref allListenersForSharing);
2✔
202
            }
203

204
            StatusChanged?.Invoke(this, new StatusChangedEventArgs
2!
205
            {
2✔
206
                IsEnabled = true,
2✔
207
                Profile = this.armedProfile
2✔
208
            });
2✔
209
        }
2✔
210
        catch (MappingArmingFailedException ex)
211
        {
212
            this.DisarmMappings();
×
213
            throw ex;
×
214
        }
215
    }
2✔
216

217
    public void DisarmMappings()
218
    {
219
        var listeners = this.ExplicitTriggerListeners;
2✔
220

221
        // Clear all intervals
222
        IdPool.CancelAll();
2✔
223

224
        foreach (var mappedOption in this.armedProfile.MappedOptions)
8✔
225
        {
226
            if (mappedOption.Trigger == null)
2✔
227
            {
228
                continue;
229
            }
230

231
            var listener = mappedOption.Trigger.GetTriggerListener();
2✔
232
            mappedOption.Action.OnStopListening(listener);
2✔
233

234
            if (!listeners.Contains(listener))
2✔
235
            {
236
                listeners.Add(listener);
2✔
237
            }
238
        }
239

240
        foreach (var listener in listeners)
8✔
241
        {
242
            listener.StopListening();
2✔
243
        }
244

245
        var xInputService = ServiceLocator.Current.GetInstance<IXInputService>();
2✔
246
        xInputService.StopPolling();
2✔
247

248
        var gamePadService = ServiceLocator.Current.GetInstance<ISimulatedGamePadService>();
2✔
249
        gamePadService.EnsureAllUnplugged();
2✔
250

251
        this.armedProfile = null;
2✔
252

253
        StatusChanged?.Invoke(this, new StatusChangedEventArgs
2!
254
        {
2✔
255
            IsEnabled = false,
2✔
256
        });
2✔
257
    }
×
258

259
    /// <summary>
260
    /// Starts Key2Joy, pausing until it's ready
261
    /// </summary>
262
    public static void StartKey2Joy(bool startMinimized = true, bool pauseUntilReady = true)
263
    {
264
        var configManager = ServiceLocator.Current.GetInstance<IConfigManager>();
×
265
        var executablePath = configManager.GetConfigState().LastInstallPath;
×
266

267
        if (executablePath == null)
×
268
        {
269
            Console.WriteLine("Error! Key2Joy executable path is not known, please start Key2Joy at least once!");
×
270
            return;
×
271
        }
272

273
        if (!File.Exists(executablePath))
×
274
        {
275
            Console.WriteLine("Error! Key2Joy executable path is invalid, please start Key2Joy at least once (and don't move the executable)!");
×
276
            return;
×
277
        }
278

279
        Process process = new()
×
280
        {
×
281
            StartInfo = new ProcessStartInfo
×
282
            {
×
283
                FileName = executablePath,
×
284
                Arguments = startMinimized ? "--minimized" : "",
×
285
                UseShellExecute = false,
×
286
                RedirectStandardOutput = true
×
287
            }
×
288
        };
×
289

290
        process.Start();
×
291

292
        if (!pauseUntilReady)
×
293
        {
294
            return;
×
295
        }
296

297
        while (!process.StandardOutput.EndOfStream)
×
298
        {
299
            if (process.StandardOutput.ReadLine() == READY_MESSAGE)
×
300
            {
301
                break;
302
            }
303
        }
304
    }
×
305
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc