mirror of
https://github.com/nomi-san/parsec-vdd.git
synced 2026-08-07 13:50:45 +00:00
app: refactor ui thread model
This commit is contained in:
+7
-6
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user