
.. _RefFAQ:

Frequently Asked Questions (FAQ)
============================================================================

The following is a list of frequently asked questions related to the
Dragonfly speech recognition framework.

.. contents:: Table of Contents
   :local:


General Questions
----------------------------------------------------------------------------

What is Dragonfly?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Dragonfly is a speech recognition framework for Python that makes it
convenient to create custom commands to use with speech recognition
software. It was written to make it very easy for Python macros, scripts,
and applications to interface with speech recognition engines. Its design
allows speech commands and grammar objects to be treated as first-class
Python objects.

Dragonfly can be used for general programming by voice. It is flexible
enough to allow programming in any language, not just Python. It can also be
used for speech-enabling applications, automating computer activities
and dictating prose.


Which speech recognition software and operating systems are supported?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Dragonfly supports the following speech recognition (SR) engines:

* :ref:`Dragon <RefNatlinkEngine>`, a product of *Nuance*. All versions up
  to 15 (the latest) are supported. *Home*, *Professional Individual* and
  previous similar editions of *Dragon* are supported. Other editions may
  work too
* :ref:`Windows Speech Recognition <RefSapi5Engine>` (WSR), included with
  Microsoft Windows Vista, Windows 7+, and freely available for Windows XP
* :ref:`Kaldi <RefKaldiEngine>`
* :ref:`CMU Pocket Sphinx <RefSphinxEngine>`

Dragonfly has cross platform support for Windows, macOS and Linux (using
X11). The following table shows which engines are available on which
platforms:

================================     =======================
Operating system                     Available SR engines
================================     =======================
Windows                              DNS, WSR, Kaldi, Sphinx
Linux                                Kaldi, Sphinx
macOS                                Kaldi, Sphinx
================================     =======================

Windows-only speech recognition software, i.e. DNS and WSR, can be used to
control Linux or macOS machines via `Aenea`_, a client-server library for
using Dragonfly voice macros on remote hosts.

Dragonfly's X11 support should work just fine on non-Linux unices, such as
FreeBSD. If you are planning to use the Kaldi SR engine backend on a
platform like FreeBSD, you will need to compile the Kaldi engine
dependencies manually.


Where can I find examples Dragonfly command modules?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

There is a list of repositories and other projects containing Dragonfly
command modules under the :ref:`RefRelatedResources` ->
:ref:`RefCommandModulesList` section of the documentation. There are also
example command modules in `dragonfly/examples`_.


What is the difference between dragonfly and dragonfly2?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

*Dragonfly* is the `original project`_ written by Christo Butcher (t4ngo).
It is no longer actively maintained. *Dragonfly2* is a `fork`_ of dragonfly
that uses a different *distribution* name in order to upload releases to the
`Python Package Index <https://pypi.org>`__, so that the package can be
installed by running::

  pip install dragonfly2

It is important to note that the import name is still "dragonfly":

.. code-block:: python

   from dragonfly import Grammar, MappingRule, Key, Text, Mouse, Dictation

Dragonfly2 is intended to be backwards-compatible continuation of the
original project. Many bugs and other issues are fixed in this version. It
supports using additional speech recognition engine backends (e.g. the
:ref:`Kaldi engine <RefKaldiEngine>`). It also works with Python 3 and has
cross-platform support for Windows, GNU/Linux and macOS. Dragonfly2 also has
many other new features not found in the old version.

See the :ref:`changelog <RefChangelog>` for the full list of changes between
the two versions.


How can I use older Dragonfly scripts with Dragonfly2?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Older dragonfly scripts are mostly written with Python 2.x in mind. Python
version 2.7 has reached the end of its life as of January 2020 (see
`Python 2.7 EOL`_). For complicated reasons, Dragonfly's Python 3.x support
has come a bit later than most other active projects. You will need to
convert older Python 2.x code, to use it with Python 3.x. There are a few
ways to convert older code:

* `2to3`_ - command-line program that reads Python 2.x source code and
  applies a series of fixers to transform it into valid Python 3.x code.
* `python-modernize`_ - a command-line program that uses *2to3* to make
  Python 2 code compatible with Python 3.

You may be interested in the `Python 2-3 code porting guide`_ if you prefer
to do things manually.

A number of older dragonfly command modules also include the following code:

.. code-block:: python

   try:
       import pkg_resources
       pkg_resources.require("dragonfly >= 0.6.5")
   except ImportError:
       pass

Since the distribution name has been changed to *dragonfly2*, you will need
to either replace :code:`dragonfly` with :code:`dragonfly2` or remove code
like this altogether.


