diff --git a/Assets/Scripts/ControlWPFWindow.cs b/Assets/Scripts/ControlWPFWindow.cs index 606ca2a4..73ef3936 100644 --- a/Assets/Scripts/ControlWPFWindow.cs +++ b/Assets/Scripts/ControlWPFWindow.cs @@ -89,8 +89,33 @@ public class ControlWPFWindow : MonoBehaviour public EasyDeviceDiscoveryProtocolManager easyDeviceDiscoveryProtocolManager; - public ModManager modManager; - + public ModManager modManager; + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + static void SetDpiAwareness() + { + // From https://note.com/taqssoft/n/n69521402e39e +#if UNITY_STANDALONE_WIN + try + { + // Windows 8.1 以降に対応 + NativeMethods.SetProcessDpiAwareness(PROCESS_DPI_AWARENESS.Process_Per_Monitor_DPI_Aware); + } + catch + { + try + { + // 古いWindows向けフォールバック(Vista以降) + NativeMethods.SetProcessDPIAware(); + } + catch + { + Debug.LogWarning("DPI設定の適用に失敗しました。"); + } + } +#endif + } + private void Awake() { Application.targetFrameRate = 60; @@ -118,6 +143,11 @@ private void Awake() server.Start(pipeName); externalMotionSender = ExternalMotionSenderObject.GetComponent(); + +#if !UNITY_EDITOR // エディタ上では動きません。 + var hwnd = GetUnityWindowHandle(); + SetWindowLong(hwnd, GWL_STYLE, defaultWindowStyle | WS_CLIPCHILDREN); +#endif } void Start() @@ -237,7 +267,7 @@ private void Server_Received(object sender, DataReceivedEventArgs e) if (IsPreRelease) { modManager.ImportMods(); - } + } } else if (e.CommandType == typeof(PipeCommands.LoadVRM)) { @@ -364,6 +394,9 @@ private void Server_Received(object sender, DataReceivedEventArgs e) LoadSettings(d.Path); //イベントを登録(何度呼び出しても1回のみ) RegisterEventCallBack(); + + // ウィンドウ情報を反映 + SendWindowInfo(); } else if (e.CommandType == typeof(PipeCommands.SaveSettings)) { @@ -512,7 +545,10 @@ await server.SendCommandAsync(new PipeCommands.SetEyeTracking_ViveProEyeEnable { //現在の設定を再適用する ApplySettings(); - } + } + + // ウィンドウ情報を反映 + SendWindowInfo(); } else if (e.CommandType == typeof(PipeCommands.EnableExternalMotionSender)) { @@ -862,7 +898,22 @@ await server.SendCommandAsync(new PipeCommands.ReturnModList else if (e.CommandType == typeof(PipeCommands.Alive)) { await server.SendCommandAsync(new PipeCommands.Alive { }); + } + else if (e.CommandType == typeof(PipeCommands.GetUnityChildWindowEnable)) + { + await server.SendCommandAsync(new PipeCommands.SetUnityChildWindowEnable + { + enable = Settings.Current.UnityChildWindowEnable + }, e.RequestId); + SendWindowInfo(); } + else if (e.CommandType == typeof(PipeCommands.SetUnityChildWindowEnable)) + { + var d = (PipeCommands.SetUnityChildWindowEnable)e.Data; + Settings.Current.UnityChildWindowEnable = d.enable; + SendWindowInfo(); + } + }, null); } @@ -1195,13 +1246,13 @@ void HideWindowBorder(bool enable) } if (enable) { - SetWindowLong(hwnd, GWL_STYLE, WS_POPUP | WS_VISIBLE); //ウインドウ枠の削除 + SetWindowLong(hwnd, GWL_STYLE, WS_POPUP | WS_VISIBLE | WS_CLIPCHILDREN); //ウインドウ枠の削除 SetUnityWindowFrameChanged(); WaitOneFrameAction(() => SetUnityWindowSize(clientrect.width, clientrect.height)); } else { - SetWindowLong(hwnd, GWL_STYLE, defaultWindowStyle); + SetWindowLong(hwnd, GWL_STYLE, defaultWindowStyle | WS_CLIPCHILDREN); SetUnityWindowFrameChanged(); WaitOneFrameAction(() => SetUnityWindowSize(clientrect.width + windowBorderWidth.Value, clientrect.height + windowBorderHeight.Value)); } @@ -2032,13 +2083,18 @@ void Update() private Vector2 OldMousePos; private bool isWindowDragging = false; + private DateTime lastWindowMoveTime = DateTime.MinValue; + private bool windowPositionSent = true; + private int lastWindowLeft = 0; + private int lastWindowTop = 0; + void LateUpdate() - { + { + var r = GetUnityWindowPosition(); //Windowの移動操作 //ドラッグ開始 if (Input.GetMouseButtonDown((int)MouseButtons.Left) && Input.GetKey(KeyCode.LeftAlt) == false && Input.GetKey(KeyCode.RightAlt) == false) { - var r = GetUnityWindowPosition(); WindowX = r.left; WindowY = r.top; OldMousePos = GetWindowsMousePosition(); @@ -2061,7 +2117,35 @@ void LateUpdate() if (Input.GetMouseButtonUp((int)MouseButtons.Left) && isWindowDragging) { isWindowDragging = false; - } - } + } + + // 位置が変わったら時刻を記録し、送信済みフラグをリセット + if (r.left != lastWindowLeft || r.top != lastWindowTop) + { + lastWindowMoveTime = DateTime.Now; + windowPositionSent = false; + lastWindowLeft = r.left; + lastWindowTop = r.top; + } + + // 指定ミリ秒間動きがなければ、まだ送信していなければ送信 + if (!windowPositionSent && (DateTime.Now - lastWindowMoveTime).TotalMilliseconds >= 200) + { + SendWindowInfo(); + windowPositionSent = true; + } + } + + // 現在のウィンドウハンドル、子ウィンドウ有効可否を送信し、ウィンドウ情報の更新を促す + void SendWindowInfo() { + context.Post(async s => + { + await server.SendCommandAsync(new PipeCommands.WindowInfo + { + Hwnd = GetUnityWindowHandle(), + Child = Settings.Current.UnityChildWindowEnable, + }); + }, null); + } } } \ No newline at end of file diff --git a/Assets/Scripts/Setting/Settings.cs b/Assets/Scripts/Setting/Settings.cs index fceb0d3f..097dc72c 100644 --- a/Assets/Scripts/Setting/Settings.cs +++ b/Assets/Scripts/Setting/Settings.cs @@ -688,8 +688,10 @@ public class Settings [OptionalField] public float PelvisOffsetAdjustY; [OptionalField] - public float PelvisOffsetAdjustZ; - + public float PelvisOffsetAdjustZ; + + [OptionalField] + public bool UnityChildWindowEnable; //初期値 [OnDeserializing()] @@ -862,6 +864,8 @@ internal void OnDeserializingMethod(StreamingContext context) OverrideBodyHeight = 1.7f; PelvisOffsetAdjustY = 0; PelvisOffsetAdjustZ = 0; + + UnityChildWindowEnable = false; } /// diff --git a/Assets/Scripts/Utils/NativeMethods.cs b/Assets/Scripts/Utils/NativeMethods.cs index efd2eb2e..3a8cba26 100644 --- a/Assets/Scripts/Utils/NativeMethods.cs +++ b/Assets/Scripts/Utils/NativeMethods.cs @@ -36,6 +36,13 @@ public struct RECT public int width => right - left; public int height => bottom - top; + } + + public enum PROCESS_DPI_AWARENESS + { + Process_DPI_Unaware = 0, + Process_System_DPI_Aware = 1, + Process_Per_Monitor_DPI_Aware = 2 } [DllImport("user32.dll")] @@ -77,7 +84,12 @@ public static bool IsWindowActive() [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect); [DllImport("user32.dll")] - public static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect); + public static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect); + [DllImport("Shcore.dll")] + public static extern int SetProcessDpiAwareness(PROCESS_DPI_AWARENESS awareness); + [DllImport("user32.dll")] + public static extern bool SetProcessDPIAware(); + public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); public static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2); public static readonly IntPtr HWND_TOP = new IntPtr(0); @@ -109,7 +121,7 @@ public enum SetWindowPosFlags : uint public static void SetUnityWindowSize(int width, int height) => SetWindowPos(GetUnityWindowHandle(), IntPtr.Zero, 0, 0, width, height, SetWindowPosFlags.IgnoreMove); public static void SetUnityWindowFrameChanged() => SetWindowPos(GetUnityWindowHandle(), IntPtr.Zero, 0, 0, 0, 0, SetWindowPosFlags.IgnoreMoveAndResize | SetWindowPosFlags.IgnoreZOrder | SetWindowPosFlags.FrameChanged | SetWindowPosFlags.ShowWindow); public static void SetUnityWindowTopMost(bool enable) => SetWindowPos(GetUnityWindowHandle(), enable ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SetWindowPosFlags.IgnoreMoveAndResize); - public static void SetUnityWindowTitle(string title) => SetWindowText(GetUnityWindowHandle(), title); + public static void SetUnityWindowTitle(string title) => SetWindowText(GetUnityWindowHandle(), title); [DllImport("Dwmapi.dll")] public static extern uint DwmExtendFrameIntoClientArea(IntPtr hWnd, ref DwmMargin margins); @@ -122,6 +134,7 @@ public static void SetDwmTransparent(bool enable) public const int GWL_STYLE = -16; public const uint WS_POPUP = 0x80000000; public const uint WS_VISIBLE = 0x10000000; + public const uint WS_CLIPCHILDREN = 0x02000000; public const int GWL_EXSTYLE = -20; public const uint WS_EX_LAYERED = 0x00080000; public const uint WS_EX_TRANSPARENT = 0x00000020; diff --git a/ControlWindowWPF/ControlWindowWPF/MainWindow.xaml.cs b/ControlWindowWPF/ControlWindowWPF/MainWindow.xaml.cs index 3bc9c058..b75fcc76 100644 --- a/ControlWindowWPF/ControlWindowWPF/MainWindow.xaml.cs +++ b/ControlWindowWPF/ControlWindowWPF/MainWindow.xaml.cs @@ -3,6 +3,7 @@ using System.Collections.ObjectModel; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Windows; @@ -23,6 +24,39 @@ namespace VirtualMotionCaptureControlPanel /// public partial class MainWindow : Window { + public struct RECT + { + public int left; + public int top; + public int right; + public int bottom; + + public int width => right - left; + public int height => bottom - top; + } + + public const int GWL_STYLE = -16; + public const uint WS_MINIMIZEBOX = 0x00020000; + public const uint WS_SYSMENU = 0x00080000; + public const uint WS_CAPTION = 0x00C00000; + public const uint WS_CHILD = 0x40000000; + public const uint WS_POPUP = 0x80000000; + public const uint WS_VISIBLE = 0x10000000; + + [DllImport("user32.dll")] + public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect); + [DllImport("user32.dll")] + public static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect); + [DllImport("user32.dll")] + public static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong); /*x uint o int unchecked*/ + [DllImport("user32.dll")] + public static extern uint GetWindowLong(IntPtr hWnd, int nIndex); + [DllImport("user32.dll", SetLastError = true)] + static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); + [DllImport("user32.dll", SetLastError = true)] + internal static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint); + + private ObservableCollection DefaultFaces = new ObservableCollection { "通常(NEUTRAL)", "喜(JOY)", @@ -49,7 +83,8 @@ public partial class MainWindow : Window private ObservableCollection LipSyncDevices = new ObservableCollection(); private int CurrentWindowNum = 0; - CalibrationResultWindow calibrationResultWindow; + CalibrationResultWindow calibrationResultWindow; + bool isChildWindow = false; public MainWindow() { @@ -65,7 +100,8 @@ public MainWindow() this.Close(); return; } - InitializeComponent(); + InitializeComponent(); + Globals.Connect(App.CommandLineArgs[1]); Globals.Client.ReceivedEvent += Client_Received; DefaultFaceComboBox.ItemsSource = DefaultFacesBase; @@ -159,7 +195,7 @@ private void CheckVMCCameraVersion() } private async void Window_Loaded(object sender, RoutedEventArgs e) - { + { if (Globals.CurrentCommonSettingsWPF.FirewallChecked == false && App.CommandLineArgs[1] != "VMCTest") { var (successExecute, exitCode) = AdminExecute.RestartAsAdmin(new[] { "/firewall" }); @@ -555,7 +591,37 @@ private void Client_Received(object sender, DataReceivedEventArgs e) else if (e.CommandType == typeof(PipeCommands.OpenVRStatus)) { var d = (PipeCommands.OpenVRStatus)e.Data; - OpenVRAlertStatusTextBlock.Visibility = d.DashboardOpened? Visibility.Visible : Visibility.Collapsed; + OpenVRAlertStatusTextBlock.Visibility = d.DashboardOpened ? Visibility.Visible : Visibility.Collapsed; + } + else if (e.CommandType == typeof(PipeCommands.WindowInfo)) + { + var d = (PipeCommands.WindowInfo)e.Data; + + var wih = new System.Windows.Interop.WindowInteropHelper(this); + var hWnd = wih.Handle; + if (d.Child && !isChildWindow) + { + var n = GetWindowLong(hWnd, GWL_STYLE); + SetWindowLong(hWnd, GWL_STYLE, WS_VISIBLE | WS_CHILD); + + // Unityを親にする + SetParent(hWnd, d.Hwnd); + isChildWindow = true; + + MoveWindow(hWnd, 0, 0, 460, 204, true); // DPIの影響を受ける + } + if (!d.Child && isChildWindow) { + var n = GetWindowLong(hWnd, GWL_STYLE); + SetWindowLong(hWnd, GWL_STYLE, WS_VISIBLE | WS_POPUP | WS_CAPTION| WS_SYSMENU| WS_MINIMIZEBOX); + + // デスクトップを親にする + SetParent(hWnd, IntPtr.Zero); + isChildWindow = false; + + MoveWindow(hWnd, 0, 0, 460, 204, true); // DPIの影響を受ける + } + this.Width = 460; + this.Height = 204; } })); } diff --git a/ControlWindowWPF/ControlWindowWPF/Resources/Chinese.xaml b/ControlWindowWPF/ControlWindowWPF/Resources/Chinese.xaml index a143eda1..c1d9ac21 100644 --- a/ControlWindowWPF/ControlWindowWPF/Resources/Chinese.xaml +++ b/ControlWindowWPF/ControlWindowWPF/Resources/Chinese.xaml @@ -282,6 +282,8 @@ 手骨接收 VMC Mod(不保证可用/不受支持) 已加载 MOD 列表 + Other + Display Control Panel in Unity 身高 diff --git a/ControlWindowWPF/ControlWindowWPF/Resources/English.xaml b/ControlWindowWPF/ControlWindowWPF/Resources/English.xaml index 0c18fce2..2ebb5384 100644 --- a/ControlWindowWPF/ControlWindowWPF/Resources/English.xaml +++ b/ControlWindowWPF/ControlWindowWPF/Resources/English.xaml @@ -282,6 +282,8 @@ Receive hand bone VMC Mod (No warranty/no support) Loaded MOD List + Other + Display Control Panel in Unity Body Height diff --git a/ControlWindowWPF/ControlWindowWPF/Resources/Japanese.xaml b/ControlWindowWPF/ControlWindowWPF/Resources/Japanese.xaml index 4f76268b..c67bdcbf 100644 --- a/ControlWindowWPF/ControlWindowWPF/Resources/Japanese.xaml +++ b/ControlWindowWPF/ControlWindowWPF/Resources/Japanese.xaml @@ -282,8 +282,10 @@ 手ボーン取得 VMC Mod (無保証/無サポート) ロード済みModリスト + その他 + コントロール画面をUnity内に表示する - + 身長設定 身長を上書き 身長 : diff --git a/ControlWindowWPF/ControlWindowWPF/Resources/Korean.xaml b/ControlWindowWPF/ControlWindowWPF/Resources/Korean.xaml index 5d8222fd..7393a575 100644 --- a/ControlWindowWPF/ControlWindowWPF/Resources/Korean.xaml +++ b/ControlWindowWPF/ControlWindowWPF/Resources/Korean.xaml @@ -283,8 +283,11 @@ 손 본받는 VMC Mod (No warranty/no support) Loaded MOD List + Other + Display Control Panel in Unity - + + Body Height Override body height Height : diff --git a/ControlWindowWPF/ControlWindowWPF/SettingWindow.xaml b/ControlWindowWPF/ControlWindowWPF/SettingWindow.xaml index bb7f0350..b2700a11 100644 --- a/ControlWindowWPF/ControlWindowWPF/SettingWindow.xaml +++ b/ControlWindowWPF/ControlWindowWPF/SettingWindow.xaml @@ -269,7 +269,10 @@