Example #1
0
class VolumeCtl(object):
    def __init__(self):
        self.pulse = Pulse('volume-controller')
        self.sinks = self.pulse.sink_list()
        self.seeed_sink = None
        for i in self.sinks:
            if "seeed" in i.name:
                self.seeed_sink = i
                break

    def setVolume(self, vol):
        if vol > 100:
            vol = 100
        elif vol < 0:
            vol = 0
        vol = vol / 100.0
        if self.seeed_sink:
            try:
                self.pulse.volume_set_all_chans(self.seeed_sink, vol)
            except Exception as e:
                print("Fail to set volume, Error:{}".format(e))
        else:
            print("Fail to find Seeed voicecard")

    def getVolume(self):
        if self.seeed_sink:
            vol = 100.0 * self.pulse.volume_get_all_chans(self.seeed_sink)
            return vol
        else:
            print("Fail to find Seeed voicecard") 
            return (0.0)

    def setMute(self, muted):
        if self.seeed_sink:
            try:
                self.pulse.mute(self.seeed_sink, muted)
            except Exception as e:
                print("Fail to set mute, Error:{}".format(e))
        else:
            print("Fail to find Seeed voicecard")
Example #2
0
class PulseMixer(object):
    profiles = {
        "analog": None,
        "a2dp": None,
        "hsp": None,
    }
    current_profile = None
    active_sink = None

    def __init__(self):
        self.pulse = Pulse('volume-control')

    def introspect(self):
        for k in self.profiles.keys():
           self.profiles[k] = None
        self.current_profile = None

        for k in self.profiles.keys():
            self.profiles[k] = None

        self.cards = self.pulse.card_list()
        self.get_active_sink()
        for card in self.cards:
            for profile in card.profile_list:
                if (card.profile_active.name == profile.name
                    and card.name[:4] == self.active_sink.name[:4]):
                    self.current_profile = profile
                key = PROFILE_MAP.get(profile.name, None)
                if key:
                    self.profiles[key] = profile

    def set_profile(self, key):
        # traverse through cards to determine which ones to turn on/off
        next_profile = self.profiles[key]
        next_card = None
        for card in self.cards:
            for profile in card.profile_list:
                if profile == next_profile:
                    next_card = card
                    break
        off_card = None
        off_profile = None
        for card in self.cards:
            if card == next_card:
                continue
            for profile in card.profile_list:
                if profile.name == PROFILE_OFF:
                    off_card = card
                    off_profile = profile
        if not next_card or not next_profile:
            return
        self.pulse.card_profile_set(next_card, next_profile)
        if off_card and off_profile:
            self.pulse.card_profile_set(off_card, off_profile)

    def get_active_sink(self):
        sink = None
        sink_inputs = self.pulse.sink_input_list()
        # check if a sink input is connected to a sink, if no, use default
        if len(sink_inputs):
            sink_id = sink_inputs[0].sink
            sinks = self.pulse.sink_list()
            sink = next((s for s in sinks if s.index == sink_id), None)
        if sink is None:
            info = self.pulse.server_info()
            sink = self.pulse.get_sink_by_name(info.default_sink_name)
        self.active_sink = sink
        return self.active_sink

    def get_sink_volume_and_mute(self):
        mute = True
        volume = 0
        if self.active_sink:
            volume = self.pulse.volume_get_all_chans(self.active_sink)
            volume = min(max(volume, 0), 1) * 100
            mute = self.active_sink.mute
        return volume, mute

    def set_volume(self, value):
        if self.active_sink:
            self.pulse.volume_set_all_chans(self.active_sink, value / 100.0)

    def change_volume(self, value):
        if self.active_sink:
            volume = self.pulse.volume_get_all_chans(self.active_sink)
            volume += value / 100.0
            volume = min(max(volume, 0), 1)
            self.pulse.volume_set_all_chans(self.active_sink, volume)

    def toggle_mute(self):
        sink = self.active_sink
        sink and self.pulse.mute(sink, not sink.mute)

    def start_listener(self, func):
        self.callback = func
        self.thread = threading.Thread(
            target=self.async_listener
        )
        self.thread.daemon = True
        self.thread.start()

    def async_listener(self):
        self.pulse_d = Pulse('volume-daemon')
        self.pulse_d.event_mask_set('sink')

        # Glib.idle_add is to run the callback in the UI thread
        self.pulse_d.event_callback_set(
            lambda e: GLib.idle_add(self.callback)
        )
        self.pulse_d.event_listen()