Where are some good resources on learning Python?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you just want to use Dragonfly for flexible computer control or for
programming in other languages and you don't have much background in Python,
then the following resources from the Python Software Foundation might be
useful to you:

* `Beginner's Guide for non-programmers
  <https://wiki.python.org/moin/BeginnersGuide/NonProgrammers>`__

* `Beginner's Guide for programmers
  <https://wiki.python.org/moin/BeginnersGuide/Programmers>`__

* `The Python Tutorial <https://docs.python.org/tutorial/index.html>`__

* `Latest Python documentation <https://docs.python.org>`__


API Questions
----------------------------------------------------------------------------

How do I use an "extra" in a Dragonfly spec multiple times?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Sometimes it is desirable to use the same "extra" multiple times in a
Dragonfly :code:`Compound`, :code:`CompoundRule` or :code:`MappingRule`
specification (or "spec"). You **cannot** use the same reference name in the
same spec. However, there is always an efficient solution available using
multiple names. Solutions to two common problems are listed below using the
generic compound spec :code:`"<X1> and <X2>"`.

.. code-block:: python

   from dragonfly import IntegerRef, Choice, RuleRef, RuleWrap

   # For saying and processing two numbers, e.g. "one and ten".
   int_extras = [
       IntegerRef("X1", 1, 20),
       IntegerRef("X2", 1, 20)
   ]

   # For saying and processing a Choice element two times,
   # e.g. "alpha and bravo".
   my_choice = Choice("", {
       "alpha": "a",
       "bravo": "b",
       "charlie": "c"
   })
   # Use RuleWrap to wrap the Choice element into a private rule only
   # pronounceable via references (i.e. with RuleRef elements).
   # This is more efficient than using two identical Choice elements.
   my_choice_rule = RuleWrap("", my_choice).rule
   alpha_extras = [
       RuleRef(my_choice_rule, "X1"),
       RuleRef(my_choice_rule, "X2")
   ]


All of these example *extras* lists and their elements can be used with
:code:`Compound` or :code:`Choice` elements or :code:`CompoundRule` or
:code:`MappingRule` grammar rules.


Is there a way to re-use a function with different "extra" names?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Dragonfly's :class:`Function` action class is normally used to call a Python
function when a spoken command is recognized. :class:`Function` actions pass
recognized "extra" values via key word arguments, rather than positional
arguments.

Below are two methods to re-use a Python function without redefining
it:

.. code-block:: python

   from dragonfly import Function

   # Define a function to be used by two Function actions.
   def add_and_print(x, y):
       print("%d" % (x + y))

   # --- Method one ---
   # Use a lambda function.
   Function(lambda x, z: add_and_print(x, z))

   # --- Method two ---
   # Use the optional 'remap_data' argument to pass the 'z' argument
   # as 'y' internally.
   Function(add_and_print, dict(z='y'))


See the :ref:`Function action's documentation <RefFunctionAction>`
for more information and code examples.


Is there a way to recognize negative integers with Dragonfly?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Yes. The simplest way of recognizing negative integers is to use
:class:`IntegerRef` and :class:`Modifier` elements together in a command
with an appropriate prefix word such as "negative" or "minus":

.. code-block:: python

   from dragonfly import IntegerRef, Modifier, Text

   # Define a MappingRule command for typing a negative integer.
   mapping = {
       "(minus|negative) <n>": Text("%(n)d"),
   }

   # The special Modifier element lets us modify the value of an element.
   # Here we use it to specify the "n" extra as a negated integer between 1
   # and 50.
   extras = [
       Modifier(IntegerRef("n", 1, 50), lambda n: n*-1)
   ]


Is there a way to construct Dragonfly grammars manually with elements?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Yes. The :class:`dragonfly.grammar.rule_basic.BasicRule` is the rule class
to use for constructing Dragonfly rules and grammars manually with elements
instead of with compound specs and extras.

The following is an example of how to use :class:`BasicRule` and common
Dragonfly element and action classes together:

.. code-block:: python

   from dragonfly import (BasicRule, Repetition, Alternative, Literal, Text,
                          Grammar)

   class ExampleRule(BasicRule):
       # Define a rule element that accepts 1 to 5 (exclusive) repetitions
       # of either 'test one', 'test two' or 'test three'. These commands
       # type their respective numbers in succession using the Text action.
       element = Repetition(
           Alternative((
               Literal("test one", value=Text("1")),
               Literal("test two", value=Text("2")),
               Literal("test three", value=Text("3")),
           )),
           1, 5
       )

   # Create a grammar with the example rule and load it.
   rule = ExampleRule()
   grammar = Grammar("BasicRule Example")
   grammar.add_rule(rule)
   grammar.load()


