P01: Personal Clipboard Manager

P01: Personal Clipboard Manager

A comprehensive guide to building a clipboard history tool with AutoHotkey v2


Project Overview

What you’ll build: A clipboard history tool that stores the last 20 copied items, lets you search them with a hotkey popup, and paste any previous clip with a keystroke.

Attribute Value
Difficulty Level 1: Beginner
Time Estimate Weekend (8-12 hours)
Programming Language AutoHotkey v2
Knowledge Area Desktop Automation, Windows API
Prerequisites Basic programming concepts, AutoHotkey v2 installed

1. Learning Objectives

By the end of this project, you will be able to:

Core Skills

  1. Implement event-driven programming patterns - Register callbacks that respond to system events (clipboard changes, hotkey presses) in real-time, understanding the difference between polling and event subscription
  2. Create native Windows GUIs - Build popup windows with interactive controls (Edit boxes, ListBoxes) that respond to user input and manage focus correctly
  3. Monitor operating system events - Use OnClipboardChange to detect clipboard changes without polling, understanding how Windows message queues work
  4. Design data persistence strategies - Choose appropriate file formats (INI, JSON, plain text) and implement save/load cycles that survive script restarts

Applied Knowledge

  1. Handle keyboard shortcuts at the OS level - Register global hotkeys that work across all applications using Windows hooks
  2. Implement real-time search filtering - Filter collections dynamically as the user types, updating UI without perceptible lag
  3. Manage application state - Track multiple state variables (history, search query, selection, visibility) and keep them synchronized
  4. Debug Windows automation issues - Diagnose common problems with clipboard access, input simulation, and GUI focus

Transferable Concepts

  1. Observer Pattern - The clipboard monitor is a canonical example of the Observer pattern
  2. MVC separation - Separating data (history array), presentation (GUI), and logic (handlers)
  3. Graceful degradation - Handling edge cases (empty clipboard, corrupted files) without crashing

2. Deep Theoretical Foundation

2.1 The Windows Clipboard Architecture

The Windows clipboard is far more complex than “a place where copied text goes.” Understanding its architecture will make you a better Windows developer.

What the Clipboard Actually Is

At the operating system level, the clipboard is a kernel-managed shared memory region with controlled access. It’s not a simple variable - it’s a multi-format data store with ownership semantics.

                    USER SPACE                        KERNEL SPACE
             +----------------------+            +-------------------+
             |    Application A     |            |                   |
             |  (Notepad - copying) |            |   CLIPBOARD       |
             |                      |            |   KERNEL OBJECT   |
             |  OpenClipboard()  ------>         |                   |
             |  EmptyClipboard() ------>         |  Owner: App A     |
             |  SetClipboardData()----->         |                   |
             |  CloseClipboard() ------>         |  Formats:         |
             +----------------------+            |  - CF_TEXT        |
                                                 |  - CF_UNICODETEXT |
             +----------------------+            |  - CF_HTML        |
             |    Application B     |            |  - CF_RTF         |
             |  (Your AHK Script)   |            |                   |
             |                      |            |  Sequence#: 42    |
             |  GetClipboardData()<-----         |                   |
             |  A_Clipboard        <-----         |  Listeners:       |
             |                      |            |  - App B (AHK)    |
             +----------------------+            |  - App C          |
                                                 +-------------------+

The Multi-Format Model

When you copy text from a rich text editor, Windows stores the same content in multiple formats simultaneously:

Copying "Hello, World!" from Microsoft Word:

+------------------------------------------------------------------+
|                    CLIPBOARD CONTENTS                             |
+------------------------------------------------------------------+
| Format ID        | Format Name      | Content                     |
|------------------|------------------|------------------------------|
| 1                | CF_TEXT          | "Hello, World!" (ANSI)      |
| 13               | CF_UNICODETEXT   | "Hello, World!" (UTF-16)    |
| 49285            | HTML Format      | <html>...<b>Hello</b>...</   |
| 49286            | Rich Text Format | {\rtf1\ansi...Hello...}     |
| 49287            | Office Drawing   | [binary data]               |
+------------------------------------------------------------------+

This is delayed rendering - applications can register that they CAN provide a format, and Windows only asks for the actual data when another application requests it. This prevents copying a 50MB image from freezing your computer.

The Ownership Model

Only ONE application can own the clipboard at a time. This prevents race conditions:

Timeline of Clipboard Operations:

T1  [App A] ----> OpenClipboard()       SUCCESS - App A now owns clipboard
T2  [App B] ----> OpenClipboard()       FAIL - clipboard locked by App A
T3  [App A] ----> EmptyClipboard()      Clears all formats
T4  [App A] ----> SetClipboardData()    Adds CF_TEXT
T5  [App A] ----> SetClipboardData()    Adds CF_UNICODETEXT
T6  [App A] ----> CloseClipboard()      Releases ownership, notifies listeners
T7  [App B] ----> OpenClipboard()       SUCCESS - clipboard now available

Clipboard Notifications

Rather than polling the clipboard (inefficient), Windows provides notification mechanisms:

                                           WM_CLIPBOARDUPDATE
                                                  |
+----------------+    +-------------------+       v      +------------------+
|   Clipboard    |    |   Windows Message |      +--->   | Your AHK Script  |
|   Contents     |--->|   Dispatcher      |------|       | OnClipboardChange|
|   Changed!     |    |                   |      +--->   +------------------+
+----------------+    +-------------------+       |      +------------------+
                                                 +--->   | Other Clipboard  |
                                                         | Managers         |
                                                         +------------------+

Notification Flow:
1. Any app calls CloseClipboard() after modifying contents
2. Windows increments the clipboard sequence number
3. Windows dispatches WM_CLIPBOARDUPDATE to all registered listeners
4. Each listener's callback is invoked (OnClipboardChange in AHK)
5. Listener can then call GetClipboardData() to read contents

Why This Matters for Your Project

Understanding this architecture helps you:

  • Know why you need OnClipboardChange instead of polling (efficiency)
  • Understand Type parameter: 1 = text copied, 2 = non-text copied
  • Avoid race conditions: Don’t hold the clipboard open for long
  • Handle failures gracefully: Another app might lock the clipboard

2.2 Event-Driven Programming Model

Event-driven programming is the dominant paradigm for interactive applications. Understanding it deeply will help you in web development, game development, and desktop automation.

The Problem with Imperative Programming

Traditional imperative programs execute linearly:

IMPERATIVE MODEL (like a batch script):

main():
    read_file()           // Step 1
    process_data()        // Step 2
    write_output()        // Step 3
    exit()                // Done!

Problem: Where does "wait for user input" fit?
Problem: How do you handle "user can copy at any time"?

For interactive applications, we need to WAIT for things to happen:

EVENT-DRIVEN MODEL (like your clipboard manager):

setup():
    register_clipboard_listener(on_copy)
    register_hotkey(Win+V, on_hotkey)

on_copy(data):
    add_to_history(data)

on_hotkey():
    show_popup()

on_selection(item):
    paste_item(item)

main():
    setup()
    run_event_loop()  // Never exits - waits forever

The Event Loop: Heart of Event-Driven Programs

Every GUI application has an event loop at its core:

+----------------------------------------------------------+
|                      EVENT LOOP                           |
+----------------------------------------------------------+

                    +------------------+
                    |  Message Queue   |
                    |  +------------+  |
                    |  | WM_KEYDOWN |  |
                    |  | WM_COPY    |  |
                    |  | WM_TIMER   |  |
        OS Events   |  | ...        |  |
        --------->  |  +------------+  |
        (Keyboard,  +--------|---------+
        Mouse,               |
        Timer,               v
        Clipboard)  +------------------+
                    |  GetMessage()    |  <-- Blocks until message available
                    +--------|---------+
                             |
                             v
                    +------------------+
                    |  DispatchMessage |  <-- Routes to appropriate handler
                    +--------|---------+
                             |
                +------------+------------+
                |            |            |
                v            v            v
        +-----------+  +-----------+  +-----------+
        | Hotkey    |  | Clipboard |  | GUI       |
        | Handler   |  | Handler   |  | Handler   |
        +-----------+  +-----------+  +-----------+

This loop runs FOREVER (until script exits)

Callback Functions: The Building Blocks

A callback is a function you provide that the system calls when something happens:

// YOU write this:
ClipboardChanged(Type) {
    if (Type = 1) {
        SaveToHistory(A_Clipboard)
    }
}

// YOU register it:
OnClipboardChange(ClipboardChanged)

