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()
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
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()
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]}")
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)
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
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()
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
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()
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()
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()
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()
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)
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)
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
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)