DEV Community

Ashutosh Kumar
Ashutosh Kumar

Posted on • Edited on • Originally published at levelup.gitconnected.com

How to develop a Wox Plugin using Python

Representative image for plugin

Read this article on Medium published by gitconnected.

Wox is a full-featured launcher for the Windows platform. It’s akin to Mac’s Spotlight and can be augmented by a plethora of plugins.

I have been using Wox (with the almighty Everything plugin) for several years now and have loved it. But I have always resented the unavailability of a working English dictionary plugin that would also work offline. So, I embarked on developing a plugin for myself but was frustrated by the lack of documentation available to do the same.

Below, I am sharing how I developed a dictionary plugin, Easy Dictionary, using Python. I hope it would help you in your quest to develop another Python-based plugin. Easy Dictionary can be installed from the official Wox plugin website. The source code is available on GitHub. Please check it out and you’d understand why I named it an “Easy” dictionary.

Easy Dictionary Usage Screenshot

Alright, all we need to do is follow below simple steps.


We need to create a python file to contain our code. Let’s call it main.py (file name could be anything). Add a class (class name could be anything) inheriting from wox.Wox and instantiate the same as follows:

# Only for type-hinting in Python
# For more info: https://docs.python.org/3/library/typing.html
from typing import Dict, List

# No need to worry about the presence of the `Wox` class. Wox runtime has its own 
# python environment that contains the required `Wox` class
from wox import Wox


# Class name could be anything
# NOTE: Don't forget to inherit from `Wox`
class EDict(Wox):
    # Overriding the `__init__` is NOT mandatory
    def __init__(self, *args, **kwargs) -> None:
        """Initializer for `EDict` class"""
        # Add all the custom initialization required for the `EDict` class

        # NOTE: If overriding the `__init__` method, make sure to call the
        # parent class initializer like below
        super().__init__(*args, **kwargs)

    # NOTE: We must override the `query` method
    # `query` method is called when a user queries the plugin using its trigger
    # keyword in Wox. Wox passes the user input as an argument
    def query(self, key: str) -> List[Dict[str, str]]:
        results: List[Dict[str, str]] = []
        # NOTE: Each item on the list must be a dictionary that should contain 
        # at least following keys. For more info, check out the annotated screenshot below
            # "Title": Main search result to show in Wox
            # "IcoPath": Path of image/icon for the search result in Wox
            # "SubTitle": Subtitle of the search result in Wox

        # Our core logic goes here or in any other method that we can call here

        return results


if __name__ == "__main__":
    # NOTE: Don't forget to instantiate the above class
    # No need to return anything
    EDict()
Enter fullscreen mode Exit fullscreen mode

We need to create another file called plugin.json (the name must be the same). Wox looks for this JSON file to get details about the plugin and instantiates the same at runtime. Below plugin.json file is for the Easy Dictionary plugin.

The plugin.json file requires the following details:

  • ID: This must be a UUID that we generate online or using any programming language. It shouldn’t clash with the ID of any other plugins available for Wox.
  • ActionKeyword: It is the trigger keyword for the plugin when using Wox. See the below screenshot showing Easy Dictionary getting triggered on typing “ed”.

Easy Dictionary Usage Screenshot with markings to highlight different components

  • Name, Description, Author, Version: These are self-explanatory.
  • Language: Wox supports writing plugins in many languages as the communication is based on RPC. So, language is not a barrier for Wox plugins. Here, we need to specify the language in which we are writing the plugin (“python” in this case).
  • Website: This can be empty string or we can link to a website for our plugin.
  • IcoPath: Path of image/icon we wish to use for our plugin. This image/icon must be part of the plugin package (we will be packaging the plugin shortly).
  • ExecuteFileName: It specified the entry point for our plugin. Note that it’s the same main.py file that we created earlier.
{
    "ID":"0c6569f79d49409e8e5e42e1ec8bb035",
    "ActionKeyword":"ed",
    "Name":"Easy Dictionary",
    "Description":"Provides an offline English Dictionary",
    "Author":"ashutosh",
    "Version":"2.2.0",
    "Language":"python",
    "Website":"https://github.com/ashu-tosh-kumar/Wox.Plugin.eDict",
    "IcoPath": "icons\\edict.png",
    "ExecuteFileName":"main.py"
  }
