Quickshell


Introduction

This page will walk you through the process of creating a simple bar/panel, and introduce you to all the basic concepts involved. You can use the QML Language Reference to learn about the syntax of the QML language.

Note that all the Green Links in code blocks will take you to the documentation for their respective types.

Config Files

Quickshell searches the quickshell subfolder of every XDG standard config path for configs. Usually this is ~/.config/quickshell.

Each named subfolder containing a shell.qml file is considered to be a config. If the base quickshell folder contains a shell.qml file, subfolders will not be considered.

A specific config can be picked using the --config or -c argument to Quickshell.

Configs at other paths, including raw qml files can be run with --path or -p.

Creating Windows

Quickshell has two main window types available, PanelWindow for bars and widgets, and FloatingWindow for standard desktop windows.

We’ll start with an example:

import Quickshell // for PanelWindow
import QtQuick // for Text

PanelWindow {
  anchors {
    top: true
    left: true
    right: true
  }

  implicitHeight: 30

  Text {
    // center the bar in its parent component (the window)
    anchors.centerIn: parent

    text: "hello world"
  }
}

The above example creates a bar/panel on your currently focused monitor with a centered piece of text. It will also reserve space for itself on your monitor.

More information about available properties is available in the type reference.

Running a process

Now that we have a piece of text, what if it did something useful? To start with lets make a clock. To get the time we’ll use the date command.

Note

Quickshell can do more than just run processes. Read until the end for more information.

We can use a Process object to run commands and a StdioCollector to read their output.

We’ll listen to the Go to StdioCollector.streamFinished() signal with a signal handler to update the text on the clock.

Note

Quickshell live-reloads your code. You can leave it open and edit the original file. The panel will reload when you save it.

import Quickshell
import Quickshell.Io // for Process
import QtQuick

PanelWindow {
  anchors {
    top: true
    left: true
    right: true
  }

  implicitHeight: 30

  Text {
    // give the text an ID we can refer to elsewhere in the file
    id: clock

    anchors.centerIn: parent

    // create a process management object
    Process {
      // the command it will run, every argument is its own string
      command: ["date"]

      // run the command immediately
      running: true

      // process the stdout stream using a StdioCollector
      // Use StdioCollector to retrieve the text the process sends
      // to stdout.
      stdout: StdioCollector {
        // Listen for the streamFinished signal, which is sent
        // when the process closes stdout or exits.
        onStreamFinished: clock.text = this.text // `this` can be omitted
      }
    }
  }
}

Running code at an interval

With the above example, your bar should now display the time, but it isn’t updating! Let’s use a Timer to fix that.

import Quickshell
import Quickshell.Io
import QtQuick

PanelWindow {
  anchors {
    top: true
    left: true
    right: true
  }

  implicitHeight: 30

  Text {
    id: clock
    anchors.centerIn: parent

    Process {
      // give the process object an id so we can talk
      // about it from the timer
      id: dateProc

      command: ["date"]
      running: true

      stdout: StdioCollector {
        onStreamFinished: clock.text = this.text
      }
    }

    // use a timer to rerun the process at an interval
    Timer {
      // 1000 milliseconds is 1 second
      interval: 1000

      // start the timer immediately
      running: true

      // run the timer again when it ends
      repeat: true

      // when the timer is triggered, set the running property of the
      // process to true, which reruns it if stopped.
      onTriggered: dateProc.running = true
    }
  }
}

Reusable components

If you have multiple monitors you might have noticed that your bar is only on one of them. If not, you’ll still want to follow this section to make sure your bar doesn’t disappear if your monitor disconnects.

We can use a Variants object to create instances of non widget items. (See Repeater for doing something similar with visual items.)

The Variants type creates instances of a Component based on a data model you supply. (A component is a re-usable tree of objects.)

The most common use of Variants in a shell is to create instances of a window (your bar) based on your monitor list (the data model).

Variants will inject the values in the data model into each new component’s modelData property, which means we can easily pass each screen to its own component. (See Go to QsWindow.screen.)

import Quickshell
import Quickshell.Io
import QtQuick

Variants {
  model: Quickshell.screens;

  delegate: Component {
    PanelWindow {
      // the screen from the screens list will be injected into this
      // property
      property var modelData

      // we can then set the window's screen to the injected property
      screen: modelData

      anchors {
        top: true
        left: true
        right: true
      }

      implicitHeight: 30

      Text {
        id: clock
        anchors.centerIn: parent

        Process {
          id: dateProc
          command: ["date"]
          running: true

          stdout: StdioCollector {
            onStreamFinished: clock.text = this.text
          }
        }

        Timer {
          interval: 1000
          running: true
          repeat: true
          onTriggered: dateProc.running = true
        }
      }
    }
  }
}

