2026-03-09 21:42:02 +03:00
|
|
|
import win32gui
|
|
|
|
|
import win32con
|
|
|
|
|
import win32api
|
|
|
|
|
import threading
|
|
|
|
|
from typing import Callable
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_active_window() -> str:
|
|
|
|
|
"""Get the currently active window title on Windows"""
|
|
|
|
|
try:
|
|
|
|
|
hwnd = win32gui.GetForegroundWindow()
|
|
|
|
|
return win32gui.GetWindowText(hwnd) or "Unknown Window"
|
|
|
|
|
except:
|
|
|
|
|
return "Error getting window title"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def register_session_listener(callback: Callable[[str], None]) -> None:
|
|
|
|
|
"""Register a callback for Windows session lock/unlock events"""
|
|
|
|
|
|
|
|
|
|
def session_event_handler(hwnd, msg, wparam, lparam):
|
2026-03-09 22:05:48 +03:00
|
|
|
# Check if the constant exists before using it
|
|
|
|
|
try:
|
|
|
|
|
if msg == win32con.WM_WTSSESSION_CHANGE:
|
|
|
|
|
if wparam == getattr(win32con, 'WTS_SESSION_LOCK', 0x1):
|
|
|
|
|
callback("locked")
|
|
|
|
|
elif wparam == getattr(win32con, 'WTS_SESSION_UNLOCK', 0x2):
|
|
|
|
|
callback("unlocked")
|
|
|
|
|
except AttributeError:
|
|
|
|
|
# Some versions of pywin32 might not have these constants
|
|
|
|
|
# Use numeric values as fallback
|
|
|
|
|
WM_WTSSESSION_CHANGE = 0x02B1
|
|
|
|
|
WTS_SESSION_LOCK = 0x1
|
|
|
|
|
WTS_SESSION_UNLOCK = 0x2
|
|
|
|
|
|
|
|
|
|
if msg == WM_WTSSESSION_CHANGE:
|
|
|
|
|
if wparam == WTS_SESSION_LOCK:
|
|
|
|
|
callback("locked")
|
|
|
|
|
elif wparam == WTS_SESSION_UNLOCK:
|
|
|
|
|
callback("unlocked")
|
|
|
|
|
|
2026-03-09 21:42:02 +03:00
|
|
|
return win32gui.DefWindowProc(hwnd, msg, wparam, lparam)
|
|
|
|
|
|
|
|
|
|
def run_message_loop():
|
|
|
|
|
hinst = win32api.GetModuleHandle(None)
|
|
|
|
|
wndclass = win32gui.WNDCLASS()
|
|
|
|
|
wndclass.hInstance = hinst
|
|
|
|
|
wndclass.lpszClassName = "SessionWatcher"
|
|
|
|
|
wndclass.lpfnWndProc = session_event_handler
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
win32gui.RegisterClass(wndclass)
|
|
|
|
|
except:
|
|
|
|
|
pass # Already registered
|
|
|
|
|
|
2026-03-09 21:56:44 +03:00
|
|
|
# Corrected CreateWindow call with all required parameters
|
2026-03-09 21:42:02 +03:00
|
|
|
hwnd = win32gui.CreateWindow(
|
2026-03-09 22:05:48 +03:00
|
|
|
wndclass.lpszClassName, # lpClassName
|
|
|
|
|
"Session Watcher", # lpWindowName
|
|
|
|
|
0, # dwStyle
|
|
|
|
|
0, # x
|
|
|
|
|
0, # y
|
|
|
|
|
0, # nWidth
|
|
|
|
|
0, # nHeight
|
|
|
|
|
0, # hWndParent
|
|
|
|
|
0, # hMenu
|
|
|
|
|
hinst, # hInstance
|
|
|
|
|
None # lParam
|
2026-03-09 21:42:02 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Register for session notifications
|
|
|
|
|
try:
|
|
|
|
|
from win32ts import WTSRegisterSessionNotification
|
|
|
|
|
WTSRegisterSessionNotification(hwnd, 1) # NOTIFY_FOR_THIS_SESSION
|
|
|
|
|
except ImportError:
|
|
|
|
|
# pywin32 might not have win32ts on all systems
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# Start message loop
|
|
|
|
|
win32gui.PumpMessages()
|
|
|
|
|
|
|
|
|
|
# Run in a separate thread
|
|
|
|
|
thread = threading.Thread(target=run_message_loop, daemon=True)
|
|
|
|
|
thread.start()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_idle_time() -> int:
|
|
|
|
|
"""Get the system idle time in seconds on Windows"""
|
|
|
|
|
from ctypes import Structure, windll, c_uint, sizeof, byref
|
|
|
|
|
|
|
|
|
|
class LASTINPUTINFO(Structure):
|
|
|
|
|
_fields_ = [('cbSize', c_uint), ('dwTime', c_uint)]
|
|
|
|
|
|
|
|
|
|
lastInputInfo = LASTINPUTINFO()
|
|
|
|
|
lastInputInfo.cbSize = sizeof(lastInputInfo)
|
|
|
|
|
windll.user32.GetLastInputInfo(byref(lastInputInfo))
|
|
|
|
|
|
|
|
|
|
millis = windll.kernel32.GetTickCount() - lastInputInfo.dwTime
|
|
|
|
|
return millis // 1000 # Convert milliseconds to seconds
|