1
0
Fork 0
mirror of https://github.com/hawkeye-stan/msfs-popout-panel-manager.git synced 2024-11-22 13:50:14 +00:00

v1.2.0.0 updates

This commit is contained in:
hawkeye 2021-09-23 09:34:10 -04:00
parent d4c4203931
commit bba7331dbb
7 changed files with 182 additions and 63 deletions

View file

@ -51,7 +51,7 @@ namespace MSFSPopoutPanelManager
public event EventHandler<EventArgs<string>> OnStatusUpdated; public event EventHandler<EventArgs<string>> OnStatusUpdated;
public event EventHandler<EventArgs<Dictionary<string, string>>> OnOcrDebugged; public event EventHandler<EventArgs<Dictionary<string, string>>> OnOcrDebugged;
public void Analyze(ref MainWindow simWindow, string profile) public void Analyze(ref MainWindow simWindow, OcrEvalData ocrEvaluationData)
{ {
MainWindow processZeroMainWindow = null; MainWindow processZeroMainWindow = null;
@ -107,7 +107,6 @@ namespace MSFSPopoutPanelManager
if(simWindow.ChildWindowsData.Count > 0) if(simWindow.ChildWindowsData.Count > 0)
{ {
var ocrEvaluationData = FileManager.ReadProfileData().Find(x => x.Profile == profile);
var ocrImageScale = ocrEvaluationData.OCRImageScale; var ocrImageScale = ocrEvaluationData.OCRImageScale;
Dictionary<string, string> debugInfo = new Dictionary<string, string>(); Dictionary<string, string> debugInfo = new Dictionary<string, string>();
@ -133,7 +132,7 @@ namespace MSFSPopoutPanelManager
SetForegroundWindow(childWindow.Handle); SetForegroundWindow(childWindow.Handle);
Thread.Sleep(500); Thread.Sleep(500);
var image = TakeScreenShot(rect); var image = TakeScreenShot(rect, childWindow.Handle.ToInt32());
MoveWindow(childWindow.Handle, rect.Left, rect.Top, originalWidth, originalHeight, true); MoveWindow(childWindow.Handle, rect.Left, rect.Top, originalWidth, originalHeight, true);
// OCR the image into text // OCR the image into text
@ -220,7 +219,7 @@ namespace MSFSPopoutPanelManager
return true; return true;
} }
private byte[] TakeScreenShot(Rect rect) private byte[] TakeScreenShot(Rect rect, int imageId)
{ {
var bounds = new System.Drawing.Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top); var bounds = new System.Drawing.Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);
var bitmap = new Bitmap(bounds.Width, bounds.Height); var bitmap = new Bitmap(bounds.Width, bounds.Height);
@ -237,9 +236,17 @@ namespace MSFSPopoutPanelManager
using(var img = SixLabors.ImageSharp.Image.Load(imageBytes)) using(var img = SixLabors.ImageSharp.Image.Load(imageBytes))
{ {
img.Mutate(x => x.Invert().GaussianSharpen().Grayscale()); img.Mutate(x => x.Invert().GaussianSharpen().Grayscale());
double imageRatio = 250 / 72.0; // change from default of 72 DPI to 250 DPI
img.Metadata.HorizontalResolution *= imageRatio;
img.Metadata.VerticalResolution *= imageRatio;
var memoryStream = new MemoryStream(); var memoryStream = new MemoryStream();
img.Save(memoryStream, new SixLabors.ImageSharp.Formats.Bmp.BmpEncoder()); img.Save(memoryStream, new SixLabors.ImageSharp.Formats.Bmp.BmpEncoder());
img.Save(@".\test.jpg"); #if DEBUG
Directory.CreateDirectory("imageDebug");
img.Save(@$"./imageDebug/test-{imageId}.jpg");
#endif
return memoryStream.ToArray(); return memoryStream.ToArray();
} }
} }

View file