// THE SYSTEM calls it when clipboard changes:
//   [User presses Ctrl+C]
//     -> Windows: "Clipboard changed!"
//     -> Windows: "Who's listening? AHK is."
//     -> Windows: "Calling ClipboardChanged(1)"

The Observer Pattern

Your clipboard manager is a textbook implementation of the Observer Pattern:

+----------------+         +-------------------+
|   SUBJECT      |         |   OBSERVER        |
| (Clipboard)    |  notify |  (Your Script)    |
|                |-------->|                   |
| - contents     |         | - ClipboardChanged|
| - sequence#    |         | - SaveToHistory() |
|                |         |                   |
| + addListener()|         | + update(data)    |
| + removeList() |         +-------------------+
| + notify()     |
+----------------+
                           +-------------------+
                   notify  |   OBSERVER 2      |
               +---------->|  (Some other app) |
               |           +-------------------+
               |
               |           +-------------------+
               +---------->|   OBSERVER 3      |
                   notify  |  (Cloud sync app) |
                           +-------------------+

Key Insight: The Subject (clipboard) doesn't know or care
            WHO is observing. It just broadcasts changes.

2.3 GUI Fundamentals in Windows

Understanding how Windows GUIs work will help you create responsive, well-behaved applications.

The Window Hierarchy

Every visual element in Windows is a “window” with a handle (HWND):

DESKTOP (root of everything)
  |
  +-- Explorer Shell (taskbar, start menu)
  |
  +-- Notepad Window (HWND: 0x001A...)
  |     |
  |     +-- Title Bar (HWND: 0x001B...)
  |     +-- Menu Bar (HWND: 0x001C...)
  |     +-- Edit Area (HWND: 0x001D...)  <-- The actual text area
  |     +-- Status Bar (HWND: 0x001E...)
  |
  +-- YOUR CLIPBOARD MANAGER (HWND: 0x00FF...)
        |
        +-- Search Edit Box (HWND: 0x0100...)
        +-- History ListBox (HWND: 0x0101...)

Every rectangle you see on screen has an HWND. AutoHotkey abstracts this but it’s useful to know.

Focus: Who Gets Keyboard Input?

Only ONE control can have “focus” at a time - this is where keyboard input goes:

Scenario: User presses Win+V to open clipboard manager

BEFORE:                         AFTER Win+V:

+---------------+               +---------------+
| Notepad       |               | Notepad       |
| +-----------+ |               | +-----------+ |
| | [FOCUSED] | | <-- Keys here | |           | |
| | Edit Box  | |               | | Edit Box  | |
| +-----------+ |               | +-----------+ |
+---------------+               +---------------+

                                +-----------------+
                                | Clipboard Mgr   |
                                | +-------------+ |
                                | | [FOCUSED]   | | <-- Keys here now
                                | | Search Box  | |
                                | +-------------+ |
                                | +-------------+ |
                                | | History     | |
                                | +-------------+ |
                                +-----------------+

Focus moved from Notepad's Edit to your Search Box

Z-Order: Window Stacking

Windows are stacked like papers on a desk. “Always on top” windows stay above normal windows:

                    Z-ORDER (top = front)
                    =====================

                    +---+
                    |   | Always On Top
                    +---+ (Your popup)
                      |
                      |
                    +-|-+
                    |   | Normal Windows
                    +---+ (Notepad)
                      |
                    +-|-+
                    |   | (Chrome)
                    +---+
                      |
                    +-|-+
                    |   | Desktop
                    +---+

GUI Controls and Their Messages

Controls communicate through Windows messages:

User types "hello" in Search Box:

Keystroke 'h':
+-------------+                 +---------------+
| Keyboard    |  WM_KEYDOWN    | Search Edit   |
| Hardware    | --------------> | Control       |
|             |                 |               |
+-------------+                 | - Updates text|
                                | - Sends EN_   |
                                |   CHANGE to   |
                                |   parent      |
                                +-------+-------+
                                        |
                                        | EN_CHANGE
                                        v
                                +---------------+
                                | Your Script   |
                                | FilterList()  |
                                +---------------+

AHK translates Windows messages into events you can handle:
  SearchEdit.OnEvent("Change", FilterHistory)

2.4 Data Persistence Strategies

Your clipboard history needs to survive script restarts and reboots. Here’s how to think about persistence:

When to Save?

Three strategies with different tradeoffs:

STRATEGY 1: Save After Every Change
==================================
+copy happens+  -->  SaveToFile()  -->  [DISK WRITE]

Pros: Never lose data
Cons: Many disk writes (could slow down rapid copying)

STRATEGY 2: Save On Exit
========================
+copy happens+  -->  [Memory only]
...
+script exits+  -->  SaveToFile()  -->  [DISK WRITE]

Pros: Fast, one write
Cons: Crash = all history lost since last restart

STRATEGY 3: Periodic Save (Recommended)
======================================
+copy happens+  -->  [Memory only]  -->  SetTimer(SaveToFile, 30000)
                                          |
                                          v (every 30 seconds)
                                    [DISK WRITE]

Pros: Balance of performance and safety
Cons: Slight complexity

File Format Comparison

OPTION 1: INI Format
====================
[History]
Count=3
Item1=First copied text
Item2=Second copied text
Item3=Third item

Pros: Native AHK support (IniRead/IniWrite)
      Human-readable
      Simple to implement

Cons: No nesting
      Newlines in text are problematic
      No standard escape mechanism

OPTION 2: JSON Format
=====================
{
  "history": [
    {"text": "First copied", "time": 1703500000},
    {"text": "Second copied", "time": 1703500100}
  ],
  "settings": {
    "maxItems": 20
  }
}

Pros: Structured data
      Industry standard
      Can store metadata

Cons: Requires JSON library in AHK v2
      Slightly more complex parsing

OPTION 3: Custom Delimiter Format
=================================
First copied text
<<<SEP>>>
Second copied text
<<<SEP>>>
Third item

Pros: Simple to implement
      Handles multi-line text

Cons: Delimiter could theoretically appear in content
      No metadata support
      Not a standard format

RECOMMENDATION: Start with Custom Delimiter (simplest)
                Upgrade to JSON if you need metadata

The Save/Load Lifecycle

SCRIPT STARTUP:
===============
                  +------------+
                  | Script     |
                  | Starts     |
                  +-----+------+
                        |
                        v
                  +------------+
                  | File       |
                  | Exists?    |
                  +-----+------+
                    Yes |   | No
                        |   +----> Initialize empty history
                        v
                  +------------+
                  | Read File  |
                  +-----+------+
                        |
                        v
                  +------------+
                  | Parse Into |
                  | ClipHistory|
                  | Array      |
                  +------------+


SCRIPT RUNNING:
===============
   [User copies]
         |
         v
   Add to ClipHistory[]
         |
         v
   Start/Reset save timer
         |
         v (30 seconds later OR on exit)
   +------------+
   | Serialize  |
   | Array to   |
   | String     |
   +-----+------+
         |
         v
   +------------+
   | Write to   |
   | File       |
   +------------+

3. Complete Project Specification

3.1 Detailed Feature List

Core Features (Must Have)

ID Feature Description Acceptance Criteria
F1 Clipboard Monitoring Detect when user copies text Callback fires within 100ms of Ctrl+C
F2 History Storage Maintain last 20 copied items Array stores items, oldest removed when > 20
F3 Hotkey Activation Open popup with Win+V GUI appears within 100ms, focused
F4 History Display Show items in scrollable list All items visible, truncated if > 50 chars
F5 Real-time Search Filter list as user types List updates on every keystroke
F6 Item Selection Navigate with arrow keys Up/Down change selection, wraps at ends
F7 Paste Action Enter pastes selected item Sets clipboard, sends Ctrl+V to previous app
F8 Dismiss Popup Escape closes without action GUI hides, focus returns to previous app

Enhanced Features (Should Have)

ID Feature Description Acceptance Criteria
F9 Persistence Save history to disk History restored after script restart
F10 Duplicate Prevention Ignore consecutive duplicates Same text copied twice = one entry
F11 Focus Handling Close when clicking outside GUI hides if another window activated
F12 Timestamp Display Show age of each item “5m ago”, “2h ago”, etc.

Polish Features (Nice to Have)

ID Feature Description Acceptance Criteria
F13 System Tray Icon Show icon in notification area Right-click menu with Show/Exit
F14 Configuration Customizable hotkey and size Settings in config file
F15 Clear History Delete all history Menu option or hotkey
F16 Visual Feedback Toast on copy Brief notification when text saved

