1
0
Fork 0
mirror of https://github.com/hawkeye-stan/msfs-popout-panel-manager.git synced 2024-11-21 21:30:12 +00:00

Started development v3.4.1

This commit is contained in:
Stanley 2022-07-25 08:40:21 -04:00
commit f9ebd4bf3f
10 changed files with 245 additions and 116 deletions

View file

@ -4,8 +4,6 @@ using MSFSPopoutPanelManager.WindowsAgent;
using System; using System;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MSFSPopoutPanelManager.Orchestration namespace MSFSPopoutPanelManager.Orchestration
{ {
@ -13,11 +11,7 @@ namespace MSFSPopoutPanelManager.Orchestration
{ {
private static PInvoke.WinEventProc _winEvent; // keep this as static to prevent garbage collect or the app will crash private static PInvoke.WinEventProc _winEvent; // keep this as static to prevent garbage collect or the app will crash
private static IntPtr _winEventHook; private static IntPtr _winEventHook;
private uint _prevWinEvent = PInvokeConstant.EVENT_SYSTEM_CAPTUREEND;
private int _winEventClickLock = 0;
private Rectangle _lastWindowRectangle; private Rectangle _lastWindowRectangle;
private object _hookLock = new object();
private bool _isHookMouseDown = false;
public PanelConfigurationOrchestrator() public PanelConfigurationOrchestrator()
{ {
@ -36,11 +30,20 @@ namespace MSFSPopoutPanelManager.Orchestration
public void StartConfiguration() public void StartConfiguration()
{ {
HookWinEvent(); HookWinEvent();
TouchEventManager.ActiveProfile = ProfileData.ActiveProfile;
TouchEventManager.AppSetting = AppSettingData.AppSetting;
if (ActiveProfile.PanelConfigs.Any(p => p.TouchEnabled) && !TouchEventManager.IsHooked)
{
TouchEventManager.Hook();
}
} }
public void EndConfiguration() public void EndConfiguration()
{ {
UnhookWinEvent(); UnhookWinEvent();
TouchEventManager.UnHook();
} }
public void LockStatusUpdated() public void LockStatusUpdated()
@ -99,6 +102,12 @@ namespace MSFSPopoutPanelManager.Orchestration
case PanelConfigPropertyName.HideTitlebar: case PanelConfigPropertyName.HideTitlebar:
WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, panelConfig.HideTitlebar); WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, panelConfig.HideTitlebar);
break; break;
case PanelConfigPropertyName.TouchEnabled:
if (ActiveProfile.PanelConfigs.Any(p => p.TouchEnabled) && !TouchEventManager.IsHooked)
TouchEventManager.Hook();
else if (ActiveProfile.PanelConfigs.All(p => !p.TouchEnabled) && TouchEventManager.IsHooked)
TouchEventManager.UnHook();
break;
} }
} }
@ -166,7 +175,7 @@ namespace MSFSPopoutPanelManager.Orchestration
private void HookWinEvent() private void HookWinEvent()
{ {
// Setup panel config event hooks // Setup panel config event hooks
_winEventHook = PInvoke.SetWinEventHook(PInvokeConstant.EVENT_SYSTEM_CAPTURESTART, PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE, IntPtr.Zero, _winEvent, 0, 0, PInvokeConstant.WINEVENT_OUTOFCONTEXT); _winEventHook = PInvoke.SetWinEventHook(PInvokeConstant.EVENT_SYSTEM_MOVESIZEEND, PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE, IntPtr.Zero, _winEvent, 0, 0, PInvokeConstant.WINEVENT_OUTOFCONTEXT);
} }
private void UnhookWinEvent() private void UnhookWinEvent()
@ -231,21 +240,6 @@ namespace MSFSPopoutPanelManager.Orchestration
PInvoke.ShowWindow(hwnd, PInvokeConstant.SW_RESTORE); PInvoke.ShowWindow(hwnd, PInvokeConstant.SW_RESTORE);
} }
break; break;
case PInvokeConstant.EVENT_SYSTEM_CAPTURESTART:
if (!panelConfig.HasTouchableEvent || _prevWinEvent == PInvokeConstant.EVENT_SYSTEM_CAPTURESTART)
break;
HandleTouchDownEvent(panelConfig);
break;
case PInvokeConstant.EVENT_SYSTEM_CAPTUREEND:
if (!panelConfig.TouchEnabled || _prevWinEvent == PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE)
break;
//if (!panelConfig.HasTouchableEvent || _prevWinEvent == PInvokeConstant.EVENT_SYSTEM_CAPTUREEND)
// break;
HandleTouchUpEvent(panelConfig);
break;
} }
} }
else else
@ -290,97 +284,8 @@ namespace MSFSPopoutPanelManager.Orchestration
case PInvokeConstant.EVENT_SYSTEM_MOVESIZEEND: case PInvokeConstant.EVENT_SYSTEM_MOVESIZEEND:
ProfileData.WriteProfiles(); ProfileData.WriteProfiles();
break; break;
case PInvokeConstant.EVENT_SYSTEM_CAPTURESTART:
if (!panelConfig.HasTouchableEvent || _prevWinEvent == PInvokeConstant.EVENT_SYSTEM_CAPTURESTART)
break;
HandleTouchDownEvent(panelConfig);
break;
case PInvokeConstant.EVENT_SYSTEM_CAPTUREEND:
if (!panelConfig.TouchEnabled || _prevWinEvent == PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE)
break;
//if (!panelConfig.HasTouchableEvent || _prevWinEvent == PInvokeConstant.EVENT_SYSTEM_CAPTUREEND)
// break;
HandleTouchUpEvent(panelConfig);
break;
}
}
_prevWinEvent = iEvent;
}
private void HandleTouchDownEvent(PanelConfig panelConfig)
{
if (!_isHookMouseDown)
{
lock (_hookLock)
{
Point point;
PInvoke.GetCursorPos(out point);
// Disable left clicking if user is touching the title bar area
if (point.Y - panelConfig.Top > (panelConfig.HideTitlebar ? 5 : 31))
{
_isHookMouseDown = true;
InputEmulationManager.LeftClickMouseDown(point.X, point.Y);
//Debug.WriteLine($"Mouse Down {_pair}");
}
}
}
}
private void HandleTouchUpEvent(PanelConfig panelConfig)
{
if (_isHookMouseDown)
{
Thread.Sleep(AppSettingData.AppSetting.TouchScreenSettings.MouseUpDownDelay);
lock (_hookLock)
{
_isHookMouseDown = false;
UnhookWinEvent();
Point point;
PInvoke.GetCursorPos(out point);
// Disable left clicking if user is touching the title bar area
if (point.Y - panelConfig.Top > (panelConfig.HideTitlebar ? 5 : 31))
{
InputEmulationManager.LeftClickMouseUp(0, 0);
HookWinEvent();
// Debug.WriteLine($"Mouse Up {_pair}");
//_pair++;
var prevWinEventClickLock = ++_winEventClickLock;
if (prevWinEventClickLock == _winEventClickLock && AppSettingData.AppSetting.TouchScreenSettings.RefocusGameWindow)
{
Task.Run(() => RefocusMsfs(prevWinEventClickLock));
} }
} }
} }
} }
} }
private void RefocusMsfs(int prevWinEventClickLock)
{
Thread.Sleep(1000);
if (prevWinEventClickLock == _winEventClickLock)
{
if (!_isHookMouseDown)
{
var simulatorProcess = WindowProcessManager.GetSimulatorProcess();
var rectangle = WindowActionManager.GetWindowRect(simulatorProcess.Handle);
var clientRectangle = WindowActionManager.GetClientRect(simulatorProcess.Handle);
PInvoke.SetCursorPos(rectangle.X + clientRectangle.Width / 2, rectangle.Y + clientRectangle.Height / 2);
}
}
}
}
}

