コード例 #1
0
def find_desktop_files(dirs: List[str] = None, pattern: str = "*.desktop") -> Generator[str, None, None]:
    """
    Returns deduped list of .desktop files (full paths)

    :param list dirs:
    :rtype: list
    """

    if dirs is None:
        dirs = DESKTOP_DIRS

    # pylint: disable=cell-var-from-loop
    all_files = chain.from_iterable(
        map(lambda f: os.path.join(f_path, f), find_files(f_path, pattern)) for f_path in dirs)

    # dedup desktop file according to follow XDG data dir order
    # specifically the first file name (i.e. firefox.desktop) take precedence
    # and other files with the same name should be ignored
    deduped_file_dict = OrderedDict()  # type: OrderedDict
    for file_path in all_files:
        file_name = os.path.basename(file_path)
        if file_name not in deduped_file_dict:
            deduped_file_dict[file_name] = file_path
    deduped_files = deduped_file_dict.values()

    blacklisted_dirs_srt = Settings.get_instance().get_property('blacklisted-desktop-dirs')
    blacklisted_dirs = blacklisted_dirs_srt.split(':') if blacklisted_dirs_srt else []
    for file in deduped_files:
        try:
            if any([file.startswith(dir) for dir in blacklisted_dirs]):
                continue
        except UnicodeDecodeError:
            continue

        yield file
コード例 #2
0
    def finish_initializing(self, builder):
        """
        Set up the main window
        """
        super(UlauncherWindow, self).finish_initializing(builder)

        self.results_nav = None
        self.builder = builder
        self.window = self.get_widget('ulauncher_window')
        self.input = self.get_widget('input')
        self.prefs_btn = self.get_widget('prefs_btn')
        self.result_box = builder.get_object("result_box")

        self.input.connect('changed', self.on_input_changed)
        self.prefs_btn.connect('clicked', self.on_mnu_preferences_activate)

        self.set_keep_above(True)

        self.AboutDialog = AboutUlauncherDialog
        self.PreferencesDialog = PreferencesUlauncherDialog

        self.position_window()
        self.init_styles()

        # bind hotkey
        Keybinder.init()
        accel_name = Settings.get_instance().get_property('hotkey-show-app')
        # bind in the main thread
        GLib.idle_add(self.bind_show_app_hotkey, accel_name)

        start_app_watcher()
コード例 #3
0
    def finish_initializing(self, builder):
        """Called while initializing this instance in __new__

        finish_initalizing should be called after parsing the ui definition
        and creating a PreferencesDialog object with it in order to
        finish initializing the start of the new PerferencesUlauncherDialog
        instance.

        Put your initialization code in here and leave __init__ undefined.
        """

        # Get a reference to the builder and set up the signals.
        self.builder = builder
        self.ui = builder.get_ui(self, True)

        # unnecessary action area can be removed only manually, like this
        self.ui['dialog_action_area'].destroy()

        self.settings = Settings.get_instance()
        self._init_webview()
        self.init_styles(get_data_file('styles', 'preferences.css'))
        self._handle_no_window_shadow_option(self.ui['window_wrapper'])
        self.autostart_pref = AutostartPreference()
        self.hotkey_dialog = HotkeyDialog()
        self.hotkey_dialog.connect('hotkey-set', self.on_hotkey_set)

        self.show_all()
コード例 #4
0
ファイル: ResultWidget.py プロジェクト: mat1010/Ulauncher
 def set_index(self, index):
     """
     Set index for the item and assign shortcut
     """
     jump_keys = Settings.get_instance().get_jump_keys()
     if index < len(jump_keys):
         self.index = index
         self.set_shortcut(f"Alt+{jump_keys[index]}")
コード例 #5
0
 def __init__(self, application):
     self.application = application
     self.autostart_pref = UlauncherSystemdController()
     self.settings = Settings.get_instance()
     self.context = WebKit2.WebContext()
     self.context.register_uri_scheme('prefs', self.on_scheme_callback)
     self.context.register_uri_scheme('file2', self.serve_file)
     self.context.set_cache_model(
         WebKit2.CacheModel.DOCUMENT_VIEWER)  # disable caching