3.2 User Experience Flows

Flow 1: Normal Copy and Paste from History

+--------+      +----------+      +-----------+      +------------+
| User   |      | Notepad  |      | Your      |      | Notepad    |
| Action |      | (Source) |      | Script    |      | (Dest)     |
+--------+      +----------+      +-----------+      +------------+
    |                |                  |                  |
    | Types text     |                  |                  |
    |--------------->|                  |                  |
    |                |                  |                  |
    | Selects, Ctrl+C|                  |                  |
    |--------------->|                  |                  |
    |                |                  |                  |
    |                | Clipboard Change |                  |
    |                |----------------->|                  |
    |                |                  |                  |
    |                |                  | Saves to history |
    |                |                  |-----+            |
    |                |                  |     |            |
    |                |                  |<----+            |
    |                |                  |                  |
    | (later)        |                  |                  |
    | Switches to    |                  |                  |
    | Notepad (new)  |---------------------------------->  |
    |                |                  |                  |
    | Presses Win+V  |                  |                  |
    |---------------------------------->|                  |
    |                |                  |                  |
    |                |                  | Shows popup      |
    |                |                  |-----+            |
    |                |                  |     |            |
    | Sees history   |<-----------------|<----+            |
    |                |                  |                  |
    | Types search   |                  |                  |
    |---------------------------------->|                  |
    |                |                  |                  |
    |                |                  | Filters list     |
    | Sees filtered  |<-----------------|                  |
    |                |                  |                  |
    | Presses Enter  |                  |                  |
    |---------------------------------->|                  |
    |                |                  |                  |
    |                |                  | Hides popup      |
    |                |                  | Sets clipboard   |
    |                |                  | Sends Ctrl+V     |
    |                |                  |----------------->|
    |                |                  |                  |
    | Sees text      |                  |                  |
    | pasted!        |<----------------------------------- |
    +----------------+------------------+------------------+

Flow 2: Dismiss Without Selection

User:         Your Script:         Result:
=====         ============         =======

Win+V     --> Show popup       --> Popup appears
              Focus search

"tes"     --> Filter list      --> 3 items matching "tes"

Escape    --> Hide popup       --> Popup disappears
              Restore focus        Previous app focused
              (no clipboard        Clipboard unchanged
               change)

Flow 3: Copy While Popup is Open

User:              Your Script:                 Result:
=====              ============                 =======

Win+V          --> Show popup                --> Popup visible

(User alt-tabs to another window, copies something)

Ctrl+C         --> ClipboardChanged fires    --> New item added
                   Add to history                to top of list

(User switches back to popup)

                   List now shows new item   --> User can select
                   at the top                    the just-copied item

4. Real World Outcome

When this project is complete, you will have a genuinely useful tool that becomes part of your daily workflow. Here is exactly what you will see and experience:

4.1 System Tray Presence

When your script starts, it appears in the Windows system tray (notification area):

                    Windows Taskbar
+-----------------------------------------------------------------------+
|                                                                       |
| [Start] [Search] [Edge] [Explorer]              [^] [Wifi] [Vol] [Clock]
|                                                   |
+-----------------------------------------------------------------------+
                                                    |
                                         +----------+----------+
                                         | Notification Area   |
                                         +---------------------+
                                         | [Wifi] [Vol] [BT]   |
                                         | [OneDrive] [CLIP]   | <-- Your icon here!
                                         +---------------------+

Right-clicking the tray icon shows:
+------------------+
| Clipboard Manager|
|------------------|
| Show History     |
| Settings...      |
|------------------|
| Exit             |
+------------------+

4.2 Hotkey Activation

Pressing Win+V (or your configured hotkey) immediately shows the popup:

BEFORE (user is typing in VS Code):

+------------------------------------------------------------------+
| VS Code - project.py                                        [-][X]|
|------------------------------------------------------------------|
|                                                                   |
|  def main():                                                      |
|      print("Hello")  |  <-- Cursor here                          |
|                                                                   |
+------------------------------------------------------------------+


AFTER Win+V (popup appears instantly, overlaying the editor):