View file

@ -141,7 +141,10 @@ namespace MSFSPopoutPanelManager.Orchestration
if (uProfile != null && uProfile.ProfileId != ActiveProfile.ProfileId) if (uProfile != null && uProfile.ProfileId != ActiveProfile.ProfileId)
return false; return false;
return !ActiveProfile.BindingAircraftLiveries.Any(p => p == FlightSimData.CurrentMsfsPlaneTitle); if (FlightSimData == null || ActiveProfile.BindingAircraftLiveries == null)
return false;
return ActiveProfile == null ? false : !ActiveProfile.BindingAircraftLiveries.Any(p => p == FlightSimData.CurrentMsfsPlaneTitle);
} }
} }

View file

@ -26,6 +26,8 @@ Things that work:
* Works on touch monitor or tablet with SpaceDesk * Works on touch monitor or tablet with SpaceDesk
Know issues and workarounds: Know issues and workarounds:
* (Reported July 24th) - When trying to click and drag a map on touch enabled panel on tablet using SpaceDesk or Duet, touch is not responsive. I'm active working on a fix or implement a better solution for touch and drag.
* When setting a panel to be touch enabled, please refrain form using a mouse to operate the panel since it is going to drive you insane. In the application, when no touch (click) action occurs within 1 second after the last touch action, the mouse cursor is going to jump back into the game window in order to give flight control back to the user. If you're using a mouse to interact with a panel that is designated as touch enabled, your mouse cursor is going to jump around. You can turn off game refocus setting but then you'll lose control of the plane as soon as you interact with a pop out panel. I believe the lost of flight control is on Asobo bug list waiting to be fixed. * When setting a panel to be touch enabled, please refrain form using a mouse to operate the panel since it is going to drive you insane. In the application, when no touch (click) action occurs within 1 second after the last touch action, the mouse cursor is going to jump back into the game window in order to give flight control back to the user. If you're using a mouse to interact with a panel that is designated as touch enabled, your mouse cursor is going to jump around. You can turn off game refocus setting but then you'll lose control of the plane as soon as you interact with a pop out panel. I believe the lost of flight control is on Asobo bug list waiting to be fixed.
* Panels are designed in the game for mouse interaction which means it has a built-in assumption for slow-er mouse click response. But using finger to touch is definitely much faster than using a mouse and some panel UI will not be able to keep up with the speed of touch entry if you send touch input too fast. When you encounter UI lag in response or button click not responding to your touch input, please slow down your touch input speed a bit and let the panel catch up. (On my to-do list for improvement.) * Panels are designed in the game for mouse interaction which means it has a built-in assumption for slow-er mouse click response. But using finger to touch is definitely much faster than using a mouse and some panel UI will not be able to keep up with the speed of touch entry if you send touch input too fast. When you encounter UI lag in response or button click not responding to your touch input, please slow down your touch input speed a bit and let the panel catch up. (On my to-do list for improvement.)

