added profile
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user