UWSCで通り抜けフープもどきを作ってみる

CreateWindow出来るようになったので、調子に乗って通り抜けフープもどきを作ってみた。
通り抜けフープもどきを使うと、そのウインドウの下が見えます。
、、、でも、CreateWindowなくても多分できるな、これ、、、。
ま、CreateWindowした方が比較的きれいに書ける、という事例。
ただ、環境によっては落ちるかも。
多分、メモリー解放のタイミングだなぁ。



操作方法

スクリプトを実行すると、灰色の輪っかが出ます。
それを、下を覗きたいところに移動するだけ。
輪っかが細いので、マウスでつかむのが面倒かもしれません。
その場合、スクリプトの定数RING_WIDTHを変えると良いかもしれません。
輪っかを右クリックで終了。

スクリプト

OPTION EXPLICIT

CONST RING_SIZE = 150
CONST RING_WIDTH = 5
DEF_DLL WindowFromPoint(long,long): DWORD: user32
DEF_DLL GetParent(DWORD): DWORD: user32
DEF_DLL CreateEllipticRgn(int,int,int,int): DWORD: gdi32
DEF_DLL CreateRectRgn(int,int,int,int): DWORD: gdi32
DEF_DLL CombineRgn(DWORD,DWORD,DWORD,int): int: gdi32
DEF_DLL DeleteObject(DWORD): BOOL: gdi32
DEF_DLL SetWindowRgn(HWND,DWORD,BOOL): int: user32
DEF_DLL GetWindowRgn(HWND,DWORD): int: user32
PUBLIC _hWndOld = 0, _hRgnOld = 0


CALL Asm

DIM    code = "VYvsg+wUx0X8EREREcdF9AAAAACLRfwzyYN4CAAPlcGJTfjHRfABAAAAi1X8g3oQ/3QPi0X8"
code = code + "i0gQO00ID4ULAQAAi1X8g3oY/3QPi0X8i0gYO00QD4XzAAAAi1X8g3oc/3QPi0X8i0gcO00U"
code = code + "D4XbAAAAi1X8g3oUAHQ+x0XwAAAAAMdF7AAAAADrCYtF7IPAAYlF7ItN/ItV7DtRFHMai0Xs"
code = code + "i038i1SBQDtVDHUJx0XwAQAAAOsC69KDffAAD4SKAAAAi0X8i00IiUgoi1X8i0UMiUIsi038"
code = code + "i1UQiVEwi0X8i00UiUg0i1X8gzoAdBeLRfyDeAwAdA6LTfyLUQxSi0X8iwj/0YtV/IN6BAB0"
code = code + "PotF/IN4JAB0NYtN/ItRIFKLRfyLSCRRi1X8i0IE/9CFwHUbi038i1E8iVX0i0X8M8mDeDgA"
code = code + "D5TBI034iU34g334AHQbi1UUUotFEFCLTQxRi1UIUotF/ItICP/RiUX0i0X0i+VdwhAA"

DIM addr = Asm.Set(code)
DIM paraSize = 80, para = Asm.Alloc(paraSize), i = 0

