2

I have a small application that the user can interact with as a command line. I want the user to be able to copy to the Windows clipboard information that the application has just displayed on-screen. Obviously, the user can do this manually, but it takes several steps: right-click on the window, select "Mark", select the rectangle of text, and press Enter to copy it. I want to allow the user to do this automatically by typing a short command like "cb" or "copy".

Per this answer, an easy way to get clipboard functionality is using the tkinter library. This does indeed work well. However, I find that when my application starts up, it loses the focus. It seems that a hidden window (opened by Tk() and then hidden by withdraw()) has it. The act of hiding the window with withdraw() did not give focus back to my application. This is inconvenient, because having opened the application, the user has to manually switch back to it rather than being able to just begin typing.

I want to create a tkinter object and either give the focus back to my application after I hide the new window, or have my application not lose focus in the first place. How can I do this?

There are various questions already relating to tkinter and focus, but they seem generally to relate to giving focus to the windows that tkinter itself opens, whereas I want to keep focus on the original window of my application, and deny it to the tkinter window.

I'm working at a Windows 8 machine.

Pastebin http://pastebin.com/6jsasiNE

Community
  • 1
  • 1
Hammerite
  • 21,755
  • 6
  • 70
  • 91
  • Not sure on Windows 8 but from XP to Windows 7, the command line has an option *Quick edit mode* (not sure, it is *Mode d'édition rapide* in french) where you copy simply by marking with mouse and right click, and paste with a single right click. – Serge Ballesta Jun 24 '14 at 13:21
  • That's good to know, but it's still more fiddly than entering a quick key sequence if your hands are over the keyboard anyway. Especially if the text you want to copy isn't a single word, because then you have to take care to highlight just the text you want. – Hammerite Jun 24 '14 at 13:32
  • I dont think win32api is overkill at all. http://stackoverflow.com/questions/101128/how-do-i-read-text-from-the-windows-clipboard-from-python –  Jun 27 '14 at 22:49
  • I've been looking at your problem for a while, and it looks like win32api is something you'll need if you want to make this a fairly easy fix. Still looking into it, but darn is it tough to find anything! – Micrified Jun 28 '14 at 02:55
  • Maybe not use tk at all? Here is a question about [accessing the clipboard in python](http://stackoverflow.com/questions/11063458/python-script-to-copy-text-to-clipboard). The suggested answers were all about [pyperclip](http://coffeeghost.net/2010/10/09/pyperclip-a-cross-platform-clipboard-module-for-python/) – dhj Jun 28 '14 at 22:38
  • I am open to not using tk, but given that tk is a part of the Python standard library, I would expect that a good answer would probably either say how to achieve this with the Python standard library, or explain why such a goal is unrealistic. – Hammerite Jun 28 '14 at 23:18

1 Answers1

3

On Windows NT, Windows Server 2003, and Windows 7+

You don't need to use Tkinter at all to achieve your goal.

clip.py:

import subprocess

def write_to_clipboard(string):
    p = subprocess.Popen(['clip'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    p.communicate(input=string)

This code just calls the standard windows clip.exe utility, pasting whatever passed in the string variable.

Usage:

from clip import write_to_clipboard
try:
    while True:
        write_to_clipboard(raw_input())        
except KeyboardInterrupt:
    pass

On Windows 95, 98, ME, and XP

Those versions of windows don't come with clip.exe, so here's a python only version:

clip.py:

import subprocess

def write_to_clipboard(string):
    p = subprocess.Popen(['python', __file__], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    p.communicate(input=string)

if __name__ == "__main__":
    import sys
    from Tkinter import Tk

    r = Tk()
    r.withdraw()
    r.clipboard_clear()
    r.clipboard_append(sys.stdin.read())
    r.update()
    r.destroy()

This will work on all version of windows and in face any OS supporting TK.

Note that you must run the Tk code in a separate process like this, because even though we call r.destroy(), Tk seems to "lock" the clipboard (no other process can access the clipboard until this process has exited).

Reading and Writing Clipboard

If you want to be able to read from the clipboard as well as write to it, this is the solution.

clip.py:

import subprocess

def write(string):
    p = subprocess.Popen(['python', __file__, 'write'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    p.communicate(input=string)

def read():
    p = subprocess.Popen(['python', __file__, 'read'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    return p.communicate()[0]

if __name__ == "__main__":
    import sys
    from Tkinter import Tk

    if len(sys.argv) != 2:
        sys.exit(1)

    r = Tk()
    try:
        r.withdraw()
        if sys.argv[1] == "write":
            r.clipboard_clear()
            r.clipboard_append(sys.stdin.read())
            r.update()
        elif sys.argv[1] == "read":
            sys.stdout.write(r.clipboard_get()),
        else:
            sys.exit(1)
    finally:
        r.destroy()

Usage:

import clip
print "clipboard contains: %s" % clip.read()
Jamie Cockburn
  • 7,379
  • 1
  • 24
  • 37
  • This answer technically allows me to do what I set out in the question, and so I am accepting it. I was dismayed to see that the suggested solution (clip.exe) only allows information to be placed into the clipboard but not retrieved from it (an additional piece of functionality for my application had occurred to me in the meantime that required the ability to read what is in the clipboard). (Comment continued) – Hammerite Jul 03 '14 at 11:16
  • (Continuation of comment) I expected that the alternate workaround involving the use of Tk in a separate process would give the same problem as I had had originally, but as it turns out, the command window gets the focus back when the subprocess's Tk window exits. The calling process loses focus momentarily, then regains it (after a fraction of a second). – Hammerite Jul 03 '14 at 11:16
  • I didn't add a proviso into the question that I'd expect to be able to read from the clipboard as well as write to it, simply because I made the assumption that any facility to write to the clipboard would naturally double as a facility to read from it. Obviously, the creators of clip did not see this as a natural part of their utility's functionality. – Hammerite Jul 03 '14 at 11:20
  • Yes, that's correct. The main difference is that I'm creating a new Tk instance each time, and destroying it immediately. You'd think you could do the same thing in a thread instead of a process but: 1. Tk must run on the main thread in python (meaning that your app code would have to move onto it's own thread) 2. you'd have to setup all the inter-thread communication (a `Queue`) yourself 3. It won't work anyway, believe me, I tried (see my note at the end of the answer) – Jamie Cockburn Jul 03 '14 at 11:21
  • Thanks, I did realise that the Tk alternative method could be made to support reading and writing, but it is good to have it in the answer. – Hammerite Jul 03 '14 at 11:48