1# Copyright (C) 2008-2010 Adam Olsen 2# 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation; either version 2, or (at your option) 6# any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software 15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16# 17# 18# The developers of the Exaile media player hereby grant permission 19# for non-GPL compatible GStreamer and Exaile plugins to be used and 20# distributed together with GStreamer and Exaile. This permission is 21# above and beyond the permissions granted by the GPL license by which 22# Exaile is covered. If you modify this code, you may extend this 23# exception to your version of the code, but you are not obligated to 24# do so. If you do not wish to do so, delete this exception statement 25# from your version. 26 27import logging 28 29from gi.repository import Gdk 30from gi.repository import Gtk 31 32from xl import event, player, providers, settings 33from xl.nls import gettext as _ 34from xlgui.widgets.info import TrackToolTip 35from xlgui.widgets import menu, menuitems, playlist, playback 36from xlgui import guiutil 37 38logger = logging.getLogger(__name__) 39 40 41def is_supported(): 42 """ 43 On some platforms (e.g. Linux+Wayland) tray icons are not supported. 44 """ 45 supported = not guiutil.platform_is_wayland() 46 47 if not supported: 48 logger.debug("No tray icon support on this platform") 49 50 return supported 51 52 53def __create_tray_context_menu(): 54 sep = menu.simple_separator 55 items = [] 56 # Play/Pause 57 items.append( 58 playback.PlayPauseMenuItem('playback-playpause', player.PLAYER, after=[]) 59 ) 60 # Next 61 items.append( 62 playback.NextMenuItem('playback-next', player.PLAYER, after=[items[-1].name]) 63 ) 64 # Prev 65 items.append( 66 playback.PrevMenuItem('playback-prev', player.PLAYER, after=[items[-1].name]) 67 ) 68 # Stop 69 items.append( 70 playback.StopMenuItem('playback-stop', player.PLAYER, after=[items[-1].name]) 71 ) 72 # ---- 73 items.append(sep('playback-sep', [items[-1].name])) 74 # Shuffle 75 items.append( 76 playlist.ShuffleModesMenuItem('playlist-mode-shuffle', after=[items[-1].name]) 77 ) 78 # Repeat 79 items.append( 80 playlist.RepeatModesMenuItem('playlist-mode-repeat', after=[items[-1].name]) 81 ) 82 # Dynamic 83 items.append( 84 playlist.DynamicModesMenuItem('playlist-mode-dynamic', after=[items[-1].name]) 85 ) 86 # ---- 87 items.append(sep('playlist-mode-sep', [items[-1].name])) 88 # Rating 89 90 def rating_get_tracks_func(parent, context): 91 current = player.PLAYER.current 92 if current: 93 return [current] 94 else: 95 return [] 96 97 items.append( 98 menuitems.RatingMenuItem('rating', [items[-1].name], rating_get_tracks_func) 99 ) 100 # Remove 101 items.append(playlist.RemoveCurrentMenuItem([items[-1].name])) 102 # ---- 103 items.append(sep('misc-actions-sep', [items[-1].name])) 104 # Quit 105 106 def quit_cb(*args): 107 from xl import main 108 109 main.exaile().quit() 110 111 items.append( 112 menu.simple_menu_item( 113 'quit-application', 114 [items[-1].name], 115 _("_Quit Exaile"), 116 'application-exit', 117 callback=quit_cb, 118 ) 119 ) 120 for item in items: 121 providers.register('tray-icon-context', item) 122 123 124__create_tray_context_menu() 125 126 127class BaseTrayIcon: 128 """ 129 Trayicon base, needs to be derived from 130 """ 131 132 def __init__(self, main): 133 self.main = main 134 self.VOLUME_STEP = 0.05 135 136 self.tooltip = TrackToolTip(self, player.PLAYER) 137 self.tooltip.set_auto_update(True) 138 self.tooltip.set_display_progress(True) 139 140 self.menu = menu.ProviderMenu('tray-icon-context', self) 141 self.update_icon() 142 self.connect_events() 143 event.log_event('tray_icon_toggled', self, True) 144 145 def destroy(self): 146 """ 147 Unhides the window and removes the tray icon 148 """ 149 # FIXME: Allow other windows too 150 if not self.main.window.get_property('visible'): 151 self.main.window.deiconify() 152 self.main.window.present() 153 154 self.disconnect_events() 155 self.set_visible(False) 156 self.tooltip.destroy() 157 event.log_event('tray_icon_toggled', self, False) 158 159 def connect_events(self): 160 """ 161 Connects various callbacks with events 162 """ 163 self.connect('button-press-event', self.on_button_press_event) 164 self.connect('scroll-event', self.on_scroll_event) 165 166 event.add_ui_callback( 167 self.on_playback_change_state, 'playback_player_end', player.PLAYER 168 ) 169 event.add_ui_callback( 170 self.on_playback_change_state, 'playback_track_start', player.PLAYER 171 ) 172 event.add_ui_callback( 173 self.on_playback_change_state, 'playback_toggle_pause', player.PLAYER 174 ) 175 event.add_ui_callback( 176 self.on_playback_change_state, 'playback_error', player.PLAYER 177 ) 178 179 def disconnect_events(self): 180 """ 181 Disconnects various callbacks from events 182 """ 183 event.remove_callback( 184 self.on_playback_change_state, 'playback_player_end', player.PLAYER 185 ) 186 event.remove_callback( 187 self.on_playback_change_state, 'playback_track_start', player.PLAYER 188 ) 189 event.remove_callback( 190 self.on_playback_change_state, 'playback_toggle_pause', player.PLAYER 191 ) 192 event.remove_callback( 193 self.on_playback_change_state, 'playback_error', player.PLAYER 194 ) 195 196 def update_icon(self): 197 """ 198 Updates icon appearance based 199 on current playback state 200 """ 201 if player.PLAYER.current is None: 202 self.set_from_icon_name('exaile') 203 self.set_tooltip(_('Exaile Music Player')) 204 elif player.PLAYER.is_paused(): 205 self.set_from_icon_name('exaile-pause') 206 else: 207 self.set_from_icon_name('exaile-play') 208 209 def set_from_icon_name(self, icon_name): 210 """ 211 Updates the tray icon 212 """ 213 pass 214 215 def set_tooltip(self, tooltip_text): 216 """ 217 Updates the tray icon tooltip 218 """ 219 pass 220 221 def set_visible(self, visible): 222 """ 223 Shows or hides the tray icon 224 """ 225 pass 226 227 def on_button_press_event(self, widget, event): 228 """ 229 Toggles main window visibility and 230 pause as well as opens the context menu 231 """ 232 if event.button == Gdk.BUTTON_PRIMARY: 233 self.main.toggle_visible(bringtofront=True) 234 if event.button == Gdk.BUTTON_MIDDLE: 235 playback.playpause(player.PLAYER) 236 if event.triggers_context_menu(): 237 self.menu.popup_at_pointer(event) 238 239 def on_scroll_event(self, widget, event): 240 """ 241 Changes volume and skips tracks on scroll 242 """ 243 if event.get_state() & Gdk.ModifierType.SHIFT_MASK: 244 if event.direction == Gdk.ScrollDirection.UP: 245 player.QUEUE.prev() 246 elif event.direction == Gdk.ScrollDirection.DOWN: 247 player.QUEUE.next() 248 else: 249 if event.direction == Gdk.ScrollDirection.UP: 250 volume = settings.get_option('player/volume', 1) 251 settings.set_option('player/volume', min(volume + self.VOLUME_STEP, 1)) 252 return True 253 elif event.direction == Gdk.ScrollDirection.DOWN: 254 volume = settings.get_option('player/volume', 1) 255 settings.set_option('player/volume', max(0, volume - self.VOLUME_STEP)) 256 return True 257 elif event.direction == Gdk.ScrollDirection.LEFT: 258 player.QUEUE.prev() 259 elif event.direction == Gdk.ScrollDirection.RIGHT: 260 player.QUEUE.next() 261 262 def on_playback_change_state(self, event, player, current): 263 """ 264 Updates tray icon appearance 265 on playback state change 266 """ 267 self.update_icon() 268 269 270class TrayIcon(Gtk.StatusIcon, BaseTrayIcon): 271 """ 272 Wrapper around GtkStatusIcon 273 """ 274 275 def __init__(self, main): 276 Gtk.StatusIcon.__init__(self) 277 BaseTrayIcon.__init__(self, main) 278 279 def set_tooltip(self, tooltip_text): 280 """ 281 Updates the tray icon tooltip 282 """ 283 self.set_tooltip_text(tooltip_text) 284