Please note that extras in action specification strings (e.g. *n* in
:code:`Key("left:%(n)d")`) will **not** work for the :class:`BasicRule`
class. For this functionality, you should use :class:`CompoundRule` or
:class:`MappingRule` instead. You can also override the
:meth:`_process_recognition` method and use the :code:`node` object to
retrieve the desired extra / element and its value.


Does Dragonfly support using Windows Speech Recognition with the GUI?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Yes. To use WSR with the GUI, you need to initialize the SAPI5 shared
process engine in the module loader script file:

.. code-block:: python

   from dragonfly import get_engine
   get_engine("sapi5shared")

If you are using Dragonfly's command-line interface, then you need to pass
"sapi5shared" as the engine name::

  python -m dragonfly load -e sapi5shared _\*.py

There are significant issues with using WSR's shared recognizer for
command-based speech recognition. This is because of the built-in commands
and dictation output. Dragonfly defaults to the *in-process* SAPI5 engine
because it doesn't have these defaults.


Is there an easy way to check which speech recognition engine is in use?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Yes. The current engine can be checked using the
:py:meth:`dragonfly.engines.get_current_engine` function. The following code
prints the name of the current engine if one has been initialized:

.. code-block:: python

   from dragonfly import get_current_engine
   engine = get_current_engine()
   if engine:
       print("Engine name: %r" % engine.name)
   else:
       print("No engine has been initialized.")


Troubleshooting Questions
----------------------------------------------------------------------------

Why are my command modules are not being loaded/detected?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you have placed Python files into the *MacroSystem* / user directory
(using DNS/Natlink) or the directory where your module loader script is
(using another engine) and there is no indication that the files were
loaded, then there can be a few reasons why:

#. Your Python files don't start with an underscore `_` and end with
   `.py`.

#. You've put the files in the wrong directory.
   If you're using Natlink, then try running the Natlink configuration-
   program to double check where Natlink loads files from.

In the case that your command modules are being loaded and you're getting
error messages not mentioned in the FAQ, then see the
:ref:`RefFAQUnansweredQuestions` section.


How do I fix "No handlers could be found for logger X" error messages?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This error is specific to Python 2.x. It isn't a Dragonfly error, but as
many users still use Python 2.7, it is listed here. This is the most common
example of the error: ::

  No handlers could be found for logger "action"

There are two easy methods for to solving this problem:

.. code-block:: python

   # --- Method one ---
   # Set up a basic logging handler for console output using the 'logging'
   # module.
   import logging
   logging.basicConfig()

   # --- Method two ---
   # Set up Dragonfly's logging handler from the 'dragonfly.log' module.
   # This sets up a logging handler for console output, appends log messages
   # to a log file (~/.dragonfly.log) and sets sane defaults for Dragonfly's
   # internal loggers.
   from dragonfly.log import setup_log
   setup_log()

For either method, add the two lines of code near the top of one of your
command modules or command module loader script, if you use one.


Cannot load compatibility module support error when starting Dragon
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This is a known issue with Natlink. Please see this
`Natlink troubleshooting page`_ for solutions on how to solve this and other
issues that occur before the Natlink messages window appears.


Why isn't GUI code working properly when using Natlink?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Common GUI libraries won't work in modules loaded by Natlink because the
Python interpreter used by Natlink is embedded. This also prevents use of
Python's `multiprocessing module`_.

Running GUI-related code in modules loaded by Natlink can cause Dragon or
Python to crash. For example, Python will crash if the `pywin32`_
:code:`win32ui` module is imported when using Natlink out of process via the
:ref:`command-line interface <RefCLI>` or an external module loader script
(see `Dragonfly issue #205`_).

Dragonfly's :ref:`Remote Procedure Call (RPC) sub-package <RefRPC>` can be
used to write external GUI programs/components with Dragonfly speech
recognition engine integration without the above concerns.


Why isn't multi-threaded code working when using Natlink?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Python threads should work normally with the Natlink backend in *dragonfly2*
versions 0.23.0 and above. Python threads will start in previous versions,
but will hang and only run very occasionally. If Python threads are still
hanging when using the Natlink engine backend, then try adding the following
code in one of your command modules:

.. code-block:: python

   from dragonfly import get_engine
   get_engine().apply_threading_fix()


The :meth:`apply_threading_fix` method is called automatically when a
grammar is loaded for the first time. Calling it manually is only required
if you need Python threads to work *before* any Dragonfly grammar is loaded.
The method only needs to be called once.

If threads still aren't working properly for you, please see the
:ref:`RefFAQUnansweredQuestions` section.


How do I fix "failed to decode recognition" errors?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

