diff --git a/ControllerCommon/Devices/AYANEO2021.cs b/ControllerCommon/Devices/AYANEO2021.cs index 54f4795d4..9ca47eeeb 100644 --- a/ControllerCommon/Devices/AYANEO2021.cs +++ b/ControllerCommon/Devices/AYANEO2021.cs @@ -1,4 +1,5 @@ -using WindowsInput.Events; +using System.Collections.Generic; +using WindowsInput.Events; namespace ControllerCommon.Devices { @@ -29,10 +30,10 @@ public AYANEO2021() : base() { 'Z', 'Y' }, }; - listeners.Add("WIN key", new ChordClick(KeyCode.LWin)); + listeners.Add("WIN key", new List() { KeyCode.LWin }); //listeners.Add("TM key", new ChordClick(KeyCode.RAlt, KeyCode.RControlKey, KeyCode.Delete)); // Conflicts with OS - listeners.Add("ESC key", new ChordClick(KeyCode.Escape)); - listeners.Add("KB key", new ChordClick(KeyCode.RControlKey, KeyCode.LWin)); // Conflicts with Ayaspace when installed + listeners.Add("ESC key", new List() { KeyCode.Escape }); + listeners.Add("KB key", new List() { KeyCode.RControlKey, KeyCode.LWin }); // Conflicts with Ayaspace when installed } } } diff --git a/ControllerCommon/Devices/AYANEONEXT.cs b/ControllerCommon/Devices/AYANEONEXT.cs index d7931282c..73339fe44 100644 --- a/ControllerCommon/Devices/AYANEONEXT.cs +++ b/ControllerCommon/Devices/AYANEONEXT.cs @@ -1,4 +1,5 @@ -using WindowsInput.Events; +using System.Collections.Generic; +using WindowsInput.Events; namespace ControllerCommon.Devices { @@ -29,8 +30,8 @@ public AYANEONEXT() : base() { 'Z', 'Y' }, }; - listeners.Add("Custom key BIG", new ChordClick(KeyCode.RControlKey, KeyCode.LWin, KeyCode.F12)); - listeners.Add("Custom key Small", new ChordClick(KeyCode.LWin, KeyCode.D)); + listeners.Add("Custom key BIG", new List() { KeyCode.RControlKey, KeyCode.LWin, KeyCode.F12 }); + listeners.Add("Custom key Small", new List() { KeyCode.LWin, KeyCode.D }); } } } diff --git a/ControllerCommon/Devices/Device.cs b/ControllerCommon/Devices/Device.cs index 274294c90..03a35697d 100644 --- a/ControllerCommon/Devices/Device.cs +++ b/ControllerCommon/Devices/Device.cs @@ -50,7 +50,7 @@ public abstract class Device public OneEuroSettings oneEuroSettings = new OneEuroSettings(0.002d, 0.008d); // trigger specific settings - public Dictionary listeners = new(); + public Dictionary> listeners = new(); private static Device device; public static Device GetDefault() diff --git a/ControllerCommon/Devices/OneXPlayerMiniAMD.cs b/ControllerCommon/Devices/OneXPlayerMiniAMD.cs index 86c916df4..53ad90625 100644 --- a/ControllerCommon/Devices/OneXPlayerMiniAMD.cs +++ b/ControllerCommon/Devices/OneXPlayerMiniAMD.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using System.Collections.Generic; +using System.Numerics; using WindowsInput.Events; namespace ControllerCommon.Devices @@ -31,8 +32,8 @@ public OneXPlayerMiniAMD() : base() { 'Z', 'Y' }, }; - listeners.Add("Keyboard key", new ChordClick(KeyCode.LWin, KeyCode.RControlKey, KeyCode.O)); - listeners.Add("Function key", new ChordClick(KeyCode.LWin, KeyCode.D)); + listeners.Add("Keyboard key", new List() { KeyCode.LWin, KeyCode.RControlKey, KeyCode.O }); + listeners.Add("Function key", new List() { KeyCode.LWin, KeyCode.D }); } } } diff --git a/ControllerCommon/Devices/OneXPlayerMiniIntel.cs b/ControllerCommon/Devices/OneXPlayerMiniIntel.cs index 189e60613..32b3defbc 100644 --- a/ControllerCommon/Devices/OneXPlayerMiniIntel.cs +++ b/ControllerCommon/Devices/OneXPlayerMiniIntel.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using System.Collections.Generic; +using System.Numerics; using WindowsInput.Events; namespace ControllerCommon.Devices @@ -32,8 +33,8 @@ public OneXPlayerMiniIntel() : base() { 'Z', 'Y' }, }; - listeners.Add("Keyboard key", new ChordClick(KeyCode.LWin, KeyCode.RControlKey, KeyCode.O)); - listeners.Add("Function key", new ChordClick(KeyCode.LWin, KeyCode.D)); + listeners.Add("Keyboard key", new List() { KeyCode.LWin, KeyCode.RControlKey, KeyCode.O }); + listeners.Add("Function key", new List() { KeyCode.LWin, KeyCode.D }); } } } diff --git a/ControllerCommon/Processor/Processor.cs b/ControllerCommon/Processor/Processor.cs index 7fb376eae..10f22d8ad 100644 --- a/ControllerCommon/Processor/Processor.cs +++ b/ControllerCommon/Processor/Processor.cs @@ -287,6 +287,9 @@ protected override void UpdateTimer_Elapsed(object sender, ElapsedEventArgs e) public override void SetTDPLimit(string type, double limit) { + if (ry == IntPtr.Zero) + return; + // 15W : 15000 limit *= 1000; diff --git a/ControllerCommon/Profile.cs b/ControllerCommon/Profile.cs index 83dcee5d7..926ecd648 100644 --- a/ControllerCommon/Profile.cs +++ b/ControllerCommon/Profile.cs @@ -110,6 +110,7 @@ public Profile(string path) : this() this.executable = AppProperties["FileName"]; this.name = ProductName; this.path = this.fullpath = path; + this.isEnabled = false; } public float GetSensiviy() diff --git a/ControllerCommon/Resources/Default.json b/ControllerCommon/Resources/Default.json index 928d755ef..220dcaa58 100644 --- a/ControllerCommon/Resources/Default.json +++ b/ControllerCommon/Resources/Default.json @@ -2,5 +2,7 @@ "name": "Default", "path": "", "executable": "Default.exe", - "whitelisted": false + "isDefault": true, + "whitelisted": false, + "umc_enabled": false } \ No newline at end of file diff --git a/ControllerCommon/Utils/ProcessUtils.cs b/ControllerCommon/Utils/ProcessUtils.cs index 141df2fca..bd7b2b9ae 100644 --- a/ControllerCommon/Utils/ProcessUtils.cs +++ b/ControllerCommon/Utils/ProcessUtils.cs @@ -9,6 +9,8 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Text; +using System.Threading; +using System.Threading.Tasks; using System.Windows; using System.Windows.Interop; using System.Windows.Media; @@ -65,21 +67,23 @@ public static IntPtr GetforegroundWindow() public class FindHostedProcess { public ProcessDiagnosticInfo Process { get; private set; } + int attempt = 0; - public FindHostedProcess() + public FindHostedProcess(IntPtr foregroundProcessID) { try { - var foregroundProcessID = WinAPIFunctions.GetforegroundWindow(); - if (foregroundProcessID == IntPtr.Zero) return; Process = ProcessDiagnosticInfo.TryGetForProcessId((uint)WinAPIFunctions.GetWindowProcessId(foregroundProcessID)); // Get real process - if (Process.ExecutableFileName == "ApplicationFrameHost.exe") + while (Process.ExecutableFileName == "ApplicationFrameHost.exe" && attempt < 10) + { EnumChildWindows(foregroundProcessID, ChildWindowCallback, IntPtr.Zero); + Thread.Sleep(500); + } } catch (Exception) { @@ -94,6 +98,7 @@ private bool ChildWindowCallback(IntPtr hwnd, IntPtr lparam) if (process.ExecutableFileName != "ApplicationFrameHost.exe") Process = process; + attempt++; return true; } } @@ -111,6 +116,18 @@ public static string GetActiveWindowTitle() return null; } + public static string GetWindowTitle(IntPtr handle) + { + const int nChars = 256; + StringBuilder Buff = new StringBuilder(nChars); + + if (GetWindowText(handle, Buff, nChars) > 0) + { + return Buff.ToString(); + } + return null; + } + public static Dictionary GetAppProperties(string filePath1) { Dictionary AppProperties = new Dictionary(); diff --git a/ControllerService/ControllerService.cs b/ControllerService/ControllerService.cs index 2b5a95e6b..ae43102c2 100644 --- a/ControllerService/ControllerService.cs +++ b/ControllerService/ControllerService.cs @@ -428,6 +428,10 @@ private void OnClientConnected(object sender) internal void ProfileUpdated(Profile profile, bool backgroundtask) { + // skip if not enabled + if (!profile.isEnabled) + return; + // skip if current profile if (profile == ControllerService.profile) return; diff --git a/HandheldCompanion/HandheldCompanion.csproj b/HandheldCompanion/HandheldCompanion.csproj index 7782c4ed9..88ee08d6f 100644 --- a/HandheldCompanion/HandheldCompanion.csproj +++ b/HandheldCompanion/HandheldCompanion.csproj @@ -1084,6 +1084,9 @@ $(DefaultXamlRuntime) + + $(DefaultXamlRuntime) + $(DefaultXamlRuntime) diff --git a/HandheldCompanion/Managers/InputsManager.cs b/HandheldCompanion/Managers/InputsManager.cs index 169937a90..d3e0ac9c7 100644 --- a/HandheldCompanion/Managers/InputsManager.cs +++ b/HandheldCompanion/Managers/InputsManager.cs @@ -64,8 +64,8 @@ public InputsManager() UpdateTimer = new MultimediaTimer(10); UpdateTimer.Tick += UpdateReport; - ResetTimer = new MultimediaTimer(10) { AutoReset = false }; - ResetTimer.Tick += (sender, e) => { ReleaseBuffer(); }; + ResetTimer = new MultimediaTimer(20) { AutoReset = false }; + ResetTimer.Tick += ReleaseBuffer; m_GlobalHook = Hook.GlobalEvents(); m_InputSimulator = new InputSimulator(); @@ -86,30 +86,59 @@ private void InjectModifiers(KeyEventArgsExt args) } } + private int KeyIndex; + private bool KeyUsed; + private void M_GlobalHook_KeyEvent(object? sender, KeyEventArgs e) { ResetTimer.Stop(); + KeyUsed = false; if (TriggerLock) return; KeyEventArgsExt args = (KeyEventArgsExt)e; - args.SuppressKeyPress = true; - // search for modifiers (improve me) - TriggerBuffer.Add(args); + // todo: implement a key index and only catch the key if it's part of a chord at the specific index + // key0: suppress if is first key of any chords + // key0+n: always suppress. if is not n key of any chords, release buffer + foreach (List chord in MainWindow.handheldDevice.listeners.Values) + { + if (KeyIndex >= chord.Count) + continue; + + KeyCode chordKey = chord[KeyIndex]; + KeyCode hookKey = (KeyCode)args.KeyValue; + if (chordKey == hookKey) + { + KeyUsed = true; + KeyIndex++; + break; // leave loop + } + } + + // if key is used or previous key was, we need to maintain key(s) order + if (KeyUsed || KeyIndex > 0) + { + args.SuppressKeyPress = true; - if (args.IsKeyUp && args.IsExtendedKey) - InjectModifiers(args); + // add key to buffer + TriggerBuffer.Add(args); + + if (args.IsKeyUp && args.IsExtendedKey) + InjectModifiers(args); + } + else + return; // search for matching triggers foreach (var pair in MainWindow.handheldDevice.listeners) { string listener = pair.Key; - ChordClick chord = pair.Value; + List chord = pair.Value; // compare ordered enumerable - var chord_keys = chord.Keys.OrderBy(key => key); + var chord_keys = chord.OrderBy(key => key); var buffer_keys = GetBufferKeys().OrderBy(key => key); if (Enumerable.SequenceEqual(chord_keys, buffer_keys)) @@ -158,7 +187,7 @@ private void M_GlobalHook_KeyEvent(object? sender, KeyEventArgs e) } else { - TriggerInputs inputs = new TriggerInputs(TriggerInputsType.Keyboard, string.Join(",", chord.Keys), listener); + TriggerInputs inputs = new TriggerInputs(TriggerInputsType.Keyboard, string.Join(",", chord), listener); Triggers[TriggerListener] = inputs; if (args.IsKeyUp) @@ -167,7 +196,6 @@ private void M_GlobalHook_KeyEvent(object? sender, KeyEventArgs e) TriggerListener = string.Empty; } } - return; } } @@ -189,11 +217,14 @@ private string GetTriggerFromName(string name) return ""; } - private void ReleaseBuffer() + private void ReleaseBuffer(object? sender, EventArgs e) { if (TriggerBuffer.Count == 0) return; + // reset index + KeyIndex = 0; + try { TriggerLock = true; @@ -263,7 +294,7 @@ public void Start() foreach (var pair in MainWindow.handheldDevice.listeners) { string listener = pair.Key; - ChordClick chord = pair.Value; + List chord = pair.Value; prevKeyUp[listener] = TIME_BURST; prevKeyDown[listener] = TIME_BURST; diff --git a/HandheldCompanion/Managers/ProcessManager.cs b/HandheldCompanion/Managers/ProcessManager.cs index 64837c1f3..43b39e36f 100644 --- a/HandheldCompanion/Managers/ProcessManager.cs +++ b/HandheldCompanion/Managers/ProcessManager.cs @@ -12,6 +12,7 @@ using System.Threading; using System.Threading.Tasks; using System.Windows; +using System.Windows.Automation; using System.Windows.Controls; using System.Windows.Threading; using Windows.System.Diagnostics; @@ -46,12 +47,13 @@ public enum ShowWindowCommands : int #endregion public Process Process; + public IntPtr MainWindowHandle; + public uint Id; public string Name; public string Executable; public string Path; - private Timer Timer; private ThreadWaitReason threadWaitReason = ThreadWaitReason.UserRequest; // UI vars @@ -65,25 +67,10 @@ public enum ShowWindowCommands : int public ProcessEx(Process process) { this.Process = process; - this.Id = (uint)process.Id; - - Timer = new Timer(1000); - } - - public void Start() - { - Timer.Elapsed += Timer_Tick; - Timer.Start(); - } - - public void Stop() - { - Timer.Elapsed -= Timer_Tick; - Timer.Stop(); } - private void Timer_Tick(object? sender, EventArgs e) + public void Timer_Tick(object? sender, EventArgs e) { try { @@ -93,10 +80,15 @@ private void Timer_Tick(object? sender, EventArgs e) Application.Current.Dispatcher.Invoke(new Action(() => { - if (string.IsNullOrEmpty(Process.MainWindowTitle)) - processName.Text = Process.ProcessName; + if (MainWindowHandle != IntPtr.Zero) + { + processBorder.Visibility = Visibility.Visible; + string MainWindowTitle = ProcessUtils.GetWindowTitle(MainWindowHandle); + if (!string.IsNullOrEmpty(MainWindowTitle)) + processName.Text = MainWindowTitle; + } else - processName.Text = Process.MainWindowTitle; + processBorder.Visibility = Visibility.Collapsed; switch (processThread.ThreadState) { @@ -142,6 +134,7 @@ public void Draw() processBorder = new Border() { Padding = new Thickness(20, 12, 12, 12), + Visibility = Visibility.Collapsed, Tag = Name }; processBorder.SetResourceReference(Control.BackgroundProperty, "SystemControlBackgroundChromeMediumLowBrush"); @@ -246,40 +239,59 @@ public void Draw() processBorder.Child = processGrid; } - private async void ProcessResume_Click(object sender, RoutedEventArgs e) + private void ProcessResume_Click(object sender, RoutedEventArgs e) { Application.Current.Dispatcher.Invoke(new Action(() => { processResume.IsEnabled = false; + NtResumeProcess(Process.Handle); + Task.Delay(500); + ShowWindow(MainWindowHandle, 9); })); - - NtResumeProcess(Process.Handle); - await Task.Delay(500); - ShowWindow(Process.MainWindowHandle, 9); } - private async void ProcessSuspend_Click(object sender, RoutedEventArgs e) + private void ProcessSuspend_Click(object sender, RoutedEventArgs e) { Application.Current.Dispatcher.Invoke(new Action(() => { processSuspend.IsEnabled = false; + + ShowWindow(MainWindowHandle, 2); + Task.Delay(500); + NtSuspendProcess(Process.Handle); })); - - ShowWindow(Process.MainWindowHandle, 2); - await Task.Delay(500); - NtSuspendProcess(Process.Handle); } } public class ProcessManager { + #region imports + [DllImport("user32.dll", SetLastError = true)] + internal static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventProc lpfnWinEventProc, int idProcess, int idThread, uint dwflags); + [DllImport("user32.dll")] + internal static extern int UnhookWinEvent(IntPtr hWinEventHook); + internal delegate void WinEventProc(IntPtr hWinEventHook, uint iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime); + + const uint WINEVENT_OUTOFCONTEXT = 0; + const uint EVENT_SYSTEM_FOREGROUND = 3; + private IntPtr winHook; + private WinEventProc listener; + + public delegate bool WindowEnumCallback(IntPtr hwnd, int lparam); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool EnumWindows(WindowEnumCallback lpEnumFunc, int lParam); + + [DllImport("user32.dll")] + public static extern bool IsWindowVisible(int h); + #endregion + // process vars private Timer MonitorTimer; - private ManagementEventWatcher startWatch; private ManagementEventWatcher stopWatch; private ConcurrentDictionary CurrentProcesses = new(); - private uint CurrentProcess; private object updateLock = new(); private bool isRunning; @@ -294,25 +306,34 @@ public class ProcessManager public ProcessManager() { - startWatch = new ManagementEventWatcher(new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace")); - startWatch.EventArrived += new EventArrivedEventHandler(ProcessCreated); + Automation.AddAutomationEventHandler( + eventId: WindowPattern.WindowOpenedEvent, + element: AutomationElement.RootElement, + scope: TreeScope.Children, + eventHandler: OnWindowOpened); stopWatch = new ManagementEventWatcher(new WqlEventQuery("SELECT * FROM Win32_ProcessStopTrace")); stopWatch.EventArrived += new EventArrivedEventHandler(ProcessHalted); + + listener = new WinEventProc(EventCallback); } public void Start() { // list all current processes - ListProcess(); + new Thread(() => + { + Thread.CurrentThread.IsBackground = true; + EnumWindows(new WindowEnumCallback(AddWnd), 0); + }).Start(); // start processes monitor MonitorTimer = new Timer(1000); MonitorTimer.Elapsed += MonitorHelper; MonitorTimer.Start(); - startWatch.Start(); stopWatch.Start(); + winHook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, listener, 0, 0, WINEVENT_OUTOFCONTEXT); isRunning = true; } @@ -322,56 +343,78 @@ public void Stop() if (!isRunning) return; - foreach (ProcessEx processEx in CurrentProcesses.Values) - processEx.Stop(); - // stop processes monitor MonitorTimer.Elapsed -= MonitorHelper; MonitorTimer.Stop(); - startWatch.Stop(); stopWatch.Stop(); + Automation.RemoveAllEventHandlers(); + + UnhookWinEvent(winHook); } - private void ListProcess() + private void OnWindowOpened(object sender, AutomationEventArgs automationEventArgs) { - Process[] processCollection = Process.GetProcesses(); - foreach (Process proc in processCollection) - ProcessCreated(proc); + try + { + var element = sender as AutomationElement; + if (element != null) + { + IntPtr hWnd = (IntPtr)element.Current.NativeWindowHandle; + ProcessDiagnosticInfo processInfo = new ProcessUtils.FindHostedProcess(hWnd).Process; + + if (processInfo == null || processInfo.ExecutableFileName == "HandheldCompanion.exe") + return; + + Process proc = Process.GetProcessById((int)processInfo.ProcessId); + ProcessCreated(proc, element.Current.NativeWindowHandle); + } + } + catch (ElementNotAvailableException) + { + } } - private void MonitorHelper(object? sender, EventArgs e) + private bool AddWnd(IntPtr hWnd, int lparam) { - lock (updateLock) + if (IsWindowVisible((int)hWnd)) { - uint processId; - string exec = string.Empty; - string path = string.Empty; - string name = string.Empty; + ProcessDiagnosticInfo processInfo = new ProcessUtils.FindHostedProcess(hWnd).Process; - ProcessDiagnosticInfo process = new ProcessUtils.FindHostedProcess().Process; - if (process == null) - return; + Process proc = Process.GetProcessById((int)processInfo.ProcessId); + ProcessCreated(proc, (int)hWnd); + } + return true; + } - name = ProcessUtils.GetActiveWindowTitle(); - if (name == "Overlay") - return; + private void EventCallback(IntPtr hWinEventHook, uint iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime) + { + ProcessDiagnosticInfo processInfo = new ProcessUtils.FindHostedProcess(hWnd).Process; - processId = process.ProcessId; + if (processInfo == null || processInfo.ExecutableFileName == "HandheldCompanion.exe") + return; - if (processId != CurrentProcess) - { - if (CurrentProcesses.ContainsKey(processId)) - { - ProcessEx processEx = CurrentProcesses[processId]; - path = ProcessUtils.GetPathToApp(processEx.Process); + Process proc = Process.GetProcessById((int)processInfo.ProcessId); + string path = ProcessUtils.GetPathToApp(proc); + string exec = Path.GetFileName(path); + + ProcessEx processEx = new ProcessEx(proc) + { + Name = exec, + Executable = exec, + Path = path, + MainWindowHandle = hWnd + }; - LogManager.LogDebug("ActiveWindow Title: {0}, Path: {1}", name, path); + ForegroundChanged?.Invoke(processEx); + } - ForegroundChanged?.Invoke(processEx); - CurrentProcess = processId; - } - } + private void MonitorHelper(object? sender, EventArgs e) + { + lock (updateLock) + { + foreach (ProcessEx proc in CurrentProcesses.Values) + proc.Timer_Tick(sender, e); } } @@ -390,7 +433,6 @@ void ProcessHalted(uint processId) if (CurrentProcesses.ContainsKey(processId)) { ProcessEx processEx = CurrentProcesses[processId]; - processEx.Stop(); CurrentProcesses.TryRemove(new KeyValuePair(processId, processEx)); @@ -400,51 +442,47 @@ void ProcessHalted(uint processId) } } - private void ProcessCreated(object sender, EventArrivedEventArgs e) + private void ProcessCreated(Process proc, int NativeWindowHandle = 0) { try { - uint processId = (uint)e.NewEvent.Properties["ProcessID"].Value; - Process proc = Process.GetProcessById((int)processId); - ProcessCreated(proc); + // process has exited on arrival + if (proc.HasExited) + return; } - catch (Exception) { } - } - - private async void ProcessCreated(Process proc) - { - // breating - await Task.Delay(1000); - - // no main window - if (proc.MainWindowHandle == IntPtr.Zero) + catch(Exception) + { + // process has too high elevation return; - + } + string path = ProcessUtils.GetPathToApp(proc); // todo : implement proper filtering if (string.IsNullOrEmpty(path)) return; - if (path.ToLower().Contains(Environment.GetEnvironmentVariable("windir").ToLower())) + if (path.Contains(Environment.GetEnvironmentVariable("windir"), StringComparison.InvariantCultureIgnoreCase)) return; string exec = Path.GetFileName(path); - ProcessEx processEx = new ProcessEx(proc) + if (!CurrentProcesses.ContainsKey((uint)proc.Id)) { - Name = exec, - Executable = exec, - Path = path - }; - processEx.Start(); + ProcessEx processEx = new ProcessEx(proc) + { + Name = exec, + Executable = exec, + Path = path, + MainWindowHandle = NativeWindowHandle != 0 ? (IntPtr)NativeWindowHandle : proc.MainWindowHandle + }; - if (!CurrentProcesses.ContainsKey(processEx.Id)) CurrentProcesses.TryAdd(processEx.Id, processEx); - ProcessStarted?.Invoke(processEx); + ProcessStarted?.Invoke(processEx); - LogManager.LogDebug("Process created: {0}", proc.ProcessName); + LogManager.LogDebug("Process created: {0}", proc.ProcessName); + } } } } diff --git a/HandheldCompanion/Views/Pages/ProfilesPage.xaml b/HandheldCompanion/Views/Pages/ProfilesPage.xaml index 64b89084b..1831c27a6 100644 --- a/HandheldCompanion/Views/Pages/ProfilesPage.xaml +++ b/HandheldCompanion/Views/Pages/ProfilesPage.xaml @@ -80,7 +80,7 @@ - + @@ -161,11 +161,11 @@ - + - + @@ -190,7 +190,7 @@ - + @@ -290,7 +290,7 @@ - + @@ -299,7 +299,7 @@ - + diff --git a/HandheldCompanion/Views/Pages/ProfilesPage.xaml.cs b/HandheldCompanion/Views/Pages/ProfilesPage.xaml.cs index caa839c04..feb64fbb0 100644 --- a/HandheldCompanion/Views/Pages/ProfilesPage.xaml.cs +++ b/HandheldCompanion/Views/Pages/ProfilesPage.xaml.cs @@ -35,12 +35,11 @@ public ProfilesPage(string Tag) : this() MainWindow.pipeClient.ServerMessage += OnServerMessage; - // initialize Profile Manager MainWindow.profileManager.Deleted += ProfileDeleted; MainWindow.profileManager.Updated += ProfileUpdated; MainWindow.profileManager.Loaded += ProfileLoaded; - // draw buttons + // draw gamepad activators foreach (GamepadButtonFlagsExt button in (GamepadButtonFlagsExt[])Enum.GetValues(typeof(GamepadButtonFlagsExt))) { // create panel @@ -64,6 +63,7 @@ public ProfilesPage(string Tag) : this() activators.Add(button, checkbox); } + // draw input modes foreach (Input mode in (Input[])Enum.GetValues(typeof(Input))) { // create panel @@ -97,6 +97,7 @@ public ProfilesPage(string Tag) : this() cB_Input.Items.Add(panel); } + // draw output modes foreach (Output mode in (Output[])Enum.GetValues(typeof(Output))) { // create panel @@ -144,10 +145,6 @@ public void Page_Closed() #region UI public void ProfileUpdated(Profile profile, bool backgroundtask) { - // inform Service we have a new default profile - if (profile.isDefault) - MainWindow.pipeClient?.SendMessage(new PipeClientProfile() { profile = profile }); - this.Dispatcher.Invoke(async () => { int idx = -1; @@ -307,42 +304,49 @@ private void cB_Profiles_SelectionChanged(object sender, SelectionChangedEventAr return; profileCurrent = (Profile)cB_Profiles.SelectedItem; - UpdateSelectedProfile(); + DrawProfile(); } - private void UpdateSelectedProfile() + private void DrawProfile() { if (profileCurrent == null) return; Dispatcher.BeginInvoke(() => { + // enable all expanders + ProfileDetails.IsEnabled = true; + GlobalSettings.IsEnabled = true; + MotionSettings.IsEnabled = true; + UniversalSettings.IsEnabled = true; + // disable button if is default profile b_DeleteProfile.IsEnabled = !profileCurrent.isDefault; + // prevent user from renaming default profile tB_ProfileName.IsEnabled = !profileCurrent.isDefault; - GlobalSettings.IsEnabled = GlobalDetails.IsEnabled = profileCurrent.error != ProfileErrorCode.MissingPermission; - b_ApplyProfile.IsEnabled = profileCurrent.error != ProfileErrorCode.MissingPermission; - b_ApplyProfile.ToolTip = b_ApplyProfile.IsEnabled == false ? Properties.Resources.WarningElevated : null; - - // populate controls + // Profile info tB_ProfileName.Text = profileCurrent.name; tB_ProfilePath.Text = profileCurrent.fullpath; - - Toggle_EnableProfile.IsEnabled = !profileCurrent.isDefault; Toggle_EnableProfile.IsOn = profileCurrent.isEnabled; - Toggle_UniversalMotion.IsOn = profileCurrent.umc_enabled; + // Global settings + cB_Whitelist.IsChecked = profileCurrent.whitelisted; + cB_Wrapper.IsChecked = profileCurrent.use_wrapper; + + // Motion control settings tb_ProfileGyroValue.Value = profileCurrent.gyrometer; tb_ProfileAcceleroValue.Value = profileCurrent.accelerometer; - tb_ProfileAntiDeadzone.Value = profileCurrent.antideadzone; + cB_GyroSteering.SelectedIndex = profileCurrent.steering; - cB_InvertVertical.IsChecked = profileCurrent.invertvertical; cB_InvertHorizontal.IsChecked = profileCurrent.inverthorizontal; + cB_InvertVertical.IsChecked = profileCurrent.invertvertical; + + // UMC settings + Toggle_UniversalMotion.IsOn = profileCurrent.umc_enabled; cB_Input.SelectedIndex = (int)profileCurrent.umc_input; cB_Output.SelectedIndex = (int)profileCurrent.umc_output; - cB_Whitelist.IsChecked = profileCurrent.whitelisted; - cB_Wrapper.IsChecked = profileCurrent.use_wrapper; + tb_ProfileAntiDeadzone.Value = profileCurrent.antideadzone; foreach (GamepadButtonFlagsExt button in (GamepadButtonFlagsExt[])Enum.GetValues(typeof(GamepadButtonFlagsExt))) if (profileCurrent.umc_trigger.HasFlag(button)) @@ -416,25 +420,28 @@ private void b_ApplyProfile_Click(object sender, RoutedEventArgs e) $"{profileCurrent.name} {Properties.Resources.ProfilesPage_ProfileUpdated2}", ContentDialogButton.Primary, null, $"{Properties.Resources.ProfilesPage_OK}"); + // Profile profileCurrent.name = tB_ProfileName.Text; profileCurrent.fullpath = tB_ProfilePath.Text; profileCurrent.isEnabled = (bool)Toggle_EnableProfile.IsOn; - profileCurrent.gyrometer = (float)tb_ProfileGyroValue.Value; - profileCurrent.accelerometer = (float)tb_ProfileAcceleroValue.Value; - profileCurrent.antideadzone = (float)tb_ProfileAntiDeadzone.Value; + // Global settings profileCurrent.whitelisted = (bool)cB_Whitelist.IsChecked; profileCurrent.use_wrapper = (bool)cB_Wrapper.IsChecked; - profileCurrent.steering = cB_GyroSteering.SelectedIndex; + // Motion control settings + profileCurrent.gyrometer = (float)tb_ProfileGyroValue.Value; + profileCurrent.accelerometer = (float)tb_ProfileAcceleroValue.Value; + profileCurrent.steering = cB_GyroSteering.SelectedIndex; profileCurrent.invertvertical = (bool)cB_InvertVertical.IsChecked; profileCurrent.inverthorizontal = (bool)cB_InvertHorizontal.IsChecked; + // UMC settings profileCurrent.umc_enabled = (bool)Toggle_UniversalMotion.IsOn; - profileCurrent.umc_input = (Input)cB_Input.SelectedIndex; profileCurrent.umc_output = (Output)cB_Output.SelectedIndex; + profileCurrent.antideadzone = (float)tb_ProfileAntiDeadzone.Value; profileCurrent.umc_trigger = 0; @@ -442,14 +449,17 @@ private void b_ApplyProfile_Click(object sender, RoutedEventArgs e) if ((bool)activators[button].IsChecked) profileCurrent.umc_trigger |= button; - MainWindow.profileManager.profiles[profileCurrent.name] = profileCurrent; MainWindow.profileManager.UpdateOrCreateProfile(profileCurrent, false); MainWindow.profileManager.SerializeProfile(profileCurrent); + + // inform service + MainWindow.pipeClient.SendMessage(new PipeClientProfile { profile = profileCurrent }); } private void cB_Whitelist_Checked(object sender, RoutedEventArgs e) { - Expander_UMC.IsEnabled = (bool)!cB_Whitelist.IsChecked; + // todo : move me to WPF + UniversalSettings.IsEnabled = (bool)!cB_Whitelist.IsChecked; } private void cB_Overlay_Checked(object sender, RoutedEventArgs e) diff --git a/HandheldCompanion/Views/QuickPages/PerformancePage.xaml b/HandheldCompanion/Views/QuickPages/QuickPerformancePage.xaml similarity index 93% rename from HandheldCompanion/Views/QuickPages/PerformancePage.xaml rename to HandheldCompanion/Views/QuickPages/QuickPerformancePage.xaml index 1900c7bc1..257cd635a 100644 --- a/HandheldCompanion/Views/QuickPages/PerformancePage.xaml +++ b/HandheldCompanion/Views/QuickPages/QuickPerformancePage.xaml @@ -1,4 +1,4 @@ - - + @@ -49,8 +49,8 @@ - - + @@ -70,7 +70,7 @@ - + @@ -95,7 +95,8 @@ - + @@ -114,8 +115,8 @@ - - + + diff --git a/HandheldCompanion/Views/QuickPages/PerformancePage.xaml.cs b/HandheldCompanion/Views/QuickPages/QuickPerformancePage.xaml.cs similarity index 97% rename from HandheldCompanion/Views/QuickPages/PerformancePage.xaml.cs rename to HandheldCompanion/Views/QuickPages/QuickPerformancePage.xaml.cs index 432bace80..8a4146da9 100644 --- a/HandheldCompanion/Views/QuickPages/PerformancePage.xaml.cs +++ b/HandheldCompanion/Views/QuickPages/QuickPerformancePage.xaml.cs @@ -19,16 +19,16 @@ namespace HandheldCompanion.Views.QuickPages { /// - /// Interaction logic for PerformancePage.xaml + /// Interaction logic for QuickPerformancePage.xaml /// - public partial class PerformancePage : Page + public partial class QuickPerformancePage : Page { private Timer cpuTimer; private Timer gpuTimer; private bool Initialized; - public PerformancePage() + public QuickPerformancePage() { InitializeComponent(); Initialized = true; diff --git a/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml b/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml new file mode 100644 index 000000000..7d2d70ac6 --- /dev/null +++ b/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml.cs b/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml.cs new file mode 100644 index 000000000..b976dced2 --- /dev/null +++ b/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml.cs @@ -0,0 +1,209 @@ +using HandheldCompanion.Managers; +using HandheldCompanion.Views.Windows; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using CoreAudio; +using System; +using ControllerCommon.Utils; +using ControllerCommon; +using ModernWpf.Controls; +using Page = System.Windows.Controls.Page; + +namespace HandheldCompanion.Views.QuickPages +{ + /// + /// Interaction logic for QuickProfilesPage.xaml + /// + public partial class QuickProfilesPage : Page + { + private bool Initialized; + private bool IgnoreMe; + private Profile currentProfile; + + public QuickProfilesPage() + { + InitializeComponent(); + Initialized = true; + + MainWindow.processManager.ForegroundChanged += ProcessManager_ForegroundChanged; + MainWindow.profileManager.Updated += ProfileUpdated; + + foreach (Input mode in (Input[])Enum.GetValues(typeof(Input))) + { + // create panel + SimpleStackPanel panel = new SimpleStackPanel() { Spacing = 6, Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center }; + + // create icon + FontIcon icon = new FontIcon() { Glyph = "" }; + + switch (mode) + { + default: + case Input.PlayerSpace: + icon.Glyph = "\uF119"; + break; + case Input.JoystickCamera: + icon.Glyph = "\uE714"; + break; + case Input.JoystickSteering: + icon.Glyph = "\uEC47"; + break; + } + + if (icon.Glyph != "") + panel.Children.Add(icon); + + // create textblock + string description = EnumUtils.GetDescriptionFromEnumValue(mode); + TextBlock text = new TextBlock() { Text = description }; + panel.Children.Add(text); + + cB_Input.Items.Add(panel); + } + + foreach (Output mode in (Output[])Enum.GetValues(typeof(Output))) + { + // create panel + SimpleStackPanel panel = new SimpleStackPanel() { Spacing = 6, Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center }; + + // create icon + FontIcon icon = new FontIcon() { Glyph = "" }; + + switch (mode) + { + default: + case Output.RightStick: + icon.Glyph = "\uF109"; + break; + case Output.LeftStick: + icon.Glyph = "\uF108"; + break; + } + + if (icon.Glyph != "") + panel.Children.Add(icon); + + // create textblock + string description = EnumUtils.GetDescriptionFromEnumValue(mode); + TextBlock text = new TextBlock() { Text = description }; + panel.Children.Add(text); + + cB_Output.Items.Add(panel); + } + } + + private void ProfileUpdated(Profile profile, bool backgroundtask) + { + if (backgroundtask) + return; + + if (profile.executable != currentProfile.executable) + return; + + this.Dispatcher.Invoke(() => + { + IgnoreMe = true; + + ProfileName.Text = currentProfile.name; + ProfilePath.Text = currentProfile.path; + + ProfileToggle.IsEnabled = true; + ProfileToggle.IsOn = currentProfile.isEnabled; + UMCToggle.IsOn = currentProfile.umc_enabled; + cB_Input.SelectedIndex = (int)currentProfile.umc_input; + cB_Output.SelectedIndex = (int)currentProfile.umc_output; + + IgnoreMe = false; + }); + } + + private void ProcessManager_ForegroundChanged(ProcessEx processEx) + { + currentProfile = MainWindow.profileManager.GetProfileFromExec(processEx.Name); + + if (currentProfile == null) + currentProfile = new Profile(processEx.Path); + + ProfileUpdated(currentProfile, false); + } + + private void Scrolllock_MouseEnter(object sender, MouseEventArgs e) + { + QuickTools.scrollLock = true; + } + + private void Scrolllock_MouseLeave(object sender, MouseEventArgs e) + { + QuickTools.scrollLock = false; + } + + private void SaveProfile() + { + if (currentProfile is null || IgnoreMe) + return; + + MainWindow.profileManager.UpdateOrCreateProfile(currentProfile, false); + MainWindow.profileManager.SerializeProfile(currentProfile); + + // inform service + MainWindow.pipeClient.SendMessage(new PipeClientProfile { profile = currentProfile }); + } + + private void ProfileToggle_Toggled(object sender, RoutedEventArgs e) + { + if (currentProfile is null || IgnoreMe) + return; + + currentProfile.isEnabled = ProfileToggle.IsOn; + SaveProfile(); + } + + private void UMCToggle_Toggled(object sender, RoutedEventArgs e) + { + if (currentProfile is null || IgnoreMe) + return; + + currentProfile.umc_enabled = UMCToggle.IsOn; + SaveProfile(); + } + + private void cB_Input_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (cB_Input.SelectedIndex == -1) + return; + + Input input = (Input)cB_Input.SelectedIndex; + + // Check which input type is selected and automatically + // set the most used output joystick accordingly. + switch (input) + { + case Input.PlayerSpace: + case Input.JoystickCamera: + cB_Output.SelectedIndex = (int)Output.RightStick; + break; + case Input.JoystickSteering: + cB_Output.SelectedIndex = (int)Output.LeftStick; + break; + } + + Text_InputHint.Text = Profile.InputDescription[input]; + + if (currentProfile is null || IgnoreMe) + return; + + currentProfile.umc_input = (Input)cB_Input.SelectedIndex; + SaveProfile(); + } + + private void cB_Output_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (currentProfile is null || IgnoreMe) + return; + + currentProfile.umc_output = (Output)cB_Output.SelectedIndex; + SaveProfile(); + } + } +} diff --git a/HandheldCompanion/Views/QuickPages/QuickSettingsPage.xaml b/HandheldCompanion/Views/QuickPages/QuickSettingsPage.xaml index d9e445803..1bbe87006 100644 --- a/HandheldCompanion/Views/QuickPages/QuickSettingsPage.xaml +++ b/HandheldCompanion/Views/QuickPages/QuickSettingsPage.xaml @@ -23,7 +23,7 @@ - + @@ -51,7 +51,7 @@ - + diff --git a/HandheldCompanion/Views/Windows/MainWindow.xaml.cs b/HandheldCompanion/Views/Windows/MainWindow.xaml.cs index 6223e6736..819fd6c00 100644 --- a/HandheldCompanion/Views/Windows/MainWindow.xaml.cs +++ b/HandheldCompanion/Views/Windows/MainWindow.xaml.cs @@ -352,7 +352,7 @@ private void ProcessManager_ProcessStopped(ProcessEx processEx) if (currentProfile == null) return; - currentProfile.fullpath = processEx.Executable; + currentProfile.fullpath = processEx.Path; currentProfile.isApplied = false; // update profile and inform settings page @@ -370,7 +370,7 @@ private void ProcessManager_ProcessStarted(ProcessEx processEx) if (currentProfile == null) return; - currentProfile.fullpath = processEx.Executable; + currentProfile.fullpath = processEx.Path; currentProfile.isApplied = true; // update profile and inform settings page @@ -396,9 +396,6 @@ private void ProcessManager_ForegroundChanged(ProcessEx processEx) profile.isApplied = true; - // inform service - pipeClient.SendMessage(new PipeClientProfile { profile = profile }); - // update current profile profileManager.CurrentProfile = profile; @@ -408,7 +405,7 @@ private void ProcessManager_ForegroundChanged(ProcessEx processEx) if (profile.isDefault) return; - profile.fullpath = processEx.Executable; + profile.fullpath = processEx.Path; profileManager.UpdateOrCreateProfile(profile); } catch (Exception) { } diff --git a/HandheldCompanion/Views/Windows/QuickTools.xaml b/HandheldCompanion/Views/Windows/QuickTools.xaml index eaa4a40d6..2f939f1c0 100644 --- a/HandheldCompanion/Views/Windows/QuickTools.xaml +++ b/HandheldCompanion/Views/Windows/QuickTools.xaml @@ -28,38 +28,21 @@ Width="400"> - - - - - - - - - - - - - - - - + - - + - - + - + diff --git a/HandheldCompanion/Views/Windows/QuickTools.xaml.cs b/HandheldCompanion/Views/Windows/QuickTools.xaml.cs index f19d44608..e7cda2812 100644 --- a/HandheldCompanion/Views/Windows/QuickTools.xaml.cs +++ b/HandheldCompanion/Views/Windows/QuickTools.xaml.cs @@ -26,8 +26,9 @@ public partial class QuickTools : Window private Dictionary _pages = new(); private string preNavItemTag; - public PerformancePage performancePage; + public QuickPerformancePage performancePage; public QuickSettingsPage settingsPage; + public QuickProfilesPage profilesPage; // touchscroll vars Point scrollPoint = new Point(); @@ -47,11 +48,13 @@ public QuickTools() brightnessControl = new(); // create pages - performancePage = new PerformancePage(); + performancePage = new QuickPerformancePage(); settingsPage = new QuickSettingsPage(); + profilesPage = new QuickProfilesPage(); - _pages.Add("PerformancePage", performancePage); + _pages.Add("QuickPerformancePage", performancePage); _pages.Add("QuickSettingsPage", settingsPage); + _pages.Add("QuickProfilesPage", profilesPage); // start manager(s) powerManager.Start(); @@ -142,7 +145,7 @@ private void navView_Loaded(object sender, RoutedEventArgs e) // If navigation occurs on SelectionChanged, this isn't needed. // Because we use ItemInvoked to navigate, we need to call Navigate // here to load the home page. - NavView_Navigate("PerformancePage"); + NavView_Navigate("QuickPerformancePage"); } private void navView_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) diff --git a/dependencies/ModernWpf.Controls.dll b/dependencies/ModernWpf.Controls.dll index 49c56cfef..b9d0e45d4 100644 Binary files a/dependencies/ModernWpf.Controls.dll and b/dependencies/ModernWpf.Controls.dll differ diff --git a/dependencies/ModernWpf.MahApps.dll b/dependencies/ModernWpf.MahApps.dll index fda47fdb0..704acd926 100644 Binary files a/dependencies/ModernWpf.MahApps.dll and b/dependencies/ModernWpf.MahApps.dll differ diff --git a/dependencies/ModernWpf.dll b/dependencies/ModernWpf.dll index fa83c3dca..deb546544 100644 Binary files a/dependencies/ModernWpf.dll and b/dependencies/ModernWpf.dll differ