Skip to content

Instantly share code, notes, and snippets.

@andrew-sayers
Last active April 26, 2025 08:24
Show Gist options
  • Select an option

  • Save andrew-sayers/1c4a24f86a9a4c1b1e38d109f1bd1d1e to your computer and use it in GitHub Desktop.

Select an option

Save andrew-sayers/1c4a24f86a9a4c1b1e38d109f1bd1d1e to your computer and use it in GitHub Desktop.
Get notified when the user in on-seat
// SPDX-License-Identifier: CC0-1.0
/*
* Linux systems can have multiple sessions logged in at once.
* For example, siblings with separate accounts on a family laptop
* can use the "switch user" function to share access without
* closing all their programs every time.
*
* Systemd provides user services to handle services that run for
* each user, but this can be a problem for services that should only
* run for the current user. For example, audio notifications
* should only be played for the user currently using the computer.
*
* To pause a service, you need to monitor whether the specified user
* is *active*, and whether they have *sessions with seats*.
*
* This gist shows one way to continually monitor those values.
*
* To run this gist, do:
*
* gcc user-on-seat.c -lsystemd -o user-on-seat
* ./user-on-seat
* # (press e.g. ctrl+alt+F8 then ctrl+alt+F7 to switch sessions)
*
* See also:
* * https://www.freedesktop.org/software/systemd/man/latest/sd_login_monitor.html
* * https://www.freedesktop.org/software/systemd/man/latest/sd_uid_get_state.html
* * https://gitlab.freedesktop.org/pipewire/wireplumber/-/blob/master/modules/module-logind.c#L52
*/
#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <stddef.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <systemd/sd-login.h>
/**
* @brief Check whether the user is currently active
*/
struct sd_active {
sd_login_monitor *m;
int uid;
} sd_active;
/**
* @brief Destroy an sd_active struct
* @param sa struct to destroy
*/
static inline void sd_active_cleanup(struct sd_active *sa) {
sd_login_monitor_unref(sa->m);
}
/**
* @brief Init an sd_active struct
* @param sa struct to init
* @return 0 on success, negative errno-style code on error
*/
static inline int sd_active_init(struct sd_active *sa) {
sa->m = NULL;
sa->uid = getuid();
return sd_login_monitor_new("uid", &sa->m);
}
/**
* @brief Init a pollfd to listen for changes in activity
* @param sa struct to listen for
* @param pfd poll fd to init
* @return 0 on success, negative errno-style code on error
*/
static inline int sd_active_pollfd_init(struct sd_active *sa, struct pollfd *pfd) {
pfd->fd = sd_login_monitor_get_fd(sa->m);
if (pfd->fd < 0) return pfd->fd;
pfd->events = sd_login_monitor_get_events(sa->m);
return (pfd->events < 0) ? pfd->events : 0;
}
/**
* @brief Get timeout for poll()
* @param sa struct to poll
* @return timeout in milliseconds on success (may be -1), negative errno-style code on error (not -1)
*/
static inline int sd_active_get_poll_timeout(struct sd_active *sa) {
uint64_t timeout_usec;
int res = sd_login_monitor_get_timeout(sa->m, &timeout_usec);
if (res < 0) {
// sd_login_monitor_get_timeout returns -EINVAL and -ENOMEM,
// neither of which should be -1:
assert(res != -1);
return res;
}
if (timeout_usec == (uint64_t)-1) {
return -1;
} else {
struct timespec ts;
res = clock_gettime(CLOCK_MONOTONIC, &ts);
if (res < 0) return -errno;
uint64_t n;
n = (uint64_t) ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
return timeout_usec > n ? (int) ((timeout_usec - n + 999) / 1000) : 0;
}
}
static inline void free_string(char **str) {
free(*str);
}
/**
* @brief Check whether the current user is active
* @param sa struct to check
* @return 1 if active, 0 if inactive, else negative errno-style code
*/
static inline int sd_active_check(struct sd_active *sa) {
__attribute__((cleanup(free_string))) char *state = NULL;
int res = sd_login_monitor_flush(sa->m);
if (res < 0) return res;
res = sd_uid_get_state(sa->uid, &state);
if (res < 0) return res;
if (strcmp(state,"active")) return 0;
res = sd_uid_get_seats(sa->uid, 1, NULL);
return (res >= 0) ? !!res : res;
}
int main() {
int res;
__attribute__((cleanup(sd_active_cleanup))) struct sd_active sa;
if ((res = sd_active_init(&sa)) < 0) {
fprintf(stderr, "sd_active_init(): %s\n", strerror(-res));
return 2;
}
struct pollfd pfd;
if ((res = sd_active_pollfd_init(&sa, &pfd)) < 0) {
fprintf(stderr, "sd_active_pollfd_init(): %s\n", strerror(-res));
return 2;
}
int was_active = 0;
while (1) {
int active = sd_active_check(&sa);
if (active < 0) {
fprintf(stderr, "sd_active_check(): %s\n", strerror(-active));
return 2;
}
if (active != was_active) {
was_active = active;
printf("Active: %d\n", active);
}
int timeout = sd_active_get_poll_timeout(&sa);
if (timeout < -1) {
fprintf(stderr, "sd_active_get_poll_timeout(): %s\n", strerror(-timeout));
return 2;
}
if (poll(&pfd, 1, timeout) < 0) {
fprintf(stderr, "poll(): %s\n", strerror(errno));
return 2;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment