bar: add idle inhibitor (#459)

* bar: add idle inhibitor

* bar: change idle inhibitor button color

* nix: link external scripts instead of install

* services/idleinhibitor: add IPC handler

* better styling

* move cpp scripts to assets/cpp

---------

Co-authored-by: Soramane <61896496+soramanew@users.noreply.github.com>
This commit is contained in:
Davi Ribeiro 2025-08-23 07:21:35 -03:00 committed by GitHub
parent b10c9a8006
commit f747d82b93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 318 additions and 9 deletions

View file

@ -237,6 +237,10 @@ All configuration options are in `~/.config/caelestia/shell.json`.
{
"id": "power",
"enabled": true
},
{
"id": "idleInhibitor",
"enabled": false
}
],
"persistent": true,

View file

@ -0,0 +1,182 @@
#include <csignal>
#include <cstring>
#include <iostream>
#include <unistd.h>
#include <wayland-client-protocol.h>
#include <wayland-client.h>
// You'll need to generate these headers from the protocol XML files:
// wayland-scanner client-header < idle-inhibit-unstable-v1.xml >
// idle-inhibit-client-protocol.h wayland-scanner private-code <
// idle-inhibit-unstable-v1.xml > idle-inhibit-protocol.c
extern "C" {
#include "idle-inhibitor.h"
}
class WaylandIdleInhibitor {
private:
struct wl_display *display;
struct wl_registry *registry;
struct wl_compositor *compositor;
struct wl_surface *surface;
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager;
struct zwp_idle_inhibitor_v1 *idle_inhibitor;
static WaylandIdleInhibitor *instance;
// Registry listener to get global objects
static void registry_global(void *data, struct wl_registry *registry,
uint32_t id, const char *interface,
uint32_t version) {
WaylandIdleInhibitor *inhibitor = static_cast<WaylandIdleInhibitor *>(data);
if (strcmp(interface, wl_compositor_interface.name) == 0) {
inhibitor->compositor = static_cast<struct wl_compositor *>(
wl_registry_bind(registry, id, &wl_compositor_interface, 1));
} else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) ==
0) {
inhibitor->idle_inhibit_manager =
static_cast<struct zwp_idle_inhibit_manager_v1 *>(wl_registry_bind(
registry, id, &zwp_idle_inhibit_manager_v1_interface, 1));
}
}
static void registry_global_remove(void *data, struct wl_registry *registry,
uint32_t id) {}
static const struct wl_registry_listener registry_listener;
public:
WaylandIdleInhibitor()
: display(nullptr), registry(nullptr), compositor(nullptr),
surface(nullptr), idle_inhibit_manager(nullptr),
idle_inhibitor(nullptr) {
instance = this;
}
~WaylandIdleInhibitor() { cleanup(); }
bool initialize() {
display = wl_display_connect(nullptr);
if (!display) {
return false;
}
registry = wl_display_get_registry(display);
if (!registry) {
return false;
}
wl_registry_add_listener(registry, &registry_listener, this);
// Roundtrip to get all globals
wl_display_roundtrip(display);
if (!compositor || !idle_inhibit_manager) {
return false;
}
return true;
}
bool createInvisibleSurface() {
surface = wl_compositor_create_surface(compositor);
if (!surface) {
return false;
}
return true;
}
bool inhibitIdle() {
if (!surface || !idle_inhibit_manager) {
return false;
}
idle_inhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(
idle_inhibit_manager, surface);
if (!idle_inhibitor) {
std::cerr << "Failed to create idle inhibitor\n";
return false;
}
wl_display_roundtrip(display);
std::cout << "Idle inhibition activated\n";
return true;
}
void cleanup() {
if (idle_inhibitor) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor);
idle_inhibitor = nullptr;
}
if (surface) {
wl_surface_destroy(surface);
surface = nullptr;
}
if (idle_inhibit_manager) {
zwp_idle_inhibit_manager_v1_destroy(idle_inhibit_manager);
idle_inhibit_manager = nullptr;
}
if (compositor) {
wl_compositor_destroy(compositor);
compositor = nullptr;
}
if (registry) {
wl_registry_destroy(registry);
registry = nullptr;
}
if (display) {
wl_display_disconnect(display);
display = nullptr;
}
}
void run() {
while (wl_display_dispatch(display) != -1)
;
}
static WaylandIdleInhibitor *getInstance() { return instance; }
};
WaylandIdleInhibitor *WaylandIdleInhibitor::instance = nullptr;
const struct wl_registry_listener WaylandIdleInhibitor::registry_listener = {
WaylandIdleInhibitor::registry_global,
WaylandIdleInhibitor::registry_global_remove};
void signalHandler(int signal) {
WaylandIdleInhibitor *inhibitor = WaylandIdleInhibitor::getInstance();
if (inhibitor) {
inhibitor->cleanup();
}
exit(0);
}
int main() {
signal(SIGINT, signalHandler);
signal(SIGTERM, signalHandler);
signal(SIGHUP, signalHandler);
WaylandIdleInhibitor inhibitor;
if (!(inhibitor.initialize() && inhibitor.createInvisibleSurface() &&
inhibitor.inhibitIdle())) {
std::cerr << "Cannot inhibit idle!" << std::endl;
return 1;
}
inhibitor.run();
return 0;
}

View file