View file

@ -1,6 +1,15 @@
# Version History # Version History
<hr/> <hr/>
## Version 3.4.1
* Button responsiveness
* Full screen mode works now
* Panning works naturally
* Adjustable getting flight control back almost instantaneous (0.5sec)
* Fixed scroll bar issue. Just scroll like you normally would
Known issue:
* After map scrolling is completed, when try to touch the map again to retarget the crosshair (in GTN 750) or just focus on a point in the map (Other touch panel), the target point may jump off screen. Right now, I'm unable to resolve this since MSFS code keeps track of internal coordinates and I'm unable to override it. Need more investigation to work around this issue. In the mean time, just touch the map again to recenter the crosshair.
## Version 3.4 ## Version 3.4
* Changed where user data files are stored. Previously, the files are saved in subfolder "userdata" in your installation directory. Now they're moved to your Windows "Documents" folder under "MSFS Pop Out Panel Manager" for easy access. When you first start the application, a data migration step will occur and your user data files will be upgraded and moved to this new folder location. This change will allow you to install Pop Out Manager to folder of your choice on your machine since the application no longer requires write access to your installation folder. * Changed where user data files are stored. Previously, the files are saved in subfolder "userdata" in your installation directory. Now they're moved to your Windows "Documents" folder under "MSFS Pop Out Panel Manager" for easy access. When you first start the application, a data migration step will occur and your user data files will be upgraded and moved to this new folder location. This change will allow you to install Pop Out Manager to folder of your choice on your machine since the application no longer requires write access to your installation folder.

View file

