Components Guide¶
Hyprpy components represent all the moving parts of a running Hyprland instance. They allow us to retrieve information about the compositor in real time.
Hyprpy handles four component types:
Let’s get started, and go through some examples.
The Instance¶
To retrieve a component, we first have to get the current Hyprland instance:
from hyprpy.components.instances import Instance
# Get the current instance
instance = Instance()
We can also use the shorthand alias Hyprland
, which looks slightly nicer:
from hyprpy import Hyprland
instance = Hyprland()
By default, Hyprpy reads the $HYPRLAND_INSTANCE_SIGNATURE
environment variable to find the Hyprland Instance Signature.
If the environment variable is unset because we are in an SSH session, or if we want to access a second
Hyprland instance running on our system, we can specify its value manually:
from hyprpy import Hyprland
instance = Hyprland("v0.25.0_1691941479")
Reading component data¶
Now that we have our instance, we can query it for components. Components have data attributes, which allow us to access information about them. Let’s grab the currently active window and print some information about it:
from hyprpy import Hyprland
instance = Hyprland()
window = instance.get_active_window()
print(window)
# Output: "<Window(address='0x1981f40', wm_class='kitty', title='python ~/d/p/src')>"
print(window.width)
# Output: 1258
print(window.wm_class)
# Output: 'kitty'
Here, we queried the instance for the active window, and printed the window’s width
and wm_class
data attributes, which tell us the current width and display class of the window.
Windows, workspaces and monitors have a wide range of useful data attributes. For a complete list of data attributes for each type of component, refer to the Component API.
Note
Component data attributes are read-only. Writing new values into them will not raise an exception, but will also have no effect on Hyprland’s actual state.
Reacting to events¶
Besides access to other components, the Instance
class provides the watch()
method, which
monitors Hyprland for events and emits a specific Signal
whenever an event occurs. By connecting our own callback functions to these signals,
we can execute python code dynamically, in response to changes in Hyprland’s state:
from hyprpy import Hyprland
from hyprpy.utils.shell import run_or_fail
instance = Hyprland()
# Define a callback function
def workspace_changed(sender, **kwargs):
run_or_fail(["notify-send", "Workspace Changed"])
# Connect the callback function to the signal
instance.signal_active_workspace_changed.connect(workspace_changed)
# Start watching for hyprland events
instance.watch()
In this example, we defined our own callback function called workspace_changed
.
The function executes a shell command, notify-send
, with "Workspace Changed"
as an argument.
We used a helper function called run_or_fail()
here to run the shell command,
but the body of our callback function can be any valid python code.
Then, we connected our callback function to the Instance’s signal_active_workspace_changed
signal and, finally, we called the Instance’s watch()
method.
The watch()
method runs indefinitely, but executes our callback
function whenever the underlying signal is emitted.
In this case, we get a desktop notification whenever we switch to another workspace.
Attention
The callback’s function signature must be (sender, **kwargs)
.
Dispatched signals include some data about the event which triggered them. The data can be retrieved from the **kwargs in our callback function:
from hyprpy import Hyprland
from hyprpy.utils.shell import run_or_fail
instance = Hyprland()
def workspace_changed(sender, **kwargs):
# Retrieve the newly active workspace from the signal's data
active_workspace_id = kwargs.get('active_workspace_id')
run_or_fail(["notify-send", "Workspace Changed", f"Workspace is now {active_workspace_id}"])
instance.signal_active_workspace_changed.connect(workspace_changed)
instance.watch()
Building on the previous example, our desktop notification now also includes the ID of the workspace we switched to.
We can disconnect signals as well:
instance.signal_active_workspace_changed.disconnect(workspace_changed)
Aside from saving resources, disconnecting signals is useful if we only want our callback to get triggered a few times.
The following table shows a list of available signals, and the data they send to the callback function:
Event |
Signal |
Signal Data |
---|---|---|
A workspace was created |
|
|
A workspace was destroyed |
|
|
The active workspace changed |
|
|
A window was created |
|
|
A window was destroyed |
|
|
The active window changed |
|
Note
The watch()
method is a blocking operation that runs
indefinitely.
Using signals in conjunction with watch()
is much more efficient
than polling, because Hyprpy watches Hyprland’s event socket
directly, saving on CPU time and I/O operations.
Component state¶
When we instantiate a component object (for example a Workspace
)
in Hyprpy, its data attributes reflect its current state in Hyprland.
As time passes and things happen in Hyprland, the object’s attributes may no longer reflect its actual
state in the compositor. There is no synchronization of state between a Hyprpy component its real-world counterpart.
Attention
With the exception of the Instance
object,
do not re-use component objects after their state may have changed.
Instead, we should use instantiated component objects immediately, and discard them once we have the information we need:
from hyprpy import Hyprland
instance = Hyprland()
workspace_3 = instance.get_workspace_by_id(3)
if workspace_3 and workspace_3.window_count > 2:
... # do some stuff
... # time passes, things happen
# Don't do this:
for window in workspace_3:
...
# The windows on workspace_3, and the workspace itself may have changed
# Instead, reinstantiate the workspace using the instance object:
workspace_3 = instance.get_workspace_by_id(3)
if workspace_3:
for window in workspace_3.windows:
... # do some other stuff
The Instance
object’s data attributes won’t change unless
you restart Hyprland, so it is generally safe to re-use. For other components, if you suspect that
the component’s state has changed since it has been instantiated, it is better to overwrite it
with a fresh copy.
Further reading¶
And that’s all you need to know to get started!
For more details, check out the Components API and Utilities.