0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Windowsでマウスの軌跡を表示するPythonスクリプト

Last updated at Posted at 2025-04-28

マウス軌跡の表示方法が違いすぎる

  • ZOOMやMeetにはレーザーポインター機能はあるが
    • オンラインミーティングツールごとに違いがありすぎる
    • リアルなミーティングの際には使えない
  • GoogleSlideにもレーザーポインタ機能があるが
    • 当然GoogleSlide以外の画面では通用しない
  • PowerToysにもマウスを見やすくするツールはあるが
    • いろんなキーボードショートカット奪わすぎて不便

ということでしかたなく自作。

どうせOS依存ならWin32APIをゴリゴリに叩けばいい

  • Win32APIをゴリゴリに叩けるスクリプト言語ならPythonがちょうどいい
  • PythonはCの関数を呼び出しやすい(なんならCよりも)

完成コード

mouse.py
import signal
import sys
import tkinter as tk
from collections import deque
import ctypes
from ctypes import wintypes

class MouseTrailOverlay(tk.Tk):
    def __init__(self):
        super().__init__()
        
        # Windows API設定
        self.user32 = ctypes.windll.user32
        self._setup_windows_cursor()
        
        # GUI設定
        self.attributes('-fullscreen', True)
        self.attributes('-topmost', True)
        self.attributes('-transparentcolor', 'white')
        self.overrideredirect(True)
        
        # クリックスルー設定
        self._make_window_click_through()
        
        # 描画用キャンバス
        self.canvas = tk.Canvas(self, bg='white', highlightthickness=0)
        self.canvas.pack(fill=tk.BOTH, expand=True)
        self.points = deque(maxlen=30)
        
        # 終了処理設定
        self.protocol("WM_DELETE_WINDOW", self.close)
        self._setup_signal_handlers()
        self._setup_timers()

    def _make_window_click_through(self):
        """ウィンドウをクリックスルー可能にする"""
        WS_EX_LAYERED = 0x00080000
        WS_EX_TRANSPARENT = 0x00000020
        hwnd = self.user32.GetParent(self.winfo_id())
        
        # 現在の拡張スタイルを取得
        ex_style = self.user32.GetWindowLongW(hwnd, -20)  # GWL_EXSTYLE=-20
        
        # 新しいスタイルを設定
        new_ex_style = ex_style | WS_EX_LAYERED | WS_EX_TRANSPARENT
        self.user32.SetWindowLongW(hwnd, -20, new_ex_style)
        self.user32.SetLayeredWindowAttributes(hwnd, 0x00FFFFFF, 255, 0x00000001)

    def _setup_windows_cursor(self):
        class POINT(ctypes.Structure):
            _fields_ = [('x', wintypes.LONG), ('y', wintypes.LONG)]
        
        def get_cursor_pos():
            pt = POINT()
            if self.user32.GetCursorPos(ctypes.byref(pt)):
                return pt.x, pt.y
            return 0, 0
        self.get_cursor_pos = get_cursor_pos

    def _setup_signal_handlers(self):
        signal.signal(signal.SIGINT, self._signal_handler)
        self.bind('<Control-c>', self._signal_handler)

    def _setup_timers(self):
        self.after(10, self._update_position)
        self.after(50, self._redraw_trail)
        self.after(100, self._check_exit)

    def _update_position(self):
        if hasattr(self, 'get_cursor_pos'):
            x, y = self.get_cursor_pos()
            self.points.append((x, y))
        self.after(10, self._update_position)

    def _redraw_trail(self):
        self.canvas.delete('trail')
        if len(self.points) > 1:
            self.canvas.create_line(
                *self.points, 
                fill='#FF0000', 
                width=5, 
                smooth=True, 
                tags='trail'
            )
        self.after(50, self._redraw_trail)

    def _check_exit(self):
        if not self.winfo_exists():
            sys.exit(0)
        self.after(100, self._check_exit)

    def _signal_handler(self, signum=None, frame=None):
        print("\n終了シグナルを受信しました")
        self.close()

    def close(self):
        self.destroy()
        self.quit()
        ctypes.windll.user32.PostQuitMessage(0)
        sys.exit(0)

if __name__ == '__main__':
    app = MouseTrailOverlay()
    try:
        app.mainloop()
    except KeyboardInterrupt:
        app.close()

実行結果

image.png

CTL+c で終了します

解説

  • tkinterモジュールで透明のウィンドウを最前面に表示
  • win32apiでマウスの位置を取得
  • 透明のウィンドウ上のcanvasに描画
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?