物置き

Win32ネイティブアプリからトースト通知を表示する

自アプリ(Win32ネイティブアプリ)でトースト表示するにあたり、手順を調べたので以下にメモしておく。

なお、本記事に張り付けたコード辺は自アプリの関連処理を切り出したものだけど、切り出したものに対する検証(ビルド、実行)はしていないため、何か不備はあるかも・・

アプリに対してAppUserModelIdを割り当てる

AppUserModelIdはGUID。guidgen.exeを使って自アプリ用のAppUserModelIdを作っておく。 トーストを表示する際、このAppUserModelIdを指定する必要がある。

AppUserModelIDを登録する(スタートメニューにアプリのショートカットを作成する)

スタートメニューにショートカットを作り、そのショートカットメニューのプロパティとして PKEY_AppUserModel_IDキーに紐づける形で前ステップで生成したAppUserModelIDを割り当てる。

以下のような形でショートカットを作成しておけばよい。

#include <propkey.h>
#include <propvarutil.h>
// 上記のほかに、CComPtrを使うための準備をしておく(関連ヘッダのinclude,CoInitializeなど)

// 事前に確保しておく↓
constexpr LPCWSTR MYAPP_USERMODEL_ID = L"{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}";

// pathToSave : ショートカットキー保存先パス
// exePath : ショートカットのリンク先(実行ファイル)のパス
bool registerShortcut(LPCWSTR pathToSave, LPCWSTR exePath)
{
    CComPtr<IShellLink> shellLinkPtr;
    HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,IID_IShellLink,(void**)&shellLinkPtr);
    if (FAILED(hr)){
        return false;
    }


    // 実行ファイルへのパス
    shellLinkPtr->SetPath(exePath);

    // 引数、カレントディレクトリなどは必要に応じて設定する
    shellLinkPtr->SetArguments(L"");
    shellLinkPtr->SetWorkingDirectory(L"");

    // AppUserModelIdをショートカットに設定する
    CComPtr<IPropertyStore> propStore;
    shellLinkPtr->QueryInterface(IID_IPropertyStore, (void**)&propStore);

    // AppUserModelIdをappIdPropVarに設定し、キーに書き込む
    PROPVARIANT appIdPropVar;
    InitPropVariantFromString(MYAPP_USERMODEL_ID, &appIdPropVar);
    propStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar);

    propStore->Commit();

    PropVariantClear(&appIdPropVar);

    CComPtr<IPersistFile> persistFilePtr;
    hr = shellLinkPtr->QueryInterface(IID_IPersistFile, (void**)&persistFilePtr);
    if(FAILED(hr)){
        return false;
    }
    // ショートカットキーをファイルとして保存する
    hr = persistFilePtr->Save(pathToSave, TRUE);
    if(FAILED(hr)){
        return false;
    }

    return true;
}

トーストを表示する

ToastNotificationインスタンスを作成し、これをToastNotificationManagerに対して渡すとトーストを表示することができる。

トーストに表示する内容はXMLベースのデータをこしらえて定義する。
テキストのほかに画像をおいたりハイパーリンクを設置したり、ボタンを置いたりなどできる。 また、クリック時の処理などいろいろ定義することができる。 ただし、クリック時の処理を定義しようとすると、そのための準備がいろいろ必要になるがここでは触れていない。

#include <winrt/Windows.UI.Notifications.h>
#include <winrt/Windows.Data.Xml.Dom.h>

using namespace winrt;
using namespace winrt::Windows::UI::Notifications;
using namespace winrt::Windows::Data::Xml::Dom;