コード例 #6
0
ファイル: Theme.py プロジェクト: Ulauncher/Ulauncher
    def get_current(cls):
        default = 'light'
        current_name = Settings.get_instance().get_property(
            'theme-name') or default
        if current_name not in themes:
            logger.warning('No theme with name %s', current_name)
            current_name = default

        return themes.get(current_name)
コード例 #7
0
ファイル: main.py プロジェクト: lakedai/Ulauncher
def main():
    """
    Main function that starts everything
    """

    # start DBus loop
    DBusGMainLoop(set_as_default=True)
    bus = dbus.SessionBus()
    instance = bus.request_name(DBUS_SERVICE)

    if instance != dbus.bus.REQUEST_NAME_REPLY_PRIMARY_OWNER:
        print(
            "DBus name already taken. Ulauncher is probably backgrounded. Did you mean `ulauncher-toggle`?",
            file=sys.stderr)
        toggle_window = dbus.SessionBus().get_object(
            DBUS_SERVICE, DBUS_PATH).get_dbus_method("toggle_window")
        toggle_window()
        return

    _create_dirs()

    options = get_options()
    setup_logging(options)
    logger = logging.getLogger('ulauncher')
    logger.info('Ulauncher version %s', get_version())
    logger.info('Extension API version %s', api_version)
    logger.info("GTK+ %s.%s.%s", Gtk.get_major_version(),
                Gtk.get_minor_version(), Gtk.get_micro_version())
    logger.info("Is Wayland: %s", is_wayland())
    logger.info("Wayland compatibility: %s",
                ('on' if is_wayland_compatibility_on() else 'off'))

    # log uncaught exceptions
    def except_hook(exctype, value, tb):
        logger.error("Uncaught exception", exc_info=(exctype, value, tb))

    sys.excepthook = except_hook

    window = UlauncherWindow.get_instance()
    UlauncherDbusService(window)
    if not options.hide_window:
        window.show()

    if Settings.get_instance().get_property('show-indicator-icon'):
        AppIndicator.get_instance().show()

    # workaround to make Ctrl+C quitting the app
    signal_handler = SignalHandler(window)
    gtk_thread = run_async(Gtk.main)()
    try:
        while gtk_thread.is_alive() and not signal_handler.killed():
            time.sleep(0.5)
    except KeyboardInterrupt:
        logger.warning('On KeyboardInterrupt')
    finally:
        Gtk.main_quit()
コード例 #8
0
ファイル: Theme.py プロジェクト: voneAbides/Ulauncher
    def get_current(cls):
        default = 'light'
        current_name = Settings.get_instance().get_property('theme-name') or default
        try:
            current = themes[current_name]
        except KeyError:
            logger.warning('No theme with name %s', current_name)
            current = themes[default]

        return current
コード例 #9
0
    def finish_initializing(self, builder):
        """Set up the preferences dialog"""
        super(PreferencesUlauncherDialog, self).finish_initializing(builder)

        # unnecessary action area can be removed only manually, like this
        self.builder.get_object('dialog_action_area').destroy()

        self.settings = Settings.get_instance()
        self.__init_indicator_switch()
        self.__init_app_hotkey()
        self.__init_autostart_switch()
コード例 #10
0
 def __init__(self):
     super().__init__(
         title="Ulauncher Preferences",
         window_position=Gtk.WindowPosition.CENTER,
     )
     self.connect("key-press-event", self.on_key_press)
     self.connect("delete-event", self.on_delete)
     self.set_default_size(1000, 600)
     self.settings = Settings.get_instance()
     self._init_webview()
     self.autostart_pref = AutostartPreference()
     self.hotkey_dialog = HotkeyDialog()
     self.hotkey_dialog.connect('hotkey-set', self.on_hotkey_set)
     self.show_all()
