E-Gamma-102
E-Gamma-102

Reputation: 11

Trying to make a ghost of whatever is in my clipboard appear where my text cursor is

I am making a program that can store a clipboard history that you can scroll through to choose what to paste. I want some ghost text to appear (where your text cursor is) of what you currently have in your clipboard. I have tried solving this problem for a week and have hit multiple dead ends.

This is a test program that is supposed to sort out the ghost display and show it on ctrl where your cursor is but it doesn't work: (Caret position (relative to window) is always (0, 0) not matter what I do)

class GhostDisplay:
def __init__(self):
    # Initialize ghost display window
    self.ghost_display = tk.Tk()
    self.ghost_display.overrideredirect(1)  # Remove window decorations
    self.ghost_display.attributes('-topmost', True)  # Always on top
    self.ghost_display.withdraw()  # Start hidden

    self.ghost_label = tk.Label(self.ghost_display, fg='white', bg='black', font=('Arial', 12))
    self.ghost_label.pack()

    # Thread for listening to hotkeys
    self.listener_thread = threading.Thread(target=self._hotkey_listener, daemon=True)

def _get_text_cursor_position(self):
    """Get the position of the text cursor (caret) in the active window."""
    try:
        hwnd = win32gui.GetForegroundWindow()  # Get the handle of the active window
        logging.info(f"Active window handle: {hwnd}")
        
        caret_pos = win32gui.GetCaretPos()  # Get the caret (text cursor) position
        logging.info(f"Caret position (relative to window): {caret_pos}")
        
        rect = win32gui.GetWindowRect(hwnd)  # Get the window's position on the screen
        logging.info(f"Window rectangle: {rect}")

        # Calculate position relative to the screen
        x, y = rect[0] + caret_pos[0], rect[1] + caret_pos[1]
        logging.info(f"Text cursor position: ({x}, {y})")
        return x, y
    except Exception as e:
        logging.error(f"Error getting text cursor position: {e}")
        return None

def show_ghost(self):
    """Display the ghost near the text cursor with clipboard content."""
    content = pyperclip.paste()
    pos = self._get_text_cursor_position()

    if pos:
        x, y = pos
        logging.info(f"Positioning ghost at: ({x}, {y})")
        self.ghost_label.config(text=content)
        self.ghost_display.geometry(f"+{x+5}+{y+20}")  # Position slightly offset from the cursor
        self.ghost_display.deiconify()  # Show the ghost window
    else:
        # Fall back to positioning near the mouse
        x, y = win32api.GetCursorPos()
        logging.info(f"Falling back to mouse cursor position: ({x}, {y})")
        self.ghost_label.config(text=f"(Fallback) {content}")
        self.ghost_display.geometry(f"+{x+5}+{y+20}")
        self.ghost_display.deiconify()

def hide_ghost(self):
    self.ghost_display.withdraw()
    logging.info("Ghost hidden.")

def _hotkey_listener(self):
    """Listen for hotkey to show/hide the ghost display."""
    def on_press(key):
        try:
            if key in {keyboard.Key.ctrl_l, keyboard.Key.ctrl_r}:
                logging.info("Ctrl pressed. Showing ghost.")
                self.show_ghost()
        except Exception as e:
            logging.error(f"Error in hotkey listener (on_press): {e}")

    def on_release(key):
        try:
            if key in {keyboard.Key.ctrl_l, keyboard.Key.ctrl_r}:
                logging.info("Ctrl released. Hiding ghost.")
                self.hide_ghost()

            # Kill switch is Esc
            if key == keyboard.Key.esc:
                logging.info("ESC pressed. Exiting program.")
                self.stop()
        except Exception as e:
            logging.error(f"Error in hotkey listener (on_release): {e}")

    with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
        listener.join()

def run(self):
    self.listener_thread.start()  # Start hotkey listener
    self.ghost_display.mainloop()

def stop(self):
    self.ghost_display.destroy()
    sys.exit(0)
if __name__ == "__main__":
    open("ghost_display_debug.log", "w").close()
    app = GhostDisplay()
    try:
        app.run()
    except KeyboardInterrupt:
        app.stop()

The second problem I have is due to not being able to override windows paste functionality. I'm trying to make the ghost appear on ctrl+v (you would scroll through your paste history here) and then paste when ctrl+v is pressed and ctrl or v is released. This is my test code for blocking windows paste functionality on hotkey (F9):

custom_paste_enabled = True

def simulate_paste():
    content = pyperclip.paste()
    print(f"Custom Pasted: {content}")

def toggle_custom_paste():
    global custom_paste_enabled
    custom_paste_enabled = not custom_paste_enabled
    print(f"Custom paste {'enabled' if custom_paste_enabled else 'disabled'}.")

def custom_paste_handler(e):
    if custom_paste_enabled:
        print("Ctrl+V intercepted. Suppressing OS behavior.")
        simulate_paste()  # Perform custom paste
        return False  # Suppress  this key event
    return True  # Allow the normal paste

# Set up the hotkeys
keyboard.add_hotkey('F9', toggle_custom_paste)  # F9 to toggle custom paste
keyboard.hook(custom_paste_handler)  # Hook into all key events

print("Listening for keyboard events. F9 to toggle custom paste. Ctrl+V to test.")

try:
    keyboard.wait('esc')  # Kill switch is Esc
except KeyboardInterrupt:
    print("\nExiting.")

Any help at all or suggestions with this would be greatly appreciated and extremely helpful. I haven't found much about this online and I'm at the end of my rope here.

Upvotes: 0

Views: 81

Answers (0)

Related Questions