1# Copyright 2014,2016 Nick Boultbee 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 of the License, or 6# (at your option) any later version. 7 8import os 9 10from senf import fsn2text, uri2fsn 11 12from quodlibet import C_, _ 13from quodlibet.util.dprint import print_, print_e 14from quodlibet.remote import Remote, RemoteError 15 16 17def exit_(status=None, notify_startup=False): 18 """Call this to abort the startup before any mainloop starts. 19 20 notify_startup needs to be true if QL could potentially have been 21 called from the desktop file. 22 """ 23 24 if notify_startup: 25 import gi 26 gi.require_version("Gdk", "3.0") 27 from gi.repository import Gdk 28 Gdk.notify_startup_complete() 29 raise SystemExit(status) 30 31 32def is_running(): 33 """If maybe is another instance running""" 34 35 return Remote.remote_exists() 36 37 38def control(command, arg=None, ignore_error=False): 39 """Sends command to the existing instance if possible and exits. 40 41 Will print any response it gets to stdout. 42 43 Does not return except if ignore_error is True and sending 44 the command failed. 45 """ 46 47 if not is_running(): 48 if ignore_error: 49 return 50 exit_(_("Quod Libet is not running (add '--run' to start it)"), 51 notify_startup=True) 52 return 53 54 message = command 55 if arg is not None: 56 message += " " + arg 57 58 try: 59 response = Remote.send_message(message) 60 except RemoteError as e: 61 if ignore_error: 62 return 63 exit_(str(e), notify_startup=True) 64 else: 65 if response is not None: 66 print_(response, end="", flush=True) 67 exit_(notify_startup=True) 68 69 70def process_arguments(argv): 71 from quodlibet.util.path import uri_is_valid 72 from quodlibet import util 73 from quodlibet import const 74 75 actions = [] 76 controls = ["next", "previous", "play", "pause", "play-pause", "stop", 77 "hide-window", "show-window", "toggle-window", 78 "focus", "quit", "unfilter", "refresh", "force-previous"] 79 controls_opt = ["seek", "repeat", "query", "volume", "filter", 80 "rating", "set-browser", "open-browser", "shuffle", 81 "queue", "stop-after", "random", "repeat-type", 82 "shuffle-type", "add-location"] 83 84 options = util.OptionParser( 85 "Quod Libet", const.VERSION, 86 _("a music library and player"), 87 _("[option]")) 88 89 options.add("print-playing", help=_("Print the playing song and exit")) 90 options.add("start-playing", help=_("Begin playing immediately")) 91 options.add("start-hidden", help=_("Don't show any windows on start")) 92 93 for opt, help in [ 94 ("next", _("Jump to next song")), 95 ("previous", 96 _("Jump to previous song or restart if near the beginning")), 97 ("force-previous", _("Jump to previous song")), 98 ("play", _("Start playback")), 99 ("pause", _("Pause playback")), 100 ("play-pause", _("Toggle play/pause mode")), 101 ("stop", _("Stop playback")), 102 ("volume-up", _("Turn up volume")), 103 ("volume-down", _("Turn down volume")), 104 ("rating-up", _("Increase rating of playing song by one star")), 105 ("rating-down", _("Decrease rating of playing song by one star")), 106 ("status", _("Print player status")), 107 ("hide-window", _("Hide main window")), 108 ("show-window", _("Show main window")), 109 ("toggle-window", _("Toggle main window visibility")), 110 ("focus", _("Focus the running player")), 111 ("unfilter", _("Remove active browser filters")), 112 ("refresh", _("Refresh and rescan library")), 113 ("list-browsers", _("List available browsers")), 114 ("print-playlist", _("Print the current playlist")), 115 ("print-queue", _("Print the contents of the queue")), 116 ("print-query-text", _("Print the active text query")), 117 ("no-plugins", _("Start without plugins")), 118 ("run", _("Start Quod Libet if it isn't running")), 119 ("quit", _("Exit Quod Libet")), 120 ]: 121 options.add(opt, help=help) 122 123 for opt, help, arg in [ 124 ("seek", _("Seek within the playing song"), _("[+|-][HH:]MM:SS")), 125 ("shuffle", _("Set or toggle shuffle mode"), "0|1|t"), 126 ("shuffle-type", _("Set shuffle mode type"), "random|weighted|off"), 127 ("repeat", _("Turn repeat off, on, or toggle it"), "0|1|t"), 128 ("repeat-type", _("Set repeat mode type"), "current|all|one|off"), 129 ("volume", _("Set the volume"), "[+|-]0..100"), 130 ("query", _("Search your audio library"), _("query")), 131 ("play-file", _("Play a file"), C_("command", "filename")), 132 ("rating", _("Set rating of playing song"), "[+|-]0.0..1.0"), 133 ("set-browser", _("Set the current browser"), "BrowserName"), 134 ("stop-after", _("Stop after the playing song"), "0|1|t"), 135 ("open-browser", _("Open a new browser"), "BrowserName"), 136 ("queue", _("Show or hide the queue"), "on|off|t"), 137 ("random", _("Filter on a random value"), C_("command", "tag")), 138 ("filter", _("Filter on a tag value"), _("tag=value")), 139 ("enqueue", _("Enqueue a file or query"), "%s|%s" % ( 140 C_("command", "filename"), _("query"))), 141 ("enqueue-files", _("Enqueue comma-separated files"), "%s[,%s..]" % ( 142 _("filename"), _("filename"))), 143 ("print-query", _("Print filenames of results of query to stdout"), 144 _("query")), 145 ("unqueue", _("Unqueue a file or query"), "%s|%s" % ( 146 C_("command", "filename"), _("query"))), 147 ("add-location", _("Add a file or directory to the library"), 148 _("location")), 149 ]: 150 options.add(opt, help=help, arg=arg) 151 152 options.add("sm-config-prefix", arg="dummy") 153 options.add("sm-client-id", arg="prefix") 154 options.add("screen", arg="dummy") 155 156 def is_vol(str): 157 if len(str) == 1 and str[0] in '+-': 158 return True 159 return is_float(str) 160 161 def is_rate(str): 162 if len(str) == 1 and str[0] in '+-': 163 return True 164 return is_float(str) 165 166 def is_time(str): 167 if str[0] not in "+-0123456789": 168 return False 169 elif str[0] in "+-": 170 str = str[1:] 171 parts = str.split(":") 172 if len(parts) > 3: 173 return False 174 else: 175 return not (False in [p.isdigit() for p in parts]) 176 177 def is_float(str): 178 try: 179 float(str) 180 except ValueError: 181 return False 182 else: 183 return True 184 185 validators = { 186 "shuffle": ["0", "1", "t", "on", "off", "toggle"].__contains__, 187 "shuffle-type": ["random", "weighted", "off", "0"].__contains__, 188 "repeat": ["0", "1", "t", "on", "off", "toggle"].__contains__, 189 "repeat-type": ["current", "all", "one", "off", "0"].__contains__, 190 "volume": is_vol, 191 "seek": is_time, 192 "rating": is_rate, 193 "stop-after": ["0", "1", "t"].__contains__, 194 } 195 196 cmds_todo = [] 197 198 def queue(*args): 199 cmds_todo.append(args) 200 201 # XXX: to make startup work in case the desktop file isn't passed 202 # a file path/uri 203 if argv[-1] == "--play-file": 204 argv = argv[:-1] 205 206 opts, args = options.parse(argv[1:]) 207 208 for command, arg in opts.items(): 209 if command in controls: 210 queue(command) 211 elif command in controls_opt: 212 if command in validators and not validators[command](arg): 213 print_e(_("Invalid argument for '%s'.") % command) 214 print_e(_("Try %s --help.") % fsn2text(argv[0])) 215 exit_(True, notify_startup=True) 216 else: 217 queue(command, arg) 218 elif command == "status": 219 queue("status") 220 elif command == "print-playlist": 221 queue("dump-playlist") 222 elif command == "print-queue": 223 queue("dump-queue") 224 elif command == "list-browsers": 225 queue("dump-browsers") 226 elif command == "volume-up": 227 queue("volume +") 228 elif command == "volume-down": 229 queue("volume -") 230 elif command == "rating-up": 231 queue("rating +") 232 elif command == "rating-down": 233 queue("rating -") 234 elif command == "enqueue" or command == "unqueue": 235 try: 236 filename = uri2fsn(arg) 237 except ValueError: 238 filename = arg 239 queue(command, filename) 240 elif command == "enqueue-files": 241 queue(command, arg) 242 elif command == "play-file": 243 if uri_is_valid(arg) and arg.startswith("quodlibet://"): 244 # TODO: allow handling of URIs without --play-file 245 queue("uri-received", arg) 246 else: 247 try: 248 filename = uri2fsn(arg) 249 except ValueError: 250 filename = arg 251 filename = os.path.abspath(util.path.expanduser(arg)) 252 queue("play-file", filename) 253 elif command == 'add-location': 254 try: 255 path = uri2fsn(arg) 256 except ValueError: 257 path = arg 258 path = os.path.abspath(util.path.expanduser(arg)) 259 queue("add-location", path) 260 elif command == "print-playing": 261 try: 262 queue("print-playing", args[0]) 263 except IndexError: 264 queue("print-playing") 265 elif command == "print-query": 266 queue(command, arg) 267 elif command == "print-query-text": 268 queue(command) 269 elif command == "start-playing": 270 actions.append(command) 271 elif command == "start-hidden": 272 actions.append(command) 273 elif command == "no-plugins": 274 actions.append(command) 275 elif command == "run": 276 actions.append(command) 277 278 if cmds_todo: 279 for cmd in cmds_todo: 280 control(*cmd, **{"ignore_error": "run" in actions}) 281 else: 282 # this will exit if it succeeds 283 control('focus', ignore_error=True) 284 285 return actions, cmds_todo 286