I need to build a Menu using curses in python.
When user select choice, it run a function within a sub-module.
I have first a json file which contains the menu structure:
[
{
"name": "Action A",
"function": "main.foo.actionA"
},
"name": "Action B",
"function": "main.bar.actionB"
}
]
I have a main script which call the the menu.load method below:
import curses
import json
class menu:
def __init__(self):
self.curses = curses
self.screen = self.curses.initscr()
self.menuItems = json.loads(<my json menu file>)
def load(self):
x = 0
while x != ord('q'):
self.screen.addstr(2, 2, "Make your choice")
i=1
for item in self.menuItems:
self.screen.addstr(i, 4, item['name'])
i+=1
self.screen.refresh()
x = self.screen.getch()
if x == <my id item id matching the menu>:
//for example
self.run("main.bar.actionB")
def run(self, action):
function = action.split('.')
// Clear the screen, remove curse stuff so I can see intercative things in the terminal
_temp = __import__("lib.functions.%s" % function[0], globals(), locals(), [function[1]], -1)
mod = getattr(_temp, function[1])()
result = getattr(mod, function[2])()
// Once run is done, get back to menu
self.load()
This is working fine (I didn't paste all the code because there is submenu).
BUT, when user run first action first time, it works.
Then, once user is back to the menu, he can run again the same action, and then, I can see the action running twice.
If I run the same action third times, it runs three times...etc etc
I first though the importer was importing menu each times and then some kind of reference was found ... so I've tried to add control with:
try:
<run the function>
except AtributeError:
//function is not loaded, load it
But it didn't work..
Related
I want to develop an app that helps me to record all the keyboard and mouse events executed in a particular Windows Application for example in Microsoft Excel, Acrobat, Notepad, and so on.
I tried Pyhook and Win32gui to achieve my goal so far. However, I do not know how to retrieve the following information:
Target element name which was clicked or where user wrote something (For example if the application is notepad I would like to store "Format" when the user clicked on Format menu)
Xpath of that target element name (Following the example above, I want to know, for that menu Format, its parent handle and respective class parent)
Thank you so much for your help or advice and please forgive me if I wrote something incorrectly. I am very new with Python ;)
First get the current mouse click position, use WindowFromPoint to get the current window handle;
GetClassName to get the window class name;
GetMenu to get the HMENU handle;
Then use MenuItemFromPoint to get the menu item ID(this function return will -1 if no menu item is at the specified location);
Finally use GetMenuItemInfo(The GetMenuString function has been superseded by GetMenuItemInfo) to get the menu text.
Here is a simple C++ test sample:
#include <Windows.h>
#include <iostream>
int main()
{
Sleep(2000);//give us the time to test clicking on the menu.
POINT p;
GetCursorPos(&p);
HWND h = WindowFromPoint(p);
char classname[100] = { 0 };
GetClassName(h, classname,100);
MENUBARINFO menubar = {0};
menubar.cbSize = sizeof(MENUBARINFO);
HMENU menu = GetMenu(h);
int id = MenuItemFromPoint(h, menu,p);
MENUITEMINFO info = { 0 };
info.cbSize = sizeof(MENUITEMINFO);
info.fMask = MIIM_STRING;
info.dwTypeData = NULL;
GetMenuItemInfo(menu, id , true,&info);
info.dwTypeData = (LPSTR)malloc(info.cch+1);
info.cch++;
GetMenuItemInfo(menu, id, true, &info);
MessageBox(NULL, info.dwTypeData,"Menu Name",0);
free(info.dwTypeData);
return 0;
}
UPDATE:
That's the code I have tested, and work for me.(test in NotePad)
import win32api
import win32gui
import win32gui_struct
import win32con
import time
import ctypes
from ctypes.wintypes import tagPOINT
time.sleep(3) #point to the menu before the time ends.
pos = win32gui.GetCursorPos()
hwnd = win32gui.WindowFromPoint(pos)
##get ClassName
ClassName = win32gui.GetClassName(hwnd)
menu = win32gui.GetMenu(hwnd)
print("ClassName = " + ClassName)
##get Id
point = tagPOINT(pos[0],pos[1])
Id = ctypes.windll.user32.MenuItemFromPoint(hwnd,menu,point)
print("Id = " + str(Id))
##get Menu
info,extras = win32gui_struct.EmptyMENUITEMINFO(win32con.MIIM_STRING)
win32gui.GetMenuItemInfo(menu,Id,1,info)
strings = win32gui_struct.UnpackMENUITEMINFO(info)
print("Menu = " + strings.text)
I'm making a clock using Tkinter and Python.
What I want to achieve is having the clock run and automatically check if the JSON file (which contains the clock's visual settings) has been updated. If the JSON file has been updated, then the clock will update live.
However, this isn't the case. What I have to do is update the JSON file, close the clock program, then re-open the clock program. Only in this case will the changed JSON settings take place.
clock.py
from tkinter import *
from datetime import datetime
from settings import *
# Updates the program and clock settings
def tick():
time_string = datetime.now().strftime(time_format)
date_string = datetime.now().strftime(date_format)
root.config(bg=background_color)
container.configure(bg=background_color)
current_time.configure(text=time_string,
font=(time_font, time_size, time_weight, time_slant),
fg=time_color,
bg=background_color)
current_date.configure(text=date_string,
font=(date_font, date_size, date_weight, date_slant),
fg=date_color,
bg=background_color)
current_time.after(1, tick)
# TKInterface
root = Tk()
root.title(window_title)
# Binds 'Esc' key to exit program
root.bind('<Escape>', exit)
# Runs program in full-screen
if full_screen:
root.attributes('-fullscreen', True)
root.config(cursor='none')
# Creates container to hold time and date
container = Frame(root)
current_time = Label(container)
current_date = Label(container)
container.pack(expand=True)
current_time.pack()
current_date.pack()
tick()
root.mainloop()
settings.py
import os
import json
with open('settings.json') as json_settings:
settings = json.load(json_settings)
# Window
full_screen = settings['window']['full_screen']
window_title = settings['window']['window_title']
# Background
background_color = settings['background']['color']
# Time
time_font = settings['time']['font']
time_size = settings['time']['size']
time_weight = settings['time']['weight']
time_slant = settings['time']['slant']
time_color = settings['time']['color']
time_format = settings['time']['format']
# Date
date_font = settings['date']['font']
date_size = settings['date']['size']
date_weight = settings['date']['weight']
date_slant = settings['date']['slant']
date_color = settings['date']['color']
date_format = settings['date']['format']
settings.json
{
"window": {
"full_screen": false,
"window_title" : "chronoberry"
},
"background": {
"color": "black"
},
"time": {
"font": "arial",
"size": 70,
"weight": "bold",
"slant": "roman",
"color": "white",
"format": "%-I:%M:%S %p"
},
"date": {
"font": "arial",
"size": 20,
"weight": "normal",
"slant": "roman",
"color": "white",
"format": "%A, %B %-d %Y"
}
}
Desired Effect:
If I change the background color in my JSON file and save it, my clock should be able to update its color during run-time.
Before changing the background color
After changing the background color
What I've Tried:
Re-importing the settings.py module using importlib.reload(), but the settings.py module isn't a valid argument.
Opening settings.json, reading from it, closing it, then opening it again. However, once a file is closed, it's impossible to open it again.
Your settings module is essentially a module of constants, which makes it quite hard to reuse. While you could do something hacky to force the reload like:
def tick():
import settings, importlib
importlib.reload(settings)
from settings import *
# ... rest of tick code ...
that's going to be both inefficient and a terrible misuse of the import mechanism (now instead of reading one file, you're reading two, the module and the JSON it depends on).
Instead, I'd recommend making settingss a little more reusable with a function that can read the data and cache it, rather than a bunch of globals:
import os
import json
class Settings:
SETTINGS_FILE = 'settings.json'
def __init__(self):
self._load_settings()
def update_settings(self):
if self._last_update != os.stat(self.SETTINGS_FILE).st_mtime:
self._load_settings()
return True
return False
def _load_settings(self):
with open(self.SETTINGS_FILE) as json_settings:
settings = json.load(json_settings)
self._last_update = os.fstat(json_settings.fileno()).st_mtime
# Window
self.full_screen = settings['window']['full_screen']
self.window_title = settings['window']['window_title']
# Background
self.background_color = settings['background']['color']
# Time
self.time_font = settings['time']['font']
self.time_size = settings['time']['size']
self.time_weight = settings['time']['weight']
self.time_slant = settings['time']['slant']
self.time_color = settings['time']['color']
self.time_format = settings['time']['format']
# Date
self.date_font = settings['date']['font']
self.date_size = settings['date']['size']
self.date_weight = settings['date']['weight']
self.date_slant = settings['date']['slant']
self.date_color = settings['date']['color']
self.date_format = settings['date']['format']
Now your clock module can import settings, construct a Settings object up front, and at each tick call update_settings() on it. If update_settings returns True, it should also reapply the configuration. The code would need to qualify the various names, so instead of just saying date_color, you'd put:
mysettings = Settings()
at the top level, and refer to date_color with:
mysettings.date_color
but that's a small price to pay to improve the code.
How serious is this code? You could just check for the file's last modified time at some interval with os.stat(path_to_file).st_mtime and refresh your interface if it's later than the last time you checked. Quick and dirty.
You have a couple of options. 1 is the option benas has suggested. Every second or so check the last modified time of the json file to see if it has changed.
Another option is to use a package such as PyiNotify. This package can identify events that occur on files and folders. 1 such event is the IN_MODIFY event which triggers when a file has been modified. A list of events can be found here.
Firstly you would need to install the package using pip:
pip install pyinotify
The following is example code (almost entirely from their documentation)
import pyinotify
# The watch manager stores the watches and provides operations on watches
wm = pyinotify.WatchManager()
wm.add_watch('/path/to/file', mask, rec=True)
mask = pyinotify.IN_MODIFY # watched events
class EventHandler(pyinotify.ProcessEvent):
def process_IN_MODIFY(self, event):
print "Modifying:", event.pathname
I have the following Python MFC code. I have a listbox which I fill with some values, and when the user clicks on the values I want the static text control to be updated with the current selection. There are two problems with this code. The first is that the value in the text control is only updated the first time I click on the listbox. The second is that the value lags behind the real selected value in the listbox, presumably because the control handles the click after my handler code gets called. I'd appreciate help with either of these issues.
An odd thing, (perhaps a clue) is that when I mouse-down over the 'OK' button but move away for mouse-up the static text does get updated as I would expect.
I've tried RedrawWindow(), UpdateWindow(), ShowWindow() on both the control and the dialog, and nothing seems to make any difference.
import win32con
from pywin.mfc import dialog
IDC_LIST = 9000
IDC_TEXT = 9001
class ChooserDialog(dialog.Dialog):
def __init__(self):
DIALOGTEMPLATE = [
["Test", (0, 0, 254, 199), win32con.WS_CAPTION | win32con.DS_MODALFRAME, None, (8, "MS SansSerif")],
[128, "OK", win32con.IDOK, (197,178,50,14), win32con.BS_PUSHBUTTON | win32con.WS_VISIBLE],
["listbox", "List", IDC_LIST, (7,7,177,186), win32con.WS_VISIBLE],
["static", "", IDC_TEXT, (197,7,50,160), win32con.WS_CHILD | win32con.WS_VISIBLE]
]
dialog.Dialog.__init__(self, DIALOGTEMPLATE)
def OnInitDialog(self):
rc = dialog.Dialog.OnInitDialog(self)
for i in ["one", "two", "three"]:
self.GetDlgItem(IDC_LIST).AddString(i)
self.HookCommand(self.OnNotify, IDC_LIST)
return rc
def OnNotify(self, ctrl, action):
if ctrl == IDC_LIST:
selected = self.GetDlgItem(IDC_LIST).GetCurSel()
self.SetDlgItemText(IDC_TEXT, "%d" % selected)
self.GetDlgItem(IDC_TEXT).RedrawWindow()
return 1
dia = ChooserDialog()
dia.DoModal()
The first problem with the code was that the win32con.LBS_NOTIFY style wasn't set for the listbox control. If that isn't set you won't get any messages from the LB. The few messages that I was getting were due to other events in the dialog.
The second problem was that I was using HookCommand(), which intercepts commands, and allows you to handle them. Instead I wanted HookMessage() to get only the notifications. It seems notifications get called after the update of the control, so that's exactly what I wanted.
The LBN_SELCHANGE notification, according to MSDN documentation is received through the WM_COMMAND message. It seems I must subscribe to all WM_COMMAND messages in the dialog, and then filter them in my handler. The LBN_SELCHANGE documentation explains about what is passed, and in conjunction with the HookMessage() Python documentation you can work out how to handle it.
Here is the working code:
import win32con
from pywin.mfc import dialog
IDC_LIST = 9500
IDC_TEXT = 9501
class ChooserDialog(dialog.Dialog):
def __init__(self):
DIALOGTEMPLATE = [
["Test", (0, 0, 254, 199), win32con.WS_CAPTION | win32con.DS_MODALFRAME, None, (8, "MS SansSerif")],
[128, "OK", win32con.IDOK, (197,178,50,14), win32con.BS_PUSHBUTTON | win32con.WS_VISIBLE],
["listbox", "List", IDC_LIST, (7,7,177,186), win32con.WS_VISIBLE|win32con.LBS_NOTIFY],
["static", "", IDC_TEXT, (197,7,50,160), win32con.WS_CHILD | win32con.WS_VISIBLE]
]
dialog.Dialog.__init__(self, DIALOGTEMPLATE)
def OnInitDialog(self):
rc = dialog.Dialog.OnInitDialog(self)
for i in ["one", "two", "three"]:
self.GetDlgItem(IDC_LIST).AddString(i)
self.HookMessage(self.OnNotifyCommand, win32con.WM_COMMAND)
return rc
def OnNotifyCommand(self, data):
msg_id = data[1] # should always be WM_COMMAND
wParam = data[2]
lParam = data[3]
list_id = wParam & 0xffff
notification_code = (wParam & 0xffff0000) >> 16
if list_id != IDC_LIST: return # not our list box.
if notification_code != win32con.LBN_SELCHANGE: return # Not a change of selection
selected = self.GetDlgItem(IDC_LIST).GetCurSel()
self.SetDlgItemText(IDC_TEXT, "%d"%selected)
dia = ChooserDialog()
dia.DoModal()
I have a GUI that has 4 widgets which are user inputs (file in/out and directory in/out). I am trying to make a button that will do two things.
I want to button when clicked to send the four user set parameters to another imported function.
self.btn.clicked.connect(lambda: self.sendData(self.rawName, self.outName, self.directoryIn, self.directoryOut))
I was using something like this. Where send data looks like this:
def sendData(self, rawName, outName, directoryIn, directoryOut):
try:
foo.main(rawName, outName, directoryIn, directoryOut)
except TypeError:
pass
In this case foo.main is the imported function. The user input method looks like this:
def setInputDirectory(self):
options = QtGui.QFileDialog.DontResolveSymlinks | QtGui.QFileDialog.ShowDirsOnly
directoryIn = QtGui.QFileDialog.getExistingDirectory(self,
"Some Directory",
self.directoryLabelIn.text(), options)
if directoryIn:
self.directoryLabelIn.setText(directoryIn)
Finally, I want to have the button (btn) be clickable only when all four values are entered in the gui.
self.rawName = ""
self.outName = ""
self.directoryIn = ""
self.directoryOut = ""
...
self.btn.clicked.connect(self.sendData)
self.btn.setEnabled(False) # disable button here, enable it later
so you can simply send those parameters directly:
def sendData(self):
try:
foo.main(self.rawName, self.outName, self.directoryIn, self.directoryOut)
except TypeError:
pass
also after every input, check if all four values are entered and if so enable button:
def areAllFourValuesEntered(self):
if (self.rawName!="" and self.outName!="" and self.directoryIn!="" and self.directoryOut!=""):
self.btn.setEnabled(True)
I'm using Esky with my frozen app. It has the following properties and methods
are available on the Esky class:
app.version: the current best available version.
app.active_version: the currently-executing version, or None
if the esky isn't for the current app.
app.find_update(): find the best available update, or None
if no updates are available.
app.fetch_version(v): fetch the specified version into local storage.
app.install_version(v): install and activate the specified version.
Now, that's nice and all, but I want to show the progress of the download task in my Gui.
How can I achieve that?
wxPython has wrapped Esky in their own SoftwareUpdate method:
https://github.com/wxWidgets/wxPython/blob/master/wx/lib/softwareupdate.py
In their implementation, the application checks for new versions, and asks the user if they'd like to update (using wx GUI for interaction). If the user chooses to update, the code simply calls esky's auto_update() method to handle the rest, but they provide it with an _updateProgress method which updates a progress bar and provides messages indicating Esky's progress:
self._esky.auto_update(self._updateProgress)
...
def _updateProgress(self, status):
# Show progress of the download and install. This function is passed to Esky
# functions to use as a callback.
if self._pd is None and status.get('status') != 'done':
self._pd = wx.ProgressDialog('Software Update', ' '*40,
style=wx.PD_CAN_ABORT|wx.PD_APP_MODAL,
parent=self._parentWindow)
self._pd.Update(0, '')
if self._parentWindow:
self._pd.CenterOnParent()
simpleMsgMap = { 'searching' : 'Searching...',
'retrying' : 'Retrying...',
'ready' : 'Download complete...',
'installing' : 'Installing...',
'cleaning up' : 'Cleaning up...',}
if status.get('status') in simpleMsgMap:
self._doUpdateProgress(True, simpleMsgMap[status.get('status')])
elif status.get('status') == 'found':
self._doUpdateProgress(True, 'Found version %s...' % status.get('new_version'))
elif status.get('status') == 'downloading':
received = status.get('received')
size = status.get('size')
currentPercentage = 1.0 * received / size * 100
if currentPercentage > 99.5:
self._doUpdateProgress(False, "Unzipping...", int(currentPercentage))
else:
self._doUpdateProgress(False, "Downloading...", int(currentPercentage))
elif status.get('status') == 'done':
if self._pd:
self._pd.Destroy()
self._pd = None
wx.Yield()
def _doUpdateProgress(self, pulse, message, value=0):
if pulse:
keepGoing, skip = self._pd.Pulse(message)
else:
keepGoing, skip = self._pd.Update(value, message)
if not keepGoing: # user pressed the cancel button
self._pd.Destroy()
self._pd = None
raise UpdateAbortedError()
The code above was take directly from https://github.com/wxWidgets/wxPython/blob/master/wx/lib/softwareupdate.py.
This feature is documented in the Esky source, file: init.py, line: 689.
The code itself shows what your callback can expect to see throughout an update. Here are some excerpts where your callback would be called:
callback({"status":"searching"})
callback({"status":"found", "new_version":version})
callback({"status":"installing", "new_version":version})
for status in self.sudo_proxy.fetch_version_iter(version):
if callback is not None:
callback(status)
callback({"status":"cleaning up"})
callback({"status":"cleaning up"})
callback({"status":"error","exception":e})
callback({"status":"done"})
Not properly documenated, there is the fetch_version_iter generator function:
fetch_version_iter: like fetch_version but yielding progress updates
during its execution
It yields the following values:
yield {"status": "downloading",
"size": infile_size,
"received": partfile.tell(),
}
yield {"status":"retrying","size":None}
yield {"status":"ready","path":name}
Also, you can get the filename like that:
app.version_finder.version_graph.get_best_path(app.version,v)