"Failed to decode recognition" is the error message displayed when Dragonfly
is unable to match what was said to a grammar rule. This can occur when
saying command words to match a dictation part of a rule.

One way around this to add a top-level grammar rule for dictating other
words in your rules:

.. code-block:: python

   from dragonfly import Dictation, Text

   mapping = {
       "reserved (word|words) <text>": Text("%(text)s")
   }

   extras = [
       Dictation("text")
   ]


Another way around the problem is to have an "extra" for reserved words:

.. code-block:: python

   from dragonfly import Choice, Text

   mapping = {
       "type <reserved>": Text("%(reserved)s")
   }

   extras = [
       Choice("reserved", {
           "alpha": "alpha",
           "bravo": "bravo",
           "charlie": "charlie",
       })
   ]


How can I increase the speech recognition accuracy?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Low recognition accuracy is usually caused by either bad-quality audio input
or a speech model that isn't trained to your voice or use case. You might
try the following:

 * Re-positioning your microphone.
 * Using a different microphone.
 * Training words or phrases.
 * Change the speech recognition engine settings (e.g. adjust Dragon's
   accuracy/speed slider).
 * Using a different engine back-end if possible, e.g. the Kaldi back-end is
   typically more accurate than CMU Pocket Sphinx and WSR back-ends.

Dragonfly also has programmatic methods for increasing recognition accuracy.
They can be used to fine tune accuracy for specific commands or parts of
commands:

 #. :ref:`Kaldi Grammar/Rule/Element Weights <RefKaldiEngineWeights>`
    (Kaldi-only)
 #. Quoted words in :class:`dragonfly.grammar.elements_basic.Literal`
    elements (only applies to Dragon)


Why isn't Dragonfly code aware of DPI scaling settings on Windows?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

There can be problems with Dragonfly's monitor-related functionality on
Windows Vista and above if the system is set up to use one or more monitors
with a high number of dots per inch (DPI). For this reason, Dragonfly
*attempts* to set the DPI awareness for the process when it is imported. The
``SetProcessDpiAwareness()`` function is used to do this on Windows 8.1 and
above.

If you need to set the DPI awareness manually using a different DPI
awareness value, do so before importing dragonfly. The following is
equivalent to what dragonfly does internally:

.. code-block:: python

   import ctypes
   ctypes.windll.shcore.SetProcessDpiAwareness(2)  # PROCESS_PER_MONITOR_DPI_AWARE

The ``SetProcessDpiAware()`` function can be used instead on older Windows
versions (e.g. Vista and 7). The ``SetProcessDpiAwarenessContext()``
function can be used on Windows 10 (version 1703) and above. For more
information on this topic, please see the following Microsoft documentation
pages:

* `High DPI Desktop Application Development on Windows`_
* `Setting the default DPI awareness for a process`_


Why aren't Dragonfly actions working with Windows admin applications?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Since Windows Vista, Windows has built-in security features to isolate
processes with different integrity levels. This is done to prevent
user-level processes from performing tasks that require administrative
authorization via `User Account Control`_. To this end, Windows also
prevents user-level processes from sending keystrokes, mouse events or
otherwise controlling processes or windows running as the administrator.
Since our Python code normally runs as the user, Windows prevents us from
interacting with administrative windows.

Windows has a way for accessibility software to run with special privileges.
This involves building a signed executable with a special manifest
``uiAccess="true"`` attribute set and installing it under either the
*Program Files* or *Windows/System32* directories. For more on this, see
Microsoft's `Security Considerations for Assistive Technologies`_
documentation page.

Unfortunately, this is not easily achievable with Python programs.
Developing a secure UI Access solution for Dragonfly would be quite
complicated and, given that it is a small project with only a few
developers, present significant security risks. For these reasons, Dragonfly
will **not** be implementing UI Automation support. The following are a few
alternative solutions:

 #. Use Dragon's UI Automation capability

    Dragon runs a UI Automation service in the background and, clearly, uses
    it to allow users to interact with administrative applications. This may
    be good enough if you don't need to perform complex tasks.

 #. Run Python as the administrator

    Running the Python process that loads, recognizes and processes your
    commands as the administrator should work around the limitations. **Be
    careful if you do this**; Windows won't go as far to stop bugs in your
    code from doing damage!

 #. Use AutoHotkey's *Run with UI Access* feature

    `AutoHotkey`_ (AHK) is an automation scripting language for Windows. One
    of its many features allows running AHK code with UI Access. See the
    relevant `AutoHotkey FAQ on UAC`_ for how to set this up and use it.

    Using this, you can define hotkeys for interacting with administrative
    windows. This can help if you are able to use a keyboard, even if only
    for a short time. Unfortunately, Dragonfly's actions won't be able to
    trigger these hotkeys since the AHK code will be running in elevated
    mode. One way around this is to run a local web server in your AHK
    script, perhaps using `AHKhttp`_ or something similar. Then you can send
    HTTP requests to the server to run your AHK code.

    If you do use the local web server approach mentioned, it is very
    important to ensure that requests received by the server are properly
    authorized so that only your Python code has (indirect) UI access
    (see `Web API security`_).


