Skip to content

Conversation

friday
Copy link
Member

@friday friday commented Jul 26, 2022

Implements #1074

New terminology:

  • "input triggers" (on_input) for the established trigger type (previously referred to as "keywords"). They are for getting user input after all.
  • "launch triggers" (on_launch) for the new type of triggers that trigger instantly when you activate them. They do work similar to apps (which are "launched"). "launch" isn't 100% spot on, since the action might not actually be to launch something. It could be to refresh/reload a file for example (like this: feature suggestion: clear list of frequently used apps #338). But it's the best I could think of which works well for the method name too.

Can be tested with this extension (v6 should support the gnome settings panels by default on most systems, so if this "works" you get duplicates):

It's as simple as this:

manifest.json:

{
  "api_version": "3",
  "authors": "friday",
  "name": "Gnome Settings",
  "icon": "icon.svg",
  "triggers": {
    "background": {
      "name": "Background Settings",
      "description": "Change your background image to a wallpaper or photo"
    },
    "bluetooth": {
      "name": "Bluetooth Settings",
      "description": "Turn Bluetooth on and off and connect your devices" 
    },
    "color": {
      "name": "Color Settings",
      "description": "Calibrate the colour of your devices, such as displays, cameras or printers" 
    }
  }
}

main.py:

import subprocess
import shutil
from ulauncher.api.shared.action.HideWindowAction import HideWindowAction
from ulauncher.api import Extension

class GnomeSettings(Extension):
    def on_launch(self, trigger_id):
        subprocess.Popen(['gnome-control-center', trigger_id])
        return HideWindowAction()

if __name__ == '__main__':
    GnomeSettings().run()

Hence, the on_launch method is only for the new trigger type. The old trigger type (now called "input triggers") need a "keyword" property in the manifest and then you will be able to use this method:

def on_input(self, input_text: str, trigger_id: str):
    ...

input_text is a string with the user input after the keyword. So if the user enters "find pdf" you only get "pdf", but you get the trigger_id corresponding to "find" as the second argument. This is better since the actual keyword can be customized by the user and hence can't be used safely to determine which trigger. It's also easier for people who want typing information to not have to import our Query class.

The documentation still doesn't include on_launch yet, but it was always kind of basic and doesn't include some of the other ones either.

Checklist

  • Verify that the test command ./ul test is passing (the CI server will check this if you don't)
  • Update the documentation according to your changes (when applicable)
  • Write unit tests for your changes (when applicable)

@friday friday added this to the 6.0.0 milestone Jul 26, 2022
@friday
Copy link
Member Author

friday commented Jul 26, 2022

This was written before settling on the new terminology: I'm not super happy with the method name on_static_trigger for triggers with no keywords or arguments. I kind of just went with something here. The other method on_query_change existed before in v6 (but not in v5). So I just changed the arguments for that. But maybe it's better if it's on_query_trigger. Or just on_query and on_static, or some variant of on_(query|input|keyword|prompt)_(change|trigger) and whatever name you can find for the other one that fits to pair with that on_(static|nullary|keywordless|void|direct)_(change|trigger).

My first attempt used the same event and method for both types of triggers, but I think that was worse, because the main argument for the input trigger is the input (aka query argument), which the other one doesn't have.

I also think what we call them matters a lot for the documentation and how people think of them. The new type is far less important than the old keyword one, so I'm fine with it taking a back seat though.

I think we should move this extension to the Ulauncher repos and also use it for the documentation since it's extremely simple, yet demonstrating a valid use case of multiple keyword arguments.

import hashlib
from ulauncher.api import Extension, ExtensionResult
from ulauncher.api.shared.action.CopyToClipboardAction import CopyToClipboardAction

class Hash2(Extension):
    def on_input(self, input_text, trigger_id):
        # Show the algorithm specified as keyword, or all if the keyword was "hash"
        algorithms = hashlib.algorithms_guaranteed if trigger_id == 'hash' else [trigger_id]

        for algorithm in algorithms:
            try:
                seed = hashlib.new(algorithm)
                seed.update(input_text.encode())
                hash = seed.hexdigest()
                yield ExtensionResult(
                    name=hash,
                    description=algorithm,
                    on_enter=CopyToClipboardAction(hash),
                )
            except:
                pass

if __name__ == '__main__':
    Hash2().run()

self.logger.debug('No listeners for event %s', event_type.__name__)
return
if event_type == QueryChangeEvent and self._listeners[KeywordQueryEvent]:
# convert QueryChangeEvent to KeywordQueryEvent for backwards compatibility
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good consideration 👍

@gornostal
Copy link
Member

I think we should move this extension to the Ulauncher repos and also use it for the documentation since it's extremely simple

Good idea!

@friday
Copy link
Member Author

friday commented Jul 31, 2022

@brpaz Would be interested in hearing if you have thoughts on the API changes. You have written more code against it than anyone (including the original hash extension).

The PR redefines preferences with the type "keyword" as "input triggers" and the query argument to "input". And also adds a new type of trigger "launch trigger" which is a simpler type of trigger that doesn't take user input but "launches" (calls the extension listener) directly.

The idea is that this will enable future changes so the other type of "keywordless" (like our calculator) will be possible also down the road. But it's more complex and won't be part of v6.0.0.

@brpaz
Copy link
Contributor

brpaz commented Aug 6, 2022

@friday hey. just saw this now. I think it looks good.

I have been using more and more this pattern of having multiple keywords/triggers in an extension, inside of having a single entry point and then parsing the entire input text, to know which sub-action to execute.

I think this "trigger" approach simplifies a lot this process. The new on_input function signature, will reduce a lot of boilerplate code that I need to do frequently, to get the trigger, parse the arguments, etc.

@friday
Copy link
Member Author

friday commented Aug 6, 2022

I have been using more and more this pattern of having multiple keywords/triggers in an extension, inside of having a single entry point and then parsing the entire input text, to know which sub-action to execute.

I think this "trigger" approach simplifies a lot this process. The new on_input function signature, will reduce a lot of boilerplate code that I need to do frequently, to get the trigger, parse the arguments, etc.

Great :) Separating preferences and triggers was definitely the right move. The differ quite a lot (one having different properties and uses). Triggers custom keywords are not a concern the extension, and removing it completely would be best (which is almost the case with this PR, but they will still be in self.preferences for backwards compatibility. Changing the terminology is tricky though, so I wanted confirmation that that part felt natural and made sense :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants