﻿/*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#
	Tascher -Task Switcher-
	Coded by x@rgs

	This code is released under NYSL Version 0.9982
	See NYSL_withfaq.TXT for further details.

	「Tascher」は、マウスの移動とホイールの回転や
	Migemoインクリメンタルサーチでウインドウを切り替えるソフトウェアです。
#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*/


・ソースコードについて
 under NYSL Version 0.9982ですが、初心者が書いたものですので、見れたものではありません。
 ＊時代遅れのWin32API直打ちに加え、CRT非依存にするため、難儀な処理が数多あります。
 ＊その上、古いバージョンのコードそのままで整理していません。
 ＊ハンガリアン記法をふんだんに取り入れていますがイマイチ統一できていません。
 ＊classが中途半端に登場します。オブジェクト指向のオの字もありません。
 ＊参考になるような情報もありません。
 ライセンスの条文については同梱の「NYSL_withfaq.TXT」をご覧下さいませ。


・コンパイル方法
 [Microsoft Visual Studio Community 2019 Preview]
    ソリューションファイル「Tascher.sln」を開き、「ビルド」->「ソリューションのビルド」をクリックしてください。
    ・他のバージョンのVisual C++でコンパイルする場合、
     「構成プロパティ」->「全般」->「プラットフォーム ツールセット」を変更してください。


