Code Vault

Layout Indicator v 1.0 (python)

# Filename: layout_indicator.py (Version 1.0.0 - Stable Release)
# Description: A stable system tray utility to display the current keyboard layout.

import tkinter as tk
import ctypes
import threading
from pynput import keyboard
import pystray
from PIL import Image
import os
import sys
import winshell
from pathlib import Path
import json

# --- APP & SETTINGS CONFIGURATION ---
APP_NAME = "LayoutIndicator"
APP_DATA_FOLDER = Path.home() / "AppData" / "Roaming" / APP_NAME
SETTINGS_FILE = APP_DATA_FOLDER / "settings.json"

# --- FONT SIZE SETTINGS ---
BASE_FONT_SIZE = 250
current_font_scale = 1.0
CURRENT_FONT_SIZE = BASE_FONT_SIZE

# --- GENERAL SETTINGS ---
TIMER_DELAY = 0.2
DISPLAY_TIME_MS = 900
WINDOW_BG_COLOR = "#2B2B2B"
FONT_FAMILY = "Arial Black"
COLORS = ['#87CEEB', '#FF6347', '#98FB98', '#FFD700', '#DDA0DD', '#F08080']

# --- AUTO-START SETUP ---
STARTUP_FOLDER = winshell.startup()
SHORTCUT_PATH = os.path.join(STARTUP_FOLDER, f"{APP_NAME}.lnk")

# --- HELPER FUNCTION TO FIND ASSETS ---
def resource_path(relative_path):
    try:
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")
    return os.path.join(base_path, relative_path)

# --- CORE LOGIC ---
LAYOUTS = {}
def build_layouts_map():
    global LAYOUTS
    user32 = ctypes.WinDLL('user32', use_last_error=True)
    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
    num_layouts = user32.GetKeyboardLayoutList(0, None)
    layout_handles = (ctypes.c_void_p * num_layouts)()
    user32.GetKeyboardLayoutList(num_layouts, layout_handles)
    temp_layouts = {}
    color_index = 0
    for handle in layout_handles:
        lcid = handle & 0xFFFF
        layout_hex = f'{lcid:04x}'.upper()
        full_id = '0000' + layout_hex
        lang_name_buffer = ctypes.create_unicode_buffer(3)
        kernel32.GetLocaleInfoW(lcid, 0x00000059, lang_name_buffer, 3)
        temp_layouts[full_id] = {'name': lang_name_buffer.value.upper(), 'color': COLORS[color_index % len(COLORS)]}
        color_index += 1
    LAYOUTS = temp_layouts
    print("Layouts reloaded:", LAYOUTS)

def get_current_layout_info():
    user32 = ctypes.WinDLL('user32', use_last_error=True)
    hwnd = user32.GetForegroundWindow()
    thread_id = user32.GetWindowThreadProcessId(hwnd, 0)
    layout_id = user32.GetKeyboardLayout(thread_id)
    layout_hex = f'{layout_id & 0xFFFF:04x}'.upper()
    full_id = '0000' + layout_hex
    return LAYOUTS.get(full_id, {'name': '??', 'color': '#FFFFFF'})

