I've created a UWP app that takes advantage of the Windows.Gaming.Input namespace, but when I deploy to my Raspberry Pi 2 B running Windows 10 IoT Core, the Gamepad.GetCurrentReading() method returns a default instance of the GamepadReading. (i.e. everything is 0)
Debugging on my local machine seems to work. Is there anything additional needed to get this API working on my device?
P.S. I noticed that one of the samples uses HidDevice, so I'll look into that as an alternative in the mean time.
Here is my (incomplete) workaround. It's a drop-in replacement for the Gamepad class.
class HidGamepad
{
static readonly List<HidGamepad> _gamepads = new List<HidGamepad>();
GamepadReading _currentReading;
static HidGamepad()
{
var deviceSelector = HidDevice.GetDeviceSelector(0x01, 0x05);
var watcher = DeviceInformation.CreateWatcher(deviceSelector);
watcher.Added += HandleAdded;
watcher.Start();
}
private HidGamepad(HidDevice device)
{
device.InputReportReceived += HandleInputReportRecieved;
}
public static event EventHandler<HidGamepad> GamepadAdded;
public static IReadOnlyList<HidGamepad> Gamepads
=> _gamepads;
public GamepadReading GetCurrentReading()
=> _currentReading;
static async void HandleAdded(DeviceWatcher sender, DeviceInformation args)
{
var hidDevice = await HidDevice.FromIdAsync(args.Id, FileAccessMode.Read);
if (hidDevice == null) return;
var gamepad = new HidGamepad(hidDevice);
_gamepads.Add(gamepad);
GamepadAdded?.Invoke(null, gamepad);
}
void HandleInputReportRecieved(
HidDevice sender, HidInputReportReceivedEventArgs args)
{
var leftThumbstickX = args.Report.GetNumericControl(0x01, 0x30).Value;
var leftThumbstickY = args.Report.GetNumericControl(0x01, 0x31).Value;
_currentReading = new GamepadReading
{
LeftThumbstickX = (leftThumbstickX - 32768) / 32768.0,
LeftThumbstickY = (leftThumbstickY - 32768) / -32768.0
};
}
}
Related
I'm working through this tutorial: https://mtaulty.com/2019/07/18/simple-shared-holograms-with-photon-networking-part-1/ with the hope of reproducing the shared coordinate system between two Hololens 2 headsets. I'm using Unity 2020, PUN2, ARFoundation and MRTK.
Because the tutorial is using WorldAnchors (WSA platform), which is a bit old, I'm trying to modify it to use ARFoundation. So far, the code I have as a result, seems to properly have the two headsets communicating via PUN2, but the blue cube as shown in the tutorial does not align between the headsets. The cube simply seems referenced to each headsets initial startup frame of reference. Below is the code. I've kept everything as one-to-one with the tutorial as possible, except where I felt I needed to swap WorldAnchors for ARAnchors and also where I swapped in a SpatialAnchorManager class to handle the Azure Spatial Service session since I found the tutorial's StartSession function didn't seem to work properly. Both AzureSpatialAnchorService.cs and PhotonScript.cs are attached to a root game object in the scene. Picture of the scene attached. Based on the debug logs I'm able to tell that the first headset is creating and saving an anchor to Azure and the second headset is able to find that same anchor. But I apparently am not performing a necessary transformation between headsets?
Can anyone suggest what I'm doing wrong and/or what specific edits that need to be made to get spatial alignment between headsets?
Thanks!
AzureSpatialAnchorService.cs:
using Microsoft.Azure.SpatialAnchors.Unity;
using Microsoft.MixedReality.Toolkit.Utilities;
using System;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.WSA;
namespace AzureSpatialAnchors
{
[RequireComponent(typeof(SpatialAnchorManager))]
public class AzureSpatialAnchorService : MonoBehaviour
{
[Serializable]
public class AzureSpatialAnchorServiceProfile
{
[SerializeField]
[Tooltip("The account id from the Azure portal for the Azure Spatial Anchors service")]
string azureAccountId;
public string AzureAccountId => this.azureAccountId;
[SerializeField]
[Tooltip("The access key from the Azure portal for the Azure Spatial Anchors service (for Key authentication)")]
string azureServiceKey;
public string AzureServiceKey => this.azureServiceKey;
}
[SerializeField]
[Tooltip("The configuration for the Azure Spatial Anchors Service")]
AzureSpatialAnchorServiceProfile profile = new AzureSpatialAnchorServiceProfile();
public AzureSpatialAnchorServiceProfile Profile => this.profile;
TaskCompletionSource<CloudSpatialAnchor> taskWaitForAnchorLocation;
//CloudSpatialAnchorSession cloudSpatialAnchorSession;
private SpatialAnchorManager _spatialAnchorManager = null;
public AzureSpatialAnchorService()
{
}
public async Task<string> CreateAnchorOnObjectAsync(GameObject gameObjectForAnchor)
{
string anchorId = string.Empty;
try
{
await this.StartSession();
Debug.Log("Started Session");
//Add and configure ASA components
CloudNativeAnchor cloudNativeAnchor = gameObjectForAnchor.AddComponent<CloudNativeAnchor>();
await cloudNativeAnchor.NativeToCloud();
Debug.Log("After NativeToCloud");
CloudSpatialAnchor cloudSpatialAnchor = cloudNativeAnchor.CloudAnchor;
cloudSpatialAnchor.Expiration = DateTimeOffset.Now.AddDays(3);
// As per previous comment.
//Collect Environment Data
while (!_spatialAnchorManager.IsReadyForCreate)
{
float createProgress = _spatialAnchorManager.SessionStatus.RecommendedForCreateProgress;
Debug.Log($"ASA - Move your device to capture more environment data: {createProgress:0%}");
}
Debug.Log($"ASA - Saving room cloud anchor... ");
await _spatialAnchorManager.CreateAnchorAsync(cloudSpatialAnchor);
anchorId = cloudSpatialAnchor?.Identifier;
bool saveSucceeded = cloudSpatialAnchor != null;
if (!saveSucceeded)
{
Debug.LogError("ASA - Failed to save, but no exception was thrown.");
return anchorId;
}
anchorId = cloudSpatialAnchor.Identifier;
Debug.Log($"ASA - Saved room cloud anchor with ID: {anchorId}");
}
catch (Exception exception) // TODO: reasonable exceptions here.
{
Debug.Log("ASA - Failed to save room anchor: " + exception.ToString());
Debug.LogException(exception);
}
return (anchorId);
}
public async Task<bool> PopulateAnchorOnObjectAsync(string anchorId, GameObject gameObjectForAnchor)
{
bool anchorLocated = false;
try
{
await this.StartSession();
this.taskWaitForAnchorLocation = new TaskCompletionSource<CloudSpatialAnchor>();
var watcher = _spatialAnchorManager.Session.CreateWatcher(
new AnchorLocateCriteria()
{
Identifiers = new string[] { anchorId },
BypassCache = true,
Strategy = LocateStrategy.AnyStrategy,
RequestedCategories = AnchorDataCategory.Spatial
}
);
var cloudAnchor = await this.taskWaitForAnchorLocation.Task;
anchorLocated = cloudAnchor != null;
if (anchorLocated)
{
Debug.Log("Anchor located");
gameObjectForAnchor.AddComponent<CloudNativeAnchor>().CloudToNative(cloudAnchor);
Debug.Log("Attached Local Anchor");
}
watcher.Stop();
}
catch (Exception ex) // TODO: reasonable exceptions here.
{
Debug.Log($"Caught {ex.Message}");
}
return (anchorLocated);
}
/// <summary>
/// Start the Azure Spatial Anchor Service session
/// This must be called before calling create, populate or delete methods.
/// </summary>
public async Task<bool> StartSession()
{
//if (this.cloudSpatialAnchorSession == null)
//{
// Debug.Assert(this.cloudSpatialAnchorSession == null);
// this.ThrowOnBadAuthConfiguration();
// // setup the session
// this.cloudSpatialAnchorSession = new CloudSpatialAnchorSession();
// // set the Azure configuration parameters
// this.cloudSpatialAnchorSession.Configuration.AccountId = this.Profile.AzureAccountId;
// this.cloudSpatialAnchorSession.Configuration.AccountKey = this.Profile.AzureServiceKey;
// // register event handlers
// this.cloudSpatialAnchorSession.Error += this.OnCloudSessionError;
// this.cloudSpatialAnchorSession.AnchorLocated += OnAnchorLocated;
// this.cloudSpatialAnchorSession.LocateAnchorsCompleted += OnLocateAnchorsCompleted;
// // start the session
// this.cloudSpatialAnchorSession.Start();
//}
_spatialAnchorManager = GetComponent<SpatialAnchorManager>();
_spatialAnchorManager.LogDebug += (sender, args) => Debug.Log($"ASA - Debug: {args.Message}");
_spatialAnchorManager.Error += (sender, args) => Debug.LogError($"ASA - Error: {args.ErrorMessage}");
_spatialAnchorManager.AnchorLocated += OnAnchorLocated;
//_spatialAnchorManager.LocateAnchorsCompleted += OnLocateAnchorsCompleted;
await _spatialAnchorManager.StartSessionAsync();
return true;
}
/// <summary>
/// Stop the Azure Spatial Anchor Service session
/// </summary>
//public void StopSession()
//{
// if (this.cloudSpatialAnchorSession != null)
// {
// // stop session
// this.cloudSpatialAnchorSession.Stop();
// // clear event handlers
// this.cloudSpatialAnchorSession.Error -= this.OnCloudSessionError;
// this.cloudSpatialAnchorSession.AnchorLocated -= OnAnchorLocated;
// this.cloudSpatialAnchorSession.LocateAnchorsCompleted -= OnLocateAnchorsCompleted;
// // cleanup
// this.cloudSpatialAnchorSession.Dispose();
// this.cloudSpatialAnchorSession = null;
// }
//}
void OnLocateAnchorsCompleted(object sender, LocateAnchorsCompletedEventArgs args)
{
Debug.Log("On Locate Anchors Completed");
Debug.Assert(this.taskWaitForAnchorLocation != null);
if (!this.taskWaitForAnchorLocation.Task.IsCompleted)
{
this.taskWaitForAnchorLocation.TrySetResult(null);
}
}
void OnAnchorLocated(object sender, AnchorLocatedEventArgs args)
{
Debug.Log($"On Anchor Located, status is {args.Status} anchor is {args.Anchor?.Identifier}, pointer is {args.Anchor?.LocalAnchor}");
Debug.Assert(this.taskWaitForAnchorLocation != null);
this.taskWaitForAnchorLocation.SetResult(args.Anchor);
}
void OnCloudSessionError(object sender, SessionErrorEventArgs args)
{
Debug.Log($"On Cloud Session Error: {args.ErrorMessage}");
}
void ThrowOnBadAuthConfiguration()
{
if (string.IsNullOrEmpty(this.Profile.AzureAccountId) ||
string.IsNullOrEmpty(this.Profile.AzureServiceKey))
{
throw new ArgumentNullException("Missing required configuration to connect to service");
}
}
}
}
PhotonScript.cs:
using System;
using System.Threading.Tasks;
using AzureSpatialAnchors;
using ExitGames.Client.Photon;
using Photon.Pun;
using Photon.Realtime;
public class PhotonScript : MonoBehaviourPunCallbacks
{
enum RoomStatus
{
None,
CreatedRoom,
JoinedRoom,
JoinedRoomDownloadedAnchor
}
public int emptyRoomTimeToLiveSeconds = 120;
RoomStatus roomStatus = RoomStatus.None;
void Start()
{
PhotonNetwork.ConnectUsingSettings();
}
public override void OnConnectedToMaster()
{
base.OnConnectedToMaster();
var roomOptions = new RoomOptions();
roomOptions.EmptyRoomTtl = this.emptyRoomTimeToLiveSeconds * 1000;
PhotonNetwork.JoinOrCreateRoom(ROOM_NAME, roomOptions, null);
}
public async override void OnJoinedRoom()
{
base.OnJoinedRoom();
// Note that the creator of the room also joins the room...
if (this.roomStatus == RoomStatus.None)
{
this.roomStatus = RoomStatus.JoinedRoom;
}
await this.PopulateAnchorAsync();
}
public async override void OnCreatedRoom()
{
base.OnCreatedRoom();
this.roomStatus = RoomStatus.CreatedRoom;
await this.CreateAnchorAsync();
}
async Task CreateAnchorAsync()
{
// If we created the room then we will attempt to create an anchor for the parent
// of the cubes that we are creating.
var anchorService = this.GetComponent<AzureSpatialAnchorService>();
var anchorId = await anchorService.CreateAnchorOnObjectAsync(this.gameObject);
// Put this ID into a custom property so that other devices joining the
// room can get hold of it.
#if UNITY_2020
PhotonNetwork.CurrentRoom.SetCustomProperties(
new Hashtable()
{
{ ANCHOR_ID_CUSTOM_PROPERTY, anchorId }
}
);
#endif
}
async Task PopulateAnchorAsync()
{
if (this.roomStatus == RoomStatus.JoinedRoom)
{
object keyValue = null;
#if UNITY_2020
// First time around, this property may not be here so we see if is there.
if (PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue(
ANCHOR_ID_CUSTOM_PROPERTY, out keyValue))
{
// If the anchorId property is present then we will try and get the
// anchor but only once so change the status.
this.roomStatus = RoomStatus.JoinedRoomDownloadedAnchor;
// If we didn't create the room then we want to try and get the anchor
// from the cloud and apply it.
var anchorService = this.GetComponent<AzureSpatialAnchorService>();
await anchorService.PopulateAnchorOnObjectAsync(
(string)keyValue, this.gameObject);
}
#endif
}
}
public async override void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
{
base.OnRoomPropertiesUpdate(propertiesThatChanged);
await this.PopulateAnchorAsync();
}
static readonly string ANCHOR_ID_CUSTOM_PROPERTY = "anchorId";
static readonly string ROOM_NAME = "HardCodedRoomName";
}
From reviewing the code and scenario, I am reading that this is in same local area. So, the ASA service will have the shared anchor as detailed in docs:
https://learn.microsoft.com/en-us/windows/mixed-reality/design/shared-experiences-in-mixed-reality
"Shared static holograms (no interactions)
Leverage Azure Spatial Anchors in your app. Enabling and sharing spatial anchors across devices allows you to create an application where users see holograms in the same place at the same time. Additional syncing across devices is needed to enable users to interact with holograms and see movements or state updates of holograms."
The tutorial that Microsoft docs point to is here if it helps comparison to the other sample:
https://learn.microsoft.com/en-us/windows/mixed-reality/develop/unity/tutorials/mr-learning-sharing-01
At startup, I try to delete all the existing hangfire jobs, but none of the methods I use seem to work.
Notice that I use a MongoDB database, but not sure it is related to the issue.
Here is how I add my job:
RecurringJob.AddOrUpdate<IPostIndexerJob>(j => j.Execute(), configuration.GetValue<string>("Job:Interval"));
Here is my code, if anyone has an idea that help?
static private void ScheduleJobs(IConfiguration configuration)
{
PurgeJobs1();
PurgeJobs2();
PurgeJobs3();
}
static private void PurgeJobs1()
{
var mon = JobStorage.Current.GetMonitoringApi();
mon.EnqueuedJobs("socloze-elasticsearch-post-indexer", 0, 99999999).ForEach(x =>
{
BackgroundJob.Delete(x.Key);
});
}
static private void PurgeJobs2()
{
var monitor = JobStorage.Current.GetMonitoringApi();
foreach (var queue in monitor.Queues())
{
PurgeQueue(queue.Name);
}
}
static private void PurgeJobs3()
{
// Clean
foreach (var item in Hangfire.JobStorage.Current.GetConnection().GetRecurringJobs())
RecurringJob.RemoveIfExists(item.Id);
}
static private void PurgeQueue(string queueName)
{
var toDelete = new List<string>();
var monitor = JobStorage.Current.GetMonitoringApi();
var queue = monitor.Queues().First(x => x.Name == queueName);
for (var i = 0; i < System.Math.Ceiling(queue.Length / 1000d); i++)
{
monitor.EnqueuedJobs(queue.Name, 1000 * i, 1000)
.ForEach(x => toDelete.Add(x.Key));
}
foreach (var jobId in toDelete)
{
BackgroundJob.Delete(jobId);
}
}
To delete recurring job (what i think you meant) you should firstly ask your storage to provide you all recurring jobs you have set:
jobStorage.GetConnection().GetRecurringJobs(); // where jobStorage is your storage instance, you can access it via JobStorage.Current in static context.
then delete a desired entry:
recurringJobManager.RemoveIfExists("someId"); // where recurringJobManager is instance of IRecurringJobManager
then PurgeJobs3() should work as expected (as it uses IRecurringJobManager under the hood). Perhaps the job identifier does not match your target?
I have a custom Nancy Bootstrapper which uses StructureMapNancyBootstrapper but the issue is the same regardless of container.
public class CustomNancyBootstrapper : StructureMapNancyBootstrapper
{
protected override void RequestStartup(IContainer container, IPipelines pipelines, NancyContext context)
{
var auth = container.GetInstance<ICustomAuth>();
auth.Authenticate(context);
}
}
I want to write a test to assert that Authenticate is called with the context... something like this...
[Test]
public void RequestStartup_Calls_CustomAuth_Authenticate_WithContext()
{
// set up
var mockAuthentication = new Mock<ICustomAuth>();
var mockContainer = new Mock<IContainer>();
var mockPipelines = new Mock<IPipelines>();
var context = new NancyContext();
mockContainer.Setup(x => x.GetInstance<ICustomAuth>()).Returns(mockAuthentication.Object);
// exercise
_bootstrapper.RequestStartup(_mockContainer.Object, _mockPipelines.Object, context);
// verify
mockAuthentication.Verify(x => x.Authenticate(context), Times.Once);
}
The problem is that I can't call RequestStartup because it's protected as defined in NancyBootstrapperBase.
protected virtual void RequestStartup(TContainer container, IPipelines pipelines, NancyContext context);
Is there a "proper"/"offical" Nancy way to do this without creating another derived class and exposing the methods as that just seems like a hack?
Thanks
I guess you can "fake" the request by using Browser from Nancy.Testing:
var browser = new Browser(new CustomNancyBootstrapper());
var response = browser.Get("/whatever");
There is a good set of articles about testing NancyFx application:
http://www.marcusoft.net/2013/01/NancyTesting1.html
Turns out Nancy offers a IRequetStartup interface so you can take the code out of the custom bootstrapper and do something like this...
public class MyRequestStart : IRequestStartup
{
private readonly ICustomAuth _customAuthentication;
public MyRequestStart(ICustomAuth customAuthentication)
{
if (customAuthentication == null)
{
throw new ArgumentNullException(nameof(customAuthentication));
}
_customAuthentication = customAuthentication;
}
public void Initialize(IPipelines pipelines, NancyContext context)
{
_customAuthentication.Authenticate(context);
}
}
and the test is easy and concise
[Test]
public void When_Initialize_Calls_CustomAuth_Authenticate_WithContext()
{
// set up
var mockAuth = new Mock<ICustomAuth>();
var requestStartup = new MyRequestStart(mockAuth.Object);
var mockPipeline = new Mock<IPipelines>();
var context = new NancyContext();
// exercise
requestStartup.Initialize(mockPipeline.Object, context);
// verify
mockAuth.Verify(x => x.Authenticate(context), Times.Once);
}
https://github.com/NancyFx/Nancy/wiki/The-Application-Before%2C-After-and-OnError-pipelines#implementing-interfaces
I love that Windows 10 now has support for virtual desktops built in, but I have some features that I'd like to add/modify (e.g., force a window to appear on all desktops, launch the task view with a hotkey, have per-monitor desktops, etc.)
I have searched for applications and developer references to help me customize my desktops, but I have had no luck.
Where should I start? I am looking for Windows API functions (ideally, that are callable from a C# application) that will give me programmatic access to manipulate virtual desktops and the windows therein.
The Windows SDK Support Team Blog posted a C# demo to switch Desktops via IVirtualDesktopManager:
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
[System.Security.SuppressUnmanagedCodeSecurity]
public interface IVirtualDesktopManager
{
[PreserveSig]
int IsWindowOnCurrentVirtualDesktop(
[In] IntPtr TopLevelWindow,
[Out] out int OnCurrentDesktop
);
[PreserveSig]
int GetWindowDesktopId(
[In] IntPtr TopLevelWindow,
[Out] out Guid CurrentDesktop
);
[PreserveSig]
int MoveWindowToDesktop(
[In] IntPtr TopLevelWindow,
[MarshalAs(UnmanagedType.LPStruct)]
[In]Guid CurrentDesktop
);
}
[ComImport, Guid("aa509086-5ca9-4c25-8f95-589d3c07b48a")]
public class CVirtualDesktopManager
{
}
public class VirtualDesktopManager
{
public VirtualDesktopManager()
{
cmanager = new CVirtualDesktopManager();
manager = (IVirtualDesktopManager)cmanager;
}
~VirtualDesktopManager()
{
manager = null;
cmanager = null;
}
private CVirtualDesktopManager cmanager = null;
private IVirtualDesktopManager manager;
public bool IsWindowOnCurrentVirtualDesktop(IntPtr TopLevelWindow)
{
int result;
int hr;
if ((hr = manager.IsWindowOnCurrentVirtualDesktop(TopLevelWindow, out result)) != 0)
{
Marshal.ThrowExceptionForHR(hr);
}
return result != 0;
}
public Guid GetWindowDesktopId(IntPtr TopLevelWindow)
{
Guid result;
int hr;
if ((hr = manager.GetWindowDesktopId(TopLevelWindow, out result)) != 0)
{
Marshal.ThrowExceptionForHR(hr);
}
return result;
}
public void MoveWindowToDesktop(IntPtr TopLevelWindow, Guid CurrentDesktop)
{
int hr;
if ((hr = manager.MoveWindowToDesktop(TopLevelWindow, CurrentDesktop)) != 0)
{
Marshal.ThrowExceptionForHR(hr);
}
}
}
it includes the API to detect on which desktop the Window is shown and it can switch and move a Windows the a Desktop.
Programmatic access to the virtual desktop feature is very limited, as Microsoft has only exposed the IVirtualDesktopManager COM interface. It does provide two key functions:
IVirtualDesktopManager::GetWindowDesktopId allows you to retrieve the ID of a virtual desktop, based on a window that is already assigned to that desktop.
IVirtualDesktopManager::MoveWindowToDesktop allows you to move a window to a specific virtual desktop.
Unfortunately, this is not nearly enough to accomplish anything useful. I've written some C# code based on the reverse-engineering work done by NickoTin. I can't read much of the Russian in his blog post, but his C++ code was pretty accurate.
I do need to emphasize that this code is not something you want to commit to in a product. Microsoft always feels free to change undocumented APIs whenever they feel like it. And there is a runtime risk as well: this code does not necessarily interact well when the user is tinkering with the virtual desktops. Always keep in mind that a virtual desktop can appear and disappear at any time, completely out of sync with your code.
To use the code, create a new C# class library project. I'll first post ComInterop.cs, it contains the COM interface declarations that match NickoTin's C++ declarations:
using System;
using System.Runtime.InteropServices;
namespace Windows10Interop {
internal static class Guids {
public static readonly Guid CLSID_ImmersiveShell =
new Guid(0xC2F03A33, 0x21F5, 0x47FA, 0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39);
public static readonly Guid CLSID_VirtualDesktopManagerInternal =
new Guid(0xC5E0CDCA, 0x7B6E, 0x41B2, 0x9F, 0xC4, 0xD9, 0x39, 0x75, 0xCC, 0x46, 0x7B);
public static readonly Guid CLSID_VirtualDesktopManager =
new Guid("AA509086-5CA9-4C25-8F95-589D3C07B48A");
public static readonly Guid IID_IVirtualDesktopManagerInternal =
new Guid("AF8DA486-95BB-4460-B3B7-6E7A6B2962B5");
public static readonly Guid IID_IVirtualDesktop =
new Guid("FF72FFDD-BE7E-43FC-9C03-AD81681E88E4");
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("FF72FFDD-BE7E-43FC-9C03-AD81681E88E4")]
internal interface IVirtualDesktop {
void notimpl1(); // void IsViewVisible(IApplicationView view, out int visible);
Guid GetId();
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("AF8DA486-95BB-4460-B3B7-6E7A6B2962B5")]
internal interface IVirtualDesktopManagerInternal {
int GetCount();
void notimpl1(); // void MoveViewToDesktop(IApplicationView view, IVirtualDesktop desktop);
void notimpl2(); // void CanViewMoveDesktops(IApplicationView view, out int itcan);
IVirtualDesktop GetCurrentDesktop();
void GetDesktops(out IObjectArray desktops);
[PreserveSig]
int GetAdjacentDesktop(IVirtualDesktop from, int direction, out IVirtualDesktop desktop);
void SwitchDesktop(IVirtualDesktop desktop);
IVirtualDesktop CreateDesktop();
void RemoveDesktop(IVirtualDesktop desktop, IVirtualDesktop fallback);
IVirtualDesktop FindDesktop(ref Guid desktopid);
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
internal interface IVirtualDesktopManager {
int IsWindowOnCurrentVirtualDesktop(IntPtr topLevelWindow);
Guid GetWindowDesktopId(IntPtr topLevelWindow);
void MoveWindowToDesktop(IntPtr topLevelWindow, ref Guid desktopId);
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("92CA9DCD-5622-4bba-A805-5E9F541BD8C9")]
internal interface IObjectArray {
void GetCount(out int count);
void GetAt(int index, ref Guid iid, [MarshalAs(UnmanagedType.Interface)]out object obj);
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("6D5140C1-7436-11CE-8034-00AA006009FA")]
internal interface IServiceProvider10 {
[return: MarshalAs(UnmanagedType.IUnknown)]
object QueryService(ref Guid service, ref Guid riid);
}
}
Next is Desktop.cs. It contains the friendly C# classes that you can use in your code:
using System;
using System.Runtime.InteropServices;
namespace Windows10Interop
{
public class Desktop {
public static int Count {
// Returns the number of desktops
get { return DesktopManager.Manager.GetCount(); }
}
public static Desktop Current {
// Returns current desktop
get { return new Desktop(DesktopManager.Manager.GetCurrentDesktop()); }
}
public static Desktop FromIndex(int index) {
// Create desktop object from index 0..Count-1
return new Desktop(DesktopManager.GetDesktop(index));
}
public static Desktop FromWindow(IntPtr hWnd) {
// Creates desktop object on which window <hWnd> is displayed
Guid id = DesktopManager.WManager.GetWindowDesktopId(hWnd);
return new Desktop(DesktopManager.Manager.FindDesktop(ref id));
}
public static Desktop Create() {
// Create a new desktop
return new Desktop(DesktopManager.Manager.CreateDesktop());
}
public void Remove(Desktop fallback = null) {
// Destroy desktop and switch to <fallback>
var back = fallback == null ? DesktopManager.GetDesktop(0) : fallback.itf;
DesktopManager.Manager.RemoveDesktop(itf, back);
}
public bool IsVisible {
// Returns <true> if this desktop is the current displayed one
get { return object.ReferenceEquals(itf, DesktopManager.Manager.GetCurrentDesktop()); }
}
public void MakeVisible() {
// Make this desktop visible
DesktopManager.Manager.SwitchDesktop(itf);
}
public Desktop Left {
// Returns desktop at the left of this one, null if none
get {
IVirtualDesktop desktop;
int hr = DesktopManager.Manager.GetAdjacentDesktop(itf, 3, out desktop);
if (hr == 0) return new Desktop(desktop);
else return null;
}
}
public Desktop Right {
// Returns desktop at the right of this one, null if none
get {
IVirtualDesktop desktop;
int hr = DesktopManager.Manager.GetAdjacentDesktop(itf, 4, out desktop);
if (hr == 0) return new Desktop(desktop);
else return null;
}
}
public void MoveWindow(IntPtr handle) {
// Move window <handle> to this desktop
DesktopManager.WManager.MoveWindowToDesktop(handle, itf.GetId());
}
public bool HasWindow(IntPtr handle) {
// Returns true if window <handle> is on this desktop
return itf.GetId() == DesktopManager.WManager.GetWindowDesktopId(handle);
}
public override int GetHashCode() {
return itf.GetHashCode();
}
public override bool Equals(object obj) {
var desk = obj as Desktop;
return desk != null && object.ReferenceEquals(this.itf, desk.itf);
}
private IVirtualDesktop itf;
private Desktop(IVirtualDesktop itf) { this.itf = itf; }
}
internal static class DesktopManager {
static DesktopManager() {
var shell = (IServiceProvider10)Activator.CreateInstance(Type.GetTypeFromCLSID(Guids.CLSID_ImmersiveShell));
Manager = (IVirtualDesktopManagerInternal)shell.QueryService(Guids.CLSID_VirtualDesktopManagerInternal, Guids.IID_IVirtualDesktopManagerInternal);
WManager = (IVirtualDesktopManager)Activator.CreateInstance(Type.GetTypeFromCLSID(Guids.CLSID_VirtualDesktopManager));
}
internal static IVirtualDesktop GetDesktop(int index) {
int count = Manager.GetCount();
if (index < 0 || index >= count) throw new ArgumentOutOfRangeException("index");
IObjectArray desktops;
Manager.GetDesktops(out desktops);
object objdesk;
desktops.GetAt(index, Guids.IID_IVirtualDesktop, out objdesk);
Marshal.ReleaseComObject(desktops);
return (IVirtualDesktop)objdesk;
}
internal static IVirtualDesktopManagerInternal Manager;
internal static IVirtualDesktopManager WManager;
}
}
And finally a little test WinForms project that I used to test the code. Just drop 4 buttons on a form and name them buttonLeft/Right/Create/Destroy:
using Windows10Interop;
using System.Diagnostics;
...
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void buttonRight_Click(object sender, EventArgs e) {
var curr = Desktop.FromWindow(this.Handle);
Debug.Assert(curr.Equals(Desktop.Current));
var right = curr.Right;
if (right == null) right = Desktop.FromIndex(0);
if (right != null) {
right.MoveWindow(this.Handle);
right.MakeVisible();
this.BringToFront();
Debug.Assert(right.IsVisible);
}
}
private void buttonLeft_Click(object sender, EventArgs e) {
var curr = Desktop.FromWindow(this.Handle);
Debug.Assert(curr.Equals(Desktop.Current));
var left = curr.Left;
if (left == null) left = Desktop.FromIndex(Desktop.Count - 1);
if (left != null) {
left.MoveWindow(this.Handle);
left.MakeVisible();
this.BringToFront();
Debug.Assert(left.IsVisible);
}
}
private void buttonCreate_Click(object sender, EventArgs e) {
var desk = Desktop.Create();
desk.MoveWindow(this.Handle);
desk.MakeVisible();
Debug.Assert(desk.IsVisible);
Debug.Assert(desk.Equals(Desktop.Current));
}
private void buttonDestroy_Click(object sender, EventArgs e) {
var curr = Desktop.FromWindow(this.Handle);
var next = curr.Left;
if (next == null) next = curr.Right;
if (next != null && next != curr) {
next.MoveWindow(this.Handle);
curr.Remove(next);
Debug.Assert(next.IsVisible);
}
}
}
The only real quirk I noticed while testing this is that moving a window from one desktop to another can move it to the bottom of the Z-order when you first switch the desktop, then move the window. No such problem if you do it the other way around.
There is this guy that made a application to map keyboard shorcut to move a window between virtual desktop.
https://github.com/Grabacr07/SylphyHorn
(I use it every day )
He has a blog where he explain what he did
http://grabacr.net/archives/5701 ( you can use google translate it is in japanese)
He in fact used the same api mantionned in the Alberto Tostado response.
http://www.cyberforum.ru/blogs/105416/blog3671.html
and the api can be found on his github https://github.com/Grabacr07/VirtualDesktop
The api is really simple to use BUT it seems impossible to move a window from another process.
public static bool MoveToDesktop(IntPtr hWnd, VirtualDesktop virtualDesktop)
{
ThrowIfNotSupported();
int processId;
NativeMethods.GetWindowThreadProcessId(hWnd, out processId);
if (Process.GetCurrentProcess().Id == processId) // THAT LINE
{
var guid = virtualDesktop.Id;
VirtualDesktop.ComManager.MoveWindowToDesktop(hWnd, ref guid);
return true;
}
return false;
}
To workaround this problem they made another implementation that they use alongside the one in the russian blog
if (VirtualDesktopHelper.MoveToDesktop(hWnd, right) //<- the one in the russian blog
|| this.helper.MoveWindowToDesktop(hWnd, right.Id)) <- the second implementation
The second implementation can be found here: https://github.com/tmyt/VDMHelper
This one can move a window from another process to another desktop. BUT it is buggy right now. For exemple when i try to move some window like google chrome it crash.
So this is the result of my research. I m rigth now trying to make a StickyWindow feature with these api.
I fear that all about "Virtual desktops" in Windows 10 is undocumented, but in a Russian page I've seen documented the interfaces. I don't speak Russian but seems that they have used reversed engineering. Anyway, the code is very clear (Thanks to them!).
Keep an eye here:
http://www.cyberforum.ru/blogs/105416/blog3671.html
I've been trying to see if the old API's CreateDesktop, OpenDesktop, etc... is linked to the new Virtual-Desktops, but no way...
The interfaces work with the final production release of Windows 10 (2015-05-08), but you shouldn't use them in a real wide distributed application until Microsoft documents them. Too much risk.
Regards.
I have a problem when running multiple impersonations of users in EWS, when I want to recieve notifications on each of the impersonated persons calendars (possible 100 persons).
Currently I have an outlook account who have rights to impersonate all other users, and all the ExchangeService-objects get this accounts credentials
Short version is, that when I try to bind to an appointment via the unique ID it works as long as I only have one thread running. When I start a new thread containing a new Exchangeservice with its own subscription I dont recieve any response on the Appointment.Bind()-request.
When I run two instances of my program with only 1 thread in each it works fine, but as soon as I start a new thread with a new ExchangeService the Appointment.Bind() doesnt give any response.
The weird part about this is, that it worked fine two weeks ago, but suddenly it stopped working and I didnt change my code.
I have created a quick demo of my problem:
class Program
{
static void Main(string[] args)
{
var x = new OutlookListener("user1#server.com");
var y = new OutlookListener("user2#server.com");
new Thread(x.Start).Start();
new Thread(y.Start).Start();
while (true)
{
}
}
}
class OutlookListener
{
private ExchangeService _ExchangeService;
private AutoResetEvent _Signal;
public OutlookListener(string emailToImp)
{
_ExchangeService = new ExchangeService(ExchangeVersion.Exchange2010_SP1)
{
Credentials = new NetworkCredential("superuser#server.com", "password"),
Url = new Uri("exchangeUrl"),
ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, emailToImp)
};
}
public void Start()
{
var subscription = _ExchangeService.SubscribeToStreamingNotifications(new FolderId[] { WellKnownFolderName.Calendar },
EventType.Created);
var connection = CreateStreamingSubscription(_ExchangeService, subscription);
Console.Out.WriteLine("Subscription created.");
_Signal = new AutoResetEvent(false);
_Signal.WaitOne();
subscription.Unsubscribe();
connection.Close();
}
private StreamingSubscriptionConnection CreateStreamingSubscription(ExchangeService service, StreamingSubscription subscription)
{
var connection = new StreamingSubscriptionConnection(service, 30);
connection.AddSubscription(subscription);
connection.OnNotificationEvent += OnNotificationEvent;
connection.OnSubscriptionError += OnSubscriptionError;
connection.OnDisconnect += OnDisconnect;
connection.Open();
return connection;
}
private void OnNotificationEvent(object sender, NotificationEventArgs args)
{
// Extract the item ids for all NewMail Events in the list.
var newMails = from e in args.Events.OfType<ItemEvent>()
where e.EventType == EventType.Created
select e.ItemId;
foreach (var newMail in newMails)
{
var appointment= Appointment.Bind(_ExchangeService, newMail); //This is where I dont get a response!
Console.WriteLine(appointment.Subject);
}
}
private void OnSubscriptionError(object sender, SubscriptionErrorEventArgs args)
{
}
private void OnDisconnect(object sender, SubscriptionErrorEventArgs args)
{
}
}
Any suggestions?
I have had the same issue and found that my EWS solution was limited by two factors.
The System.Net.ServicePointManager.DefaultConnectionLimit is by default set to 2, which I've changed to 20 which i beleive to match the throttling policy of Exchange Online.
Second the ConnectionGroupName property on the ExchangeService object can be used to pool connections into different relevant groups which have a limit of concurrent connections cohernet with the DefaultConnectionLimit property.
A way to override the settings is to set the ConnectionGroupName property to a uniquevalue for each ExchangeService object you create.
ExchangeService exchangeService = new ExchangeService()
{
ConnectionGroupName = Guid.NewGuid().ToString()
};
Why do you need multiple threads ?
In my case , I have created a dictionary of Services based on the smtpaddress for each email I want to impersonate, and I subscribe to them all. All can happen in one thread, and all notification from any user will be handled in the OnNotificationEvent .
[THIS CODE IS JUST TO SHOW THE LOGIC AND IS NOT COMPLETE FOR FULL COMPILATION AND RUN]
var service = new ExchangeService(exchangeVersion);
var serviceCred = ((System.Net.NetworkCredential)(((WebCredentials)(Services.First().Value.Credentials)).Credentials));
service.Credentials = new WebCredentials(serviceCred.UserName, serviceCred.Password);
service.AutodiscoverUrl(userSmtp, RedirectionUrlValidationCallback);
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, userSmtp);
Services.Add(userSmtp, service);
Note that Services.First().Value is the service that can impersonate all the other users, and here it is cloned as the number of the user.
After that Subscriptions for the all services (note that now each service is impersonating different user)
foreach (var service in Services.Values)
{
SubscribeToService(service);
}
and the definition for SubscribeToService is as follow
private void SubscribeToService(ExchangeService service)
{
if (service.ImpersonatedUserId == null)
return;
if (service.Url == null)
return;
var serviceName = service.ImpersonatedUserId.Id;
var streamingSubscription =
service.SubscribeToStreamingNotifications(new FolderId[] { WellKnownFolderName.DeletedItems, WellKnownFolderName.Calendar },
EventType.FreeBusyChanged, EventType.Moved, EventType.Created, EventType.Modified);
if (!Connections.ContainsKey(service.Url))
{
Connections.Add(service.Url, new StreamingSubscriptionConnection(service, 30));
}
var connection = Connections[service.Url];
CloseConnection(connection);
if (!_subscriptions.ContainsKey(serviceName))
{
_subscriptions.Add(serviceName, streamingSubscription);
connection.AddSubscription(streamingSubscription);
}
}
}
All of this can happen in one single thread, and I hope my answer will help you
Cheers