class pulseaudiostuff(): def __init__(self): logging.debug("Initing %s", self.__class__.__name__) self.pulse = Pulse() self.barecmd = connect_to_cli() default_sink_name = self.pulse.server_info().default_sink_name self.default_sink_info = self.pulse.get_sink_by_name(default_sink_name) out_sink_name = "game-out" # music + mic self.out_sink_module_id = self.pulse.module_load( "module-null-sink", 'sink_name=' + out_sink_name) self.out_sink_info = self.pulse.get_sink_by_name(out_sink_name) MP_sink_name = "Media-player" # that is our main sink. send your media here # everything that comes in is being copied to game sink and default sink self.MP_sink_module_id = self.pulse.module_load( "module-combine-sink", 'sink_name=' + MP_sink_name + ' slaves=' + str(self.out_sink_info.index) + ',' + str(self.default_sink_info.index)) self.MP_sink_info = self.pulse.get_sink_by_name(MP_sink_name) # Get stream media -> speakers # TODO: this is also gay but it is somehow possible to retreve all inputs for sink. (sink_input_list(sinkIndex)) for stream in self.pulse.sink_input_list(): if stream.owner_module == self.MP_sink_module_id: if stream.sink == self.default_sink_info.index: self.sound2speakers = stream elif stream.sink == self.out_sink_info.index: self.sound2game = stream # send mic stream to game sink. (btw rip 20 ms) self.loopback_module_id = self.pulse.module_load( "module-loopback", 'sink=' + str(self.out_sink_info.index) + ' latency_msec=20 source_dont_move=true sink_dont_move=true') # Get stream mic -> game # TODO: this is also gay but it is somehow possible to retreve all inputs for sink. (sink_input_list(sinkIndex)) for stream in self.pulse.sink_input_list(): if stream.sink == self.out_sink_info.index and stream.owner_module == self.loopback_module_id: self.mic2game = stream # TODO: combine sink sets volume to earrape because reasons? hell = self.sound2speakers.volume hell.value_flat = 0.5 self.pulse.volume_set(self.sound2speakers, hell) hell.value_flat = 1.0 self.pulse.volume_set(self.sound2game, hell) # TODO: change names of sinks. # https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/615 # self.barecmd.write( # 'update-sink-proplist ' + str(self.out_sink_info.index) + ' device.description="Game media player out"') # self.barecmd.write( # 'update-sink-proplist ' + str(self.MP_sink_info.index) + ' device.description="Game media player sink"') self.pulse.sink_default_set(self.default_sink_info) logging.debug("%s class loaded", self.__class__.__name__) logging.info("out sink module id: %d", self.out_sink_module_id) logging.info("MP sink module id: %d", self.MP_sink_module_id) logging.info("loopback module id: %d", self.loopback_module_id) def __del__(self): self.pulse.module_unload(self.loopback_module_id) self.pulse.module_unload(self.out_sink_module_id) self.pulse.module_unload(self.MP_sink_module_id) logging.debug("%s class unloaded", self.__class__.__name__) def printstuff(self): print('csgo launch options: "pacmd set-default-source ' + self.out_sink_info.name + '.monitor"')
import sys import time from pulsectl import Pulse pulse = Pulse() arg = "".join(sys.argv[1:]).strip() if arg == "": exit() sink = int(arg) assert isinstance(sink, int) sink_obj = pulse.get_sink_by_name(arg) for sink_in in pulse.sink_input_list(): pulse.sink_input_move(sink_in.index, sink) time.sleep(1) pulse.sink_default_set(sink_obj)
def main(args): pulse = Pulse("pulse-audio-cycle") current_sink = pulse.get_sink_by_name( pulse.server_info().default_sink_name) current_profile = pulse.card_info(current_sink.card).profile_active # card -> holds all profiles and sets the active one # sink uses a card+profile combination and is names accordingly matching_cards_with_profiles = [] card_pattern = re.compile(args.card) logging.debug(f"card_pattern: {card_pattern}") # Get a list of all matching cards for card in pulse.card_list(): # Prepare to also match against sink description sink_for_current_card = None if args.use_sink_description: sink_for_current_card = sink_for_card(card, pulse) card_pattern_matched = False if re.search(card_pattern, card.name): card_pattern_matched = True logging.info(f"Card matched: {card.name}") if sink_for_current_card: logging.info( f"-> Sink Description: {sink_for_current_card.description}" ) elif args.use_sink_description: if sink_for_current_card: if re.search(card_pattern, sink_for_current_card.description): card_pattern_matched = True logging.info(f"Card matched: {card.name}") logging.info( f"-> Sink Description: {sink_for_current_card.description}" ) logging.info("-> matched via Sink Description") # Ignore cards that are not wanted by the user given pattern if not card_pattern_matched: continue matched_profiles = [] # Check if we need filter for certain profiles or leave as is for profile in card.profile_list: # skip unavailable profiles (unless wanted) if not profile.available and not args.with_unavailable: continue # Check every given profile for cp_card_pattern, cp_profile_pattern in args.profile: cp_card_pattern_matched = False if re.search(cp_card_pattern, card.name): cp_card_pattern_matched = True elif args.use_sink_description: if sink_for_current_card: if re.search(cp_card_pattern, sink_for_current_card.description): cp_card_pattern_matched = True # This cp_profile_pattern does not apply to this card if not cp_card_pattern_matched: continue if re.search(cp_profile_pattern, profile.name): logging.info(f" Profile matched: {profile.name} ") matched_profiles.append(profile) if not matched_profiles: logging.info(" No Profile matched – Keeping profile.") # put infos into list matching_cards_with_profiles.append((card, matched_profiles)) # separator betweem cards logging.info("") new_card, new_profile = new_card_and_profile(matching_cards_with_profiles, pulse) # change profile if necessary if new_profile: if args.verbose: logging.info(f"New Profile: {new_profile.description}") if not args.dry: pulse.card_profile_set(new_card, new_profile) else: if args.verbose: logging.info("NO new Profile.") # change sink (has to be done always because card profile also changes sink) new_sink = sink_for_card(new_card, pulse) if args.verbose: logging.info(f"New Card: {new_card.name}") if args.use_sink_description: logging.info(f"-> New Sink: {new_sink.description} ") if not args.dry: pulse.sink_default_set(new_sink) # move all input sinks (apps/X clients) to new output sink for input_sink in pulse.sink_input_list(): if args.verbose: logging.info( f" -> Switching {input_sink.proplist['application.name']}") if not args.dry: pulse.sink_input_move(input_sink.index, new_sink.index) # Show notification if args.notify: details = f"New Sink: {new_sink.description}" if new_profile: details += f"\nNew Profile: {new_profile.description}" notify("Sink Changed", details)