@ -147,6 +147,20 @@ namespace MSFSPopoutPanelManager.WindowsAgent
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern int UnhookWinEvent(IntPtr hWinEventHook); public static extern int UnhookWinEvent(IntPtr hWinEventHook);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(HookType hookType, WindowsHookExProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool UnhookWindowsHookEx(IntPtr hook);
[DllImport("user32.dll", SetLastError = true)]
public static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
public delegate int WindowsHookExProc(int code, IntPtr wParam, IntPtr lParam);
public delegate bool CallBack(IntPtr hwnd, int lParam); public delegate bool CallBack(IntPtr hwnd, int lParam);
public delegate void WinEventProc(IntPtr hWinEventHook, uint iEvent, IntPtr hwnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime); public delegate void WinEventProc(IntPtr hWinEventHook, uint iEvent, IntPtr hwnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime);
@ -163,4 +177,21 @@ namespace MSFSPopoutPanelManager.WindowsAgent
public Rectangle rcNormalPosition; public Rectangle rcNormalPosition;
public Rectangle rcDevice; public Rectangle rcDevice;
} }
[StructLayout(LayoutKind.Sequential)]
public struct MSLLHOOKSTRUCT
{
public Point pt;
public int mouseData;
public int flags;
public int time;
public UIntPtr dwExtraInfo;
}
public enum HookType : int
{
WH_GETMESSAGE = 3,
WH_MOUSE = 7,
WH_MOUSE_LL = 14
}
} }

View file

@ -0,0 +1,175 @@
using MSFSPopoutPanelManager.UserDataAgent;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace MSFSPopoutPanelManager.WindowsAgent
{
public class TouchEventManager
{
private static WindowProcess _simulatorProcess;
private static IntPtr _hHook = IntPtr.Zero;
private static PInvoke.WindowsHookExProc callbackDelegate = HookCallBack;
private static bool _isTouchDown;
private static bool _isMouseMoveBlock = false;
private static object _mouseTouchLock = new object();
private static int _mouseMoveUnblockCount = 0;
private static int _mouseMoveCount = 0;
private static bool _touchUsePreClick = false;
private static Point _blockPoint;
private const int PANEL_MENUBAR_HEIGHT = 31;
private const uint TOUCH_FLAG = 0xFF515700;
private const uint WM_MOUSEMOVE = 0x0200;
private const uint WM_LBUTTONDOWN = 0x0201;
private const uint WM_LBUTTONUP = 0x0202;
private const uint WM_RBUTTONDOWN = 0x0204;
private const uint WM_RBUTTONUP = 0x0205;
private const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
private const uint MOUSEEVENTF_LEFTUP = 0x0004;
private const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;
private const uint MOUSEEVENTF_RIGHTUP = 0x0010;
public static Profile ActiveProfile { private get; set; }
public static AppSetting AppSetting { private get; set; }
public static void Hook()
{
_simulatorProcess = WindowProcessManager.GetSimulatorProcess();
Process curProcess = Process.GetCurrentProcess();
ProcessModule curModule = curProcess.MainModule;
var hookWindowPtr = PInvoke.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
_hHook = PInvoke.SetWindowsHookEx(HookType.WH_MOUSE_LL, callbackDelegate, hookWindowPtr, 0);
}
public static void UnHook()
{
PInvoke.UnhookWindowsHookEx(_hHook);
_hHook = IntPtr.Zero;
}
public static bool IsHooked { get { return _hHook != IntPtr.Zero; } }
private static int HookCallBack(int code, IntPtr wParam, IntPtr lParam)
{
if (code != 0)
return PInvoke.CallNextHookEx(_hHook, code, wParam, lParam);
var info = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
var extraInfo = (uint)info.dwExtraInfo;
var isTouched = (extraInfo & TOUCH_FLAG) == TOUCH_FLAG;
// Mouse Click
if (!isTouched)
{
switch ((uint)wParam)
{
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
if (_isTouchDown)
return 1;
break;
}
return PInvoke.CallNextHookEx(_hHook, code, wParam, lParam);
}
// Touch
// If touch point is within pop out panel boundaries and have touch enabled
if (!ActiveProfile.PanelConfigs.Any(p => p.TouchEnabled
&& info.pt.X > p.Left
&& info.pt.X < p.Left + p.Width
&& info.pt.Y > p.Top + (p.HideTitlebar || p.FullScreen ? 5 : PANEL_MENUBAR_HEIGHT)
&& info.pt.Y < p.Top + p.Height))
return PInvoke.CallNextHookEx(_hHook, code, wParam, lParam);
switch ((uint)wParam)
{
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
return 1;
case WM_LBUTTONDOWN:
if (!_isTouchDown)
{
_isTouchDown = true;
lock (_mouseTouchLock)
{
Task.Run(() =>
{
_isMouseMoveBlock = true;
Thread.Sleep(25);
if (_mouseMoveCount > 1 || _mouseMoveCount > 1)
{
PInvoke.mouse_event(MOUSEEVENTF_LEFTUP, _blockPoint.X, _blockPoint.Y, 0, 0);
Thread.Sleep(25);
_touchUsePreClick = true;
}
PInvoke.mouse_event(MOUSEEVENTF_LEFTDOWN, _blockPoint.X, _blockPoint.Y, 0, 0);
_isMouseMoveBlock = false;
_mouseMoveUnblockCount = 0;
_mouseMoveCount = 0;
});
}
}
break;
case WM_LBUTTONUP:
if (_isTouchDown)
{
lock (_mouseTouchLock)
{
Task.Run(() =>
{
Thread.Sleep(AppSetting.TouchScreenSettings.MouseUpDownDelay + (_touchUsePreClick ? 175 : 125));
PInvoke.mouse_event(MOUSEEVENTF_LEFTUP, info.pt.X, info.pt.Y, 0, 0);
_isTouchDown = false;
_touchUsePreClick = false;
RefocusMsfsGameWindow();
});
}
return 1;
}
return 1;
case WM_MOUSEMOVE:
if (_isMouseMoveBlock)
{
_mouseMoveUnblockCount++;
_blockPoint = new Point(info.pt.X, info.pt.Y);
break;
}
_mouseMoveCount++;
break;
}
return PInvoke.CallNextHookEx(_hHook, code, wParam, lParam);
}
private static void RefocusMsfsGameWindow()
{
// ToDo: Adjustable refocus MSFS game, min 500 milliseconds
Thread.Sleep(1000);
if (!_isTouchDown && AppSetting.TouchScreenSettings.RefocusGameWindow)
{
var rectangle = WindowActionManager.GetWindowRect(_simulatorProcess.Handle);
var clientRectangle = WindowActionManager.GetClientRect(_simulatorProcess.Handle);
PInvoke.SetCursorPos(rectangle.X + clientRectangle.Width / 2, rectangle.Y + clientRectangle.Height / 2);
}
}
}
}

View file

@ -45,7 +45,8 @@ namespace MSFSPopoutPanelManager.WindowsAgent
{ {
ProcessId = process.Id, ProcessId = process.Id,
ProcessName = process.ProcessName, ProcessName = process.ProcessName,
Handle = process.MainWindowHandle Handle = process.MainWindowHandle,
MainModule = process.MainModule
}; };
} }
} }
@ -61,5 +62,7 @@ namespace MSFSPopoutPanelManager.WindowsAgent
public string ProcessName { get; set; } public string ProcessName { get; set; }
public IntPtr Handle { get; set; } public IntPtr Handle { get; set; }
public ProcessModule MainModule { get; set; }
} }
} }

View file

@ -26,6 +26,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" /> <ProjectReference Include="..\Shared\Shared.csproj" />
<ProjectReference Include="..\UserDataAgent\UserDataAgent.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -193,7 +193,7 @@
SourceUpdated="GridData_SourceUpdated" SourceUpdated="GridData_SourceUpdated"
IsChecked="{Binding FullScreen, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" IsChecked="{Binding FullScreen, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{c:Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid, AncestorLevel=1}, Path='!DataContext.ProfileData.ActiveProfile.IsLocked'}" IsEnabled="{c:Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid, AncestorLevel=1}, Path='!DataContext.ProfileData.ActiveProfile.IsLocked'}"
IsHitTestVisible="{c:Binding Path='IsCustomPopOut and !TouchEnabled'}"/> IsHitTestVisible="{c:Binding Path='IsCustomPopOut'}"/>
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn> </DataGridTemplateColumn>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<item> <item>
<version>3.3.5.0</version> <version>3.4.0</version>
<url>https://github.com/hawkeye-stan/msfs-popout-panel-manager/releases/download/v3.3.5/msfs-popout-panel-manager.zip</url> <url>https://github.com/hawkeye-stan/msfs-popout-panel-manager/releases/download/v3.4.0/msfs-popout-panel-manager.zip</url>
<changelog>https://raw.githubusercontent.com/hawkeye-stan/msfs-popout-panel-manager/master/latestreleasenotes.txt</changelog> <changelog>https://raw.githubusercontent.com/hawkeye-stan/msfs-popout-panel-manager/master/latestreleasenotes.txt</changelog>
<mandatory>false</mandatory> <mandatory>false</mandatory>
</item> </item>