gpodder/share/gpodder/extensions/taskbar_progress.py

216 lines
7.6 KiB
Python

# -*- coding: utf-8 -*-
#
# gPodder - A media aggregator and podcast client
# Copyright (c) 2005-2018 The gPodder Team
#
# gPodder is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# gPodder is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Windows 7 taskbar progress
# Sean Munkel; 2013-01-05
import ctypes
import functools
import logging
from ctypes import (HRESULT, POINTER, Structure, alignment, c_int, c_uint,
c_ulong, c_ulonglong, c_ushort, c_wchar_p, sizeof)
from ctypes.wintypes import tagRECT
from comtypes import COMMETHOD, GUID, IUnknown, client, wireHWND
import gpodder
import gi # isort:skip
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk # isort:skip
_ = gpodder.gettext
__title__ = _('Show download progress on the taskbar')
__description__ = _('Displays the progress on the Windows taskbar.')
__authors__ = 'Sean Munkel <seanmunkel@gmail.com>'
__category__ = 'desktop-integration'
__only_for__ = 'win32'
logger = logging.getLogger(__name__)
WSTRING = c_wchar_p
# values for enumeration 'TBPFLAG'
TBPF_NOPROGRESS = 0
TBPF_INDETERMINATE = 1
TBPF_NORMAL = 2
TBPF_ERROR = 4
TBPF_PAUSED = 8
TBPFLAG = c_int # enum
# values for enumeration 'TBATFLAG'
TBATF_USEMDITHUMBNAIL = 1
TBATF_USEMDILIVEPREVIEW = 2
TBATFLAG = c_int # enum
# return code
S_OK = HRESULT(0).value
class tagTHUMBBUTTON(Structure):
_fields_ = [
('dwMask', c_ulong),
('iId', c_uint),
('iBitmap', c_uint),
('hIcon', POINTER(IUnknown)),
('szTip', c_ushort * 260),
('dwFlags', c_ulong)]
class ITaskbarList(IUnknown):
_case_insensitive_ = True
_iid_ = GUID('{56FDF342-FD6D-11D0-958A-006097C9A090}')
_idlflags_ = []
_methods_ = [
COMMETHOD([], HRESULT, 'HrInit'),
COMMETHOD([], HRESULT, 'AddTab',
(['in'], c_int, 'hwnd')),
COMMETHOD([], HRESULT, 'DeleteTab',
(['in'], c_int, 'hwnd')),
COMMETHOD([], HRESULT, 'ActivateTab',
(['in'], c_int, 'hwnd')),
COMMETHOD([], HRESULT, 'SetActivateAlt',
(['in'], c_int, 'hwnd'))]
class ITaskbarList2(ITaskbarList):
_case_insensitive_ = True
_iid_ = GUID('{602D4995-B13A-429B-A66E-1935E44F4317}')
_idlflags_ = []
_methods_ = [
COMMETHOD([], HRESULT, 'MarkFullscreenWindow',
(['in'], c_int, 'hwnd'),
(['in'], c_int, 'fFullscreen'))]
class ITaskbarList3(ITaskbarList2):
_case_insensitive_ = True
_iid_ = GUID('{EA1AFB91-9E28-4B86-90E9-9E9F8A5EEFAF}')
_idlflags_ = []
_methods_ = [
COMMETHOD([], HRESULT, 'SetProgressValue',
(['in'], c_int, 'hwnd'),
(['in'], c_ulonglong, 'ullCompleted'),
(['in'], c_ulonglong, 'ullTotal')),
COMMETHOD([], HRESULT, 'SetProgressState',
(['in'], c_int, 'hwnd'),
(['in'], TBPFLAG, 'tbpFlags')),
COMMETHOD([], HRESULT, 'RegisterTab',
(['in'], c_int, 'hwndTab'),
(['in'], wireHWND, 'hwndMDI')),
COMMETHOD([], HRESULT, 'UnregisterTab',
(['in'], c_int, 'hwndTab')),
COMMETHOD([], HRESULT, 'SetTabOrder',
(['in'], c_int, 'hwndTab'),
(['in'], c_int, 'hwndInsertBefore')),
COMMETHOD([], HRESULT, 'SetTabActive',
(['in'], c_int, 'hwndTab'),
(['in'], c_int, 'hwndMDI'),
(['in'], TBATFLAG, 'tbatFlags')),
COMMETHOD([], HRESULT, 'ThumbBarAddButtons',
(['in'], c_int, 'hwnd'),
(['in'], c_uint, 'cButtons'),
(['in'], POINTER(tagTHUMBBUTTON), 'pButton')),
COMMETHOD([], HRESULT, 'ThumbBarUpdateButtons',
(['in'], c_int, 'hwnd'),
(['in'], c_uint, 'cButtons'),
(['in'], POINTER(tagTHUMBBUTTON), 'pButton')),
COMMETHOD([], HRESULT, 'ThumbBarSetImageList',
(['in'], c_int, 'hwnd'),
(['in'], POINTER(IUnknown), 'himl')),
COMMETHOD([], HRESULT, 'SetOverlayIcon',
(['in'], c_int, 'hwnd'),
(['in'], POINTER(IUnknown), 'hIcon'),
(['in'], WSTRING, 'pszDescription')),
COMMETHOD([], HRESULT, 'SetThumbnailTooltip',
(['in'], c_int, 'hwnd'),
(['in'], WSTRING, 'pszTip')),
COMMETHOD([], HRESULT, 'SetThumbnailClip',
(['in'], c_int, 'hwnd'),
(['in'], POINTER(tagRECT), 'prcClip'))]
assert sizeof(tagTHUMBBUTTON) in [540, 552], sizeof(tagTHUMBBUTTON)
assert alignment(tagTHUMBBUTTON) in [4, 8], alignment(tagTHUMBBUTTON)
def consume_events():
""" consume pending events """
while Gtk.events_pending():
Gtk.main_iteration()
# based on http://stackoverflow.com/a/1744503/905256
class gPodderExtension:
def __init__(self, container):
self.container = container
self.window_handle = None
self.restart_warning = True
def on_load(self):
self.taskbar = client.CreateObject(
'{56FDF344-FD6D-11d0-958A-006097C9A090}',
interface=ITaskbarList3)
ret = self.taskbar.HrInit()
if ret != S_OK:
logger.warning("taskbar.HrInit failed: %r", ret)
del self.taskbar
def on_unload(self):
# let the window change state? otherwise gpodder is stuck on exit
# (tested on windows 7 pro)
consume_events()
if self.taskbar is not None:
self.taskbar.SetProgressState(self.window_handle, TBPF_NOPROGRESS)
# let the taskbar change state otherwise gpodder is stuck on exit
# (tested on windows 7 pro)
consume_events()
def on_ui_object_available(self, name, ui_object):
def callback(self, window, *args):
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object]
win_gpointer = ctypes.pythonapi.PyCapsule_GetPointer(window.get_window().__gpointer__, None)
gdkdll = ctypes.CDLL("libgdk-3-0.dll")
self.window_handle = gdkdll.gdk_win32_window_get_handle(win_gpointer)
ret = self.taskbar.ActivateTab(self.window_handle)
if ret != S_OK:
logger.warning("taskbar.ActivateTab failed: %r", ret)
del self.taskbar
if name == 'gpodder-gtk':
ui_object.main_window.connect('realize',
functools.partial(callback, self))
def on_download_progress(self, progress):
if not self.taskbar:
return
if self.window_handle is None:
if not self.restart_warning:
return
logger.warning("No window handle available, a restart max fix this")
self.restart_warning = False
return
if 0 < progress < 1:
self.taskbar.SetProgressState(self.window_handle, TBPF_NORMAL)
self.taskbar.SetProgressValue(self.window_handle,
int(progress * 100), 100)
else:
self.taskbar.SetProgressState(self.window_handle, TBPF_NOPROGRESS)