See also: Property Bindings, Array.map

With this example, bars will be created and destroyed as you plug and unplug them, due to the reactive nature of the Go to Quickshell.screens property. (See: Reactive Bindings.)

Now there’s an important problem you might have noticed: when the window is created multiple times we also make a new Process and Timer, which makes the bar less efficient than it could be. We can fix this by moving the Process and Timer outside of the window using Scope.

Error

This code will not work correctly.

import Quickshell
import Quickshell.Io
import QtQuick

Scope {
  Variants {
    model: Quickshell.screens

    delegate: Component {
      PanelWindow {
        property var modelData
        screen: modelData

        anchors {
          top: true
          left: true
          right: true
        }

        implicitHeight: 30

        Text {
          id: clock
          anchors.centerIn: parent
        }
      }
    }
  }

  Process {
    id: dateProc
    command: ["date"]
    running: true

    stdout: StdioCollector {
      onStreamFinished: clock.text = this.text
    }
  }

  Timer {
    interval: 1000
    running: true
    repeat: true
    onTriggered: dateProc.running = true
  }
}

However there is a problem with naively moving the Process and Timer out of the component. What about the clock that the process references?

If you run the above example you’ll see something like this in the console every second:

WARN scene: **/shell.qml[36:-1]: ReferenceError: clock is not defined
WARN scene: **/shell.qml[36:-1]: ReferenceError: clock is not defined
WARN scene: **/shell.qml[36:-1]: ReferenceError: clock is not defined
WARN scene: **/shell.qml[36:-1]: ReferenceError: clock is not defined
WARN scene: **/shell.qml[36:-1]: ReferenceError: clock is not defined

This is because the clock object, even though it has an ID, cannot be referenced outside of its component. Remember, components can be created any number of times, including zero, so clock may not exist or there may be more than one, meaning there isn’t an object to refer to from here.

We can fix it with a Property Definition.

We can define a property inside of the ShellRoot and reference it from the clock text instead. Due to QML’s Reactive Bindings, the clock text will be updated when we update the property for every clock that currently exists.

import Quickshell
import Quickshell.Io
import QtQuick

Scope {
  id: root

  // add a property in the root
  property string time

  Variants {
    model: Quickshell.screens

    delegate: Component {
      PanelWindow {
        property var modelData
        screen: modelData

        anchors {
          top: true
          left: true
          right: true
        }

        implicitHeight: 30

        Text {
          // remove the id as we don't need it anymore

          anchors.centerIn: parent

          // bind the text to the root object's time property
          text: root.time
        }
      }
    }
  }

  Process {
    id: dateProc
    command: ["date"]
    running: true

    stdout: StdioCollector {
      // update the property instead of the clock directly
      onStreamFinished: root.time = this.text
    }
  }

  Timer {
    interval: 1000
    running: true
    repeat: true
    onTriggered: dateProc.running = true
  }
}

Now we’ve fixed the problem so there’s nothing actually wrong with the above code, but we can make it more concise:

  1. Components can be defined implicitly, meaning we can remove the component wrapping the window and place the window directly into the delegate property.
  2. The Go to Variants.delegate property is a Default Property, which means we can skip the delegate: part of the assignment. We’re already using the default property of ShellRoot to store our Variants, Process, and Timer components among other things.

This is what our shell looks like with the above (optional) cleanup:

import Quickshell
import Quickshell.Io
import QtQuick

Scope {
  id: root
  property string time

  Variants {
    model: Quickshell.screens

    PanelWindow {
      property var modelData
      screen: modelData

      anchors {
        top: true
        left: true
        right: true
      }

      implicitHeight: 30

      Text {
        anchors.centerIn: parent
        text: root.time
      }
    }
  }

  Process {
    id: dateProc
    command: ["date"]
    running: true

    stdout: StdioCollector {
      onStreamFinished: root.time = this.text
    }
  }

  Timer {
    interval: 1000
    running: true
    repeat: true
    onTriggered: dateProc.running = true
  }
}

Multiple files

In an example as small as this, it isn’t a problem, but as the shell grows it might be preferable to separate it into multiple files.

To start with, let’s move the entire bar into a new file.

// shell.qml
import Quickshell

Scope {
  Bar {}
}
// Bar.qml
import Quickshell
import Quickshell.Io
import QtQuick

Scope {
  id: root
  property string time

  Variants {
    model: Quickshell.screens

    PanelWindow {
      property var modelData
      screen: modelData

      anchors {
        top: true
        left: true
        right: true
      }

      implicitHeight: 30

      Text {
        anchors.centerIn: parent
        text: root.time
      }
    }
  }

  Process {
    id: dateProc
    command: ["date"]
    running: true

    stdout: StdioCollector {
      onStreamFinished: root.time = this.text
    }
  }

  Timer {
    interval: 1000
    running: true
    repeat: true
    onTriggered: dateProc.running = true
  }
}
See also: Scope

