mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-19 14:53:50 +02:00
Folder cs files
This commit is contained in:
201
Dotnet/Overlay/OffScreenBrowser.cs
Normal file
201
Dotnet/Overlay/OffScreenBrowser.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
// Copyright(c) 2019-2022 pypy, Natsumi and individual contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using CefSharp;
|
||||
using CefSharp.Enums;
|
||||
using CefSharp.OffScreen;
|
||||
using CefSharp.Structs;
|
||||
using SharpDX.Direct3D11;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public class OffScreenBrowser : ChromiumWebBrowser, IRenderHandler
|
||||
{
|
||||
private readonly ReaderWriterLockSlim _paintBufferLock;
|
||||
private GCHandle _paintBuffer;
|
||||
private int _width;
|
||||
private int _height;
|
||||
|
||||
public OffScreenBrowser(string address, int width, int height)
|
||||
: base(
|
||||
address,
|
||||
new BrowserSettings()
|
||||
{
|
||||
DefaultEncoding = "UTF-8"
|
||||
}
|
||||
)
|
||||
{
|
||||
_paintBufferLock = new ReaderWriterLockSlim();
|
||||
|
||||
Size = new System.Drawing.Size(width, height);
|
||||
RenderHandler = this;
|
||||
|
||||
Util.ApplyJavascriptBindings(JavascriptObjectRepository);
|
||||
}
|
||||
|
||||
public new void Dispose()
|
||||
{
|
||||
RenderHandler = null;
|
||||
base.Dispose();
|
||||
|
||||
_paintBufferLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_paintBuffer.IsAllocated == true)
|
||||
{
|
||||
_paintBuffer.Free();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_paintBufferLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
_paintBufferLock.Dispose();
|
||||
}
|
||||
|
||||
public void RenderToTexture(Texture2D texture)
|
||||
{
|
||||
_paintBufferLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (_width > 0 &&
|
||||
_height > 0)
|
||||
{
|
||||
var context = texture.Device.ImmediateContext;
|
||||
var dataBox = context.MapSubresource(
|
||||
texture,
|
||||
0,
|
||||
MapMode.WriteDiscard,
|
||||
MapFlags.None
|
||||
);
|
||||
if (dataBox.IsEmpty == false)
|
||||
{
|
||||
var sourcePtr = _paintBuffer.AddrOfPinnedObject();
|
||||
var destinationPtr = dataBox.DataPointer;
|
||||
var pitch = _width * 4;
|
||||
var rowPitch = dataBox.RowPitch;
|
||||
if (pitch == rowPitch)
|
||||
{
|
||||
WinApi.CopyMemory(
|
||||
destinationPtr,
|
||||
sourcePtr,
|
||||
(uint)(_width * _height * 4)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var y = _height; y > 0; --y)
|
||||
{
|
||||
WinApi.CopyMemory(
|
||||
destinationPtr,
|
||||
sourcePtr,
|
||||
(uint)pitch
|
||||
);
|
||||
sourcePtr += pitch;
|
||||
destinationPtr += rowPitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
context.UnmapSubresource(texture, 0);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_paintBufferLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
ScreenInfo? IRenderHandler.GetScreenInfo()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
bool IRenderHandler.GetScreenPoint(int viewX, int viewY, out int screenX, out int screenY)
|
||||
{
|
||||
screenX = viewX;
|
||||
screenY = viewY;
|
||||
return false;
|
||||
}
|
||||
|
||||
Rect IRenderHandler.GetViewRect()
|
||||
{
|
||||
return new Rect(0, 0, Size.Width, Size.Height);
|
||||
}
|
||||
|
||||
void IRenderHandler.OnAcceleratedPaint(PaintElementType type, Rect dirtyRect, IntPtr sharedHandle)
|
||||
{
|
||||
// NOT USED
|
||||
}
|
||||
|
||||
void IRenderHandler.OnCursorChange(IntPtr cursor, CursorType type, CursorInfo customCursorInfo)
|
||||
{
|
||||
}
|
||||
|
||||
void IRenderHandler.OnImeCompositionRangeChanged(Range selectedRange, Rect[] characterBounds)
|
||||
{
|
||||
}
|
||||
|
||||
void IRenderHandler.OnPaint(PaintElementType type, Rect dirtyRect, IntPtr buffer, int width, int height)
|
||||
{
|
||||
if (type == PaintElementType.View)
|
||||
{
|
||||
_paintBufferLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_width != width ||
|
||||
_height != height)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
if (_paintBuffer.IsAllocated == true)
|
||||
{
|
||||
_paintBuffer.Free();
|
||||
}
|
||||
_paintBuffer = GCHandle.Alloc(
|
||||
new byte[_width * _height * 4],
|
||||
GCHandleType.Pinned
|
||||
);
|
||||
}
|
||||
|
||||
WinApi.CopyMemory(
|
||||
_paintBuffer.AddrOfPinnedObject(),
|
||||
buffer,
|
||||
(uint)(width * height * 4)
|
||||
);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_paintBufferLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IRenderHandler.OnPopupShow(bool show)
|
||||
{
|
||||
}
|
||||
|
||||
void IRenderHandler.OnPopupSize(Rect rect)
|
||||
{
|
||||
}
|
||||
|
||||
void IRenderHandler.OnVirtualKeyboardRequested(IBrowser browser, TextInputMode inputMode)
|
||||
{
|
||||
}
|
||||
|
||||
bool IRenderHandler.StartDragging(IDragData dragData, DragOperationsMask mask, int x, int y)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void IRenderHandler.UpdateDragCursor(DragOperationsMask operation)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
713
Dotnet/Overlay/VRCXVR.cs
Normal file
713
Dotnet/Overlay/VRCXVR.cs
Normal file
@@ -0,0 +1,713 @@
|
||||
// Copyright(c) 2019-2022 pypy, Natsumi and individual contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using CefSharp;
|
||||
using SharpDX;
|
||||
using SharpDX.Direct3D;
|
||||
using SharpDX.Direct3D11;
|
||||
using SharpDX.DXGI;
|
||||
using Valve.VR;
|
||||
using Device = SharpDX.Direct3D11.Device;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public class VRCXVR
|
||||
{
|
||||
public static VRCXVR Instance;
|
||||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
private static readonly float[] _rotation = { 0f, 0f, 0f };
|
||||
private static readonly float[] _translation = { 0f, 0f, 0f };
|
||||
private static readonly float[] _translationLeft = { -7f / 100f, -5f / 100f, 6f / 100f };
|
||||
private static readonly float[] _translationRight = { 7f / 100f, -5f / 100f, 6f / 100f };
|
||||
private static readonly float[] _rotationLeft = { 90f * (float)(Math.PI / 180f), 90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f) };
|
||||
private static readonly float[] _rotationRight = { -90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f) };
|
||||
public static OffScreenBrowser _browser1;
|
||||
public static OffScreenBrowser _browser2;
|
||||
private readonly List<string[]> _deviceList;
|
||||
private readonly ReaderWriterLockSlim _deviceListLock;
|
||||
private bool _active;
|
||||
private Device _device;
|
||||
private bool _hmdOverlayActive;
|
||||
private bool _menuButton;
|
||||
private int _overlayHand;
|
||||
private Texture2D _texture1;
|
||||
private Texture2D _texture2;
|
||||
private Thread _thread;
|
||||
private bool _wristOverlayActive;
|
||||
|
||||
static VRCXVR()
|
||||
{
|
||||
Instance = new VRCXVR();
|
||||
}
|
||||
|
||||
public VRCXVR()
|
||||
{
|
||||
_deviceListLock = new ReaderWriterLockSlim();
|
||||
_deviceList = new List<string[]>();
|
||||
_thread = new Thread(ThreadLoop)
|
||||
{
|
||||
IsBackground = true
|
||||
};
|
||||
}
|
||||
|
||||
// NOTE
|
||||
// 메모리 릭 때문에 미리 생성해놓고 계속 사용함
|
||||
internal void Init()
|
||||
{
|
||||
_thread.Start();
|
||||
}
|
||||
|
||||
internal void Exit()
|
||||
{
|
||||
var thread = _thread;
|
||||
_thread = null;
|
||||
thread.Interrupt();
|
||||
thread.Join();
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
{
|
||||
Exit();
|
||||
Instance = new VRCXVR();
|
||||
Instance.Init();
|
||||
MainForm.Instance.Browser.ExecuteScriptAsync("console.log('VRCXVR Restarted');");
|
||||
}
|
||||
|
||||
private void ThreadLoop()
|
||||
{
|
||||
var active = false;
|
||||
var e = new VREvent_t();
|
||||
var nextInit = DateTime.MinValue;
|
||||
var nextDeviceUpdate = DateTime.MinValue;
|
||||
var nextOverlay = DateTime.MinValue;
|
||||
var overlayIndex = OpenVR.k_unTrackedDeviceIndexInvalid;
|
||||
var overlayVisible1 = false;
|
||||
var overlayVisible2 = false;
|
||||
var dashboardHandle = 0UL;
|
||||
var overlayHandle1 = 0UL;
|
||||
var overlayHandle2 = 0UL;
|
||||
|
||||
// REMOVE THIS
|
||||
// nextOverlay = DateTime.MaxValue;
|
||||
// https://stackoverflow.com/questions/38312597/how-to-choose-a-specific-graphics-device-in-sharpdx-directx-11/38596725#38596725
|
||||
Factory f = new Factory1();
|
||||
var a = f.GetAdapter(1);
|
||||
|
||||
var flags = DeviceCreationFlags.BgraSupport;
|
||||
|
||||
_device = Program.GPUFix ? new Device(a, flags) : new Device(DriverType.Hardware, DeviceCreationFlags.SingleThreaded | DeviceCreationFlags.BgraSupport);
|
||||
|
||||
_texture1 = new Texture2D(
|
||||
_device,
|
||||
new Texture2DDescription
|
||||
{
|
||||
Width = 512,
|
||||
Height = 512,
|
||||
MipLevels = 1,
|
||||
ArraySize = 1,
|
||||
Format = Format.B8G8R8A8_UNorm,
|
||||
SampleDescription = new SampleDescription(1, 0),
|
||||
Usage = ResourceUsage.Dynamic,
|
||||
BindFlags = BindFlags.ShaderResource,
|
||||
CpuAccessFlags = CpuAccessFlags.Write
|
||||
}
|
||||
);
|
||||
|
||||
_texture2 = new Texture2D(
|
||||
_device,
|
||||
new Texture2DDescription
|
||||
{
|
||||
Width = 1024,
|
||||
Height = 1024,
|
||||
MipLevels = 1,
|
||||
ArraySize = 1,
|
||||
Format = Format.B8G8R8A8_UNorm,
|
||||
SampleDescription = new SampleDescription(1, 0),
|
||||
Usage = ResourceUsage.Dynamic,
|
||||
BindFlags = BindFlags.ShaderResource,
|
||||
CpuAccessFlags = CpuAccessFlags.Write
|
||||
}
|
||||
);
|
||||
|
||||
_browser1 = new OffScreenBrowser(
|
||||
"file://vrcx/vr.html?1",
|
||||
512,
|
||||
512
|
||||
);
|
||||
|
||||
_browser2 = new OffScreenBrowser(
|
||||
"file://vrcx/vr.html?2",
|
||||
1024,
|
||||
1024
|
||||
);
|
||||
|
||||
while (_thread != null)
|
||||
{
|
||||
if (_wristOverlayActive)
|
||||
_browser1.RenderToTexture(_texture1);
|
||||
if (_hmdOverlayActive)
|
||||
_browser2.RenderToTexture(_texture2);
|
||||
try
|
||||
{
|
||||
Thread.Sleep(32);
|
||||
}
|
||||
catch (ThreadInterruptedException)
|
||||
{
|
||||
}
|
||||
|
||||
if (_active)
|
||||
{
|
||||
var system = OpenVR.System;
|
||||
if (system == null)
|
||||
{
|
||||
if (DateTime.UtcNow.CompareTo(nextInit) <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var _err = EVRInitError.None;
|
||||
system = OpenVR.Init(ref _err, EVRApplicationType.VRApplication_Background);
|
||||
nextInit = DateTime.UtcNow.AddSeconds(5);
|
||||
if (system == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
active = true;
|
||||
}
|
||||
|
||||
while (system.PollNextEvent(ref e, (uint)Marshal.SizeOf(e)))
|
||||
{
|
||||
var type = (EVREventType)e.eventType;
|
||||
if (type == EVREventType.VREvent_Quit)
|
||||
{
|
||||
active = false;
|
||||
OpenVR.Shutdown();
|
||||
nextInit = DateTime.UtcNow.AddSeconds(10);
|
||||
system = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (system != null)
|
||||
{
|
||||
if (DateTime.UtcNow.CompareTo(nextDeviceUpdate) >= 0)
|
||||
{
|
||||
overlayIndex = OpenVR.k_unTrackedDeviceIndexInvalid;
|
||||
UpdateDevices(system, ref overlayIndex);
|
||||
if (overlayIndex != OpenVR.k_unTrackedDeviceIndexInvalid)
|
||||
{
|
||||
nextOverlay = DateTime.UtcNow.AddSeconds(10);
|
||||
}
|
||||
|
||||
nextDeviceUpdate = DateTime.UtcNow.AddSeconds(0.1);
|
||||
}
|
||||
|
||||
var overlay = OpenVR.Overlay;
|
||||
if (overlay != null)
|
||||
{
|
||||
var dashboardVisible = overlay.IsDashboardVisible();
|
||||
var err = ProcessDashboard(overlay, ref dashboardHandle, dashboardVisible);
|
||||
if (err != EVROverlayError.None &&
|
||||
dashboardHandle != 0)
|
||||
{
|
||||
overlay.DestroyOverlay(dashboardHandle);
|
||||
dashboardHandle = 0;
|
||||
logger.Error(err);
|
||||
}
|
||||
|
||||
err = ProcessOverlay1(overlay, ref overlayHandle1, ref overlayVisible1, dashboardVisible, overlayIndex, nextOverlay);
|
||||
if (err != EVROverlayError.None &&
|
||||
overlayHandle1 != 0)
|
||||
{
|
||||
overlay.DestroyOverlay(overlayHandle1);
|
||||
overlayHandle1 = 0;
|
||||
logger.Error(err);
|
||||
}
|
||||
|
||||
err = ProcessOverlay2(overlay, ref overlayHandle2, ref overlayVisible2, dashboardVisible);
|
||||
if (err != EVROverlayError.None &&
|
||||
overlayHandle2 != 0)
|
||||
{
|
||||
overlay.DestroyOverlay(overlayHandle2);
|
||||
overlayHandle2 = 0;
|
||||
logger.Error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (active)
|
||||
{
|
||||
active = false;
|
||||
OpenVR.Shutdown();
|
||||
_deviceListLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_deviceList.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_deviceListLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_browser2.Dispose();
|
||||
_browser1.Dispose();
|
||||
_texture2.Dispose();
|
||||
_texture1.Dispose();
|
||||
_device.Dispose();
|
||||
}
|
||||
|
||||
public void SetActive(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand)
|
||||
{
|
||||
_active = active;
|
||||
_hmdOverlayActive = hmdOverlay;
|
||||
_wristOverlayActive = wristOverlay;
|
||||
_menuButton = menuButton;
|
||||
_overlayHand = overlayHand;
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
_browser1.Reload();
|
||||
_browser2.Reload();
|
||||
}
|
||||
|
||||
public string[][] GetDevices()
|
||||
{
|
||||
_deviceListLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return _deviceList.ToArray();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_deviceListLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateDevices(CVRSystem system, ref uint overlayIndex)
|
||||
{
|
||||
_deviceListLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_deviceList.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_deviceListLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
var sb = new StringBuilder(256);
|
||||
var state = new VRControllerState_t();
|
||||
var poses = new TrackedDevicePose_t[OpenVR.k_unMaxTrackedDeviceCount];
|
||||
system.GetDeviceToAbsoluteTrackingPose(ETrackingUniverseOrigin.TrackingUniverseStanding, 0, poses);
|
||||
for (var i = 0u; i < OpenVR.k_unMaxTrackedDeviceCount; ++i)
|
||||
{
|
||||
var devClass = system.GetTrackedDeviceClass(i);
|
||||
if (devClass == ETrackedDeviceClass.Controller ||
|
||||
devClass == ETrackedDeviceClass.GenericTracker ||
|
||||
devClass == ETrackedDeviceClass.TrackingReference)
|
||||
{
|
||||
var err = ETrackedPropertyError.TrackedProp_Success;
|
||||
var batteryPercentage = system.GetFloatTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float, ref err);
|
||||
if (err != ETrackedPropertyError.TrackedProp_Success)
|
||||
{
|
||||
batteryPercentage = 1f;
|
||||
}
|
||||
|
||||
err = ETrackedPropertyError.TrackedProp_Success;
|
||||
var isCharging = system.GetBoolTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_DeviceIsCharging_Bool, ref err);
|
||||
if (err != ETrackedPropertyError.TrackedProp_Success)
|
||||
{
|
||||
isCharging = false;
|
||||
}
|
||||
|
||||
sb.Clear();
|
||||
system.GetStringTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_TrackingSystemName_String, sb, (uint)sb.Capacity, ref err);
|
||||
var isOculus = sb.ToString().IndexOf("oculus", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
// Oculus : B/Y, Bit 1, Mask 2
|
||||
// Oculus : A/X, Bit 7, Mask 128
|
||||
// Vive : Menu, Bit 1, Mask 2,
|
||||
// Vive : Grip, Bit 2, Mask 4
|
||||
var role = system.GetControllerRoleForTrackedDeviceIndex(i);
|
||||
if (role == ETrackedControllerRole.LeftHand || role == ETrackedControllerRole.RightHand)
|
||||
{
|
||||
if (_overlayHand == 0 ||
|
||||
(_overlayHand == 1 && role == ETrackedControllerRole.LeftHand) ||
|
||||
(_overlayHand == 2 && role == ETrackedControllerRole.RightHand))
|
||||
{
|
||||
if (system.GetControllerState(i, ref state, (uint)Marshal.SizeOf(state)) &&
|
||||
(state.ulButtonPressed & (_menuButton ? 2u : isOculus ? 128u : 4u)) != 0)
|
||||
{
|
||||
if (role == ETrackedControllerRole.LeftHand)
|
||||
{
|
||||
Array.Copy(_translationLeft, _translation, 3);
|
||||
Array.Copy(_rotationLeft, _rotation, 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(_translationRight, _translation, 3);
|
||||
Array.Copy(_rotationRight, _rotation, 3);
|
||||
}
|
||||
|
||||
overlayIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var type = string.Empty;
|
||||
if (devClass == ETrackedDeviceClass.Controller)
|
||||
{
|
||||
if (role == ETrackedControllerRole.LeftHand)
|
||||
{
|
||||
type = "leftController";
|
||||
}
|
||||
else if (role == ETrackedControllerRole.RightHand)
|
||||
{
|
||||
type = "rightController";
|
||||
}
|
||||
else
|
||||
{
|
||||
type = "controller";
|
||||
}
|
||||
}
|
||||
else if (devClass == ETrackedDeviceClass.GenericTracker)
|
||||
{
|
||||
type = "tracker";
|
||||
}
|
||||
else if (devClass == ETrackedDeviceClass.TrackingReference)
|
||||
{
|
||||
type = "base";
|
||||
}
|
||||
|
||||
var item = new[]
|
||||
{
|
||||
type,
|
||||
system.IsTrackedDeviceConnected(i)
|
||||
? "connected"
|
||||
: "disconnected",
|
||||
isCharging
|
||||
? "charging"
|
||||
: "discharging",
|
||||
(batteryPercentage * 100).ToString(),
|
||||
poses[i].eTrackingResult.ToString()
|
||||
};
|
||||
_deviceListLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_deviceList.Add(item);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_deviceListLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal EVROverlayError ProcessDashboard(CVROverlay overlay, ref ulong dashboardHandle, bool dashboardVisible)
|
||||
{
|
||||
var err = EVROverlayError.None;
|
||||
|
||||
if (dashboardHandle == 0)
|
||||
{
|
||||
err = overlay.FindOverlay("VRCX", ref dashboardHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
if (err != EVROverlayError.UnknownOverlay)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
ulong thumbnailHandle = 0;
|
||||
err = overlay.CreateDashboardOverlay("VRCX", "VRCX", ref dashboardHandle, ref thumbnailHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
var iconPath = Path.Combine(Program.BaseDirectory, "VRCX.png");
|
||||
err = overlay.SetOverlayFromFile(thumbnailHandle, iconPath);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
err = overlay.SetOverlayWidthInMeters(dashboardHandle, 1.5f);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
err = overlay.SetOverlayInputMethod(dashboardHandle, VROverlayInputMethod.Mouse);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var e = new VREvent_t();
|
||||
|
||||
while (overlay.PollNextOverlayEvent(dashboardHandle, ref e, (uint)Marshal.SizeOf(e)))
|
||||
{
|
||||
var type = (EVREventType)e.eventType;
|
||||
if (type == EVREventType.VREvent_MouseMove)
|
||||
{
|
||||
var m = e.data.mouse;
|
||||
var s = _browser1.Size;
|
||||
_browser1.GetBrowserHost().SendMouseMoveEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), false, CefEventFlags.None);
|
||||
}
|
||||
else if (type == EVREventType.VREvent_MouseButtonDown)
|
||||
{
|
||||
var m = e.data.mouse;
|
||||
var s = _browser1.Size;
|
||||
_browser1.GetBrowserHost().SendMouseClickEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), MouseButtonType.Left, false, 1, CefEventFlags.LeftMouseButton);
|
||||
}
|
||||
else if (type == EVREventType.VREvent_MouseButtonUp)
|
||||
{
|
||||
var m = e.data.mouse;
|
||||
var s = _browser1.Size;
|
||||
_browser1.GetBrowserHost().SendMouseClickEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), MouseButtonType.Left, true, 1, CefEventFlags.None);
|
||||
}
|
||||
}
|
||||
|
||||
if (dashboardVisible)
|
||||
{
|
||||
var texture = new Texture_t
|
||||
{
|
||||
handle = _texture1.NativePointer
|
||||
};
|
||||
err = overlay.SetOverlayTexture(dashboardHandle, ref texture);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
internal EVROverlayError ProcessOverlay1(CVROverlay overlay, ref ulong overlayHandle, ref bool overlayVisible, bool dashboardVisible, uint overlayIndex, DateTime nextOverlay)
|
||||
{
|
||||
var err = EVROverlayError.None;
|
||||
|
||||
if (overlayHandle == 0)
|
||||
{
|
||||
err = overlay.FindOverlay("VRCX1", ref overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
if (err != EVROverlayError.UnknownOverlay)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
overlayVisible = false;
|
||||
err = overlay.CreateOverlay("VRCX1", "VRCX1", ref overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
err = overlay.SetOverlayAlpha(overlayHandle, 0.9f);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
err = overlay.SetOverlayWidthInMeters(overlayHandle, 1f);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
err = overlay.SetOverlayInputMethod(overlayHandle, VROverlayInputMethod.None);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (overlayIndex != OpenVR.k_unTrackedDeviceIndexInvalid)
|
||||
{
|
||||
// http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices
|
||||
// Scaling-Rotation-Translation
|
||||
var m = Matrix.Scaling(0.25f);
|
||||
m *= Matrix.RotationX(_rotation[0]);
|
||||
m *= Matrix.RotationY(_rotation[1]);
|
||||
m *= Matrix.RotationZ(_rotation[2]);
|
||||
m *= Matrix.Translation(_translation[0], _translation[1], _translation[2]);
|
||||
var hm34 = new HmdMatrix34_t
|
||||
{
|
||||
m0 = m.M11,
|
||||
m1 = m.M21,
|
||||
m2 = m.M31,
|
||||
m3 = m.M41,
|
||||
m4 = m.M12,
|
||||
m5 = m.M22,
|
||||
m6 = m.M32,
|
||||
m7 = m.M42,
|
||||
m8 = m.M13,
|
||||
m9 = m.M23,
|
||||
m10 = m.M33,
|
||||
m11 = m.M43
|
||||
};
|
||||
err = overlay.SetOverlayTransformTrackedDeviceRelative(overlayHandle, overlayIndex, ref hm34);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dashboardVisible &&
|
||||
DateTime.UtcNow.CompareTo(nextOverlay) <= 0)
|
||||
{
|
||||
var texture = new Texture_t
|
||||
{
|
||||
handle = _texture1.NativePointer
|
||||
};
|
||||
err = overlay.SetOverlayTexture(overlayHandle, ref texture);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!overlayVisible)
|
||||
{
|
||||
err = overlay.ShowOverlay(overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
overlayVisible = true;
|
||||
}
|
||||
}
|
||||
else if (overlayVisible)
|
||||
{
|
||||
err = overlay.HideOverlay(overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
overlayVisible = false;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
internal EVROverlayError ProcessOverlay2(CVROverlay overlay, ref ulong overlayHandle, ref bool overlayVisible, bool dashboardVisible)
|
||||
{
|
||||
var err = EVROverlayError.None;
|
||||
|
||||
if (overlayHandle == 0)
|
||||
{
|
||||
err = overlay.FindOverlay("VRCX2", ref overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
if (err != EVROverlayError.UnknownOverlay)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
overlayVisible = false;
|
||||
err = overlay.CreateOverlay("VRCX2", "VRCX2", ref overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
err = overlay.SetOverlayAlpha(overlayHandle, 0.9f);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
err = overlay.SetOverlayWidthInMeters(overlayHandle, 1f);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
err = overlay.SetOverlayInputMethod(overlayHandle, VROverlayInputMethod.None);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
var m = Matrix.Scaling(1f);
|
||||
m *= Matrix.Translation(0, -0.3f, -1.5f);
|
||||
var hm34 = new HmdMatrix34_t
|
||||
{
|
||||
m0 = m.M11,
|
||||
m1 = m.M21,
|
||||
m2 = m.M31,
|
||||
m3 = m.M41,
|
||||
m4 = m.M12,
|
||||
m5 = m.M22,
|
||||
m6 = m.M32,
|
||||
m7 = m.M42,
|
||||
m8 = m.M13,
|
||||
m9 = m.M23,
|
||||
m10 = m.M33,
|
||||
m11 = m.M43
|
||||
};
|
||||
err = overlay.SetOverlayTransformTrackedDeviceRelative(overlayHandle, OpenVR.k_unTrackedDeviceIndex_Hmd, ref hm34);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dashboardVisible)
|
||||
{
|
||||
var texture = new Texture_t
|
||||
{
|
||||
handle = _texture2.NativePointer
|
||||
};
|
||||
err = overlay.SetOverlayTexture(overlayHandle, ref texture);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!overlayVisible)
|
||||
{
|
||||
err = overlay.ShowOverlay(overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
overlayVisible = true;
|
||||
}
|
||||
}
|
||||
else if (overlayVisible)
|
||||
{
|
||||
err = overlay.HideOverlay(overlayHandle);
|
||||
if (err != EVROverlayError.None)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
overlayVisible = false;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
110
Dotnet/Overlay/VRForm.Designer.cs
generated
Normal file
110
Dotnet/Overlay/VRForm.Designer.cs
generated
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright(c) 2019-2022 pypy, Natsumi and individual contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
partial class VRForm
|
||||
{
|
||||
/// <summary>
|
||||
/// 필수 디자이너 변수입니다.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// 사용 중인 모든 리소스를 정리합니다.
|
||||
/// </summary>
|
||||
/// <param name="disposing">관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form 디자이너에서 생성한 코드
|
||||
|
||||
/// <summary>
|
||||
/// 디자이너 지원에 필요한 메서드입니다.
|
||||
/// 이 메서드의 내용을 코드 편집기로 수정하지 마세요.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.timer = new System.Windows.Forms.Timer(this.components);
|
||||
this.panel1 = new System.Windows.Forms.Panel();
|
||||
this.panel2 = new System.Windows.Forms.Panel();
|
||||
this.button_refresh = new System.Windows.Forms.Button();
|
||||
this.button_devtools = new System.Windows.Forms.Button();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// panel1
|
||||
//
|
||||
this.panel1.Location = new System.Drawing.Point(0, 0);
|
||||
this.panel1.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
this.panel1.Name = "panel1";
|
||||
this.panel1.Size = new System.Drawing.Size(731, 768);
|
||||
this.panel1.TabIndex = 0;
|
||||
//
|
||||
// panel2
|
||||
//
|
||||
this.panel2.Location = new System.Drawing.Point(740, 0);
|
||||
this.panel2.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
this.panel2.Name = "panel2";
|
||||
this.panel2.Size = new System.Drawing.Size(731, 768);
|
||||
this.panel2.TabIndex = 1;
|
||||
//
|
||||
// button_refresh
|
||||
//
|
||||
this.button_refresh.Location = new System.Drawing.Point(17, 777);
|
||||
this.button_refresh.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
this.button_refresh.Name = "button_refresh";
|
||||
this.button_refresh.Size = new System.Drawing.Size(107, 34);
|
||||
this.button_refresh.TabIndex = 27;
|
||||
this.button_refresh.Text = "Refresh";
|
||||
this.button_refresh.UseVisualStyleBackColor = true;
|
||||
this.button_refresh.Click += new System.EventHandler(this.button_refresh_Click);
|
||||
//
|
||||
// button_devtools
|
||||
//
|
||||
this.button_devtools.Location = new System.Drawing.Point(133, 777);
|
||||
this.button_devtools.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
this.button_devtools.Name = "button_devtools";
|
||||
this.button_devtools.Size = new System.Drawing.Size(107, 34);
|
||||
this.button_devtools.TabIndex = 27;
|
||||
this.button_devtools.Text = "DevTools";
|
||||
this.button_devtools.UseVisualStyleBackColor = true;
|
||||
this.button_devtools.Click += new System.EventHandler(this.button_devtools_Click);
|
||||
//
|
||||
// VRForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(144F, 144F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
this.ClientSize = new System.Drawing.Size(1483, 830);
|
||||
this.Controls.Add(this.button_devtools);
|
||||
this.Controls.Add(this.button_refresh);
|
||||
this.Controls.Add(this.panel2);
|
||||
this.Controls.Add(this.panel1);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
|
||||
this.MaximizeBox = false;
|
||||
this.Name = "VRForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "VR";
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Timer timer;
|
||||
private System.Windows.Forms.Panel panel1;
|
||||
private System.Windows.Forms.Panel panel2;
|
||||
private System.Windows.Forms.Button button_refresh;
|
||||
private System.Windows.Forms.Button button_devtools;
|
||||
}
|
||||
}
|
||||
68
Dotnet/Overlay/VRForm.cs
Normal file
68
Dotnet/Overlay/VRForm.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright(c) 2019 pypy. All rights reserved.
|
||||
//
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public partial class VRForm : WinformBase
|
||||
{
|
||||
public static VRForm Instance;
|
||||
private ChromiumWebBrowser _browser1;
|
||||
private ChromiumWebBrowser _browser2;
|
||||
|
||||
public VRForm()
|
||||
{
|
||||
Instance = this;
|
||||
InitializeComponent();
|
||||
|
||||
_browser1 = new ChromiumWebBrowser(
|
||||
Path.Combine(Program.BaseDirectory, "html/vr.html?1")
|
||||
)
|
||||
{
|
||||
DragHandler = new CefNoopDragHandler(),
|
||||
BrowserSettings =
|
||||
{
|
||||
DefaultEncoding = "UTF-8",
|
||||
},
|
||||
Dock = DockStyle.Fill
|
||||
};
|
||||
|
||||
_browser2 = new ChromiumWebBrowser(
|
||||
Path.Combine(Program.BaseDirectory, "html/vr.html?2")
|
||||
)
|
||||
{
|
||||
DragHandler = new CefNoopDragHandler(),
|
||||
BrowserSettings =
|
||||
{
|
||||
DefaultEncoding = "UTF-8",
|
||||
},
|
||||
Dock = DockStyle.Fill
|
||||
};
|
||||
|
||||
Util.ApplyJavascriptBindings(_browser1.JavascriptObjectRepository);
|
||||
Util.ApplyJavascriptBindings(_browser2.JavascriptObjectRepository);
|
||||
|
||||
panel1.Controls.Add(_browser1);
|
||||
panel2.Controls.Add(_browser2);
|
||||
}
|
||||
|
||||
private void button_refresh_Click(object sender, System.EventArgs e)
|
||||
{
|
||||
_browser1.ExecuteScriptAsync("location.reload()");
|
||||
_browser2.ExecuteScriptAsync("location.reload()");
|
||||
VRCXVR.Instance.Refresh();
|
||||
}
|
||||
|
||||
private void button_devtools_Click(object sender, System.EventArgs e)
|
||||
{
|
||||
_browser1.ShowDevTools();
|
||||
_browser2.ShowDevTools();
|
||||
}
|
||||
}
|
||||
}
|
||||
123
Dotnet/Overlay/VRForm.resx
Normal file
123
Dotnet/Overlay/VRForm.resx
Normal file
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="timer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
Reference in New Issue
Block a user