Enter fullscreen mode Exit fullscreen mode

We are almost done. Next we need to just grab the files main.py , plugin.json, folder named icons with files edict.png (used in plugin.json) & edit.ico (used in main.py) and zip them together into Wox.Plugin.<plugin_name>.zip. In our case, it’s Wox.Plugin.eDict.zip.

Once zipped, change the zip format manually to .wox. So, finally we would have the zipped file: Wox.Plugin.eDict.wox.

The file Wox.Plugin.eDict.wox can be dragged and dropped onto Wox launcher to install it manually. Or, we can share it on Wox’s official website for wider use by creating an account and uploading the above file.

That’s it. Follow the above steps and you can create your own Wox plugins using our old chap Python language. For the sake of completeness, let’s go through the actual code of Easy Dictionary below.


First, let’s have a look at the files that we would need to package for the Easy Dictionary. Please checkout the source code on GitHub.

|-icons  # Folder
| |-edict.ico
| |-edict.png
|-dictionary_compact_with_words.zip
|-main.py
|-plugin.json
|-spell.py
Enter fullscreen mode Exit fullscreen mode
  • icons: icons folder contains the image edict.png and icon edict.ico used in the project.
  • dictionary_compact_with_words.zip: It contains the unabridged Webster dictionary in json format. We have zipped it to reduce the plugin size. We unzip it for use in Python in __init__ method of class EDict in main.py
class EDict(Wox):
    """Easy Dictionary Class used by Wox"""

    def __init__(self, *args, **kwargs) -> None:
        """Initializer for `EDict` class"""
        # Unzipping the dictionary for usage
        with ZipFile(DICTIONARY_ZIP_FILE, "r") as zip_file:
            with zip_file.open(DICTIONARY_JSON_FILE) as edict_file:
                self._edict = load(edict_file)

        # Key "cb2b20da-9168-4e8e-8e8f-9b54e7d42214" gives a list of all words
        # For more info, checkout: https://github.com/matthewreagan/WebstersEnglishDictionary
        words = self._edict["cb2b20da-9168-4e8e-8e8f-9b54e7d42214"]
        self._spell_correct = SpellCorrect(words)

        super().__init__(*args, **kwargs)
Enter fullscreen mode Exit fullscreen mode
  • main.py: This is our starting point for our plugin. We override the query method in class EDict to provide results to user query. We use the method _format_result to format each result as a dictionary as explained earlier. Some formatting rules in _format_result are in place because of the underlying dictionary used. Moreover, if we fail to find a definition for user input key, we try to auto-correct the word and show results for the auto-corrected word instead.
def query(self, key: str) -> List[Dict[str, str]]:
    """Overrides Wox query function to capture user input

    Args:
        key (str): User search input

    Returns:
        List[Dict[str, str]]: Returns list of results where each result is a dictionary
    """
    results: List[Dict[str, str]] = []
    key = key.strip().lower()

    if not key:
        # Avoid looking for empty key
        return results

    try:
        # Look for the given key
        definitions = self._edict[key]

        # Format results in the form of a dictionary
        results = self._format_result(definitions, key, MAXIMUM_RESULTS, False)
    except KeyError:
        # Try correcting the key and looking again with the corrected key
        # This is an additional feature where we try to auto-correct the user input
        # This helps in case of spelling mistakes
        try:
            corrected_key = self._spell_correct.correction(key)
            definitions = self._edict[corrected_key]
            results = self._format_result(definitions, corrected_key, MAXIMUM_RESULTS, True)
        except KeyError:
            # Word doesn't exist in our dictionary
            pass

    return results
Enter fullscreen mode Exit fullscreen mode
  • plugin.json: Explained earlier.
  • spell.py: A simple implementation of probability based method to auto-correct a word. Implementation taken from norvig.com. This file is used by main.py.

Top comments (1)

Collapse
 
atkumar profile image
Ashutosh Kumar

Let me know if you have comments.