Any qml file that starts with an uppercase letter can be referenced this way. We can bring in other folders as well using import statements.

Now what about breaking out the clock? This is a bit more complex because the clock component in the bar, as well as the process and timer that make up the actual clock, need to be dealt with.

To start with, we can move the clock widget to a new file. For now it’s just a single Text object but the same concepts apply regardless of complexity.

// ClockWidget.qml
import QtQuick

Text {
  // A property the creator of this type is required to set.
  // Note that we could just set `text` instead, but don't because your
  // clock probably will not be this simple.
  required property string time

  text: time
}
// Bar.qml
import Quickshell
import Quickshell.Io
import QtQuick

Scope {
  id: root
  property string time

  Variants {
    model: Quickshell.screens

    PanelWindow {
      property var modelData
      screen: modelData

      anchors {
        top: true
        left: true
        right: true
      }

      implicitHeight: 30

      // the ClockWidget type we just created
      ClockWidget {
        anchors.centerIn: parent
        time: root.time
      }
    }
  }

  Process {
    id: dateProc
    command: ["date"]
    running: true

    stdout: StdioCollector {
      onStreamFinished: root.time = this.text
    }
  }

  Timer {
    interval: 1000
    running: true
    repeat: true
    onTriggered: dateProc.running = true
  }
}

While this example is larger than what we had before, we can now expand on the clock widget without cluttering the bar file.

Let’s deal with the clock’s update logic now:

// Time.qml
import Quickshell
import Quickshell.Io
import QtQuick

Scope {
  id: root
  property string time

  Process {
    id: dateProc
    command: ["date"]
    running: true

    stdout: StdioCollector {
      onStreamFinished: root.time = this.text
    }
  }

  Timer {
    interval: 1000
    running: true
    repeat: true
    onTriggered: dateProc.running = true
  }
}
// Bar.qml
import Quickshell

Scope {
  // the Time type we just created
  Time { id: timeSource }

  Variants {
    model: Quickshell.screens

    PanelWindow {
      property var modelData
      screen: modelData

      anchors {
        top: true
        left: true
        right: true
      }

      implicitHeight: 30

      ClockWidget {
        anchors.centerIn: parent
        // now using the time from timeSource
        time: timeSource.time
      }
    }
  }
}

Singletons

Now you might be thinking, why do we need the Time type in our bar file, and the answer is we don’t. We can make Time a Singleton.

A singleton object has only one instance, and is accessible from any scope.

// Time.qml

// with this line our type becomes a singleton
pragma Singleton

import Quickshell
import Quickshell.Io
import QtQuick

// your singletons should always have Singleton as the type
Singleton {
  id: root
  property string time

  Process {
    id: dateProc
    command: ["date"]
    running: true

    stdout: StdioCollector {
      onStreamFinished: root.time = this.text
    }
  }

  Timer {
    interval: 1000
    running: true
    repeat: true
    onTriggered: dateProc.running = true
  }
}
// ClockWidget.qml
import QtQuick

Text {
  // we no longer need time as an input

  // directly access the time property from the Time singleton
  text: Time.time
}
// Bar.qml
import Quickshell

Scope {
  // no more time object

  Variants {
    model: Quickshell.screens

    PanelWindow {
      property var modelData
      screen: modelData

      anchors {
        top: true
        left: true
        right: true
      }

      implicitHeight: 30

      ClockWidget {
        anchors.centerIn: parent

        // no more time binding
      }
    }
  }
}

Quickshell Support Libraries

In addition to calling external processes, Quickshell comes with a large set of support libraries for common OS integrations and tasks. These libraries are indexed on the left sidebar.

One of these integrations is SystemClock, which exposes the system time in an easy to use way.

We can use Go to SystemClock.date to get a Date object to display. The Qt.formatDateTime() function can be used to easily format the date as shown below.

Go to SystemClock.precision can be set to Minutes to improve battery life if you don’t show seconds on your clock, as Quickshell will have less work to do.

// Time.qml
pragma Singleton

import Quickshell
import QtQuick

Singleton {
  id: root
  // an expression can be broken across multiple lines using {}
  readonly property string time: {
    // The passed format string matches the default output of
    // the `date` command.
    Qt.formatDateTime(clock.date, "ddd MMM d hh:mm:ss AP t yyyy")
  }

  SystemClock {
    id: clock
    precision: SystemClock.Seconds
  }
}