jithurjacob / Windows-10-Toast-Notifications

Python library to display Windows 10 Toast Notifications
MIT License
970 stars 168 forks source link

Support Qt Resources #56

Open MolotovCherry opened 5 years ago

MolotovCherry commented 5 years ago

I was in the process of adding this functionality in order to make a pull request, but sadly there were some limitations, and I don't know enough about ctypes to use it effectively.

So far, I imagine it working like this: It would support PyQt5 and PySide2. There can be a simple flag that can be passed useQtResource, which can flag to use a Qt resource instead.

Now, awesomely enough, Qt comes with its own function toHICON. Great, right? Yes, but unfortunately, for whoever knows what reason, PySide2 (the official Qt Python bindings supported by Nokia) doesn't make QtWin available, whereas PyQt does! If the user is using PyQt, you could probably still make use of the toHICON function.

-- P.S. for sake of completeness, here's a toHICON code example.

pixmap = QPixmap(icon_path)
HICON = QtWin.toHICON(QPixmap)

Pretty simple isn't it?

--

Please note that if you choose to use the toHICON function for PyQt, you will have to include an additional flag useQtapi='pyside2' (or pyqt5). Why? Because the lovely qtpy (qtpy is a import library which allows you to abstract away the differences between API's, so you don't have to write or test for multiple APIs, thus, we wouldn't have to specify which API to use) also decided not to include QtWin! Seriously? Why's everybody forgetting to include important modules in their official distributions?

As for PySide2, as are the cirucmstances, I've found a way to get the raw image data from the resources. QResource(':/resource.ico').data().tobytes() will get you the raw image data represented as a Python string. Using this, I managed to cobble together a ctypes interface to convert it into a HICON using CreateIconFromResourceEx. The code looks like the following:

from ctypes import *
from ctypes.wintypes import *

CreateIconFromResourceEx = windll.user32.CreateIconFromResourceEx
size_x, size_y = 32, 32
LR_DEFAULTCOLOR = 0

imgData = QResource(icon_path).data().tobytes()
hicon = CreateIconFromResourceEx(iconData, len(iconData), 1, 0x30000, size_x, size_y, LR_DEFAULTCOLOR)

Looks lovely right? It works, but ... Yeah. It only works on PNG images, not on ICO images. :/ Frustrating. After several searching and API lookups, I found this piece of C code for ICO files.

int offset = LookupIconIdFromDirectoryEx(iconData, TRUE, iconSize, iconSize, LR_DEFAULTCOLOR);     

   if (offset != 0) {     

      hIcon = CreateIconFromResourceEx(iconData + offset, 0, TRUE, 0x30000, iconSize, iconSize, LR_DEFAULTCOLOR);     

   }

I managed to Pythonize it, but, I should warn you, iconData + offset isn't actually valid, because you cant concatenate a bytes and an int data type. I had to use offset.to_bytes(len(offset), byteorder=sys.byteorder). Well, this passed the test... No Exceptions, BUT, HICON is always 0. Anyways, no matter what I try to do, I can't get this to work with an ICO file. Yeah, I could just write the file to the disk and read it again, but that's just UGLY.

CreateIconFromResourceEx = windll.user32.CreateIconFromResourceEx
LookupIconIdFromDirectoryEx =  windll.user32.LookupIconIdFromDirectoryEx
size_x, size_y = 32, 32
LR_DEFAULTCOLOR = 0

imgData = QResource(icon_path).data().tobytes()

# works
offset = LookupIconIdFromDirectoryEx(iconData, 1, size_x, size_y, LR_DEFAULTCOLOR)
# "works", as in, won't return an exception, but it ALWAYS returns 0 (using ICO files)
hicon = CreateIconFromResourceEx(iconData + offset.to_bytes(len(offset), byteorder=sys.byteorder), 0, 1, 0x30000, size_x, size_y, LR_DEFAULTCOLOR)

So, this is where I'm at. I can do all this AS LONG as the image is a PNG, not ICO. Also, since I don't have PyQt5, I can't get toHICON, but that shouldn't be too hard for you to implement.

Any suggestions for fixing this? I'll be glad to throw a pull request if we can solve this. Please help me make this a reality.

-- Speaking of which, your code can only read an ICO image, not an PNG image. It would be nice if it supported PNG as well. As I tested PNG above, I know it works with the system as long as you can convert it to a HICON.

-- Also, I know absolutely nothing about C or ctypes, so please forgive any "obvious mistakes".

Probably the most important question: Why?

Because when you compile Python programs to an exe, and you use Qt, you will want to use the resource system (after all, you don't want raw files lying around when it can become part of the code).

MolotovCherry commented 5 years ago

I have added an issue to the official PySide2 issue tracker. It says the target fix will be in 5.14. Perhaps this will make it much easier.