@ -46,6 +46,10 @@ JsonObject {
id: "power",
enabled: true
},
{
id: "idleInhibitor",
enabled: false
}
]
component Workspaces: JsonObject {

View file

@ -50,6 +50,7 @@
inputsFrom = [shell];
packages = with pkgs; [material-symbols rubik nerd-fonts.caskaydia-cove];
CAELESTIA_BD_PATH = "${shell}/bin/beat_detector";
CAELESTIA_II_PATH = "${shell}/bin/inhibit_idle";
};
});

View file

@ -143,6 +143,12 @@ ColumnLayout {
}
}
}
DelegateChoice {
roleValue: "idleInhibitor"
delegate: WrappedLoader {
sourceComponent: IdleInhibitor {}
}
}
}
}

View file

@ -0,0 +1,33 @@
import qs.components
import qs.services
import qs.config
import Quickshell
import QtQuick
StyledRect {
id: root
implicitWidth: implicitHeight
implicitHeight: icon.implicitHeight + Appearance.padding.small * 2
radius: Appearance.rounding.full
color: Qt.alpha(Colours.palette.m3primaryContainer, IdleInhibitor.enabled ? 1 : 0)
StateLayer {
function onClicked(): void {
IdleInhibitor.enabled = !IdleInhibitor.enabled;
}
}
MaterialIcon {
id: icon
anchors.centerIn: parent
anchors.horizontalCenterOffset: -1
text: "coffee"
color: IdleInhibitor.enabled ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3secondary
font.bold: true
font.pointSize: Appearance.font.size.normal
}
}

View file

@ -30,6 +30,9 @@
quickshell,
aubio,
pipewire,
wayland,
wayland-protocols,
wayland-scanner,
caelestia-cli,
withCli ? false,
extraRuntimeDeps ? [],
@ -61,15 +64,15 @@
fontconfig = makeFontsConf {
fontDirectories = [material-symbols rubik nerd-fonts.caskaydia-cove];
};
in
stdenv.mkDerivation {
pname = "caelestia-shell";
version = "${rev}";
beatDetector = stdenv.mkDerivation {
pname = "beat-detector";
version = "1.0";
src = ./..;
nativeBuildInputs = [gcc makeWrapper qt6.wrapQtAppsHook];
buildInputs = [quickshell aubio pipewire qt6.qtbase];
propagatedBuildInputs = runtimeDeps;
nativeBuildInputs = [gcc];
buildInputs = [aubio pipewire];
buildPhase = ''
mkdir -p bin
@ -77,22 +80,62 @@ in
-I${pipewire.dev}/include/pipewire-0.3 \
-I${pipewire.dev}/include/spa-0.2 \
-I${aubio}/include/aubio \
assets/beat_detector.cpp \
assets/cpp/beat-detector.cpp \
-o bin/beat_detector \
-lpipewire-0.3 -laubio
'';
installPhase = ''
install -Dm755 bin/beat_detector $out/bin/beat_detector
'';
};
idleInhibitor = stdenv.mkDerivation {
pname = "wayland-idle-inhibitor";
version = "1.0";
src = ./..;
nativeBuildInputs = [gcc wayland-scanner wayland-protocols];
buildInputs = [wayland];
buildPhase = ''
wayland-scanner client-header < ${wayland-protocols}/share/wayland-protocols/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml > idle-inhibitor.h
wayland-scanner private-code < ${wayland-protocols}/share/wayland-protocols/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml > idle-inhibitor.c
cp assets/cpp/idle-inhibitor.cpp .
gcc -o idle-inhibitor.o -c idle-inhibitor.c
g++ -o inhibit_idle idle-inhibitor.cpp idle-inhibitor.o -lwayland-client
'';
installPhase = ''
mkdir -p $out/bin
install -Dm755 inhibit_idle $out/bin/inhibit_idle
'';
};
in
stdenv.mkDerivation {
pname = "caelestia-shell";
version = "${rev}";
src = ./..;
nativeBuildInputs = [gcc makeWrapper qt6.wrapQtAppsHook];
buildInputs = [quickshell beatDetector idleInhibitor qt6.qtbase];
propagatedBuildInputs = runtimeDeps;
installPhase = ''
mkdir -p $out/share/caelestia-shell
cp -r ./* $out/share/caelestia-shell
makeWrapper ${quickshell}/bin/qs $out/bin/caelestia-shell \
--prefix PATH : "${lib.makeBinPath runtimeDeps}" \
--set FONTCONFIG_FILE "${fontconfig}" \
--set CAELESTIA_BD_PATH $out/bin/beat_detector \
--set CAELESTIA_BD_PATH ${beatDetector}/bin/beat_detector \
--set CAELESTIA_II_PATH ${idleInhibitor}/bin/inhibit_idle \
--add-flags "-p $out/share/caelestia-shell"
ln -sf ${beatDetector}/bin/beat_detector $out/bin
ln -sf ${idleInhibitor}/bin/inhibit_idle $out/bin
'';
meta = {

View file

@ -0,0 +1,36 @@
pragma Singleton
import Quickshell
import Quickshell.Io
Singleton {
id: root
property bool enabled: false
Process {
id: idleInhibitProc
running: root.enabled
command: [Quickshell.env("CAELESTIA_II_PATH") || "/usr/lib/caelestia/inhibit_idle"]
}
IpcHandler {
target: "idleInhibitor"
function isEnabled(): bool {
return root.enabled;
}
function toggle(): void {
root.enabled = !root.enabled;
}
function enable(): void {
root.enabled = true;
}
function disable(): void {
root.enabled = false;
}
}
}