+------------------------------------------------------------------+
| VS Code - project.py                                        [-][X]|
|------------------------------------------------------------------|
|                                                                   |
|  def main():                                                      |
|      print(  +----------------------------------+                 |
|              | Clipboard History           [X] |                 |
|              |----------------------------------|                 |
|              | Search: [                    ]   | <-- Cursor here|
|              |----------------------------------|                 |
|              | > print("Hello, World!")    2m  | <-- Selected   |
|              |   import numpy as np        5m  |                 |
|              |   def calculate(x, y):     12m  |                 |
|              |   return sorted(items)     15m  |                 |
|              |   "API_KEY=sk-1234..."     22m  |                 |
|              |                                  |                 |
|              +----------------------------------+                 |
+------------------------------------------------------------------+

4.3 GUI Popup Appearance

The popup window has a clean, functional design:

+--------------------------------------------------+
| Clipboard History                            [X] |
+--------------------------------------------------+
|                                                  |
|  Search: [_________________________]             |
|                                                  |
|  +--------------------------------------------+  |
|  | > typedef struct { int x; int y; } Point  |  | <-- Selected (highlighted)
|  |   import React from 'react';              |  |
|  |   const API_URL = "https://api.exam...    |  |
|  |   git commit -m "Fix null pointer bug"    |  |
|  |   docker run -it --rm ubuntu:22.04        |  |
|  |   SELECT * FROM users WHERE active = 1    |  |
|  |   npm install --save-dev typescript       |  |
|  |   export PATH=$PATH:/usr/local/bin        |  |
|  |   curl -X POST https://api.example.c...   |  |
|  |   ssh -i key.pem ubuntu@192.168.1.100     |  |
|  +--------------------------------------------+  |
|                                                  |
|  20 items | Newest: just now | Oldest: 2h ago   |
+--------------------------------------------------+

Window Properties:
- Always on top (won't go behind other windows)
- No taskbar entry (clean, popup-like behavior)
- Centered on active monitor
- Keyboard-focused on search box immediately

4.4 Search Filtering Behavior

As you type, the list filters in real-time (no delay):

TYPING "git" IN SEARCH BOX:

Keystroke 'g':
+--------------------------------------------+
| Search: [g_______________________]          |
|-------------------------------------------- |
| > git commit -m "Fix null pointer bug"     |
|   git push origin main                     |
|   git checkout -b feature/auth             |
|   longer text here                         | <-- "g" appears somewhere
+--------------------------------------------+

Keystroke 'i':
+--------------------------------------------+
| Search: [gi______________________]          |
|--------------------------------------------|
| > git commit -m "Fix null pointer bug"     |
|   git push origin main                     |
|   git checkout -b feature/auth             |
+--------------------------------------------+

Keystroke 't':
+--------------------------------------------+
| Search: [git_____________________]          |
|--------------------------------------------|
| > git commit -m "Fix null pointer bug"     |
|   git push origin main                     |
|   git checkout -b feature/auth             |
+--------------------------------------------+

The filtering:
- Is case-insensitive ("Git" matches "git")
- Searches full text (not just beginning)
- Updates instantly on every keystroke
- Auto-selects first match

4.5 Selection and Pasting

Keyboard navigation is intuitive:

NAVIGATION:
+---------+------------------------------------------+
| Key     | Action                                   |
+---------+------------------------------------------+
| Down    | Move selection to next item              |
| Up      | Move selection to previous item          |
| Enter   | Paste selected item, close popup         |
| Escape  | Close popup without pasting              |
| Tab     | Move focus between search and list       |
+---------+------------------------------------------+

PASTE SEQUENCE:
1. User presses Enter on "git commit -m 'Fix bug'"
2. Popup instantly disappears
3. Focus returns to VS Code
4. "git commit -m 'Fix bug'" appears at cursor position
5. Total time: <100ms

WHAT HAPPENS BEHIND THE SCENES:
   [Enter pressed]
         |
         v
   [Get selected item: "git commit..."]
         |
         v
   [Set A_Clipboard = "git commit..."]
         |
         v
   [Hide GUI]
         |
         v
   [Sleep 50ms]  <-- Brief pause for focus to transfer
         |
         v
   [Send ^v]     <-- Simulates Ctrl+V keypress
         |
         v
   [Active window receives paste]

4.6 Persistence Across Reboots

Your history survives everything:

SCENARIO: Computer Restart

Day 1, 5:00 PM:
  - User copies important SQL query
  - User copies API key
  - User copies code snippet
  - User shuts down computer

  [Script receives WM_CLOSE]
  [SaveHistory() called]
  [History written to: %APPDATA%\ClipboardManager\history.dat]

Day 2, 9:00 AM:
  - Computer boots
  - Script starts with Windows (if configured)
  - LoadHistory() reads saved file
  - All items from yesterday are available!

FILE LOCATION:
  C:\Users\{username}\AppData\Roaming\ClipboardManager\
      |
      +-- history.dat       (the actual history)
      +-- settings.ini      (optional: hotkey, max items)
      +-- backup_history.dat (optional: safety backup)

4.7 Example Terminal/GUI Output

Here’s what you see at various stages:

Script Startup (Console/ToolTip):

[10:30:15] Clipboard Manager Started
[10:30:15] Loaded 18 items from history
[10:30:15] Hotkey registered: Win+V
[10:30:15] Monitoring clipboard...

On Copy (Brief ToolTip):

+------------------------------+
| Copied: "const x = 42;"     |
+------------------------------+
     (disappears after 1 second)

Debug Mode Output (if enabled):

[10:30:45] ClipboardChanged: Type=1
[10:30:45] Text length: 142 chars
[10:30:45] Added to history at position 1
[10:30:45] History size: 19 items
[10:30:45] Scheduling save in 30 seconds...

5. Solution Architecture

5.1 Component Diagram

+===========================================================================+
||                        CLIPBOARD MANAGER SYSTEM                         ||
+===========================================================================+
|                                                                           |
|  +------------------------------------------------------------------+    |
|  |                      PRESENTATION LAYER                          |    |
|  |                                                                  |    |
|  |  +-------------------+  +-------------------+  +--------------+  |    |
|  |  |   Main GUI        |  |   Tray Icon       |  |   ToolTips   |  |    |
|  |  |                   |  |                   |  |              |  |    |
|  |  | - Search Edit     |  | - Icon display    |  | - Copy toast |  |    |
|  |  | - History ListBox |  | - Context menu    |  | - Errors     |  |    |
|  |  | - Status bar      |  | - Double-click    |  |              |  |    |
|  |  +--------+----------+  +--------+----------+  +------+-------+  |    |
|  |           |                      |                    |          |    |
|  +-----------|----------------------|--------------------|-----------+    |
|              |                      |                    |                |
|              v                      v                    v                |
|  +------------------------------------------------------------------+    |
|  |                       CONTROLLER LAYER                           |    |
|  |                                                                  |    |
|  |  +-------------------+  +-------------------+  +--------------+  |    |
|  |  |   GUI Controller  |  |   Hotkey Handler  |  | Clipboard    |  |    |
|  |  |                   |  |                   |  | Monitor      |  |    |
|  |  | - ShowPopup()     |  | - RegisterKeys()  |  |              |  |    |
|  |  | - HidePopup()     |  | - HandleWinV()    |  | - OnChange() |  |    |
|  |  | - FilterList()    |  | - HandleEscape()  |  | - AddItem()  |  |    |
|  |  | - PasteItem()     |  |                   |  |              |  |    |
|  |  +--------+----------+  +--------+----------+  +------+-------+  |    |
|  |           |                      |                    |          |    |
|  +-----------|----------------------|--------------------|-----------+    |
|              |                      |                    |                |
|              +----------------------+--------------------+                |
|                                     |                                     |
|                                     v                                     |
|  +------------------------------------------------------------------+    |
|  |                         MODEL LAYER                              |    |
|  |                                                                  |    |
|  |  +---------------------------+  +---------------------------+    |    |
|  |  |   HistoryManager          |  |   SettingsManager         |    |    |
|  |  |                           |  |                           |    |    |
|  |  | - ClipHistory[] (Array)   |  | - Hotkey                  |    |    |
|  |  | - MaxItems = 20           |  | - MaxItems                |    |    |
|  |  |                           |  | - SaveInterval            |    |    |
|  |  | + Add(text)               |  |                           |    |    |
|  |  | + Get(index)              |  | + Load()                  |    |    |
|  |  | + Filter(query)           |  | + Save()                  |    |    |
|  |  | + Remove(index)           |  | + Get(key)                |    |    |
|  |  | + Clear()                 |  | + Set(key, value)         |    |    |
|  |  +-------------+-------------+  +-------------+-------------+    |    |
|  |                |                              |                  |    |
|  +----------------|------------------------------|-------------------+    |
|                   |                              |                        |
|                   v                              v                        |
|  +------------------------------------------------------------------+    |
|  |                      PERSISTENCE LAYER                           |    |
|  |                                                                  |    |
|  |  +---------------------------+  +---------------------------+    |    |
|  |  |   HistoryFile             |  |   SettingsFile            |    |    |
|  |  |   (history.dat)           |  |   (settings.ini)          |    |    |
|  |  |                           |  |                           |    |    |
|  |  | + Read()                  |  | + IniRead()               |    |    |
|  |  | + Write()                 |  | + IniWrite()              |    |    |
|  |  +---------------------------+  +---------------------------+    |    |
|  |                                                                  |    |
|  +------------------------------------------------------------------+    |
|                                                                           |
+===========================================================================+

5.2 Data Flow Diagrams

Clipboard Capture Flow

                                    CLIPBOARD CAPTURE
                                    =================

+--------+     Ctrl+C      +----------+    WM_CLIPBOARD    +---------+
| User   | --------------> | Windows  | -----------------> | AHK     |
| Copies |                 | OS       |    UPDATE          | Runtime |
+--------+                 +----------+                    +----+----+
                                                               |
                                                               v
                                                      ClipboardChanged(Type)
                                                               |
                                             +-----------------+
                                             |
                                             v
                                      +--------------+
                                      | Type == 1?  |
                                      | (text)      |
                                      +------+------+
                                        Yes  |   | No
                                             |   +----> Return (ignore non-text)
                                             v
                                      +--------------+
                                      | Get          |
                                      | A_Clipboard  |
                                      +------+-------+
                                             |
                                             v
                                      +--------------+
                                      | Is Empty?   |
                                      +------+------+
                                        Yes  |   | No
                                             |   v
                                      Return |   +--------------+
                                             |   | Is Duplicate |
                                             |   | of Last?     |
                                             |   +------+-------+
                                             |     Yes  |   | No
                                             |          |   v
                                             +----------+   +--------------+
                                                            | InsertAt(1,  |
                                                            | text)        |
                                                            +------+-------+
                                                                   |
                                                                   v
                                                            +--------------+
                                                            | Length > 20? |
                                                            +------+-------+
                                                              Yes  |   | No
                                                                   v   |
                                                            +------+---+---+
                                                            | Pop()        |
                                                            | (remove old) |
                                                            +------+-------+
                                                                   |
                                                                   v
                                                            +--------------+
                                                            | Schedule     |
                                                            | SaveToFile() |
                                                            +--------------+

GUI Interaction Flow

                              GUI INTERACTION FLOW
                              ====================

+--------+    Win+V     +-------------+
| User   | -----------> | Hotkey      |
+--------+              | Handler     |
                        +------+------+
                               |
                               v
                        +-------------+
                        | ShowPopup() |
                        +------+------+
                               |
                  +------------+------------+
                  |                         |
                  v                         v
           +-------------+          +-------------+
           | Create/Show |          | Populate    |
           | GUI Window  |          | ListBox     |
           +------+------+          +------+------+
                  |                        |
                  +------------------------+
                               |
                               v
                        +-------------+
                        | Focus       |
                        | Search Edit |
                        +------+------+
                               |
                               v
                    +===================+
                    ||   EVENT LOOP    ||
                    ||    (waiting)    ||
                    +===================+
                               |
               +---------------+---------------+---------------+
               |               |               |               |
               v               v               v               v
        +-----------+   +-----------+   +-----------+   +-----------+
        | Text      |   | Arrow     |   | Enter     |   | Escape    |
        | Changed   |   | Keys      |   | Pressed   |   | Pressed   |
        +-----------+   +-----------+   +-----------+   +-----------+
               |               |               |               |
               v               v               v               v
        +-----------+   +-----------+   +-----------+   +-----------+
        | Filter()  |   | Move      |   | Paste()   |   | Hide()    |
        | Update    |   | Selection |   | Hide()    |   | Return    |
        | List      |   |           |   |           |   |           |
        +-----------+   +-----------+   +-----------+   +-----------+
               |               |               |               |
               +-------+-------+               |               |
                       |                       |               |
                       v                       v               |
                (loop back to         +-----------+            |
                 event loop)          | Set A_    |            |
                                      | Clipboard |            |
                                      +-----+-----+            |
                                            |                  |
                                            v                  |
                                      +-----------+            |
                                      | Sleep(50) |            |
                                      +-----+-----+            |
                                            |                  |
                                            v                  |
                                      +-----------+            |
                                      | Send ^v   |            |
                                      +-----+-----+            |
                                            |                  |
                                            +------------------+
                                                      |
                                                      v
                                               [End of flow]

5.3 State Management Approach

GLOBAL STATE VARIABLES
======================

+--------------------------------------------------------------------+
|  ClipHistory        : Array of strings (or Maps for metadata)      |
|                       ["newest item", "second newest", ...]        |
|                                                                    |
|  MaxHistory         : Integer = 20 (configurable)                  |
|                                                                    |
|  SaveTimer          : Timer handle (for periodic saves)            |
|                                                                    |
|  IgnoreNextClip     : Boolean = false                              |
|                       (prevents recursive clipboard triggers)      |
|                                                                    |
|  Settings           : Map                                          |
|                       { "hotkey": "#v", "maxItems": 20 }           |
+--------------------------------------------------------------------+


GUI-LOCAL STATE (exists only while popup is open)
=================================================

+--------------------------------------------------------------------+
|  MyGui              : GUI object reference                         |
|                                                                    |
|  SearchEdit         : Edit control reference                       |
|                                                                    |
|  HistoryList        : ListBox control reference                    |
|                                                                    |
|  FilteredItems      : Array of indices into ClipHistory            |
|                       (maps displayed position to actual index)    |
|                                                                    |
|  CurrentQuery       : String (what user has typed)                 |
|                                                                    |
|  SelectedIndex      : Integer (which item is highlighted)          |
|                                                                    |
|  PreviousWindow     : HWND of window active before popup           |
|                       (for returning focus)                        |
+--------------------------------------------------------------------+


STATE TRANSITIONS
=================

IDLE STATE:
- Waiting for events
- ClipHistory may be modified by clipboard changes
- GUI is hidden

    +---> [Clipboard Change] ---> Add to history, return to IDLE
    |
    +---> [Win+V pressed] ------> Transition to POPUP state


POPUP STATE:
- GUI is visible and has focus
- ClipHistory read-only (display only)
- FilteredItems updated on each keystroke

    +---> [Keystroke] ----------> Filter, update display, stay in POPUP
    |
    +---> [Enter] --------------> Paste item, transition to IDLE
    |
    +---> [Escape] -------------> Hide popup, transition to IDLE
    |
    +---> [Focus lost] ---------> Hide popup, transition to IDLE
    |
    +---> [Clipboard Change] ---> Add to history, update display, stay in POPUP

5.4 File Structure Recommendation

ClipboardManager/
|
+-- ClipboardManager.ahk          # Main script (entry point)
|
+-- lib/
|   +-- HistoryManager.ahk        # History array management
|   +-- PersistenceManager.ahk    # File save/load operations
|   +-- GuiManager.ahk            # GUI creation and event handling
|   +-- HotkeyManager.ahk         # Hotkey registration
|   +-- Utils.ahk                 # Helper functions (truncate, timestamp)
|
+-- assets/
|   +-- tray_icon.ico             # Custom tray icon (optional)
|
+-- docs/
|   +-- README.md                 # Installation and usage
|   +-- CHANGELOG.md              # Version history
|
+-- tests/
|   +-- test_history.ahk          # Manual test scripts
|   +-- test_persistence.ahk
|
+-- config/
|   +-- settings_default.ini      # Default settings (shipped with app)


ALTERNATIVE: Single-File Structure (for beginners)
==================================================

ClipboardManager.ahk              # Everything in one file
                                  # Sections separated by comments:
                                  #   ;=============== CONFIGURATION
                                  #   ;=============== HISTORY MANAGER
                                  #   ;=============== GUI
                                  #   ;=============== PERSISTENCE
                                  #   ;=============== MAIN


APPDATA STRUCTURE (created at runtime)
======================================

C:\Users\{username}\AppData\Roaming\ClipboardManager\
|
+-- history.dat                   # Saved clipboard history
+-- settings.ini                  # User settings (overrides defaults)
+-- debug.log                     # Debug output (if enabled)

6. Phased Implementation Guide

Phase 1: Basic Clipboard Monitoring (1-2 hours)

Goal: Prove you can detect clipboard changes and store history in memory.

What You’re Learning:

  • Event registration with callbacks
  • Global state management in AHK
  • Basic AHK v2 syntax

Steps:

  1. Create a new script file ClipboardManager.ahk

  2. Add the script header and global state: ```autohotkey #Requires AutoHotkey v2.0 #SingleInstance Force

; Global state ClipHistory := [] MaxHistory := 20


3. **Register the clipboard callback**:
```autohotkey
OnClipboardChange(ClipboardChanged)

ClipboardChanged(Type) {
    global ClipHistory, MaxHistory

    ; Type 1 = text, Type 2 = non-text
    if (Type != 1)
        return

    text := A_Clipboard

    ; Skip empty
    if (text = "")
        return

    ; Skip duplicates
    if (ClipHistory.Length > 0 && ClipHistory[1] = text)
        return

    ; Add to front
    ClipHistory.InsertAt(1, text)

    ; Trim to max
    while (ClipHistory.Length > MaxHistory)
        ClipHistory.Pop()

    ; Debug feedback
    ToolTip("Saved: " . SubStr(text, 1, 30) . "...")
    SetTimer(() => ToolTip(), -1500)
}

; Keep script running
Persistent
  1. Run the script and test:
    • Copy text from different applications
    • Each copy should show a tooltip
    • Script should not exit (stays resident)

Verification Checklist:

  • Script starts without errors
  • Copying text shows tooltip
  • Copying image does NOT show tooltip
  • Script stays running

Debugging Tips:

  • If nothing happens: Check that OnClipboardChange is registered
  • If script exits: Make sure Persistent is at the end
  • If Type is wrong: Add ToolTip("Type: " . Type) to see all events

Phase 2: Simple GUI Popup (2-3 hours)

Goal: Show the history in a window when hotkey is pressed.

What You’re Learning:

  • GUI creation in AHK v2
  • Control types (Edit, ListBox)
  • Window events and focus

Steps:

  1. Add a hotkey handler:
    #v::ShowClipboardGUI()  ; Win+V
    
  2. Create the basic GUI:
    ShowClipboardGUI(*) {
     global ClipHistory
    
     ; Create window
     MyGui := Gui("+AlwaysOnTop -MinimizeBox", "Clipboard History")
     MyGui.OnEvent("Escape", (*) => MyGui.Destroy())
     MyGui.OnEvent("Close", (*) => MyGui.Destroy())
    
     ; Add listbox
     lb := MyGui.Add("ListBox", "w300 h250 vHistoryList")
    
     ; Populate with history
     for index, item in ClipHistory {
         ; Clean up display text
         displayText := StrReplace(item, "`n", " ")  ; Replace newlines
         displayText := SubStr(displayText, 1, 50)    ; Truncate
         lb.Add([displayText])
     }
    
     ; Select first item
     if (lb.Length > 0)
         lb.Choose(1)
    
     ; Show centered on screen
     MyGui.Show("AutoSize Center")
    }
    
  3. Run and test:
    • Copy a few items
    • Press Win+V
    • See the history in a list
    • Press Escape to close

Verification Checklist:

  • Win+V opens the popup
  • History items appear in the list
  • Most recent item is at the top
  • Escape closes the popup
  • Clicking X closes the popup

Debugging Tips:

  • If hotkey doesn’t work: Another app might be using Win+V (try different key)
  • If GUI is blank: Check that ClipHistory has items (add debug ToolTip)
  • If GUI appears behind other windows: Verify +AlwaysOnTop is present

Phase 3: Search Filtering (2-3 hours)

Goal: Add a search box that filters the list in real-time.

What You’re Learning:

  • Control events (OnEvent for Edit changes)
  • Real-time UI updates
  • String searching

Steps:

  1. Add search box to GUI (modify ShowClipboardGUI):
    ShowClipboardGUI(*) {
     global ClipHistory
    
     MyGui := Gui("+AlwaysOnTop -MinimizeBox", "Clipboard History")
     MyGui.OnEvent("Escape", (*) => MyGui.Destroy())
    
     ; Search box FIRST (so it gets focus)
     searchEdit := MyGui.Add("Edit", "w300 vSearchBox")
    
     ; History list
     lb := MyGui.Add("ListBox", "w300 h250 vHistoryList")
    
     ; Store control references on the GUI object
     MyGui.searchEdit := searchEdit
     MyGui.historyList := lb
     MyGui.filteredIndices := []  ; Maps listbox position to ClipHistory index
    
     ; Register change event
     searchEdit.OnEvent("Change", FilterHistory)
    
     ; Initial population
     PopulateList(MyGui, "")
    
     MyGui.Show("AutoSize Center")
    }
    
  2. Add filtering function: ```autohotkey FilterHistory(ctrl, *) { query := ctrl.Value guiObj := ctrl.Gui PopulateList(guiObj, query) }

PopulateList(guiObj, query) { global ClipHistory

lb := guiObj.historyList
lb.Delete()  ; Clear current items
guiObj.filteredIndices := []

for index, item in ClipHistory {
    ; Case-insensitive search
    if (query = "" || InStr(item, query, false)) {
        displayText := StrReplace(item, "`n", " ")
        displayText := SubStr(displayText, 1, 50)
        lb.Add([displayText])
        guiObj.filteredIndices.Push(index)
    }
}

; Select first match
if (lb.Length > 0)
    lb.Choose(1) } ```
  1. Run and test:
    • Copy several items with different text
    • Press Win+V
    • Type in search box
    • Watch list filter as you type

Verification Checklist:

  • Search box appears above list
  • Cursor is in search box when popup opens
  • Typing filters the list immediately
  • Empty search shows all items
  • Case-insensitive search works

Debugging Tips:

  • If search doesn’t filter: Verify OnEvent is registered
  • If list doesn’t update: Check that lb.Delete() is called first
  • If selection is wrong: Verify lb.Choose(1) is called after populating

Phase 4: Paste on Selection (1-2 hours)

Goal: When user presses Enter, paste the selected item and close the popup.

What You’re Learning:

  • Mapping UI selection to data
  • Clipboard manipulation
  • Simulating keyboard input

Steps:

  1. Add Enter key handler:
    ShowClipboardGUI(*) {
     ; ... existing code ...
    
     ; Handle Enter key
     MyGui.OnEvent("Submit", PasteSelected)
    
     ; Also handle Enter in listbox directly
     lb.OnEvent("DoubleClick", (ctrl, *) => PasteFromListbox(ctrl.Gui))
    
     ; ... rest of function ...
    }
    
  2. Add paste function: ```autohotkey PasteSelected(guiObj) { PasteFromListbox(guiObj) }

