Classic Alt Tab (Reincarnation)


dacrone

Well-known member
Pro User
VIP
Local time
10:19 PM
Posts
3,179
OS
Windows 11 Pro
I'm working on recreating the Classic Alt Tab via Python. So far it is semi-limited, but am posting the source code for anyone that wants to play with it while i finish it up... unless someone better at python beats me to it.

1) as of now, it is CTRL+ALT+TAB - meaning it stays open until a selection is made. it does not replace Alt+Tab (i have it pinned to my taskbar for now).
2) you can cycle through the icons via Tab / Left Arrow / Up Arrow or counter via Shift+Tab / Right Arrow / Down Arrow
3) you can select via Single Left Click / Enter / Spacebar
4) holds 7 icons per row. height will dynamically expand per number of rows

1745979141597.webp

original classic alt tab:
1745979207947.webp


Code:

Code:
import tkinter as tk
import win32gui
import win32ui
import win32con
import win32api
from PIL import Image, ImageTk
import ctypes
from ctypes import wintypes

class AltTabReplacement:
    def __init__(self, root):
        self.root = root
        self.root.title("Alt+Tab Replacement")
        self.root.configure(bg="#F0F0F0")
        self.root.overrideredirect(True)

        self.windows = self.get_open_windows()
        self.adjust_window_size()

        self.current_index = 0
        self.icon_frames = []
        self.label_display = None

        self.create_interface()
        self.refresh_interface()

        root.bind("<Tab>", self.next_icon)
        root.bind("<Shift-Tab>", self.previous_icon)
        root.bind("<Left>", self.previous_icon)
        root.bind("<Right>", self.next_icon)
        root.bind("<Up>", self.previous_icon)
        root.bind("<Down>", self.next_icon)
        root.bind("<space>", lambda event: self.execute_highlighted(self.current_index))
        root.bind("<Return>", lambda event: self.execute_highlighted(self.current_index))

    def get_cursor_monitor_center(self):
        cursor_pos = ctypes.windll.user32.GetCursorPos
        point = wintypes.POINT()
        cursor_pos(ctypes.byref(point))

        monitor = win32api.MonitorFromPoint((point.x, point.y))
        monitor_info = win32api.GetMonitorInfo(monitor)

        monitor_rect = monitor_info["Monitor"]
        screen_width = monitor_rect[2] - monitor_rect[0]
        screen_height = monitor_rect[3] - monitor_rect[1]

        return monitor_rect[0], monitor_rect[1], screen_width, screen_height

    def adjust_window_size(self):
        icon_size = 50
        max_columns = 7
        total_icons = len(self.windows)

        columns = min(total_icons, max_columns)
        rows = (total_icons // max_columns) + (1 if total_icons % max_columns > 0 else 0)

        window_width = columns * icon_size + 40
        window_height = rows * icon_size + 100

        x, y, screen_width, screen_height = self.get_cursor_monitor_center()
        x = x + (screen_width // 2) - (window_width // 2)
        y = y + (screen_height // 2) - (window_height // 2)

        self.root.geometry(f"{window_width}x{window_height}+{x}+{y}")

    def refresh_interface(self):
        new_windows = self.get_open_windows()

        if len(new_windows) != len(self.windows):
            self.windows = new_windows
            for widget in self.icon_grid.winfo_children():
                widget.destroy()
            self.icon_frames.clear()
            self.create_interface()

        self.root.after(2000, self.refresh_interface)

    def get_open_windows(self):
        windows = []

        def callback(hwnd, _):
            window_title = win32gui.GetWindowText(hwnd)
            if win32gui.IsWindowVisible(hwnd) and window_title and "Windows Input Experience" not in window_title:
                style = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)
                if not (style & win32con.WS_EX_TOOLWINDOW) and (win32gui.GetParent(hwnd) == 0 or style & win32con.WS_EX_APPWINDOW):
                    windows.append((hwnd, window_title))

        win32gui.EnumWindows(callback, None)
        return windows

    def get_window_icon(self, hwnd):
        try:
            hicon = win32gui.SendMessage(hwnd, win32con.WM_GETICON, win32con.ICON_SMALL, 0)
            if hicon == 0:
                hicon = win32gui.SendMessage(hwnd, win32con.WM_GETICON, win32con.ICON_BIG, 0)
            if hicon == 0:
                hicon = win32gui.GetClassLong(hwnd, win32con.GCL_HICON)

            if hicon == 0:
                return None

            hdc = win32ui.CreateDCFromHandle(win32gui.GetDC(0))
            hdc_mem = hdc.CreateCompatibleDC()
            hbitmap = win32ui.CreateBitmap()
            hbitmap.CreateCompatibleBitmap(hdc, 32, 32)
            hdc_mem.SelectObject(hbitmap)

            ctypes.windll.user32.DrawIconEx(hdc_mem.GetHandleAttrib(), 0, 0, hicon, 32, 32, 0, 0, 0x0003)

            bmpinfo = hbitmap.GetInfo()
            bmpstr = hbitmap.GetBitmapBits(True)
            icon_image = Image.frombuffer("RGBA", (bmpinfo["bmWidth"], bmpinfo["bmHeight"]), bmpstr, "raw", "BGRA", 0, 1)

            hdc.DeleteDC()
            hdc_mem.DeleteDC()
            win32gui.DeleteObject(hbitmap.GetHandle())

            return icon_image.resize((28, 28), Image.LANCZOS)
        except Exception as e:
            print(f"Error fetching icon: {e}")
            return Image.new("RGBA", (28, 28), color=(255, 255, 255, 0))

    def create_interface(self):
        self.icon_grid = tk.Frame(self.root, bg="#F0F0F0")
        self.icon_grid.pack(fill=tk.BOTH, expand=True)

        icon_size = 50
        max_columns = 7

        for i, (hwnd, title) in enumerate(self.windows):
            icon_image = self.get_window_icon(hwnd)
            if icon_image is None:
                icon_image = Image.new("RGBA", (24, 24), color=(255, 255, 255, 0))

            tk_icon = ImageTk.PhotoImage(icon_image)

            frame = tk.Frame(self.icon_grid, bg="#F0F0F0", width=icon_size, height=icon_size, relief="flat", borderwidth=1)
            frame.grid(row=i // max_columns, column=i % max_columns, padx=10, pady=10)
            self.icon_frames.append((frame, hwnd, title))

            icon_label = tk.Label(frame, image=tk_icon, bg="#F0F0F0")
            icon_label.image = tk_icon
            icon_label.pack(side=tk.TOP)

            frame.bind("<Button-1>", lambda event, idx=i: self.execute_highlighted(idx))
            icon_label.bind("<Button-1>", lambda event, idx=i: self.execute_highlighted(idx))

        label_width = max_columns * icon_size + 40

        self.label_display = tk.Label(
            self.root, text="", fg="black", bg="#E0E0E0",
            font=("Consolas", 12), relief="sunken", bd=3,
            anchor="w", width=label_width // 10
        )
        self.label_display.pack(pady=10, fill=tk.X, padx=10)

        if self.icon_frames:
            self.highlight_icon(0)

    def highlight_icon(self, index):
        for frame, _, _ in self.icon_frames:
            frame.configure(bg="#F0F0F0", relief="flat", borderwidth=1)

        self.icon_frames[index][0].configure(bg="blue", relief="ridge", borderwidth=3)
        self.label_display.configure(text=self.icon_frames[index][2][:30])  # Truncate label text if too long
        self.current_index = index

    def next_icon(self, event=None):
        self.current_index = (self.current_index + 1) % len(self.icon_frames)
        self.highlight_icon(self.current_index)

    def previous_icon(self, event=None):
        self.current_index = (self.current_index - 1) % len(self.icon_frames)
        self.highlight_icon(self.current_index)

    def execute_highlighted(self, index):
        hwnd = self.icon_frames[index][1]

        window_placement = win32gui.GetWindowPlacement(hwnd)
        is_maximized = window_placement[1] == win32con.SW_SHOWMAXIMIZED

        if not is_maximized:
            win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)

        win32gui.SetForegroundWindow(hwnd)
        self.root.destroy()

if __name__ == "__main__":
    root = tk.Tk()
    app = AltTabReplacement(root)
    root.mainloop()
 

My Computer

System One

  • OS
    Windows 11 Pro

Latest Support Threads

Back
Top Bottom