I'm working on a debug feature, that shows an in-app log console inspired by https://github.com/duraidabdul/LocalConsole.
In the perfect world I want to hi-jack NSLog and show messages in the log console, but it's not straight forward. I even remember trying this many years ago in Obj-C.
This is what I got so far, it runs without exceptions, but LogCString is never called:
public static class NSLogHiJacker
{
public static void HiJack()
{
try
{
var del = new LogCStringDelegate(LogCString);
IntPtr func = Marshal.GetFunctionPointerForDelegate(del);
var handle = GCHandle.Alloc(func);
NSSetLogCStringFunction(func);
handle.Free();
Console.WriteLine("NSLog was hi-jacked!");
}
catch (Exception e)
{
// ignored
}
}
[DllImport(Constants.ObjectiveCLibrary, EntryPoint = "_NSSetLogCStringFunction")]
private extern static void NSSetLogCStringFunction(IntPtr funcPointer);
[MonoNativeFunctionWrapper]
public delegate void LogCStringDelegate (IntPtr message, UIntPtr length, bool withSyslogBanner);
[MonoPInvokeCallback (typeof (LogCStringDelegate))]
public static void LogCString(IntPtr message, UIntPtr length, bool withSyslogBanner)
{
// Now what?
}
}
Anybody tried this before? I know it's one big hack, but I don't care, since it's a debug feature that will never hit App Store.
Related
I am working on a console application that is supposed to catch the event when the clipboard content changes. There is an API in WinRT for this, Windows.ApplicationModel.DataTransfer.Clipboard.ContentChanged. I have already tested this on a WinForms and WPF application, and it works perfectly. However I am experiencing problems when doing this in a console application. The code is pretty basic. When doing it on a WinForms application, I simply write this line of code:
public MyApp()
{
InitializeComponent();
Windows.ApplicationModel.DataTransfer.Clipboard.ContentChanged += OnClipboardChanged;
}
public async void OnClipboardChanged(Object sender, Object e)
{
MyCodeHere
}
However when trying to do the same in my console application:
class Program
{
[STAThread]
static void Main(string[] args)
{
Windows.ApplicationModel.DataTransfer.Clipboard.ContentChanged += OnClipboardChanged;
}
public static void OnClipboardChanged(Object sender, Object e)
{
Console.WriteLine("Hello");
}
}
Yet the console just exits after starting it. If I put "Console.ReadKey" then it still errors but does not exit. Neither way invokes the event I have written in Main. I want the console to be running and not end, even if there is a clipboard change. So it should constantly run in the background, and everytime the clipboard changes then it should just WriteLine a "Hello" to the console. I have gone through all the other answers but none of them works for me, because they want to manipulate the clipboard whereas I am invoking an event on the content change of the clipboard. Thanks for all the help!
Another question, will there be any perfomance difference if I use C++/winRT instead?
In a console context, you must ensure windows messages are processed as clipboard depends on it, so for example, you can use Winforms' DoEvents method (if you don't have a real window and nothing pumps messages):
class Program
{
static void Main()
{
Windows.ApplicationModel.DataTransfer.Clipboard.ContentChanged += (s, e) => Console.WriteLine("ContentChanged");
Console.WriteLine("Press any key to quit");
do
{
System.Windows.Forms.Application.DoEvents();
if (Console.KeyAvailable)
break;
Thread.Sleep(100); // for example
}
while (true);
}
}
To enable Winforms support in a .NET 5 Console project, here is the simplest way of doing it (you don't even need to add the Windows.SDK nuget package), just modify the .csproj to something like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<DisableWinExeOutputInference>true</DisableWinExeOutputInference>
</PropertyGroup>
</Project>
If you don't have .NET 5, or don't want to reference Winforms, then you can declare your own message pump using P/Invoke, like this:
class Program
{
[STAThread]
static void Main()
{
Windows.ApplicationModel.DataTransfer.Clipboard.ContentChanged += (s, e) => Console.WriteLine("ContentChanged");
Console.WriteLine("Press any key to quit");
do
{
while (GetMessage(out var msg, IntPtr.Zero, 0, 0) != 0)
{
TranslateMessage(ref msg);
DispatchMessage(ref msg);
}
if (Console.KeyAvailable)
break;
Thread.Sleep(100);
Console.Write(".");
}
while (true);
}
[StructLayout(LayoutKind.Sequential)]
private struct MSG
{
public IntPtr hwnd;
public int message;
public IntPtr wParam;
public IntPtr lParam;
public int time;
public POINT pt;
public int lPrivate;
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
}
[DllImport("user32")]
private static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, int wMsgFilterMin, int wMsgFilterMax);
[DllImport("user32")]
private static extern bool TranslateMessage(ref MSG lpMsg);
[DllImport("user32")]
private static extern IntPtr DispatchMessage(ref MSG lpmsg);
}
I'm trying to listen for macOS sleep/wake events using C# .Net Core in Visual Studio for Mac. After searching the forum and various documentation I've written some code that should apparently receive these notifications. Here it is:
using System;
using System.Runtime.InteropServices;
using System.Text;
using CFNotificationCenterRef = System.IntPtr;
namespace NativeMac
{
// I've noticed that some people are using this attribute to decorate their callback method.
[AttributeUsage(AttributeTargets.Method)]
public sealed class MonoPInvokeCallbackAttribute : Attribute
{
public MonoPInvokeCallbackAttribute(Type delegateType)
{
DelegateType = delegateType;
}
public Type DelegateType { get; }
}
// Delegate definition for a notification center callback.
delegate void CFNotificationCallback(CFNotificationCenterRef center, IntPtr observer, IntPtr name, IntPtr obj, IntPtr userInfo);
// Required enum for the call to CFNotificationCenterAddObserver.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1008:Enums should have zero value", Justification = "There is no zero value specified for NotificationSuspensionBehavior.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1028:Enum Storage should be Int32", Justification = "Required to be long to work with IntPtr.")]
public enum CFNotificationSuspensionBehavior : long
{
Drop = 1,
Coalesce = 2,
Hold = 3,
DeliverImmediately = 4
}
/// <summary>
/// The intention is for this class to allow us to register for the Mac's native sleep and wake events.
/// </summary>
public static class MacPowerMode
{
private enum CFStringEncoding : uint
{
UTF16 = 0x0100,
UTF16BE = 0x10000100,
UTF16LE = 0x14000100,
ASCII = 0x0600
}
/// <summary>
/// Native methods we can call on the Mac.
/// </summary>
private static class NativeMethods
{
private const string FoundationFramework = "/System/Library/Frameworks/Foundation.framework/Foundation";
private const string AppKitFramework = "/System/Library/Frameworks/AppKit.framework/AppKit";
[DllImport(AppKitFramework, CharSet = CharSet.Ansi)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments", Justification = "objc_getClass method requires CharSet.Ansi to work.")]
public static extern IntPtr objc_getClass(string name);
[DllImport(FoundationFramework, EntryPoint = "objc_msgSend")]
public static extern IntPtr objc_msgSend_retIntPtr(IntPtr target, IntPtr selector);
[DllImport(AppKitFramework)]
public static extern IntPtr NSSelectorFromString(IntPtr cfstr);
[DllImport(FoundationFramework)]
public static extern void CFRelease(IntPtr handle);
[DllImport(FoundationFramework)]
public static extern IntPtr CFStringCreateWithBytes(IntPtr allocator, IntPtr buffer, long bufferLength, CFStringEncoding encoding, bool isExternalRepresentation);
[DllImport(FoundationFramework)]
public static extern unsafe void CFNotificationCenterAddObserver(CFNotificationCenterRef center, IntPtr observer,
CFNotificationCallback callback, IntPtr name, IntPtr obj,
CFNotificationSuspensionBehavior suspensionBehavior);
}
public static void Initialise()
{
Console.WriteLine("Init Mac power mode test.");
// Get the appropriate notification centre for system notifications.
var nsWorkspace = NativeMethods.objc_getClass("NSWorkspace");
var sharedWorkspace = NativeMethods.objc_msgSend_retIntPtr(nsWorkspace, GetSelector("sharedWorkspace"));
var notificationCenter = NativeMethods.objc_msgSend_retIntPtr(sharedWorkspace, GetSelector("notificationCenter"));
// Create the names of the notifications that we will be listening for.
var nsWorkspaceWillSleepNotification = CreateCFString("NSWorkspaceWillSleepNotification");
var nsWorkspaceDidWakeNotification = CreateCFString("NSWorkspaceDidWakeNotification");
// Listen out for when the Mac is about to sleep. (Test by selecting 'sleep' from the Apple Menu.)
NativeMethods.CFNotificationCenterAddObserver(center: notificationCenter,
observer: notificationCenter,
callback: NotificationCallbackDelegate,
name: nsWorkspaceWillSleepNotification,
obj: IntPtr.Zero,
suspensionBehavior: CFNotificationSuspensionBehavior.DeliverImmediately);
// Listen out for when the Mac is awoken. (Test by pressing space bar whilst sleeping, to wake Mac.)
NativeMethods.CFNotificationCenterAddObserver(center: notificationCenter,
observer: notificationCenter,
callback: NotificationCallbackDelegate,
name: nsWorkspaceDidWakeNotification,
obj: IntPtr.Zero,
suspensionBehavior: CFNotificationSuspensionBehavior.DeliverImmediately);
}
private static IntPtr GetSelector(string name)
{
IntPtr cfstrSelector = CreateCFString(name);
IntPtr selector = NativeMethods.NSSelectorFromString(cfstrSelector);
NativeMethods.CFRelease(cfstrSelector);
return selector;
}
private static unsafe IntPtr CreateCFString(string aString)
{
var bytes = Encoding.Unicode.GetBytes(aString);
fixed (byte* b = bytes)
{
var cfStr = NativeMethods.CFStringCreateWithBytes(IntPtr.Zero, (IntPtr)b, bytes.Length, CFStringEncoding.UTF16, false);
return cfStr;
}
}
// Delegate definition for a notification center callback.
private delegate void CFNotificationCallback(CFNotificationCenterRef center, IntPtr observer, IntPtr name, IntPtr obj, IntPtr userInfo);
private static CFNotificationCallback NotificationCallbackDelegate = NotificationCallback;
[MonoPInvokeCallback(typeof(CFNotificationCallback))]
private static void NotificationCallback(CFNotificationCenterRef centerPtr, IntPtr observer, IntPtr name, IntPtr obj, IntPtr userInfo)
{
Console.WriteLine($"NotificationCallback received {name}");
}
}
}
There are no errors reported when I call the code, however the callback method never receives a notification for either sleep or wake. I've even tried creating a simple Cocoa app in XCode to see if I can receive the events at all, and that worked fine.
Various links that I've researched:
Apple Documentation Archive: Registering and unregistering for sleep and wake notifications
Apple Developer Documentation: CFNotificationCenter
Xamarin C# Implementation of CFNotificationCenter
What am I doing wrong?
EDIT (11:30 23/09/2020): I've created a static delegate and changed the code above accordingly as per Hans Passant's suggestion in the comments, the code runs but I still do not receive a notification.
I have a C++ DLL that I need to use in may c# project.
Here is the important part of my code:
public static class MTSCRA_API
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void DataReceiveDelegate([MarshalAsAttribute(UnmanagedType.LPStr)]String x);
//More methods....
[DllImport("MTSCRA.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
public static extern void OnDeviceConnectionStateChanged(IntPtr lpFuncNotify);
}
Where I use it:
public void Open()
{
if (!MTSCRA_API.IsDeviceConnected())
{
UInt32 result = MTSCRA_API.OpenDevice("");
if (result == 0)
{
MTSCRA_API.OnDataReceived(
Marshal.GetFunctionPointerForDelegate(
new Kiosk.Hardware.CardReaderMagTek.MTSCRA_API.DataReceiveDelegate(CardReaderMagTek_OnCardDataReceived)));
}
}
}
Mutex mutex = new Mutex();
void CardReaderMagTek_OnCardDataReceived(String info)
{
try
{
//Do stuff
}
catch(Exception ex)
{
}
finally
{
mutex.ReleaseMutex();
}
MTSCRA_API.ClearCardData();
info = null;
}
Each time I swipe a card in a device, the CardReaderMagTek_OnCardDataReceived() event is called.
The Open() method is executed and the event CardReaderMagTek_OnCardDataReceived() is called but only 9 times. A the 10ยบ the code crash with a NullReferenceException without entering in the event and I don't have access to the callstack...
Anyone knows what could be the problem?
MTSCRA_API.OnDataReceived(
Marshal.GetFunctionPointerForDelegate(
new Kiosk.Hardware.CardReaderMagTek.MTSCRA_API.DataReceiveDelegate(
CardReaderMagTek_OnCardDataReceived)
)
);
You are not keeping your delegate alive. You create an instance of DataReceiveDelegate and pass it to GetFunctionPointerForDelegate. But after GetFunctionPointerForDelegate returns, there's no reason for the delegate to stay alive. At some point it will be collected.
Hold the delegate in a managed variable for as long as the unmanaged function needs to be able to call it.
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'm running the following c# code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Net;
using System.IO;
namespace TCLRunner
{
class Program
{
private static TclInterpreter interp;
static void Main(string[] args)
{
try
{
interp = new TclInterpreter();
//interp.evalScript(#"cd ..");
interp.evalScript(#"set a ""this is a""");
interp.evalScript(#"puts ""Free text""");
interp.evalScript(#"puts $a");
interp.evalScript(#"package require Tcl");
printResults();
interp.evalScript(#"package require http");
printResults();
//
//
// Shutdown and end connection
}
catch (Exception e)
{
Console.WriteLine("SocketException: {0}", e);
}
}
public static void printResults()
{
string result = interp.Result;
Console.WriteLine("Received: {0}", result);
}
}
public class TclAPI
{
[DllImport("tcl84.DLL")]
public static extern IntPtr Tcl_CreateInterp();
[DllImport("tcl84.Dll")]
public static extern int Tcl_Eval(IntPtr interp, string skript);
[DllImport("tcl84.Dll")]
public static extern IntPtr Tcl_GetObjResult(IntPtr interp);
[DllImport("tcl84.Dll")]
unsafe public static extern char* Tcl_GetStringFromObj(IntPtr tclObj, IntPtr length);
}
public class TclInterpreter
{
private IntPtr interp;
public TclInterpreter()
{
interp = TclAPI.Tcl_CreateInterp();
if (interp == IntPtr.Zero)
{
throw new SystemException("can not initialize Tcl interpreter");
}
}
public int evalScript(string script)
{
return TclAPI.Tcl_Eval(interp, script);
}
unsafe public string Result
{
get
{
IntPtr obj = TclAPI.Tcl_GetObjResult(interp);
if (obj == IntPtr.Zero)
{
return "";
}
else
{
return Marshal.PtrToStringAnsi((IntPtr)TclAPI.Tcl_GetStringFromObj(obj, IntPtr.Zero));
}
}
}
}
}
I get the following output:
Free text
this is a
Received: 8.4
Received: can't find package http
How comes it doesn't find http package?
When I try the same thing manually on tclsh, it works with no problems.
Thanks!
You need to initialize the library before calling Tcl_CreateInterp(). The library is initialized by calling Tcl_FindExecutable with the name of the binary running things as known, but that can be left as NULL relatively safely; it's the other things that (poorly-named) function does that matter, such as setting up where to find its libraries.
The correct declaration in C# is this (though that MarshalAs attribute is optional in this case):
[DllImport("tcl84.DLL")]
public static extern void Tcl_FindExecutable([MarshalAs(UnmanagedType.LPTStr)] string s);
And you invoke it like this, exactly once:
TclAPI.Tcl_FindExecutable(null);
(It's probably wisest to do so as part of a static constructor in the TclAPI class. Yes, it belongs that early.)
If that doesn't work (I really can't test!) you'll have to set up some environment variables. The key one will be TCL_LIBRARY which overrides the default location Tcl looks for its support scripts (including the http package), and you might need to set TCLLIBPATH as well. (Note that TCL_LIBRARY is there mainly to support pre-installation testing, but has a history of being set at the system level by Python other systems that ought not to. Beware!)
Again, this must be done prior to the Tcl_CreateInterp() call.