・Note
 [Ver.1.64](2021/05/08)
     ・Windows10環境でWin+Tabと同様の順番でリスト表示できるようになりました。
       EnumWindows()では、ウインドウが最小化されていてもWS_EX_TOPMOSTであれば先頭に列挙されてしまいますが、
       1.IApplicationViewCollectionで列挙
       2.IApplicationViewのGetLastActivationTimestamp()で表示タイムスタンプを取得しソート
       で解決。
       なお、これらのインターフェースは公式ドキュメントがないため、ジェット氏( https://www.pg-fl.jp/ )のTipsを参考にさせていただきました。
       また、ビルドによってIIDが異なるためレジストリから取得するようにしています。
     ・↑の方法で列挙することで、Microsoft Edgeのタブも列挙できるようになりましたが、切り替え方法が不明のため対応を見送っています。
       (プロセスがexplorer.exe、クラスがWindows.Internal.Shell.TabProxyWindow)
     ・IApplicationView::SwitchTo()をテストするもSetForegroundWindow()と同様に切り替わらないケースがあるため実装見送り。
     ・WinUIアプリはクラス名がWinUIDesktopWin32WindowClassかどうかで判断し、他処理はUWPアプリと同様とすることで対応できました。
     ・Ver.1.63はオーナー付きウインドウ(例:名前を付けて保存等のモーダルダイアログ)をリストアップしていましたが、
       Ver.1.64からオーナーウインドウをリストアップし、切り替えの際にオーナー付きウインドウを検索して切り替える処理に変更しました。
     ・「左/右にスナップ」の実装を断念。「Win+←」と同じサイズ・位置に持っていってもタイトルバードラッグで元の大きさに戻ってくれません。
       PowerToysのFancyZoneはSet/GetProp()でサイズを保存/復元しているようですが、「Win+←」は違うようです。
     ・他プロセスのコンテキストメニューやシステムメニュー表示中にホットキーでTascherを呼び出すとメニューが表示されたままになり、
       ウインドウの切り替えができない(タスクバー点滅になる)不具合を修正。メニュー表示中であればSendInput()でVK_ESCAPEを押下する強引な手法をとっています。
       Windows10のエクスプローラのシステムメニューはキャンセルしても一度復活する謎仕様があるため、Tascherアクティブ化処理でのSendInput(クリック)で対応しています。
     ・リソースファイルをVC++でも開けるようにしました。元々はResEditで作成したファイルで、VC++だと「Symbol name too long.」とエラーが表示され開けませんでした。
     ・アクティブウインドウがフルスクリーン表示ならTascherの表示を無効にする設定を追加しました。
       モニタとウインドウの大きさが一致するかどうかだけで判断しているとデスクトップ表示時も巻き込まれるので、
       1.クラス名がProgmanかWorkerW
       2.その子ウインドウのクラス名がSHELLDLL_DefView
       3.さらにその子ウインドウのクラス名がSysListView32でタイトルがFolderView
       ならデスクトップと判断して除外するようにしています。
     ・ウインドウのアクティブ化処理をまたもや変更。SwitchToThisWindow()するだけです。
     ・Tascherをアクティブ化するためのSendInput(クリック)処理を無くすためAttachThreadInput()を試みるも、
       explorer.exeにアタッチしている状態でTascherがハングアップするとPCを再起動するしかなくなったため、SendInput(クリック)復活。
     ・DirectWriteに対応してみました。
       LVS_OWNERDATA|LVS_OWNERDRAWFIXEDなリストビューのWM_DRAWITEMでBeginDraw()/EndDraw()すると遅いため、
       WM_PAINTで描画しています。
       PCのスペックにもよりますが、アイテムのスクロールはDirectWriteモードの方がスムーズです。
     ・ストアアプリのアイコン取得で
       "@{パッケージ名?ms-resource://パッケージID/Files/ロゴのパス}"では
       一部アプリでパッケージIDが「resources.pri」内のResourceMapのnameと異なることから
      「ERROR_MRM_MAP_NOT_FOUND : ResourceMap が見つかりません。」とエラーになっていました。
       そのため、"@{パッケージ名?ms-resource:///Files/ロゴのパス}"
       のようにパッケージIDを省略するようにしました。
       ちなみに「resources.pri」は
       MrmDumpPriFile(L"resources.pri",NULL,MrmDumpType_Basic,L"dump.xml");
       でダンプできます。
     ・Windows.Internal.Shell.TabProxyWindowの方法ではEdgeで開いているタブのサムネイルは取得できるものの、切り替え等の操作方法は分かりませんでした。
       そこで、Chrome DevTools Protocol(CDP)を使用する方法でタブの取得や切り替えに対応してみました。
       ☆CDPでタブを切り替えてみるテスト
       1.ブラウザをremote-debugging-port付きで起動し、タブの一覧を取得。
       msedge.exe --remote-debugging-port=9222
       curl http://127.0.0.1:9222/json/list
       2.タブ等の情報が出力されるので、一覧の中のidを使用し、アクティブ化
       curl http://127.0.0.1:9222/json/activate/id
       でタブを切り替えることができます。
       切り替えの前にAllowSetForegroundWindow()しておかないと点滅するだけで切り替えできないので注意が必要です。


 [Ver.1.63](2020/08/22)
     ・UWPアプリが最小化されている場合の対応について、ウインドウタイトルで処理するのではなく、
       1.トップレベルのWindows.UI.Core.CoreWindowを列挙、そのプロセスIdからAppUserModelIdを取得。
       2.ApplicationFrameWindowのウインドウハンドルから取得したAppUserModelIdと1で取得したAppUserModelIdを比較。
       という処理に変更。
     ・Windows7でスタートアップに登録されているプログラムを無効化すると、
       HKLM\SOFTWARE\Microsoft\Shared Tools\MSConfig\startupfolder
       以下にプログラム名(\は^に置換)でキーが作成されます。また、スタートアップディレクトリからファイルが削除されます。
       この状態でスタートアップディレクトリに再度ファイルを配置するとmsconfig上では無効になっていますが自動起動します。
     ・Windows8～だとタスクマネージャからスタートアップの無効化をすると、
       HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\StartupFolder
       以下にプログラム名の値が作成されます。
       この状態だとスタートアップディレクトリにファイルが配置されていても自動起動しません。
     ・値の先頭4バイトが有効化/無効化のフラグ、以降8バイトが無効にした日のFILETIME構造体のようです。
       今回より「自動起動する」を設定した場合、スタートアップディレクトリへのショートカット作成に加え、上記レジストリ処理を行うようにしました。
       よってアンインストールの際は「自動起動する」を無効化しないとレジストリを汚したままになってしまいます。
     ・ドラッグ中かどうかの判定をカーソルの比較で行なうようにしました。
       ドロップ禁止がole32.dllのMAKEINTRESOURCE(1)、ドロップOKがole32.dllのMAKEINTRESOURCE(3)でした。
     ・マウスフックを廃止。RawInputで処理するようにしました。
       Windows7以降、マウスフックは何度かタイムアウトするとフックが解除されてしまうらしいのでその対応他のため。
     ・Windows10追加の仮想デスクトップ機能は列挙が難しそうなため、ウインドウ移動系の実装は見送り。
     ・IVirtualDesktopManager::IsWindowOnCurrentVirtualDesktop()そのままだとモーダルダイアログやDelphiウインドウが上手く判定できません。
       →モーダルダイアログはDS_MODALFRAMEもしくはWS_EX_DLGMODALFRAMEなら親ウインドウで判定。
       →Delphiウインドウはサイズ0のオーナーウインドウで判定。
       他にも色々と判定処理が必要です。
     ・アイコン・ウインドウタイトルの取得及びウインドウの切り替えを別スレッド化しました。
       重い処理をしているウインドウにTascherが巻き込まれにくくなったはず。
       その分無慈悲なTerminateThread()があります。

 [Ver.1.62](2017/03/27)
     ・migemo_setproc_int2char()で'[',']','~'でもマッチできるようになりました。
       migemo_setproc_int2char()でプロシージャを登録すると、0を返してmigemo側のデフォルトプロシージャに丸投げしようとしても、
       内部で文字コード自動判別が行われないので、Tascher側のint2char()内でUTF-8向け処理を行う必要がありました。
       <UWPアプリ対応メモいろいろ>
       ・中断かつタスクバーに表示されていないUWPアプリ(「設定」、「ストア」、「フォト」等々)を弾くため、
         1.WS_EX_NOREDIRECTIONBITMAPの有無でUWPアプリか確認。
         2.DwmGetWindowAttribute(DWMWA_CLOAKED)でFALSEを返すウインドウのみを列挙。
       ・↑で列挙されるウインドウはクラス名「ApplicationFrameWindow」、プロセス名は「ApplicationFrameHost.exe」とUWPアプリ共通なので、
         FindWindowEx(hWnd,NULL,"Windows.UI.Core.CoreWindow",NULL)
         で取得したウインドウハンドルを利用し個別のプロセスを操作。
       ・UWPアプリが最小化されている場合、「Windows.UI.Core.CoreWindow」は「ApplicationFrameWindow」以下ではなくトップレベルに存在するため、
           1.トップレベルの「Windows.UI.Core.CoreWindow」を列挙。
           2.↑のウインドウタイトルを含む「ApplicationFrameWindow」を親とする。
         という処理に。
         ただ、これだと一部UWPアプリに対応出来ないと思われます。->Ver.1.63で処理を変更。
       ・UWPアプリのアイコンはSendMessage(WM_GETICON)やGetClassLongPtr(GCLP_HICON)で取得することができません。
         アイコン(HICON)取得方法は以下の通り。
           1.↑で取得した「Windows.UI.Core.CoreWindow」のウインドウハンドルからGetWindowThreadProcessId()でプロセスIDを取得。
           2.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION)で開く。
           3.GetPackageFullName()でパッケージ名取得。
           4.GetPackagePathByFullName()でUWPアプリのディレクトリを取得。
           5.PackageIdFromFullName()でパッケージIDを取得。
           6.UWPアプリのディレクトリにある「AppxManifest.xml」をMSXMLで開く。
           7.Square44x44Logoからロゴのパスを取得。(Windows 8.1ならSquare30x30Logo、Windows 8ならSmallLogoらしい。未確認。)
             (Microsoft Edgeなら"Assets\MicrosoftEdgeSquare44x44.png"が取得出来ました。)
           8.BackgroundColorから背景色を取得。
           9."@{パッケージ名?ms-resource://パッケージID/Files/ロゴのパス}"をSHLoadIndirectString()に投げ、ロゴ用PNGファイルをパスを取得。
           10.Gdiplus::Bitmap()でロゴを開く。
           11.表示用アイコンのサイズでGdiplus::Bitmapを作成し、Gdiplus::Graphics()で開き、FillRectangle(BackgroundColor)で塗りつぶす。
           12.読み込んだロゴをDrawImage()で描画し、GetHICON()でHICONを取得。
       ・上記は全てWindows 10で確認しました。Windows 8/8.1で動作するかは不明です。
     ・サムネイル表示機能を実装しました。サムネイルを表示するウインドウはトップレベルのウインドウである必要があります。
       (Tascherメインウインドウの子だとだめ。)
     ・切り替えなしで非表示にする際、ShowWindow(SW_HIDE)でZオーダー順ではないウインドウに切り替わってしまう不具合について、
       ShowListView()で自前のSetForegroundWindowEx()ではなくSetForegroundWindow()を呼び出すことで解決。

 [Ver.1.61](2016/04/21)
     ・ウインドウリスト表示時に取得したアイコンを使い回す設定を追加。
       インクリメンタルサーチがスムーズに行えるように?
     ・コマンドメニューの一部項目について、その設定が有効であればチェックを付けるようにしました。
       incrementalsearchモードやウインドウの状態、プロセス優先度等々...
     ・SendMessageTimeout(WM_GETTEXT)ではなく、InternalGetWindowText()を用いるように。
     ・不透明度が255でない場合に限り、WS_EX_LAYEREDを付加するようにしました。
       Print ScreenでTascherのウインドウリストがキャプチャされない不具合解決。
     ・アイテム数が多い場合、ウインドウリストの高さは(SM_CYSCREEN-アイテム3行分の高さ)としていましたが、
       リスト下部に余白が発生するため、アイテム1行分の高さの倍数に切り下げするように変更しました。
     ・TaskSwitch()でのSetForegroundWindowEx()でWM_ACTIVATEAPPが発生してしまい、
       (WM_ACTIVATEAPPでの)HideListView()後にTaskSwitch()のSetFocus()が呼び出され、落ちてしまう不具合を修正。
       グローバル変数をWM_ACTIVATEAPPでチェックという手抜き対策です。
     ・特に意味もなくフレーム表示に。[ListView]DialogFrameで設定変更できます。
       フレーム部分の幅、高さはAdjustWindowRectEx()とGetWindowRect()との差から取得。
     ・ウインドウリスト移動機能こっそり廃止。
       DWMとマウスフック(のPostMessage(WM_MOUSEMOVE))の相性が悪いのか、移動中にちらついて2つウインドウが表示?されてしまう。
     ・簡易フィルタリング機能として[Exclude]FileNameを追加しました。パスと拡張子を除いたファイル名を「;」で区切って指定してください。
     ・自己解凍ファイルをインストーラに変更。各プラットフォーム対応ファイルのみが展開されます。
     ・バージョン情報にx64版を示す文字列を埋め込むように。
     ・Migemoで'['や']'にマッチさせるためmigemo_setproc_int2char()を用いるも、どうもうまく動作せず...対応見送り。

 [Ver.1.60](2016/03/29)
     ・「button」メニュー系(idok,idcancel,idabort,idretry,idignore,idyes,idno)を追加。「xxへの変更を保存しますか?」等のMessageBox()を操作することができます。
       ->Windows 7以降ではIDOK系はあまり使われない?ようなので削除。
     ・HideListView()の処理を改善。リストビューをSW_MINIMIZEすることで、非表示時にフォーカスを保持したままになることがなくなりました。
       ->リストビューを操作せずとも、メインウインドウをSetWindowPos(HWND_NOTOPMOST)で上手く処理できそう。
     ・メニュー表示時にホットキーを押下すると、ウインドウリストは消えるものの、メニューが残ってしまう問題。
       cogma氏作「cltc」も同様の問題あり。Takla氏作「窓替え」はメニュー表示中はウインドウリストは消えない仕様。
            SetWindowPos(MenuWnd,HWND_NOTOPMOST,...);
            SendMessage(hMenuWnd,WM_KEYDOWN,VK_ESCAPE,0);
            SendMessage(hMenuWnd,WM_KEYDOWN,VK_MENU,0);
            SendMessage(hListView,WM_CANCELMODE,0,0);
       等々メニューを閉じる方法は多々あるものの、このままウインドウリストを非表示にすると、
       Tascherがフォーカスを掴んだまま?になってしまう。
       仕方がないので、タイマーを設定し、そこから再度HideListView()するように。
     ・ウインドウが多数あると、一度も描画されない、つまりDrawItem()で行うウインドウタイトル取得処理が行われないアイテムが発生する不具合発見。
       IncrementalSearch()でチェックするように。
     ・Ver.1.58から検討中のプロセス再起動コマンド「restart」はやはり権限の処理が面倒に思えるので今回も見送り...
     ・インクリメンタルサーチ「あいまい検索」を追加。「readme.txt」に対して「rdm」でマッチします。
       Migemoとの組み合わせはあまり相性が良くなく(Tascherの問題?)、おすすめできません。
     ・「候補が1つなら切り替えする/しない」をコマンド化。
       通常は有効にしておき、「uniquewindow」で無効化->インクリメンタルサーチ->絞り込まれたウインドウに対してコマンド実行、とか使えそう。
     ・背景画像の描画をBitBlt()/StretchBlt()/AlphaBlend()からGDI+のDrawImage()に変更。
       透過PNGや透過GIFを使用することができるようになりました。
     ・アイテム数が多い場合に、ListView_SetItemCountEx()すると、背景画像が一瞬スクロールしてしまう不具合。
          SendMessage(hListView,WM_SETREDRAW,false,0);
          ListView_SetItemCountEx(hListView,iCount,LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);
          ResizeListView(hMainWnd);
          SendMessage(hListView,WM_SETREDRAW,true,0);
          ListView_RedrawItems(hListView,0,iCount);
       とすると回避できました。
     ・インストーラ作成ソフトウェアを7-ZipからNSISに変更。ファイルサイズが半分近く減少しました。

 [Ver.1.59](2015/11/11)
     ・ハングしているプログラムがあるとウインドウリスト操作が遅くなる/フリーズする不具合を避けるため、
       自作関数IsWindowHung()をIsHungAppWindow()に置き替え、SendMessageTimeout()を用いるように。
     ・マウスカーソルを四隅に素早く移動させると、マウスアウト判定されていなかったため、
       ShowListView()の時点で判定するように。ただ、偶にすり抜けます...
     ・背景画像を設定した状態でスクロールするとえらいことになります。
       ->EnableWindow(hListView),SetFocus(hWnd)で解決?
     ・PROCESSENTRY32のth32ParentProcessIDが既に他のプロセスで再利用されている場合に、誤って親として列挙してしまう不具合。
       ->GetProcessTimes()で作成された時間を取得し、子より後であれば親とみなさないように。
     ・Code::blocks用プロジェクトを削除しました。その他、少しだけソースコードを整理。
     ・GDI+を使用し背景画像を読み込むように。PNGに対応しました。
       /NODEFAULTLIBが有効であるなら、[C/C++]->[言語]->[ランタイム型情報を有効にする]を「いいえ(/GR-)」にすること。

 [Ver.1.58](2015/10/29)
     ・便利(?)なコマンドをいくつか追加しました。「info」「copyname」「copytitle」「copycommandline」「copyinfo」「childprocess」「terminatetree」
     ・子プロセス一覧機能「childprocess」はフリーズしたrecesをterminateするために実装^q^
     ・Tascher.cppの行数がVer.1.40当時の2倍に。これからも旧来のソースをベースに気ままに書き殴り、ゆるりと改良を重ねていきます。
     ・右クリックやCtrl+右クリックからコマンドメニューを呼び出すとメニューの下線が消えてしまう...
       ->「コンピュータの簡単操作センター」->「キーボードを使いやすくします」->「ショートカットキーとアクセスキーに下線を表示します」を有効にすると表示されました。
     ・コマンド「move」「size」実装。動作はしているものの、自信なし^q^

 [Ver.1.57](2015/10/03)
     ・MMHook.dll内でPostMessage(HWND_BROADCAST,WM_NULL,0,0);していたため、
       7-zip32.dll処理中にウインドウリストを表示させると書庫処理が中断してしまう不具合を修正。
       Ver.1.56で修正したかった^q^

 [Ver.1.56](2015/09/30)
     ・念願のMigemo検索実装。一文字入力するごとに検索していると負担が大きいため、タイマーでdelayを設定。
     ・リサイズ時のちらつきはSetLayeredWindowAttributes(LWA_COLORKEY)で。弥縫策。
       ->表示が重いので採用しないことに。

 [Ver.1.55](2015/09/25)
     ・「マウスアウトで確定」オプションを追加。この設定が無効であるとき、フォーカスが外れるとリストが非表示になります。
     ・「ショートカットキー」の設定ができるように。ホットキーコードをキー名にして保持しています。
     ・「マウス」の設定ができるように。CommonSettings.hで定義されている列挙型をキー名にして保持しています。
     ・NM_CLICKとNM_DBLCLKを判別するため、タイマーを使用。
     ・ウインドウリストを移動させる方法、こっそり変更。(Ctrl+からAlt+)
     ・前回の内容が表示される問題について、非表示時にSetLayeredWindowAttributes(),SetWindowPos()等々を詰め込み対策。

 [Ver.1.50](2015/09/12)
     ・念願のインクリメンタルサーチ実装。
     ・しかし、絞り込むごとにListView_SetItemCountEx()するとListView_SetBkImage()で設定した背景がちらつく(末尾のアイテムのみ?)ので、
       背景を自前で描画することに。
       ->LVS_EX_DOUBLEBUFFERを設定し忘れていたから?
         AlphaBlend()による明るさ調整を反映した背景描画も実装したので、このまま放置で。
     ・メインのコンパイラをVC++に。CRTに依存しないようにmemset(),memcpy()を自前で実装。
     ・設定ファイルは過去のバージョンとの互換性がないので注意すること(2回目)。
     ・通常行と選択行で別のフォントを指定できるように。オーナードローに変更した前バージョンで実装し忘れ。
     ・外部DLLでWH_MOUSE_LLによりWM_MOUSEMOVEをフックし、本体に投げることでドラッグ中のマウスアウトも検出できるように。
     ・ウインドウリストを表示させると、一瞬前回の内容が表示されてしまう。

 [Ver.1.40](2012/03/13)
     ・ほとんど作り直した。
     ・Makefileではなく、Code::Blocks用のプロジェクトファイルを同梱するようにした。
     ・DLL作成で詰んだので、低レベルフックに変更。
     ・リストビューを仮想&オーナードローの組み合わせに変更。
     ・カラムが二つであること前提でコードを書いてしまったので、今後「ファイルパス」等のカラムが復活する可能性は低い。
     ・壁紙を透過したり、半透明ブラシの採用も考えたが、x64版でのgdiplusのリンクがうまくいかないので断念。
     ・設定ファイルは過去のバージョンとの互換性がないので注意すること。
     ・偶に右クリックしてもキャンセルされず、ウインドウが切り替わる不具合あり。原因究明中。

 [Ver.1.30](2010/08/28)
     ・Alt+Tabの置き換えは完全ではないので注意する事。(Tacherの処理が優先されない状態で実行すると従来のAlt+Tabが表示される)

 [Ver.1.25](2010/08/04)
     ・フォント最大サイズを48にしていますが、普通は使用しないと思う。

 [Ver.1.20](2010/07/28)
     ・ファイル分割を行ってみた。Tascher.cのデスクトップアイコン取得は偶々動いているに過ぎないと思う。

 [Ver.1.15](2010/07/23)
     ・Makefile内のMINGW=$(MINGW_PATH)のコメントを外した。

 [Ver.1.10](2010/07/17)
     ・gcc用のbatファイルを非同梱に。

 [Ver.1.00](2010/06/26)
