ArticlesScrapsPagesAbout

オーバーレイウィンドウ

次はWindowsで画面上の左上原点座標(100, 100)に幅高(300, 300)の赤色の円を表示する例:

#include <Windows.h>

#pragma comment(lib, "user32")
#pragma comment(lib, "gdi32")

LPCWSTR WINDOW_CLASS_NAME = L"OverlayClass";
LPCWSTR WINDOW_TITLE      = L"Overlay";
DWORD EX_WINDOW_STYLE = WS_EX_TOPMOST // the window appears above all other windows
	| WS_EX_LAYERED                   // the window is transparentable
	| WS_EX_TRANSPARENT               // the window passes clicks through to the background
	| WS_EX_TOOLWINDOW                // the window doesn't appear in both window switcher and task bar
	| WS_EX_NOACTIVATE;               // the window can't be focused with click
DWORD WINDOW_STYLE    = WS_POPUP;     // the window doesn't have any frame

void showErrorMessage(LPCWSTR text) {
	MessageBoxW(nullptr, text, L"Error", MB_OK | MB_ICONERROR);
}

void paint(HWND window) {
	PAINTSTRUCT ps;
	const HDC hdc = BeginPaint(window, &ps);
	const HBRUSH br = CreateSolidBrush(RGB(255, 0, 0));
	SelectObject(hdc, br);
	Ellipse(hdc, 100, 100, 300, 300);
	DeleteObject(br);
	EndPaint(window, &ps);
}

LRESULT CALLBACK windowProc(HWND window, UINT msg, WPARAM wparam, LPARAM lparam) {
	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_PAINT:
		paint(window);
		return 0;
	default:
		return DefWindowProc(window, msg, wparam, lparam);
	}
}

bool registerWindowClass(HINSTANCE inst) {
	WNDCLASSEXW windowClass;
	windowClass.cbSize = sizeof(WNDCLASSEXW);

	if (GetClassInfoExW(inst, WINDOW_CLASS_NAME, &windowClass)) {
		return true;
	}

	windowClass.style         = CS_CLASSDC;
	windowClass.lpfnWndProc   = windowProc;
	windowClass.cbClsExtra    = 0;
	windowClass.cbWndExtra    = 0;
	windowClass.hInstance     = inst;
	windowClass.hIcon         = nullptr;
	windowClass.hCursor       = nullptr;
	windowClass.hbrBackground = nullptr;
	windowClass.lpszMenuName  = nullptr;
	windowClass.lpszClassName = WINDOW_CLASS_NAME;
	windowClass.hIconSm       = nullptr;
	if (RegisterClassExW(&windowClass) == 0) {
		return false;
	}
	return true;
}

int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR, int) {
	if (!registerWindowClass(inst)) {
		showErrorMessage(L"Failed to register the window class");
		return 1;
	}

	const int x = GetSystemMetrics(SM_XVIRTUALSCREEN); // screen's left edge
	const int y = GetSystemMetrics(SM_YVIRTUALSCREEN); // screen's top edge
	const int w = GetSystemMetrics(SM_CXVIRTUALSCREEN); // screen's width
	const int h = GetSystemMetrics(SM_CYVIRTUALSCREEN); // screen's height

	const HWND window = CreateWindowExW(
		EX_WINDOW_STYLE,
		WINDOW_CLASS_NAME,
		WINDOW_TITLE,
		WINDOW_STYLE,
		x,
		y,
		w,
		h,
		nullptr,
		nullptr,
		inst,
		nullptr
	);
	if (!window) {
		showErrorMessage(L"Failed to create the window");
		return 1;
	}

	SetLayeredWindowAttributes(window, RGB(0, 0, 0), 0, LWA_COLORKEY);
	ShowWindow(window, SW_SHOW);

	MSG msg;
	while (GetMessage(&msg, nullptr, 0, 0)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return 0;
}

次はmacOSで画面上の左下原点座標(100, 100)に幅高(300, 300)の赤色の円を表示する例:

import Cocoa

func showErrorDialog(_ message: String) {
  let alert = NSAlert()
  alert.messageText = "Error"
  alert.informativeText = message
  alert.alertStyle = .critical
  alert.addButton(withTitle: "OK")
  alert.runModal()
}

class OverlayPanel: NSPanel {
    override var canBecomeKey: Bool { false }  // the window can't be a key window
    override var canBecomeMain: Bool { false } // the window can't be a main window
}

class OverlayView: NSView {
    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)

        guard let ctx = NSGraphicsContext.current?.cgContext else { return }

        ctx.setFillColor(NSColor.red.cgColor)
        ctx.fillEllipse(in: CGRect(x: 100, y: 100, width: 300, height: 300))
    }

    // the view skip the hit test
    override func hitTest(_ point: NSPoint) -> NSView? { nil }
}

class AppDelegate: NSObject, NSApplicationDelegate {
    var panel: OverlayPanel?

    func applicationDidFinishLaunching(_ notification: Notification) {
        let fullScreenRect = NSScreen.screens.reduce(NSRect.zero) {
            $0.union($1.frame)
        }

        panel = OverlayPanel(
            contentRect: fullScreenRect,
            styleMask: [.borderless, .nonactivatingPanel], // the window is borderless and can't be focused with click
            backing: .buffered,
            defer: false
        )

        guard let panel else {
            showErrorDialog("Failed to create an overlay panel")
            return
        }

        panel.backgroundColor = .clear
        panel.isOpaque        = false
        panel.level           = .screenSaver // the window appears above all other windows
        panel.ignoresMouseEvents = true      // the window passes clicks through to the background
        panel.collectionBehavior = [         // the window doesn't appear in window switcher, task bar and mission control
            .canJoinAllSpaces,
            .stationary,
            .ignoresCycle,
        ]

        let view = OverlayView(frame: panel.contentView!.bounds)
        view.autoresizingMask = [.width, .height]
        panel.contentView = view
        panel.orderFrontRegardless()
    }
}

let app = NSApplication.shared
let delegate = AppDelegate()
app.delegate = delegate
app.run()