PasteFromListbox(guiObj) { global ClipHistory

lb := guiObj.historyList
selectedPos := lb.Value  ; 1-based position in listbox

if (selectedPos = 0)
    return

; Map listbox position to actual history index
actualIndex := guiObj.filteredIndices[selectedPos]
selectedText := ClipHistory[actualIndex]

; Set clipboard
A_Clipboard := selectedText

; Close GUI
guiObj.Destroy()

; Brief pause for focus to transfer
Sleep(50)

; Paste
Send("^v") } ```
  1. Handle arrow key navigation (optional but nice):
    ; In ShowClipboardGUI, add:
    lb.OnEvent("Change", (*) => {})  ; Acknowledge selection changes
    
  2. Run and test:
    • Copy some items
    • Press Win+V
    • Use arrow keys to navigate
    • Press Enter
    • Text should paste into previous application

Verification Checklist:

  • Arrow keys change selection
  • Enter pastes the selected item
  • Double-click also pastes
  • Text appears in the application that was active before
  • Popup closes after pasting

Debugging Tips:

  • If paste goes to wrong window: Try increasing Sleep duration
  • If wrong text pastes: Check filteredIndices mapping
  • If Ctrl+V doesn’t work in target app: Try SendInput instead of Send

Phase 5: Persistence (1-2 hours)

Goal: Save history to disk and load on startup.

What You’re Learning:

  • File I/O in AHK
  • Serialization strategies
  • Application lifecycle (startup/shutdown)

Steps:

  1. Define file paths:
    ; Near the top of the script
    DataDir := A_AppData . "\ClipboardManager"
    HistoryFile := DataDir . "\history.dat"
    
  2. Add save function:
    SaveHistory() {
     global ClipHistory, DataDir, HistoryFile
    
     ; Ensure directory exists
     if !DirExist(DataDir)
         DirCreate(DataDir)
    
     ; Build content with delimiter
     content := ""
     for index, item in ClipHistory {
         content .= item . "`n<<<CLIP_SEPARATOR>>>`n"
     }
    
     ; Write to file (delete first to avoid appending)
     try {
         if FileExist(HistoryFile)
             FileDelete(HistoryFile)
         FileAppend(content, HistoryFile, "UTF-8")
     } catch Error as e {
         ToolTip("Save failed: " . e.Message)
         SetTimer(() => ToolTip(), -3000)
     }
    }
    
  3. Add load function:
    LoadHistory() {
     global ClipHistory, HistoryFile
    
     if !FileExist(HistoryFile)
         return
    
     try {
         content := FileRead(HistoryFile, "UTF-8")
         items := StrSplit(content, "`n<<<CLIP_SEPARATOR>>>`n")
    
         ClipHistory := []
         for index, item in items {
             item := Trim(item)
             if (item != "")
                 ClipHistory.Push(item)
         }
     } catch Error as e {
         ToolTip("Load failed: " . e.Message)
         SetTimer(() => ToolTip(), -3000)
     }
    }
    
  4. Hook into lifecycle: ```autohotkey ; At script startup LoadHistory()

; On clipboard change (after adding to history) ; Schedule a save (debounced) SaveTimer := 0

ClipboardChanged(Type) { global SaveTimer ; … existing code to add item …

; Schedule save in 30 seconds (resets if another copy happens)
if (SaveTimer)
    SetTimer(SaveTimer, 0)  ; Cancel previous
SaveTimer := SetTimer(SaveHistory, -30000)  ; -30000 = run once after 30 seconds }

; On exit OnExit(ExitHandler)

ExitHandler(ExitReason, ExitCode) { SaveHistory() return 0 ; Allow exit }


5. **Run and test**:
   - Copy some items
   - Wait 30 seconds OR close the script
   - Restart the script
   - Press Win+V - items should be there

**Verification Checklist**:
- [ ] history.dat file is created in AppData
- [ ] After restart, history is preserved
- [ ] Multi-line items survive save/load
- [ ] Special characters are handled

**Debugging Tips**:
- If file not created: Check that DataDir exists
- If content is garbled: Verify UTF-8 encoding
- If items are merged: Check separator is unique enough

---

### Phase 6: Polish (2-3 hours)

**Goal**: Make it production-ready with final touches.

**Improvements to Add**:

1. **System Tray Icon**:
```autohotkey
; Near top of script
TraySetIcon("shell32.dll", 261)  ; Clipboard icon
A_TrayMenu.Delete()  ; Clear default menu
A_TrayMenu.Add("Show History", (*) => ShowClipboardGUI())
A_TrayMenu.Add()  ; Separator
A_TrayMenu.Add("Exit", (*) => ExitApp())
A_TrayMenu.Default := "Show History"  ; Double-click action
  1. Prevent Capturing Own Pastes: ```autohotkey global IgnoreNextClip := false

ClipboardChanged(Type) { global IgnoreNextClip

if (IgnoreNextClip) {
    IgnoreNextClip := false
    return
}

; ... rest of function ... }

PasteFromListbox(guiObj) { global IgnoreNextClip ; … get text …

IgnoreNextClip := true  ; Set before changing clipboard
A_Clipboard := selectedText

; ... rest of function ... } ```
  1. Add Timestamps: ```autohotkey ; Change ClipHistory from array of strings to array of maps ClipHistoryItem := Map( “text”, “the copied content”, “time”, A_Now ; YYYYMMDDHH24MISS format )

; Update ClipboardChanged: ClipHistory.InsertAt(1, Map(“text”, text, “time”, A_Now))

; Update display to show relative time: FormatTimeAgo(timestamp) { diff := DateDiff(A_Now, timestamp, “Minutes”) if (diff < 1) return “just now” else if (diff < 60) return diff . “m ago” else if (diff < 1440) return Floor(diff / 60) . “h ago” else return Floor(diff / 1440) . “d ago” }


