Пример #1
0
class Application(Gtk.Application):
    def __init__(self):
        app_id = 'com.github.wwmm.pulseeffects'
        app_flags = Gio.ApplicationFlags.HANDLES_COMMAND_LINE

        GLib.set_application_name('PulseEffects')
        GLib.setenv('PULSE_PROP_application.icon_name', 'pulseeffects', True)
        GLib.setenv('PULSE_PROP_application.id',
                    'com.github.wwmm.pulseeffects', True)
        GLib.setenv('PULSE_PROP_application.version', '3.2.3', True)
        GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, 2, self.quit)  # sigint

        Gtk.Application.__init__(self, application_id=app_id, flags=app_flags)

        help_msg = _('Quit PulseEffects. Useful when running in service mode.')

        self.add_main_option('quit', ord('q'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, help_msg, None)

        help_msg = _('Show available presets.')

        self.add_main_option('presets', ord('p'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, help_msg, None)

        help_msg = _('Load a preset. Example: pulseeffects -l music')

        self.add_main_option('load-preset', ord('l'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.STRING, help_msg, None)

        log_format = '%(asctime)s.%(msecs)d - %(name)s - %(levelname)s'
        log_format = log_format + ' - %(message)s'

        logging.basicConfig(format=log_format,
                            datefmt='%H:%M:%S',
                            level=logging.INFO)

        self.log = logging.getLogger('PulseEffects')

        self.log_tag = 'MAIN - '

        if os.environ.get('PULSEEFFECTS_DEBUG'):
            self.log.setLevel(logging.DEBUG)

            self.log.debug(self.log_tag + 'debug logging enabled')

    def do_startup(self):
        Gtk.Application.do_startup(self)

        self.ui_initialized = False
        self.window_activated = False
        self.running_as_service = False
        self.module_path = os.path.dirname(__file__)

        self.sink_list = []
        self.source_list = []

        self.gtk_settings = Gtk.Settings.get_default()

        self.settings = Gio.Settings('com.github.wwmm.pulseeffects')

        # creating user presets folder
        self.user_config_dir = os.path.join(GLib.get_user_config_dir(),
                                            'PulseEffects')

        os.makedirs(self.user_config_dir, exist_ok=True)

        # autostart file path
        autostart_file_name = 'autostart/pulseeffects-service.desktop'

        self.autostart_file = os.path.join(GLib.get_user_config_dir(),
                                           autostart_file_name)

        self.create_appmenu()

        # custom css styles

        self.apply_css_style('listbox.css')

        icon_theme = Gtk.IconTheme.get_default()
        icon_theme.append_search_path(self.module_path + '/ui')

        # pulseaudio

        self.pm = PulseManager()
        self.pm.connect('new_default_sink', self.on_new_default_sink)
        self.pm.connect('new_default_source', self.on_new_default_source)
        self.pm.connect('sink_added', self.on_sink_added)
        self.pm.connect('sink_removed', self.on_sink_removed)
        self.pm.connect('source_added', self.on_source_added)
        self.pm.connect('source_removed', self.on_source_removed)

        flag = Gio.SettingsBindFlags.DEFAULT

        self.settings.bind('use-default-sink', self.pm, 'use_default_sink',
                           flag)
        self.settings.bind('use-default-source', self.pm, 'use_default_source',
                           flag)

        # sink inputs and source outputs effects managers

        self.sie = SinkInputEffects(self.pm)
        self.soe = SourceOutputEffects(self.pm)

        self.settings.bind('enable-all-apps', self.sie, 'switch_on_all_apps',
                           flag)
        self.settings.bind('enable-all-apps', self.soe, 'switch_on_all_apps',
                           flag)

        # output effects pipeline latency

        self.settings.bind('buffer-out', self.sie.audio_src, 'buffer-time',
                           flag)
        self.settings.bind('latency-out', self.sie.audio_src, 'latency-time',
                           flag)
        self.settings.bind('buffer-out', self.sie.audio_sink, 'buffer-time',
                           flag)
        self.settings.bind('latency-out', self.sie.audio_sink, 'latency-time',
                           flag)

        # input effects pipeline latency

        self.settings.bind('buffer-in', self.soe.audio_src, 'buffer-time',
                           flag)
        self.settings.bind('latency-in', self.soe.audio_src, 'latency-time',
                           flag)
        self.settings.bind('buffer-in', self.soe.audio_sink, 'buffer-time',
                           flag)
        self.settings.bind('latency-in', self.soe.audio_sink, 'latency-time',
                           flag)

        self.presets = PresetsManager(self)

        if self.props.flags & Gio.ApplicationFlags.IS_SERVICE:
            self.running_as_service = True

            self.init_ui()

            self.sie.post_messages(False)
            self.soe.post_messages(False)

            self.pm.find_sink_inputs()
            self.pm.find_source_outputs()

            self.log.info(self.log_tag + _('Running in background'))

            self.hold()

    def init_ui(self):
        self.builder = Gtk.Builder.new_from_file(self.module_path +
                                                 '/ui/main_ui.glade')

        self.builder.connect_signals(self)

        self.window = self.builder.get_object('MainWindow')
        self.window.set_application(self)
        self.window.connect('destroy', self.on_window_destroy)

        self.sie.init_ui()
        self.soe.init_ui()

        self.draw_spectrum = DrawSpectrum(self)

        self.init_spectrum_widgets()
        self.init_stack_widgets()
        self.init_autostart_switch()

        # Gsettings bindings

        flag = Gio.SettingsBindFlags.DEFAULT
        flag_invert_boolean = Gio.SettingsBindFlags.INVERT_BOOLEAN

        enable_all_apps = self.builder.get_object('enable_all_apps')
        theme_switch = self.builder.get_object('theme_switch')
        use_default_sink = self.builder.get_object('use_default_sink')
        use_default_source = self.builder.get_object('use_default_source')
        self.ui_output_device = self.builder.get_object('output_device')
        self.ui_input_device = self.builder.get_object('input_device')

        buffer_out = self.builder.get_object('buffer_out')
        latency_out = self.builder.get_object('latency_out')
        buffer_in = self.builder.get_object('buffer_in')
        latency_in = self.builder.get_object('latency_in')

        self.settings.bind('use-dark-theme', theme_switch, 'active', flag)

        self.settings.bind('use-dark-theme', self.gtk_settings,
                           'gtk_application_prefer_dark_theme', flag)

        self.settings.bind('enable-all-apps', enable_all_apps, 'active', flag)

        self.settings.bind('use-default-sink', use_default_sink, 'active',
                           flag)

        self.settings.bind('use-default-sink', self.ui_output_device,
                           'sensitive', flag | flag_invert_boolean)

        self.settings.bind('use-default-source', use_default_source, 'active',
                           flag)

        self.settings.bind('use-default-source', self.ui_input_device,
                           'sensitive', flag | flag_invert_boolean)

        self.settings.bind('buffer-out', buffer_out, 'value', flag)
        self.settings.bind('latency-out', latency_out, 'value', flag)
        self.settings.bind('buffer-in', buffer_in, 'value', flag)
        self.settings.bind('latency-in', latency_in, 'value', flag)

        # this connection is changed inside the stack switch handler
        # depending on the selected child. The connection below is not
        # permanent but just a default

        self.spectrum_handler_id = self.sie.connect(
            'new_spectrum', self.draw_spectrum.on_new_spectrum)

        self.presets.load_menu()

        self.ui_initialized = True

    def on_window_destroy(self, window):
        self.ui_initialized = False
        self.window_activated = False
        self.sie.there_is_window = False
        self.soe.there_is_window = False

        self.sie.disconnect_streams()
        self.soe.disconnect_streams()

        if self.running_as_service:
            self.sie.post_messages(False)
            self.soe.post_messages(False)

    def do_activate(self):
        if not self.ui_initialized:
            self.init_ui()

        self.window.present()

        self.window_activated = True
        self.sie.there_is_window = True
        self.soe.there_is_window = True

        if self.running_as_service:
            # adding to ui devices found while in background

            self.update_output_dev_combobox()
            self.update_input_dev_combobox()

        self.pm.find_sink_inputs()
        self.pm.find_source_outputs()
        self.pm.find_sinks()
        self.pm.find_sources()

        self.sie.post_messages(True)
        self.soe.post_messages(True)

    def do_command_line(self, command_line):
        options = command_line.get_options_dict()

        if options.contains('quit'):
            self.quit()
        elif options.contains('presets'):
            self.presets.list_presets()
        elif options.contains('load-preset'):
            value = options.lookup_value('load-preset', None).unpack()

            self.presets.load_preset(value)
        else:
            self.do_activate()

        return Gtk.Application.do_command_line(self, command_line)

    def do_shutdown(self):
        Gtk.Application.do_shutdown(self)

        self.sie.set_state('null')
        self.soe.set_state('null')

        self.sie.pe_is_closing = True
        self.soe.pe_is_closing = True

        self.pm.exit()

    def create_appmenu(self):
        menu = Gio.Menu()

        menu.append('About', 'app.about')
        menu.append('Quit', 'app.quit')

        self.set_app_menu(menu)

        about_action = Gio.SimpleAction.new('about', None)
        about_action.connect('activate', self.onAbout)
        self.add_action(about_action)

        quit_action = Gio.SimpleAction.new('quit', None)
        quit_action.connect('activate', lambda action, parameter: self.quit())
        self.add_action(quit_action)

    def init_stack_widgets(self):
        self.stack = self.builder.get_object('stack')

        self.stack.add_named(self.sie.ui_window, 'sink_inputs')
        self.stack.child_set_property(self.sie.ui_window, 'icon-name',
                                      'audio-speakers-symbolic')

        self.stack.add_named(self.soe.ui_window, 'source_outputs')
        self.stack.child_set_property(self.soe.ui_window, 'icon-name',
                                      'audio-input-microphone-symbolic')

        self.stack_current_child_name = 'sink_inputs'

        self.stack.connect('notify::visible-child',
                           self.on_stack_visible_child_changed)

    def on_stack_visible_child_changed(self, stack, visible_child):
        name = stack.get_visible_child_name()

        if name == 'sink_inputs':
            if self.stack_current_child_name == 'source_outputs':
                self.soe.disconnect(self.spectrum_handler_id)

            self.spectrum_handler_id = self.sie.connect(
                'new_spectrum', self.draw_spectrum.on_new_spectrum)

            self.stack_current_child_name = 'sink_inputs'
        elif name == 'source_outputs':
            if self.stack_current_child_name == 'sink_inputs':
                self.sie.disconnect(self.spectrum_handler_id)

            self.spectrum_handler_id = self.soe.connect(
                'new_spectrum', self.draw_spectrum.on_new_spectrum)

            self.stack_current_child_name = 'source_outputs'

        self.draw_spectrum.clear()

    def init_autostart_switch(self):
        switch = self.builder.get_object('enable_autostart')

        if os.path.isfile(self.autostart_file):
            switch.set_state(True)
        else:
            switch.set_state(False)

    def on_enable_autostart_state_set(self, obj, state):
        if state:
            with open(self.autostart_file, 'w') as f:
                f.write('[Desktop Entry]\n')
                f.write('Name=PulseEffects\n')
                f.write('Comment=PulseEffects Service\n')
                f.write('Exec=pulseeffects --gapplication-service\n')
                f.write('Icon=pulseeffects\n')
                f.write('StartupNotify=false\n')
                f.write('Terminal=false\n')
                f.write('Type=Application\n')
                f.close()

            self.log.debug(self.log_tag +
                           'autostart_file created successfully')
        else:
            os.remove(self.autostart_file)

            self.log.debug(self.log_tag +
                           'autostart_file removed successfully')

    def init_spectrum_widgets(self):
        show_spectrum_switch = self.builder.get_object('show_spectrum')
        spectrum_n_points_obj = self.builder.get_object('spectrum_n_points')

        show_spectrum = self.settings.get_value('show-spectrum').unpack()
        spectrum_n_points = self.settings.get_value(
            'spectrum-n-points').unpack()

        show_spectrum_switch.set_active(show_spectrum)
        spectrum_n_points_obj.set_value(spectrum_n_points)

        self.sie.set_spectrum_n_points(spectrum_n_points)
        self.soe.set_spectrum_n_points(spectrum_n_points)

        if show_spectrum:
            self.draw_spectrum.show()
        else:
            self.draw_spectrum.hide()

    def on_show_spectrum_state_set(self, obj, state):
        if state:
            self.draw_spectrum.show()
            self.sie.enable_spectrum(True)
            self.soe.enable_spectrum(True)
        else:
            self.sie.enable_spectrum(False)
            self.soe.enable_spectrum(False)
            self.draw_spectrum.hide()

        out = GLib.Variant('b', state)
        self.settings.set_value('show-spectrum', out)

    def on_spectrum_n_points_value_changed(self, obj):
        value = obj.get_value()

        out = GLib.Variant('i', value)
        self.settings.set_value('spectrum-n-points', out)

        self.sie.set_spectrum_n_points(value)
        self.soe.set_spectrum_n_points(value)

    def on_new_default_sink(self, obj, value):
        if self.ui_initialized:
            for s in self.sink_list:
                name = s['name']

                if name == value:
                    idx = self.sink_list.index(s)

                    self.ui_output_device.set_active(idx)

                    self.soe.set_webrtc_probe_src(name + '.monitor')
        else:
            self.sie.set_output_sink_name(value)

            self.soe.set_webrtc_probe_src(value + '.monitor')

    def on_new_default_source(self, obj, value):
        if self.ui_initialized:
            for s in self.source_list:
                name = s['name']

                if name == self.pm.default_source_name:
                    idx = self.source_list.index(s)

                    self.ui_input_device.set_active(idx)
        else:
            self.soe.set_source_monitor_name(value)

    def update_output_dev_combobox(self):
        active_text = self.ui_output_device.get_active_text()

        use_default = self.settings.get_value('use-default-sink').unpack()

        self.ui_output_device.remove_all()

        if use_default:
            for s in self.sink_list:
                name = s['name']

                self.ui_output_device.append_text(name)

                if name == self.pm.default_sink_name:
                    idx = self.sink_list.index(s)

                    self.ui_output_device.set_active(idx)
        else:
            active_is_set = False

            for s in self.sink_list:
                name = s['name']

                self.ui_output_device.append_text(name)

                if not active_is_set:
                    if name == active_text:
                        idx = self.sink_list.index(s)

                        self.ui_output_device.set_active(idx)

                        active_is_set = True
                    elif name == self.pm.default_sink_name:
                        idx = self.sink_list.index(s)

                        self.ui_output_device.set_active(idx)

                        active_is_set = True

    def update_input_dev_combobox(self):
        active_text = self.ui_input_device.get_active_text()

        use_default = self.settings.get_value('use-default-source').unpack()

        self.ui_input_device.remove_all()

        if use_default:
            for s in self.source_list:
                name = s['name']

                self.ui_input_device.append_text(name)

                if name == self.pm.default_source_name:
                    idx = self.source_list.index(s)

                    self.ui_input_device.set_active(idx)
        else:
            active_is_set = False

            for s in self.source_list:
                name = s['name']

                self.ui_input_device.append_text(name)

                if not active_is_set:
                    if name == active_text:
                        idx = self.source_list.index(s)

                        self.ui_input_device.set_active(idx)

                        active_is_set = True
                    elif name == self.pm.default_source_name:
                        idx = self.source_list.index(s)

                        self.ui_input_device.set_active(idx)

                        active_is_set = True

    def on_sink_added(self, obj, sink):
        add_to_list = True

        for s in self.sink_list:
            if s['idx'] == sink['idx']:
                add_to_list = False

                break

        if add_to_list:
            self.sink_list.append(sink)

            self.log.debug(self.log_tag + 'added sink: ' + sink['name'])

            if self.ui_initialized:
                self.update_output_dev_combobox()

    def on_sink_removed(self, obj, idx):
        for s in self.sink_list:
            if s['idx'] == idx:
                name = s['name']

                self.sink_list.remove(s)

                self.log.debug(self.log_tag + 'removed sink: ' + name)

                if self.ui_initialized:
                    self.update_output_dev_combobox()

                break

    def on_source_added(self, obj, source):
        add_to_list = True

        for s in self.source_list:
            if s['idx'] == source['idx']:
                add_to_list = False

                break

        if add_to_list:
            self.source_list.append(source)

            self.log.debug(self.log_tag + 'added source: ' + source['name'])

            if self.ui_initialized:
                self.update_input_dev_combobox()

    def on_source_removed(self, obj, idx):
        for s in self.source_list:
            if s['idx'] == idx:
                name = s['name']

                self.source_list.remove(s)

                self.log.debug(self.log_tag + 'removed source: ' + name)

                if self.ui_initialized:
                    self.update_input_dev_combobox()

                break

    def on_use_default_sink_toggled(self, obj):
        state = obj.get_active()

        if state:
            for s in self.sink_list:
                name = s['name']

                if name == self.pm.default_sink_name:
                    idx = self.sink_list.index(s)

                    self.ui_output_device.set_active(idx)

            self.log.debug(self.log_tag + 'using default sink')

    def on_use_default_source_toggled(self, obj):
        state = obj.get_active()

        if state:
            for s in self.source_list:
                name = s['name']

                if name == self.pm.default_source_name:
                    idx = self.source_list.index(s)

                    self.ui_input_device.set_active(idx)

            self.log.debug(self.log_tag + 'using default source')

    def on_output_device_changed(self, obj):
        name = obj.get_active_text()

        for s in self.sink_list:
            if s['name'] == name:
                self.sie.set_output_sink_name(name)

                self.soe.set_webrtc_probe_src(name + '.monitor')

                self.log.debug(self.log_tag + 'output device changed: ' + name)

    def on_input_device_changed(self, obj):
        name = obj.get_active_text()

        for s in self.source_list:
            if s['name'] == name:
                self.soe.set_source_monitor_name(name)

                self.log.debug(self.log_tag + 'input device changed: ' + name)

    def apply_css_style(self, css_file):
        provider = Gtk.CssProvider()

        css_file = Gio.File.new_for_path(self.module_path + '/ui/' + css_file)

        provider.load_from_file(css_file)

        screen = Gdk.Screen.get_default()
        priority = Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION

        Gtk.StyleContext.add_provider_for_screen(screen, provider, priority)

    def on_test_clicked(self, obj):
        t = Test(self.pm)

        t.run()

    def on_reset_all_settings_clicked(self, obj):
        self.settings.reset('buffer-in')
        self.settings.reset('buffer-out')
        self.settings.reset('latency-in')
        self.settings.reset('latency-out')
        self.settings.reset('show-spectrum')
        self.settings.reset('spectrum-n-points')
        self.settings.reset('use-dark-theme')
        self.settings.reset('enable-all-apps')
        self.settings.reset('use-default-sink')
        self.settings.reset('use-default-source')

        self.init_spectrum_widgets()

        self.stack.set_visible_child(self.sie.ui_window)

        self.sie.reset()
        self.soe.reset()

    def onAbout(self, action, parameter):
        builder = Gtk.Builder()

        builder.add_from_file(self.module_path + '/ui/about.glade')

        dialog = builder.get_object('about_dialog')

        dialog.set_transient_for(self.window)

        dialog.run()

        dialog.destroy()
Пример #2
0
class Application(Gtk.Application):
    def __init__(self):
        app_id = 'com.github.wwmm.pulseeffects'
        app_flags = Gio.ApplicationFlags.HANDLES_COMMAND_LINE

        GLib.set_application_name('PulseEffects')
        GLib.setenv('PULSE_PROP_media.role', 'production', True)
        GLib.setenv('PULSE_PROP_application.icon_name', 'pulseeffects', True)

        Gtk.Application.__init__(self, application_id=app_id, flags=app_flags)

        self.add_main_option('no-window', ord('n'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, 'do not show window', None)

    def do_startup(self):
        Gtk.Application.do_startup(self)

        self.ui_initialized = False
        self.module_path = os.path.dirname(__file__)

        log_format = '%(asctime)s.%(msecs)d - %(name)s - %(levelname)s'
        log_format = log_format + ' - %(message)s'

        logging.basicConfig(format=log_format,
                            datefmt='%H:%M:%S',
                            level=logging.INFO)

        self.log = logging.getLogger('PulseEffects')

        self.gtk_settings = Gtk.Settings.get_default()

        self.settings = Gio.Settings('com.github.wwmm.pulseeffects')

        # creating user presets folder
        self.user_config_dir = os.path.join(GLib.get_user_config_dir(),
                                            'PulseEffects')
        os.makedirs(self.user_config_dir, exist_ok=True)

        # pulseaudio

        self.pm = PulseManager()
        self.pm.load_apps_sink()
        self.pm.load_mic_sink()

        # ui initializations

        self.builder = Gtk.Builder()

        self.builder.add_from_file(self.module_path + '/ui/main_ui.glade')

        self.builder.connect_signals(self)

        self.window = self.builder.get_object('MainWindow')
        self.server_info_label = self.builder.get_object('server_info_label')

        self.window.set_application(self)

        self.create_appmenu()

        # gstreamer sink input effects

        self.sie = SinkInputEffects(self.pm.default_sink_rate)
        self.sie.set_source_monitor_name(self.pm.apps_sink_monitor_name)
        self.sie.set_output_sink_name(self.pm.default_sink_name)

        # gstreamer source outputs effects

        self.soe = SourceOutputEffects(self.pm.default_source_rate)
        self.soe.set_source_monitor_name(self.pm.default_source_name)
        self.soe.set_output_sink_name('PulseEffects_mic')

        self.draw_spectrum = DrawSpectrum(self)

        self.init_theme()
        self.init_settings_menu()
        self.init_buffer_time()
        self.init_latency_time()

        self.list_sink_inputs = ListSinkInputs(self.sie, self.pm)
        self.list_source_outputs = ListSourceOutputs(self.soe, self.pm)

        self.list_sink_inputs.connect('app_removed', self.on_app_removed)
        self.list_source_outputs.connect('app_removed', self.on_app_removed)

        self.init_spectrum_widgets()
        self.init_stack_widgets()

        # this connection is changed inside the stack switch handler
        # depending on the selected child. The connection below is not
        # permanent but just a default

        self.spectrum_handler_id = self.sie.connect(
            'new_spectrum', self.draw_spectrum.on_new_spectrum)

        # searching for apps

        self.pm.find_sink_inputs()
        self.pm.find_source_outputs()

    def do_activate(self):
        self.window.present()

        self.ui_initialized = True

    def do_command_line(self, command_line):
        options = command_line.get_options_dict()

        if options.contains("no-window"):
            self.log.info('Running as a background service')
        else:
            self.activate()

        return 0

    def do_shutdown(self):
        Gtk.Application.do_shutdown(self)

        self.sie.set_state('null')
        self.soe.set_state('null')

        self.pm.exit()

    def create_appmenu(self):
        menu = Gio.Menu()

        menu.append('About', 'app.about')
        menu.append('Quit', 'app.quit')

        self.set_app_menu(menu)

        about_action = Gio.SimpleAction.new('about', None)
        about_action.connect('activate', self.onAbout)
        self.add_action(about_action)

        quit_action = Gio.SimpleAction.new('quit', None)
        quit_action.connect('activate', lambda action, parameter: self.quit())
        self.add_action(quit_action)

    def init_theme(self):
        switch = self.builder.get_object('theme_switch')

        use_dark = self.settings.get_value('use-dark-theme').unpack()

        switch.set_active(use_dark)

    def on_theme_switch_state_set(self, obj, state):
        self.gtk_settings.props.gtk_application_prefer_dark_theme = state

        out = GLib.Variant('b', state)
        self.settings.set_value('use-dark-theme', out)

    def init_stack_widgets(self):
        self.stack = self.builder.get_object('stack')

        self.stack.add_named(self.sie.ui_window, 'sink_inputs')
        self.stack.child_set_property(self.sie.ui_window, 'icon-name',
                                      'audio-speakers-symbolic')

        self.stack.add_named(self.soe.ui_window, "source_outputs")
        self.stack.child_set_property(self.soe.ui_window, 'icon-name',
                                      'audio-input-microphone-symbolic')

        self.stack_current_child_name = 'sink_inputs'

        server_info = str(self.pm.default_sink_format) + ', ' + \
            '{:.1f}'.format(round(
                self.pm.default_sink_rate / 1000.0, 1)) + ' kHz'

        self.server_info_label.set_text(server_info)

        def on_visible_child_changed(stack, visible_child):
            name = stack.get_visible_child_name()

            if name == 'sink_inputs':
                if self.stack_current_child_name == 'source_outputs':
                    self.soe.disconnect(self.spectrum_handler_id)

                self.spectrum_handler_id = self.sie.connect(
                    'new_spectrum', self.draw_spectrum.on_new_spectrum)

                self.stack_current_child_name = 'sink_inputs'

                server_info = str(self.pm.default_sink_format) + ', ' + \
                    '{:.1f}'.format(round(
                        self.pm.default_sink_rate / 1000.0, 1)) + ' kHz'

                self.server_info_label.set_text(server_info)
            elif name == 'source_outputs':
                if self.stack_current_child_name == 'sink_inputs':
                    self.sie.disconnect(self.spectrum_handler_id)

                self.spectrum_handler_id = self.soe.connect(
                    'new_spectrum', self.draw_spectrum.on_new_spectrum)

                self.stack_current_child_name = 'source_outputs'

                server_info = str(self.pm.default_source_format) + ', ' + \
                    '{:.1f}'.format(round(
                        self.pm.default_source_rate / 1000.0, 1)) + ' kHz'

                self.server_info_label.set_text(server_info)

            self.draw_spectrum.clear()

        self.stack.connect("notify::visible-child", on_visible_child_changed)

    def init_settings_menu(self):
        button = self.builder.get_object('settings_popover_button')
        menu = self.builder.get_object('settings_menu')

        popover = Gtk.Popover.new(button)
        popover.props.transitions_enabled = True
        popover.add(menu)

        def button_clicked(arg):
            if popover.get_visible():
                popover.hide()
            else:
                popover.show_all()

        button.connect("clicked", button_clicked)

    def init_buffer_time(self):
        value = self.settings.get_value('buffer-time').unpack()

        buffer_time = self.builder.get_object('buffer_time')

        buffer_time.set_value(value)

        self.sie.init_buffer_time(value * 1000)
        self.soe.init_buffer_time(value * 1000)

    def on_buffer_time_value_changed(self, obj):
        value = obj.get_value()

        out = GLib.Variant('i', value)
        self.settings.set_value('buffer-time', out)

        if self.ui_initialized:
            self.sie.set_buffer_time(value * 1000)
            self.soe.set_buffer_time(value * 1000)
        else:
            self.sie.init_buffer_time(value * 1000)
            self.soe.init_buffer_time(value * 1000)

    def init_latency_time(self):
        value = self.settings.get_value('latency-time').unpack()

        latency_time = self.builder.get_object('latency_time')

        latency_time.set_value(value)

        self.sie.init_latency_time(value * 1000)
        # self.soe.init_latency_time(value * 1000)

    def on_latency_time_value_changed(self, obj):
        value = obj.get_value()

        out = GLib.Variant('i', value)
        self.settings.set_value('latency-time', out)

        if self.ui_initialized:
            self.sie.set_latency_time(value * 1000)
            # self.soe.set_latency_time(value * 1000)
        else:
            self.sie.init_latency_time(value * 1000)
            # self.soe.init_latency_time(value * 1000)

    def init_spectrum_widgets(self):
        show_spectrum_switch = self.builder.get_object('show_spectrum')
        spectrum_n_points_obj = self.builder.get_object('spectrum_n_points')

        show_spectrum = self.settings.get_value('show-spectrum').unpack()
        spectrum_n_points = self.settings.get_value(
            'spectrum-n-points').unpack()

        show_spectrum_switch.set_active(show_spectrum)
        spectrum_n_points_obj.set_value(spectrum_n_points)

        self.sie.set_spectrum_n_points(spectrum_n_points)
        self.soe.set_spectrum_n_points(spectrum_n_points)

        if show_spectrum:
            self.draw_spectrum.show()
        else:
            self.draw_spectrum.hide()

    def on_show_spectrum_state_set(self, obj, state):
        if state:
            self.draw_spectrum.show()
            self.sie.enable_spectrum(True)
            self.soe.enable_spectrum(True)
        else:
            self.sie.enable_spectrum(False)
            self.soe.enable_spectrum(False)
            self.draw_spectrum.hide()

        out = GLib.Variant('b', state)
        self.settings.set_value('show-spectrum', out)

    def on_spectrum_n_points_value_changed(self, obj):
        value = obj.get_value()

        out = GLib.Variant('i', value)
        self.settings.set_value('spectrum-n-points', out)

        self.sie.set_spectrum_n_points(value)
        self.soe.set_spectrum_n_points(value)

    def on_app_removed(self, obj):
        self.window.resize(1, 1)

    def on_reset_all_settings_clicked(self, obj):
        self.settings.reset('buffer-time')
        self.settings.reset('latency-time')
        self.settings.reset('show-spectrum')
        self.settings.reset('spectrum-n-points')
        self.settings.reset('use-dark-theme')

        self.init_theme()
        self.init_buffer_time()
        self.init_latency_time()
        self.init_spectrum_widgets()

        self.stack.set_visible_child(self.sie.ui_window)

        self.sie.reset()
        self.soe.reset()

    def add_file_filter(self, dialog):
        file_filter = Gtk.FileFilter()
        file_filter.set_name("preset")
        file_filter.add_mime_type("text/plain")

        dialog.add_filter(file_filter)

    def on_save_user_preset_clicked(self, obj):
        dialog = Gtk.FileChooserDialog(
            '', self.window, Gtk.FileChooserAction.SAVE,
            (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE,
             Gtk.ResponseType.OK))

        dialog.set_current_folder(self.user_config_dir)
        dialog.set_current_name('user.preset')

        self.add_file_filter(dialog)

        response = dialog.run()

        if response == Gtk.ResponseType.OK:
            path = dialog.get_filename()

            if not path.endswith(".preset"):
                path += ".preset"

            s = SavePresets(path)

            s.save_sink_inputs_presets(self.sie.settings)
            s.save_source_outputs_presets(self.soe.settings)

            s.write_config()

        dialog.destroy()

    def on_load_user_preset_clicked(self, obj):
        dialog = Gtk.FileChooserDialog(
            '', self.window, Gtk.FileChooserAction.OPEN,
            (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN,
             Gtk.ResponseType.OK))

        dialog.set_current_folder(self.user_config_dir)

        self.add_file_filter(dialog)

        response = dialog.run()

        if response == Gtk.ResponseType.OK:
            path = dialog.get_filename()

            l = LoadPresets(path)

            l.load_sink_inputs_presets(self.sie.settings)
            l.load_source_outputs_presets(self.soe.settings)

        dialog.destroy()

    def onAbout(self, action, parameter):
        builder = Gtk.Builder()

        builder.add_from_file(self.module_path + '/ui/about.glade')

        dialog = builder.get_object('about_dialog')

        dialog.set_transient_for(self.window)

        dialog.run()

        dialog.destroy()
Пример #3
0
class Application(Gtk.Application):
    def __init__(self):
        app_id = 'com.github.wwmm.pulseeffects'
        app_flags = Gio.ApplicationFlags.HANDLES_COMMAND_LINE

        GLib.set_application_name('PulseEffects')
        GLib.setenv('PULSE_PROP_application.icon_name', 'pulseeffects', True)
        GLib.setenv('PULSE_PROP_application.id',
                    'com.github.wwmm.pulseeffects', True)
        GLib.setenv('PULSE_PROP_application.version', '3.2.1', True)

        Gtk.Application.__init__(self, application_id=app_id, flags=app_flags)

        GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, 2, self.quit)  # sigint

        help_msg = _('Quit PulseEffects. Useful when running in service mode.')

        self.add_main_option('quit', ord('q'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, help_msg, None)

        help_msg = _('Show available presets.')

        self.add_main_option('presets', ord('p'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, help_msg, None)

        help_msg = _('Load a preset. Example: pulseeffects -l music')

        self.add_main_option('load-preset', ord('l'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.STRING, help_msg, None)

    def do_startup(self):
        Gtk.Application.do_startup(self)

        self.ui_initialized = False
        self.running_as_service = False
        self.module_path = os.path.dirname(__file__)

        log_format = '%(asctime)s.%(msecs)d - %(name)s - %(levelname)s'
        log_format = log_format + ' - %(message)s'

        logging.basicConfig(format=log_format,
                            datefmt='%H:%M:%S',
                            level=logging.INFO)

        self.log = logging.getLogger('PulseEffects')

        self.gtk_settings = Gtk.Settings.get_default()

        self.settings = Gio.Settings('com.github.wwmm.pulseeffects')

        # creating user presets folder
        self.user_config_dir = os.path.join(GLib.get_user_config_dir(),
                                            'PulseEffects')
        os.makedirs(self.user_config_dir, exist_ok=True)

        # autostart file path
        autostart_file_name = 'autostart/pulseeffects-service.desktop'

        self.autostart_file = os.path.join(GLib.get_user_config_dir(),
                                           autostart_file_name)

        self.create_appmenu()

        # custom css styles

        self.apply_css_style('listbox.css')

        icon_theme = Gtk.IconTheme.get_default()
        icon_theme.append_search_path(self.module_path + '/ui')

        # pulseaudio

        self.pm = PulseManager()
        self.pm.load_apps_sink()
        self.pm.load_mic_sink()

        self.sie = SinkInputEffects(self.pm)
        self.soe = SourceOutputEffects(self.pm)

        if self.props.flags & Gio.ApplicationFlags.IS_SERVICE:
            self.running_as_service = True

            self.init_ui()

            self.sie.post_messages(False)
            self.soe.post_messages(False)

            self.pm.find_sink_inputs()
            self.pm.find_source_outputs()

            self.log.info(_('Running in background'))

            self.hold()

    def init_ui(self):
        self.builder = Gtk.Builder.new_from_file(self.module_path +
                                                 '/ui/main_ui.glade')

        self.builder.connect_signals(self)

        self.window = self.builder.get_object('MainWindow')
        self.window.set_application(self)
        self.window.connect('destroy', self.on_window_destroy)

        image_test = self.builder.get_object('image_test')

        image_test.set_from_icon_name('pulseeffects-sine-symbolic',
                                      Gtk.IconSize.BUTTON)

        self.sie.init_ui()
        self.soe.init_ui()

        self.draw_spectrum = DrawSpectrum(self)

        self.init_settings_menu()
        self.init_buffer_time()
        self.init_latency_time()
        self.init_spectrum_widgets()
        self.init_stack_widgets()
        self.init_autostart_switch()

        # Gsettings bindings

        flag = Gio.SettingsBindFlags.DEFAULT

        switch_apps = self.builder.get_object('enable_all_apps')
        switch_theme = self.builder.get_object('theme_switch')

        self.settings.bind('use-dark-theme', switch_theme, 'active', flag)
        self.settings.bind('use-dark-theme', self.gtk_settings,
                           'gtk_application_prefer_dark_theme', flag)
        self.settings.bind('enable-all-apps', switch_apps, 'active', flag)
        self.settings.bind('enable-all-apps', self.sie, 'switch_on_all_apps',
                           flag)
        self.settings.bind('enable-all-apps', self.soe, 'switch_on_all_apps',
                           flag)

        # this connection is changed inside the stack switch handler
        # depending on the selected child. The connection below is not
        # permanent but just a default

        self.spectrum_handler_id = self.sie.connect(
            'new_spectrum', self.draw_spectrum.on_new_spectrum)

        self.presets = PresetsManager(self)

        self.ui_initialized = True
        self.sie.there_is_window = True
        self.soe.there_is_window = True

    def on_window_destroy(self, window):
        self.ui_initialized = False
        self.sie.there_is_window = False
        self.soe.there_is_window = False

        if self.running_as_service:
            self.sie.post_messages(False)
            self.soe.post_messages(False)

    def do_activate(self):
        if not self.ui_initialized:
            self.init_ui()

            self.pm.find_sink_inputs()
            self.pm.find_source_outputs()

        self.window.present()

        self.sie.post_messages(True)
        self.soe.post_messages(True)

    def do_command_line(self, command_line):
        options = command_line.get_options_dict()

        if options.contains('quit'):
            self.quit()
        elif options.contains('presets'):
            self.presets.list_presets()
        elif options.contains('load-preset'):
            value = options.lookup_value('load-preset', None).unpack()

            self.presets.load_preset(value)
        else:
            self.do_activate()

        return Gtk.Application.do_command_line(self, command_line)

    def do_shutdown(self):
        Gtk.Application.do_shutdown(self)

        self.sie.set_state('null')
        self.soe.set_state('null')

        self.pm.exit()

    def create_appmenu(self):
        menu = Gio.Menu()

        menu.append('About', 'app.about')
        menu.append('Quit', 'app.quit')

        self.set_app_menu(menu)

        about_action = Gio.SimpleAction.new('about', None)
        about_action.connect('activate', self.onAbout)
        self.add_action(about_action)

        quit_action = Gio.SimpleAction.new('quit', None)
        quit_action.connect('activate', lambda action, parameter: self.quit())
        self.add_action(quit_action)

    def init_stack_widgets(self):
        self.stack = self.builder.get_object('stack')

        self.stack.add_named(self.sie.ui_window, 'sink_inputs')
        self.stack.child_set_property(self.sie.ui_window, 'icon-name',
                                      'audio-speakers-symbolic')

        self.stack.add_named(self.soe.ui_window, "source_outputs")
        self.stack.child_set_property(self.soe.ui_window, 'icon-name',
                                      'audio-input-microphone-symbolic')

        self.stack_current_child_name = 'sink_inputs'

        self.stack.connect("notify::visible-child",
                           self.on_stack_visible_child_changed)

    def on_stack_visible_child_changed(self, stack, visible_child):
        name = stack.get_visible_child_name()

        if name == 'sink_inputs':
            if self.stack_current_child_name == 'source_outputs':
                self.soe.disconnect(self.spectrum_handler_id)

            self.spectrum_handler_id = self.sie.connect(
                'new_spectrum', self.draw_spectrum.on_new_spectrum)

            self.stack_current_child_name = 'sink_inputs'
        elif name == 'source_outputs':
            if self.stack_current_child_name == 'sink_inputs':
                self.sie.disconnect(self.spectrum_handler_id)

            self.spectrum_handler_id = self.soe.connect(
                'new_spectrum', self.draw_spectrum.on_new_spectrum)

            self.stack_current_child_name = 'source_outputs'

        self.draw_spectrum.clear()

    def init_settings_menu(self):
        button = self.builder.get_object('settings_popover_button')
        menu = self.builder.get_object('settings_menu')

        popover = Gtk.Popover.new(button)
        popover.props.transitions_enabled = True
        popover.add(menu)

        def button_clicked(arg):
            if popover.get_visible():
                popover.hide()
            else:
                popover.show_all()

        button.connect("clicked", button_clicked)

    def init_buffer_time(self):
        value = self.settings.get_value('buffer-time').unpack()

        buffer_time = self.builder.get_object('buffer_time')

        buffer_time.set_value(value)

        self.sie.init_buffer_time(value * 1000)

    def on_buffer_time_value_changed(self, obj):
        value = obj.get_value()

        out = GLib.Variant('i', value)
        self.settings.set_value('buffer-time', out)

        if self.ui_initialized:
            self.sie.set_buffer_time(value * 1000)
        else:
            self.sie.init_buffer_time(value * 1000)

    def init_latency_time(self):
        value = self.settings.get_value('latency-time').unpack()

        latency_time = self.builder.get_object('latency_time')

        latency_time.set_value(value)

        self.sie.init_latency_time(value * 1000)

    def on_latency_time_value_changed(self, obj):
        value = obj.get_value()

        out = GLib.Variant('i', value)
        self.settings.set_value('latency-time', out)

        if self.ui_initialized:
            self.sie.set_latency_time(value * 1000)
        else:
            self.sie.init_latency_time(value * 1000)

    def init_autostart_switch(self):
        switch = self.builder.get_object('enable_autostart')

        if os.path.isfile(self.autostart_file):
            switch.set_state(True)
        else:
            switch.set_state(False)

    def on_enable_autostart_state_set(self, obj, state):
        if state:
            with open(self.autostart_file, "w") as f:
                f.write('[Desktop Entry]\n')
                f.write('Name=PulseEffects\n')
                f.write('Comment=PulseEffects Service\n')
                f.write('Exec=pulseeffects --gapplication-service\n')
                f.write('Icon=pulseeffects\n')
                f.write('StartupNotify=false\n')
                f.write('Terminal=false\n')
                f.write('Type=Application\n')
        else:
            os.remove(self.autostart_file)

    def init_spectrum_widgets(self):
        show_spectrum_switch = self.builder.get_object('show_spectrum')
        spectrum_n_points_obj = self.builder.get_object('spectrum_n_points')

        show_spectrum = self.settings.get_value('show-spectrum').unpack()
        spectrum_n_points = self.settings.get_value(
            'spectrum-n-points').unpack()

        show_spectrum_switch.set_active(show_spectrum)
        spectrum_n_points_obj.set_value(spectrum_n_points)

        self.sie.set_spectrum_n_points(spectrum_n_points)
        self.soe.set_spectrum_n_points(spectrum_n_points)

        if show_spectrum:
            self.draw_spectrum.show()
        else:
            self.draw_spectrum.hide()

    def on_show_spectrum_state_set(self, obj, state):
        if state:
            self.draw_spectrum.show()
            self.sie.enable_spectrum(True)
            self.soe.enable_spectrum(True)
        else:
            self.sie.enable_spectrum(False)
            self.soe.enable_spectrum(False)
            self.draw_spectrum.hide()

        out = GLib.Variant('b', state)
        self.settings.set_value('show-spectrum', out)

    def on_spectrum_n_points_value_changed(self, obj):
        value = obj.get_value()

        out = GLib.Variant('i', value)
        self.settings.set_value('spectrum-n-points', out)

        self.sie.set_spectrum_n_points(value)
        self.soe.set_spectrum_n_points(value)

    def apply_css_style(self, css_file):
        provider = Gtk.CssProvider()

        css_file = Gio.File.new_for_path(self.module_path + '/ui/' + css_file)

        provider.load_from_file(css_file)

        screen = Gdk.Screen.get_default()
        priority = Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION

        Gtk.StyleContext.add_provider_for_screen(screen, provider, priority)

    def on_test_clicked(self, obj):
        t = Test(self.pm)

        t.run()

    def on_reset_all_settings_clicked(self, obj):
        self.settings.reset('buffer-time')
        self.settings.reset('latency-time')
        self.settings.reset('show-spectrum')
        self.settings.reset('spectrum-n-points')
        self.settings.reset('use-dark-theme')
        self.settings.reset('enable-all-apps')

        self.init_buffer_time()
        self.init_latency_time()
        self.init_spectrum_widgets()

        self.stack.set_visible_child(self.sie.ui_window)

        self.sie.reset()
        self.soe.reset()

    def onAbout(self, action, parameter):
        builder = Gtk.Builder()

        builder.add_from_file(self.module_path + '/ui/about.glade')

        dialog = builder.get_object('about_dialog')

        dialog.set_transient_for(self.window)

        dialog.run()

        dialog.destroy()