Why aren't Dragonfly's input actions working on my Linux system?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Dragonfly's :code:`Key`, :code:`Text` and :code:`Mouse` action classes use
the `xdotool`_ program on Linux. These actions will not work if it isn't
installed. It can normally be installed through your system's package
manager. On Debian-based or Ubuntu-based systems, this is done by running
the following console command::

  sudo apt install xdotool

The :code:`Window` class also requires the `wmctrl`_ program::

  sudo apt install wmctrl

The keyboard/mouse input classes will only work in an X11 session. You will
get the following error if you are using `Wayland`_ or something else::

  NotImplementedError: Keyboard support is not implemented for this platform!

If you see this message, then you will probably need to `switch to X11`_ or
use something like `ydotool`_ to have keyboard/mouse input work properly.

If you are using X11 and still see this message, then the
:code:`XDG_SESSION_TYPE` environment variable checked by Dragonfly
internally is not set to :code:`x11`. This can happen in some circumstances
and can be fixed by setting the variable manually, e.g. in your
:code:`~/.profile` file::

  export XDG_SESSION_TYPE=x11


.. _RefFAQUnansweredQuestions:

Unanswered Questions
----------------------------------------------------------------------------

If your question isn't listed above, then there are a few ways to get in
touch:

* Open a `new issue`_ on GitHub.
* Join one of Dragonfly's chat channels:

  * `Gitter channel`_
  * `Matrix channel`_

* Ask your question on the `Dragonfly mailing list`_.
* Send an email to Dane Finlay, the project maintainer, at
  `Danesprite@posteo.net`_.


.. Links.
.. _2to3: https://docs.python.org/2/library/2to3.html
.. _AHKhttp: https://github.com/zhamlin/AHKhttp
.. _Aenea: https://github.com/dictation-toolbox/aenea
.. _AutoHotkey FAQ on UAC: https://www.autohotkey.com/docs/FAQ.htm#uac
.. _AutoHotkey: https://www.autohotkey.com/
.. _Danesprite@posteo.net: mailto:Danesprite@posteo.net
.. _Dragonfly issue #205: https://github.com/dictation-toolbox/dragonfly/issues/205
.. _Dragonfly mailing list: https://groups.google.com/forum/#!forum/dragonflyspeech
.. _Gitter Channel: https://gitter.im/dictation-toolbox/dragonfly
.. _High DPI Desktop Application Development on Windows: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
.. _Matrix channel: https://app.element.io/#/room/#dictation-toolbox_dragonfly:gitter.im
.. _Natlink Troubleshooting page: https://qh.antenna.nl/unimacro/installation/installforpython27/problemswithinstallation.html
.. _Python 2-3 code porting guide: https://docs.python.org/3/howto/pyporting.html
.. _Python 2.7 EOL: https://www.python.org/dev/peps/pep-0373/
.. _Security Considerations for Assistive Technologies: https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-securityoverview
.. _Setting the default DPI awareness for a process: https://docs.microsoft.com/en-us/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process
.. _User Account Control: https://en.wikipedia.org/wiki/User_Account_Control
.. _Wayland: https://wayland.freedesktop.org/
.. _Web API security: https://en.wikipedia.org/wiki/Web_API_security
.. _dragonfly/examples: https://github.com/dictation-toolbox/dragonfly/tree/master/dragonfly/examples
.. _fork: https://en.wikipedia.org/wiki/Fork_(software_development)
.. _multiprocessing module: https://docs.python.org/3/library/multiprocessing.html
.. _new issue: https://github.com/dictation-toolbox/dragonfly/issues/new
.. _original project: https://github.com/t4ngo/dragonfly
.. _python-modernize: https://pypi.org/project/modernize/
.. _pywin32: https://github.com/mhammond/pywin32
.. _switch to X11: https://askubuntu.com/questions/961304/how-do-you-switch-from-wayland-back-to-xorg-in-ubuntu-17-10
.. _wmctrl: https://www.freedesktop.org/wiki/Software/wmctrl/
.. _xdotool: https://www.semicomplete.com/projects/xdotool
.. _ydotool: https://github.com/ReimuNotMoe/ydotool
