added profile

This commit is contained in:
lucca
2026-09-19 21:23:27 -04:00
parent 96895c9755
commit 498aed50d2
10 changed files with 898 additions and 3 deletions
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
cc controller-off.c -o controller-off $(pkg-config --cflags --libs sdl3)
BIN
View File
Binary file not shown.
+704
View File
@@ -0,0 +1,704 @@
/*
* controller-off.c
*
* Global SDL3 controller disconnect utility.
*
* Button combination:
*
* Guide/Home + North
*
* SDL mapping:
*
* PlayStation -> Home + Triangle
* Xbox -> Guide + Y
* Nintendo -> Home + X
*
* Build:
*
* cc controller-off.c -o controller-off \
* $(pkg-config --cflags --libs sdl3)
*
* Run:
*
* ./controller-off
*/
#include <SDL3/SDL.h>
#include <ctype.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define MAX_CONTROLLERS 32
#define MAC_LEN 18
typedef struct {
SDL_JoystickID id;
SDL_Gamepad *gamepad;
int guide_pressed;
int north_pressed;
/*
* Prevent repeated triggers while both buttons
* remain held.
*/
int triggered;
} Controller;
static Controller controllers[MAX_CONTROLLERS];
static int controller_count = 0;
static volatile sig_atomic_t running = 1;
/*
* ------------------------------------------------------------
* Check for XX:XX:XX:XX:XX:XX
* ------------------------------------------------------------
*/
static int is_mac(const char *s)
{
if (!s || strlen(s) != 17)
return 0;
for (int i = 0; i < 17; i++) {
if (i == 2 ||
i == 5 ||
i == 8 ||
i == 11 ||
i == 14) {
if (s[i] != ':')
return 0;
} else {
if (!isxdigit(
(unsigned char)s[i]))
return 0;
}
}
return 1;
}
/*
* ------------------------------------------------------------
* Find a connected Bluetooth device whose name matches
* the SDL controller name.
*
* bluetoothctl output:
*
* Device AA:BB:CC:DD:EE:FF PS3 Controller
*
* We use:
*
* bluetoothctl devices Connected
* ------------------------------------------------------------
*/
static int find_bluetooth_device(
const char *controller_name,
char mac[MAC_LEN])
{
if (!controller_name)
return 0;
FILE *fp = popen(
"bluetoothctl devices Connected 2>/dev/null",
"r"
);
if (!fp)
return 0;
char line[1024];
while (fgets(
line,
sizeof(line),
fp
)) {
char candidate_mac[MAC_LEN];
char device_name[768];
memset(
candidate_mac,
0,
sizeof(candidate_mac)
);
memset(
device_name,
0,
sizeof(device_name)
);
/*
* bluetoothctl:
*
* Device MAC NAME
*/
if (sscanf(
line,
"Device %17s %[^\n]",
candidate_mac,
device_name
) != 2) {
continue;
}
if (!is_mac(candidate_mac))
continue;
/*
* SDL and bluetoothctl may use slightly
* different names.
*
* First try exact match.
*/
if (strcmp(
controller_name,
device_name
) == 0) {
strncpy(
mac,
candidate_mac,
MAC_LEN
);
mac[MAC_LEN - 1] = '\0';
pclose(fp);
return 1;
}
/*
* PS3-specific fallback.
*
* SDL:
*
* PS3 Controller
*
* BlueZ:
*
* PLAYSTATION(R)3 Controller
*/
if (
strstr(controller_name, "PS3") &&
strstr(device_name, "3 Controller")
) {
strncpy(
mac,
candidate_mac,
MAC_LEN
);
mac[MAC_LEN - 1] = '\0';
pclose(fp);
return 1;
}
}
pclose(fp);
return 0;
}
/*
* ------------------------------------------------------------
* Disconnect Bluetooth device.
* ------------------------------------------------------------
*/
static void disconnect_bluetooth(
const char *mac)
{
if (!is_mac(mac))
return;
printf(
"Disconnecting Bluetooth device %s\n",
mac
);
fflush(stdout);
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return;
}
if (pid == 0) {
/*
* Suppress bluetoothctl output.
*/
FILE *devnull =
fopen("/dev/null", "w");
if (devnull) {
dup2(
fileno(devnull),
STDOUT_FILENO
);
dup2(
fileno(devnull),
STDERR_FILENO
);
fclose(devnull);
}
execlp(
"bluetoothctl",
"bluetoothctl",
"disconnect",
mac,
(char *)NULL
);
_exit(127);
}
/*
* Reap child process.
*/
waitpid(
pid,
NULL,
0
);
}
/*
* ------------------------------------------------------------
* Handle the button combination.
* ------------------------------------------------------------
*/
static void disconnect_controller(
Controller *controller)
{
if (!controller ||
!controller->gamepad)
return;
const char *name =
SDL_GetGamepadName(
controller->gamepad
);
printf("\n");
printf(
"PS/Home + North detected\n"
);
printf(
"Controller: %s\n",
name ? name : "Unknown"
);
char mac[MAC_LEN];
if (!find_bluetooth_device(
name,
mac
)) {
fprintf(
stderr,
"Could not find a connected Bluetooth "
"device matching \"%s\".\n",
name ? name : "Unknown"
);
return;
}
printf(
"Bluetooth MAC: %s\n",
mac
);
disconnect_bluetooth(mac);
}
/*
* ------------------------------------------------------------
* Find SDL controller.
* ------------------------------------------------------------
*/
static Controller *find_controller(
SDL_JoystickID id)
{
for (int i = 0;
i < controller_count;
i++) {
if (controllers[i].id == id)
return &controllers[i];
}
return NULL;
}
/*
* ------------------------------------------------------------
* Add controller.
* ------------------------------------------------------------
*/
static void add_controller(
SDL_JoystickID id)
{
if (controller_count >=
MAX_CONTROLLERS) {
fprintf(
stderr,
"Controller limit reached.\n"
);
return;
}
SDL_Gamepad *gamepad =
SDL_OpenGamepad(id);
if (!gamepad) {
fprintf(
stderr,
"SDL_OpenGamepad failed: %s\n",
SDL_GetError()
);
return;
}
Controller *controller =
&controllers[
controller_count++
];
memset(
controller,
0,
sizeof(*controller)
);
controller->id = id;
controller->gamepad = gamepad;
const char *name =
SDL_GetGamepadName(gamepad);
printf(
"Controller connected: %s\n",
name ? name : "Unknown"
);
}
/*
* ------------------------------------------------------------
* Remove controller.
* ------------------------------------------------------------
*/
static void remove_controller(
SDL_JoystickID id)
{
for (int i = 0;
i < controller_count;
i++) {
if (controllers[i].id != id)
continue;
SDL_CloseGamepad(
controllers[i].gamepad
);
/*
* Fill the removed slot with the last
* controller.
*/
controllers[i] =
controllers[
controller_count - 1
];
controller_count--;
return;
}
}
/*
* ------------------------------------------------------------
* Signal handler.
* ------------------------------------------------------------
*/
static void handle_signal(
int signal)
{
(void)signal;
running = 0;
}
/*
* ------------------------------------------------------------
* Main
* ------------------------------------------------------------
*/
int main(void)
{
signal(
SIGINT,
handle_signal
);
signal(
SIGTERM,
handle_signal
);
if (!SDL_Init(
SDL_INIT_GAMEPAD)) {
fprintf(
stderr,
"SDL_Init failed: %s\n",
SDL_GetError()
);
return 1;
}
/*
* Receive controller events even though we have
* no window/focus.
*/
SDL_SetHint(
SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
"1"
);
printf(
"====================================\n"
" Controller Off\n"
"====================================\n"
"Hold Home/Guide + Triangle/North\n"
"to disconnect the controller.\n\n"
);
/*
* Open controllers that already exist.
*/
int count = 0;
SDL_JoystickID *ids =
SDL_GetGamepads(&count);
if (ids) {
for (int i = 0;
i < count;
i++) {
add_controller(ids[i]);
}
SDL_free(ids);
}
SDL_Event event;
while (running) {
while (SDL_PollEvent(&event)) {
/*
* Controller added.
*/
if (event.type ==
SDL_EVENT_GAMEPAD_ADDED) {
add_controller(
event.gdevice.which
);
}
/*
* Controller removed.
*/
else if (event.type ==
SDL_EVENT_GAMEPAD_REMOVED) {
remove_controller(
event.gdevice.which
);
}
/*
* Button press/release.
*/
else if (
event.type ==
SDL_EVENT_GAMEPAD_BUTTON_DOWN ||
event.type ==
SDL_EVENT_GAMEPAD_BUTTON_UP
) {
Controller *controller =
find_controller(
event.gbutton.which
);
if (!controller)
continue;
int pressed =
event.type ==
SDL_EVENT_GAMEPAD_BUTTON_DOWN;
/*
* Guide/Home/PS.
*/
if (event.gbutton.button ==
SDL_GAMEPAD_BUTTON_GUIDE) {
controller->guide_pressed =
pressed;
}
/*
* North:
*
* PS = Triangle
* Xbox = Y
* Nintendo = X
*/
if (event.gbutton.button ==
SDL_GAMEPAD_BUTTON_NORTH) {
controller->north_pressed =
pressed;
}
/*
* Trigger only once per press.
*/
if (controller->guide_pressed &&
controller->north_pressed &&
!controller->triggered) {
controller->triggered = 1;
disconnect_controller(
controller
);
}
/*
* Allow another trigger after
* either button is released.
*/
if (!controller->guide_pressed ||
!controller->north_pressed) {
controller->triggered = 0;
}
}
}
SDL_Delay(5);
}
/*
* Cleanup.
*/
for (int i = 0;
i < controller_count;
i++) {
if (controllers[i].gamepad)
SDL_CloseGamepad(
controllers[i].gamepad
);
}
SDL_Quit();
return 0;
}