20

I'm trying to get the clipboard content using a Python script on my Mac Lion.

I'm searching for an event or something similar, because if I use a loop, my application spends all its time watching the clipboard.

Any ideas?

Thorsten Kranz
  • 12,492
  • 2
  • 39
  • 56
Marvin Oßwald
  • 376
  • 1
  • 3
  • 20

4 Answers4

24

Have you thought about using an endless loop and "sleeping" between tries? I used pyperclip for a simple PoC and it worked like a charm, and Windows and Linux.

import time
import sys
import os

import pyperclip

recent_value = ""
while True:
    tmp_value = pyperclip.paste()
    if tmp_value != recent_value:
        recent_value = tmp_value
        print("Value changed: %s" % str(recent_value)[:20])
    time.sleep(0.1)

Instead of the print, do whatever you want.


Here is a complete multithreading example.

import time
import threading

import pyperclip

def is_url_but_not_bitly(url):
    if url.startswith("http://") and not "bit.ly" in url:
        return True
    return False

def print_to_stdout(clipboard_content):
    print ("Found url: %s" % str(clipboard_content))

class ClipboardWatcher(threading.Thread):
    def __init__(self, predicate, callback, pause=5.):
        super(ClipboardWatcher, self).__init__()
        self._predicate = predicate
        self._callback = callback
        self._pause = pause
        self._stopping = False
            
    def run(self):       
        recent_value = ""
        while not self._stopping:
            tmp_value = pyperclip.paste()
            if tmp_value != recent_value:
                recent_value = tmp_value
                if self._predicate(recent_value):
                    self._callback(recent_value)
            time.sleep(self._pause)
    
    def stop(self):
        self._stopping = True

def main():
    watcher = ClipboardWatcher(is_url_but_not_bitly, 
                               print_to_stdout,
                               5.)
    watcher.start()
    while True:
        try:
            print("Waiting for changed clipboard...")
            time.sleep(10)
        except KeyboardInterrupt:
            watcher.stop()
            break
    

if __name__ == "__main__":
    main()

I create a subclass of threading.Thread, override the methods run and __init__ and create an instance of this class. By calling watcher.start() (not run()!), you start the thread.

To safely stop the thread, I wait for <Ctrl>-C (keyboard interrupt) and tell the thread to stop itself.

In the initialization of the class, you also have a parameter pause to control how long to wait between tries.

Use the class ClipboardWatcher like in my example, replace the callback with what you do, e.g., lambda x: bitly(x, username, password).

SuperStormer
  • 4,997
  • 5
  • 25
  • 35
Thorsten Kranz
  • 12,492
  • 2
  • 39
  • 56
  • Thank you, yes pls tell me how to do this in a background thread my loop looks like that : `while True: pbstring = checkclipboard() if 'http://' in pbstring and not 'bit.ly' in pbstring: bitly(pbstring,username,key) else: print 'Waiting...' print pbstring time.sleep(5)` so at the moment my whole app waits for the loop and i couldn't do anything simultaneously.. – Marvin Oßwald Feb 04 '13 at 13:21
  • Your script seems like a nice idea. You watch for URLs in the Clipboard and, if you find one, replace it with a shortened version? – Thorsten Kranz Feb 04 '13 at 14:22
  • OK now it appears that the rest of my code only works when i interrupt my while loop, any idea why ? – Marvin Oßwald Feb 04 '13 at 15:10
  • The second while-loop (in the `main`)-method was only intended as a placeholder for your actual code. Do I understand you correctly that you didn't remove it? – Thorsten Kranz Feb 04 '13 at 15:14
2

Looking at pyperclip the meat of it on Macosx is :

import os
def macSetClipboard(text):
    outf = os.popen('pbcopy', 'w')
    outf.write(text)
    outf.close()

def macGetClipboard():
    outf = os.popen('pbpaste', 'r')
    content = outf.read()
    outf.close()
    return content

These work for me how do you get on?

I don't quite follow your comment on being in a loop.


EDIT Added 'orrid polling example that shows how changeCount() bumps up on each copy to the pasteboard. It's still not what the OP wants as there seems no event or notification for modifications to the NSPasteboard.

from LaunchServices import *
from AppKit import *
import os

from threading import Timer

def poll_clipboard():
    pasteboard = NSPasteboard.generalPasteboard()
    print pasteboard.changeCount()

def main():
    while True:
        t = Timer(1, poll_clipboard)
        t.start()
        t.join()

if __name__ == "__main__":
    main()
sotapme
  • 4,695
  • 2
  • 19
  • 20
  • I'm using the Appkit Framework: `NSPasteboard.generalPasteboard()`, I need the loop to get new clipboard contents instantly.. because i have no callback event from the clipboard. – Marvin Oßwald Feb 04 '13 at 14:03
1

simple!

import os
def macSetClipboard(text):
    outf = os.popen('pbcopy', 'w')
    outf.write(text)
    outf.close()

def macGetClipboard():
    outf = os.popen('pbpaste', 'r')
    content = outf.read()
    outf.close()
    return content
current_clipboard = macGetClipboard()
while True:
   clipboard = macGetClipboard()
   
   if clipboard != current_clipboard:
       print(clipboard)
       macSetClipboard("my new string")
       print(macGetClipboard())
       break
1

I originaly posted my answer on a duplicate Run a python code when copying text with specific keyword

Here the answer I came up with.

import clipboard
import asyncio


# Exemple function.
async def your_function():
    print("Running...")


async def wait4update(value):
    while True:
        if clipboard.paste() != value : # If the clipboard changed.
            return

async def main():
    value = clipboard.paste() # Set the default value.
    while True :
        update = asyncio.create_task(wait4update(value))
        await update
        value = clipboard.paste() # Change the value.    
        asyncio.create_task(your_function()) #Start your function.

asyncio.run(main())
Tirterra
  • 579
  • 2
  • 4
  • 14