コード例 #11
0
ファイル: UlauncherWindow.py プロジェクト: sahwar/Ulauncher
    def finish_initializing(self, builder):
        """Called while initializing this instance in __new__

        finish_initializing should be called after parsing the UI definition
        and creating a UlauncherWindow object with it in order to finish
        initializing the start of the new UlauncherWindow instance.
        """
        # Get a reference to the builder and set up the signals.
        self.builder = builder
        self.ui = builder.get_ui(self, True)
        self.PreferencesDialog = None  # class
        self.preferences_dialog = None  # instance

        self.results_nav = None
        self.window = self.ui['ulauncher_window']
        self.window_body = self.ui['body']
        self.input = self.ui['input']
        self.prefs_btn = self.ui['prefs_btn']
        self.result_box = self.ui["result_box"]

        self.input.connect('changed', self.on_input_changed)
        self.prefs_btn.connect('clicked', self.on_mnu_preferences_activate)

        self.set_keep_above(True)

        self.PreferencesDialog = PreferencesUlauncherDialog
        self.settings = Settings.get_instance()

        self.fix_window_width()
        self.position_window()
        self.init_theme()

        # this will trigger to show frequent apps if necessary
        self.show_results([])

        if not is_wayland_compatibility_on():
            # bind hotkey
            Keybinder.init()
            accel_name = self.settings.get_property('hotkey-show-app')
            # bind in the main thread
            GLib.idle_add(self.bind_show_app_hotkey, accel_name)

        start_app_watcher()
        ExtensionServer.get_instance().start()
        time.sleep(0.01)
        ExtensionRunner.get_instance().run_all()
        if not get_options().no_extensions:
            ExtensionDownloader.get_instance().download_missing()
コード例 #12
0
    def finish_initializing(self, ui):
        # pylint: disable=attribute-defined-outside-init
        self.ui = ui
        self.preferences = None  # instance

        self.results_nav = None
        self.window_body = self.ui['body']
        self.input = self.ui['input']
        self.prefs_btn = self.ui['prefs_btn']
        self.result_box = self.ui["result_box"]
        self.scroll_container = self.ui["result_box_scroll_container"]

        self.input.connect('changed', self.on_input_changed)
        self.prefs_btn.connect('clicked', self.on_mnu_preferences_activate)

        self.set_keep_above(True)

        self.settings = Settings.get_instance()

        self.fix_window_width()
        self.position_window()
        self.init_theme()

        # this will trigger to show frequent apps if necessary
        self.show_results([])

        self.connect('button-press-event', self.mouse_down_event)
        self.connect('button-release-event', self.mouse_up_event)
        self.connect('motion_notify_event', self.mouse_move_event)

        if self.settings.get_property('show-indicator-icon'):
            AppIndicator.get_instance(self).show()

        if IS_X11:
            # bind hotkey
            Keybinder.init()
            accel_name = self.settings.get_property('hotkey-show-app')
            # bind in the main thread
            GLib.idle_add(self.bind_hotkey, accel_name)

        ExtensionServer.get_instance().start()
        time.sleep(0.01)
        ExtensionRunner.get_instance().run_all()
        if not get_options().no_extensions:
            ExtensionDownloader.get_instance().download_missing()
コード例 #13
0
def main():
    _create_dirs()

    options = parse_options()
    set_up_logging(options)
    logger = logging.getLogger('ulauncher')
    logger.info('Ulauncher version: %s' % get_version())

    # start DBus loop
    DBusGMainLoop(set_as_default=True)
    bus = dbus.SessionBus()
    instance = bus.request_name(DBUS_SERVICE)

    if instance != dbus.bus.REQUEST_NAME_REPLY_PRIMARY_OWNER:
        logger.debug("Getting the existing instance...")
        show_window = dbus.SessionBus().get_object(
            DBUS_SERVICE, DBUS_PATH).get_dbus_method("show_window")
        show_window()
    else:
        logger.debug("Starting a new instance...")
        window = UlauncherWindow.get_instance()
        UlauncherDbusService(window)
        if not options.hide_window:
            window.show()

        if Settings.get_instance().get_property('show-indicator-icon'):
            AppIndicator.get_instance().show()

        @run_async
        def run_main():
            Gtk.main()

        main_thread = run_main()

        # workaround to make Ctrl+C quiting the app
        try:
            while main_thread.is_alive():
                time.sleep(1)
        except KeyboardInterrupt:
            logger.info('On KeyboardInterrupt')
            Gtk.main_quit()
            main_thread.join()

    sys.exit(0)
