Skip to content
📖 10 min🎯Difficulty:
Prerequisites:Power profilesPopupWindow
You'll build:A session management dialog with power actions

The Problem

Users need to log out, reboot, shut down, suspend, or hibernate their system. Without shell integration, they fall back to terminal commands or the compositor's built-in exit keybind.

The Naive Approach

Run systemctl commands directly:

qml
Button {
  text: "Shut Down"
  onClicked: Qt.process(["systemctl", "poweroff"])
}

systemctl poweroff works, but doesn't support session-specific actions (exit the compositor without shutting down the system) and may not integrate with polkit for password-protected actions.

💡Mental Model

Think of session management like the exit door of a building. You can leave temporarily (suspend — step outside for air), leave for the day (logout — clock out), or close the building entirely (shutdown — lock up and go home). Each action needs a clear label and confirmation.

The Idea

Create a session popup with buttons for each action. Use systemctl for system-level actions and hyprctl (or compositor-specific) for logout. Integrate with logind via loginctl for session-level control.

Let's Build It

qml
// SessionControl.qml
PopupWindow {
  id: root
  width: 280; height: 240
  anchor.window: panel

  property bool confirmShutdown: false

  Rectangle {
    anchors.fill: parent; radius: 8; color: "#1e1e2e"

    Column {
      anchors { fill: parent; margins: 16 }; spacing: 12

      Text { text: "Session"; font.pixelSize: 14; font.bold: true; color: "#cdd6f4" }

      // Log out
      SessionButton {
        text: "Log Out"
        icon: "🚪"
        onClicked: {
          Qt.process(["hyprctl", "dispatch", "exit"])
          // For Sway: Qt.process(["swaymsg", "exit"])
          // For KWin: Qt.process(["qdbus", "org.kde.KWin", "/Session", "logout"])
        }
      }

      // Suspend
      SessionButton {
        text: "Suspend"
        icon: "💤"
        onClicked: Qt.process(["systemctl", "suspend"])
      }

      // Reboot
      SessionButton {
        text: "Reboot"
        icon: "🔄"
        onClicked: Qt.process(["systemctl", "reboot"])
      }

      // Shutdown with confirmation
      SessionButton {
        text: root.confirmShutdown ? "Confirm Shutdown?" : "Shut Down"
        icon: "⏻"
        onClicked: {
          if (root.confirmShutdown) {
            Qt.process(["systemctl", "poweroff"])
          } else {
            root.confirmShutdown = true
            timer.start()
          }
        }
      }
    }
  }

  Timer {
    id: timer
    interval: 3000
    onTriggered: root.confirmShutdown = false
  }
}

Helper component:

qml
// SessionButton.qml
Rectangle {
  id: control
  property string text: ""
  property string icon: ""
  signal clicked()

  width: parent.width; height: 36; radius: 6; color: "#313244"

  Row {
    anchors { fill: parent; leftMargin: 12; rightMargin: 12 }
    spacing: 8; anchors.verticalCenter: parent.verticalCenter
    Text { text: control.icon; font.pixelSize: 16; anchors.verticalCenter: parent.verticalCenter }
    Text { text: control.text; font.pixelSize: 13; color: "#cdd6f4"; anchors.verticalCenter: parent.verticalCenter }
  }

  MouseArea {
    anchors.fill: parent
    cursorShape: Qt.PointingHandCursor
    hoverEnabled: true
    onEntered: control.color = "#45475a"
    onExited: control.color = "#313244"
    onClicked: control.clicked()
  }
}
🛠️Let's Build It

The session popup provides a clean list of power actions. Shutdown requires confirmation (click once to arm, click again within 3 seconds to execute). Each compositor has a different logout command — detect and use the appropriate one.

Action Reference

ActionCommandNotes
Log Out (Hyprland)hyprctl dispatch exitExits the compositor
Log Out (Sway)swaymsg exitExits the compositor
Log Out (KWin)qdbus org.kde.KWin /Session logoutD-Bus call
Suspendsystemctl suspendSaves state to RAM
Hibernatesystemctl hibernateSaves state to disk
Hybrid Sleepsystemctl hybrid-sleepRAM + disk suspend
Rebootsystemctl rebootFull system restart
Shutdownsystemctl poweroffPower off system
Lock Screenloginctl lock-sessionLocks the current session
⚠️Common Mistake

Not checking for polkit. systemctl poweroff may fail on systems where the user doesn't have passwordless sudo. Use pkexec systemctl poweroff or integrate with polkit via qt-polkit for proper authentication.

Forgetting hibernate support. Hibernate requires sufficient swap space. Check cat /sys/power/state for available sleep states. Disable hibernate if disk is not listed.

No confirmation for destructive actions. Shutdown and reboot should always have a confirmation step. Accidental clicks are too destructive to ignore. A 3-second confirmation timer is standard UX practice.

Under the Hood

Session management uses systemd-logind for session lifecycle. Actions like suspend, hibernate, and hybrid-sleep go through logind's inhibitors — applications can inhibit sleep (e.g., "A movie is playing"). Your session popup can query inhibitors via loginctl list-inhibitors and warn the user before forcing a suspend.

Exercises

Exercise
Add a "countdown shutdown" option. When shutdown is selected, show a 30-second countdown with a cancel button. This gives users time to save work before the system powers off.
⭐⭐Exercise
Query logind inhibitors before suspend. If an application is inhibiting sleep (e.g., "Firefox playing audio"), warn the user and list the inhibiting applications.
⭐⭐⭐Exercise
Add a "schedule shutdown" dialog. Let users set a timer (30 min, 1 hour, custom) after which the system auto-shuts down. Implement the timer in your shell service using `shutdown +30` command.
What You've Learned
  • Use compositor-specific commands for logout, systemctl for power actions
  • Add confirmation step for destructive actions (shutdown, reboot)
  • Support suspend, hibernate, and hybrid-sleep where available
  • Check for polkit and logind inhibitors to avoid failed or blocked actions

Built with VitePress. Licensed under MIT.