@ -7,11 +7,18 @@ namespace MSFSPopoutPanelManager
{ {
public class FileManager public class FileManager
{ {
public static UserData ReadUserData() private string _startupPath;
public FileManager(string startupPath)
{
_startupPath = startupPath;
}
public UserData ReadUserData()
{ {
try try
{ {
using (StreamReader reader = new StreamReader(@".\config\userdata.json")) using (StreamReader reader = new StreamReader(_startupPath + @"\config\userdata.json"))
{ {
string json = reader.ReadToEnd(); string json = reader.ReadToEnd();
return JsonConvert.DeserializeObject<UserData>(json); return JsonConvert.DeserializeObject<UserData>(json);
@ -23,18 +30,18 @@ namespace MSFSPopoutPanelManager
} }
} }
public static void WriteUserData(UserData userData) public void WriteUserData(UserData userData)
{ {
using (StreamWriter file = File.CreateText(@".\config\userdata.json")) using (StreamWriter file = File.CreateText(_startupPath + @"\config\userdata.json"))
{ {
JsonSerializer serializer = new JsonSerializer(); JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(file, userData); serializer.Serialize(file, userData);
} }
} }
public static List<OcrEvalData> ReadProfileData() public List<OcrEvalData> ReadProfileData()
{ {
using (StreamReader reader = new StreamReader(@".\config\ocrdata.json")) using (StreamReader reader = new StreamReader(_startupPath + @"\config\ocrdata.json"))
{ {
string json = reader.ReadToEnd(); string json = reader.ReadToEnd();

View file

@ -5,7 +5,7 @@
<TargetFramework>net5.0-windows</TargetFramework> <TargetFramework>net5.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
<Version>1.1</Version> <Version>1.2</Version>
<AssemblyName>MSFSPopoutPanelManager</AssemblyName> <AssemblyName>MSFSPopoutPanelManager</AssemblyName>
<RootNamespace>MSFSPopoutPanelManager</RootNamespace> <RootNamespace>MSFSPopoutPanelManager</RootNamespace>
<ApplicationIcon>WindowManager.ico</ApplicationIcon> <ApplicationIcon>WindowManager.ico</ApplicationIcon>
@ -104,6 +104,9 @@
<None Update="LICENSE"> <None Update="LICENSE">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="VERSION.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Vesion.md"> <None Update="Vesion.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>

View file

@ -12,7 +12,8 @@ namespace MSFSPopoutPanelManager
public partial class MainForm : Form public partial class MainForm : Form
{ {
private SynchronizationContext _syncRoot; private SynchronizationContext _syncRoot;
private WindowManager _popoutWindowsManager; private FileManager _fileManager;
private WindowManager _windowManager;
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
private const UInt32 SWP_NOSIZE = 0x0001; private const UInt32 SWP_NOSIZE = 0x0001;
@ -25,19 +26,22 @@ namespace MSFSPopoutPanelManager
public MainForm() public MainForm()
{ {
InitializeComponent(); InitializeComponent();
_syncRoot = SynchronizationContext.Current; _syncRoot = SynchronizationContext.Current;
_fileManager = new FileManager(Application.StartupPath);
_windowManager = new WindowManager(_fileManager);
_windowManager.OnStatusUpdated += HandleOnStatusUpdated;
_windowManager.OnSimulatorStarted += HandleOnSimulatorStarted;
_windowManager.OnOcrDebugged += HandleOnOcrDebugged;
_windowManager.CheckSimulatorStarted();
SetProfileDropDown(); SetProfileDropDown();
_popoutWindowsManager = new WindowManager(); #if DEBUG
_popoutWindowsManager.OnStatusUpdated += HandleOnStatusUpdated; // Set application windows always on top for easy debugging
_popoutWindowsManager.OnSimulatorStarted += HandleOnSimulatorStarted;
_popoutWindowsManager.OnOcrDebugged += HandleOnOcrDebugged;
_popoutWindowsManager.CheckSimulatorStarted();
SetWindowPos(this.Handle, HWND_TOPMOST, this.Left, this.Top, this.Width, this.Height, TOPMOST_FLAGS); SetWindowPos(this.Handle, HWND_TOPMOST, this.Left, this.Top, this.Width, this.Height, TOPMOST_FLAGS);
#endif
var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
lblVersion.Text += version.ToString(); lblVersion.Text += version.ToString();
} }
@ -47,7 +51,7 @@ namespace MSFSPopoutPanelManager
txtStatus.Clear(); txtStatus.Clear();
var profile = GetProfileDropDown(); var profile = GetProfileDropDown();
var success = _popoutWindowsManager.Analyze(profile); var success = _windowManager.Analyze(profile);
btnApplySettings.Enabled = success; btnApplySettings.Enabled = success;
btnSaveSettings.Enabled = success; btnSaveSettings.Enabled = success;
@ -58,7 +62,7 @@ namespace MSFSPopoutPanelManager
txtStatus.Clear(); txtStatus.Clear();
var profile = GetProfileDropDown(); var profile = GetProfileDropDown();
_popoutWindowsManager.ApplySettings(profile, chkHidePanelTitleBar.Checked, chkAlwaysOnTop.Checked); _windowManager.ApplySettings(profile, chkHidePanelTitleBar.Checked, chkAlwaysOnTop.Checked);
} }
private void btnSaveSettings_Click(object sender, EventArgs e) private void btnSaveSettings_Click(object sender, EventArgs e)
@ -66,15 +70,15 @@ namespace MSFSPopoutPanelManager
txtStatus.Clear(); txtStatus.Clear();
var profile = GetProfileDropDown(); var profile = GetProfileDropDown();
_popoutWindowsManager.SaveSettings(profile, chkHidePanelTitleBar.Checked, chkAlwaysOnTop.Checked); _windowManager.SaveSettings(profile, chkHidePanelTitleBar.Checked, chkAlwaysOnTop.Checked);
} }
private void SetProfileDropDown() private void SetProfileDropDown()
{ {
try try
{ {
var profileData = FileManager.ReadProfileData(); var profileData = _fileManager.ReadProfileData();
var profiles = profileData.Select(x => x.Profile).Distinct(); var profiles = profileData.Select(x => x.Profile).Distinct().OrderBy(x => x);
var defaultProfile = profileData.Find(x => x.DefaultProfile); var defaultProfile = profileData.Find(x => x.DefaultProfile);
comboBoxProfile.DataSource = profiles.ToList(); comboBoxProfile.DataSource = profiles.ToList();
@ -91,6 +95,32 @@ namespace MSFSPopoutPanelManager
return comboBoxProfile.SelectedItem.ToString(); return comboBoxProfile.SelectedItem.ToString();
} }
private void comboBoxProfile_SelectedIndexChanged(object sender, EventArgs e)
{
_windowManager.Reset();
var userData = _fileManager.ReadUserData();
if (userData != null)
{
var userProfile = userData.Profiles.Find(x => x.Name == Convert.ToString(comboBoxProfile.SelectedValue));
if (userProfile != null)
{
chkAlwaysOnTop.Checked = userProfile.AlwaysOnTop;
chkHidePanelTitleBar.Checked = userProfile.HidePanelTitleBar;
}
else
{
// default values
chkAlwaysOnTop.Checked = false;
chkHidePanelTitleBar.Checked = false;
}
}
btnApplySettings.Enabled = false;
btnSaveSettings.Enabled = false;
}
private void HandleOnStatusUpdated(object source, EventArgs<string> arg) private void HandleOnStatusUpdated(object source, EventArgs<string> arg)
{ {
_syncRoot.Post(SetStatusMessage, arg.Value); _syncRoot.Post(SetStatusMessage, arg.Value);
@ -163,7 +193,7 @@ namespace MSFSPopoutPanelManager
private void MainForm_FormClosing(object sender, FormClosingEventArgs e) private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{ {
// Put all panels popout back to original state // Put all panels popout back to original state
_popoutWindowsManager.RestorePanelTitleBar(); _windowManager.RestorePanelTitleBar();
} }
private void MainForm_Resize(object sender, EventArgs e) private void MainForm_Resize(object sender, EventArgs e)
@ -186,28 +216,6 @@ namespace MSFSPopoutPanelManager
WindowState = FormWindowState.Normal; WindowState = FormWindowState.Normal;
} }
private void comboBoxProfile_SelectedIndexChanged(object sender, EventArgs e)
{
var userData = FileManager.ReadUserData();
if (userData != null)
{
var userProfile = userData.Profiles.Find(x => x.Name == Convert.ToString(comboBoxProfile.SelectedValue));
if (userProfile != null)
{
chkAlwaysOnTop.Checked = userProfile.AlwaysOnTop;
chkHidePanelTitleBar.Checked = userProfile.HidePanelTitleBar;
}
else
{
// default values
chkAlwaysOnTop.Checked = false;
chkHidePanelTitleBar.Checked = false;
}
}
btnApplySettings.Enabled = false;
btnSaveSettings.Enabled = false;
}
} }
} }

View file

@ -1,13 +1,21 @@
# Version History # Version History
<hr/> <hr/>
## Version 1.1 ## Version 1.2.0.0
* Increase OCR image accuracy by raising image DPI before analysis.
* Added (very experimental) Asobo A320 and FlybyWire A320NX profiles as testing sample. These profiles do only work 100% of the time. Continue investigation into better OCR accuracy will be needed.
* Added profile dropdown sorted by profile name.
* Fixed an issue of unable to set or reset panel to NOT ALWAYS ON TOP
* Fixed application path issue for not able to find ocrdata.json file at startup.
* Removed MSFS Pop Out Panel Manager is always on top (not the actual in-game panels themselves). Now it is only always on top during development debug mode if you compile and run the application from source code.
## Version 1.1.0.0
* Added caption title for the "untitled" windows. After analysis, if the panel window matches the name in the profile/ocr definition file, it will now display a caption of "Custom - XXXXX" (ie. Custom - PFD). This allows user to use various 3rd party windows layout manager to organize pop out panel windows. * Added caption title for the "untitled" windows. After analysis, if the panel window matches the name in the profile/ocr definition file, it will now display a caption of "Custom - XXXXX" (ie. Custom - PFD). This allows user to use various 3rd party windows layout manager to organize pop out panel windows.
* Added hide panel title bar feature. * Added hide panel title bar feature.
* Added ability to have pop out panels to be always on top. * Added ability to have pop out panels to be always on top.
* Added minimize application to tray feature. * Added minimize application to tray feature.
* Made application flow more intuitive. * Made application flow more intuitive.
* Fix various small bugs in the application. * Fixed various small bugs in the application.
## Version 1.0 ## Version 1.0.0.0
* Initial Release * Initial Release

View file

@ -35,14 +35,20 @@ namespace MSFSPopoutPanelManager
const int WS_CAPTION = WS_BORDER | WS_DLGFRAME; const int WS_CAPTION = WS_BORDER | WS_DLGFRAME;
private const int MSFS_CONNECTION_RETRY_TIMEOUT = 2000; private const int MSFS_CONNECTION_RETRY_TIMEOUT = 2000;
private FileManager _fileManager;
private Timer _timer; private Timer _timer;
private UserData _userData; private UserData _userData;
private MainWindow _simWindow; private MainWindow _simWindow;
private AnalysisEngine _analysisEngine; private AnalysisEngine _analysisEngine;
private Dictionary<IntPtr, Int64> _originalChildWindowStyles; private Dictionary<IntPtr, Int64> _originalChildWindowStyles;
public WindowManager() private bool _currentHidePanelTitleBarStatus;
private bool _currentAlwaysOnTopStatus;
public WindowManager(FileManager fileManager)
{ {
_fileManager = fileManager;
_analysisEngine = new AnalysisEngine(); _analysisEngine = new AnalysisEngine();
_analysisEngine.OnStatusUpdated += (source, e) => OnStatusUpdated?.Invoke(source, e); _analysisEngine.OnStatusUpdated += (source, e) => OnStatusUpdated?.Invoke(source, e);
_analysisEngine.OnOcrDebugged += (source, e) => OnOcrDebugged?.Invoke(source, e); _analysisEngine.OnOcrDebugged += (source, e) => OnOcrDebugged?.Invoke(source, e);
@ -70,20 +76,33 @@ namespace MSFSPopoutPanelManager
}; };
} }
public void Reset()
{
// reset these statuses
_currentHidePanelTitleBarStatus = false;
_currentAlwaysOnTopStatus = false;
RestorePanelTitleBar();
_originalChildWindowStyles = null;
}
public bool Analyze(string profileName) public bool Analyze(string profileName)
{ {
_originalChildWindowStyles = null; _originalChildWindowStyles = null;
_simWindow.ChildWindowsData = new List<ChildWindow>(); _simWindow.ChildWindowsData = new List<ChildWindow>();
_analysisEngine.Analyze(ref _simWindow, profileName); var evalData = _fileManager.ReadProfileData().Find(x => x.Profile == profileName);
_analysisEngine.Analyze(ref _simWindow, evalData);
return _simWindow.ChildWindowsData.FindAll(x => x.PopoutType == PopoutType.Custom || x.PopoutType == PopoutType.BuiltIn).Count > 0; return _simWindow.ChildWindowsData.FindAll(x => x.PopoutType == PopoutType.Custom || x.PopoutType == PopoutType.BuiltIn).Count > 0;
} }
public void ApplySettings(string profileName, bool showPanelTitleBar, bool alwaysOnTop) public void ApplySettings(string profileName, bool hidePanelTitleBar, bool alwaysOnTop)
{ {
// Try to load previous profiles // Try to load previous profiles
_userData = FileManager.ReadUserData(); _userData = _fileManager.ReadUserData();
var profileSettings = _userData != null ? _userData.Profiles.Find(x => x.Name == profileName) : null; var profileSettings = _userData != null ? _userData.Profiles.Find(x => x.Name == profileName) : null;
if (profileSettings == null) if (profileSettings == null)
@ -98,8 +117,18 @@ namespace MSFSPopoutPanelManager
if (childWindows.Count > 0) if (childWindows.Count > 0)
{ {
ApplyPositions(profileSettings, childWindows); ApplyPositions(profileSettings, childWindows);
if (_currentHidePanelTitleBarStatus != hidePanelTitleBar)
{
_currentHidePanelTitleBarStatus = hidePanelTitleBar;
ApplyHidePanelTitleBar(hidePanelTitleBar, childWindows);
}
if(_currentAlwaysOnTopStatus != alwaysOnTop)
{
_currentAlwaysOnTopStatus = alwaysOnTop;
ApplyAlwaysOnTop(alwaysOnTop, childWindows); ApplyAlwaysOnTop(alwaysOnTop, childWindows);
ApplyHidePanelTitleBar(showPanelTitleBar, childWindows); }
} }
} }
@ -134,7 +163,7 @@ namespace MSFSPopoutPanelManager
} }
} }
FileManager.WriteUserData(_userData); _fileManager.WriteUserData(_userData);
OnStatusUpdated?.Invoke(this, new EventArgs<string>("Pop out panel positions have been saved.")); OnStatusUpdated?.Invoke(this, new EventArgs<string>("Pop out panel positions have been saved."));
} }
} }
@ -173,6 +202,15 @@ namespace MSFSPopoutPanelManager
SetWindowPos(childWindow.Handle, new IntPtr(-1), rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top, SWP_ALWAYS_ON_TOP); SetWindowPos(childWindow.Handle, new IntPtr(-1), rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top, SWP_ALWAYS_ON_TOP);
} }
} }
else
{
foreach (var childWindow in childWindows)
{
Rect rect = new Rect();
GetWindowRect(childWindow.Handle, ref rect);
SetWindowPos(childWindow.Handle, new IntPtr(-2), rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top, 0);
}
}
} }
private void ApplyHidePanelTitleBar(bool hidePanelTitleBar, List<ChildWindow> childWindows) private void ApplyHidePanelTitleBar(bool hidePanelTitleBar, List<ChildWindow> childWindows)

