Altering Win10 virtual desktop behavior

后端 未结 1 993
眼角桃花
眼角桃花 2020-12-05 12:04

I love that windows now has virtual desktops built in, but I have some features that I\'d like to add/modify. (force a window to appear on all desktops, launch the task view

相关标签:
1条回答
  • 2020-12-05 12:56

    Programmatic access to the virtual desktop feature is very limited, Microsoft has only exposed the IVirtualDesktopManager COM interface. 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 any 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, this code does not necessarily interact well when the user is tinkering with the virtual desktops as well. Always keep in mind that a virtual desktop can appear and disappear at any time, completely out of sync with your 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.

    0 讨论(0)
提交回复
热议问题