Skip to content

Instantly share code, notes, and snippets.

@icculus
Created January 26, 2022 18:38
Show Gist options
  • Save icculus/d6a150a97f427599f1ab8837d7c4d4e6 to your computer and use it in GitHub Desktop.
Save icculus/d6a150a97f427599f1ab8837d7c4d4e6 to your computer and use it in GitHub Desktop.
Management of the power button on the PlayStation 1 playing card tin. https://twitter.com/icculus/status/1484628302853726208
// This C code is public domain. --ryan.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <time.h>
#include <errno.h>
#include <wiringPi.h>
#include <dirent.h>
#include <sys/types.h>
#include <signal.h>
#define RESTART_THRESHOLD 1000
#define SHUTDOWN_THRESHOLD 2000
static const char *us = "pacman-power-gpio"; // name for logging purposes
// look for the deepest decendant of procid and kill it with SIGINT. Returns
// 0 if it had no children we could see (this _is_ the deepest descendant).
// This lets us find the emulators running under EmulationStation and not the
// upper-level shell scripts that sit between EmulationStation and the
// emulators they launch. We stop looking once we kill something, as doing
// so will make other things look like the deepest decendent, so this isn't
// perfect, but it will catch the EmulationStation use case: one emulator
// running.
static int killDeepestChildren(const int procid)
{
int has_children = 0;
//printf("%s: Looking for children of pid %d...\n", us, procid);
DIR *dirp = opendir("/proc");
if (!dirp) {
fprintf(stderr, "%s: Failed to opendir('/proc'): %s\n", us, strerror(errno));
} else {
struct dirent *dent;
while ((dent = readdir(dirp)) != NULL) {
const int thisprocid = atoi(dent->d_name);
if (!thisprocid) {
continue; // only care about numbers.
}
char path[32];
char statstr[256];
snprintf(path, sizeof (path), "/proc/%d/stat", thisprocid);
FILE *io = fopen(path, "rb");
if (io) {
const char *rc = fgets(statstr, sizeof (statstr), io);
fclose(io);
if (rc != NULL) {
char state = 0;
int pid = 0, ppid = 0;
char comm[256];
if (sscanf(statstr, "%d %s %c %d", &pid, comm, &state, &ppid) == 4) {
if (ppid == procid) {
//printf("%s: Process %d %s is a child of %d\n", us, pid, comm, procid);
has_children = 1;
if (strcmp(comm, "(omxplayer.bin)") != 0) { // don't kill the preview video player running under emulationstation, it's not an emulator.
if (!killDeepestChildren(pid)) {
//printf("%s: Process %d %s has no children, killing it...\n", us, pid, comm);
printf("%s: Sending a SIGINT process %d %s ...\n", us, pid, comm);
kill(pid, SIGINT);
break;
}
}
}
}
}
}
}
closedir(dirp);
}
return has_children;
}
static int killProcesses(void)
{
// let's find Emulation Station's process id. You can use pidof for this,
// but we have to walk the /proc tree anyhow.
int found = 0;
int procid = 0;
DIR *dirp = opendir("/proc");
if (!dirp) {
fprintf(stderr, "%s: Failed to opendir('/proc'): %s\n", us, strerror(errno));
} else {
struct dirent *dent;
while ((dent = readdir(dirp)) != NULL) {
const int thisprocid = atoi(dent->d_name);
if (!thisprocid) {
continue; // only care about numbers.
}
char path[32];
char realpath[256];
snprintf(path, sizeof (path), "/proc/%d/exe", thisprocid);
const ssize_t rc = readlink(path, realpath, sizeof (realpath) - 1);
if (rc != -1) {
realpath[rc] = '\0'; // just in case.
char *ptr = strrchr(realpath, '/');
ptr = ptr ? ptr + 1 : realpath;
if (strcmp(ptr, "emulationstation") == 0) {
found = 1;
procid = thisprocid;
break;
}
}
}
closedir(dirp);
}
if (!found) {
fprintf(stderr, "%s: Couldn't find Emulation Station's process ID, can't stop its children.\n", us);
return 0;
}
printf("%s: Emulation Station's process ID appears to be %d\n", us, procid);
return killDeepestChildren(procid);
}
static uint64_t getTicks(void)
{
struct timespec t;
if (clock_gettime(CLOCK_MONOTONIC, &t) < 0) {
fprintf(stderr, "clock_gettime failed! (%s)\n", strerror(errno));
return 0;
}
return ((((uint64_t) t.tv_sec) * 1000) + (((uint64_t) t.tv_nsec) / 1000000));
}
int main(int argc, char **argv)
{
uint64_t pressedTicks = 0;
setvbuf(stdout, NULL, _IOLBF, 0); // make sure output is line-buffered.
setvbuf(stderr, NULL, _IOLBF, 0); // make sure output is line-buffered.
wiringPiSetupPhys();
while (1) {
const int buttonState = !digitalRead(5);
if (!buttonState && pressedTicks) {
const uint64_t elapsed = getTicks() - pressedTicks;
if (elapsed > RESTART_THRESHOLD) {
printf("%s: Power button has been held at least %d ticks. Restarting!\n", us,RESTART_THRESHOLD);
system("killall -SIGINT emulationstation ; sleep 1 ; reboot");
pressedTicks = 0;
break;
} else {
printf("%s: Power button was clicked. Kill current game!\n", us);
killProcesses();
usleep(300 * 1000);
}
pressedTicks = 0;
} else if (buttonState && !pressedTicks) {
printf("Power button just pressed!\n");
pressedTicks = getTicks();
usleep(10 * 1000);
} else if (buttonState && pressedTicks) {
if ((getTicks() - pressedTicks) > SHUTDOWN_THRESHOLD) {
printf("%s: Power button has been held at least %d ticks. Shutting down!\n", us, SHUTDOWN_THRESHOLD);
system("killall -SIGINT emulationstation ; sleep 1 ; shutdown -h now");
pressedTicks = 0;
break;
}
usleep(10 * 1000);
} else { //if (!buttonState && !pressedTicks) {
pressedTicks = 0;
usleep(100 * 1000); // nothing going on yet.
}
}
return 0;
}
// end of pacman-power-gpio.c ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment