From 250fbf8c3fe5c2f4c42235dabe7c75ffbb384e7f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 14 Oct 2020 08:53:15 +0530 Subject: [PATCH] Get icons for programs that dont specify an icon resource in the registry --- src/calibre/gui2/open_with.py | 17 +++++++++-- src/calibre/utils/open_with/windows.py | 25 +++++++++++++-- src/calibre/utils/windows/winutil.cpp | 42 ++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/open_with.py b/src/calibre/gui2/open_with.py index e38a52e175..8dba201cad 100644 --- a/src/calibre/gui2/open_with.py +++ b/src/calibre/gui2/open_with.py @@ -67,7 +67,7 @@ def entry_to_icon_text(entry, only_text=False): if iswindows: # Windows {{{ from calibre.utils.winreg.default_programs import find_programs, friendly_app_name - from calibre.utils.open_with.windows import load_icon_resource + from calibre.utils.open_with.windows import load_icon_resource, load_icon_for_cmdline from win32process import CreateProcess, STARTUPINFO from win32event import WaitForInputIdle import win32con @@ -76,9 +76,20 @@ if iswindows: def entry_sort_key(entry): return sort_key(entry.get('name') or '') + def icon_for_entry(entry, delete_icon_resource=False, as_data=False): + res = entry.pop('icon_resource', None) if delete_icon_resource else entry.get('icon_resource') + if res is None: + return load_icon_for_cmdline(entry['cmdline'], as_data=as_data) + try: + return load_icon_resource(res, as_data=as_data) + except Exception: + import traceback + traceback.print_exc() + return load_icon_for_cmdline(entry['cmdline'], as_data=as_data) + def finalize_entry(entry): try: - data = load_icon_resource(entry.pop('icon_resource', None), as_data=True) + data = icon_for_entry(entry, delete_icon_resource=True, as_data=True) except Exception: data = None import traceback @@ -89,7 +100,7 @@ if iswindows: def entry_to_item(entry, parent): try: - icon = load_icon_resource(entry.get('icon_resource')) + icon = icon_for_entry(entry) except Exception: icon = None import traceback diff --git a/src/calibre/utils/open_with/windows.py b/src/calibre/utils/open_with/windows.py index 6f88e85d4b..534a8972c5 100644 --- a/src/calibre/utils/open_with/windows.py +++ b/src/calibre/utils/open_with/windows.py @@ -6,6 +6,7 @@ __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' import re +import sys from PyQt5.Qt import QBuffer, QByteArray, QPixmap, Qt, QtWin from calibre.constants import plugins @@ -75,8 +76,24 @@ def load_icon_resource(icon_resource, as_data=False, size=ICON_SIZE): return ans +def load_icon_for_file(path: str, as_data=False, size=ICON_SIZE): + try: + hicon = winutil.get_icon_for_file(path) + except Exception: + return + must_use_qt() + pmap = hicon_to_pixmap(hicon) + if not pmap.isNull(): + if pmap.width() != size: + pmap = pmap.scaled(size, size, aspectRatioMode=Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation) + return pixmap_to_data(pmap) if as_data else pmap + + +def load_icon_for_cmdline(cmdline: str): + return load_icon_for_cmdline(split_commandline(cmdline)[0]) + + def display_image(png_data): - import sys from base64 import standard_b64encode def serialize_gr_command(cmd, payload=None): @@ -105,6 +122,10 @@ def display_image(png_data): def test(): - import sys png_data = load_icon_resource(sys.argv[-1], as_data=True) display_image(png_data) + + +def test_shell(): + png_data = load_icon_for_file(sys.argv[-1], as_data=True) + display_image(png_data) diff --git a/src/calibre/utils/windows/winutil.cpp b/src/calibre/utils/windows/winutil.cpp index d8b9981a32..a01997f43d 100644 --- a/src/calibre/utils/windows/winutil.cpp +++ b/src/calibre/utils/windows/winutil.cpp @@ -14,7 +14,12 @@ #include #include #include +#include +#include #include +#include +#include +#include #include // for CComPtr #include #include @@ -781,6 +786,7 @@ load_library(PyObject *self, PyObject *args) { return (PyObject*)Handle_create(h, ModuleHandle, PyTuple_GET_ITEM(args, 0)); } +// Icon loading {{{ #pragma pack( push ) #pragma pack( 2 ) typedef struct { @@ -873,6 +879,41 @@ load_icons(PyObject *self, PyObject *args) { return ans; } +_COM_SMARTPTR_TYPEDEF(IImageList, __uuidof(IImageList)); + +static HICON +get_icon_at_index(int shilsize, int index) { + IImageListPtr spiml; + HRESULT hr = SHGetImageList(shilsize, IID_PPV_ARGS(&spiml)); + HICON hico = NULL; + if (SUCCEEDED(hr)) spiml->GetIcon(index, ILD_TRANSPARENT, &hico); + return hico; +} + +static PyObject* +get_icon_for_file(PyObject *self, PyObject *args) { + wchar_raii path; + if (!PyArg_ParseTuple(args, "O&", py_to_wchar_no_none, &path)) return NULL; + scoped_com_initializer com; + if (!com.succeded()) { PyErr_SetString(PyExc_OSError, "Failed to initialize COM"); return NULL; } + SHFILEINFO fi = {0}; + DWORD_PTR res; + Py_BEGIN_ALLOW_THREADS + res = SHGetFileInfoW(path.ptr(), 0, &fi, sizeof(fi), SHGFI_SYSICONINDEX); + Py_END_ALLOW_THREADS + if (!res) return PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, ERROR_RESOURCE_TYPE_NOT_FOUND, PyTuple_GET_ITEM(args, 0)); + HICON icon; +#define R(shil) { \ + Py_BEGIN_ALLOW_THREADS \ + icon = get_icon_at_index(SHIL_JUMBO, fi.iIcon); \ + Py_END_ALLOW_THREADS \ + if (icon) return (PyObject*)Handle_create(icon, IconHandle); \ +} + R(SHIL_JUMBO); R(SHIL_EXTRALARGE); R(SHIL_LARGE); R(SHIL_SYSSMALL); R(SHIL_SMALL); +#undef R + return PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, ERROR_RESOURCE_TYPE_NOT_FOUND, PyTuple_GET_ITEM(args, 0)); +} // }}} + // Boilerplate {{{ static const char winutil_doc[] = "Defines utility methods to interface with windows."; @@ -888,6 +929,7 @@ static PyMethodDef winutil_methods[] = { M(get_last_error, METH_NOARGS), M(load_library, METH_VARARGS), M(load_icons, METH_VARARGS), + M(get_icon_for_file, METH_VARARGS), {"special_folder_path", winutil_folder_path, METH_VARARGS, "special_folder_path(csidl_id) -> path\n\n"