diff options
author | Javier <dev.git@javispedro.com> | 2014-08-15 20:49:23 +0200 |
---|---|---|
committer | Javier <dev.git@javispedro.com> | 2014-08-15 20:49:23 +0200 |
commit | 9694ec75ab1464b3699ef367f3800e60bef0cc7a (patch) | |
tree | 645c4d0fc9b8c71162cdf9f4ac572904b0bc4079 | |
download | coveraudiod-9694ec75ab1464b3699ef367f3800e60bef0cc7a.tar.gz coveraudiod-9694ec75ab1464b3699ef367f3800e60bef0cc7a.zip |
initial import
-rw-r--r-- | Makefile | 25 | ||||
-rw-r--r-- | audio.c | 225 | ||||
-rw-r--r-- | audio.h | 21 | ||||
-rw-r--r-- | coveraudiod.config | 1 | ||||
-rw-r--r-- | coveraudiod.creator | 1 | ||||
-rw-r--r-- | coveraudiod.files | 8 | ||||
-rw-r--r-- | coveraudiod.includes | 2 | ||||
-rw-r--r-- | key.c | 19 | ||||
-rw-r--r-- | key.h | 13 | ||||
-rw-r--r-- | key1.wav | bin | 0 -> 6000 bytes | |||
-rw-r--r-- | key2.wav | bin | 0 -> 5908 bytes | |||
-rw-r--r-- | key3.wav | bin | 0 -> 5868 bytes | |||
-rw-r--r-- | key4.wav | bin | 0 -> 5828 bytes | |||
-rw-r--r-- | key5.wav | bin | 0 -> 5704 bytes | |||
-rw-r--r-- | key6.wav | bin | 0 -> 5584 bytes | |||
-rw-r--r-- | main.c | 29 | ||||
-rw-r--r-- | mod1.wav | bin | 0 -> 4676 bytes | |||
-rw-r--r-- | mod2.wav | bin | 0 -> 4676 bytes | |||
-rw-r--r-- | x11.c | 257 | ||||
-rw-r--r-- | x11.h | 9 |
20 files changed, 610 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f186a15 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +CFLAGS:=-Wall -O0 -ggdb + +pkgmodules:=glib-2.0 libpulse-mainloop-glib sndfile x11 xi +includes:=$(shell pkg-config --cflags $(pkgmodules)) +libs:=$(shell pkg-config --libs $(pkgmodules)) +objs:=main.o x11.o audio.o key.o + +all: coveraudiod + +coveraudiod: $(objs) + $(CC) -o $@ $+ $(libs) + +$(objs): %.o: %.c + $(CC) -std=c99 $(includes) $(CFLAGS) -o $@ -c $< + +clean: + rm -f *.o coveraudiod + +install: coveraudiod + install coveraudiod $(DESTDIR)/usr/bin + install -d $(DESTDIR)/usr/share/coveraudiod + install *.wav $(DESTDIR)/usr/share/coveraudiod/ + +.PHONY: all clean install + @@ -0,0 +1,225 @@ +#include <stdio.h> + +#include <pulse/glib-mainloop.h> +#include <pulse/pulseaudio.h> +#include <sndfile.h> + +#include "audio.h" + +static pa_context *context; + +#define PA_SAMPLE_NAME_PREFIX "coveraudiod-" +#define SAMPLE_FILES_PATH "/usr/share/coveraudiod/" + +struct sample_info { + const char *name; +} samples[NUM_SAMPLES] = { + {"key1"}, + {"key2"}, + {"key3"}, + {"key4"}, + {"key5"}, + {"key6"}, + {"mod1"}, + {"mod2"} +}; + +struct sample_loading_status { + const struct sample_info *sample; + SNDFILE *sndfile; + pa_stream *stream; + size_t nbytes; +}; + +static const char *build_file_name(const struct sample_info *sample) +{ + static char buffer[sizeof(SAMPLE_FILES_PATH) * 2]; + + snprintf(buffer, sizeof(buffer), "%s%s.%s", SAMPLE_FILES_PATH, sample->name, "wav"); + + return buffer; +} + +static const char *build_sample_name(const struct sample_info *sample) +{ + static char buffer[sizeof(PA_SAMPLE_NAME_PREFIX) * 2]; + + snprintf(buffer, sizeof(buffer), "%s%s", PA_SAMPLE_NAME_PREFIX, sample->name); + + return buffer; +} + +static void handle_stream_request(pa_stream *s, size_t nbytes, void *userdata) +{ + struct sample_loading_status *ldstatus = (struct sample_loading_status*) userdata; + + void *buffer; + + int err = pa_stream_begin_write(s, &buffer, &nbytes); + g_return_if_fail(err == 0); + + sf_count_t read = sf_read_raw(ldstatus->sndfile, buffer, nbytes); + + err = pa_stream_write(s, buffer, read, NULL, 0, PA_SEEK_RELATIVE); + if (err) { + g_warning("Failed to upload data to PulseAudio"); + pa_stream_disconnect(s); + } + + ldstatus->nbytes -= read; + + if (read < nbytes || ldstatus->nbytes <= 0) { + // EOF + pa_stream_finish_upload(s); + } +} + +static void handle_stream_notify(pa_stream *p, void *userdata) +{ + struct sample_loading_status *ldstatus = (struct sample_loading_status*) userdata; + + pa_stream_state_t status = pa_stream_get_state(p); + switch (status) { + case PA_STREAM_READY: + // Nothing to do, write request will be shortly called by PA. + break; + case PA_STREAM_FAILED: + g_warning("PulseAudio stream upload failed, closing"); + // Fallthrough + case PA_STREAM_TERMINATED: + pa_stream_unref(ldstatus->stream); + sf_close(ldstatus->sndfile); + free(ldstatus); + break; + default: + break; + } +} + +static bool load_sample(const struct sample_info *sample) +{ + struct sample_loading_status *ldstatus = calloc(1, sizeof(struct sample_loading_status)); + ldstatus->sample = sample; + + const char *file_name = build_file_name(sample); + SF_INFO info; + + ldstatus->sndfile = sf_open(file_name, SFM_READ, &info); + if (!ldstatus->sndfile) { + g_warning("Could not open sample: %s", file_name); + return false; + } + + pa_sample_spec spec; + spec.rate = info.samplerate; + spec.channels = info.channels; + + switch (info.format & SF_FORMAT_SUBMASK) { + case SF_FORMAT_PCM_16: + spec.format = PA_SAMPLE_S16NE; + break; + case SF_FORMAT_PCM_24: + spec.format = PA_SAMPLE_S24NE; + break; + case SF_FORMAT_PCM_32: + spec.format = PA_SAMPLE_S32NE; + break; + case SF_FORMAT_ULAW: + spec.format = PA_SAMPLE_ULAW; + break; + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + spec.format = PA_SAMPLE_FLOAT32NE; + break; + default: + g_warning("Unknown sample format: %s", file_name); + sf_close(ldstatus->sndfile); + free(ldstatus); + return false; + } + + g_return_val_if_fail(pa_sample_spec_valid(&spec), false); + + ldstatus->nbytes = pa_frame_size(&spec) * info.frames; + + pa_proplist *prop = pa_proplist_new(); + pa_proplist_sets(prop, PA_PROP_MEDIA_ROLE, "event"); + pa_proplist_sets(prop, PA_PROP_MEDIA_FILENAME, file_name); + + ldstatus->stream = pa_stream_new_with_proplist(context, build_sample_name(sample), + &spec, NULL, prop); + + pa_stream_set_state_callback(ldstatus->stream, handle_stream_notify, ldstatus); + pa_stream_set_write_callback(ldstatus->stream, handle_stream_request, ldstatus); + + int err = pa_stream_connect_upload(ldstatus->stream, ldstatus->nbytes); + if (err) { + g_warning("pa_stream_connect_upload failed"); + pa_stream_unref(ldstatus->stream); + sf_close(ldstatus->sndfile); + free(ldstatus); + return false; + } + + return true; +} + +static void load_samples() +{ + for (int i = 0; i < NUM_SAMPLES; i++) { + if (!load_sample(&samples[i])) { + return; + } + } +} + +static void handle_context_notify(pa_context *c, void *userdata) +{ + pa_context_state_t state = pa_context_get_state(c); + switch (state) { + case PA_CONTEXT_READY: + load_samples(); + break; + case PA_CONTEXT_FAILED: + g_warning("Cannot connect to PulseAudio"); + break; + default: + break; + } +} + +bool audio_init() +{ + pa_glib_mainloop *paloop = pa_glib_mainloop_new(NULL); + context = pa_context_new(pa_glib_mainloop_get_api(paloop), "coveraudiod"); + + if (!context) { + g_warning("Cannot create PulseAudio context"); + return false; + } + + pa_context_set_state_callback(context, handle_context_notify, NULL); + + int err = pa_context_connect(context, NULL, 0, NULL); + if (err) { + g_warning("Cannot connect to PulseAudio"); + return false; + } + + return true; +} + +void audio_close() +{ + pa_context_unref(context); +} + +void audio_play_sample(int num_sample) +{ + const char *sample_name = build_sample_name(&samples[num_sample]); + + pa_operation *op = pa_context_play_sample(context, sample_name, NULL, + PA_VOLUME_NORM, NULL, NULL); + + pa_operation_unref(op); +} @@ -0,0 +1,21 @@ +#ifndef AUDIO_H +#define AUDIO_H + +#include <stdbool.h> + +enum samples { + FIRST_KEY_SAMPLE = 0, + LAST_KEY_SAMPLE = 5, + NUM_KEY_SAMPLES = 6, + FIRST_MOD_SAMPLE = 6, + LAST_MOD_SAMPLE = 7, + NUM_MOD_SAMPLES = 2, + NUM_SAMPLES = 8 +}; + +bool audio_init(); +void audio_close(); + +void audio_play_sample(int num_sample); + +#endif // AUDIO_H diff --git a/coveraudiod.config b/coveraudiod.config new file mode 100644 index 0000000..8cec188 --- /dev/null +++ b/coveraudiod.config @@ -0,0 +1 @@ +// ADD PREDEFINED MACROS HERE! diff --git a/coveraudiod.creator b/coveraudiod.creator new file mode 100644 index 0000000..e94cbbd --- /dev/null +++ b/coveraudiod.creator @@ -0,0 +1 @@ +[General] diff --git a/coveraudiod.files b/coveraudiod.files new file mode 100644 index 0000000..76f544c --- /dev/null +++ b/coveraudiod.files @@ -0,0 +1,8 @@ +Makefile +main.c +x11.h +x11.c +audio.h +audio.c +key.h +key.c diff --git a/coveraudiod.includes b/coveraudiod.includes new file mode 100644 index 0000000..eb319c9 --- /dev/null +++ b/coveraudiod.includes @@ -0,0 +1,2 @@ +/usr/include/glib-2.0 +/usr/lib64/glib-2.0/include @@ -0,0 +1,19 @@ +#include "audio.h" +#include "key.h" + +static unsigned int key_press_count = 0; + +void handle_keypress(bool dead_key) +{ + int num_sample = 0; + + if (dead_key) { + num_sample = (key_press_count % NUM_MOD_SAMPLES) + FIRST_MOD_SAMPLE; + } else { + num_sample = (key_press_count % NUM_KEY_SAMPLES) + FIRST_KEY_SAMPLE; + } + + audio_play_sample(num_sample); + + key_press_count++; +} @@ -0,0 +1,13 @@ +#ifndef KEY_H +#define KEY_H + +#include <stdbool.h> + +#define MICROSOFT_VENDOR_ID 0x045e + +#define MICROSOFT_TOUCH_COVER_1_PRODUCT_ID 0x079a +#define MICROSOFT_TOUCH_COVER_2_PRODUCT_ID 0x07a7 + +void handle_keypress(bool dead_key); + +#endif // KEY_H diff --git a/key1.wav b/key1.wav Binary files differnew file mode 100644 index 0000000..873f216 --- /dev/null +++ b/key1.wav diff --git a/key2.wav b/key2.wav Binary files differnew file mode 100644 index 0000000..15da16f --- /dev/null +++ b/key2.wav diff --git a/key3.wav b/key3.wav Binary files differnew file mode 100644 index 0000000..15f64bf --- /dev/null +++ b/key3.wav diff --git a/key4.wav b/key4.wav Binary files differnew file mode 100644 index 0000000..a464895 --- /dev/null +++ b/key4.wav diff --git a/key5.wav b/key5.wav Binary files differnew file mode 100644 index 0000000..387bef1 --- /dev/null +++ b/key5.wav diff --git a/key6.wav b/key6.wav Binary files differnew file mode 100644 index 0000000..2ccbde2 --- /dev/null +++ b/key6.wav @@ -0,0 +1,29 @@ +#include <stdio.h> +#include <stdlib.h> + +#include <glib.h> + +#include "x11.h" +#include "audio.h" + +int main(int argc, char *argv[]) +{ + GMainLoop *loop = g_main_loop_new(NULL, TRUE); + + if (!x11_init()) { + return EXIT_FAILURE; + } + + if (!audio_init()) { + return EXIT_FAILURE; + } + + g_debug("coveraudiod running"); + + g_main_loop_run(loop); + + audio_close(); + x11_close(); + + return EXIT_SUCCESS; +} diff --git a/mod1.wav b/mod1.wav Binary files differnew file mode 100644 index 0000000..c535f8a --- /dev/null +++ b/mod1.wav diff --git a/mod2.wav b/mod2.wav Binary files differnew file mode 100644 index 0000000..c535f8a --- /dev/null +++ b/mod2.wav @@ -0,0 +1,257 @@ +#include <ctype.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#include <X11/extensions/XInput.h> +#include <X11/extensions/XInput2.h> +#include <glib-unix.h> + +#include "x11.h" +#include "key.h" + +#define ONLY_SURFACE_COVER 1 + +static Display *display; + +static int xi_opcode; +static int xi_event; +static int xi_error; + +static Atom product_id; +static Atom button_horiz_wheel_left; + +static bool is_keyboard(const XIDeviceInfo *info) +{ + if (!info->enabled) + return false; + + // Surface keyboard appears as a SlavePointer sometimes + if (info->use != XISlaveKeyboard && info->use != XISlavePointer) + return false; + + for (int i = 0; i < info->num_classes; i++) { + if (info->classes[i]->type != KeyClass) + continue; + + const XIKeyClassInfo *key_info = (XIKeyClassInfo *)info->classes[i]; + if (key_info->num_keycodes > 40) { + // If it has more than 40 keys, it is a keyboard + return true; + } + } + + return false; +} + +static bool is_surface_touch_cover(const XIDeviceInfo *info) +{ + Atom act_type; + int act_format; + unsigned long nitems, bytes_after; + unsigned char *data; + Status status; + + // First test: product_id + status = XIGetProperty(display, info->deviceid, product_id, 0, 2, False, XA_INTEGER, + &act_type, &act_format, &nitems, &bytes_after, &data); + + if (status != Success) { + return false; + } + + if (act_type != XA_INTEGER || act_format != 32) { + XFree(data); + return false; + } + + int32_t *int_data = (int32_t*)data; + int vendor_id = int_data[0]; + int product_id = int_data[1]; + XFree(data); + + if (vendor_id == MICROSOFT_VENDOR_ID) { + if (product_id != MICROSOFT_TOUCH_COVER_1_PRODUCT_ID && + product_id != MICROSOFT_TOUCH_COVER_2_PRODUCT_ID) { + return false; + } + // TODO: Touch cover 2 + } else { + return false; + } + + // Second test: check if it has a horizontal wheel (all Surface covers 'have' one) + // This way we ignore the device with the volume buttons. + for (int i = 0; i < info->num_classes; i++) { + if (info->classes[i]->type != ButtonClass) + continue; + + const XIButtonClassInfo *btn_info = (XIButtonClassInfo *)info->classes[i]; + for (int j = 0; j < btn_info->num_buttons; j++) { + if (btn_info->labels[j] == button_horiz_wheel_left) { + return true; + } + } + } + + return false; +} + + +static bool find_and_select_devices() +{ + int ndevices; + XIDeviceInfo *info = XIQueryDevice(display, XIAllDevices, &ndevices); + + if (!info) return false; + + for (int i = 0; i < ndevices; i++) { + // Looking for keyboards only currently + if (!is_keyboard(&info[i])) continue; +#if ONLY_SURFACE_COVER + if (!is_surface_touch_cover(&info[i])) continue; +#endif + + g_debug("Capturing events from '%s'", info[i].name); + + // Subscribe to this device's events + XIEventMask mask; + mask.deviceid = info[i].deviceid; + mask.mask_len = XIMaskLen(XI_LASTEVENT); + mask.mask = calloc(mask.mask_len, 1); + XISetMask(mask.mask, XI_KeyPress); + XISelectEvents(display, DefaultRootWindow(display), &mask, 1); + free(mask.mask); + } + + XIFreeDeviceInfo(info); + return true; +} + +static void handle_key_press(const XIDeviceEvent *event) +{ + // Convert into an XKeyEvent + XKeyEvent kevent; + kevent.serial = event->serial; + kevent.send_event = event->send_event; + kevent.display = event->display; + kevent.same_screen = True; + kevent.window = event->event; + kevent.root = event->root; + kevent.subwindow = event->child; + kevent.time = event->time; + kevent.x = event->event_x; + kevent.y = event->event_y; + kevent.x_root = event->root_x; + kevent.y_root = event->root_y; + kevent.state = event->mods.effective; + kevent.keycode = event->detail; + + char buf[4]; + KeySym sym; + + int len = XLookupString(&kevent, buf, sizeof(buf), &sym, NULL); + + bool is_dead_key; + switch (len) { + case 0: + is_dead_key = true; + break; + case 1: + is_dead_key = iscntrl(buf[0]) || isblank(buf[0]); + break; + default: + is_dead_key = false; + break; + } + + handle_keypress(is_dead_key); +} + +static void handle_xi_event(const XGenericEventCookie *event) +{ + switch (event->evtype) { + case XI_HierarchyChanged: + g_debug("Hierarchy changed event"); + find_and_select_devices(); + break; + case XI_KeyPress: + handle_key_press(event->data); + break; + case XI_RawKeyPress: + g_debug("Raw key press"); + break; + } +} + +static gboolean handle_x11_fd(gint fd, GIOCondition condition, gpointer data) +{ + while (XPending(display)) { + XEvent event; + XNextEvent(display, &event); + + switch (event.type) { + case GenericEvent: + if (XGetEventData(display, &event.xcookie)) { + if (event.xcookie.extension == xi_opcode) { + handle_xi_event(&event.xcookie); + } + XFreeEventData(display, &event.xcookie); + } + break; + } + + // Ignoring events + } + + return G_SOURCE_CONTINUE; +} + +bool x11_init() +{ + display = XOpenDisplay(NULL); + if (!display) { + g_warning("Cannot connect to X display"); + return false; + } + + if (!XQueryExtension(display, INAME, &xi_opcode, &xi_event, &xi_error)) { + g_warning("X Input extension not available"); + return false; + } + + int major = 2; + int minor = 0; + if (XIQueryVersion(display, &major, &minor) != Success) { + g_warning("X Input 2.0 not available"); + return false; + } + + product_id = XInternAtom(display, "Device Product ID", False); + button_horiz_wheel_left = XInternAtom(display, "Button Horiz Wheel Left", False); + + // Setup a mask for hierarchy changed events + XIEventMask mask; + mask.deviceid = XIAllDevices; + mask.mask_len = XIMaskLen(XI_LASTEVENT); + mask.mask = calloc(mask.mask_len, 1); + XISetMask(mask.mask, XI_HierarchyChanged); + XISelectEvents(display, DefaultRootWindow(display), &mask, 1); + free(mask.mask); + + if (!find_and_select_devices()) { + return false; + } + + // Prepare integration with glib event loop + g_unix_fd_add(ConnectionNumber(display), G_IO_IN, + handle_x11_fd, NULL); + XFlush(display); + + return true; +} + +void x11_close() +{ + XCloseDisplay(display); +} @@ -0,0 +1,9 @@ +#ifndef X11_H +#define X11_H + +#include <stdbool.h> + +bool x11_init(); +void x11_close(); + +#endif // X11_H |