app: refactor ui thread model

This commit is contained in:
Nguyen Duy
2024-06-06 20:37:04 +07:00
parent b76a4d7774
commit 78df4e0192
13 changed files with 410 additions and 334 deletions
+7 -6
View File
@@ -1,8 +1,9 @@
<Application x:Class="ParsecVDisplay.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ParsecVDisplay"
StartupUri="MainWindow.xaml">
<Application
x:Class="ParsecVDisplay.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ParsecVDisplay"
>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
@@ -10,4 +11,4 @@
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
</Application>
+50 -65
View File
@@ -1,78 +1,57 @@
using System;
using System.Windows;
using System.Linq;
using System.Windows.Interop;
using System.Windows.Media;
using System.Reflection;
using System.Collections.Generic;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Markup;
using System.Windows.Media;
namespace ParsecVDisplay
{
public partial class App : Application
{
public static bool Silent { get; private set; }
public static string[] Languages => LanguageDicts.Keys.ToArray();
static Dictionary<string, ResourceDictionary> LanguageDicts;
static ResourceDictionary CurrentLanguage;
protected override void OnStartup(StartupEventArgs e)
{
Silent = e.Args.Contains("-silent");
base.OnStartup(e);
LoadLanguages();
string error = null;
var status = ParsecVDD.QueryStatus();
if (!(status == Device.Status.OK || Config.SkipDriverCheck))
{
switch (status)
{
case Device.Status.RESTART_REQUIRED:
error = GetTranslation("t_msg_must_restart_pc");
break;
case Device.Status.DISABLED:
error =GetTranslation("t_msg_driver_is_disabled", ParsecVDD.ADAPTER);
break;
case Device.Status.NOT_INSTALLED:
error = GetTranslation("t_msg_please_install_driver");
break;
default:
error = GetTranslation("t_msg_driver_status_not_ok", status);
break;
}
}
else if (ParsecVDD.Init() != true)
{
error = GetTranslation("t_msg_failed_to_obtain_handle");
}
if (error != null)
{
MessageBox.Show(error, Program.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
Shutdown();
}
// Disable GPU to prevent flickering when adding display
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
base.OnStartup(e);
var silent = e.Args.Contains("-silent");
var window = new MainWindow();
if (!silent)
{
window.Show();
}
}
protected override void OnExit(ExitEventArgs e)
public static class BamlReader
{
ParsecVDD.Uninit();
base.OnExit(e);
public static object Load(Stream stream)
{
ParserContext pc = new ParserContext();
MethodInfo loadBamlMethod = typeof(XamlReader).GetMethod("LoadBaml",
BindingFlags.NonPublic | BindingFlags.Static);
return loadBamlMethod.Invoke(null, new object[] { stream, pc, null, false });
}
}
static void LoadLanguages()
public static void LoadTranslations()
{
LanguageDicts = new Dictionary<string, ResourceDictionary>();
var assembly = Assembly.GetExecutingAssembly();
var assembly = ResourceAssembly;
var rm = new ResourceManager(assembly.GetName().Name + ".g", assembly);
try
{
var list = rm.GetResourceSet(CultureInfo.CurrentCulture, true, true);
@@ -81,17 +60,17 @@ namespace ParsecVDisplay
if (item.Key is string key
&& key.StartsWith("languages/"))
{
var source = key
.Replace("languages/", "Languages/")
.Replace(".baml", ".xaml");
try
{
var dict = new ResourceDictionary();
dict.Source = new Uri(source, UriKind.Relative);
var source = key
.Replace("languages/", "/Languages/")
.Replace(".baml", ".xaml");
var name = dict["lang_name"].ToString();
LanguageDicts.Add(name, dict);
var sri = GetResourceStream(new Uri(source, UriKind.Relative));
var resources = (ResourceDictionary)BamlReader.Load(sri.Stream);
var name = resources["lang_name"].ToString();
LanguageDicts.Add(name, resources);
}
catch
{
@@ -111,7 +90,15 @@ namespace ParsecVDisplay
{
if (LanguageDicts.TryGetValue(name, out var dict))
{
Current.Resources.MergedDictionaries.Add(dict);
CurrentLanguage = dict;
if (Current != null)
{
Current.Dispatcher.Invoke(() =>
{
Current.Resources.MergedDictionaries.Add(dict);
});
}
if (saveConfig)
{
@@ -122,18 +109,16 @@ namespace ParsecVDisplay
public static string GetTranslation(string key, params object[] args)
{
try
if (CurrentLanguage != null)
{
var translation = Current.FindResource(key);
if (translation != null && translation is string t)
if (CurrentLanguage.Contains(key))
{
t = t.Replace("\\n", "\n");
var t = CurrentLanguage[key]
.ToString()
.Replace("\\n", "\n");
return string.Format(t, args);
}
}
catch
{
}
return string.Format("{{{{{0}}}}}", key);
}
+2 -5
View File
@@ -9,7 +9,6 @@ namespace ParsecVDisplay.Components
{
public partial class DisplayItem : UserControl
{
int Index = -1;
public bool Active { get; set; } = true;
public string DisplayNum { get; set; } = "1";
public string DisplayName { get; set; } = "Display [1]";
@@ -33,9 +32,7 @@ namespace ParsecVDisplay.Components
internal DisplayItem(Display display) : this()
{
Display = display;
Active = display.Active;
Index = display.Address - 0x100;
DisplayNum = $"{display.Identifier}";
DisplayName = $"Display [{display.Identifier}]";
@@ -197,9 +194,9 @@ namespace ParsecVDisplay.Components
private void RemoveDisplay(object sender, RoutedEventArgs e)
{
if (Index != -1)
if (Display.DisplayIndex >= 0)
{
ParsecVDD.RemoveDisplay(Index);
ParsecVDD.RemoveDisplay(Display.DisplayIndex);
}
}
}
+2
View File
@@ -76,6 +76,8 @@ namespace ParsecVDisplay
public List<Mode> ModeList;
public List<ModeSet> SupportedResolutions;
public int DisplayIndex => Address - 0x100;
Display()
{
ModeList = new List<Mode>();
+29 -1
View File
@@ -4,6 +4,8 @@ using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Windows;
using System.Windows.Interop;
namespace ParsecVDisplay
{
@@ -74,7 +76,7 @@ namespace ParsecVDisplay
}
[DllImport("kernel32.dll")]
static extern int SetThreadExecutionState(uint esFlags);
static extern uint SetThreadExecutionState(uint esFlags);
public static void EnableDropShadow(IntPtr hwnd)
{
@@ -105,5 +107,31 @@ namespace ParsecVDisplay
[DllImport("dwmapi.dll")]
static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMarInset);
public static void ShowMe(this Window window)
{
if (window.Visibility != Visibility.Visible)
{
window.Show();
}
if (PresentationSource.FromVisual(window) is HwndSource hwndSource)
{
ShowWindow(hwndSource.Handle, 5);
SetForegroundWindow(hwndSource.Handle);
}
}
[DllImport("user32.dll")]
static extern IntPtr SetForegroundWindow(IntPtr hwnd);
[DllImport("user32.dll")]
static extern int ShowWindow(IntPtr hwnd, int flag);
public class ArbitraryWindow : System.Windows.Forms.IWin32Window
{
public ArbitraryWindow(IntPtr handle) { Handle = handle; }
public IntPtr Handle { get; private set; }
}
}
}
+1
View File
@@ -26,6 +26,7 @@
<!--Tray menu-->
<system:String x:Key="t_remove_last_display">Remove last display</system:String>
<system:String x:Key="t_options">Options</system:String>
<system:String x:Key="t_run_on_startup">Run on startup</system:String>
<system:String x:Key="t_fallback_display">Fallback display</system:String>
<system:String x:Key="t_keep_screen_on">Keep screen on</system:String>
+1
View File
@@ -27,6 +27,7 @@
<!--Tray menu-->
<system:String x:Key="t_remove_last_display">Bỏ màn hình cuối</system:String>
<system:String x:Key="t_options">Tùy chọn</system:String>
<system:String x:Key="t_run_on_startup">Chạy cùng hệ thống</system:String>
<system:String x:Key="t_fallback_display">Màn hình dự phòng</system:String>
<system:String x:Key="t_keep_screen_on">Luôn sáng màn hình</system:String>
+1
View File
@@ -26,6 +26,7 @@
<!--Tray menu-->
<system:String x:Key="t_remove_last_display">删除最后一个虚拟显示器</system:String>
<system:String x:Key="t_options">选项</system:String>
<system:String x:Key="t_run_on_startup">开机启动</system:String>
<system:String x:Key="t_fallback_display">备用显示</system:String>
<system:String x:Key="t_keep_screen_on">保持屏幕开启</system:String>
+1 -22
View File
@@ -31,7 +31,7 @@
</WrapPanel>
<StackPanel x:Name="xButtons" Margin="0,0,25,25" Orientation="Horizontal" Height="36" VerticalAlignment="Bottom" HorizontalAlignment="Right">
<components:Button FontSize="14" Width="40" Click="OpenSettings">
<components:Button FontSize="14" Width="40" Click="OpenDisplaySettings">
<components:Button.Children>
<Image Source="Resources/settings.png" Width="24" Height="24" />
</components:Button.Children>
@@ -107,25 +107,4 @@
<Frame Visibility="Hidden" NavigationUIVisibility="Hidden" Background="#C222" x:Name="xFrame" />
<components:CloseButton Width="48" Height="30" HorizontalAlignment="Right" VerticalAlignment="Top" />
</Grid>
<Window.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static local:Program.AppName}" Click="QueryStatus">
<MenuItem.Icon>
<Image Source="./Resources/icon.ico" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="{DynamicResource t_add_display}" Click="AddDisplay" />
<MenuItem Header="{DynamicResource t_remove_last_display}" Click="RemoveLastDisplay" />
<Separator />
<MenuItem Header="{DynamicResource t_run_on_startup}" IsCheckable="True" IsChecked="{Binding Path=(local:Config.RunOnStartup), Mode=TwoWay}" />
<MenuItem Header="{DynamicResource t_fallback_display}" IsCheckable="True" IsChecked="{Binding Path=(local:Config.FallbackDisplay), Mode=TwoWay}" />
<MenuItem Header="{DynamicResource t_keep_screen_on}" IsCheckable="True" IsChecked="{Binding Path=(local:Config.KeepScreenOn), Mode=TwoWay}" />
<Separator />
<MenuItem Header="{DynamicResource t_check_for_update}" Click="CheckUpdate" />
<Separator />
<MenuItem Header="{DynamicResource t_exit}" Click="ExitApp" />
</ContextMenu>
</Window.ContextMenu>
</Window>
+36 -102
View File
@@ -1,4 +1,5 @@
using System;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
@@ -11,31 +12,31 @@ namespace ParsecVDisplay
{
public partial class MainWindow : Window
{
public static IntPtr Handle { get; private set; }
public static MainWindow Instance { get; private set; }
public static bool IsMenuOpen;
public MainWindow()
{
Instance = this;
InitializeComponent();
xAppName.Content += $" v{Program.AppVersion}";
// prevent frame history
xFrame.Navigating += (_, e) => { e.Cancel = e.NavigationMode != NavigationMode.New; };
xFrame.Navigated += (_, e) => { xFrame.NavigationService.RemoveBackEntry(); };
xFrame.Navigating += (_, e) => e.Cancel = e.NavigationMode != NavigationMode.New;
xFrame.Navigated += (_, e) => xFrame.NavigationService.RemoveBackEntry();
xDisplays.Children.Clear();
xNoDisplay.Visibility = Visibility.Hidden;
// setup tray context menu
ContextMenu.DataContext = this;
ContextMenu.Resources = App.Current.Resources;
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
var hwnd = new WindowInteropHelper(this).EnsureHandle();
Helper.EnableDropShadow(hwnd);
Handle = new WindowInteropHelper(this).EnsureHandle();
Helper.EnableDropShadow(Handle);
}
protected override void OnClosing(CancelEventArgs e)
@@ -65,14 +66,6 @@ namespace ParsecVDisplay
{
Loaded -= Window_Loaded;
Tray.Init(this, ContextMenu);
ContextMenu = null;
if (App.Silent)
Hide();
CheckUpdate(null, null);
var defaultLang = Config.Language;
foreach (var item in App.Languages)
{
@@ -90,58 +83,48 @@ namespace ParsecVDisplay
mi.IsChecked = true;
App.SetLanguage(mi.Header.ToString());
Tray.Instance?.Invoke(Tray.Instance.UpdateContent);
};
xLanguageMenu.Items.Add(mi);
}
ParsecVDD.DisplayChanged += DisplayChanged;
ParsecVDD.Invalidate();
SystemEvents.DisplaySettingsChanged += DisplayChanged;
DisplayChanged(null, EventArgs.Empty);
}
private void Window_Unloaded(object sender, RoutedEventArgs e)
{
ParsecVDD.DisplayChanged -= DisplayChanged;
Tray.Uninit();
SystemEvents.DisplaySettingsChanged -= DisplayChanged;
}
private void DisplayChanged(List<Display> displays, bool noMonitors)
private void DisplayChanged(object sender, EventArgs e)
{
xDisplays.Children.Clear();
xNoDisplay.Visibility = displays.Count <= 0 ? Visibility.Visible : Visibility.Hidden;
foreach (var display in displays)
Dispatcher.Invoke(() =>
{
var item = new Components.DisplayItem(display);
xDisplays.Children.Add(item);
}
var displays = ParsecVDD.GetDisplays(out bool noMonitors);
xAdd.IsEnabled = true;
xDisplays.Children.Clear();
xNoDisplay.Visibility = displays.Count <= 0 ? Visibility.Visible : Visibility.Hidden;
if (noMonitors && Config.FallbackDisplay)
{
AddDisplay(null, EventArgs.Empty);
}
foreach (var display in displays)
{
var item = new Components.DisplayItem(display);
xDisplays.Children.Add(item);
}
xAdd.IsEnabled = true;
if (noMonitors && Config.FallbackDisplay)
{
AddDisplay(null, EventArgs.Empty);
}
});
}
private void AddDisplay(object sender, EventArgs e)
{
if (ParsecVDD.DisplayCount >= ParsecVDD.MAX_DISPLAYS)
{
MessageBox.Show(this, App.GetTranslation("t_msg_exceeded_display_limit", ParsecVDD.MAX_DISPLAYS),
Title, MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{
ParsecVDD.AddDisplay(out var _);
xAdd.IsEnabled = false;
}
}
private void RemoveLastDisplay(object sender, EventArgs e)
{
xAdd.IsEnabled = false;
ParsecVDD.RemoveLastDisplay();
Tray.Instance.Invoke(() => Tray.Instance.AddDisplay(null, null));
}
private void OpenCustom(object sender, EventArgs e)
@@ -152,7 +135,7 @@ namespace ParsecVDisplay
xFrame.Visibility = Visibility.Visible;
}
private void OpenSettings(object sender, EventArgs e)
private void OpenDisplaySettings(object sender, EventArgs e)
{
Helper.ShellExec("ms-settings:display");
}
@@ -162,7 +145,7 @@ namespace ParsecVDisplay
xAdd.IsEnabled = false;
xDisplays.Children.Clear();
ParsecVDD.Invalidate();
DisplayChanged(null, null);
}
private void QueryStatus(object sender, EventArgs e)
@@ -170,25 +153,7 @@ namespace ParsecVDisplay
if (e is MouseEventArgs mbe)
mbe.Handled = true;
Tray.ShowApp();
var status = ParsecVDD.QueryStatus();
ParsecVDD.QueryVersion(out string version);
MessageBox.Show(this, $"Parsec Virtual Display v{version}\n" +
$"{App.GetTranslation("t_msg_driver_status")}: {status}",
Program.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
}
private void ExitApp(object sender, EventArgs e)
{
if (ParsecVDD.DisplayCount > 0)
if (MessageBox.Show(this, App.GetTranslation("t_msg_prompt_leave_all"),
Program.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning) != MessageBoxResult.Yes)
return;
Tray.Uninit();
Application.Current.Shutdown();
Tray.Instance.Invoke(() => Tray.Instance.QueryDriver(null, null));
}
private void OpenRepoLink(object sender, MouseButtonEventArgs e)
@@ -197,37 +162,6 @@ namespace ParsecVDisplay
Helper.OpenLink($"https://github.com/{Program.GitHubRepo}");
}
private async void CheckUpdate(object sender, RoutedEventArgs e)
{
MenuItem menuItem = null;
if (sender is MenuItem)
{
menuItem = sender as MenuItem;
menuItem.IsEnabled = false;
}
var newVersion = await Updater.CheckUpdate();
if (!string.IsNullOrEmpty(newVersion))
{
var ret = MessageBox.Show(this, App.GetTranslation("t_msg_update_available", newVersion),
Program.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
if (ret == MessageBoxResult.Yes)
{
Helper.OpenLink(Updater.DOWNLOAD_URL);
}
}
else if (sender != null)
{
MessageBox.Show(this, App.GetTranslation("t_msg_up_to_date"),
Program.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
}
if (menuItem != null)
{
menuItem.IsEnabled = true;
}
}
private void LanguageText_MouseEvent(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
+7 -87
View File
@@ -1,12 +1,8 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using Microsoft.Win32;
using Timer = System.Windows.Forms.Timer;
namespace ParsecVDisplay
{
internal static class ParsecVDD
@@ -21,94 +17,36 @@ namespace ParsecVDisplay
public const string CLASS_GUID = "{4d36e968-e325-11ce-bfc1-08002be10318}";
static IntPtr VddHandle;
static Timer UpdateTimer;
static Thread UpdateThread;
// actually 16 devices could be created per adapter
// so just use a half to avoid plugging lag
public static int MAX_DISPLAYS => 8;
public static int DisplayCount { get; private set; } = 0;
public delegate void DisplayChangedCallback(List<Display> displays, bool noMonitors);
public static event DisplayChangedCallback DisplayChanged;
public static bool Init()
{
if (Device.OpenHandle(ADAPTER_GUID, out VddHandle))
{
UpdateThread = new Thread(() =>
{
Control.CheckForIllegalCrossThreadCalls = false;
UpdateTimer = new Timer();
UpdateTimer.Tick += delegate { Ping(); };
UpdateTimer.Interval = 50;
UpdateTimer.Start();
Application.Run();
});
UpdateThread.SetApartmentState(ApartmentState.STA);
UpdateThread.Start();
SystemEvents.DisplaySettingsChanged += DisplaySettingsChanged;
SystemEvents.SessionEnding += SessionEnding;
return true;
}
return false;
return Device.OpenHandle(ADAPTER_GUID, out VddHandle);
}
public static void Uninit()
{
//Config.DisplayCount = DisplayCount;
SystemEvents.DisplaySettingsChanged -= DisplaySettingsChanged;
//UpdateTimer.Tick -= UpdateRoutine;
UpdateTimer?.Stop();
UpdateThread?.Abort();
Device.CloseHandle(VddHandle);
}
static void UpdateRoutine(object s, EventArgs e)
{
// TODO: restore added displays
//int initialCount = Config.DisplayCount;
//if (initialCount > 0)
//{
// for (int i = 0; i < initialCount; i++)
// AddDisplay();
//}
//var sw = Stopwatch.StartNew();
//Core.Update(VddHandle);
}
public static void Invalidate()
{
DisplaySettingsChanged(null, EventArgs.Empty);
}
static void DisplaySettingsChanged(object sender, EventArgs e)
public static List<Display> GetDisplays(out bool noMonitors)
{
var displays = Display.GetAllDisplays();
bool noMonitors = displays.Count == 0;
noMonitors = displays.Count == 0;
displays = displays.FindAll(d => d.DisplayName
.Equals(DISPLAY_ID, StringComparison.OrdinalIgnoreCase));
DisplayCount = displays.Count;
noMonitors = DisplayCount == 0 && noMonitors;
DisplayChanged?.Invoke(displays, noMonitors);
noMonitors = displays.Count == 0 && noMonitors;
return displays;
}
static void SessionEnding(object sender, SessionEndingEventArgs e)
public static List<Display> GetDisplays()
{
Config.DisplayCount = DisplayCount;
return GetDisplays(out var _);
}
public static Device.Status QueryStatus()
@@ -157,15 +95,6 @@ namespace ParsecVDisplay
return false;
}
public static void RemoveLastDisplay()
{
if (DisplayCount > 0)
{
int index = DisplayCount - 1;
RemoveDisplay(index);
}
}
public static void Ping()
{
Core.IoControl(VddHandle, Core.IOCTL_UPDATE, null, out var _, 1000);
@@ -339,15 +268,6 @@ namespace ParsecVDisplay
ref OVERLAPPED lpOverlapped
);
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetOverlappedResult(
IntPtr handle,
ref OVERLAPPED lpOverlapped,
out uint lpNumberOfBytesTransferred,
[MarshalAs(UnmanagedType.Bool)] bool bWait
);
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetOverlappedResultEx(
+55 -6
View File
@@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ParsecVDisplay
{
@@ -12,7 +13,7 @@ namespace ParsecVDisplay
public const string GitHubRepo = "nomi-san/parsec-vdd";
[STAThread]
static int Main(string[] args)
static void Main(string[] args)
{
if (args.Length >= 2 && args[0] == "-custom")
{
@@ -27,9 +28,23 @@ namespace ParsecVDisplay
}
}
return 0;
return;
}
if (SingleInstance())
{
App.LoadTranslations();
if (InitDriver())
{
Application.Run(new Tray());
ParsecVDD.Uninit();
}
}
}
static bool SingleInstance()
{
bool isOwned = false;
var signal = new EventWaitHandle(false,
EventResetMode.AutoReset, AppId, out isOwned);
@@ -40,11 +55,9 @@ namespace ParsecVDisplay
{
while (signal.WaitOne())
{
Tray.ShowApp();
Tray.Instance?.Invoke(Tray.Instance.ShowApp);
}
});
App.Main();
}
else
{
@@ -52,7 +65,43 @@ namespace ParsecVDisplay
signal.Dispose();
}
return 0;
return isOwned;
}
static bool InitDriver()
{
string error = null;
var status = ParsecVDD.QueryStatus();
if (!(status == Device.Status.OK || Config.SkipDriverCheck))
{
switch (status)
{
case Device.Status.RESTART_REQUIRED:
error = App.GetTranslation("t_msg_must_restart_pc");
break;
case Device.Status.DISABLED:
error = App.GetTranslation("t_msg_driver_is_disabled", ParsecVDD.ADAPTER);
break;
case Device.Status.NOT_INSTALLED:
error = App.GetTranslation("t_msg_please_install_driver");
break;
default:
error = App.GetTranslation("t_msg_driver_status_not_ok", status);
break;
}
}
else if (ParsecVDD.Init() != true)
{
error = App.GetTranslation("t_msg_failed_to_obtain_handle");
}
if (error != null)
{
MessageBox.Show(error, AppName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
return error == null;
}
}
}
+218 -40
View File
@@ -1,84 +1,262 @@
using System;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
namespace ParsecVDisplay
{
internal static class Tray
internal class Tray : ApplicationContext
{
static NotifyIcon Icon;
static Window Window;
public static Tray Instance { get; private set; }
static IWin32Window Owner => new Helper.ArbitraryWindow(MainWindow.Handle);
NotifyIcon TrayIcon;
static System.Windows.Controls.ContextMenu Menu;
public static void Init(Window window, System.Windows.Controls.ContextMenu menu)
Thread AppThread;
System.Windows.Forms.Timer VddTimer;
ToolStripMenuItem MI_RunOnStartup;
ToolStripMenuItem MI_FallbackDisplay;
ToolStripMenuItem MI_KeepScreenOn;
// ParsecVDisplay
// ______________
// Add display
// Remove last display
// --------------
// Options > Run on startup
// | Fallback display
// | Keep screen on
// Check update
// --------------
// Exit
public Tray()
{
Window = window;
Menu = menu;
Instance = this;
Icon = new NotifyIcon();
AppThread = new Thread(App.Main);
AppThread.IsBackground = true;
AppThread.SetApartmentState(ApartmentState.STA);
AppThread.Start();
var uri = new Uri(window.Icon.ToString());
var streamInfo = System.Windows.Application.GetResourceStream(uri);
Icon.Icon = new System.Drawing.Icon(streamInfo.Stream);
VddTimer = new System.Windows.Forms.Timer();
VddTimer.Interval = 50;
VddTimer.Tick += VddTimer_Tick;
VddTimer.Start();
Icon.MouseClick += Icon_MouseClick;
Icon.DoubleClick += Icon_DoubleClick;
var appName = Program.AppName;
var appIcon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
TrayIcon = new NotifyIcon()
{
Text = appName,
Icon = appIcon,
Visible = true,
ContextMenuStrip = new ContextMenuStrip()
{
Items =
{
new ToolStripMenuItem(appName, appIcon.ToBitmap(), QueryDriver),
new ToolStripSeparator(),
new ToolStripMenuItem("t_add_display", null, AddDisplay),
new ToolStripMenuItem("t_remove_last_display", null, RemoveLastDisplay),
new ToolStripSeparator(),
new ToolStripMenuItem("t_options")
{
DropDownItems =
{
(MI_RunOnStartup = new ToolStripMenuItem("t_run_on_startup",
null, OptionsCheck) { CheckOnClick = true, Checked = Config.RunOnStartup }),
(MI_FallbackDisplay = new ToolStripMenuItem("t_fallback_display",
null, OptionsCheck) { CheckOnClick = true, Checked = Config.FallbackDisplay }),
(MI_KeepScreenOn = new ToolStripMenuItem("t_keep_screen_on",
null, OptionsCheck) { CheckOnClick = true, Checked = Config.KeepScreenOn }),
}
},
new ToolStripMenuItem("t_check_for_update", null, CheckUpdate),
new ToolStripSeparator(),
new ToolStripMenuItem("t_exit", null, Exit),
}
}
};
UpdateContent();
TrayIcon.DoubleClick += delegate { ShowApp(); };
TrayIcon.Visible = true;
Invoke(async () =>
{
await Task.Delay(2000);
CheckUpdate(null, null);
});
Icon.Visible = true;
}
public static void Uninit()
void VddTimer_Tick(object sender, EventArgs e)
{
if (Icon != null)
ParsecVDD.Ping();
}
public void AddDisplay(object sender, EventArgs e)
{
if (ParsecVDD.GetDisplays().Count >= ParsecVDD.MAX_DISPLAYS)
{
Icon.Visible = false;
Icon.Dispose();
MessageBox.Show(Owner, App.GetTranslation("t_msg_exceeded_display_limit", ParsecVDD.MAX_DISPLAYS),
Program.AppName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
ParsecVDD.AddDisplay(out var _);
}
}
private static void Icon_DoubleClick(object sender, EventArgs e)
void RemoveLastDisplay(object sender, EventArgs e)
{
var displays = ParsecVDD.GetDisplays();
if (displays.Count > 0)
{
var last = displays[displays.Count - 1];
ParsecVDD.RemoveDisplay(last.DisplayIndex);
}
}
public void QueryDriver(object sender, EventArgs e)
{
ShowApp();
var status = ParsecVDD.QueryStatus();
ParsecVDD.QueryVersion(out string version);
MessageBox.Show(Owner, $"Parsec Virtual Display v{version}\n" +
$"{App.GetTranslation("t_msg_driver_status")}: {status}",
Program.AppName, MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private static void Icon_MouseClick(object sender, MouseEventArgs e)
async void CheckUpdate(object sender, EventArgs e)
{
if (e.Button == MouseButtons.Right)
ToolStripMenuItem menuItem = null;
if (sender is ToolStripMenuItem)
{
Menu.IsOpen = true;
menuItem = (ToolStripMenuItem)sender;
menuItem.Enabled = false;
}
if (PresentationSource.FromVisual(Menu) is HwndSource hwndSource)
var newVersion = await Updater.CheckUpdate()
.ConfigureAwait(false);
if (!string.IsNullOrEmpty(newVersion))
{
var ret = MessageBox.Show(Owner, App.GetTranslation("t_msg_update_available", newVersion),
Program.AppName, MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (ret == DialogResult.Yes)
{
SetForegroundWindow(hwndSource.Handle);
Helper.OpenLink(Updater.DOWNLOAD_URL);
}
}
else if (sender != null)
{
MessageBox.Show(Owner, App.GetTranslation("t_msg_up_to_date"),
Program.AppName, MessageBoxButtons.OK, MessageBoxIcon.Information);
}
if (menuItem != null)
{
menuItem.Enabled = true;
}
}
public static void ShowApp()
void OptionsCheck(object sender, EventArgs e)
{
if (Window == null) return;
if (sender == MI_RunOnStartup)
Config.RunOnStartup = MI_RunOnStartup.Checked;
else if (sender == MI_FallbackDisplay)
Config.FallbackDisplay = MI_FallbackDisplay.Checked;
else if (sender == MI_KeepScreenOn)
Config.KeepScreenOn = MI_KeepScreenOn.Checked;
}
if (Window.Dispatcher.Thread.ManagedThreadId != Thread.CurrentThread.ManagedThreadId)
public void ShowApp()
{
MainWindow.Instance?.Dispatcher
.Invoke(MainWindow.Instance.ShowMe);
}
public void UpdateContent()
{
void UpdateItem(ToolStripItem item, bool submenu)
{
Window.Dispatcher.BeginInvoke(new Action(ShowApp));
return;
if (item is ToolStripMenuItem mi)
{
if (mi.Tag is string t) { }
else
{
t = mi.Text;
mi.Tag = t;
}
mi.Text = App.GetTranslation(t);
if (submenu && mi.HasDropDownItems)
{
foreach (ToolStripItem sub in mi.DropDownItems)
{
UpdateItem(sub, false);
}
}
}
}
if (Window.Visibility == Visibility.Hidden)
var items = TrayIcon.ContextMenuStrip.Items;
for (int i = 1; i < items.Count; i++)
{
Window.Show();
}
if (PresentationSource.FromVisual(Window) is HwndSource hwndSource)
{
SetForegroundWindow(hwndSource.Handle);
UpdateItem(items[i], true);
}
}
[DllImport("user32.dll")]
static extern IntPtr SetForegroundWindow(IntPtr hwnd);
void Exit(object sender, EventArgs e)
{
var displays = ParsecVDD.GetDisplays();
if (displays.Count > 0)
{
if (MessageBox.Show(Owner, App.GetTranslation("t_msg_prompt_leave_all"),
Program.AppName, MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes)
return;
for (int i = displays.Count - 1; i >= 0; i--)
{
var index = displays[i].DisplayIndex;
ParsecVDD.RemoveDisplay(index);
}
}
VddTimer.Tick -= VddTimer_Tick;
VddTimer.Stop();
App.Current?.Dispatcher.Invoke(App.Current.Shutdown);
AppThread.Join();
TrayIcon.Visible = false;
Application.Exit();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
VddTimer.Dispose();
TrayIcon.Dispose();
}
base.Dispose(disposing);
}
public void Invoke(Action action)
{
TrayIcon.ContextMenuStrip.Invoke(action);
}
}
}