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.
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()
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”.
- 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"
}
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
-
icons: icons folder contains the image
edict.png
and iconedict.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 classEDict
inmain.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)
-
main.py: This is our starting point for our plugin. We override the
query
method in classEDict
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
- 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)
Let me know if you have comments.