View file

@ -1,7 +1,7 @@
[ [
// G1000 // G1000 NXi
{ {
"profile": "G1000", "profile": "G1000 / G1000 NXi",
"defaultProfile": "true", "defaultProfile": "true",
"ocrImageScale": 1.0, "ocrImageScale": 1.0,
"evalData": [ "evalData": [
@ -35,9 +35,57 @@
} }
] ]
}, },
// Flybywire A320NX
{
"profile": "Flybywire A320NX",
"defaultProfile": "false",
"ocrImageScale": 1,
"evalData": [
{
"popoutName": "Screen1",
"data": [ "QNH", "29.92" ]
},
{
"popoutName": "Screen2",
"data": [ "REL", "GW" ]
},
{
"popoutName": "Screen3",
"data": [ "TAS", "GS", "NM", "GPS", "Primary", "PPOS" ]
},
{
"popoutName": "Screen4",
"data": [ "F.FLOW", "AUTO BRK", "KG/MIN", "FOB", "TAT", "SAT" ]
}
]
},
// Asobo A320
{
"profile": "Asobo A320",
"defaultProfile": "false",
"ocrImageScale": 1,
"evalData": [
{
"popoutName": "Screen1",
"data": [ "IDLE" ]
},
{
"popoutName": "Screen2",
"data": [ "TAS", "GS", "NM" ]
},
{
"popoutName": "Screen3",
"data": [ "F.FLOW", "KG", "KG/MIN", "FOB", "TAT", "SAT" ]
},
{
"popoutName": "Screen4",
"data": [ "SPD", "SEL", "CLB", "NAV", "QNH" ]
}
]
},
// Custom Sample Profile - delete this if you don't need it // Custom Sample Profile - delete this if you don't need it
{ {
"profile": "Custom Sample Profile", "profile": "ZZZ - Custom Sample Profile",
"defaultProfile": "false", "defaultProfile": "false",
"ocrImageScale": 1.0, "ocrImageScale": 1.0,
"evalData": [ "evalData": [