Quantcast
Channel: C# – grabacr.nét
Viewing all articles
Browse latest Browse all 30

Windows 10 の仮想デスクトップを制御しようとして失敗した話

$
0
0

Windows 10 で仮想デスクトップ機能が加わり、個人的にそこそこ活用しているつもりなのですが、残念な点が 1 つ。 アクティブ ウィンドウごと仮想デスクトップを移動したいことが結構あるのですが、それに対するショートカット キーがありません。

仮想デスクトップ間の移動は Ctrl + Win + 左右キー ですが、ウィンドウも一緒に、となると基本的にはマウス操作を伴います (しかも結構面倒なやつ)。 一応、マウス使わずにやる方法もあるようですが、Ctrl + Win + 左右キー に近い手軽なものがほしいです… (フィードバック済み)

で、公式で実装されるまでは自分でアプリ作るか、と思って Windows 10 の仮想デスクトップ周りの API を調べ始め、最終的に頓挫しました。 具体的には、以下のような状況です。

  • アプリ (C# コード) から仮想デスクトップの作成、削除、移動はできた
  • 同一プロセス内のウィンドウを、任意の仮想デスクトップに移動させることはできた
  • 他プロセスのウィンドウは、他の仮想デスクトップに移動させることはできなかった

一番やりたかったのが「グローバル キーフックを使って現在アクティブなウィンドウを別な仮想デスクトップに飛ばすアプリ」だったのですが、3 番目の理由により現時点では実現できませんでした。 ざんねん。

しかしまあ、せっかく調べた&仮想デスクトップ関連 API の C# ラッパー的なものを作ったので、公開しておきます。 だいたいここ見ながら作りました:

2015/09/14 追記:
最終的に完成しました。
http://grabacr.net/archives/5701

IVirtualDesktopManager

という、COM interface が追加されています。 Windows SDK for Windows 10 をインストールした環境で、C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\um\ShObjIdl.h あたりに定義されてるっぽい。

なので、以下のように。

[ComImport]
[Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IVirtualDesktopManager
{
    bool IsWindowOnCurrentVirtualDesktop(IntPtr topLevelWindow);

    Guid GetWindowDesktopId(IntPtr topLevelWindow);

    void MoveWindowToDesktop(IntPtr topLevelWindow, ref Guid desktopId);
}

一方、VirtualDesktopManager class は CSLID “aa509086-5ca9-4c25-8f95-589d3c07b48a” で定義されています。 ですので、インスタンスは次のように作成します。

public static IVirtualDesktopManager GetVirtualDesktopManager()
{
    var vdmType = Type.GetTypeFromCLSID(CLSID.VirtualDesktopManager);
    var instance = Activator.CreateInstance(vdmType);

    return (IVirtualDesktopManager)instance;
}

Windows 10 の仮想デスクトップは GUID を識別子として管理されているようですね。 しかし、この I/F で実現できる機能は、指定したウィンドウ ハンドルのトップレベル ウィンドウに対して

  • 現在の仮想デスクトップ上にいるかどうかを判定
  • 属している仮想デスクトップの ID を取得
  • 指定した ID の仮想デスクトップへ移動

であり、そもそも今仮想デスクトップがいくつあるかを判断したり、仮想デスクトップ自体を追加・削除したりするのは難しそうです。

そこで、以下を使います。

IVirtualDesktopManagerInternal

MSDN には載ってません (先のロシア語の記事はどこからこれ持ってきたんだろう、、、)。 で、以下のようなメンバーらしい、です。

[ComImport]
[Guid("af8da486-95bb-4460-b3b7-6e7a6b2962b5")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IVirtualDesktopManagerInternal
{
    int GetCount();

    void MoveViewToDesktop(object pView, IVirtualDesktop desktop);

    bool CanViewMoveDesktops(object pView);

    IVirtualDesktop GetCurrentDesktop();

    IObjectArray GetDesktops();

    IVirtualDesktop GetAdjacentDesktop(IVirtualDesktop pDesktopReference, AdjacentDesktop uDirection);

    void SwitchDesktop(IVirtualDesktop desktop);

    IVirtualDesktop CreateDesktopW();

    void RemoveDesktop(IVirtualDesktop pRemove, IVirtualDesktop pFallbackDesktop);

    IVirtualDesktop FindDesktop(ref Guid desktopId);
}

[ComImport]
[Guid("ff72ffdd-be7e-43fc-9c03-ad81681e88e4")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IVirtualDesktop
{
    bool IsViewVisible(object pView);
    
    Guid GetID();
}

public enum AdjacentDesktop
{
    LeftDirection = 3,
    RightDirection = 4
}

こちらは、インスタンスを取得するのが若干面倒で、ImmersiveShell (IServiceProvider) 経由で以下のように。

public static IVirtualDesktopManagerInternal GetVirtualDesktopManagerInternal()
{
    var shellType = Type.GetTypeFromCLSID("c2f03a33-21f5-47fa-b4bb-156362a2f239");
    var shell = (IServiceProvider)Activator.CreateInstance(shellType);

    object ppvObject;
    shell.QueryService("aa509086-5ca9-4c25-8f95-589d3c07b48a", typeof(IVirtualDesktopManagerInternal).GUID, out ppvObject);

    return (IVirtualDesktopManagerInternal)ppvObject;
}

これを使えば、

  • 仮想デスクトップの数を取得
  • 現在表示されている仮想デスクトップを取得
  • すべての仮想デスクトップを取得
  • 指定した仮想デスクトップの隣の仮想デスクトップを取得
  • 指定した仮想デスクトップに切り替え
  • 仮想デスクトップを作成
  • 仮想デスクトップを削除
  • 指定した ID の仮想デスクトップを検索

ができるようになります。
これらを C# でラップしたライブラリを作ってあるので、サクっと触りたい方はこちらを使ってみてください。

https://github.com/Grabacr07/VirtualDesktop

注意しなければならないのが、ビルドによって IVirtualDesktopManagerInternal の IID が変わっているらしい、ということ。 10240 では “af8da486-95bb-4460-b3b7-6e7a6b2962b5″ ですが、10130 では “ef9f1a6c-d3cc-4358-b712-f84b635bebe7″ だった、らしい。。。 (つまり、今動いても次のビルドで動かなくなる、的なことが発生しそう)

ちなみに、ロシア語のブログの方には、IApplicationView というインターフェイスがでてきます。 WinRT の内部にある internal なインターフェイスです (確か Windows.UI だった気がする)。 使い方わからない。

WinRT アプリ内から IVirtualDesktopManager 叩けるかどうか、確かめて下さる方お願いしまうs

MoveWindowToDesktop が動かない

GitHub で公開してるラッパーには簡単なサンプルを付属させています。 仮想デスクトップ間の移動や、ウィンドウごと移動したりする挙動は、そのサンプルで確認できます。

ウィンドウを別の仮想デスクトップへ移動させるためには、IVirtualDesktopManager.MoveWindowToDesktop(IntPtr, GUID) を使います。 しかしながら、この関数は「同一プロセス内のハンドルでないと動かない」ようです。

アクセス拒否

なので、アプリ内で、自分のアプリが表示したウィンドウを別の仮想デスクトップに移動させることはできます。 一方で、別のプロセスが表示しているウィンドウは、仮想デスクトップ間を移動させることはできません。 今後できるようになるかも不明 (無理な気がしている)。

まとめ

ということで、仮想デスクトップに対する操作ができるようになるライブラリ作りました。 が、肝心の部分が動かないので使う機会は少なそう。


Viewing all articles
Browse latest Browse all 30

Trending Articles