#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/soundcard.h>
#include <sys/ioctl.h>
#include <pthread.h>
#include <math.h>
#include <paths.h>
#include <stdbool.h>
#define LOG_FILE "/tmp/xbell_events.log"
#define SAMPLE_RATE_DEF 48000 /* hz */
#define SAMPLE_RATE_MAX 48000 /* hz */
#define SAMPLE_RATE_MIN 8000 /* hz */
#define DURATION_DEF 150 /* ms */
#define DURATION_MAX 2000 /* ms */
#define DURATION_MIN 50 /* ms */
#define GAIN_DEF 75
#define GAIN_MAX 100
#define GAIN_MIN 0
#define WAVE_POWER 1.25f
#define DEFAULT_HZ 440
#define DEFAULT_DEVICE _PATH_DEV "dsp"
static int frequency = DEFAULT_HZ;
static int duration_ms = DURATION_DEF;
static int sample_rate = SAMPLE_RATE_DEF;
static int gain = GAIN_DEF;
static const char *oss_dev = DEFAULT_DEVICE;
static bool background;
int f, c;
float *buffer;
size_t size;
/*
* wave_function_16
*
* "phase" should be in the range [0.0f .. 1.0f>
* "power" should be in the range <0.0f .. 2.0f>
*
* The return value is in the range [-1.0f .. 1.0f]
*/
static float
wave_function_16(float phase, float power)
{
uint16_t x = phase * (1U << 16);
float retval;
uint8_t num;
/* Handle special cases, if any */
switch (x) {
case 0xffff:
case 0x0000:
return (1.0f);
case 0x3fff:
case 0x4000:
case 0xBfff:
case 0xC000:
return (0.0f);
case 0x7FFF:
case 0x8000:
return (-1.0f);
default:
break;
}
/* Apply Gray coding */
for (uint16_t mask = 1U << 15; mask != 1; mask /= 2) {
if (x & mask)
x ^= (mask - 1);
}
/* Find first set bit */
for (num = 0; num != 14; num++) {
if (x & (1U << num)) {
num++;
break;
}
}
/* Initialize return value */
retval = 0.0;
/* Compute the rest of the power series */
for (; num != 14; num++) {
if (x & (1U << num)) {
retval = (1.0f - retval) / 2.0f;
retval = powf(retval, power);
} else {
retval = (1.0f + retval) / 2.0f;
retval = powf(retval, power);
}
}
/* Check if halfway */
if (x & (1ULL << 14))
retval = -retval;
return (retval);
}
static void
usage(void)
{
fprintf(stderr, "Usage: %s [parameters]\n"
"\t" "-F <frequency in HZ, default %d Hz>\n"
"\t" "-D <duration in ms, from %d ms to %d ms, default %d ms>\n"
"\t" "-r <sample rate in HZ, from %d Hz to %d Hz, default %d Hz>\n"
"\t" "-d <OSS device (default %s)>\n"
"\t" "-g <gain from %d to %d, default %d>\n"
"\t" "-B Run in background\n"
"\t" "-h Show usage\n",
getprogname(),
DEFAULT_HZ,
DURATION_MIN, DURATION_MAX, DURATION_DEF,
SAMPLE_RATE_MIN, SAMPLE_RATE_MAX, SAMPLE_RATE_DEF,
DEFAULT_DEVICE,
GAIN_MIN, GAIN_MAX, GAIN_DEF);
exit(1);
}
/* Function to log bell events to file */
void log_bell_event(const char *message) {
FILE *fp = fopen(LOG_FILE, "a");
if (fp == NULL) {
perror("Error opening log file");
return;
}
time_t now = time(NULL);
char *timestamp = ctime(&now);
timestamp[strlen(timestamp) - 1] = '\0';
fprintf(fp, "[%s] %s\n", timestamp, message);
fclose(fp);
}
/* Play WAV file using OSS */
void *play_wav_thread(void *arg)
{
if (write(f, buffer, size * sizeof(buffer[0])) !=
(ssize_t)(size * sizeof(buffer[0])))
errx(1, "failed writing to DSP device(%s)", oss_dev);
/* wait for data to be written */
while (ioctl(f, SNDCTL_DSP_GETODELAY, &c) == 0) {
if (c == 0)
break;
usleep(10000);
}
/* wait for audio to go out */
usleep(50000);
return NULL;
}
/* Play WAV file in a separate thread */
void play_wav_file() {
pthread_t thread;
if (pthread_create(&thread, NULL, play_wav_thread, NULL) != 0) {
log_bell_event("Error: Cannot create playback thread");
return;
}
pthread_detach(thread);
}
/* XKB Bell event handler */
void handle_xkb_event(Display *dpy, XEvent *event) {
XkbEvent *xkb_event = (XkbEvent *)event;
if (xkb_event->any.xkb_type == XkbBellNotify) {
XkbBellNotifyEvent *bell = (XkbBellNotifyEvent *)xkb_event;
char msg[256];
snprintf(msg, sizeof(msg),
"Bell event detected - Window: 0x%lx, Percent: %d, Pitch: %d, Duration: %d - Playing WAV",
bell->window, bell->percent, bell->pitch, bell->duration);
log_bell_event(msg);
printf("%s\n", msg);
/* Play the WAV file */
play_wav_file();
}
}
int main(int argc, char *argv[]) {
Display *dpy;
int xkb_event_base, xkb_error_base;
int major = XkbMajorVersion;
int minor = XkbMinorVersion;
XEvent event;
size_t slope;
size_t off;
float a;
float d;
float p;
/* Open connection to X server */
dpy = XOpenDisplay(NULL);
if (dpy == NULL) {
fprintf(stderr, "Cannot open display\n");
exit(1);
}
while ((c = getopt(argc, argv, "BF:D:r:g:d:h")) != -1) {
switch (c) {
case 'F':
frequency = strtol(optarg, NULL, 10);
break;
case 'D':
duration_ms = strtol(optarg, NULL, 10);
if (duration_ms < DURATION_MIN ||
duration_ms > DURATION_MAX)
usage();
break;
case 'r':
sample_rate = strtol(optarg, NULL, 10);
if (sample_rate < SAMPLE_RATE_MIN ||
sample_rate > SAMPLE_RATE_MAX)
usage();
break;
case 'g':
gain = strtol(optarg, NULL, 10);
if (gain < GAIN_MIN ||
gain > GAIN_MAX)
usage();
break;
case 'd':
oss_dev = optarg;
break;
case 'B':
background = true;
break;
default:
usage();
break;
}
}
if (background && daemon(0, 0) != 0)
errx(1, "daemon(0,0) failed");
f = open(oss_dev, O_WRONLY);
if (f < 0)
err(1, "Failed to open '%s'", oss_dev);
c = 1; /* mono */
if (ioctl(f, SOUND_PCM_WRITE_CHANNELS, &c) != 0)
errx(1, "ioctl SOUND_PCM_WRITE_CHANNELS(1) failed");
c = AFMT_FLOAT;
if (ioctl(f, SNDCTL_DSP_SETFMT, &c) != 0)
errx(1, "ioctl SNDCTL_DSP_SETFMT(AFMT_FLOAT) failed");
if (ioctl(f, SNDCTL_DSP_SPEED, &sample_rate) != 0)
errx(1, "ioctl SNDCTL_DSP_SPEED(%d) failed", sample_rate);
c = (2 << 16);
while ((1ULL << (c & 63)) < (size_t)(4 * sample_rate / 50))
c++;
if (ioctl(f, SNDCTL_DSP_SETFRAGMENT, &c))
errx(1, "ioctl SNDCTL_DSP_SETFRAGMENT(0x%x) failed", c);
if (ioctl(f, SNDCTL_DSP_GETODELAY, &c) != 0)
errx(1, "ioctl SNDCTL_DSP_GETODELAY failed");
size = ((sample_rate * duration_ms) + 999) / 1000;
buffer = malloc(sizeof(buffer[0]) * size);
if (buffer == NULL)
errx(1, "out of memory");
/* compute slope duration in samples */
slope = (DURATION_MIN * sample_rate) / 2000;
/* compute base gain */
a = powf(65536.0f, (float)gain / (float)GAIN_MAX) / 65536.0f;
/* set initial phase and delta */
p = 0;
d = (float)frequency / (float)sample_rate;
/* compute wave */
for (p = off = 0; off != size; off++, p += d) {
float sample;
p = p - floorf(p);
sample = a * wave_function_16(p, WAVE_POWER);
if (off < slope)
sample = sample * off / (float)slope;
else if (off > (size - slope))
sample = sample * (size - off - 1) / (float)slope;
buffer[off] = sample;
}
printf("Connected to display: %s\n", DisplayString(dpy));
printf("Logging bell events to: %s\n", LOG_FILE);
/* Initialize XKB extension */
if (!XkbQueryExtension(dpy, NULL, &xkb_event_base, &xkb_error_base,
&major, &minor)) {
fprintf(stderr, "XKB extension not available\n");
XCloseDisplay(dpy);
exit(1);
}
printf("XKB extension initialized (version %d.%d)\n", major, minor);
/* Select for XKB bell events */
if (!XkbSelectEvents(dpy, XkbUseCoreKbd, XkbBellNotifyMask,
XkbBellNotifyMask)) {
fprintf(stderr, "Failed to select XKB bell events\n");
XCloseDisplay(dpy);
exit(1);
}
/* Disable the actual bell sound */
XkbChangeEnabledControls(dpy, XkbUseCoreKbd, XkbAudibleBellMask, 0);
log_bell_event("XBell monitor started with WAV playback");
printf("\nMonitoring XBell events... Press Ctrl+C to exit\n");
printf("Test with: xkbbell -display %s\n\n", DisplayString(dpy));
/* Main event loop */
while (1) {
XNextEvent(dpy, &event);
if (event.type == xkb_event_base) {
handle_xkb_event(dpy, &event);
}
}
/* Cleanup */
XCloseDisplay(dpy);
return 0;
}