Issue copying to clipboard using tkinter and pyautogui - python

I am working on an automation program to review/test content within a third party application. At the moment Im going with pyautogui to highlight and copy values(at least until we get access to query the applications database) and tkinter to retrieve data from the clipboard.
The script below has worked in copying content that can be highlighted on the screen (under the #get manager section in the script).
However, when I navigate to a section that has a text body (#QAR test 2), use pyautogui.hotkey("Ctrl","a") to highlight all and pyautogui.hotkey("Ctrl","c") to copy, it seems like the clipboard is not populated (due to the error message when trying to print out the variable it has been assigned to).
For reference, I am attaching a screen shot of the target text. Can text (specifically, paragraphs) not be copied over to the clipboard?
The error message raised after the #QAR Test 2 is:
Traceback (most recent call last):
File "C:/Users/haudrxr/Downloads/PCA_5_5_18_QAR.py", line 92, in <module>
background_tm= copy_clipboard()
File "C:/Users/haudrxr/Downloads/PCA_5_5_18_QAR.py", line 10, in copy_clipboard
clipboard = Tk().clipboard_get()
File "C:\Users\haudrxr\AppData\Local\Continuum\anaconda3\lib\tkinter\__init__.py", line 804, in clipboard_get
return self.tk.call(('clipboard', 'get') + self._options(kw))
_tkinter.TclError: CLIPBOARD selection doesn't exist or form "STRING" not defined
...
#Get Manager Value
x=115
y=450
for i in range (10):
pyautogui.click(x, y)
time.sleep(1)
pyautogui.doubleClick(839, 567)
pyautogui.hotkey("Ctrl","c")
level=copy_clipboard()
y += 23
if level=="1":
pyautogui.mouseDown(750, 437,button="left",duration=1)
pyautogui.dragTo(1049, 437,1, button='left')
pyautogui.hotkey("Ctrl", "c")
staffname = copy_clipboard()
if len(staffname)>1:
team_tab.append(staffname)
print(team_tab)
else:
continue
team_tab = list(filter(None, team_tab)) # fastest
print(len(team_tab))
if len(team_tab)>2:
print("QAR Item 1: PASS")
else:
print("QAR Item 1: FAIL")
#QAR Test 2
if windll.user32.OpenClipboard(None):
windll.user32.EmptyClipboard()
windll.user32.CloseClipboard()
pyautogui.click(262, 162) # navigates to tab with text box
pyautogui.click(614, 314) #clicks in text box
pyautogui.hotkey("Ctrl", "a")
pyautogui.hotkey("Ctrl", "c")
background_tm= copy_clipboard()
time.sleep(10)
print(background_tm)
print("test1")

According to #TerryJanReedy ,The error says that There is nothing in the clipboard, not even an empty string.
So trying appending something into it first.
Try:
From time import sleep
from tkinter import Tk
try:
r=Tk()
r.clipboard_clear()
r.clipboard_append('testing ')
result = r.selection_get(selection="CLIPBOARD")
sleep(1)
except:
selection = None

As mentioned in my comment, the control-c shortcut does not act immediately:
Try adding a very small pause before trying to access the clipboard data as the keyboard shortcut is not instantaneous. This is noted in a code comment "ctrl-c is usually very fast but your program may execute faster" found in another SO thread)
In this situation, I would move the sleep to before you get the clipboard content (I don't know the context as to why it is there) and reduce it to just 0.1s. The changed code for QAR Test 2 can be seen below:
#QAR Test 2
if windll.user32.OpenClipboard(None):
windll.user32.EmptyClipboard()
windll.user32.CloseClipboard()
pyautogui.click(262, 162) # navigates to tab with text box
pyautogui.click(614, 314) #clicks in text box
pyautogui.hotkey("Ctrl", "a")
pyautogui.hotkey("Ctrl", "c")
time.sleep(0.1)
background_tm= copy_clipboard()
print(background_tm)
print("test1")
Note: If this still doesn't work it may be worth looking into a different method for getting the clipboard content as many people (far more experienced than myself!) have reported that it returns None instead of the actual content in some situations.

Hey I guess the main reason is the typo you have done:
You used 'Ctrl' i.e Control with capital C, which is sometimes accepted due to error handling mechanism but no always. Try this:
pyautogui.hotkey('ctrl','c')
Or use the backend method of hot keys which is
pyautogui.keyDown('ctrl')
pyautogui.press('c')
pyautogui.keyUp('ctrl')
And use some more sleep() time before calling this, this maybe because the program might be running faster (or slower for that matter) its better to be safe than sorry.
Good luck
Hope this helps

Related

How to replace keyboard output using python

I am not exactly sure how to word this question so I'll try to explain my problem here.
I am trying to code a program that reads text (for example an essay) from a txt file and then types each letter from that txt file as you type a letter on the keyboard (think those hacker games where you mash the keyboard and it looks like you're typing something that you are not).
Currently I am handling this by simply deleting the character right after but I noticed that I needed to add a delay for this to work with any stability whatsoever. A delay of 0.05 works okay, but any lower is unstable and 0.05 is already far too much for my liking. I also tried using keyboard.press_and_release() but this needed just as large of a delay without breaking.
I am using the keyboard module because it works on both Windows and Mac which is a must have. I am also not exactly sure that I understand why this is happening especially with the press and release function so hopefully someone might know an answer or maybe a different module to use. I have also tried pyautogui and that was even worse.
import pyautogui
import time
import keyboard
# only keyboard needs to be pip installed i think
if __name__ == '__main__':
keyboard.wait("ctrl")
time.sleep(2)
inFile = open('Essay', 'r')
while True:
line = inFile.readline();
# if line is empty meaning file is reached
if not line:
break
while len(line) > 0:
keyboard.read_key()
time.sleep(0.05)
keyboard.press("backspace")
time.sleep(0.05)
keyboard.press(line[0])
line = line[1:len(line)]
time.sleep(0.05)
keyboard.press("enter")
I am not sure but it may help you. Install the PyPI package:
pip install keyboard
or clone the repository (no installation required, source files are sufficient):
git clone https://github.com/boppreh/keyboard
or download and extract the zip into your project folder.
Then check the API docs below to see what features are available.
Example:
import keyboard
keyboard.press_and_release('shift+s, space')
keyboard.write('The quick brown fox jumps over the lazy dog.')
keyboard.add_hotkey('ctrl+shift+a', print, args=('triggered', 'hotkey'))
# Press PAGE UP then PAGE DOWN to type "foobar".
keyboard.add_hotkey('page up, page down', lambda: keyboard.write('foobar'))
# Blocks until you press esc.
keyboard.wait('esc')
# Record events until 'esc' is pressed.
recorded = keyboard.record(until='esc')
# Then replay back at three times the speed.
keyboard.play(recorded, speed_factor=3)
# Type ## then press space to replace with abbreviation.
keyboard.add_abbreviation('##', 'my.long.email#example.com')
# Block forever, like `while True`.
keyboard.wait()
Courtesy: https://softans.com/how-to-replace-keyboard-output-using-python/

Selenium Python Instagram Scraping All Images in a post not working

I am writing a small code to download all images/videos in a post. Here is my code:
import urllib.request as reqq
from selenium import webdriver
import time
browser = webdriver.Chrome("D:\\Python_Files\\Programs\\chromedriver.exe")
browser.get(url)
browser.maximize_window()
url_list = ['https://www.instagram.com/p/CE9CZmsghan/']
img_urls = []
vid_urls = []
img_url = ""
vid_url = ""
for x in url_list:
count = 0
browser.get(x)
while True:
try:
elements = browser.find_elements_by_class_name('_6CZji')
elements[0].click()
time.sleep(1)
except:
count+=1
time.sleep(1)
if count == 2:
break
try:
vid_url = browser.find_element_by_class_name('_5wCQW').find_element_by_tag_name('video').get_attribute('src')
vid_urls.append(vid_url)
except:
img_url = browser.find_element_by_class_name('KL4Bh').find_element_by_tag_name('img').get_attribute('src')
img_urls.append(img_url)
for x in range(len(img_urls)):
reqq.urlretrieve(img_urls[x],f"D:\\instaimg"+str(x+1)+".jpg")
for x in range(len(vid_urls)):
reqq.urlretrieve(vid_urls[x],"D:\\instavid"+str(x+1)+".mp4")
browser.close()
This code extracts all the images in the post except the last image. IMO, this code is right. Do you know why this code doesn't extract the last image? Any help would be appreciated. Thanks!
Go to the URL that you're using in the example and open the inspector, and very carefully watch how the DOM changes as you click between images. There are multiple page elements with class KL4Bh because it tracks the previous image, the current image, and the next image.
So doing find_element_by_class_name('KL4Bh') returns the first match on the page.
Ok, lets break down this loop and see what is happening:
first iteration
page opens
immediately click 'next' to second photo
grab the first element for class 'KL4Bh' from the DOM
the first element for that class is the first image (now the 'previous' image)
[... 2, 3, 4 same as 1 ...]
fifth iteration
look for a "next" button to click
find no next button
`elements[0]` fails with index error
grab the first element for class 'KL4Bh' from the DOM
the first element for that class is **still the fourth image**
sixth iteration
look for a "next" button to click
find no next button
`elements[0]` fails with index error
error count exceeds threshold
exit loop
try something like this:
n = 0
while True:
try:
elements = browser.find_elements_by_class_name('_6CZji')
elements[0].click()
time.sleep(1)
except IndexError:
n=1
count+=1
time.sleep(1)
if count == 2:
break
try:
vid_url = browser.find_elements_by_class_name('_5wCQW')[n].find_element_by_tag_name('video').get_attribute('src')
vid_urls.append(vid_url)
except:
img_url = browser.find_elements_by_class_name('KL4Bh')[n].find_element_by_tag_name('img').get_attribute('src')
img_urls.append(img_url)
it will do the same thing as before, except since it's now using find_elements_by_class and indexing into the resulting list, when it gets to the last image the index error for the failed button click will also cause the image lookup to increment the index it uses. So it will take the second element (the current image) on the last iteration of the loop.
There are still some serious problems with this code, but it does fix the bug you are seeing. One problem at a time :)
Edit
A few things that I think would improve this code:
When using try-except blocks to catch exceptions/errors, there are a few rules that should almost always be followed:
Name specific exceptions & errors to handle, don't use unqualified except. The reason for this is that by catching every possible error, we actually suppress and obfuscate the source of bugs. The only legitimate reason to do this is to generate a custom error message, and the last line of the except-block should always be raise to allow the error to propagate. It goes against how we typically think of software errors, but when writing code, errors are your friend.
The try-except blocks are also problematic because they are being used as a conditional control structure. Sometimes it seems easier to code like this, but it is usually a sign of incomplete understanding of the libraries being used. I am specifically referring to the block that is checking for a video versus an image, although the other one could be refactored too. As a rule, when doing conditional branching, use an if statement.
Using sleep with selenium is almost always incorrect, but it's by far the most common pitfall for new selenium users. What happens is that the developer will start getting errors about missing elements when trying to search the DOM. They will correctly conclude that it is because the page was not full loaded in the browser before selenium tried to read it. But using sleep is not the right approach because just waiting for a fixed time makes no guarantee that the page will be fully loaded. Selenium has a built-in mechanism to handle this, called explicit wait (along with implicit wait and fluent wait). Using an explicit wait will guarantee that the page element is visible before your code is allowed to proceed.

Make a PyWinAuto Program wait for a popup with Python

I've got a Python 2.7 script that uses PyWinAuto to automate an application. At one point in the application, it goes off and does something for a long time...sometimes 15 seconds, sometimes up to 2 minutes...You never know...each time is different.
Once the process is finished, a popup appears, and I want PyWinAuto to continue on. So, I'm trying to make the program wait and check if that popup dialog has appeared. When it does, I try to break out of the loop. Here's a look at the code that does what I'm describing:
all_done = 1
while all_done != 1:
try:
app.Phazer.Static2.Texts() == [u'Would you like to Store Results?']
all_done = 1
break
except:
print("Testing...")
time.sleep(2)
..rest of the code..
However, 80% of the time, it works every time....Otherwise, PyWinAuto sometimes jumps out of the loop and tries to work on the popup dialog that hasn't appeared yet.
I'm new to Python, but I know there has to be a smarter, more reliable way to pull this off....Any ideas?
The only case I can imagine is that app.Phazer.Static2 is matched with another control sometimes.
app.Phazer.Static2 is equivalent to app.Phazer.ChildWindow(best_match='Static2'). Best match algorithm used in pywinauto can capture another static text with similar name.
Comparison operator == does't raise any exception, so you may get incorrect static text, it will return False to nothing, all_done = 1, break. That's it.
You can make the following code to make sure you connected with appropriate control:
app.Phazer.ChildWindow(class_name='Static', ctrl_index=1)
or
app.Window_(title='Phazer', class_name='#32770').ChildWindow(class_name='Static', ctrl_index=1)
if you're in doubt with capturing dialog.
And so the final check should be
if app.Phazer.ChildWindow(class_name='Static', ctrl_index=1).Texts() != [u'Would you like to Store Results?']:
raise Exception()

Python: how to modify/edit the string printed to screen and read it back?

I'd like to print a string to command line / terminal in Windows and then edit / change the string and read it back. Anyone knows how to do it? Thanks
print "Hell"
Hello! <---Edit it on the screen
s = raw_input()
print s
Hello!
You could do some ANSI trickery to make it look like you are editing on screen. Check out this link (also similar to this SO post on colors).
This would only work on certain terminals and configurations. ymmv.
This python script worked in my Cygwin terminal on Win7:
print 'hell'
print '\033[1A\033[4CO!'
Ends up printing hellO! on one line. The 2nd print moves the cursor up one line (Esc[1A) then over 4 characters (Esc[4C]) and then prints the 'O!'.
It wouldn't let you read it back though... only a 1/2 answer.
I had this same use-case for a command-line application.
Finally found a hack to do this.
# pip install pyautogui gnureadline
import pyautogui
import readline
from threading import Thread
def editable_input(text):
Thread(target=pyautogui.write, args=(text,)).start()
modified_input = input()
return modified_input
a = editable_input("This is a random text")
print("Received input : ", a)
The trick here is use pyautogui to send the text from keyboard. But we want to do this immediately after the input(). Since input() is a blocking call, we can run the pyautogui command in a different thread. And have an input function immediately after that in the main thread.
gnureadline is for making sure we can press left and right arrow keys to move the cursor in a terminal without printing escape characters.
Tested this on Ubuntu 20, python 3.7
raw_input accepts a parameter for a "prompt message", so use that to output the message, and then prepend it to what you get back. However, this won't allow you to backspace into the prompt, because it's a prompt and not really part of the input.
s = "Hell" + raw_input("Hell")
print s
os.sys.stdout is write only, but you can erase some characters of the last line with \b or the whole line with \r, as long as you did not write a carriage return.
(however, see also my question about limitations to the standard python console/terminal)
I once made some output exercise (including a status bar) to write,erase or animate if you will, perhaps it is helpfull:
from __future__ import print_function
import sys, time
# status generator
def range_with_status(total):
n=0
while n<total:
done = '#'*(n+1)
todo = '-'*(total-n-1)
s = '<{0}>'.format(done+todo)
if not todo:
s+='\n'
if n>0:
s = '\r'+s
sys.stdout.write(s)
sys.stdout.flush()
yield n
n+=1
print ('doing something ...')
for i in range_with_status(10):
time.sleep(0.1)
print('ready')
time.sleep(0.4)
print ('And now for something completely different ...')
time.sleep(0.5)
msg = 'I am going to erase this line from the console window.'
sys.stdout.write(msg); sys.stdout.flush()
time.sleep(1)
sys.stdout.write('\r' + ' '*len(msg))
sys.stdout.flush()
time.sleep(0.5)
print('\rdid I succeed?')
time.sleep(4)
If it's for your own purposes, then here's a dirty wee hack using the clipboard without losing what was there before:
def edit_text_at_terminal(text_to_edit):
import pyperclip
# Save old clipboard contents so user doesn't lose them
old_clipboard_contents = pyperclip.paste()
#place text you want to edit in the clipboard
pyperclip.copy(text_to_edit)
# If you're on Windows, and ctrl+v works, you can do this:
import win32com.client
shell = win32com.client.Dispatch("WScript.Shell")
shell.SendKeys("^v")
# Otherwise you should tell the user to type ctrl+v
msg = "Type ctrl+v (your old clipboard contents will be restored):\n"
# Get the new value, the old value will have been pasted
new_value= str(raw_input(msg))
# restore the old clipboard contents before returning new value
pyperclip.copy(old_clipboard_contents )
return new_value
Note that ctrl+v doesn't work in all terminals, notably the Windows default (there are ways to make it work, though I recommend using ConEmu instead).
Automating the keystrokes for other OSs will involve a different process.
Please remember this is a quick hack and not a "proper" solution. I will not be held responsible for loss of entire PhD dissertations momentarily stored on your clipboard.
For a proper solution there are better approaches such as curses for Linux, and on Windows it's worth looking into AutHotKey (perhaps throw up an input box, or do some keystrokes/clipboard wizardry).

Pygtk StatusIcon not loading?

I'm currently working on a small script that needs to use gtk.StatusIcon(). For some reason, I'm getting some weird behavior with it. If I go into the python interactive shell and type:
>> import gtk
>> statusIcon = gtk.status_icon_new_from_file("img/lin_idle.png")
Pygtk does exactly what it should do, and shows an icon (lin_idle.png) in the system tray:
However, if I try to do the same task in my script:
def gtkInit(self):
self.statusIcon = gtk.status_icon_new_from_file("img/lin_idle.png")
When gtkInit() gets called, I see this instead:
I made I ran the script in the same working directory as the interactive python shell, so I'm pretty sure it's finding the image, so I'm stumped... Any ideas anyone? Thanks in advance.
Update: For some reason or another, after calling gtk.status_icon_new_from_file() a few times in the script, it does eventually create the icon, but this issue still remains unfortunately. Does anyone at all have any ideas as to what could be going wrong?
As requested: Here's the full script. This is actually an application that I'm in the very early stages of making, but it does work at the moment if you get it setup correctly, so feel free to play around with it if you want (and also help me!), you just need to get an imgur developer key and put it in linup_control.py
Linup.py
#
# Linup - A dropbox alternative for Linux!
# Written by Nakedsteve
# Released under the MIT License
#
import os
import time
import ConfigParser
from linup_control import Linup
cfg = ConfigParser.RawConfigParser()
# See if we have a .linuprc file
home = os.path.expanduser("~")
if not os.path.exists(home+"/.linuprc"):
# Nope, so let's make one
cfg.add_section("paths")
cfg.set("paths","watch_path", home+"/Desktop/screenshot1.png")
# Now write it to the file
with open(home+"/.linuprc","wb") as configfile:
cfg.write(configfile)
else:
cfg.read(home+"/.linuprc")
linup = Linup()
# Create the GUI (status icon, menus, etc.)
linup.gtkInit()
# Enter the main loop, where we check to see if there's a shot to upload
# every 1 second
path = cfg.get("paths","watch_path")
while 1:
if(os.path.exists(path)):
linup.uploadImage(path)
url = linup.getURL()
linup.toClipboard(url)
linup.json = ""
print "Screenshot uploaded!"
os.remove(path)
else:
# If you're wondering why I'm using time.sleep()
# it's because I found that without it, my CPU remained
# at 50% at all times while running linup. If you have a better
# method for doing this, please contact me about it (I'm relatively new at python)
time.sleep(1)
linup_control.py
import gtk
import json
import time
import pycurl
import os
class Linup:
def __init__(self):
self.json = ""
def uploadImage(self, path):
# Set the status icon to busy
self.statusIcon.set_from_file("img/lin_busy.png")
# Create new pycurl instance
cu = pycurl.Curl()
# Set the POST variables to the image and dev key
vals = [
("key","*************"),
("image", (cu.FORM_FILE, path))
]
# Set the URL to send to
cu.setopt(cu.URL, "http://imgur.com/api/upload.json")
# This lets us get the json returned by imgur
cu.setopt(cu.WRITEFUNCTION, self.resp_callback)
cu.setopt(cu.HTTPPOST, vals)
# Do eet!
cu.perform()
cu.close()
# Set the status icon to done...
self.statusIcon.set_from_file("img/lin_done.png")
# Wait 3 seconds
time.sleep(3)
# Set the icon to idle
self.statusIcon.set_from_file("img/lin_idle.png")
# Used for getting the response json from imgur
def resp_callback(self, buff):
self.json += buff
# Extracts the image URL from the json data
def getURL(self):
js = json.loads(self.json)
return js['rsp']['image']['original_image']
# Inserts the text variable into the clipboard
def toClipboard(self, text):
cb = gtk.Clipboard()
cb.set_text(text)
cb.store()
# Initiates the GUI elements of Linup
def gtkInit(self):
self.statusIcon = gtk.StatusIcon()
self.statusIcon.set_from_file("img/lin_idle.png")
You need to call the gtk.main function like qba said, however the correct way to call a function every N milliseconds is to use the gobject.timeout_add function. In most cases you would want to have anything that could tie up the gui in a separate thread, however in your case where you just have an icon you don't need to. Unless you are planning on making the StatusIcon have a menu. Here is the part of Linup.py that I changed:
# Enter the main loop, where we check to see if there's a shot to upload
# every 1 second
path = cfg.get("paths","watch_path")
def check_for_new():
if(os.path.exists(path)):
linup.uploadImage(path)
url = linup.getURL()
linup.toClipboard(url)
linup.json = ""
print "Screenshot uploaded!"
os.remove(path)
# Return True to keep calling this function, False to stop.
return True
if __name__ == "__main__":
gobject.timeout_add(1000, check_for_new)
gtk.main()
You will have to import gobject somewhere too.
I don't know for sure if this works because I can't get pycurl installed.
EDIT: In linup_control.py, I would try changing
# Wait 3 seconds
time.sleep(3)
# Set the icon to idle
self.statusIcon.set_from_file("img/lin_idle.png")
to
gobject.timeout_add(3000, self.statusIcon.set_from_file, "img/lin_idle.png")
You made two mistakes. One is important one is not.
At first if you want to use stock icon use .set_from_stock( stock_id ) method. If you want to use your own icon then the .set_from_file(/path/to/img.png) is ok.
The other think witch is the probably the main problem is that when you write gtk application you have to call gtk.main() function. This is main gtk loop where all signal handling/window drawing and all other gtk stuff is done. If you don't do this, simply your icon is not drawing.
The solution in your case is to make two threads - one for gui, second for your app. In the first one you simply call gtk.main(). In second you put your main program loop. Of course when you call python program you have one thread already started:P
If you aren't familiar whit threads there is other solution. Gtk have function which calls function specified by you with some delay:
def call_me:
print "Hello World!"
gtk.timeout_add( 1000 , call_me )
gtk.timeout_add( 1000 , call_me )
gtk.main()
But it seems to be deprecated now. Probably they have made a better solution.

Categories

Resources