コード例 #14
0
class UlauncherApp(Gtk.Application):
    # Gtk.Applications check if the app is already registered and if so,
    # new instances sends the signals to the registered one
    # So all methods except __init__ runs on the main app
    settings = Settings.get_instance()
    window = None  # type: UlauncherWindow
    appindicator = None  # type: AppIndicator
    _current_accel_name = None

    def __init__(self, *args, **kwargs):
        super().__init__(
            *args,
            application_id="net.launchpad.ulauncher",
            flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE,
            **kwargs
        )
        self.connect("startup", self.setup)  # runs only once on the main instance

    def do_before_emit(self, data):
        query = data.lookup_value("query", GLib.VariantType("s"))
        if query:
            self.window.initial_query = query.unpack()

    def do_activate(self, *args, **kwargs):
        self.window.show_window()

    def do_command_line(self, *args, **kwargs):
        # This is where we handle "--no-window" which we need to get from the remote call
        # All other aguments are persistent and handled in config.get_options()
        parser = argparse.ArgumentParser(prog='gui')
        parser.add_argument("--no-window", action="store_true")
        args, _ = parser.parse_known_args(args[0].get_arguments()[1:])

        if not args.no_window:
            self.activate()

        return 0

    def setup(self, _):
        self.hold()  # Keep the app running even without a window
        self.window = window = UlauncherWindow.get_instance()
        window.set_application(self)
        window.set_keep_above(True)
        window.position_window()
        window.init_theme()

        # this will trigger to show frequent apps if necessary
        window.show_results([])

        if self.settings.get_property('show-indicator-icon'):
            self.appindicator = AppIndicator(self)
            self.appindicator.switch(True)

        if IS_X11:
            # bind hotkey
            Keybinder.init()
            accel_name = self.settings.get_property('hotkey-show-app')
            # bind in the main thread
            GLib.idle_add(self.bind_hotkey, accel_name)

        ExtensionServer.get_instance().start()
        time.sleep(0.01)
        ExtensionRunner.get_instance().run_all()

    def toggle_appindicator(self, enable):
        if not self.appindicator:
            self.appindicator = AppIndicator(self)
        self.appindicator.switch(enable)

    def bind_hotkey(self, accel_name):
        if not IS_X11 or self._current_accel_name == accel_name:
            return

        if self._current_accel_name:
            Keybinder.unbind(self._current_accel_name)
            self._current_accel_name = None

        logger.info("Trying to bind app hotkey: %s", accel_name)
        Keybinder.bind(accel_name, lambda _: self.window.show_window())
        self._current_accel_name = accel_name
        if FIRST_RUN:
            display_name = Gtk.accelerator_get_label(*Gtk.accelerator_parse(accel_name))
            show_notification("Ulauncher", f"Hotkey is set to {display_name}")

    def show_preferences(self, page=None):
        self.window.hide()
        if not str or not isinstance(page, str):
            page = 'preferences'

        PreferencesWindow(application=self).show(page=page)