def show_indicator():
    layout_info = get_current_layout_info()
    text_to_show = layout_info['name']
    text_color = layout_info['color']
    root = tk.Tk()
    root.withdraw()
    indicator = tk.Toplevel(root)
    indicator.attributes("-topmost", True)
    indicator.overrideredirect(True)
    indicator.attributes("-alpha", 0.85)
    indicator.config(bg=WINDOW_BG_COLOR)
    label = tk.Label(indicator, text=text_to_show, font=(FONT_FAMILY, CURRENT_FONT_SIZE, "bold"), fg=text_color, bg=WINDOW_BG_COLOR)
    label.pack(padx=40, pady=20)
    indicator.update_idletasks()
    screen_width = indicator.winfo_screenwidth()
    screen_height = indicator.winfo_screenheight()
    x = (screen_width // 2) - (indicator.winfo_width() // 2)
    y = (screen_height // 2) - (indicator.winfo_height() // 2)
    indicator.geometry(f"+{x}+{y}")
    indicator.after(DISPLAY_TIME_MS, root.destroy)
    root.mainloop()

def start_keyboard_listener():
    SWITCH_COMBINATIONS = [ {keyboard.Key.alt_l, keyboard.Key.shift}, {keyboard.Key.cmd, keyboard.Key.space} ]
    current_keys = set()
    def on_press(key):
        if key in {keyboard.Key.alt_l, keyboard.Key.shift, keyboard.Key.cmd, keyboard.Key.space}:
            current_keys.add(key)
            for combination in SWITCH_COMBINATIONS:
                if all(k in current_keys for k in combination):
                    threading.Timer(TIMER_DELAY, show_indicator).start()
                    break
    def on_release(key):
        if key in current_keys:
            try:
                current_keys.remove(key)
            except KeyError:
                pass
    with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
        listener.join()

# --- SETTINGS MANAGEMENT ---
def save_settings():
    try:
        os.makedirs(APP_DATA_FOLDER, exist_ok=True)
        settings = {'font_scale': current_font_scale}
        with open(SETTINGS_FILE, 'w') as f:
            json.dump(settings, f)
        print(f"Settings saved to {SETTINGS_FILE}")
    except Exception as e:
        print(f"Error saving settings: {e}")

def load_settings():
    global current_font_scale
    try:
        if os.path.exists(SETTINGS_FILE):
            with open(SETTINGS_FILE, 'r') as f:
                settings = json.load(f)
                if 'font_scale' in settings:
                    current_font_scale = settings['font_scale']
            print(f"Settings loaded from {SETTINGS_FILE}")
    except Exception as e:
        print(f"Error loading settings, using defaults: {e}")
    set_font_size(current_font_scale, save=False)

# --- SYSTEM TRAY MENU ACTIONS ---
def set_font_size(scale, save=True):
    global CURRENT_FONT_SIZE, current_font_scale
    current_font_scale = scale
    CURRENT_FONT_SIZE = int(BASE_FONT_SIZE * scale)
    print(f"Font size set to {CURRENT_FONT_SIZE} ({scale*100}%)")
    if save:
        save_settings()

def toggle_autostart():
    if os.path.exists(SHORTCUT_PATH):
        os.remove(SHORTCUT_PATH)
        print("Autostart disabled.")
    else:
        with winshell.shortcut(SHORTCUT_PATH) as shortcut:
            shortcut.path = sys.executable
            shortcut.description = "Keyboard Layout Indicator"
            shortcut.working_directory = str(Path(sys.executable).parent)
        print("Autostart enabled.")

def is_autostart_enabled():
    return os.path.exists(SHORTCUT_PATH)

def reload_layouts(icon):
    build_layouts_map()

def quit_program(icon):
    icon.stop()
    os._exit(0)

# --- MAIN EXECUTION BLOCK ---
if __name__ == "__main__":
    load_settings()
    build_layouts_map()
    listener_thread = threading.Thread(target=start_keyboard_listener, daemon=True)
    listener_thread.start()
    
    try:
        icon_path = resource_path("icon.png")
        image = Image.open(icon_path)
    except FileNotFoundError:
        print("Error: icon.png not found! Make sure it's in the same folder.")
        sys.exit(1)
        
    # Final, simplified menu
    menu = pystray.Menu(
        pystray.MenuItem('Reload Layouts', reload_layouts),
        pystray.MenuItem('Font Size', pystray.Menu(
            pystray.MenuItem('100% (Default)', lambda: set_font_size(1.0), checked=lambda item: current_font_scale == 1.0, radio=True),
            pystray.MenuItem('75%', lambda: set_font_size(0.75), checked=lambda item: current_font_scale == 0.75, radio=True),
            pystray.MenuItem('50%', lambda: set_font_size(0.50), checked=lambda item: current_font_scale == 0.50, radio=True),
            pystray.MenuItem('25%', lambda: set_font_size(0.25), checked=lambda item: current_font_scale == 0.25, radio=True)
        )),
        pystray.MenuItem('Start with Windows', toggle_autostart, checked=lambda item: is_autostart_enabled()),
        pystray.MenuItem('Exit', quit_program)
    )
    
    icon = pystray.Icon("LayoutIndicator", image, "Layout Indicator", menu)
    
    print("Program started and is running in the system tray.")
    icon.run()

 

 

Follow the Vibe


Read More