Building commands from variables in Python and Tkinter - python

Partly to learn, partly to help myself I'm trying to write an app with a GUI for encoding/decoding. At the moment, I'm just working on encoding.
I've a Tkinter menu which feeds a variable to the GUI def with item specified as base64, urllib or encoding hex.
A button exists on the GUI which runs gettext. I'm having difficulty getting encodedvar to contain the process + variable and for the results to be displayed in the bottom frame.
On running this, at the moment, the following appears (as an example) in the bottom frame - blackcat obviously being what was entered into the middle frame.
base64.encodestring('blackcat
')
Have 2 issues:
Getting the code to actually be formatted correctly i.e. not over 2 lines as shown above
Have the code run, rather than the command itself be printed in the bottom.
The code I'm using is displayed below:
def gui(item):
if item == 'encode_b64':
process = 'base64.encodestring'
elif item == 'encode_url':
process = 'urllib.quote_plus'
else:
process = '.encode("hex")'
def getText():
bottomtext.delete(1.0, END)
var = middletext.get(1.0, END)
encodedvar = process + "('%s')" % var
bottomtext.insert(INSERT, encodedvar)

The text widget guarantees a trailing newline, so you should use "end-1c" when getting the contents of the text widget. Doing that guarantees you get only the text that was entered by the user without that extra trailing newline.
Second, to run the function instead of printing it out, you can store the actual function in a variable, then use the variable to invoke the function:
if item == 'encode_b64':
process = base64.encodestring
elif item == 'encode_url':
process = urllib.quote_plus
else:
process = default_encode
def default_encode(s):
s.encode("hex")
...
bottomtext.insert(INSERT, process(var))
The above can be written a bit more succinctly like this:
mapping = {"encode_b64": base64.encodestring,
"encode_url": urllib.quote_plus}
process = mapping.get(item, default_encode)

Related

Python (preferably 3) equivalent to INKEY$

I am using Python 3.6.4 on a Windows 7 system (I have other systems like Win 10 and Android but this is my starting point).
INKEY$, for those not familiar with BASIC (pretty much any flavor), is a function that checks the keyboard buffer, returning that data as a string or a null/empty value ("") if there was no data, and clears the buffer. The length of the returned string depends on the data in the buffer, normally 0, 1, or 2 on a single keystroke (a fast typist could fill the small buffer between checks in the old days). The Enter key was not needed (unless that was what you were looking for) or processed and the program did not pause unless programmed to do so.
Pauser:
a$=""
while a$=""
a$=inkey$
wend
Flow Interrupter:
a=0
while a < 1000
a=a+1
print a
a$=inkey$
if a$<>"" then exit while
wend
Quick parser:
a$=inkey$
if a$<>"" then
rem process input
rem like arrow keys/a w s z for directional movement
rem useful for games and custom editors
end if
I am wanting to know if Python has a simple cross platform function (ie not 10+ lines of code unless in an importable module/class) that is the equivalent to the INKEY$ function? Also, I am not wanting to import the gaming module(s), just want an INKEY$ function equivalent (simple, straight forth, small).
import inkey
a = inkey.inkey()
Update #1:
After I installed the readchar module and corrected a reported error by Python (stdout.write(a) needs to be stdout.write(str(a)) as the variable 'a' appears to be returned as a byte string from readchar() function) when using the code listed by Mr. Stratton below, I only get continuous stream of b'\xff' and console echoed characters if there where any keypresses.
Stripping it down to use only the function doesn't help either:
from readchar import readchar
from sys import stdout
import os
#I like clear screens, and I can not lie
os.system('cls') # For Windows
#os.system('clear') # For Linux/OS X
def inkey():
"INKEY$ function"
return readchar()
#let the processing hit the floor, elsewhere
b=0
step = 1
while b < 1000:
b = b + step
print(b)
#convert bytes to integers
a = int.from_bytes(inkey(), "big")
#remember I keep getting b'\xff' (255) when buffer is empty
if chr(a) == "a":
step = step - 1
a = 255 #don't stop
if chr(a) == "l":
step = step + 1
a = 255 #don't stop
if a != 255:
break
It is supposed to count b from 0 to 999, stopping on almost any keypress, 'a' decreases the step, 'l' increases it. Instead, it prints the keypress either before or after the value of b depending on timing and continues until b = 1000. Nothing I did made a difference.
While the Pauser function can be replaced with an input() (i = input("Press Enter key to continue")) the other two variants can't be changed so easily it seems.
The closest to what you’re looking for is probably the readchar library.
Here’s an example that resembles the old BASIC logic:
from readchar import readchar
from sys import stdout
a = ' '
while ord(a) not in [3,24,27]:
a = readchar()
stdout.write(a)
stdout.flush()
if ord(a) == 13:
stdout.write("\n")
Those numbers that break the loop are CTRL-C, CTRL-X, and ESC respectively. The number 13 is the carriage return; the example writes a line feed following each carriage return to avoid overwriting text.
The equivalant to the old ASC(A$) in BASIC is ord(a) in Python. (And CHR$(A$) is chr(a).)
Note that this will block on reading. If no keypress is waiting, Python will stop on the line a = readchar() until a key is pressed. To get the full effect of BASIC’s INKEY$, you’ll need to verify that some data is waiting before reading it. You can do this using Python’s select library.
from readchar import readchar
from sys import stdin, stdout
from select import select
def inkey():
if select([stdin,],[],[],0.0)[0]:
return readchar()
return ''
a = ''
timer = 0
while a != 'Q':
a = inkey()
if a != '':
stdout.write(a)
stdout.flush()
if ord(a) == 13:
stdout.write("\n")
timer += 1
if timer > 1000000:
print("Type faster, human!")
timer = 0
This defines an inkey function and returns the empty string if nothing is waiting. If something is reading, it returns readchar(). Every 1,000,000 times through the loop, it tells the human on the other end to type faster.
In this version, it quits on a capital letter “Q”, as this does not block CTRL-C, CTRL-X, and ESC from breaking out of the program altogether.
This may have trouble if you’re using Windows, as select.select at least at one time did not work on Windows. I have no means of testing that, however.
You may also want to look at pynput if you don’t need it to look exactly like BASIC. The canonical means to do this in Python is probably to set up an event that calls a function or method. Your script goes on doing its thing, and if that function (or method) is invoked, it cancels or modifies the action of the main loop.

Issue copying to clipboard using tkinter and pyautogui

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

AppJar threading to allow for GUI updates

I have been studying the AppJar documentation for the last few hours, but I really can't seem to figure out how to get the GUI to update during the data processing. I split the 4 main functions into different threads, and within the threads I added the update function as a .queuefunction, but the GUI still hangs until everything has completed.
This is the update function I wrote:
label_status = ["Ready"]
def update_label():
app.setLabel("status_label", label_status[-1])
I then broke down the process into 4 threads, but it didn't change anything compared to before. So I'm guessing I missed something pretty obvious here, but I can't find it.
def press(button):
""" Process a button press
Args:
button: The name of the button. Either Process of Quit
"""
if button == "Process":
global label_status
global output_directory
global filename_out
src_file = app.getEntry("input_file")
output_directory = app.getEntry("output_directory")
filename_out = app.getEntry("output_name")
errors, error_msg = validate_inputs(src_file, output_directory, filename_out)
if errors:
label_status.append("Error")
update_label()
app.errorBox("Error", "\n".join(error_msg), parent=None)
return label_status
else:
#Create single xlsx doc from data
trimmed_input = src_file[:-4]
app.thread(create_xlsx_file(trimmed_input))
# add graphs to excel file
app.thread(add_graphs())
#clean temporary files
app.thread(clean_files())
#move output.xlsx to location chosen with filename chosen
app.thread(move_output())
I have attempted to update the GUI within in threads in the following way:
def clean_files():
label_status.append("Cleaning temporary files")
app.queueFunction(update_label())
file_path = os.path.join("csv_output/" + "temp*")
del_files = glob.glob(file_path)
for files in del_files:
os.remove(files)
Since I'm appending to a list, I can see all statuses are being added, but only the first and last are displayed to the user. What am I missing here?
It looks like all your calls to app.thread() and app.queueFunction() are being done wrong.
I think you should be passing just the name of a function. But, because you’ve put brackets after the function names, you’re actually passing the result of the functions.
Try it without including brackets after the function names, eg. app.queueFunction(update_label)

How to stop autocomplete and have normal tab with GNU readline library in Python

Right now, I'm running the following code in Python 2.7:
import readline as rl
rl.parse_and_bind('set editing-mode vi') #allow for arrow keys to be used
rl.set_completer()
raw_input()
According to this, rl.set_completer() should remove the completer function, which I assumed would make tab work normally. But, the tab key just doesn't work at all.
I've also tried writing my own function and passing it in as a completer, but that didn't work either. (If someone could find a way of doing this that would make the tab key work normally, that would also suffice.)
How do I get the ability to use arrow keys with raw_input, but also have a normal tab?
You can use
#allow for arrow keys to be used for raw_input.
readline.parse_and_bind('set editing-mode vi')
#set the tab key to make 4 spaces
readline.parse_and_bind("TAB: ' '")
For some reason, using readline.parse_and_bind("TAB: '\t'") caused Python to use way too much of the CPU and would just freeze the screen, so I had to switch it to use spaces.
rl.set_completer()
Removes completer function, but not the TAB binding. So tab is eaten, but nothing is done as the tab is passed to a "None function".
readline.parse_and_bind("TAB: '\t'")
results in infinite calls to the underlying completer function.
Read TAB -> Return TAB -> Read TAB …
Simplest method, at least when testing here, is to use the default emacs mode. If one only import readline without setting anything, read_raw works for arrow keys, and TAB result in TAB.
If that is not an option set a custom completer function. (Works the same for Python 3). This is also very likely the safer option.
If one press TAB the completer function is called. For one TAB key press the state argument starts at 0 and increment for each time function is called. As we return TAB it is important that we check that state is 0. Else it will be called again and again.
We also have to return the text, if any, else it is eaten by readline.
Simple example:
#!/usr/bin/env python2.7
import readline
def rl_tab_expander():
def completer(txt, state):
if state == 0:
return txt + '\t'
return completer
readline.set_completer(rl_tab_expander())
readline.parse_and_bind('set editing-mode vi')
while 1:
inp = raw_input("$ ")
if inp.strip() in ('exit', 'q', 'quit'):
break
Check on original test
You could use a log file. Here we look at what happens in the completer function if we return TAB or if we do not check state as per example above. We set a limit for state at 25, else it would continue ad infinity.
Run script in one console window and do a tail -f /tmp/test-readline.log in another.
#!/usr/bin/env python2.7
import readline
import logging
logging.basicConfig(filename = '/tmp/test-readline.log', level = logging.DEBUG)
def rl_tab_expander():
def completer(txt, state):
logging.debug("STATE: %2d TXT: %s", state, repr(txt))
if state > 24:
return None
return txt + '\t'
return completer
readline.set_completer(rl_tab_expander())
readline.parse_and_bind('set editing-mode vi')
while 1:
inp = raw_input("$ ")
if inp.strip() in ('exit', 'q', 'quit'):
break
Result
DEBUG:root:STATE: 0 TXT: ''
DEBUG:root:STATE: 1 TXT: ''
DEBUG:root:STATE: 2 TXT: ''
...
DEBUG:root:STATE: 24 TXT: ''
DEBUG:root:STATE: 25 TXT: ''
Without limiting state level this would have continued until program was killed or if we press Ctrl+C and get lucky.

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