1 # -*- coding: utf-8 -*-
3 # gPodder - A media aggregator and podcast client
4 # Copyright (c) 2005-2018 The gPodder Team
6 # gPodder is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # gPodder is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 # Notification implementation for Windows
21 # Sean Munkel; 2012-12-29
23 Current state (2018/07/29 ELL):
24 - I can't get pywin32 to work in msys2 (the platform used for this python3/gtk3 installer)
25 so existing code using COM doesn't work.
26 - Gio.Notification is not implemented on windows yet.
27 see https://bugzilla.gnome.org/show_bug.cgi?id=776583
28 - Gtk.StatusIcon with a context works but is deprecated. Showing a balloon using set_tooltip_markup
30 See https://github.com/afiskon/py-gtk-example
31 - hexchat have implemented a solid c++ solution.
32 See https://github.com/hexchat/hexchat/tree/master/src/fe-gtk/notifications
33 I've chosen to implement notifications by calling a PowerShell script invoking
34 Windows Toast Notification API or Balloon Notification as fallback.
35 It's tested on Win7 32bit and Win10 64bit VMs from modern.ie
36 So we have a working solution until Gio.Notification is implemented on Windows.
47 logger
= logging
.getLogger(__name__
)
50 __title__
= _('Notification Bubbles for Windows')
51 __description__
= _('Display notification bubbles for different events.')
52 __authors__
= 'Sean Munkel <SeanMunkel@gmail.com>'
53 __category__
= 'desktop-integration'
54 __mandatory_in__
= 'win32'
55 __only_for__
= 'win32'
58 class gPodderExtension(object):
59 def __init__(self
, *args
):
60 gpodder_script
= sys
.argv
[0]
61 gpodder_script
= os
.path
.realpath(gpodder_script
)
62 self
._icon
= os
.path
.join(os
.path
.dirname(gpodder_script
), "gpodder.ico")
64 def on_notification_show(self
, title
, message
):
67 if ([Environment]::OSVersion.Version -ge (new-object 'Version' 10,0,10240)) {{
68 # use Windows 10 Toast notification
69 [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
70 [Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
71 [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
72 # Need a real AppID (see https://stackoverflow.com/q/46814858)
73 # use gPodder app id if it's the installed, otherwise use PowerShell's AppID
75 $gpo_appid = Get-StartApps -Name "gpodder"
77 write-host "Get-StartApps not available"
80 if ($gpo_appid -ne $null) {{
81 $APP_ID = $gpo_appid[0].AppID
83 $APP_ID = '{{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}}\\WindowsPowerShell\\v1.0\\powershell.exe'
86 <toast activationType="protocol" launch="" duration="long">
88 <binding template="ToastGeneric">
89 <image placement="appLogoOverride" src="{icon}" />
90 <text><![CDATA[{title}]]></text>
91 <text><![CDATA[{message}]]></text>
94 <audio silent="true" />
97 $xml = New-Object Windows.Data.Xml.Dom.XmlDocument
98 $xml.LoadXml($template)
99 $toast = New-Object Windows.UI.Notifications.ToastNotification $xml
100 [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($APP_ID).Show($toast)
101 Remove-Item -LiteralPath $MyInvocation.MyCommand.Path -Force # Delete this script temp file.
103 # use older Balloon notification when not on Windows 10
104 [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
105 $o = New-Object System.Windows.Forms.NotifyIcon
108 $o.BalloonTipIcon = "None"
109 $o.BalloonTipText = @"
112 $o.BalloonTipTitle = @"
117 $Delay = 10 # Delay value in seconds.
118 $o.ShowBalloonTip($Delay*1000)
119 Start-Sleep -s $Delay
121 Remove-Item -LiteralPath $MyInvocation.MyCommand.Path -Force # Delete this script temp file.
124 write-host "Caught an exception:"
125 write-host "Exception Type: $($_.Exception.GetType().FullName)"
126 write-host "Exception Message: $($_.Exception.Message)"
129 """.format(icon
=self
._icon
, message
=message
, title
=title
)
130 fh
, path
= tempfile
.mkstemp(suffix
=".ps1")
131 with
open(fh
, "w", encoding
="utf_8_sig") as f
:
134 # hide powershell command window using startupinfo
135 startupinfo
= subprocess
.STARTUPINFO()
136 startupinfo
.dwFlags
= subprocess
.CREATE_NEW_CONSOLE | subprocess
.STARTF_USESHOWWINDOW
137 startupinfo
.wShowWindow
= subprocess
.SW_HIDE
138 # to run 64bit powershell on Win10 64bit when running from 32bit gPodder
139 # (we need 64bit powershell on Win10 otherwise Get-StartApps is not available)
140 powershell
= r
"{}\sysnative\WindowsPowerShell\v1.0\powershell.exe".format(os
.environ
["SystemRoot"])
141 if not os
.path
.exists(powershell
):
142 powershell
= "powershell.exe"
143 subprocess
.Popen([powershell
,
144 "-ExecutionPolicy", "Bypass", "-File", path
],
145 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
146 startupinfo
=startupinfo
)
147 except subprocess
.CalledProcessError
as e
:
148 logger
.error("Error in on_notification_show(title=%r, message=%r):\n"
149 "\t%r exit code %i\n\tstdout=%s\n\tstderr=%s",
150 title
, message
, e
.cmd
, e
.returncode
, e
.stdout
, e
.stderr
)
151 except FileNotFoundError
:
152 logger
.error("Error in on_notification_show(title=%r, message=%r): %s not found",
153 title
, message
, powershell
)