コード例 #15
0
class UlauncherWindow(Gtk.ApplicationWindow):
    __gtype_name__ = "UlauncherWindow"
    input: Gtk.Entry  # These have to be declared on a separate line for some reason
    prefs_btn: Gtk.Button
    result_box: Gtk.Box
    scroll_container: Gtk.ScrolledWindow
    window_body: Gtk.Box

    input = Gtk.Template.Child("input")
    prefs_btn = Gtk.Template.Child("prefs_btn")
    result_box = Gtk.Template.Child("result_box")
    scroll_container = Gtk.Template.Child("result_box_scroll_container")
    window_body = Gtk.Template.Child("body")
    results_nav = None
    settings = Settings.get_instance()
    is_focused = False
    initial_query = None
    _css_provider = None
    _drag_start_coords = None

    @classmethod
    @singleton
    def get_instance(cls):
        return cls()

    ######################################
    # GTK Signal Handlers
    ######################################

    @Gtk.Template.Callback()
    def on_focus_out(self, widget, event):
        # apparently Gtk doesn't provide a mechanism to tell if window is in focus
        # this is a simple workaround to avoid hiding window
        # when user hits Alt+key combination or changes input source, etc.
        self.is_focused = False
        timer(0.07, lambda: self.is_focused or self.hide())

    @Gtk.Template.Callback()
    def on_focus_in(self, *args):
        if self.settings.get_property('grab-mouse-pointer'):
            ptr_dev = self.get_pointer_device()
            result = ptr_dev.grab(self.get_window(), Gdk.GrabOwnership.NONE,
                                  True, Gdk.EventMask.ALL_EVENTS_MASK, None, 0)
            logger.debug("Focus in event, grabbing pointer: %s", result)
        self.is_focused = True

    @Gtk.Template.Callback()
    def on_input_changed(self, entry):
        """
        Triggered by user input
        """
        query = self._get_user_query()
        # This might seem odd, but this makes sure any normalization done in get_user_query() is
        # reflected in the input box. In particular, stripping out the leading white-space.
        self.input.set_text(query)
        ModeHandler.get_instance().on_query_change(query)

    @Gtk.Template.Callback()
    def on_input_key_press(self, widget, event):
        keyval = event.get_keyval()
        keyname = Gdk.keyval_name(keyval[1])
        alt = event.state & Gdk.ModifierType.MOD1_MASK
        ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
        jump_keys = self.settings.get_jump_keys()
        ModeHandler.get_instance().on_key_press_event(widget, event,
                                                      self._get_user_query())

        if keyname == 'Escape':
            self.hide()

        elif ctrl and keyname == 'comma':
            self.show_preferences()

        elif self.results_nav:
            if keyname in ('Up', 'ISO_Left_Tab') or (ctrl and keyname == 'p'):
                self.results_nav.go_up()
                return True
            if keyname in ('Down', 'Tab') or (ctrl and keyname == 'n'):
                self.results_nav.go_down()
                return True
            if alt and keyname in ('Return', 'KP_Enter'):
                self.enter_result(alt=True)
            elif keyname in ('Return', 'KP_Enter'):
                self.enter_result()
            elif alt and keyname in jump_keys:
                # on Alt+<num/letter>
                try:
                    self.select_result(jump_keys.index(keyname))
                except IndexError:
                    # selected non-existing result item
                    pass

        return False

    ######################################
    # Helpers
    ######################################

    def get_input(self):
        return self.input

    def init_styles(self, path):
        if not self._css_provider:
            self._css_provider = Gtk.CssProvider()
        self._css_provider.load_from_path(path)
        self.apply_css(self)
        # pylint: disable=no-member
        visual = self.get_screen().get_rgba_visual()
        if visual:
            self.set_visual(visual)

    def apply_css(self, widget):
        Gtk.StyleContext.add_provider(widget.get_style_context(),
                                      self._css_provider,
                                      Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
        if isinstance(widget, Gtk.Container):
            widget.forall(self.apply_css)

    def set_cursor(self, cursor_name):
        # pylint: disable=no-member
        window_ = self.get_window()
        cursor = Gdk.Cursor.new_from_name(window_.get_display(), cursor_name)
        window_.set_cursor(cursor)

    def init_theme(self):
        load_available_themes()
        theme = Theme.get_current()
        theme.clear_cache()

        if self.settings.get_property('disable-window-shadow'):
            self.window_body.get_style_context().add_class('no-window-shadow')

        self._render_prefs_icon()
        self.init_styles(theme.compile_css())

    @Gtk.Template.Callback()
    def show_preferences(self, *_):
        self.get_application().show_preferences()

    def position_window(self):
        monitor = get_monitor(
            self.settings.get_property('render-on-screen') != "default-monitor"
        )
        geo = monitor.get_geometry()
        max_height = geo.height - (
            geo.height *
            0.15) - 100  # 100 is roughly the height of the text input
        window_width = 500 * get_scaling_factor()
        self.set_property('width-request', window_width)
        self.scroll_container.set_property('max-content-height', max_height)
        self.move(geo.width * 0.5 - window_width * 0.5 + geo.x,
                  geo.y + geo.height * 0.12)

    def show_window(self):
        # works only when the following methods are called in that exact order
        self.present()
        self.position_window()
        if IS_X11_COMPATIBLE:
            self.present_with_time(Keybinder.get_current_event_time())

        if self.initial_query:
            self.input.set_text(self.initial_query)
            self.input.set_position(len(self.initial_query))
            self.initial_query = None
        elif not self._get_input_text():
            # make sure frequent apps are shown if necessary
            self.show_results([])
        elif self.settings.get_property('clear-previous-query'):
            self.input.set_text('')
        else:
            self.input.grab_focus()

    @Gtk.Template.Callback()
    def on_mouse_down(self, _, event):
        """
        Prepare moving the window if the user drags
        """
        # Only on left clicks and not on the results
        if event.button == 1 and event.y < 100:
            self.set_cursor("grab")
            self._drag_start_coords = {'x': event.x, 'y': event.y}

    @Gtk.Template.Callback()
    def on_mouse_up(self, *_):
        """
        Clear drag to move event data
        """
        self._drag_start_coords = None
        self.set_cursor("default")

    @Gtk.Template.Callback()
    def on_mouse_move(self, _, event):
        """
        Move window if cursor is held
        """
        start = self._drag_start_coords
        if start and event.state == Gdk.ModifierType.BUTTON1_MASK:
            self.move(event.x_root - start['x'], event.y_root - start['y'])

    def _get_input_text(self):
        return self.input.get_text().lstrip()

    def _get_user_query(self):
        return Query(self._get_input_text())

    def select_result(self, index):
        self.results_nav.select(index)

    def enter_result(self, index=None, alt=False):
        if self.results_nav.enter(self._get_user_query(), index, alt=alt):
            # hide the window if it has to be closed on enter
            self.hide_and_clear_input()

    def hide(self, *args, **kwargs):
        """Override the hide method to ensure the pointer grab is released."""
        if self.settings.get_property('grab-mouse-pointer'):
            self.get_pointer_device().ungrab(0)
        super().hide(*args, **kwargs)

    def get_pointer_device(self):
        return (self.get_window().get_display().get_device_manager().
                get_client_pointer())

    def hide_and_clear_input(self):
        self.input.set_text('')
        self.hide()

    def show_results(self, results):
        """
        :param list results: list of Result instances
        """
        self.results_nav = None
        self.result_box.foreach(lambda w: w.destroy())

        limit = len(self.settings.get_jump_keys()) or 25
        show_recent_apps = self.settings.get_property('show-recent-apps')
        recent_apps_number = int(
            show_recent_apps) if show_recent_apps.isnumeric() else 0
        if not self.input.get_text() and recent_apps_number > 0:
            results = AppResult.get_most_frequent(recent_apps_number)

        results = self.create_item_widgets(results, self._get_user_query())

        if results:
            for item in results[:limit]:
                self.result_box.add(item)
            self.results_nav = ItemNavigation(self.result_box.get_children())
            self.results_nav.select_default(self._get_user_query())

            self.result_box.set_margin_bottom(10)
            self.result_box.set_margin_top(3)
            self.apply_css(self.result_box)
            self.scroll_container.show_all()
        else:
            # Hide the scroll container when there are no results since it normally takes up a
            # minimum amount of space even if it is empty.
            self.scroll_container.hide()
        logger.debug('render %s results', len(results))

    def _render_prefs_icon(self):
        prefs_pixbuf = load_icon(get_asset('icons/gear.svg'),
                                 16 * get_scaling_factor())
        prefs_image = Gtk.Image.new_from_pixbuf(prefs_pixbuf)
        self.prefs_btn.set_image(prefs_image)

    @staticmethod
    def create_item_widgets(items, query):
        results = []
        for index, result in enumerate(items):
            glade_filename = get_asset(f"ui/{result.UI_FILE}.ui")
            if not os.path.exists(glade_filename):
                glade_filename = None

            builder = Gtk.Builder()
            builder.set_translation_domain('ulauncher')
            builder.add_from_file(glade_filename)

            item_frame = builder.get_object('item-frame')
            item_frame.initialize(builder, result, index, query)

            results.append(item_frame)

        return results
コード例 #16
0
import logging
import pipes
import subprocess

from ulauncher.utils.desktop.reader import read_desktop_file
from ulauncher.utils.Settings import Settings
from ulauncher.api.shared.action.BaseAction import BaseAction

logger = logging.getLogger(__name__)
settings = Settings.get_instance()


class LaunchAppAction(BaseAction):
    """
    Launches app by given `.desktop` file path

    :param str filename: path to .desktop file
    """
    def __init__(self, filename):
        self.filename = filename

    def keep_app_open(self):
        return False

    def run(self):
        app = read_desktop_file(self.filename)
        command = app.get_string('Exec')
        terminal_exec = settings.get_property('terminal-command')
        if app.get_boolean('Terminal') and terminal_exec and command:
            logger.info('Run command %s (%s) in preferred terminal (%s)',
                        command, self.filename, terminal_exec)
コード例 #17
0
ファイル: test_Settings.py プロジェクト: Ulauncher/Ulauncher
 def settings(self):
     return Settings()