Example #3
0
class Dispatcher:
    """
    A simple dispatcher class that receive powermate events and dispatch them to the right controller
    """

    def __init__(self, observer):
        """
        Initialize the super class and define the local members
        :param path: The path to the powermate device
        """
        self._long_pressed = False
        self._pulse = Pulse(threading_lock=True)
        self._stored_app = None
        self._display = Display()  # Connects to the default display
        self._note = pynotify.Notification("Volume", "0", "/usr/share/icons/Faenza/apps/48/"
                                                          "gnome-volume-control.png")
        self._note.set_urgency(0)

        self._led = PowermateLed()
        self._led.max()

        self._rofi = Rofi()
        self._rofi.hide_scrollbar = True
        self._rofi.prompt = "App. name?"

    def short_press(self):
        """
        Manage the short_press event
        :return: None
        """

        # Get the list of active sinks
        sinks = self._get_sinks()
        # Get the names of the apps linked to the sinks
        app_sinks = {"{} {}".format(sink.proplist.get("application.name"), sink.index): sink for sink in sinks}
        if len(app_sinks) > 1:
            # Display a menu to select the application to control
            try:
                res = self._rofi(app_sinks)
            except MenuError:
                return
            app_sink = res.value
        elif len(app_sinks) == 1:
            _, app_sink = app_sinks.popitem()
        else:
            app_sink = None

        # If successful
        if app_sink is not None:
            # Toggle the mute status of the selected sink
            self._toggle_mute_sinks([app_sink])

            # Declare a new notification
            self._note.update("Toggle Mute status", "{}".format(app_sink.proplist.get("application.name")), "/usr/share/icons/Faenza/apps/48/gnome-volume-control.png")

            # Show the notification
            self._note.show()

    def long_press(self):
        """
        For the moment this method simply toggles the lights on/off
        :return: A LedEvent class
        """

        if self._long_pressed:
            # Re-initialize the state of the powermate
            self._long_pressed = False
            self._stored_app = None
            # Just light up the powermate
            self._led.max()
        else:
            # Get the list of active sinks
            sinks = self._get_sinks()
            # Get the names of the apps linked to the sinks
            app_sinks = {sink.proplist.get("application.name"):sink.proplist.get("application.process.binary") for sink in sinks if sink.proplist.get("application.process.binary") not in self._get_active_win_class()}
            if len(app_sinks) > 1:
                # Display a menu to select the application to control
                try:
                    res = self._rofi(app_sinks)
                except MenuError:
                    return
                app_name = res.value
            elif len(app_sinks) == 1:
                _, app_name = app_sinks.popitem()
            else:
                app_name = None

            # If successful
            if app_name is not None:
                # Store the list of sinks corresponding to the app name
                self._stored_app = app_name

                # Toggle the long press state
                self._long_pressed = True

                # Have the powermate pulse
                self._led.pulse()
            else:
                # Make sure the long press flag is off
                self._long_pressed = False
                # Stop the pulse
                self._led.max()

    def rotate(self, rotation):
        """
        Manage the rotate event
        :param rotation: The direction of rotation negative->left, positive->right
        :return: None
        """

        # Get the class of the active window
        win_cls = self._get_active_win_class()
        if win_cls is not None:
            # Change the volume of the sinks
            self._change_volume_sinks(self._get_app_sinks(win_cls), rotation)

    def push_rotate(self, rotation):
        """
        Changes the volume of the sinks registered by the long_press event, according to the given rotation.
        :param rotation: The direction and amplitude of the rotation. (negative = left, positive = right).
        :return: Nothing.
        """

        # Change the volume of the current sinks
        self._change_volume_sinks(self._get_app_sinks(self._stored_app), rotation)

    def _toggle_mute_sinks(self, sinks):
        """
        Simply toggle the mute status of all given sinks.
        :param sinks: A list of sink objects.
        :return: Nothing.
        """

        # Toggle the mute status
        for sink in sinks:
            muted = bool(sink.mute)
            self._pulse.mute(sink, mute=not muted)

    def _change_volume_sinks(self, sinks, rotation):
        """
        Simple change the volume of all given sinks and display a notification.
        :param sinks: A list of sink objects.
        :param rotation: The amount and direction of the rotation.
        :return: Nothing.
        """

        # Change the volume of the sinks
        for sink in sinks:
            self._pulse.volume_change_all_chans(sink, rotation * 0.005)

            # Show the notification
            self._display_notification(sink)


    def _get_active_win_class(self):
        """
        Use the xlib module to get the class of the window that has the focus
        :return: Return the window class or None if none found
        """

        # Get the window that has the focus
        focus_win = self._display.get_input_focus().focus
        # Get the window class
        win_cls = focus_win.get_wm_class()
        if win_cls is None:
            # Get the class of the parent window
            parent_cls = focus_win.query_tree().parent.get_wm_class()
            if parent_cls is not None:
                return str(parent_cls[-1].lower())
            else:
                return None
        else:
            return str(win_cls[-1].lower())

    def _get_app_sinks(self, app_name):
        """
        Get the sinks corresponding to the given application
        :param app_name: Name of the application
        :return: List of sink objects otherwise.
        """

        # Make sure the app_name is a string
        if isinstance(app_name, str) and app_name is not None:
            # Get the list of input sinks
            sinks = self._get_sinks()
            # Return the list of sinks corresponding to the application
            return [sink for sink in sinks if sink.proplist.get("application.process.binary").lower() == app_name]
        else:
            return []

    def _get_sinks(self):
        """
        Get a list of active pulseaudio sinks
        :return: List. A list containing all the active sink objects.
        """

        # Get the list of input sinks
        sinks = [sink for sink in self._pulse.sink_input_list()
                 if sink.proplist.get("application.process.binary", None) is not None]

        # Return the list of active sinks
        return sinks

    def _display_notification(self, sink_in):
        """
        Display a notification showing the overall current volume.
        :param volume: A float representing the value of the current sink input.
        :return: Nothing.
        """

        # Get the volume of the input sink
        volume = self._pulse.volume_get_all_chans(sink_in)

        # Get the main sink
        for sink in self._pulse.sink_list():
            if sink.index == sink_in.sink:
                main_vol = sink.volume.value_flat
                break
        else:
            main_vol = 1

        # Declare a new notification
        self._note.update("Volume", "{:.2%}".format(volume * main_vol), "/usr/share/icons/Faenza/apps/48/"
                                                                        "gnome-volume-control.png")

        # Show the notification
        self._note.show()

    def _handle_exception(self):
        """
        Close the connection to the pulse server.
        :return: Nothing
        """

        # Close the connection to the pulse server
        self._pulse.close()
        self._led.off()
Example #4
0
class pulseToTalk:
    """Simple push to talk binding for X / Pulseaudio

    Work in user mode (command line only)
    """
    def __init__(self, **kwargs):
        """Init all variables for pulseToTalk and save config

        Kwargs :
            - event_code (list) : Bind key names list
                (optional) (default: None)
            - no_indicator (bool) : Do not show recording
                indicator (default: False)
            - sources (list) : Operate only on the given
                pulseaudio sources (default: None)
            - debug (bool) : Activate debug mode (default: False)
        """
        self.config = kwargs

        # Init logger
        handler = logging.StreamHandler()
        handler.setFormatter(
            colorlog.ColoredFormatter(
                '[%(name)s]%(log_color)s %(levelname)s > %(message)s'))
        self.logger = colorlog.getLogger(self.__class__.__name__)
        self.logger.addHandler(handler)
        if self.config.get('debug', False) is True:
            self.logger.setLevel(logging.DEBUG)
        else:
            self.logger.setLevel(logging.WARNING)

        # Version
        self.logger.debug('Starting version 1.3')

        # Create hookmanager
        self.hookman = pyxhook.HookManager()
        # Define our callback to fire when a key / mouse is pressed or released
        self.hookman.KeyDown = self.on_key_event
        self.hookman.KeyUp = self.on_key_event
        self.hookman.MouseAllButtonsDown = self.on_key_event
        self.hookman.MouseAllButtonsUp = self.on_key_event
        # Hook the keyboard
        self.hookman.HookKeyboard()

        # Init pulse (do not support multi-thread !)
        self.pulse = Pulse(self.__class__.__name__)

        # Init empty recording indicator
        self.recording_indicator = None
        if self.config.get('no_indicator', False) is False:
            # Init RecordingIndicator
            self.recording_indicator = RecordingIndicator()

        # Store if recording is locked
        self.lock_recording = False
        # Store if control is keep pushed
        self.is_control_modifier = False

        # State of recording
        self.is_recording = False
        # Mute all inputs at start
        self.mute_sources()
        # Start listener
        self.hookman.start()
        # Init custom variables
        self.stored_event_code = set()
        # If event code is passed by args
        if self.config.get('event_code'):
            self.stored_event_code = set(self.config['event_code'])

        # Process stored_event_code
        self.logger.warning('Press CTRL + C to exit...')
        if len(self.stored_event_code) == 0:
            self.logger.debug('!!! Key binding not yet configured !')
            self.logger.critical('Press key/mouse to bind :')
        else:
            self.logger.critical('Configured bind(s) : %s' %
                                 ', '.join(self.stored_event_code))

    def run(self):
        """Run infinite loop to watch user keys / mouse
        """
        try:
            # Create a loop to keep the application running
            while True:
                if self.recording_indicator is not None:
                    self.recording_indicator.do_update()
                time.sleep(0.1)
        except KeyboardInterrupt:
            pass
        # Close the listener when we are done
        self.hookman.cancel()
        # Unmute all
        self.mute_sources(False)
        self.logger.debug('Terminated')

    def mute_sources(self, is_mute=True):
        """Mute or unmute all audio sources with pulseaudio
        """

        if is_mute:
            self.logger.info('Do source(s) MUTE :')
        else:
            self.logger.info('Do source(s) UNMUTE :')

        for source in self.pulse.source_list():

            # Exclude monitors input
            if source.name.endswith('.monitor'):
                continue

            # Mute only specified sources
            if self.config.get(
                    'sources') and source.name not in self.config.get(
                        'sources'):
                continue

            # Mute source
            self.pulse.mute(source, is_mute)

            self.logger.info('- %s' % source)

    def on_stored_event(self, event):
        """Switch mode if needed if one of stored event is trigger
        """
        # Lock recording if Ctrl is pressed once with event
        if self.is_control_modifier is True and self.is_event_down(event):
            if self.lock_recording is False:
                self.lock_recording = True
            else:
                self.lock_recording = False

        # If lock_recording force recording until it's stopped
        # Normal activate recording on press
        if self.is_recording is False and (self.is_event_down(event)
                                           or self.lock_recording is True):
            self.is_recording = True
            self.mute_sources(is_mute=False)
        # Normal disable recording on release key
        elif self.is_recording is True and self.is_event_up(
                event) and self.lock_recording is False:
            self.is_recording = False
            self.mute_sources()

    @staticmethod
    def get_event_code(event):
        """Return cleaned event code
        """
        # Try to detect if Mouse or Key are pressed
        if hasattr(event, 'Key') is True:
            return event.Key.lower()
        if hasattr(event, 'Position') is True:
            # Create mouse key without 'up' and 'down' like 'mouse_left'
            return '_'.join(event.MessageName.lower().split()[0:2])

        raise ValueError('Event not recognized : "%s" ' % event)

    @staticmethod
    def is_event_down(event):
        if event is None:
            return False
        return event.MessageName.endswith(' down')

    @staticmethod
    def is_event_up(event):
        if event is None:
            return False
        return event.MessageName.endswith(' up')

    def on_key_event(self, event):
        event_code = self.get_event_code(event)
        self.logger.debug('Event detected : %s %s' % (event, event_code))

        # Store key on first key to be used if no event previously registred
        if len(self.stored_event_code) == 0:

            self.stored_event_code.add(event_code)

            # Print binded event and exit function
            return self.logger.critical(
                'Binded %s event : \'%s\'.' %
                ('KEY' if hasattr(event, 'Key') else 'MOUSE', event_code))

        # Check ctrl modifier key
        if event_code in ['control_l', 'control_r']:
            if self.is_event_down(event):
                self.is_control_modifier = True
            else:
                self.is_control_modifier = False

        # Trigger if event is the same as stored event
        if event_code in self.stored_event_code:
            self.on_stored_event(event)