IFB addr > 0 AND para > 0 THEN
	DEF_DLL CreateEventA(DWORD, BOOL, BOOL, DWORD): DWORD: kernel32
	DIM hEvent = CreateEventA(0, FALSE, FALSE, 0)
	DIM hEventSync = CreateEventA(0, FALSE, FALSE, 0)
	i = i + Asm.SetDword(para + i, Asm.GetProcAddress(Asm.hK32, "SetEvent"))
	i = i + Asm.SetDword(para + i, Asm.GetProcAddress(Asm.hK32, "WaitForSingleObject"))
	i = i + Asm.SetDword(para + i, Asm.GetProcAddress(Asm.hU32, "DefWindowProcA"))
	i = i + Asm.SetDword(para + i, hEvent)	// メッセージ通知イベント
	i = i + Asm.SetDword(para + i, -1)	// hwnd
	i = i + Asm.SetDword(para + i, 4)	// msgNum
	i = i + Asm.SetDword(para + i, -1)	// wp
	i = i + Asm.SetDword(para + i, -1)	// lp
	i = i + Asm.SetDword(para + i, $FFFFFFFF)	// UWSCスレッドの処理待ち時間
	i = i + Asm.SetDword(para + i, 0)	// 同期イベント(ウインドウ作成後)
	i = i + 16	// 取得メッセージ領域
	i = i + 8	// メッセージ処理後領域
	i = i + Asm.SetDword(para + i, 2)	// msg	WM_DESTROY
	i = i + Asm.SetDword(para + i, $201)	// msg	WM_LBUTTONDOWN
	i = i + Asm.SetDword(para + i, $205)	// msg	WM_RBUTTONUP
	i = i + Asm.SetDword(para + i, $232)	// msg	WM_EXITSIZEMOVE
	//Asm.Dump(para, paraSize)
	Asm.SetDword(addr + 9, para)

	// クラスを登録する
	DEF_DLL RegisterClassExA({DWORD,DWORD,DWORD,int,int,DWORD,DWORD,DWORD,DWORD,DWORD,var string,DWORD}): DWORD: user32
	DEF_DLL GetModuleHandleA(string): DWORD: kernel32
	DEF_DLL GetStockObject(int): DWORD: gdi32
	DIM className = "JuneUwscClass"
	DIM hInst = GetModuleHandleA(NULL)
	IFB RegisterClassExA(48,3,addr,0,0,hInst,0,0,GetStockObject(2),0,className,0) = 0 THEN
		PRINT "RegisterClassEx Error:" + Asm.GetLastError()
		EXITEXIT
	ENDIF

	// ウインドウを作成する
	DEF_DLL CreateWindowExA(DWORD,string,string,DWORD,int,int,int,int,DWORD,DWORD,DWORD,DWORD): DWORD: user32
	DIM hWnd = CreateWindowExA(0,className,className,$80000000,10,10,RING_SIZE,RING_SIZE,0,0,0,0)
	IFB hWnd = 0 THEN
		PRINT "CreateWindowEx Error:" + Asm.GetLastError()
		EXITEXIT
	ENDIF
	// ウインドウを丸にする
	DIM hRgn = CreateEllipticRgn(0, 0, 1, 1)
	DIM hRgnO = CreateEllipticRgn(0, 0, RING_SIZE, RING_SIZE)
	DIM hRgnI = CreateEllipticRgn(RING_WIDTH, RING_WIDTH, RING_SIZE-RING_WIDTH, RING_SIZE-RING_WIDTH)
	CombineRgn(hRgn, hRgnO, hRgnI, 4)	// RGN_DIFF
	DeleteObject(hRgnI)
	DeleteObject(hRgnO)
	SetWindowRgn(hWnd, hRgn, FALSE)
	CTRLWIN(HNDTOID(hWnd), TOPMOST)
	// 作り終わったら表示する
	CTRLWIN(HNDTOID(hWnd), SHOW)

	// UWSC側メッセージ処理スレッドを起動する
	DIM hEventFin = CreateEventA(0, FALSE, FALSE, 0)
	THREAD ProcMessage(hEvent, hEventSync, para + 36, hEventFin)

	// メッセージ処理スレッドの終了を待つ
	DEF_DLL MoveWindow(DWORD,int,int,int,int,BOOL): BOOL: user32
	DIM tarHwnd = 0, tarId, tarX, tarY, newX, newY
	DIM rId = HNDTOID(hWnd)
	i = 258
	WHILE i = 258
		i = Asm.WaitForSingleObject(hEventFin, 10)
		IFB _hWndOld <> 0 THEN
			IFB _hWndOld = tarHwnd THEN
				newX = STATUS(tarId, ST_X) - tarX
				newY = STATUS(tarId, ST_Y) - tarY
				IFB newX <> 0 OR newY <> 0 THEN
					MoveWindow(hWnd, STATUS(rId, ST_X) + newX, STATUS(rId, ST_Y) + newY, RING_SIZE, RING_SIZE, TRUE)
					tarX = tarX + newX
					tarY = tarY + newY
				ENDIF
			ELSE
				tarHwnd = _hWndOld
				tarId = HNDTOID(tarHwnd)
				tarX = STATUS(tarId, ST_X)
				tarY = STATUS(tarId, ST_Y)
			ENDIF
		ENDIF
	WEND
	SLEEP(0.1)

	// 解放
	DEF_DLL UnregisterClassA(string,DWORD): BOOL: user32
	IF !UnregisterClassA(className, hInst) THEN SLEEP(1)
	SLEEP(0.1)
	Asm.Free(addr)
	Asm.Free(para)
	Asm.CloseHandle(hEvent)
	Asm.CloseHandle(hEventSync)
	Asm.CloseHandle(hEventFin)
ENDIF

PROCEDURE ProcMessage(hEvent, hEventSync, p, hEventFin)
	DEF_DLL PostMessageA(DWORD,DWORD,DWORD,DWORD): bool: user32
	DEF_DLL SetEvent(DWORD): bool: kernel32
	DIM hwnd = 0, msg = 0, wp, lp, res = 258
	// 同期イベントを設定する
	//Asm.SetDword(p, hEventSync)
	WHILE res = 258
		res = Asm.WaitForSingleObject(hEvent, 100)
		IFB res = 0 THEN
			hwnd = Asm.GetDword(p + 4)
			msg = Asm.GetDword(p + 8)
			wp = Asm.GetDword(p + 12)
			lp = Asm.GetDword(p + 16)
			//PRINT "hwnd:" + FORMAT(hwnd,8,-1) + " msg:" + FORMAT(msg,8,-1) + " wp:" + FORMAT(wp,8,-1) + " lp:" + FORMAT(lp,8,-1)
		ELSEIF res = 258 THEN
			// timeout
			CONTINUE
		ELSE
			msg = 2	// 異常事態なのでWM_DESTROYを模して終わる
			PRINT "ProcMessage Wait fail:" + res
		ENDIF
		res = 258
		// メッセージ処理
		IFB msg = 2 THEN	// WM_DESTROY
			Asm.SetDword(p, 0)
			IF hwnd THEN PostMessageA(hwnd, $12, 0, 0)	// WM_QUIT
			res = 0
		ELSEIF msg = $205 THEN	// WM_RBUTTONUP
			// 右クリックされたら終了
			PostMessageA(hwnd, $10, 0, 0)	// WM_CLOSE
		ELSEIF msg = $201 THEN	// WM_LBUTTONDOWN
			// 左クリックされたらタイトルバーがクリックされたことにする
			PostMessageA(hwnd, $A1, 2, 0)	// WM_NCLBUTTONDOWN,HTCAPTION
		ELSEIF msg = $232 THEN	// WM_EXITSIZEMOVE
			// 移動完了したのならその下のウインドウに穴を空ける
			DIM wid = HNDTOID(hwnd)
			Through(STATUS(wid,ST_X), STATUS(wid,ST_Y))
		ENDIF
		//SetEvent(hEventSync)
	WEND
	IFB _hwndOld <> 0 THEN
		SetWindowRgn(_hwndOld, _hRgnOld, TRUE)
		_hwndOld = 0
	ENDIF
	SetEvent(hEventFin)
FEND

