Originally published on tamiz.pro.
Windows has a built-in screenshot tool (Snip & Sketch / Snipping Tool) that owns the Win+Shift+S shortcut, and it's notoriously difficult to override. But what if you want your own custom screenshot utility—one that captures to clipboard, saves with custom filenames, or even runs OCR? This tutorial shows you how to build a Python-based screenshot tool that can intercept global hotkeys, including the elusive Win+Shift+S, by using low-level keyboard hooks and a little Windows API trickery. By the end, you'll have a working, extensible tool that you can bind to your own hotkey or even attempt to hijack the system shortcut (with caveats).
Table of Contents
- 1. Why Windows Won't Let You Own Win+Shift+S
- 2. Prerequisites and Setup
- 3. Building the Screenshot Capture Module
- 4. Global Hotkey Interception with
keyboardLibrary - 5. Attempting to Override Win+Shift+S
- 6. Adding OCR and Clipboard Integration
- 7. Packaging as a Background App
- 8. Full Code Example
- 9. Frequently Asked Questions
1. Why Windows Won't Let You Own Win+Shift+S
On Windows 10 and 11, Win+Shift+S is a system-level shortcut registered by the built-in screen snipping tool. The Windows shell (explorer.exe) registers this hotkey globally, and it takes precedence over most user-level hotkeys. This is because the hotkey is registered with the MOD_WIN modifier and is handled at a higher privilege level. When you try to register a hotkey with the same combination using standard APIs (like RegisterHotKey), you'll get a conflict error.
However, there are workarounds:
- Low-level keyboard hooks: Using
SetWindowsHookExwithWH_KEYBOARD_LL, you can intercept keystrokes before the system processes them. This allows you to "swallow" the key combination and execute your own code instead. - Modifying the system shortcut: You can disable the built-in shortcut via registry settings or Group Policy, but that's invasive and not always reliable.
- Using a different hotkey: The simplest and most robust approach is to use your own hotkey (e.g.,
Ctrl+Alt+S). This tutorial covers both: building a custom tool with your own hotkey, and an attempt to overrideWin+Shift+Susing low-level hooks.
2. Prerequisites and Setup
You'll need Python 3.8+ and the following libraries:
-
Pillow– for image capture and processing -
keyboard– for global hotkey registration (works on Windows, Linux, and macOS) -
pytesseract– for OCR (optional) -
pyperclip– for clipboard operations -
pywin32(optional) – for low-level Windows API access if you want to try the hook approach
Install them with pip:
pip install Pillow keyboard pytesseract pyperclip pywin32 Note: keyboard requires administrator privileges on Windows to register global hooks. Run your script as administrator.
3. Building the Screenshot Capture Module
First, let's create a simple module that captures the entire screen or a region. We'll use Pillow's ImageGrab which works cross-platform on Windows.
Create a file screenshotter.py:
import time import tkinter as tk from PIL import ImageGrab class Screenshotter: @staticmethod def capture_fullscreen(): """Capture the entire screen.""" return ImageGrab.grab() @staticmethod def capture_region(bbox): """Capture a specific region given a bounding box (x1, y1, x2, y2).""" return ImageGrab.grab(bbox=bbox) @staticmethod def select_region(): """Let the user select a region with a crosshair overlay.""" # Create a fullscreen overlay window root = tk.Tk() root.attributes('-fullscreen', True) root.attributes('-alpha', 0.3) root.configure(bg='gray') # Canvas for drawing selection rectangle canvas = tk.Canvas(root, cursor='cross', bg='gray') canvas.pack(fill=tk.BOTH, expand=True) start_x = start_y = 0 rect = None def on_press(event): nonlocal start_x, start_y, rect start_x, start_y = event.x, event.y rect = canvas.create_rectangle(start_x, start_y, start_x, start_y, outline='red', width=2) def on_drag(event): nonlocal rect canvas.coords(rect, start_x, start_y, event.x, event.y) def on_release(event): nonlocal root # Get final coordinates x1 = min(start_x, event.x) y1 = min(start_y, event.y) x2 = max(start_x, event.x) y2 = max(start_y, event.y) root.destroy() # Capture the region img = ImageGrab.grab(bbox=(x1, y1, x2, y2)) return img canvas.bind('<ButtonPress-1>', on_press) canvas.bind('<B1-Motion>', on_drag) canvas.bind('<ButtonRelease-1>', on_release) root.mainloop() # The function will return after mainloop ends, but we need to get the image # This is a simplified version; for a full implementation, use a callback. # We'll improve this later. return None This is a basic version. For a robust region selection, we'll use a more advanced approach later.
4. Global Hotkey Interception with keyboard Library
The keyboard library makes it easy to register global hotkeys. It uses low-level hooks on Windows, so it can detect keys even when your app is not focused. However, it cannot intercept Win+Shift+S directly because the system handles it first. But you can register your own hotkey like Ctrl+Alt+S.
Let's create a script that listens for a hotkey and triggers a screenshot action:
import keyboard from screenshotter import Screenshotter from PIL import Image import time def on_hotkey(): print("Hotkey pressed! Taking screenshot...") img = Screenshotter.capture_fullscreen() timestamp = time.strftime("%Y%m%d_%H%M%S") filename = f"screenshot_{timestamp}.png" img.save(filename) print(f"Saved {filename}") keyboard.add_hotkey('ctrl+alt+s', on_hotkey) print("Listening for Ctrl+Alt+S...") keyboard.wait() This works, but it's not very interesting. Let's make it more feature-rich: capture region, copy to clipboard, and run OCR.
5. Attempting to Override Win+Shift+S
Now, the fun part: trying to intercept Win+Shift+S. The keyboard library allows you to register a hotkey with the Windows key, but it won't work because the system captures it first. However, we can use keyboard.hook to listen for all key events and suppress them if they match our combination.
Here's an attempt:
import keyboard import time def on_key_event(event): # Check if the combination Win+Shift+S is pressed # event.name gives the key name, event.is_keypad, etc. # We need to track state of modifiers if event.event_type == 'down': if event.name == 's' and keyboard.is_pressed('win') and keyboard.is_pressed('shift'): # Suppress the event to prevent system action # But this might not work because the system may have already handled it print("Intercepted Win+Shift+S") return False # Suppress the key event return True keyboard.hook(on_key_event) keyboard.wait() Why this fails: On Windows, the Win+Shift+S shortcut is processed by the shell at a very low level, likely before the keyboard library's hook can see it. The keyboard library uses WH_KEYBOARD_LL, which is a low-level hook, but the shell may have a higher priority or handle it in a different thread. In practice, the hook may receive the event but returning False might not prevent the system from opening the snipping overlay.
Alternative approach: Use SetWindowsHookEx directly with WH_KEYBOARD_LL and set the LLKHF_INJECTED flag to detect injected events? Not helpful. Actually, the only reliable way is to disable the system shortcut via registry or use a utility like AutoHotkey that uses a different hooking mechanism.
Let's try a more aggressive approach: use keyboard.block_key for the Windows key and then listen for Shift+S? That would break the Windows key entirely. Not ideal.
Practical solution: Accept that you can't easily override Win+Shift+S and instead use Ctrl+Alt+S or Ctrl+Shift+S. This is what most custom screenshot tools do. If you really want to hijack Win+Shift+S, you can disable the built-in shortcut by modifying the registry:
- Navigate to
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced - Create a DWORD value
DisabledHotkeyswith a string value containing the hotkey, e.g.,"S"? That's for disabling Win+S, not Win+Shift+S. Actually, you can useDisabledHotkeysto disable specific hotkeys that include the Windows key. Setting it to"S"disables Win+S, but Win+Shift+S may still work.
A more reliable method is to disable the Snipping Tool via Group Policy or remove the shortcut from the Start Menu. But that's beyond the scope of this tutorial.
For the sake of this tutorial, we'll focus on building a robust tool with your own hotkey. If you still want to attempt Win+Shift+S, you can try the hook approach and see if it works on your system—sometimes it does if you run as administrator and use a lower-level hook like WH_KEYBOARD_LL with SetWindowsHookEx from pywin32. Here's a more advanced implementation:
import ctypes from ctypes import wintypes # Define necessary structures user32 = ctypes.WinDLL('user32', use_last_error=True) # SetWindowsHookExW function WH_KEYBOARD_LL = 13 WM_KEYDOWN = 0x0100 WM_KEYUP = 0x0101 # Define callback type HOOKPROC = ctypes.WINFUNCTYPE(ctypes.c_long, ctypes.c_int, wintypes.WPARAM, wintypes.LPARAM) # Define KBDLLHOOKSTRUCT class KBDLLHOOKSTRUCT(ctypes.Structure): _fields_ = [ ("vkCode", wintypes.DWORD), ("scanCode", wintypes.DWORD), ("flags", wintypes.DWORD), ("time", wintypes.DWORD), ("dwExtraInfo", wintypes.ULONG_PTR), ] # Global hook handle hook_handle = None def hook_proc(nCode, wParam, lParam): global hook_handle if nCode >= 0: kb = ctypes.cast(lParam, ctypes.POINTER(KBDLLHOOKSTRUCT)).contents # VK codes: VK_LWIN=0x5B, VK_RSHIFT=0xA1, VK_S=0x53 if wParam == WM_KEYDOWN and kb.vkCode == 0x53: # S key # Check if Win and Shift are pressed if (user32.GetAsyncKeyState(0x5B) & 0x8000) and (user32.GetAsyncKeyState(0xA1) & 0x8000): print("Intercepted Win+Shift+S") # Return 1 to suppress the event return 1 return user32.CallNextHookEx(hook_handle, nCode, wParam, lParam) # Set the hook CALLBACK = HOOKPROC(hook_proc) hook_handle = user32.SetWindowsHookExW(WH_KEYBOARD_LL, CALLBACK, None, 0) if not hook_handle: raise ctypes.WinError(ctypes.get_last_error()) # Message loop import threading import time def message_loop(): msg = wintypes.MSG() while user32.GetMessageW(ctypes.byref(msg), None, 0, 0) != 0: user32.TranslateMessage(ctypes.byref(msg)) user32.DispatchMessageW(ctypes.byref(msg)) thread = threading.Thread(target=message_loop) thread.start() print("Listening for Win+Shift+S...") try: while True: time.sleep(1) except KeyboardInterrupt: user32.UnhookWindowsHookEx(hook_handle) This code sets a low-level keyboard hook and checks for the specific key combination. It might work on some systems, but it's not guaranteed. In practice, the Windows shell may still open the snipping bar before the hook runs, because the shell has its own hook that runs earlier. The only way to guarantee it is to disable the system shortcut.
6. Adding OCR and Clipboard Integration
Let's make our tool genuinely useful. We'll add a region selector that lets the user draw a rectangle, then capture, copy to clipboard, and optionally run OCR.
For region selection, we'll use tkinter with a fullscreen transparent overlay. Here's a more complete implementation:
# region_selector.py import tkinter as tk from PIL import ImageGrab class RegionSelector: def __init__(self): self.root = tk.Tk() self.root.attributes('-fullscreen', True) self.root.attributes('-alpha', 0.3) self.root.configure(bg='gray') self.canvas = tk.Canvas(self.root, cursor='cross', bg='gray') self.canvas.pack(fill=tk.BOTH, expand=True) self.start_x = 0 self.start_y = 0 self.rect = None self.image = None self.canvas.bind('<ButtonPress-1>', self.on_press) self.canvas.bind('<B1-Motion>', self.on_drag) self.canvas.bind('<ButtonRelease-1>', self.on_release) def on_press(self, event): self.start_x = event.x self.start_y = event.y self.rect = self.canvas.create_rectangle(self.start_x, self.start_y, self.start_x, self.start_y, outline='red', width=2) def on_drag(self, event): self.canvas.coords(self.rect, self.start_x, self.start_y, event.x, event.y) def on_release(self, event): x1 = min(self.start_x, event.x) y1 = min(self.start_y, event.y) x2 = max(self.start_x, event.x) y2 = max(self.start_y, event.y) self.root.destroy() # Capture after destroying overlay self.image = ImageGrab.grab(bbox=(x1, y1, x2, y2)) def select(self): self.root.mainloop() return self.image Now, integrate it into the main tool. We'll also add OCR with pytesseract and clipboard copy with pyperclip.
7. Packaging as a Background App
To run the tool in the background, you can either run the script directly (with pythonw.exe on Windows to avoid a console window) or package it with PyInstaller into a standalone executable. We'll cover both.
- To run without console: use
pythonw.exe your_script.py - To package:
pyinstaller --onefile --noconsole screenshot_tool.py
Make sure to include the required libraries. PyInstaller will bundle them.
8. Full Code Example
Let's put it all together into a single script screenshot_tool.py:
import sys import time import tkinter as tk import keyboard import pyperclip import pytesseract from PIL import ImageGrab, Image # Configuration SAVE_DIR = "screenshots" USE_OCR = True class RegionSelector: def __init__(self): self.root = tk.Tk() self.root.attributes('-fullscreen', True) self.root.attributes('-alpha', 0.3) self.root.configure(bg='gray') self.canvas = tk.Canvas(self.root, cursor='cross', bg='gray') self.canvas.pack(fill=tk.BOTH, expand=True) self.start_x = 0 self.start_y = 0 self.rect = None self.image = None self.canvas.bind('<ButtonPress-1>', self.on_press) self.canvas.bind('<B1-Motion>', self.on_drag) self.canvas.bind('<ButtonRelease-1>', self.on_release) def on_press(self, event): self.start_x = event.x self.start_y = event.y self.rect = self.canvas.create_rectangle(self.start_x, self.start_y, self.start_x, self.start_y, outline='red', width=2) def on_drag(self, event): self.canvas.coords(self.rect, self.start_x, self.start_y, event.x, event.y) def on_release(self, event): x1 = min(self.start_x, event.x) y1 = min(self.start_y, event.y) x2 = max(Source:https://dev.to/tamizuddin/hacking-your-own-keyboard-building-a-custom-screenshot-tool-in-python-when-windows-wont-let-you-39a7Related posts
The Annual Renewal Is a Year-Long Conversation, Not a Surprise
My money had no memory, so I built an agent that does
Give your LangChain agent a leash
