I am trying to prepare an application similar to 'Windows Start Menu Search'.
That's why I need each applications own icon.
From the C:\ProgramData\Start Menu\Programs\ file path, I add existing applications to a list (QListWidget) with their names and path.
And I get the icons like this:
https://forum.qt.io/topic/62866/getting-icon-from-external-applications
provider = QFileIconProvider()
info = QFileInfo("program_path")
icon = QIcon(provider.icon(info))
And naturally the result is this:
But I don't want this "shortcut icon" to appear.
Then, I am thinking and I came to this conclusion:
shell = win32com.client.Dispatch("WScript.Shell")
provider = QFileIconProvider()
shortcut = shell.CreateShortCut(programPath)
info = QFileInfo(shortcut.targetPath)
icon = QIcon(provider.icon(info))
This solution worked. But, It has created issue for some applications.
So I am looking for an alternative solution.
You were almost there.
Browsing the menu directory tree is actually the right path, but you also have to ensure that the icon of the link is actually the same of the target, as it might not.
The shortcut.iconlocation is a string representing a "tuple" (sort of) including the icon path and the index (as icon resources might contain more than one icon).
>>> shortcut = shell.createShortCut(linkPath)
>>> print(shortcut.iconlocation)
# most links will return this:
> ",0"
# some might return this:
> ",4"
# or this:
> "C:\SomePath\SomeProgram\SomeExe.exe,5"
As long as the icon index is 0, you can get the icon using QFileIconProvider with the targetPath or iconLocation (if there's something before the comma).
The problem comes when there's a value different from 0 for the icon index, as Qt doesn't handle that.
I've put together a simple function (based on some research here on StackOverflow).
def getIcon(self, shortcut):
iconPath, iconId = shortcut.iconLocation.split(',')
iconId = int(iconId)
if not iconPath:
iconPath = shortcut.targetPath
iconPath = os.path.expandvars(iconPath)
if not iconId:
return QICon(self.iconProvider.icon(QFileInfo(iconPath)))
iconRes = win32gui.ExtractIconEx(iconPath, iconId)
hdc = win32ui.CreateDCFromHandle(win32gui.GetDC(0))
hbmp = win32ui.CreateBitmap()
# I think there's a way to find available icon sizes, I'll leave it up to you
hbmp.CreateCompatibleBitmap(hdc, 32, 32)
hdc = hdc.CreateCompatibleDC()
hdc.SelectObject(hbmp)
hdc.DrawIcon((0, 0), iconRes[0][0])
hdc.DeleteDC()
# the original QtGui.QPixmap.fromWinHBITMAP is now part of the
# QtWin sub-module
return QtGui.QIcon(QtWin.fromWinHBITMAP(hbmp.GetHandle(), 2))
Related
How do I change the default size/geometry of tkinter.filedialog.askdirectory()? I find its default size small and would like to make it wider and taller upon activation.
I am aware that:
Its window size can be changed manually using the mouse pointer but that is not what I am after.
tkinter.filedialog.askdirectory() is a tkinter.filedialog.Directory object which inherits from the tkinter.commondialog.Dialog base class. However, I have not yet figure out how to change this object size.
# For the following classes and modules:
#
# options (all have default values):
#
# - defaultextension: added to filename if not explicitly given
#
# - filetypes: sequence of (label, pattern) tuples. the same pattern
# may occur with several patterns. use "*" as pattern to indicate
# all files.
#
# - initialdir: initial directory. preserved by dialog instance.
#
# - initialfile: initial file (ignored by the open dialog). preserved
# by dialog instance.
#
# - parent: which window to place the dialog on top of
#
# - title: dialog title
#
# - multiple: if true user may select more than one file
#
# options for the directory chooser:
#
# - initialdir, parent, title: see above
#
# - mustexist: if true, user must pick an existing directory
#
def askdirectory (**options):
"Ask for a directory, and return the file name"
return Directory(**options).show()
# the directory dialog has its own _fix routines.
class Directory(commondialog.Dialog):
"Ask for a directory"
command = "tk_chooseDirectory"
def _fixresult(self, widget, result):
if result:
# convert Tcl path objects to strings
try:
result = result.string
except AttributeError:
# it already is a string
pass
# keep directory until next time
self.options["initialdir"] = result
self.directory = result # compatibility
return result
I did an experiment trying out an option called width. The returned error message clearly showed it was invalid and only 4 options can be specified (i.e. initialdir, mustexist, parent, or title).
File "/usr/lib/python3.10/tkinter/commondialog.py", line 45, in show
s = master.tk.call(self.command, *master._options(self.options))
_tkinter.TclError: bad option "-width": must be -initialdir, -mustexist, -parent, or -title
https://www.tcl.tk/man/tcl/TkCmd/chooseDirectory.html tcl/tk documentation also does not provide such an option.
I'm trying to take a screenshot with python. But every option I try only seems to return the desktop wallpaper and not the programs on top.
Here's a run through of what I've tried and how I'm doing it.
First I used autopy to try and get pixels from the screen using autopy.color.hex_to_rgb(autopy.screen.get_color(x, y)). But it was only telling me the pixels of the desktop background.
Then I tried PIL(Pillow) using this code:
from PIL import ImageGrab
im = ImageGrab.grab()
im.save('screenshot.png')
This only returned the desktop wallpaper.
Finally, I've tried using this script which I found on this thread.
import Quartz
import LaunchServices
from Cocoa import NSURL
import Quartz.CoreGraphics as CG
def screenshot(path, region = None):
"""region should be a CGRect, something like:
>>> import Quartz.CoreGraphics as CG
>>> region = CG.CGRectMake(0, 0, 100, 100)
>>> sp = ScreenPixel()
>>> sp.capture(region=region)
The default region is CG.CGRectInfinite (captures the full screen)
"""
if region is None:
region = CG.CGRectInfinite
# Create screenshot as CGImage
image = CG.CGWindowListCreateImage(
region,
CG.kCGWindowListOptionOnScreenOnly,
CG.kCGNullWindowID,
CG.kCGWindowImageDefault)
dpi = 72 # FIXME: Should query this from somewhere, e.g for retina displays
url = NSURL.fileURLWithPath_(path)
dest = Quartz.CGImageDestinationCreateWithURL(
url,
LaunchServices.kUTTypePNG, # file type
1, # 1 image in file
None
)
properties = {
Quartz.kCGImagePropertyDPIWidth: dpi,
Quartz.kCGImagePropertyDPIHeight: dpi,
}
# Add the image to the destination, characterizing the image with
# the properties dictionary.
Quartz.CGImageDestinationAddImage(dest, image, properties)
# When all the images (only 1 in this example) are added to the destination,
# finalize the CGImageDestination object.
Quartz.CGImageDestinationFinalize(dest)
if __name__ == '__main__':
# Capture full screen
screenshot("/tmp/testscreenshot_full.png")
# Capture region (100x100 box from top-left)
region = CG.CGRectMake(0, 0, 100, 100)
screenshot("/tmp/testscreenshot_partial.png", region=region)
Again it returns my desktop wallpaper.
Why is it doing this? How can I get a screenshot in the same way I would if I were to press 'cmd + shift + 3'.
This is a privacy feature, macOS prevents arbitrary apps from viewing the contents of other app windows. To get permission, your app will need access to the screen recording permission in macOS preferences.
Since this is a Python script, I think the app you're running the script from needs to be given permission, so either Terminal or whatever IDE you're using.
When reading from fileChooser.selection I get a nearly correct path to the choosen file, but with one additional folder in the path that does not exist. It seems like fileType is added after 'MY_GUI_KIVY'. What am I missing here?
The code is part of a GUI that should help manage projects. To load the related file I need to get the path to that file. After trying to find the problem on my own, searching the net to find similar problems and eventually adopt their given solutions, I could not find a working solution.
def loadFile(self, fileType):
self.getPath(fileType)
# code shortened for better readability
return
def getPath(self, fileType):
# create popup layout
content = BoxLayout(orientation='vertical', spacing=5)
popup_width = 500
self.popup = popup = Popup(title='Open ' + str(fileType), content=content, size_hint=(None, 0.9), width=popup_width)
# create the filechooser
initDir = 'projects'
if not fileType == 'project':
initDir += '\\' + fileType
initDir = initDir + '\\'
textinput = FileChooserListView(path=initDir, size_hint=(1, 1), dirselect=False, filters=['*.' + fileType])
self.textinput = textinput
# construct the content
content.add_widget(textinput)
# 2 buttons are created for accept or cancel the current value
btnlayout = BoxLayout(size_hint_y=None, height='50dp', spacing='5dp')
btn = Button(text='Ok')
btn.bind(on_release=partial(self.loadSelectedFile, fileType, textinput, popup))
btnlayout.add_widget(btn)
btn = Button(text='Cancel')
btn.bind(on_release=popup.dismiss)
btnlayout.add_widget(btn)
content.add_widget(btnlayout)
# all done, open the popup !
popup.open()
return
def loadSelectedFile(self, fileType, fileChooser, popup, *args): # *args ist unnötig, aber da die partial den Button auch noch zwingend mitliefert...
log.debug(str(fileChooser.selection))
# code shortened for better readability
return True
Sorry, I'm not allowed to post images yet. Link should show the Image. Shows
order structure - I had to blur some folders but they are not linked to the problem
If I set fileType to 'project', the output is something like that:
'C:\GUI\MY_KIVY_GUI\projects\projects\test.project'
The expected path should have been: 'C:\GUI\MY_KIVY_GUI\projects\test.project'
If fileType is set to 'subproject', the output looks like this:
'C:\GUI\MY_KIVY_GUI\projects\subproject\projects\subproject\test.subproject'
The correct path should have been the following:
'C:\GUI\MY_KIVY_GUI\projects\subproject\test.subproject'
So it seems like initDir is added after 'MY_KIVY_GUI' but I don't get why this happens.
I see the same behavior that you described. I think you may have discovered a bug in the FileChooser code. I think your code should work as written. However, a work around is to add the line:
initDir = os.path.abspath(initDir)
just before you create the FileChooserListView. This uses an absolute path instead of a relative path, and seems to work.
Can someone tell me how I can get all the selected files on Windowsd desktop in Python? I've been searching for a way to do it and I can't find anyone. The only one I found is for C#, but I don't code in C#, so I don't even know if it works: Get list of selected files from Windows Desktop (if someone understands it and could explain/convert it, that'd be appreciated too). I've found something very near of this, but I can only make it get the number of seleted files, not their path, as I'd like:
import ctypes
from commctrl import LVM_GETITEMCOUNT,LVM_GETSELECTEDCOUNT
#The LVM_GETITEMCOUNT came with the script, I got the other one from Microsoft documentation about SendMessage(), and both are near, but none returns the paths, only numbers
import pywintypes
import win32gui
GetShellWindow = ctypes.windll.user32.GetShellWindow
def get_desktop():
"""Get the window of the icons, the desktop window contains this window"""
shell_window = GetShellWindow()
shell_dll_defview = win32gui.FindWindowEx(shell_window, 0, "SHELLDLL_DefView", "")
if shell_dll_defview == 0:
sys_listview_container = []
try:
win32gui.EnumWindows(_callback, sys_listview_container)
except pywintypes.error as e:
if e.winerror != 0:
raise
sys_listview = sys_listview_container[0]
else:
sys_listview = win32gui.FindWindowEx(shell_dll_defview, 0, "SysListView32", "FolderView")
return sys_listview
def _callback(hwnd, extra):
class_name = win32gui.GetClassName(hwnd)
if class_name == "WorkerW":
child = win32gui.FindWindowEx(hwnd, 0, "SHELLDLL_DefView", "")
if child != 0:
sys_listview = win32gui.FindWindowEx(child, 0, "SysListView32", "FolderView")
extra.append(sys_listview)
return False
return True
def get_item_count(window):
return win32gui.SendMessage(window, LVM_GETSELECTEDCOUNT)
desktop = get_desktop()
print(get_item_count(desktop))
I've searched on the commands that can be sent to a window, but I didn't find anyone to get the path of the selected items (maybe I missed one?).
A way I found of getting the selected files from Windows Explorer windows, but now from the desktop: https://stackoverflow.com/a/21250927/8228163.
Any help (with any Windows, preferably 7) is much appreciated. Thanks in advance!
Is there any way to make pywinauto find a window just with a part of the title?
This is my code:
import pywinauto
pwa_app = pywinauto.application.Application()
w_handle = pywinauto.findwindows.find_windows(title=u'Minitab Professional 5.1 64bit - 3333348.temp.project',
class_name='Window')[0]
The problem is that the number before temp.project changes every time I open the software and because of that I cannot get pywinauto to find the right window.
By skimming the source code on google code, I see you can feed a regex for the title :
#=========================================================================
def find_windows(class_name = None,
class_name_re = None,
parent = None,
process = None,
title = None,
title_re = None,
top_level_only = True,
visible_only = True,
enabled_only = False,
best_match = None,
handle = None,
ctrl_index = None,
predicate_func = None,
active_only = False,
control_id = None,
):
"""Find windows based on criteria passed in
Possible values are:
* **class_name** Windows with this window class
* **class_name_re** Windows whose class match this regular expression
* **parent** Windows that are children of this
* **process** Windows running in this process
* **title** Windows with this Text
* **title_re** Windows whose Text match this regular expression
* **top_level_only** Top level windows only (default=True)
* **visible_only** Visible windows only (default=True)
* **enabled_only** Enabled windows only (default=True)
* **best_match** Windows with a title similar to this
* **handle** The handle of the window to return
* **ctrl_index** The index of the child window to return
* **active_only** Active windows only (default=False)
* **control_id** Windows with this control id
"""
According to me pywinauto.findwindows.find_windows(title_re = r'Minitab Professional 5.1 64bit*', class_name='Window')[0] should work.
Use best_match, no need for regex:
handle = pywinauto.findwindows.find_window(best_match='Minitab')
app = pywinauto.application.Application().connect(handle=handle)
or shorter:
app = pywinauto.application.Application().connect(best_match='Minitab')
title_re works as Python regular expression. In your case it should be like title_re=u'Minitab Professional 5\.1 64bit - \d+\.temp\.project'.
\. means dot symbol, . means any symbol.
For fully functional dialog wrapper (instead of handle) the following thing is simpler:
dlg = pwa_app.Window_(title_re=u'Minitab Professional 5\.1 64bit - \d+\.temp\.project', class_name='Window')
It calls find_window with proper process param (this is pid), so you will not be confused by many similar windows from several app instances.
BTW, for 64-bit application you need 64-bit compatible clone of pywinauto (official 0.4.2 supports only 32-bit Python and apps because of different WinAPI structures alignment).
In this case it's better to connect to App by path, like:
app = application.Application(backend="uia")
app.connect(path = r"C:/Program Files/iTunes/iTunes.exe")
Here is another solution with pyautogui:
we will find a window by partial title then close it.
import pyautogui
win = [w for w in pyautogui.getAllWindows() if 'your window partial title' in w.title]
if len(win)>0:
win[0].close()