1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
|
=========================
Command graph development
=========================
This page provides further detail on how Qtile's command graph works.
If you just want to script your Qtile window manager the :doc:`earlier information <index>`, in
addition to the documentation on the :doc:`available commands <api/index>` should be enough to get started.
To develop the Qtile manager itself, we can dig into how Qtile represents these objects,
which will lead to the way the commands are dispatched.
Client-Server Scripting Model
=============================
Qtile has a client-server control model - the main Qtile instance listens on a
named pipe, over which marshalled command calls and response data is passed.
This allows Qtile to be controlled fully from external scripts. Remote
interaction occurs through an instance of the
``libqtile.command.interface.IPCCommandInterface`` class. This class
establishes a connection to the currently running instance of Qtile. A
``libqtile.command.client.InteractiveCommandClient`` can use this connection to dispatch
commands to the running instance. Commands then appear as methods with the
appropriate signature on the ``InteractiveCommandClient`` object. The object hierarchy is
described in the :ref:`commands-api` section of this manual. Full
command documentation is available through the :ref:`Qtile Shell
<qtile-shell>`.
Digging Deeper: Command Objects
===============================
All of the configured objects setup by Qtile are ``CommandObject`` subclasses.
These objects are so named because we can issue commands against them using the
command scripting API. Looking through the code, the commands that are exposed
are commands that are decorated with the ``@expose_command()`` decorator.
When writing custom layouts, widgets, or any other object, you can add your own
custom functions and, once you add the decorator, they will be callable using the
standard command infrastructure. An available command can be extracted by calling
``.command()`` with the name of the command.
In addition to having a set of associated commands, each command object also
has a collection of items associated with it. This is what forms the graph
that is shown above. For a given object type, the ``items()`` method returns
all of the names of the associated objects of that type and whether or not
there is a defaultable value. For example, from the root, ``.items("group")``
returns the name of all of the groups and that there is a default value, the
currently focused group.
To navigate from one command object to the next, the ``.select()`` method is
used. This method resolves a requested object from the command graph by
iteratively selecting objects. A selector like ``[("group", "b"), ("screen",
None)]`` would be to first resolve group "b", then the screen associated to the
group.
The Command Graph
=================
In order to help in specifying command objects, there is the abstract command
graph structure. The command graph structure allows us to address any valid
command object and issue any command against it without needing to have any
Qtile instance running or have anything to resolve the objects to. This is
particularly useful when constructing lazy calls, where the Qtile instance does
not exist to specify the path that will be resolved when the command is
executed. The only limitation of traversing the command graph is that it must
follow the allowed edges specified in the first section above.
Every object in the command graph is represented by a ``CommandGraphNode``.
Any call can be resolved from a given node. In addition, each node knows about
all of the children objects that can be reached from it and have the ability to
``.navigate()`` to the other nodes in the command graph. Each of the object
types are represented as ``CommandGraphObject`` types and the root node of the
graph, the ``CommandGraphRoot`` represents the Qtile instance. When a call is
performed on an object, it returns a ``CommandGraphCall``. Each call will know
its own name as well as be able to resolve the path through the command graph
to be able to find itself.
Note that the command graph itself can standalone, there is no other
functionality within Qtile that it relies on. While we could have started here
and built up, it is helpful to understand the objects that the graph is meant
to represent, as the graph is just a representation of a traversal of the real
objects in a running Qtile window manager. In order to tie the running Qtile
instance to the abstract command graph, we move on to the command interface.
.. _command-interface:
Executing graph commands: Command Interface
===========================================
The ``CommandInterface`` is what lets us take an abstract call on the command
graph and resolve it against a running command object. Put another way, this
is what takes the graph traversal ``.group["b"].screen.info()`` and executes
the ``info()`` command against the addressed ``screen`` object. Additional
functionality can be used to check that a given traversal resolves to actual
objcets and that the requested command actually exists. Note that by
construction of the command graph, the traversals here must be feasible, even
if they cannot be resolved for a given configuration state. For example, it is
possible to check the screen assoctiated to a group, even though the group may
not be on a screen, but it is not possible to check the widget associated to a
group.
The simplest form of the command interface is the ``QtileCommandInterface``,
which can take an in-process ``Qtile`` instance as the root ``CommandObject``
and execute requested commands. This is typically how we run the unit tests
for Qtile.
The other primary example of this is the ``IPCCommandInterface`` which is able
to then route all calls through an IPC client connected to a running Qtile
instance. In this case, the command graph call can be constructed on the
client side without having to dispatch to Qtile and once the call is
constructed and deemed valid, the call can be executed.
In both of these cases, executing a command on a command interface will return
the result of executing the command on a running Qtile instance. To support
lazy execution, the ``LazyCommandInterface`` instead returns a ``LazyCall``
which is able to be resolved later by the running Qtile instance when it is
configured to fire.
Tying it together: Command Client
=================================
So far, we have our running Command Objects and the Command Interface to
dispatch commands against these objects as well as the Command Graph structure
itself which encodes how to traverse the connections between the objects. The
final component which ties everything together is the Command Client, which
allows us to navigate through the graph to resolve objects, find their
associated commands, and execute the commands against the held command
interface.
The idea of the command client is that it is created with a reference into the
command graph and a command interface. All navigation can be done against the
command graph, and traversal is done by creating a new command client starting
from the new node. When a command is executed against a node, that command is
dispatched to the held command interface. The key decision here is how to
perform the traversal. The command client exists in two different flavors: the
standard ``CommandClient`` which is useful for handling more programatic
traversal of the graph, calling methods to traverse the graph, and the
``InteractiveCommandClient`` which behaves more like a standard Python object,
traversing by accessing properties and performing key lookups.
Returning to our examples above, we now have the full context to see what is
going on when we call:
.. code-block:: python
from libqtile.command.client import CommandClient
c = CommandClient()
print(c.call("status")())
from libqtile.command.client import InteractiveCommandClient
c = InteractiveCommandClient()
print(c.status())
In both cases, the command clients are constructed with the default command
interface, which sets up an IPC connection to the running Qtile instance, and
starts the client at the graph root. When we call ``c.call("status")`` or
``c.status``, we navigate the command client to the ``status`` command on the
root graph object. When these are invoked, the commands graph calls are
dispatched via the IPC command interface and the results then sent back and
printed on the local command line.
The power that can be realized by separating out the traversal and resolution
of objects in the command graph from actually invoking or looking up any
objects within the graph can be seen in the ``lazy`` module. By creating a
lazy evaluated command client, we can expose the graph traversal and object
resolution functionality via the same ``InteractiveCommandClient`` that is used
to perform live command execution in the Qtile prompt.
|