4. **Handle Edge Cases**:
```autohotkey
; Very long text - truncate for storage too (optional)
MaxTextLength := 10000
if (StrLen(text) > MaxTextLength)
    text := SubStr(text, 1, MaxTextLength) . "... [truncated]"

; Empty history
if (ClipHistory.Length = 0) {
    lb.Add(["(No clipboard history)"])
    lb.Opt("+Disabled")
}
  1. Better Focus Management: ```autohotkey ShowClipboardGUI(*) { global PreviousWindow

    ; Remember which window was active PreviousWindow := WinExist(“A”)

    ; … create GUI … }

; When closing without pasting: MyGui.OnEvent(“Close”, (*) => { guiObj.Destroy() WinActivate(PreviousWindow) })


**Final Verification Checklist**:
- [ ] Tray icon appears with working menu
- [ ] Own pastes are not captured
- [ ] Timestamps display correctly
- [ ] Very long text is handled
- [ ] Empty history shows message
- [ ] Focus returns to previous window

---

## 7. Testing Strategy

### 7.1 Manual Test Scenarios

#### Core Functionality Tests

| Test ID | Test Case | Steps | Expected Result | Pass/Fail |
|---------|-----------|-------|-----------------|-----------|
| T01 | Basic capture | 1. Copy "Hello" in Notepad 2. Check history | "Hello" appears in history | |
| T02 | Multiple captures | 1. Copy 5 different texts 2. Open popup | All 5 appear, newest first | |
| T03 | History limit | 1. Copy 25 items 2. Check history | Only 20 items remain | |
| T04 | Duplicate prevention | 1. Copy "Test" twice in a row | Only one "Test" entry | |
| T05 | Non-text clipboard | 1. Copy an image 2. Check history | No new entry added | |
| T06 | Popup display | 1. Copy items 2. Press Win+V | Popup appears with items | |
| T07 | Search filter | 1. Copy items with "foo" 2. Search "foo" | Only matching items shown | |
| T08 | Case-insensitive | 1. Copy "FooBar" 2. Search "foobar" | Item is found | |
| T09 | Paste via Enter | 1. Open popup 2. Select item 3. Enter | Item pastes to active app | |
| T10 | Cancel via Escape | 1. Open popup 2. Press Escape | Popup closes, no paste | |

#### Persistence Tests

| Test ID | Test Case | Steps | Expected Result | Pass/Fail |
|---------|-----------|-------|-----------------|-----------|
| T11 | Save on exit | 1. Copy items 2. Exit script 3. Check file | history.dat contains items | |
| T12 | Load on start | 1. Restart script 2. Open popup | Previous items restored | |
| T13 | Periodic save | 1. Copy item 2. Wait 30s 3. Check file | File updated | |
| T14 | Survive crash | 1. Copy items 2. Kill process 3. Restart | Recent items preserved | |

#### UI/UX Tests

| Test ID | Test Case | Steps | Expected Result | Pass/Fail |
|---------|-----------|-------|-----------------|-----------|
| T15 | Focus on open | 1. Press Win+V | Cursor in search box | |
| T16 | Arrow navigation | 1. Open popup 2. Press Down/Up | Selection moves | |
| T17 | Always on top | 1. Open popup 2. Click another window | Popup stays visible | |
| T18 | Click outside | 1. Open popup 2. Click desktop | Popup closes (if configured) | |
| T19 | Tray icon | 1. Look at system tray | Icon visible | |
| T20 | Tray menu | 1. Right-click tray icon | Menu appears | |

### 7.2 Edge Cases

| Test ID | Edge Case | Steps | Expected Result | Pass/Fail |
|---------|-----------|-------|-----------------|-----------|
| E01 | Very long text | Copy 10,000 character string | Stored, displayed truncated, pastes full | |
| E02 | Multi-line text | Copy text with newlines | Handled correctly | |
| E03 | Special characters | Copy: `"quotes" \backslash \t tab` | No corruption | |
| E04 | Unicode | Copy: emoji, Chinese, Arabic | Preserved correctly | |
| E05 | Empty clipboard | Clear clipboard, press Win+V | No crash, shows empty message | |
| E06 | Rapid copying | Copy 10 items in 2 seconds | All captured | |
| E07 | Copy while popup open | 1. Open popup 2. Alt-tab, copy 3. Return | New item appears in list | |
| E08 | Paste to elevated app | Paste to admin CMD | Either works or fails gracefully | |
| E09 | Corrupted file | Manually corrupt history.dat | Script handles gracefully | |
| E10 | Read-only file | Make history.dat read-only | Save fails with message, script continues | |

### 7.3 Regression Checklist

Run these tests after any code change:

- [ ] Copy basic text - captured
- [ ] Win+V - popup appears
- [ ] Search - filters correctly
- [ ] Enter - pastes correctly
- [ ] Escape - closes cleanly
- [ ] Restart - history preserved
- [ ] No error dialogs or crashes

---

## 8. Common Pitfalls and Debugging Tips

### Pitfall 1: GUI Doesn't Appear

**Symptoms**: Win+V does nothing, or shows briefly then disappears

**Common Causes**:
1. Hotkey conflict with another application
2. Script crashed and isn't running
3. GUI creation error

**Debugging Steps**:
```autohotkey
; Add at start of hotkey handler:
#v::{
    MsgBox("Hotkey triggered!")  ; Does this appear?
    ShowClipboardGUI()
}

; If MsgBox doesn't appear:
;   - Check Task Manager for your script
;   - Try different hotkey (^!v for Ctrl+Alt+V)
;   - Check if another app uses Win+V (Windows itself uses this!)

; If MsgBox appears but no GUI:
;   - Add MsgBox after each GUI line to find where it fails
;   - Check for typos in control names

Note: Windows 10/11 have a built-in Win+V clipboard history. You may need to disable it or use a different hotkey!

Pitfall 2: Clipboard Changes Not Detected

Symptoms: Copy in applications, but nothing gets saved

Common Causes:

  1. OnClipboardChange not registered
  2. Type parameter check is wrong
  3. Callback throws an error silently

Debugging Steps:

; Add verbose logging:
ClipboardChanged(Type) {
    ; Log EVERYTHING
    FileAppend(A_Now . " Type=" . Type . "`n", "debug.log")

    ; Also show tooltip for ALL types
    ToolTip("Clipboard event! Type=" . Type)
    SetTimer(() => ToolTip(), -2000)

    ; Wrap rest in try/catch
    try {
        ; ... your code ...
    } catch Error as e {
        MsgBox("Error: " . e.Message)
    }
}

Pitfall 3: Wrong Text Pastes

Symptoms: User selects item A, but item B gets pasted

Common Causes:

  1. Filtered indices not mapped correctly
  2. Selection index off by one
  3. Array mutation during operation

Debugging Steps:

PasteFromListbox(guiObj) {
    lb := guiObj.historyList
    selectedPos := lb.Value

    ; Debug output
    MsgBox("Listbox position: " . selectedPos . "`n"
         . "Filtered indices: " . guiObj.filteredIndices.Length . "`n"
         . "Mapped index: " . guiObj.filteredIndices[selectedPos])

    ; Verify the text
    actualText := ClipHistory[guiObj.filteredIndices[selectedPos]]
    MsgBox("Will paste: " . SubStr(actualText, 1, 100))

    ; ... rest of function
}

Pitfall 4: Text Doesn’t Paste Into Target App

Symptoms: Clipboard is set, but Ctrl+V does nothing or pastes old content

Common Causes:

  1. Target app isn’t focused when Send(“^v”) runs
  2. Target app doesn’t accept simulated input
  3. Timing issues - clipboard not ready

Debugging Steps:

PasteFromListbox(guiObj) {
    ; ... set clipboard ...

    guiObj.Destroy()

    ; Longer delay
    Sleep(100)  ; Try 100ms instead of 50ms

    ; Check what window will receive the paste
    ToolTip("Pasting to: " . WinGetTitle("A"))
    Sleep(500)
    ToolTip()

    ; Try different send modes
    SendInput("^v")  ; More reliable than Send

    ; Or try SendEvent for problematic apps
    ; SendEvent("^v")
}

Pitfall 5: History File Not Saving

Symptoms: File doesn’t exist or is empty after restart

Common Causes:

  1. Directory doesn’t exist
  2. Permission denied
  3. Path has invalid characters
  4. OnExit not triggering

Debugging Steps:

SaveHistory() {
    global DataDir, HistoryFile

    ; Log everything
    FileAppend(A_Now . " SaveHistory called`n", "debug.log")

    ; Check paths
    MsgBox("DataDir: " . DataDir . "`nFile: " . HistoryFile)

    ; Check directory
    if !DirExist(DataDir) {
        try {
            DirCreate(DataDir)
            FileAppend("Created dir`n", "debug.log")
        } catch Error as e {
            MsgBox("Can't create dir: " . e.Message)
            return
        }
    }

    ; Try writing
    try {
        FileAppend("test", HistoryFile)
        FileAppend("Write succeeded`n", "debug.log")
    } catch Error as e {
        MsgBox("Write failed: " . e.Message)
    }
}