Example #5
0
class PulseMixer(object):
    all_profiles = {}
    current_profile = None
    active_sink = None

    def __init__(self):
        self.pulse = Pulse('volume-control')

    def introspect(self):
        self.all_profiles = {}
        self.current_profile = None
        self.cards = self.pulse.card_list()

        for card in self.cards:
            description = card.proplist.get('device.description')
            for profile in card.profile_list:
                #print(profile)
                prof_key = PROFILE_MAP.get(profile.name, None)
                if prof_key and profile.available:
                    key = description + '__' + prof_key
                    self.all_profiles[key] = [card, profile]
                    if (card.profile_active.name == profile.name
                            and self.active_sink
                            and card.name[:4] == self.active_sink.name[:4]):
                        self.current_profile = key

    def set_profile(self, key):
        prof = self.all_profiles[key]
        if not prof:
            return
        card, profile = prof
        for c, p in self.all_profiles.values():
            if c != card:
                self.pulse.card_profile_set(c, PROFILE_OFF)
            elif p == profile:
                self.pulse.card_profile_set(card, profile)

    def get_active_sink(self):
        sink = None
        sink_inputs = self.pulse.sink_input_list()
        # check if a sink input is connected to a sink, if no, use default
        if len(sink_inputs):
            sink_id = sink_inputs[0].sink
            sinks = self.pulse.sink_list()
            sink = next((s for s in sinks if s.index == sink_id), None)
        if sink is None:
            info = self.pulse.server_info()
            if info.default_sink_name == '@DEFAULT_SINK@':
                return None
            sink = self.pulse.get_sink_by_name(info.default_sink_name)
        self.active_sink = sink
        return self.active_sink

    def get_sink_volume_and_mute(self):
        mute = True
        volume = 0
        if self.active_sink:
            volume = self.pulse.volume_get_all_chans(self.active_sink)
            volume = min(max(volume, 0), 1) * 100
            mute = self.active_sink.mute
        return volume, mute

    def set_volume(self, value):
        if self.active_sink:
            self.pulse.volume_set_all_chans(self.active_sink, value / 100.0)

    def change_volume(self, value):
        if self.active_sink:
            volume = self.pulse.volume_get_all_chans(self.active_sink)
            volume += value / 100.0
            volume = min(max(volume, 0), 1)
            self.pulse.volume_set_all_chans(self.active_sink, volume)

    def get_mute(self):
        return self.active_sink.mute

    def toggle_mute(self):
        sink = self.active_sink
        sink and self.pulse.mute(sink, not sink.mute)

    def start_listener(self, func):
        self.callback = func
        self.thread = threading.Thread(target=self.async_listener)
        self.thread.daemon = True
        self.thread.start()

    def async_listener(self):
        self.pulse_d = Pulse('volume-daemon')
        self.pulse_d.event_mask_set('sink', 'card')

        # Glib.idle_add is to run the callback in the UI thread
        self.pulse_d.event_callback_set(self.callback())
        try:
            self.pulse_d.event_listen()
        except PulseDisconnected:
            time.sleep(3)
            self.pulse = Pulse('volume-control')
            self.async_listener()