void ShowToast()
{
    XmlDocument doc;
    doc.LoadXml(L"
          <toast>\
          <visual>\
          <binding template=\"ToastGeneric\">\
          <text></text>\
          <text></text>\
          </binding>\
          </visual>\
          </toast>");

    doc.DocumentElement().SetAttribute(L"launch", L"action=xxx&message=yyy");  // コールバック時にここで指定した文字列がえられる


    doc.SelectSingleNode(L"//text[1]").InnerText(L"こんにちは");
    doc.SelectSingleNode(L"//text[2]").InnerText(L"テストです");

    // トーストを表示する
    winrt::Windows::UI::Notifications::ToastNotification notif(doc);

    winrt::Windows::UI::Notifications::ToastNotificationManager toastManager;

    // CreateToastNotifierの引数に自アプリのAppUserModelIdを指定することにより、
    // 自アプリの通知としてトースト表示される。
    ToastNotifier toastNotifier(toastManager.CreateToastNotifier(MYAPP_USERMODEL_ID));
    toastNotifier.Show(notif);

}

その他メモ

  • Windows10/11環境では、Shell_NotifyIconを使って、トースト表示をすることもできる

    • ただし、以下のような制約があるようだ
      • クリックしたときの処理を定義できない
      • 見た目の細かなカスタマイズはできない(せいぜいアイコンを指定するくらい)
      • 通知センターに通知が残らない、
  • 自前でトースト表示する場合、トーストをクリックしたときの処理を自アプリで処理することができるが、その場合はコールバックを登録したり、コールバックを用意する必要がある

  • 他アプリが登録したUserAppModeIdを流用することで、自アプリでAppUserModeIdを登録しなくてもトースト表示はできる

    • ただし、流用元アプリとしてトースト表示が行われる

既存のAppUserModelIdを調べる

  • Excplorerのアドレスバーにshell:AppsFolderと打つ
  • 何もない領域を右クリック(Win11環境であれば、シフトキー押しながら右クリック)し、グループで表示>その他を選択する
  • 「詳細表示の設定」ダイアログが表示されるので、AppUserModelIdをチェックしてOKボタンを押下する
  • 再度、 何もない領域を右クリックして、表示>詳細を選択する

リスト表示にAppUserModelIdという列が表示されるので、ここからアプリごとのAppUserModelIdを確認することができる

LibreOfficeで開いている表計算ドキュメントのシート一覧を列挙する

最近作っている自分用ツール から、LibreOfficeで開いている表計算ドキュメントのシート名一覧を列挙して、それを選択したら、そのシートをアクティブにしたうえで、ウインドウを前面に出す、みたいなことができたいかと思って調べたので以下に自分用のメモ

Excelでは同様のことが実現できているが、自宅ではLibreOfficeを使っているので同じことできないかと思って調べたらできそうであることがわかった。

とりあえず、JScriptで書いているが、あとでC++のCOMで使う形に書き換える

var factory = new ActiveXObject("com.sun.star.ServiceManager");
var loader = factory.createInstance("com.sun.star.frame.Desktop");

var docs = loader.getComponents();
var e = docs.createEnumeration();
while(e.hasMoreElements()) {
    var doc = e.nextElement();

    // ドキュメントのパス(URL)を取得
    WScript.Echo(doc.getURL());

    // シートオブジェクトを取得
    var sheets = doc.getSheets();

    // シート数を得る
    var sheetCount = sheets.getCount();
    WScript.Echo(sheetCount);

    // シート名を列挙する
    for (var i = 0; i < sheetCount; ++i) {
        var sheet = sheets.getByIndex(i);
        WScript.Echo(sheet.name);
    }

    // アクティブなワークシートを変える
    var controller = doc.getCurrentController();
    if (sheetCount >= 4) {
        // 例えば、4番目のワークシートをアクティブにする
        controller.setActiveSheet(sheets.getByIndex(3));
    }

    var frame = controller.getFrame();

    // ウインドウを前面に出す
    // ※ ただし、今日の環境だと、タスクバー上のCalcのアイコンが点滅するだけになってしまう。
    frame.getContainerWindow().toFront();
}
  • 多分ここで列挙されるドキュメントは表計算以外のドキュメントも含まれる形になるはず
    • 種類を判別する方法はあるはずだけど調べていない。とりあえずドキュメントパスの拡張子でも判断できそうな気も
  • ウインドウを前面に出すのはtoFront()を使うのではなく、ウインドウハンドルを探して、AttachThreadInput()SetForegroundWindow()でいけるはず
    • 確実じゃないかもしれないけど、ドキュメントパスとウインドウタイトルを見れば見つけられそうな気がしている

コントロールパネルの一覧を列挙・項目を実行(表示)する手順についてのメモ

自作ツールでコントロールパネルの項目を列挙し、任意の項目を実行するにあたり方法を調べたので以下にメモ

列挙する

  1. 以下のレジストリキー直下にあるキーを列挙する
    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ControlPanel\NameSpace

  2. 直下にあるキー名がコントロールパネルの項目を表すCLSIDなので、これを使って HKEY_CLASSES_ROOT\CLSID 直下にのサブキーを参照する
    例: HKEY_CLASSES_ROOT\CLSID\{87D66A43-7B11-4A28-9811-C86EE395ACF7}

  3. 各キーにLocalizedString値(種類はREG_EXPAND_SZ型)があるので、これを取得する
    例: @%SystemRoot%\System32\srchadmin.dll,-601#immutable1

  4. 取得した値SHLoadIndirectString関数を用いて変換する
    @で始まる文字列を上記APIを使うと実際の文字列に置き換えることができる

その他の情報

  • アイコンはHKEY_CLASSES_ROOT/CLSID/{xxxx...}/DefaultIcon の(既定)にパスが入っている
    • (アイコンリソースを持つモジュールのパス),(インデックス or 識別子) の形式で格納されている

      • (インデックス or 識別子)が正の値の場合はインデックス、負の値の場合はリソース識別子
    • この情報をもとに、WIN32 APIのExtractIconExでアイコンハンドルを取得できるが、-1の時にうまく取れなかった。

      • APIリファレンスをみると、ExtractIcon系のAPIは、indexに-1を指定したとき、アイコンリソース数を返す仕様になっている。
        じかし、ExtractIconExであれば、indexが-1でもラージアイコンorスモールアイコンを受け取る配列を指定していれば、
        アイコンリソース数を返すのでなく、ハンドルをロードする、と読めるような記載があるが、 手元の環境で動かしてみる感じ、アイコンリソース数と思われる数が返ってくる...
    • これは解決できなかったのでしかたなく、indexが-1の時は別途、LoadLibraryEx -> FindResource -> LoadResource -> LockResource -> CreateIconFromResourceで取得する形にした
      • ただ、この方法で取得したアイコンはサイズか色が違ってそうで、このアイコンを描画するとなんか見た目がガビガビなので、もっとよい方法がある気がしてるがとりあえず放置

コントロールパネルの項目を実行(表示)する

HKEY_CLASSES_ROOT/CLSID/{xxxx...}キー内のSystem.ApplicationName値を取得し、 これを %SystemRoot%\System32\control.exe /name <取得した値> とすると、 そのコントロールパネル項目を表示できる。

Alt-Tabでのウインドウ選択操作をHJKLキーでできるようにするためのyamy設定(Windows11向け)

背景

Alt-Tabキー押下でウインドウを切り替える際の選択操作をHJKLキーで選択できるよう、 yamyの設定ファイルでキー割り当てを定義している。

Windows10では、Alt-Tabキー押下中に表示されるあの切り替えウインドウのウインドウクラス名はMultitaskingViewFrameだったので、 下記のように記載していた。

# タスク切り替えウインドウ(Win10)
window TaskSwitchWindowForWin10 /.*:MultitaskingViewFrame/ : Global
 key A-H = A-Left
 key A-L = A-Right
 key A-J = A-Down
 key A-K = A-Up

Windows11(21H2)ではAlt-Tabキー押下時に表示されるウインドウが別物になってしまったようで、 上記の定義ではヒットしなくなり、HJKLキーで移動できなくなってしまった。

この環境での、Alt-Tabキー押下時に出てくるウインドウクラス名を確認したところ、 XamlExplorerHostIslandWindowという名前になっていたので、 yamy設定ファイルに下記のような定義を追加したところ、Windows11環境でもHJKLキーで選択できるようになった。

Windows11での設定

# タスク切り替えウインドウ(Win11)
window TaskSwitchWindowForWin11 ( /.*:XamlExplorerHostIslandWindow/ && /タスクの切り替え/ ) : Global
 key A-H = A-Left
 key A-L = A-Right
 key A-J = A-Down
 key A-K = A-Up

なお、ウインドウクラス名が切り替えウインドウ専用の名前っぽくないので、ウインドウタイトルも見ることにした。 システムが出すウインドウのタイトルを見て判断するのは、システムの言語設定の影響を受けそうでいやではあるが、どうせ日本語以外の環境で使うことないしな・・

Yamy私家版 0.31

Yamyのプロジェクトサイトリポジトリからgithubにソースをアップして、個人的な修正を入れた。

https://github.com/ampmmn/yamy/releases/tag/v0.031

変更点

  • スクリーンロックから復帰したときに、内部のモディファイヤキー(Win/Ctrl/Shift/Alt)の状態をリセットするようにした。

変更の背景

Yamyというキーバインディング変更ソフトがある。 このツールのリリースは、2009年頃のv0.3を最後に止まっているが、ツール自体は今のWindows10環境でも普通に動作する。

このツールを10年以上使っているが、このツールの既知の問題として、
Win-Lキーでスクリーンロックをかけてから復帰した後、Winキーがモディファイヤキーとして押されっぱなしの状態になる、
というのがある。

この問題に対して、
スクリーンロックを解除した後、Winキーを手動で押下することにより、Yamyのモディファイヤキーの状態をリセットする、
という運用をいままで10年以上続けていたけど、これはだるいのでいっそ手を入れてしまおうということで修正したものを私家版として公開する次第。

この私家版を使うと、上記の現象が起こらなくなる(スクリーンロック復帰後にWinキーが押された状態にならない)ので、スクリーンロックからの復帰後もキーバインドが期待通りの動きになる。

メモ

  • この現象自体はreadme.txtの制限事項として記載されているし、下記issueで報告されていて既知のもの

  • この修正はスクリーンロック解除のタイミングでWin/Shift/Ctrl/AltキーのUpイベントを内部で投げる,というものなので、これによる弊害はあるかも

    • スクリーンロックをまたがってこれらのモディファイヤーの状態が維持されていることが期待されるケース..?そんなのあるかしら

vim-dsfcg 0.1.7

だいぶ昔に作ったVimプラグインGitHubリポジトリを作ってアップしたのでここに記載しておきます。

github.com

ずいぶん昔、レンタルサーバにファイルを置いたうえで、このブログで公開してたけど、 レンタルサーバを解約してしまっていた。 そのため、ブログ上に記事はある一方でファイルはデッドリンク、みたいな状態になってたので一応挙げておく次第

ずいぶんメンテしてなかったけど、いつのまにか今日の環境では動かなくなっていたので、そのあたりも修正した。

Firefox使うのをやめた

だいぶいまさらだけど、 FirefoxでVimperatorが使えなくなったので Vimium-FFを使っていた。

Firefox使い始めのころはいろいろ拡張機能を使っていたけど、 Firefox(だかVimperatorだか)のバージョンアップで拡張機能が動かなくなる →対応したプラグインを更新して追従する→またバージョンアップで動かない・・・ を繰り返すうち、いつの間にか拡張機能はほとんど使わなくなっていたので、 Vimiumに移行してもそれほど違和感は感じることなくつかえていた。 (quickmark的な機能がないっぽいのは不便・・・)

結局のところ、JKでスクロールできて、Hit-a-hint的なものを使えていればそれでOKだったみたい。

その流れで、VimiumはChromeにもあることを知り(もともとはChrome拡張機能?) Firefoxは、大きい変更が入って今まで使えてたものが使えなくなるみたいなのが しばしばあることにうんざりしていたので、Chromeに移行することにした。