Pitfall 6: Script Captures Its Own Pastes

Symptoms: Clipboard history fills with duplicates after pasting

Solution:

global IgnoreNextClip := false

ClipboardChanged(Type) {
    global IgnoreNextClip

    ; FIRST thing: check and reset flag
    if (IgnoreNextClip) {
        IgnoreNextClip := false
        return  ; Don't process this change
    }

    ; ... rest of function
}

PasteFromListbox(guiObj) {
    global IgnoreNextClip

    ; Set flag BEFORE changing clipboard
    IgnoreNextClip := true

    ; Now safe to change clipboard
    A_Clipboard := selectedText

    ; ... rest of function
}

9. Extensions and Challenges

Easy Extensions (1-2 hours each)

  1. Configurable Hotkey
    • Read hotkey from settings.ini
    • Implement a settings dialog
    • Allow user to change without editing code
  2. Adjustable History Size
    • Add setting for max items (10, 20, 50, 100)
    • Update UI to show current limit
    • Handle migration when size decreases
  3. Clear History Option
    • Add tray menu option “Clear History”
    • Confirm dialog before clearing
    • Also clear the file
  4. Search Highlighting
    • Highlight matched text in results
    • Use different color for matched portion
    • (Note: ListBox has limited styling - may need ListView)

Medium Extensions (3-5 hours each)

  1. Pin/Star Important Items
    • Add ability to “star” items
    • Starred items never expire
    • Show at top of list
    • Persist star status
  2. Categories or Tags
    • Auto-detect content type (URL, code, email)
    • Show icons for each type
    • Filter by category
  3. Rich Text Preview
    • For HTML clipboard content, show formatted preview
    • Use a separate preview panel
    • Option to paste as plain text or rich
  4. Keyboard Shortcuts in Popup
    • Ctrl+1 through Ctrl+9 to paste item by position
    • Ctrl+D to delete selected item
    • Ctrl+C to copy without closing

Advanced Extensions (8+ hours each)

  1. Cloud Sync
    • Sync clipboard across multiple computers
    • Use a service like Dropbox/Google Drive
    • Handle conflict resolution
    • Encrypt sensitive data
  2. OCR Integration
    • When image is copied, run OCR
    • Store extracted text alongside image
    • Search finds image by text content
  3. Snippet Expansion
    • Define shortcuts that expand to full text
    • Example: type “@@email” -> “your.email@example.com”
    • Store snippets separately from history
  4. Clipboard Templates
    • Define templates with placeholders
    • Example: “Hi {name}, thanks for {reason}”
    • Prompt for values when pasting

Challenge Projects

Challenge 1: Cross-Platform Version

  • Port the concept to macOS using Python + pynput
  • Or to Linux using Python + pyperclip
  • Compare the OS clipboard architectures

Challenge 2: Web Companion

  • Create a browser extension
  • Sync clipboard to a web service
  • Access history from any device
  • Handle security concerns

Challenge 3: Clipboard Analytics

  • Track what you copy over time
  • Generate reports (most copied domains, text patterns)
  • Identify sensitive data (passwords, keys)
  • Privacy-focused: all local, no cloud

10. Books That Will Help

Topic Book Chapter Why This Helps
Observer Pattern Game Programming Patterns by Robert Nystrom Chapter 4: “Observer” Your clipboard monitor is a textbook Observer implementation. This chapter explains why the pattern exists, when to use it, and common pitfalls. Nystrom’s clear diagrams make the concept click.
Event-Driven Programming Game Programming Patterns by Robert Nystrom Chapter 8: “Game Loop” Understanding the event loop is crucial for any interactive application. Though game-focused, the concepts apply directly to your clipboard manager’s architecture.
Windows Internals Windows Security Internals by James Forshaw Part 1: “Security Fundamentals”, specifically clipboard sections Understanding how Windows actually implements the clipboard - ownership, formats, notifications. Essential for debugging clipboard issues.
Windows Architecture Windows Internals by Russinovich, Solomon, Ionescu Part 1, Chapter 4: “Threads” Deep understanding of how Windows dispatches messages and manages application threads. Helps understand why your event handlers behave as they do.
Flexible Configuration The Pragmatic Programmer by Hunt & Thomas Chapter 17: “Configuration” and Topic 32: “Flexible Configuration” How to design configuration systems that survive changes. Relevant for your settings file architecture.
Clean Code Principles The Pragmatic Programmer by Hunt & Thomas Topic 11: “Orthogonality” How to design components that don’t interfere with each other. Helps structure your clipboard manager into clean, testable modules.
Data Structures Algorithms, Fourth Edition by Sedgewick & Wayne Chapter 1.3: “Bags, Queues, and Stacks” Understanding arrays, queues, and efficient data access. Your clipboard history is essentially a capped stack (LIFO with size limit).
File Formats Designing Data-Intensive Applications by Martin Kleppmann Chapter 4: “Encoding and Evolution” Understanding serialization formats, forward/backward compatibility. Helps design a persistence format that can evolve.
AutoHotkey Specifics AutoHotkey v2 Official Documentation GUI Object, OnClipboardChange, Hotkey sections The authoritative reference for AHK v2 syntax and behavior. Not a book, but essential reading.
UI/UX for Developers Don’t Make Me Think by Steve Krug Chapters 1-4 Principles of intuitive UI design. Helps you create a popup that users can understand instantly without instructions.

Reading Order Recommendation

  1. Start with: AutoHotkey v2 Documentation (essential for syntax)
  2. Then read: Game Programming Patterns - Observer chapter (understand the pattern)
  3. While building: The Pragmatic Programmer - Configuration topics (good practices)
  4. For debugging: Windows Security Internals - Clipboard sections (deep understanding)
  5. For polish: Don’t Make Me Think (better UX)

11. Self-Assessment Checklist

Use this checklist to verify your understanding and implementation quality.

Conceptual Understanding

  • I can explain what the Windows clipboard actually is (shared memory region, multi-format storage, ownership model)
  • I understand event-driven vs imperative programming (callbacks, event loop, reactive design)
  • I can describe the Observer pattern and how my clipboard monitor implements it
  • I understand why polling is bad and event subscription is good (efficiency, responsiveness)
  • I can explain HWND, focus, and Z-order in Windows GUI programming
  • I understand the tradeoffs between INI, JSON, and custom formats for persistence

Implementation Quality

  • Clipboard monitoring works reliably - never misses a copy, never double-captures
  • GUI appears within 100ms of hotkey press (feels instant to user)
  • Search filtering is real-time - no perceptible delay while typing
  • Paste works in all applications I’ve tested (including VS Code, browsers, Office)
  • History survives script restart - data integrity maintained
  • Edge cases are handled - empty clipboard, very long text, special characters

Code Quality

  • Code is organized - related functions grouped together, clear sections
  • Variable names are descriptive - ClipHistory not ch, FilteredIndices not fi
  • Comments explain WHY, not just WHAT - especially for non-obvious logic
  • Error handling is present - try/catch around file operations, graceful degradation
  • Magic numbers are named - MaxHistory := 20 not just 20 scattered in code
  • No global variable soup - state is organized and documented

Debugging Skills

  • I can diagnose hotkey conflicts using the techniques from Section 8
  • I can trace clipboard events using tooltips and log files
  • I know how to debug paste failures (timing, focus, send modes)
  • I can verify file operations by checking file contents manually
  • I understand single-instance behavior and why it matters

Extension Readiness

  • I have ideas for improvements beyond the basic requirements
  • I understand how to add timestamps to history items
  • I could implement a settings dialog if asked
  • I know what would be needed to add image clipboard support

Final Reflection Questions

Answer these without looking at the code:

  1. What Windows message tells applications the clipboard changed?
  2. Why do we use InsertAt(1, item) instead of Push(item)?
  3. What’s the purpose of filteredIndices in the GUI?
  4. Why do we set IgnoreNextClip := true before setting the clipboard?
  5. What happens if two copies occur within 100ms of each other?
  6. How does the script know to save history when it exits?

Resources

Official Documentation

Community Resources


Begin with Phase 1 and build your understanding layer by layer. Each phase makes the next one easier to comprehend. By the end, you’ll have a tool you use every day and a deep understanding of Windows automation.