aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCalvin Morrison <calvin@fastmailteam.com>2022-07-27 20:58:28 -0400
committerCalvin Morrison <calvin@fastmailteam.com>2022-07-27 20:58:28 -0400
commitbcd8da8b1a3d02a90b3c9c8ca73d8b26760bb219 (patch)
treea89057557b9076306864428ff542c672b5ce7eb4
parentd210af2268c29420983cdd6937affc7a2d3f5b47 (diff)
libmoc
our previous attempt ran into issues where we have two loops, a GUI loop and the MOC loop. I rewrote this into lib_moc which has it's own thread that manages the MOC socket connection and exposes the interface to a client program via a moc object. all you need to do is call moc_init(); Pretty cool, still runs into issues regarding autoconnect and some other problems.
-rw-r--r--moc_library.c541
-rw-r--r--moc_library.h47
-rw-r--r--moc_x11.c142
3 files changed, 730 insertions, 0 deletions
diff --git a/moc_library.c b/moc_library.c
new file mode 100644
index 0000000..1e382aa
--- /dev/null
+++ b/moc_library.c
@@ -0,0 +1,541 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h> //Header file for sleep(). man 3 sleep for details.
+#include <errno.h>
+
+#include <pthread.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+// A normal C function that is executed as a thread
+// when its name is specified in pthread_create()
+//
+
+char *moc_socket_path = "/home/calvin/.moc/socket2";
+
+/* State of the server. */
+#define STATE_PLAY 0x01
+#define STATE_STOP 0x02
+#define STATE_PAUSE 0x03
+#define CMD_GET_STATE 0x13 /* get the state */
+#define CMD_GET_CTIME 0x0d /* get the current song time */
+#define CMD_GET_TAGS 0x2c /* get tags for the currently played file */
+#define CMD_GET_FILE_TAGS 0x2f /* get tags for the specified file */
+#define CMD_GET_SNAME 0x0f /* get the stream file name */
+#define MAX_SEND_STRING 4096
+#define RANGE(min, val, max) ((val) >= (min) && (val) <= (max))
+
+
+/* Definition of events sent by server to the client. */
+#define EV_STATE 0x01 /* server has changed the state */
+#define EV_CTIME 0x02 /* current time of the song has changed */
+#define EV_SRV_ERROR 0x04 /* an error occurred */
+#define EV_BUSY 0x05 /* another client is connected to the server */
+#define EV_DATA 0x06 /* data in response to a request will arrive */
+#define EV_BITRATE 0x07 /* the bitrate has changed */
+#define EV_RATE 0x08 /* the rate has changed */
+#define EV_CHANNELS 0x09 /* the number of channels has changed */
+#define EV_EXIT 0x0a /* the server is about to exit */
+#define EV_PONG 0x0b /* response for CMD_PING */
+#define EV_OPTIONS 0x0c /* the options has changed */
+#define EV_SEND_PLIST 0x0d /* request for sending the playlist */
+#define EV_TAGS 0x0e /* tags for the current file have changed */
+#define EV_STATUS_MSG 0x0f /* followed by a status message */
+#define EV_MIXER_CHANGE 0x10 /* the mixer channel was changed */
+#define EV_FILE_TAGS 0x11 /* tags in a response for tags request */
+#define EV_AVG_BITRATE 0x12 /* average bitrate has changed (new song) */
+#define EV_AUDIO_START 0x13 /* playing of audio has started */
+#define EV_AUDIO_STOP 0x14 /* playing of audio has stopped */
+
+/* Events caused by a client that wants to modify the playlist (see
+ * CMD_CLI_PLIST* commands). */
+#define EV_PLIST_ADD 0x50 /* add an item, followed by the file name */
+#define EV_PLIST_DEL 0x51 /* delete an item, followed by the file name */
+#define EV_PLIST_MOVE 0x52 /* move an item, followed by 2 file names */
+#define EV_PLIST_CLEAR 0x53 /* clear the playlist */
+
+/* These events, though similar to the four previous are caused by server
+ * which takes care of clients' queue synchronization. */
+#define EV_QUEUE_ADD 0x54
+#define EV_QUEUE_DEL 0x55
+#define EV_QUEUE_MOVE 0x56
+#define EV_QUEUE_CLEAR 0x57
+/* Flags for the info decoder function. */
+enum tags_select
+{
+ TAGS_COMMENTS = 0x01, /* artist, title, etc. */
+ TAGS_TIME = 0x02 /* time of the file. */
+};
+
+enum moc_state {
+ STOPPED,
+ PLAYING,
+ PAUSED
+};
+
+enum moc_status {
+ INITIALIZING,
+ CONNECTED,
+ FAILED_TO_CONNECT,
+ ERROR
+};
+
+struct moc {
+ pthread_mutex_t lock;
+ enum moc_status status;
+ enum moc_state state;
+
+ char *filename;
+ char *title;
+ char *album;
+ char *artist;
+ int track;
+ int time;
+ int filled;
+};
+
+extern const char * moc_str_state(enum moc_state s) {
+ static const char *strings[] = { "STOPPED", "PLAYING", "PAUSED"};
+ return strings[s];
+
+}
+
+static char *get_curr_file(int sock);
+static char *get_data_str(int sock);
+static void *get_event_data (const int sock, const int type);
+int send_str (int sock, const char *str);
+char *get_str (int sock);
+int send_int (int sock, int i);
+int get_int (int sock, int *i);
+
+static char *get_curr_file (const int sock) {
+ send_int (sock, CMD_GET_SNAME);
+ int ev;
+ while(ev != EV_DATA) {
+ get_int(sock, &ev);
+ fprintf(stderr, "EVENT: 0x%02x\n", ev);
+ }
+ return get_str(sock);
+}
+static void wait_for_data (const int sock) {
+ int event = -1;
+ do {
+ fprintf(stderr, "waiting for data last was %d\n", event);
+ event = get_int(sock, &event);
+ if (event == EV_EXIT) {
+ fprintf(stderr, "The server exited!");
+ pthread_exit(NULL);
+ }
+ if(event == EV_STATE) {
+ char *skip;
+ skip = get_str(sock);
+ fprintf(stderr, "got str%s\n", skip);
+ }
+ if (event != EV_DATA)
+ get_event_data(sock, event);
+ } while (event != EV_DATA);
+}
+
+static void *get_event_data (const int sock, const int type)
+{
+ switch (type) {
+ case EV_PLIST_ADD:
+ case EV_QUEUE_ADD:
+ break;
+ // return recv_item_from_srv ();
+ case EV_PLIST_DEL:
+ case EV_QUEUE_DEL:
+ case EV_STATUS_MSG:
+ case EV_SRV_ERROR:
+ return get_str(sock);
+ case EV_FILE_TAGS:
+ // return recv_tags_data_from_srv ();
+ break;
+ case EV_PLIST_MOVE:
+ break;
+ }
+
+ return NULL;
+}
+static char *get_data_str(int sock) {
+ wait_for_data (sock);
+ return get_str(sock);
+}
+
+
+int send_str (int sock, const char *str)
+{
+ int len;
+
+ len = strlen (str);
+ if (!send_int (sock, len))
+ return 0;
+
+ if (send (sock, str, len, 0) != len)
+ return 0;
+
+ return 1;
+}
+char *get_str (int sock) {
+ int len;
+ int res, nread = 0;
+ char *str;
+
+ if (!get_int(sock, &len))
+ return NULL;
+
+ if (!RANGE(0, len, MAX_SEND_STRING)) {
+ printf("Bad string length.");
+ return NULL;
+ }
+
+ str = (char *)malloc (sizeof(char) * (len + 1));
+ while (nread < len) {
+ res = recv (sock, str + nread, len - nread, 0);
+ if (res == -1) {
+ printf("recv() failed when getting string: %s\n",
+ strerror(errno));
+ free (str);
+ return NULL;
+ }
+ if (res == 0) {
+ printf("Unexpected EOF when getting string\n");
+ free (str);
+ return NULL;
+ }
+ nread += res;
+ }
+ str[len] = 0;
+
+ return str;
+}
+int send_int (int sock, int i)
+{
+ int res;
+
+ res = send (sock, &i, sizeof(int), 0);
+ if (res == -1)
+ printf("send() failed: %s", strerror(errno));
+
+ return res == sizeof(int) ? 1 : 0;
+}
+
+int get_int (int sock, int *i)
+{
+ int res;
+
+ res = recv (sock, i, sizeof(int), 0);
+ if (res == -1)
+ printf("recv() failed when getting int: %s", strerror(errno));
+
+ return res == sizeof(int) ? 1 : 0;
+}
+
+static int moc_server_connect ()
+{
+ char *path = moc_socket_path;
+ struct sockaddr_un sock_name;
+ int sock;
+
+ /* Create a socket */
+ if ((sock = socket (PF_LOCAL, SOCK_STREAM, 0)) == -1)
+ return -1;
+
+ sock_name.sun_family = AF_LOCAL;
+ strcpy (sock_name.sun_path, moc_socket_path);
+
+ if (connect(sock, (struct sockaddr *)&sock_name,
+ SUN_LEN(&sock_name)) == -1) {
+ close (sock);
+ fprintf(stderr, "%s at (%s)\n", strerror(errno), sock_name);
+ return -1;
+ }
+
+ return sock;
+}
+
+
+void update_state(struct moc *data, int state) {
+ fprintf(stderr, "moc: state %d, state\n");
+ if(state == STATE_PLAY) {
+ data->state = PLAYING;
+ fprintf(stderr, "moc: playing\n");
+ }
+ if(state == STATE_PAUSE) {
+ data->state = PAUSED;
+ fprintf(stderr, "moc: paused\n");
+ }
+ if(state == STATE_STOP) {
+ data->state = STOPPED;
+ fprintf(stderr, "moc: state stopped\n");
+ }
+}
+void *moc_loop(void *input) {
+
+
+ struct moc *data = (struct moc*)input;
+
+ int srv_sock = -1;
+
+ srv_sock = moc_server_connect();
+ if(srv_sock == -1) {
+ pthread_mutex_lock(&data->lock);
+ data->status = FAILED_TO_CONNECT;
+ pthread_mutex_unlock(&data->lock);
+ pthread_exit(NULL);
+ }
+
+ data->status = CONNECTED;
+
+ int last_cmd = -1;
+ int event = -1;
+
+ send_int(srv_sock, CMD_GET_STATE);
+ last_cmd = EV_STATE;
+ while(get_int(srv_sock, &event)) {
+ switch (event) {
+ // handle our data. we MUST know what the last request was otherwise we
+ // don't know how to parse it.
+ case EV_DATA:
+ fprintf(stderr, "moc: EV_DATA\n");
+ if(last_cmd == EV_CTIME) {
+ get_int(srv_sock, &data->time);
+ fprintf(stderr, "moc: update ctime\n");
+ }
+ // on state change, we need to figure out what file we have and what
+ // tags it has.
+ else if(last_cmd == EV_STATE) {
+ int state;
+ get_int(srv_sock, &state);
+ update_state(data, state);
+
+ char *file = get_curr_file(srv_sock);
+ fprintf(stderr, "moc: current file %s\n", file);
+ // if it's the same file as we had, don't update.
+ if(data->filename == NULL || strcmp(file, data->filename) != 0) {
+ fprintf(stderr, "moc: new file, asking for tags\n");
+ data->filename = file;
+ // get tags.
+ send_int(srv_sock, CMD_GET_FILE_TAGS);
+ send_str(srv_sock, file);
+ send_int(srv_sock,TAGS_COMMENTS | TAGS_TIME);
+ last_cmd = EV_FILE_TAGS;
+ }
+ }
+ else {
+ fprintf(stderr, "moc: shit i have data IDK what it is\n");
+ }
+ last_cmd = -1;
+
+ break;
+ case EV_BUSY:
+ fprintf(stderr, "moc: EV_BUSY\n");
+ break;
+ case EV_CTIME:
+ fprintf(stderr, "moc: EV_CTIME\n");
+ last_cmd = EV_CTIME;
+ send_int(srv_sock, CMD_GET_CTIME);
+ break;
+ case EV_STATE:
+ fprintf(stderr, "moc: EV_STATE\n");
+ last_cmd = EV_STATE;
+ send_int(srv_sock, CMD_GET_STATE);
+ break;
+ case EV_EXIT:
+ fprintf(stderr,"moc: EV_EXIT\n");
+ break;
+ case EV_BITRATE:
+ fprintf(stderr, "moc: EV_BITRATE\n");
+ break;
+ case EV_RATE:
+ fprintf(stderr, "moc: EV_RATE\n");
+ break;
+ case EV_CHANNELS:
+ printf("moc: EV_CHANNELS\n");
+ break;
+ case EV_SRV_ERROR:
+ printf("moc: EV_SRV_ERROR\n");
+ break;
+ case EV_OPTIONS:
+ printf("moc: EV_OPTIONS\n");
+ break;
+ case EV_SEND_PLIST:
+ case EV_PLIST_ADD:
+ case EV_PLIST_CLEAR:
+ case EV_PLIST_DEL:
+ case EV_PLIST_MOVE:
+ printf("moc: EV_PLAYLIST\n");
+ case EV_TAGS:
+ last_cmd = EV_TAGS;
+ printf("moc: EV_TAGS\n");
+ break;
+ case EV_STATUS_MSG:
+ printf("moc: EV_STATUS_MSG\n");
+ printf("moc: status update: %s\n", get_str(srv_sock));
+ break;
+ case EV_MIXER_CHANGE:
+ printf("moc: EV_MIXER_CHANGE\n");
+ break;
+ case EV_FILE_TAGS:
+ printf("moc: EV_FILE_TAGS\n");
+
+ if (!(data->filename = get_str(srv_sock))) {
+ fprintf(stderr, "Error while receiving filename\n");
+ }
+
+ if (!(data->title = get_str(srv_sock))) {
+ fprintf(stderr, "Error while receiving title\n");
+ }
+
+ if (!(data->artist = get_str(srv_sock))) {
+ fprintf(stderr, "Error while receiving artist\n");
+ }
+
+ if (!(data->album = get_str(srv_sock))) {
+ fprintf(stderr, "Error while receiving album\n");
+ }
+
+ if (!get_int(srv_sock, &data->track)) {
+ fprintf(stderr, "Error while receiving track\n");
+ }
+
+ if (!get_int(srv_sock, &data->time)) {
+ fprintf(stderr, "Error while receiving time\n");
+ }
+
+ if (!get_int(srv_sock, &data->filled)) {
+ fprintf(stderr, "Error while receiving filled\n");
+ }
+
+ break;
+ case EV_AVG_BITRATE:
+ printf("moc: EV_AVG_BITRATE\n");
+ break;
+ case EV_AUDIO_START:
+ printf("moc: AUDIO START\n");
+ break;
+ case EV_AUDIO_STOP:
+ printf("moc: AUDIO STOP\n");
+ break;
+ default:
+ fprintf(stderr, "Unknown event: 0x%02x!\n", event);
+ break;
+ }
+ }
+}
+void *moc_loop2(void *input) {
+
+ struct moc *data = (struct moc*)input;
+
+ int srv_sock = -1;
+
+ srv_sock = moc_server_connect();
+ if(srv_sock == -1) {
+ pthread_mutex_lock(&data->lock);
+ data->status = FAILED_TO_CONNECT;
+ pthread_mutex_unlock(&data->lock);
+ pthread_exit(NULL);
+ }
+ data->status = CONNECTED;
+
+ while(1) {
+
+ int state = 0;
+ int ev = 0;
+
+ while(ev != EV_DATA) {
+ send_int(srv_sock, CMD_GET_STATE);
+ if (!get_int(srv_sock, &ev)) {
+ fprintf(stderr, "Can't get data from the server.\n");
+ }
+ fprintf(stderr, "EVENT: 0x%02x\n", ev);
+ }
+
+ if(!get_int(srv_sock, &state)) {
+ fprintf(stderr, "Can't get state from the server!\n");
+ }
+ fprintf(stderr, "Got state%d\n", state);
+
+ pthread_mutex_lock(&data->lock);
+ if(state == STATE_PLAY) {
+ data->state = PLAYING;
+ fprintf(stderr, "moc: playing\n");
+ }
+ if(state == STATE_PAUSE) {
+ data->state = PAUSED;
+ fprintf(stderr, "moc: paused\n");
+ }
+ if(state == STATE_STOP) {
+ data->state = STOPPED;
+ fprintf(stderr, "moc: stopped\n");
+ }
+ pthread_mutex_unlock(&data->lock);
+ continue;
+
+ char *file = get_curr_file(srv_sock);
+ fprintf(stderr, "moc: filename: %s\n", file);
+
+ send_int(srv_sock, CMD_GET_FILE_TAGS);
+ send_str(srv_sock, file);
+ send_int(srv_sock,TAGS_COMMENTS | TAGS_TIME);
+
+ // filename first.
+ if (!(data->filename = get_str(srv_sock))) {
+ fprintf(stderr, "Error while receiving filename\n");
+ }
+
+ if (!(data->title = get_str(srv_sock))) {
+ fprintf(stderr, "Error while receiving title\n");
+ }
+
+ if (!(data->artist = get_str(srv_sock))) {
+ fprintf(stderr, "Error while receiving artist\n");
+ }
+
+ if (!(data->album = get_str(srv_sock))) {
+ fprintf(stderr, "Error while receiving album\n");
+ }
+
+ if (!get_int(srv_sock, &data->track)) {
+ fprintf(stderr, "Error while receiving track\n");
+ }
+
+ if (!get_int(srv_sock, &data->time)) {
+ fprintf(stderr, "Error while receiving time\n");
+ }
+
+ if (!get_int(srv_sock, &data->filled)) {
+ fprintf(stderr, "Error while receiving filled\n");
+ }
+
+
+
+ fprintf(stderr, "moc: end switch\n");
+
+ pthread_mutex_unlock(&data->lock);
+ fprintf(stderr, "end lock\n");
+ }
+
+ fprintf(stderr, "moc: end of loop\n");
+ return NULL;
+}
+
+extern struct moc *moc_init() {
+
+ struct moc *moc = (struct moc*)malloc(sizeof(struct moc));
+ moc->filename = NULL;
+ moc->title = NULL;
+ moc->album = NULL;
+ moc->artist = NULL;
+ moc->time = -1;
+ pthread_t thread_id;
+
+ if (pthread_mutex_init(&moc->lock, NULL) != 0) {
+ printf("\n mutex init has failed\n");
+ return NULL;
+ }
+ moc->status = INITIALIZING;
+
+ pthread_create(&thread_id, NULL, moc_loop, (void *)moc);
+ return moc;
+}
diff --git a/moc_library.h b/moc_library.h
new file mode 100644
index 0000000..60a9c09
--- /dev/null
+++ b/moc_library.h
@@ -0,0 +1,47 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h> //Header file for sleep(). man 3 sleep for details.
+#include <errno.h>
+
+#include <pthread.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+
+/* Flags for the info decoder function. */
+enum tags_select
+{
+ TAGS_COMMENTS = 0x01, /* artist, title, etc. */
+ TAGS_TIME = 0x02 /* time of the file. */
+};
+
+enum moc_state {
+ STOPPED,
+ PLAYING,
+ PAUSED
+};
+
+enum moc_status {
+ INITIALIZING,
+ CONNECTED,
+ FAILED_TO_CONNECT,
+ ERROR
+};
+
+struct moc {
+ pthread_mutex_t lock;
+ enum moc_status status;
+ enum moc_state state;
+
+ char *filename;
+ char *title;
+ char *album;
+ char *artist;
+ int track;
+ int time;
+ int filled;
+};
+
+extern const char * moc_str_state(enum moc_state s);
+extern struct moc * moc_init();
+
diff --git a/moc_x11.c b/moc_x11.c
new file mode 100644
index 0000000..efc4ce3
--- /dev/null
+++ b/moc_x11.c
@@ -0,0 +1,142 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h> //Header file for sleep(). man 3 sleep for details.
+#include <errno.h>
+
+#include <pthread.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xos.h>
+
+#include "moc_library.h"
+
+void *xmalloc (size_t size)
+{
+ void *p;
+
+
+ if ((p = malloc(size)) == NULL) {
+ fprintf(stderr, "Can't allocate memory!");
+ exit(0);
+ }
+ return p;
+}
+
+/* Obtains the next X11 event with specified timeout. */
+ static int XNextEventTimed(Display* dsp, XEvent* event_return, long millis) {
+ if (millis == 0)
+ {
+ XNextEvent(dsp, event_return);
+ return True;
+ }
+
+ struct timeval tv;
+ tv.tv_sec = millis / 1000;
+ tv.tv_usec = (millis % 1000) * 1000;
+
+ XFlush(dsp);
+ if (XPending(dsp))
+ {
+ XNextEvent(dsp, event_return);
+ return True;
+ }
+ else
+ {
+ int fd = ConnectionNumber(dsp);
+ fd_set readset;
+ FD_ZERO(&readset);
+ FD_SET(fd, &readset);
+ if (select(fd+1, &readset, NULL, NULL, &tv) <= 0)
+ {
+ return False;
+ }
+ else
+ {
+ if (XPending(dsp))
+ {
+ XNextEvent(dsp, event_return);
+ return True;
+ }
+ else
+ {
+ return False;
+ }
+ }
+ }
+}
+
+char *time_format(int sec) {
+
+ int h = (sec/3600);
+ int m = (sec -(3600*h))/60;
+ int s = (sec -(3600*h)-(m*60));
+
+ char *str = xmalloc(256);
+ if(h) {
+ sprintf(str, "%d:%2d:%02d",h,m,s);
+ } else {
+ sprintf(str, "%02d:%02d",m,s);
+ }
+ return str;
+}
+
+int main() {
+
+ Display* dpy = XOpenDisplay(NULL);
+ if (dpy == NULL)
+ {
+ fprintf(stderr, "Cannot open display\n");
+ exit(1);
+ }
+
+ int s = DefaultScreen(dpy);
+ Window win = XCreateSimpleWindow(dpy, RootWindow(dpy, s), 10, 10, 660, 200, 1,
+ BlackPixel(dpy, s), WhitePixel(dpy, s));
+ XSelectInput(dpy, win, ExposureMask | ButtonPress | KeyPressMask);
+ XMapWindow(dpy, win);
+
+ XStoreName(dpy, win, "mocicon");
+
+ Atom WM_DELETE_WINDOW = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
+ XSetWMProtocols(dpy, win, &WM_DELETE_WINDOW, 1);
+
+ struct moc *mh = moc_init();
+ if(mh == NULL) {
+ return 1;
+ }
+
+ XEvent e;
+ while(1) {
+ int got_event = XNextEventTimed(dpy, &e, 250);
+
+ if(mh->status != CONNECTED) {
+ printf("can't connect\n");
+ }
+
+ XClearWindow(dpy, win);
+ int y_offset = 20;
+ int x = 10;
+ pthread_mutex_lock(&mh->lock);
+ if(mh->state == PLAYING || mh->state == PAUSED) {
+ char *timestr= time_format(mh->time);
+
+ XDrawString(dpy, win, DefaultGC(dpy, s), x, y_offset, mh->title, strlen(mh->title));
+ y_offset += 15;
+ XDrawString(dpy, win, DefaultGC(dpy, s), x, y_offset, mh->artist, strlen(mh->artist));
+ y_offset += 15;
+ XDrawString(dpy, win, DefaultGC(dpy, s), x, y_offset, mh->album, strlen(mh->album));
+ y_offset += 15;
+ XDrawString(dpy, win, DefaultGC(dpy, s), x, y_offset, timestr, strlen(timestr));
+ y_offset += 15;
+
+
+ free(timestr);
+
+ }
+ pthread_mutex_unlock(&mh->lock);
+
+ }
+ };