aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJavier <dev.git@javispedro.com>2014-08-15 20:49:23 +0200
committerJavier <dev.git@javispedro.com>2014-08-15 20:49:23 +0200
commit9694ec75ab1464b3699ef367f3800e60bef0cc7a (patch)
tree645c4d0fc9b8c71162cdf9f4ac572904b0bc4079
downloadcoveraudiod-9694ec75ab1464b3699ef367f3800e60bef0cc7a.tar.gz
coveraudiod-9694ec75ab1464b3699ef367f3800e60bef0cc7a.zip
initial import
-rw-r--r--Makefile25
-rw-r--r--audio.c225
-rw-r--r--audio.h21
-rw-r--r--coveraudiod.config1
-rw-r--r--coveraudiod.creator1
-rw-r--r--coveraudiod.files8
-rw-r--r--coveraudiod.includes2
-rw-r--r--key.c19
-rw-r--r--key.h13
-rw-r--r--key1.wavbin0 -> 6000 bytes
-rw-r--r--key2.wavbin0 -> 5908 bytes
-rw-r--r--key3.wavbin0 -> 5868 bytes
-rw-r--r--key4.wavbin0 -> 5828 bytes
-rw-r--r--key5.wavbin0 -> 5704 bytes
-rw-r--r--key6.wavbin0 -> 5584 bytes
-rw-r--r--main.c29
-rw-r--r--mod1.wavbin0 -> 4676 bytes
-rw-r--r--mod2.wavbin0 -> 4676 bytes
-rw-r--r--x11.c257
-rw-r--r--x11.h9
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
+
diff --git a/audio.c b/audio.c
new file mode 100644
index 0000000..6d38783
--- /dev/null
+++ b/audio.c
@@ -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);
+}
diff --git a/audio.h b/audio.h
new file mode 100644
index 0000000..c180ed0
--- /dev/null
+++ b/audio.h
@@ -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
diff --git a/key.c b/key.c
new file mode 100644
index 0000000..6fe76a2
--- /dev/null
+++ b/key.c
@@ -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++;
+}
diff --git a/key.h b/key.h
new file mode 100644
index 0000000..c70dfd1
--- /dev/null
+++ b/key.h
@@ -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
new file mode 100644
index 0000000..873f216
--- /dev/null
+++ b/key1.wav
Binary files differ
diff --git a/key2.wav b/key2.wav
new file mode 100644
index 0000000..15da16f
--- /dev/null
+++ b/key2.wav
Binary files differ
diff --git a/key3.wav b/key3.wav
new file mode 100644
index 0000000..15f64bf
--- /dev/null
+++ b/key3.wav
Binary files differ
diff --git a/key4.wav b/key4.wav
new file mode 100644
index 0000000..a464895
--- /dev/null
+++ b/key4.wav
Binary files differ
diff --git a/key5.wav b/key5.wav
new file mode 100644
index 0000000..387bef1
--- /dev/null
+++ b/key5.wav
Binary files differ
diff --git a/key6.wav b/key6.wav
new file mode 100644
index 0000000..2ccbde2
--- /dev/null
+++ b/key6.wav
Binary files differ
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..bfec675
--- /dev/null
+++ b/main.c
@@ -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
new file mode 100644
index 0000000..c535f8a
--- /dev/null
+++ b/mod1.wav
Binary files differ
diff --git a/mod2.wav b/mod2.wav
new file mode 100644
index 0000000..c535f8a
--- /dev/null
+++ b/mod2.wav
Binary files differ
diff --git a/x11.c b/x11.c
new file mode 100644
index 0000000..3b8bcdc
--- /dev/null
+++ b/x11.c
@@ -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);
+}
diff --git a/x11.h b/x11.h
new file mode 100644
index 0000000..b5054f6
--- /dev/null
+++ b/x11.h
@@ -0,0 +1,9 @@
+#ifndef X11_H
+#define X11_H
+
+#include <stdbool.h>
+
+bool x11_init();
+void x11_close();
+
+#endif // X11_H