Example #6
0
class pulseToTalk(object):
    """Simple push to talk binding for X / Pulseaudio

    Work in user mode (command line only)
    """
    def __init__(self, event_code=None, no_indicator=False, debug=False):
        """Init all variables for pulseToTalk

        Args :
            - event_code (list) : Bind key names list (optional)
            - no_indicator (book) : Do not show recording indicator
            - debug (bool) : Activate debug mode
        """

        # Init logger
        handler = logging.StreamHandler()
        handler.setFormatter(
            colorlog.ColoredFormatter('%(log_color)s%(message)s'))
        self.logger = colorlog.getLogger(self.__class__.__name__)
        self.logger.addHandler(handler)
        if debug:
            self.logger.setLevel(logging.DEBUG)
        else:
            self.logger.setLevel(logging.WARNING)

        # Version
        self.logger.debug('[%s] Starting version 1.2' %
                          (self.__class__.__name__))

        # Create hookmanager
        self.hookman = pyxhook.HookManager()
        # Define our callback to fire when a key / mouse is pressed or released
        self.hookman.KeyDown = self.on_key_event
        self.hookman.KeyUp = self.on_key_event
        self.hookman.MouseAllButtonsDown = self.on_key_event
        self.hookman.MouseAllButtonsUp = self.on_key_event
        # Hook the keyboard
        self.hookman.HookKeyboard()

        # Init pulse (do not support multi-thread !)
        self.pulse = Pulse(self.__class__.__name__)

        self.no_indicator = no_indicator
        if self.no_indicator is False:
            # Init RecordingIndicator
            self.recording_indicator = RecordingIndicator()

        # State of recording
        self.is_recording = False
        # Mute all inputs at start
        self.mute_sources()
        # Start listener
        self.hookman.start()
        # Init custom variables
        self.stored_event_code = set()
        # If event code is passed by args
        if event_code:
            self.stored_event_code = set(event_code)

        # Process stored_event_code
        self.logger.warning('> Press CTRL + C to exit...')
        if len(self.stored_event_code) == 0:
            self.logger.debug('!!! Key binding not yet configured !')
            self.logger.critical('> Press key/mouse to bind :')
        else:
            self.logger.critical('Configured bind(s) : %s' %
                                 ', '.join(self.stored_event_code))

        # Finally run
        self.run()

    def run(self):
        """Run infinite loop to watch user keys / mouse
        """
        try:
            # Create a loop to keep the application running
            while True:
                if self.no_indicator is False:
                    self.recording_indicator.do_update()
                time.sleep(0.1)
        except KeyboardInterrupt:
            pass
        # Close the listener when we are done
        self.hookman.cancel()
        # Unmute all
        self.mute_sources(False)
        self.logger.debug('[%s] terminated.' % self.__class__.__name__)

    def mute_sources(self, is_mute=True):
        """Mute or unmute all audio sources with pulseaudio
        """

        if is_mute:
            self.logger.info('Do source(s) MUTE :')
        else:
            self.logger.info('Do source(s) UNMUTE :')

        for source in self.pulse.source_list():

            # Exclude monitors input
            if source.name.endswith('.monitor'):
                continue
            # Mute source
            self.pulse.mute(source, is_mute)

            self.logger.info('- %s' % source)

    def on_stored_event(self, event):
        """Switch mode if needed if one of stored event is trigger
        """
        if not self.is_recording and event.MessageName.endswith(' down'):
            self.is_recording = True
            self.mute_sources(is_mute=False)

        elif self.is_recording and event.MessageName.endswith(' up'):
            self.is_recording = False
            self.mute_sources()

    def get_event_code(self, event):
        """Return cleaned event code
        """
        if isinstance(event, pyxhook.pyxhookkeyevent):
            return event.Key.lower()

        if isinstance(event, pyxhook.pyxhookmouseevent):
            # Create mouse key without 'up' and 'down' like 'mouse_left'
            return '_'.join(event.MessageName.lower().split()[0:2])

        raise ValueError('Event not recognized : "%s" ' % event)

    def on_key_event(self, event):

        event_code = self.get_event_code(event)
        self.logger.debug('Event detected : %s' % event_code)

        # Store key on first key to be used if no event previously registred
        if len(self.stored_event_code) == 0:

            self.stored_event_code.add(event_code)

            # Print binded event and exit function
            return self.logger.critical(
                'Binded %s event : \'%s\'.' % ('KEY' if isinstance(
                    event, pyxhook.pyxhookkeyevent) else 'MOUSE', event_code))

        # Trigger if event is the same as stored event
        if event_code in self.stored_event_code:
            self.on_stored_event(event)
Example #7
0
    sink = pulse.sink_list()[SINK_ID]
    active_port = sink.port_active.name
    volume = round(sink.volume.value_flat * 100)
    is_muted = sink.mute == 1

    args = args_parser()

    if args.PORT:
        if active_port == SPEAKERS:
            pulse.port_set(sink, HEADPHONES)
        else:
            pulse.port_set(sink, SPEAKERS)

    if args.MUTE:
        if is_muted:
            pulse.mute(sink, mute=False)
        else:
            pulse.mute(sink, mute=True)

    if args.UP:
        if volume < 150:
            pulse.volume_change_all_chans(sink, +0.05)
            volume = round(sink.volume.value_flat * 100)

    if args.DOWN:
        pulse.volume_change_all_chans(sink, -0.05)
        volume = round(sink.volume.value_flat * 100)

    volume = round(sink.volume.value_flat * 100)
    if not is_muted:
        if active_port == SPEAKERS: