Skip to main content

On This Page

How to Hide Tkinter Windows from Screen Sharing via Python Win32 API

3 min read
Share

These articles are AI-generated summaries. Please check the original sources for full details.

Python Remove Window From Screen Share With Tkinter

Developer Avinash Tare has demonstrated a method to programmatically hide GUI windows from capture software using the Windows API. The system utilizes the SetWindowDisplayAffinity function to render content invisible to screen recording and sharing tools.

Why This Matters

Operating systems manage display content through affinity flags that dictate how windows are rendered in capture buffers. While standard GUI libraries like Tkinter do not natively expose these controls, direct interaction with user32.dll allows developers to enforce hardware-level privacy by excluding specific handles from the desktop duplication stream. This approach represents a technical intersection between high-level scripting and low-level OS display management, though it introduces significant ethical considerations regarding transparency and integrity in remote environments.

Key Insights

  • The SetWindowDisplayAffinity function with the WDA_EXCLUDEFROMCAPTURE (0x11) constant prevents content from appearing in video captures.
  • Accessing the window handle (HWND) requires ctypes.windll.user32.GetParent to target the Tkinter root window correctly.
  • Applying the WS_EX_LAYERED (0x80000) style via SetWindowLongW is necessary for enabling advanced window display attributes.
  • Using overrideredirect(True) removes the standard OS title bar, necessitating custom Python implementations for window dragging and resizing.

Working Examples

Python script using Tkinter and ctypes to create a window that is excluded from screen capture.

import tkinter as tk
from tkinter import Listbox, END
import ctypes
import json
import os
from datetime import datetime

# ---------------- FILE ----------------
chat_file = "chat_history.json"
def load_chat():
    if os.path.exists(chat_file):
        with open(chat_file, "r") as f:
            return json.load(f)
    return []

def save_chat(data):
    with open(chat_file, "w") as f:
        json.dump(data, f, indent=4)

chat_data = load_chat()

# ---------------- WINDOW ----------------
root = tk.Tk()
root.overrideredirect(True)
root.attributes("-topmost", True)
root.geometry("340x450+200+200")
root.configure(bg="#1e1e1e")
root.update()

# ---------------- WINDOWS API ----------------
hwnd = ctypes.windll.user32.GetParent(root.winfo_id())
GWL_EXSTYLE = -20
WS_EX_LAYERED = 0x80000
styles = ctypes.windll.user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
ctypes.windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, styles | WS_EX_LAYERED)
ctypes.windll.user32.SetLayeredWindowAttributes(hwnd, 0, 255, 0x2)

try:
    WDA_EXCLUDEFROMCAPTURE = 0x11
    ctypes.windll.user32.SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE)
except:
    pass

# ---------------- TOOLBAR ----------------
toolbar = tk.Frame(root, bg="#0f0f0f", height=28)
toolbar.pack(fill=tk.X)
title = tk.Label(toolbar, text="Mini Chat", fg="white", bg="#0f0f0f")
title.pack(side=tk.LEFT, padx=8)

def close_app():
    root.destroy()

close_btn = tk.Button(toolbar, text="✕", command=close_app, bg="#0f0f0f", fg="white", bd=0, activebackground="red", activeforeground="white")
close_btn.pack(side=tk.RIGHT, padx=5)

def start_move(event):
    root._x = event.x
    root._y = event.y

def do_move(event):
    x = root.winfo_pointerx() - root._x
    y = root.winfo_pointery() - root._y
    root.geometry(f"+{x}+{y}")

toolbar.bind("<Button-1>", start_move)
toolbar.bind("<B1-Motion>", do_move)

# ---------------- CHAT AREA ----------------
chat_list = Listbox(root, bg="#2b2b2b", fg="white", bd=0, highlightthickness=0)
chat_list.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
for msg in chat_data:
    chat_list.insert(END, msg)

# ---------------- INPUT ----------------
entry = tk.Entry(root, bg="#3a3a3a", fg="white", bd=0)
entry.pack(fill=tk.X, padx=5, pady=5, ipady=6)

def send_message(event=None):
    msg = entry.get().strip()
    if msg:
        time = datetime.now().strftime("%H:%M")
        full_msg = f"[{time}] You: {msg}"
        chat_data.append(full_msg)
        save_chat(chat_data)
        chat_list.insert(END, full_msg)
        chat_list.yview(END)
        entry.delete(0, END)

entry.bind("<Return>", send_message)

# ---------------- RESIZE ----------------
resizer = tk.Frame(root, bg="#444444", cursor="size_nw_se", width=10, height=10)
resizer.place(relx=1.0, rely=1.0, anchor="se")

def start_resize(event):
    root._rw = root.winfo_width()
    root._rh = root.winfo_height()
    root._rx = event.x
    root._ry = event.y

def do_resize(event):
    w = root._rw + (event.x - root._rx)
    h = root._rh + (event.y - root._ry)
    if w > 220 and h > 250:
        root.geometry(f"{w}x{h}")

resizer.bind("<Button-1>", start_resize)
resizer.bind("<B1-Motion>", do_resize)

root.mainloop()

Practical Applications

  • Privacy-Centric Chat Tools: Developing applications that allow users to view sensitive data locally while sharing their screen for other purposes without exposing private information.
  • Security Pitfall: Using capture-exclusion techniques to bypass proctoring or interview monitoring systems, which leads to immediate disqualification and loss of professional trust.

References:

Continue reading

Next article

Real-Time Breath Detection in the Browser: Spectral Centroid and Dual-Path State Machines

Related Content