Skip to content

Hacking into Fabric

Fabric is made to be a hackable package at its core. This guide will walk you through making your own modifications to Fabric’s source!

Before doing anything else, you should prepare your development environment; to do that, read this page.

Fabric has a very special widget at its core: Widget(fabric.widgets.widget). Nearly every other Fabric widget inherits properties and methods from this widget. This makes it simple to make a modification that is reflected across all widgets.

For details on services, head over to this wiki page.

To start writing your own widget, you should first check if GTK already includes that widget; after all, we don’t want to reinvent the wheel.

If GTK does implement the widget you’re looking for, then the work will be much simpler. If it doesn’t, you don’t need to worry, since this page will also teach you how to write your own widget from scratch!

Do one of the following to get the widget:

  • Just use it: Fabric is fully compatible with GTK widgets, so you can import the widget and use it however you need to. Note that this will not provide the new widget with all of Fabric’s features (i.e setting the style within the construction), so if you require these see the option below.

  • Fabricate the widget: fabricating a widget means that you take a GTK widget and convert it into a Fabric widget; this will make it possible to use Fabric’s features like special methods and properties.

To start fabricating a widget, first check the type of the widget you want to fabricate. In general, there are two types of widgets; a regular widget and a container.

Normal widgets (AKA non-containers) are widgets that inherit properties and methods directly from the base Widget class and can’t hold any child widgets (i.e. labels and images).

Containers are widgets that inherit properties and methods from the base Container class (which eventually inherits methods and properties from the base Widget). These container widgets can hold one or multiple widgets as children (i.e. boxes and windows).

Next, you should locate where your widget class lives. For example, if your non-container widget is under the name MyWidget in GTK:

import gi # import the gi repository to get GTK from it later
gi.require_version("Gtk", "3.0") # graps the version 3 of GTK since this is what fabric uses
from gi.repository import Gtk # now we have GTK (version 3) imported
from fabric.widgets.widget import Widget # imports fabric's base widget
class MyFabricatedWidget(Gtk.MyWidget, Widget): # creates a new class named "MyFabricatedWidget", this class inherits the desired GTK widget and fabric's base widget
def __init__(self, **kwargs): # the initializer function, **kwargs (a dict) means whatever you pass as extra argument will be in that dict
# you can set more arguments to this newly created class, this is useful if you want to make this widget able to do more during the initialization phase
# you may add more logic here to handle the new arguments (if any)
super().__init__(**kwargs) # initializes the new mixed class

Fabricating a container widget is the same process as fabricating a normal widget; just use the Container widget from Fabric instead of Widget when inheriting.

You ran into an issue? Don’t panic! If your code does not work at all, read the error; most of the times, it provides lots of information (i.e the call stack).

There are some specific errors that may be more difficult to debug, such as errors produced by GTK itself due to your function/method call. These errors, in most cases, only provide the assertion error (which might be handy to get a traceback). Here’s how to debug these invisible Heisenbugs:

  • Check your environment variables
    • For example, I try running an X11 window under X11 but the environment variable GDK_BACKEND is set to wayland due to me forcing the editor to to set it this way and forgetting. This was the problem that caused my X11 window to open as a normal window, not a layer. (Another possibility here was that I was using GNOME, which hasn’t implemented the layer-shell protocol on Wayland and therefore makes it impossible to do anything to fix issues with it.)
  • Use a debugger and breakpoints to identify the source of the problem
    • For example, I decide to use a function that I know exists in the source of the GI repository package I’m trying to use, but my code produces a segmentation fault every time I run it. (Later, while reading the docs, I figured out that this specific function was marked as unsafe by PyGObject.)
  • Check your thread-safety
    • For example, I make a new service and it works like a charm. But after an unknown amount of time, the entire thing crashes. I later found out from a friend that I was updating the properties of an object defined in the main thread in a different thread, leading to a loss of thread safety. After discovering this issue, I used a call to fabric.utils.idle_add in my thread whenever I want to update anything in the main thread.
  • Variable or method overwrites
    • For example, I make a new class that inherits Service. I then write a new method: connect, but when I run the code I get an ArgumentError even though I pass everything necessary into the method. I then found out that the Service class already has a connect method that is used internally, so when I try to use connect in my new class I’m actually calling the method in the Service class. I fixed this issue by simply renaming the method.

There’s another kind of bugs, the visual bugs. A visual bug means that the code and logic works as expected, but there are graphical issues with rendering or drawing. A great tool to use for these issues with Fabric or GTK is the GTK Inspector.

To get the inspector window in Fabric, use the open_inspector() method on your Application instance or set the environment variable GTK_DEBUG to interactive before running your code.


If you still can’t find what’s causing your code not to act as it should, you can always hop into Fabric’s Discord server and ask your question - we’re happy to help!