PROCEDURE Through(x, y)
	// 輪っか中心あたりのウインドウを取得する
	DIM hwndTar = WindowFromPoint(x + RING_SIZE / 2, y + RING_SIZE / 2)
	// 子ウインドウの場合、親をたどる
	DIM hwndPar = GetParent(hwndTar)
	WHILE hwndPar <> 0
		hwndTar = hwndPar
		hwndPar = GetParent(hwndTar)
	WEND
	// 違うウインドウに穴あけしてた場合、元に戻す
	IFB _hwndOld <> 0 AND _hwndOld <> hwndTar THEN
		SetWindowRgn(_hwndOld, _hRgnOld, TRUE)
		_hwndOld = 0
	ENDIF
	IFB hwndTar <> 0 THEN
		// 前と違うなら、ウインドウの形を取得する
		IFB hwndTar <> _hwndOld THEN
			_hRgnOld = CreateEllipticRgn(0, 0, 1, 1)
			IFB GetWindowRgn(hwndTar, _hRgnOld) = 0 THEN
				// 取得失敗はNULLにしておく
				DeleteObject(_hRgnOld)
				_hRgnOld = 0
			ENDIF
		ENDIF
		// 穴あき型を作成する
		DIM wid = HNDTOID(hwndTar), tarX = STATUS(wid, ST_X), tarY = STATUS(wid, ST_Y), hRgnNew = 0
		DIM hRgnR = CreateEllipticRgn(x - tarX, y - tarY, RING_SIZE + x - tarX, RING_SIZE + y - tarY)
		DIM hRgn = CreateRectRgn(0, 0, 1, 1)
		// 元の形がNULLの場合、一般的な矩形とみなす
		DIM hRgnOld = _hRgnOld
		IF hRgnOld = 0 THEN hRgnOld = CreateRectRgn(0, 0, STATUS(wid, ST_WIDTH), STATUS(wid, ST_HEIGHT))
		// 元の形と穴を合成(穴開け)する
		CombineRgn(hRgn, hRgnOld, hRgnR, 4)
		DeleteObject(hRgnR)
		IF hRgnOld <> _hRgnOld THEN DeleteObject(hRgnOld)
		// 対象を穴あき形にする
		SetWindowRgn(hWndTar, hRgn, TRUE)
		_hwndOld = hWndTar
	ENDIF
FEND

Asmモジュールは、こちらから。
UWSCにできないことはない? - じゅんじゅんのきまぐれ

解説

ほとんど、UWSCでCreateWindowする - じゅんじゅんのきまぐれと同じ。
違いは、

  • 捕まえるメッセージが、WM_DESTROY/WM_LBUTTONDOWN/WM_RBUTTONUP/WM_EXITSIZEMOVEの四つ
  • 作成するウインドウは輪っか型
  • メッセージ処理の都合上、非同期処理としている(同期にするとデッドロックする)
    このため処理したいメッセージが重複すると拾えないことがある、、、
  • Through(x, y)関数の追加
Through(x, y)関数について

引数は、輪っかの左上。
輪っかの中心にあるウインドウを穴あけ対象とする。
前回、別ウインドウで穴あけ処理が行われていたら、穴を戻す。
対象のウインドウの元の形を退避(_hRgnOld)する(未設定は未設定の情報を取得する)
穴用の円リージョン(hRgnR)を作成。
対象の元の形が未設定の場合、対象ウインドウ全面が元の形であるとする。
元の形に穴を抜いた形を作成する(hRgn)。
不要になったリージョンを削除する。
対象のウインドウに作成した形(リージョン)を適用する。
なお、CombineRgnすればそれぞれの元の形が不要です。
ウインドウに適用したリージョンはWindows管理になるため、解放は不要です。


これで、いろいろなウインドウの形が変えられたりします。
UWSCは、万能実行環境ですね。

追記 20120718

通り抜けフープ(もどき)を設置した壁(Window)を動かす人が出たようなので、スクリプトを修正した。

  • メッセージループ終了待ちの空ループを、壁に追従処理に変更
  • 終了時に不正実行で落ちる対策で、UnregisterClassの追加

追記 20120720

終了関連を再度修正。

  • UnregisterClassはANSIUnicodeがある、、、。
  • WM_CLOSEすべきだった