mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Open With: Load high resolution icons from exe files on windows
This commit is contained in:
parent
63caf21996
commit
615a3dfcf5
@ -6,10 +6,9 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import os, uuid, re
|
import os, uuid
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from future_builtins import map
|
|
||||||
|
|
||||||
from PyQt5.Qt import (
|
from PyQt5.Qt import (
|
||||||
QApplication, QStackedLayout, QVBoxLayout, QWidget, QLabel, Qt,
|
QApplication, QStackedLayout, QVBoxLayout, QWidget, QLabel, Qt,
|
||||||
@ -63,37 +62,15 @@ def entry_to_icon_text(entry, only_text=False):
|
|||||||
if iswindows:
|
if iswindows:
|
||||||
# Windows {{{
|
# Windows {{{
|
||||||
from calibre.utils.winreg.default_programs import find_programs, friendly_app_name
|
from calibre.utils.winreg.default_programs import find_programs, friendly_app_name
|
||||||
from calibre.gui2 import must_use_qt
|
from calibre.utils.open_with.windows import load_icon_resource
|
||||||
from win32gui import ExtractIconEx, DestroyIcon
|
|
||||||
from win32process import CreateProcess, STARTUPINFO
|
from win32process import CreateProcess, STARTUPINFO
|
||||||
from win32event import WaitForInputIdle
|
from win32event import WaitForInputIdle
|
||||||
import win32con
|
import win32con
|
||||||
from PyQt5.Qt import QtWin
|
|
||||||
oprefs = JSONConfig('windows_open_with')
|
oprefs = JSONConfig('windows_open_with')
|
||||||
|
|
||||||
def entry_sort_key(entry):
|
def entry_sort_key(entry):
|
||||||
return sort_key(entry.get('name') or '')
|
return sort_key(entry.get('name') or '')
|
||||||
|
|
||||||
def load_icon_resource(icon_resource, as_data=False):
|
|
||||||
if not icon_resource:
|
|
||||||
return
|
|
||||||
parts = tuple(filter(None, re.split(r',([-0-9]+$)', icon_resource)))
|
|
||||||
if len(parts) != 2:
|
|
||||||
return
|
|
||||||
module, index = parts
|
|
||||||
large_icons, small_icons = ExtractIconEx(module, int(index), 1)
|
|
||||||
icons = large_icons + small_icons
|
|
||||||
try:
|
|
||||||
if icons:
|
|
||||||
must_use_qt()
|
|
||||||
pixmap = QtWin.fromHICON(icons[0])
|
|
||||||
pixmap = pixmap.scaled(48, 48, transformMode=Qt.SmoothTransformation)
|
|
||||||
if as_data:
|
|
||||||
return pixmap_to_data(pixmap)
|
|
||||||
return QIcon(pixmap)
|
|
||||||
finally:
|
|
||||||
tuple(map(DestroyIcon, icons))
|
|
||||||
|
|
||||||
def finalize_entry(entry):
|
def finalize_entry(entry):
|
||||||
data = load_icon_resource(entry.pop('icon_resource', None), as_data=True)
|
data = load_icon_resource(entry.pop('icon_resource', None), as_data=True)
|
||||||
if data:
|
if data:
|
||||||
@ -104,7 +81,7 @@ if iswindows:
|
|||||||
icon = load_icon_resource(entry.get('icon_resource'))
|
icon = load_icon_resource(entry.get('icon_resource'))
|
||||||
if not icon:
|
if not icon:
|
||||||
icon = entry_to_icon_text(entry)[0]
|
icon = entry_to_icon_text(entry)[0]
|
||||||
ans = QListWidgetItem(icon, entry.get('name') or _('Unknown'), parent)
|
ans = QListWidgetItem(QIcon(icon), entry.get('name') or _('Unknown'), parent)
|
||||||
ans.setData(ENTRY_ROLE, entry)
|
ans.setData(ENTRY_ROLE, entry)
|
||||||
ans.setToolTip(_('Command line:') + '\n' + entry['cmdline'])
|
ans.setToolTip(_('Command line:') + '\n' + entry['cmdline'])
|
||||||
|
|
||||||
|
110
src/calibre/utils/open_with/windows.py
Normal file
110
src/calibre/utils/open_with/windows.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
|
import re, struct, ctypes
|
||||||
|
from collections import namedtuple
|
||||||
|
from future_builtins import map
|
||||||
|
|
||||||
|
from PyQt5.Qt import QtWin, Qt, QIcon, QByteArray, QBuffer, QPixmap
|
||||||
|
import win32con, win32api, win32gui
|
||||||
|
|
||||||
|
from calibre.gui2 import must_use_qt
|
||||||
|
ICON_SIZE = 64
|
||||||
|
|
||||||
|
def hicon_to_pixmap(hicon):
|
||||||
|
return QtWin.fromHICON(hicon)
|
||||||
|
|
||||||
|
def pixmap_to_data(pixmap):
|
||||||
|
ba = QByteArray()
|
||||||
|
buf = QBuffer(ba)
|
||||||
|
buf.open(QBuffer.WriteOnly)
|
||||||
|
pixmap.save(buf, 'PNG')
|
||||||
|
return bytearray(ba.data())
|
||||||
|
|
||||||
|
def copy_to_size(pixmap, size=ICON_SIZE):
|
||||||
|
if pixmap.width() > ICON_SIZE:
|
||||||
|
return pixmap.scaled(ICON_SIZE, ICON_SIZE, transformMode=Qt.SmoothTransformation)
|
||||||
|
return pixmap.copy()
|
||||||
|
|
||||||
|
def simple_load_icon(module, index, as_data=False, size=ICON_SIZE):
|
||||||
|
' Use the win32 API ExtractIcon to load the icon. This restricts icon size to 32x32, but has less chance of failing '
|
||||||
|
large_icons, small_icons = win32gui.ExtractIconEx(module, index, 10)
|
||||||
|
icons = large_icons + small_icons
|
||||||
|
try:
|
||||||
|
if icons:
|
||||||
|
must_use_qt()
|
||||||
|
pixmap = copy_to_size(QtWin.fromHICON(icons[0]), size=size)
|
||||||
|
if as_data:
|
||||||
|
return pixmap_to_data(pixmap)
|
||||||
|
return QIcon(pixmap)
|
||||||
|
finally:
|
||||||
|
tuple(map(win32gui.DestroyIcon, icons))
|
||||||
|
|
||||||
|
|
||||||
|
def read_icon(handle, icon):
|
||||||
|
must_use_qt()
|
||||||
|
resource = win32api.LoadResource(handle, win32con.RT_ICON, icon.id)
|
||||||
|
pixmap = QPixmap()
|
||||||
|
pixmap.loadFromData(resource)
|
||||||
|
hicon = None
|
||||||
|
if pixmap.isNull():
|
||||||
|
if icon.width > 0 and icon.height > 0:
|
||||||
|
hicon = ctypes.windll.user32.CreateIconFromResourceEx(
|
||||||
|
resource, len(resource), True, 0x00030000, icon.width, icon.height, win32con.LR_DEFAULTCOLOR)
|
||||||
|
else:
|
||||||
|
hicon = win32gui.CreateIconFromResource(resource, True)
|
||||||
|
pixmap = hicon_to_pixmap(hicon).copy()
|
||||||
|
win32gui.DestroyIcon(hicon)
|
||||||
|
return pixmap
|
||||||
|
|
||||||
|
|
||||||
|
def load_icon(module, index, as_data=False, size=ICON_SIZE):
|
||||||
|
handle = win32api.LoadLibraryEx(module, 0, 0x20 | 0x2)
|
||||||
|
try:
|
||||||
|
ids = win32api.EnumResourceNames(handle, win32con.RT_GROUP_ICON)
|
||||||
|
grp_id = -index if index < 0 else ids[index]
|
||||||
|
data = win32api.LoadResource(handle, win32con.RT_GROUP_ICON, grp_id)
|
||||||
|
pos = 0
|
||||||
|
reserved, idtype, count = struct.unpack_from(b'<HHH', data, pos)
|
||||||
|
pos += 6
|
||||||
|
fmt = b'<BBBBHHIH'
|
||||||
|
ssz = struct.calcsize(fmt)
|
||||||
|
icons = []
|
||||||
|
Icon = namedtuple('Icon', 'size width height id')
|
||||||
|
while count > 0:
|
||||||
|
count -= 1
|
||||||
|
width, height, color_count, reserved, planes, bitcount, isize, icon_id = struct.unpack_from(fmt, data, pos)
|
||||||
|
icons.append(Icon(isize, width, height, icon_id))
|
||||||
|
pos += ssz
|
||||||
|
# Unfortunately we cannot simply choose the icon with the largest
|
||||||
|
# width/height as the width and height are not recorded for PNG images
|
||||||
|
pixmaps = []
|
||||||
|
for icon in icons:
|
||||||
|
ans = read_icon(handle, icon)
|
||||||
|
if ans is not None and not ans.isNull():
|
||||||
|
pixmaps.append(ans)
|
||||||
|
pixmaps.sort(key=lambda p:p.size().width(), reverse=True)
|
||||||
|
pixmap = copy_to_size(pixmaps[0], size=size)
|
||||||
|
if as_data:
|
||||||
|
pixmap = pixmap_to_data(pixmap)
|
||||||
|
return pixmap
|
||||||
|
finally:
|
||||||
|
win32api.FreeLibrary(handle)
|
||||||
|
|
||||||
|
def load_icon_resource(icon_resource, as_data=False, size=ICON_SIZE):
|
||||||
|
if not icon_resource:
|
||||||
|
return
|
||||||
|
parts = tuple(filter(None, re.split(r',([-0-9]+$)', icon_resource)))
|
||||||
|
if len(parts) != 2:
|
||||||
|
return
|
||||||
|
module, index = parts
|
||||||
|
index = int(index)
|
||||||
|
try:
|
||||||
|
return load_icon(module, index, as_data=as_data, size=size)
|
||||||
|
except Exception:
|
||||||
|
return simple_load_icon(module, index, as_data=as_data, size=size)
|
Loading…
x
Reference in New Issue
Block a user