diff --git a/bin/mkchromecast b/bin/mkchromecast index eca56583..12aabf45 100755 --- a/bin/mkchromecast +++ b/bin/mkchromecast @@ -16,12 +16,11 @@ import mkchromecast from mkchromecast.version import __version__ from mkchromecast.audio_devices import (inputint, inputdev, outputdev, outputint) -from mkchromecast.cast import Casting -import mkchromecast.colors as colors +from mkchromecast import cast +from mkchromecast import colors from mkchromecast.constants import OpMode from mkchromecast.pulseaudio import create_sink, get_sink_list, remove_sink from mkchromecast.utils import terminate, checkmktmp, writePidFile -from mkchromecast.messages import print_available_devices def maybe_execute_single_action(mkcc: mkchromecast.Mkchromecast): @@ -51,10 +50,10 @@ class CastProcess(object): self.mkcc = mkcc # Type declarations - self.cc: Casting + self.cc: cast.Casting def run(self): - self.cc = Casting(self.mkcc) + self.cc = cast.Casting(self.mkcc) checkmktmp() writePidFile() diff --git a/mkchromecast/cast.py b/mkchromecast/cast.py index f699a116..39e77001 100644 --- a/mkchromecast/cast.py +++ b/mkchromecast/cast.py @@ -1,12 +1,13 @@ # This file is part of mkchromecast. +import dataclasses import os import pickle import socket import subprocess from threading import Thread import time -from typing import Any, Optional +from typing import Any, Iterable, Optional import mkchromecast from mkchromecast import colors @@ -15,32 +16,53 @@ from mkchromecast.constants import OpMode from mkchromecast.utils import terminate, checkmktmp from mkchromecast.pulseaudio import remove_sink -from mkchromecast.messages import print_available_devices from mkchromecast.version import __version__ """ We verify that soco is installed to give Sonos support """ +has_sonos: bool try: import soco - sonos = True + has_sonos = True except ImportError: - sonos = False + has_sonos = False """ We verify that pychromecast is installed """ +has_chromecast: bool try: import pychromecast - chromecast = True + has_chromecast = True except ImportError: - chromecast = False + has_chromecast = False -class Casting(object): +@dataclasses.dataclass +class AvailableDevice: + index: int + name: str + type: str + + def __str__(self): + return f"{self.index} \t{self.type} \t{self.name}" + + +def print_available_devices(devices: Iterable[AvailableDevice]): + """Prints a list of available devices.""" + print(colors.important("List of Devices Available in Network:")) + print(colors.important("-------------------------------------\n")) + print(colors.important("Index Type Friendly Name ")) + print(colors.important("===== ===== ============= ")) + for device in devices: + print(device) + + +class Casting: """Main casting class.""" def __init__(self, mkcc: mkchromecast.Mkchromecast): @@ -50,19 +72,10 @@ def __init__(self, mkcc: mkchromecast.Mkchromecast): self.ip = utils.get_effective_ip(self.mkcc.platform, host_override=self.mkcc.host) - self.cast: Optional[Any] = None - self.sonos: Optional[Any] = None - - def _get_chromecasts(self): - # TODO(xsdg): Drop backwards compatibility with old versions of - # pychromecast - - # compatibility - try: - return list(pychromecast.get_chromecasts_as_dict().keys()) - except AttributeError: - pass + self.cast: Optional[pychromecast.Chromecast] = None + self._chromecasts_by_name: dict[str, pychromecast.Chromecast] + def _get_chromecast_names(self) -> list[str]: _chromecasts = pychromecast.get_chromecasts(tries=self.mkcc.tries) # since PR380, pychromecast.get_chromecasts returns a tuple @@ -74,13 +87,6 @@ def _get_chromecasts(self): return list(self._chromecasts_by_name.keys()) - def _get_chromecast(self, name): - # compatibility - try: - return pychromecast.get_chromecast(friendly_name=self.cast_to) - except AttributeError: - return self._chromecasts_by_name[name] - """ Cast processes """ @@ -89,27 +95,396 @@ def initialize_cast(self): # This fixes the `No handlers could be found for logger # "pychromecast.socket_client` warning"`. # See commit 18005ebd4c96faccd69757bf3d126eb145687e0d. - if chromecast: - from pychromecast import socket_client + from pychromecast import socket_client - self.cclist = self._get_chromecasts() - self.cclist = [[i, _, "Gcast"] for i, _ in enumerate(self.cclist)] - else: - self.cclist = [] + tmp_cclist = self._get_chromecast_names() + self.cclist = [[i, name, "Gcast"] for i, name in enumerate(tmp_cclist)] + + if self.mkcc.debug is True: + print("self.cclist", self.cclist) + + if ( + len(self.cclist) != 0 + and self.mkcc.select_device is False + and self.mkcc.device_name is None + ): + if self.mkcc.debug is True: + print("if len(self.cclist) != 0 and self.mkcc.select_device == False:") + print(" ") + print_available_devices(self.available_devices) + print(" ") + if self.mkcc.operation != OpMode.DISCOVER: + print(colors.important("Casting to first device shown above!")) + print(colors.important("Select devices by using the -s flag.")) + print(" ") + self.cast_to = self.cclist[0][1] + print(colors.success(self.cast_to)) + print(" ") + + elif ( + len(self.cclist) != 0 + and self.mkcc.select_device is True + and self.mkcc.operation != OpMode.TRAY + and self.mkcc.device_name is None + ): + if self.mkcc.debug is True: + print( + "elif len(self.cclist) != 0 and self.mkcc.select_device == True" + " and self.mkcc.tray == False:" + ) + if os.path.exists("/tmp/mkchromecast.tmp") is False: + self.tf = open("/tmp/mkchromecast.tmp", "wb") + print(" ") + print_available_devices(self.available_devices) + else: + if self.mkcc.debug is True: + print("else:") + self.tf = open("/tmp/mkchromecast.tmp", "rb") + self.index = pickle.load(self.tf) + self.cast_to = self.cclist[int(self.index)] + print(" ") + print( + colors.options("Casting to:") + " " + colors.success(self.cast_to) + ) + print(" ") + + elif len(self.cclist) != 0 and self.mkcc.select_device and self.mkcc.operation == OpMode.TRAY: + if self.mkcc.debug is True: + print( + "elif len(self.cclist) != 0 and self.mkcc.select_device == True" + " and self.mkcc.tray == True:" + ) + if os.path.exists("/tmp/mkchromecast.tmp") is False: + self.tf = open("/tmp/mkchromecast.tmp", "wb") + print(" ") + print_available_devices(self.available_devices) + else: + if self.mkcc.debug is True: + print("else:") + self.tf = open("/tmp/mkchromecast.tmp", "rb") + self.cast_to = pickle.load(self.tf) + print_available_devices(self.available_devices) + print(" ") + print( + colors.options("Casting to:") + " " + colors.success(self.cast_to) + ) + print(" ") + + elif len(self.cclist) == 0 and self.mkcc.operation != OpMode.TRAY: + if self.mkcc.debug is True: + print("elif len(self.cclist) == 0 and self.mkcc.tray == False:") + print(colors.error("No devices found!")) + if self.mkcc.platform == "Linux" and self.mkcc.adevice is None: + remove_sink() + elif self.mkcc.platform == "Darwin": + inputint() + outputint() + terminate() + exit() + + elif len(self.cclist) == 0 and self.mkcc.operation == OpMode.TRAY: + print(colors.error(":::Tray::: No devices found!")) - if sonos: + def select_a_device(self): + print(" ") + print( + "Please, select the " + + colors.important("Index") + + " of the Google Cast device that you want to use:" + ) + self.index = input() + + def input_device(self, write_to_pickle=True): + while True: try: - # Checking groups - zone = soco.discovery.any_soco() + if write_to_pickle: + pickle.dump(self.index, self.tf) + self.tf.close() + self.cast_to = self.cclist[int(self.index)][1] + print(" ") + print( + colors.options("Casting to:") + " " + colors.success(self.cast_to) + ) + print(" ") + except TypeError: + print( + colors.options("Casting to:") + + " " + + colors.success(self.cast_to.player_name) + ) + except IndexError: + checkmktmp() + self.tf = open("/tmp/mkchromecast.tmp", "wb") + # TODO(xsdg): The original code had what was likely a typo here, + # in that this called `self.select_device()`, which did not + # exist. It likely was supposed to be `self.select_a_device()`, + # but it's better to just start over, here. + raise Exception( + "Internal error: Never worked; needs to be fixed.") + self.mkcc.select_device() + continue + break - self.sonos_list = zone.all_groups + def get_devices(self): + if self.mkcc.debug is True: + print("def get_devices(self):") + + if self.mkcc.device_name: + self.cast_to = self.mkcc.device_name + + if self.cast_to not in self._chromecasts_by_name: + self.cast = None + print(colors.warning(f"No Chromecast found named {self.cast_to}")) + + if self.mkcc.platform == "Darwin": + inputint() + outputint() + elif self.mkcc.platform == "Linux": + remove_sink() + + # In the case that the tray is used, we don't kill the + # application + if self.mkcc.operation == OpMode.TRAY: + return + + print(colors.error("Finishing the application...")) + terminate() + exit() - for self.index, group in enumerate(self.sonos_list): - add_sonos = [self.index, group.coordinator, "Sonos"] - self.cclist.append(add_sonos) - except (TypeError, AttributeError): + self.cast = self._chromecasts_by_name[self.cast_to] + # Wait for cast device to be ready + self.cast.wait() + print() + print( + colors.important("Status of device ") + + " " + + colors.success(self.cast_to) + ) + print() + print(self.cast.status) + print() + + def play_cast(self): + if self.mkcc.debug is True: + print("def play_cast(self):") + if not self.cast: + print(colors.warning("Calling get_devices before proceeding with play_cast")) + self.get_devices() + if not self.cast: + raise Exception("Internal error, self.cast was not set.") + localip = self.ip + + try: + print( + colors.options("The IP of ") + + colors.success(self.cast_to) + + colors.options(" is:") + + " " + + self.cast.socket_client.host # valid since at least v3.0.0 + ) + except TypeError: + print( + colors.options("The IP of ") + + colors.success(self.cast_to.player_name) + + colors.options(" is:") + + " " + + self.cast_to.ip_address + ) + + if self.mkcc.host is None: + print(colors.options("Your local IP is:") + " " + localip) + else: + print(colors.options("Your manually entered local IP is:") + " " + localip) + + media_controller = self.cast.media_controller + + # Set up the mime type and conditionally import video or audio + # TODO(xsdg): Get rid of these conditional imports. + media_type: str + if self.mkcc.videoarg: + import mkchromecast.video + + # TODO(xsdg): Centralize media type storage in some way. + # In mkcc? + media_type = self.mkcc.mtype or "video/mp4" + else: + import mkchromecast.audio + + media_type = mkchromecast.audio.media_type + print(" ") + print(colors.options("Using media type:") + f" {media_type}") + + play_url: str + if self.mkcc.operation == OpMode.SOURCE_URL: + play_url = self.mkcc.source_url + print(colors.options("Casting from stream URL:") + + f" {play_url}") + else: + play_url = f"http://{localip}:{self.mkcc.port}/stream" + + media_controller.play_media( + play_url, media_type, title=self.title, stream_type="LIVE", + ) + + if media_controller.is_active: + media_controller.play() + + print(" ") + print(colors.important("Cast media controller status")) + print(" ") + print(self.cast.status) + print(" ") + + time.sleep(5.0) + media_controller.play() + + if self.mkcc.hijack is True: + self.r = Thread(target=self.hijack_cc) + # This has to be set to True so that we catch + # KeyboardInterrupt. + self.r.daemon = True + self.r.start() + + def pause(self): + """Pause casting""" + if not self.cast: + raise Exception("Internal error: not initialized.") + media_controller = self.cast.media_controller + media_controller.pause() + + def play(self): + """Play casting""" + if not self.cast: + raise Exception("Internal error: not initialized.") + media_controller = self.cast.media_controller + media_controller.play() + + def stop_cast(self): + if self.cast: + self.cast.quit_app() + + def volume_up(self): + """Increment volume by 0.1 unless it is already maxed. + Returns the new volume. + """ + if not self.cast: + raise Exception("Internal error: not initialized.") + if self.mkcc.debug is True: + print("Increasing volume... \n") + volume = round(self.cast.status.volume_level, 1) + return self.cast.set_volume(volume + 0.1) + + def volume_down(self): + """Decrement the volume by 0.1 unless it is already 0. + Returns the new volume. + """ + if not self.cast: + raise Exception("Internal error: not initialized.") + if self.mkcc.debug is True: + print("Decreasing volume... \n") + volume = round(self.cast.status.volume_level, 1) + return self.cast.set_volume(volume - 0.1) + + @property + def available_devices(self) -> list[AvailableDevice]: + """The list of available devices.""" + devices: list[AvailableDevice] = [] + for device_index, (_, name, type_) in enumerate(self.cclist): + devices.append(AvailableDevice(device_index, name, type_)) + + return devices + + def hijack_cc(self): + """Dummy method to call _hijack_cc_(). + + In the cast that the self.r thread is alive, we check that the + chromecast is connected. If it is connected, we check again in + 5 seconds. + """ + try: + while self.r.is_alive(): + self._hijack_cc_() + # FIXME: I think that this has to be set by users. + time.sleep(5) + except KeyboardInterrupt: + self.stop_cast() + if self.mkcc.platform == "Darwin": + inputint() + outputint() + elif self.mkcc.platform == "Linux" and self.mkcc.adevice is None: + remove_sink() + terminate() + + def _hijack_cc_(self): + """Check if chromecast is disconnected and hijack. + + This function checks if the chromecast is online. Then, if the display + name is different from "Default Media Receiver", it hijacks to the + chromecast. + """ + if not self.cast: + raise Exception("Internal error: not initialized.") + + ip = self.cast.socket_client.host # valid since at least v3.0.0 + + if ping_chromecast(ip) is True: # The chromecast is online. + if str(self.cast.status.display_name) != "Default Media Receiver": + self.mkcc.device_name = self.cast_to + self.get_devices() + self.play_cast() + else: # The chromecast is offline. + try: + self.mkcc.device_name = self.cast_to + self.get_devices() + self.play_cast() + except AttributeError: pass + +def ping_chromecast(ip): + """This function pings to hosts. + + Credits: http://stackoverflow.com/a/34455969/1995261 + """ + try: + subprocess.check_output("ping -c 1 " + ip, shell=True) + except: + return False + return True + + +class _DisabledSonosCasting: + """Half-hearted attempt at refactoring Sonos support into its own class. + + This is broken, but should simplify the Chromecast support code until the + Sonos support can be unbroken at some later point. + """ + + def __init__(self, mkcc: mkchromecast.Mkchromecast): + self.mkcc = mkcc + + self.title = "Mkchromecast v" + __version__ + + self.ip = utils.get_effective_ip(self.mkcc.platform, host_override=self.mkcc.host) + + self.sonos: Optional[Any] = None + + """ + Cast processes + """ + + def initialize_cast(self): + self.cclist: list[Any] = [] + if has_sonos: + # Checking groups + zone = soco.discovery.any_soco() + + self.sonos_list = zone.all_groups + + for self.index, group in enumerate(self.sonos_list): + add_sonos = [self.index, group.coordinator, "Sonos"] + self.cclist.append(add_sonos) + if self.mkcc.debug is True: print("self.cclist", self.cclist) @@ -242,7 +617,7 @@ def input_device(self, write_to_pickle=True): def get_devices(self): if self.mkcc.debug is True: print("def get_devices(self):") - if chromecast: + if has_chromecast: try: if self.mkcc.device_name is not None: self.cast_to = self.mkcc.device_name @@ -295,101 +670,32 @@ def play_cast(self): print("def play_cast(self):") localip = self.ip - try: - print( - colors.options("The IP of ") - + colors.success(self.cast_to) - + colors.options(" is:") - + " " - + self.cast.socket_client.host # valid since at least v3.0.0 - ) - except TypeError: - print( - colors.options("The IP of ") - + colors.success(self.cast_to.player_name) - + colors.options(" is:") - + " " - + self.cast_to.ip_address - ) - except AttributeError: # what AttributeError is being expected? - for _ in self.sonos_list: - if self.cast_to == _.player_name: - self.cast_to = _ - print( - colors.options("The IP of ") - + colors.success(self.cast_to.player_name) - + colors.options(" is:") - + " " - + self.cast_to.ip_address - ) + for sonos_target in self.sonos_list: + if self.cast_to == sonos_target.player_name: + self.cast_to = sonos_target + print( + colors.options("The IP of ") + + colors.success(self.cast_to.player_name) + + colors.options(" is:") + + " " + + self.cast_to.ip_address + ) if self.mkcc.host is None: print(colors.options("Your local IP is:") + " " + localip) else: print(colors.options("Your manually entered local IP is:") + " " + localip) - try: - media_controller = self.cast.media_controller - - # Set up the mime type and conditionally import video or audio - # TODO(xsdg): Get rid of these conditional imports. - media_type: str - if self.mkcc.videoarg: - import mkchromecast.video - - # TODO(xsdg): Centralize media type storage in some way. - # In mkcc? - media_type = self.mkcc.mtype or "video/mp4" - else: - import mkchromecast.audio - - media_type = mkchromecast.audio.media_type - print(" ") - print(colors.options("Using media type:") + f" {media_type}") - - play_url: str - if self.mkcc.operation == OpMode.SOURCE_URL: - play_url = self.mkcc.source_url - print(colors.options("Casting from stream URL:") - + f" {play_url}") - else: - play_url = f"http://{localip}:{self.mkcc.port}/stream" - - media_controller.play_media( - play_url, media_type, title=self.title, stream_type="LIVE", - ) - - if media_controller.is_active: - media_controller.play() - - print(" ") - print(colors.important("Cast media controller status")) - print(" ") - print(self.cast.status) - print(" ") - - time.sleep(5.0) - media_controller.play() - - if self.mkcc.hijack is True: - self.r = Thread(target=self.hijack_cc) - # This has to be set to True so that we catch - # KeyboardInterrupt. - self.r.daemon = True - self.r.start() - - # TODO(xsdg): This isn't an appropriate exception-handling strategy. - except AttributeError: - raise Exception("Internal error: This code path is broken and " - "needs to be fixed.") - self.sonos = self.cast_to - self.sonos.play_uri( - "x-rincon-mp3radio://" + localip + ":" + self.mkcc.port + "/stream", - title=self.title, - ) - if self.mkcc.operation == OpMode.TRAY: - # TODO(xsdg): No. - self.cast = self.sonos + raise Exception("Internal error: This code path is broken and " + "needs to be fixed.") + self.sonos = self.cast_to + self.sonos.play_uri( + "x-rincon-mp3radio://" + localip + ":" + self.mkcc.port + "/stream", + title=self.title, + ) + if self.mkcc.operation == OpMode.TRAY: + # TODO(xsdg): No. + self.cast = self.sonos def pause(self): """Pause casting""" @@ -402,8 +708,6 @@ def play(self): media_controller.play() def stop_cast(self): - if self.cast: - self.cast.quit_app() if self.sonos: self.sonos.stop() @@ -413,12 +717,8 @@ def volume_up(self): """ if self.mkcc.debug is True: print("Increasing volume... \n") - try: - volume = round(self.cast.status.volume_level, 1) - return self.cast.set_volume(volume + 0.1) - except AttributeError: - self.sonos.volume += 1 - self.sonos.play() + self.sonos.volume += 1 + self.sonos.play() def volume_down(self): """Decrement the volume by 0.1 unless it is already 0. @@ -426,31 +726,8 @@ def volume_down(self): """ if self.mkcc.debug is True: print("Decreasing volume... \n") - try: - volume = round(self.cast.status.volume_level, 1) - return self.cast.set_volume(volume - 0.1) - except AttributeError: - self.sonos.volume -= 1 - self.sonos.play() - - def reboot(self): - try: - from pychromecast.dial import reboot - except ImportError: - # reboot is removed from pychromecast.dial since PR394 - # see: https://github.com/home-assistant-libs/pychromecast/pull/394 - print( - colors.warning( - "This version of pychromecast does not support reboot. Will do nothing." - ) - ) - reboot = lambda x: None - - if self.mkcc.platform == "Darwin": - self.cast.host = socket.gethostbyname(self.cast_to + ".local") - reboot(self.cast.host) - else: - print(colors.error("This method is not supported in Linux yet.")) + self.sonos.volume -= 1 + self.sonos.play() # TOOD(xsdg): Unclear how this works, but the self.available_devices method # and the self.available_devices attribute are in obvious conflict. @@ -481,59 +758,3 @@ def available_devices(self): self.available_devices.append(to_append) return self.available_devices - - def hijack_cc(self): - """Dummy method to call _hijack_cc_(). - - In the cast that the self.r thread is alive, we check that the - chromecast is connected. If it is connected, we check again in - 5 seconds. - """ - try: - while self.r.is_alive(): - self._hijack_cc_() - # FIXME: I think that this has to be set by users. - time.sleep(5) - except KeyboardInterrupt: - self.stop_cast() - if self.mkcc.platform == "Darwin": - inputint() - outputint() - elif self.mkcc.platform == "Linux" and self.mkcc.adevice is None: - remove_sink() - terminate() - - def _hijack_cc_(self): - """Check if chromecast is disconnected and hijack. - - This function checks if the chromecast is online. Then, if the display - name is different from "Default Media Receiver", it hijacks to the - chromecast. - """ - - ip = self.cast.socket_client.host # valid since at least v3.0.0 - - if ping_chromecast(ip) is True: # The chromecast is online. - if str(self.cast.status.display_name) != "Default Media Receiver": - self.mkcc.device_name = self.cast_to - self.get_devices() - self.play_cast() - else: # The chromecast is offline. - try: - self.mkcc.device_name = self.cast_to - self.get_devices() - self.play_cast() - except AttributeError: - pass - - -def ping_chromecast(ip): - """This function pings to hosts. - - Credits: http://stackoverflow.com/a/34455969/1995261 - """ - try: - subprocess.check_output("ping -c 1 " + ip, shell=True) - except: - return False - return True diff --git a/mkchromecast/messages.py b/mkchromecast/messages.py index 8460fd60..7f29525a 100644 --- a/mkchromecast/messages.py +++ b/mkchromecast/messages.py @@ -16,16 +16,3 @@ def print_samplerate_warning(codec: str) -> None: f"Sample rates supported by {codec} are: {joined_rates}." ) ) - - -def print_available_devices(list_of_devices: Iterable[Any]): - """Prints a list of available devices.""" - print(colors.important("List of Devices Available in Network:")) - print(colors.important("-------------------------------------\n")) - print(colors.important("Index Types Friendly Name ")) - print(colors.important("===== ===== ============= ")) - for device in list_of_devices: - device_index = device[0] - device_name = device[1] - device_type = device[2] - print("%s \t%s \t%s" % (device_index, device_type, device_name)) diff --git a/mkchromecast/systray.py b/mkchromecast/systray.py index 27978035..ea53cda0 100644 --- a/mkchromecast/systray.py +++ b/mkchromecast/systray.py @@ -11,12 +11,12 @@ from urllib.request import urlopen import mkchromecast +from mkchromecast import cast from mkchromecast import colors from mkchromecast import config from mkchromecast import preferences from mkchromecast import tray_threading from mkchromecast.audio_devices import inputint, outputint -from mkchromecast.cast import Casting from mkchromecast.pulseaudio import remove_sink from mkchromecast.utils import del_tmp, checkmktmp from mkchromecast.version import __version__ @@ -42,12 +42,15 @@ class menubar(QtWidgets.QMainWindow): def __init__(self): - self.cc = Casting(_mkcc) + self.cc = cast.Casting(_mkcc) signal.signal(signal.SIGINT, signal.SIG_DFL) self.cast = None self.stopped = False self.played = False self.pcastfailed = False + + self.available_devices: list[cast.AvailableDevice] = [] + # TODO(xsdg): pull this directly from _mkcc. self.config = config.Config(platform=_mkcc.platform, read_only=True, @@ -160,7 +163,6 @@ def createUI(self): self.stop_menu() self.volume_menu() self.resetaudio_menu() - self.reboot_menu() self.separator_menu() self.preferences_menu() self.update_menu() @@ -198,10 +200,6 @@ def resetaudio_menu(self): self.ResetAudioAction = self.menu.addAction("Reset Audio") self.ResetAudioAction.triggered.connect(self.reset_audio) - def reboot_menu(self): - self.rebootAction = self.menu.addAction("Reboot Streaming Device") - self.rebootAction.triggered.connect(self.reboot) - def preferences_menu(self): self.preferencesAction = self.menu.addAction("Preferences...") self.preferencesAction.triggered.connect(self.preferences_show) @@ -222,7 +220,7 @@ def exit_menu(self): These are methods for interacting with the mkchromecast objects """ - def onIntReady(self, available_devices): + def onIntReady(self, available_devices: list): print("available_devices received") self.available_devices = available_devices self.cast_list() @@ -276,7 +274,7 @@ def search_cast(self): def cast_list(self): self.set_icon_idle() - if len(self.available_devices) == 0: + if not self.available_devices: self.menu.clear() self.search_menu() self.separator_menu() @@ -287,7 +285,6 @@ def cast_list(self): self.stop_menu() self.volume_menu() self.resetaudio_menu() - self.reboot_menu() self.separator_menu() self.preferences_menu() self.update_menu() @@ -334,16 +331,13 @@ def cast_list(self): self.search_menu() self.separator_menu() print("Available Media Streaming Devices", self.available_devices) - for index, menuentry in enumerate(self.available_devices): - try: - a = self.ag.addAction( - (QtWidgets.QAction(str(menuentry[1]), self, checkable=True)) - ) - self.menuentry = self.menu.addAction(a) - except UnicodeEncodeError: - a = self.menuentry = self.menu.addAction( - str(unicode(menuentry[1]).encode("utf-8")) - ) + for index, device in enumerate(self.available_devices): + # TODO(xsdg): self.ag isn't actually referenced from anywhere, + # so just make it local. + action = self.ag.addAction( + (QtWidgets.QAction(device.name, self, checkable=True)) + ) + # The receiver is a lambda function that passes clicked as # a boolean, and the clicked_item as an argument to the # self.clicked_cc() method. This last method, sets the correct @@ -351,32 +345,30 @@ def cast_list(self): # self.play_cast(). Credits to this question in stackoverflow: # # http://stackoverflow.com/questions/1464548/pyqt-qmenu-dynamically-populated-and-clicked - receiver = lambda clicked, clicked_item=menuentry: self.clicked_cc( + receiver = lambda clicked, clicked_item=device: self.clicked_cc( clicked_item ) - a.triggered.connect(receiver) + action.triggered.connect(receiver) + + self.menu.addAction(action) self.separator_menu() self.stop_menu() self.volume_menu() self.resetaudio_menu() - self.reboot_menu() self.separator_menu() self.preferences_menu() self.update_menu() self.about_menu() self.exit_menu() - def clicked_cc(self, clicked_item): - if self.played is True: - try: - self.cast.quit_app() - except AttributeError: - self.cast.stop() + def clicked_cc(self, clicked_item: cast.AvailableDevice): + if self.played: + self.cast.quit_app() if _mkcc.debug is True: print(":::tray::: clicked item: %s." % clicked_item) - self.index = clicked_item[0] - self.cast_to = clicked_item[1] + self.index = clicked_item.index + self.cast_to = clicked_item.name self.play_cast() def pcastready(self, message): @@ -563,53 +555,6 @@ def reset_audio(self): else: remove_sink() - def reboot(self): - try: - from pychromecast.dial import reboot - except ImportError: - # reboot is removed from pychromecast.dial since PR394 - # see: https://github.com/home-assistant-libs/pychromecast/pull/394 - print( - colors.warning( - "This version of pychromecast does not support reboot. Will do nothing." - ) - ) - reboot = lambda x: None - - if _mkcc.platform == "Darwin": - try: - self.cast.host_ = socket.gethostbyname(self.cast_to + ".local") - print("Cast device IP: " + str(self.cast.host_)) - self.reset_audio() - self.stop_cast() - reboot(self.cast.host_) - except socket.gaierror: - print("Cast device IP: " + str(self.cast.host)) - self.reset_audio() - self.stop_cast() - reboot(self.cast.host) - except AttributeError: - # FIXME I should add a notification here - pass - else: - try: - print("Cast device IP: %s" % str(self.cast.host)) - self.reset_audio() - self.stop_cast() - reboot(self.cast.host) - except AttributeError: - self.reset_audio() - self.stop_cast() - try: - for device in self.available_devices: - if self.cast_to in device: - ip = device[3] - print("Sonos device IP: %s" % str(ip)) - url = "http://" + ip + ":1400/reboot" - urlopen(url).read() - except AttributeError: - pass - def preferences_show(self): self.p = mkchromecast.preferences.preferences(self.scale_factor) self.p.show() diff --git a/mkchromecast/tray_threading.py b/mkchromecast/tray_threading.py index 951e559b..a8134f2b 100644 --- a/mkchromecast/tray_threading.py +++ b/mkchromecast/tray_threading.py @@ -5,11 +5,11 @@ import mkchromecast from mkchromecast import audio +from mkchromecast import cast from mkchromecast import colors from mkchromecast import config from mkchromecast import node from mkchromecast.audio_devices import inputdev, outputdev -from mkchromecast.cast import Casting from mkchromecast.constants import OpMode from mkchromecast.pulseaudio import create_sink, check_sink from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot @@ -28,26 +28,15 @@ def _search_cast_(self): # This should fix the error socket.gaierror making the system tray to # be closed. try: - self.cc = Casting(_mkcc) - self.cc.initialize_cast() - self.cc.available_devices() + cc = cast.Casting(_mkcc) + cc.initialize_cast() + self.intReady.emit(cc.available_devices) + self.finished.emit() except socket.gaierror: if _mkcc.debug is True: - print(colors.warning(":::Threading::: Socket error, CC set to 0")) - pass - except TypeError: - # TODO(xsdg): this is probably a bad idea. - pass - except OSError: - self.cc.available_devices = [] - - if len(self.cc.available_devices) == 0 and _mkcc.operation == OpMode.TRAY: - available_devices = [] - self.intReady.emit(available_devices) - self.finished.emit() - else: - available_devices = self.cc.available_devices - self.intReady.emit(available_devices) + print(colors.warning( + ":::Threading::: Socket error, failed to search for devices")) + self.intReady.emit([]) self.finished.emit() @@ -78,7 +67,7 @@ def _play_cast_(self): if check_sink() is False and _mkcc.adevice is None: create_sink() - start = Casting(_mkcc) + start = cast.Casting(_mkcc) start.initialize_cast() try: start.get_devices() @@ -108,7 +97,7 @@ class Updater(QObject): @pyqtSlot() def _updater_(self): - chk = Casting(_mkcc) + chk = cast.Casting(_mkcc) if chk.ip == "127.0.0.1" or None: # We verify the local IP. self.updateready.emit("None") else: