#include #include #include #include #include #include #include #include #include #define _USE_BSD #include #include #include #include #include #include #include #include /* For inet_ntoa() */ #include #include #include #include "../include/shm.h" #include "../include/strexp.h" #include "../include/string.h" #include "../include/prochandle.h" #include "../include/Y2/Y.h" #include "../include/Y2/Ylib.h" #include "../include/Y2/Yclientnet.h" static int YIFF_CONNECT_TO(const char *address, int port); static void YIFF_COPY_EVENTS(YEvent *tar, YEvent *src); static void YIFF_FREE_CON(YConnection *con); void YShutdownSocket(int socket, int how, time_t timeout); void YCloseSocketWait(int socket, time_t timeout); YConnection *YOpenConnection( const char *start_arg, const char *con_arg ); int YSetAudioModeValues( YConnection *con, int sample_size, int channels, int sample_rate, int direction, int allow_fragmenting, int num_fragments, int fragment_size /* In bytes */ ); int YChangeAudioModePreset(YConnection *con, const char *name); YAudioModeValuesStruct **YGetAudioModes(YConnection *con, int *count); void YFreeAudioModesList(YAudioModeValuesStruct **list, int count); Boolean YMatchAudioModeValues( YConnection *con, YAudioModeValuesStruct *buf, int sample_rate, int sample_size, int channels, int direction ); int YGetServerStats(YConnection *con, YEventServerStats *buf); long YCalculateCycle( YConnection *con, int sample_rate, int channels, int sample_size, int fragment_size ); int YSetCycle(YConnection *con, long us); void YSyncAll(YConnection *con, Boolean block); int YGetAudioStats(YConnection *con, YEventAudioStats *buf); int YAddHost(YConnection *con, YIPUnion *ip); int YRemoveHost(YConnection *con, YIPUnion *ip); int YSetMixerChannel( YConnection *con, int mixer_channel_code, Coefficient value1, Coefficient value2 ); int YSetMixerChannelQuad( YConnection *con, int mixer_channel_code, Coefficient value1, Coefficient value2, Coefficient value3, Coefficient value4 ); int YGetMixerChannel( YConnection *con, int mixer_channel_code, Coefficient *value1, Coefficient *value2 ); int YGetMixerChannelQuad( YConnection *con, int mixer_channel_code, Coefficient *value1, Coefficient *value2, Coefficient *value3, Coefficient *value4 ); YID YStartPlaySoundObjectSimple(YConnection *con, const char *path); YID YStartPlaySoundObject( YConnection *con, const char *path, YEventSoundPlay *value ); int YGetSoundObjectAttributes( YConnection *con, const char *path, YEventSoundObjectAttributes *buf ); void YSetPlaySoundObjectValues( YConnection *con, YID yid, YEventSoundPlay *value ); int YGetPlaySoundObjectValues( YConnection *con, YID yid, YEventSoundPlay *value ); void YDestroyPlaySoundObject(YConnection *con, YID yid); int YSendClientMessage( YConnection *con, Boolean notify_self, int format, int type, const char *message, int length ); void *YSHMOpenSound(YConnection *con, int *length, int *id); void YSHMCloseSound(YConnection *con, void *ptr); int YPlayAudioCDTrack(YConnection *con, int track_number); int YStopAudioCD(YConnection *con); int YEjectAudioCD(YConnection *con); YAudioCDTrackStruct **YGetAudioCDTracks(YConnection *con, int *count); void YFreeAudioCDTracksList(YAudioCDTrackStruct **list, int count); void YCloseConnection(YConnection *con, Boolean no_shutdown); void YShutdownServer(YConnection *con); int YGetNextEvent(YConnection *con, YEvent *event, Boolean block); void YPutBackEvent(YConnection *con, YEvent *event); /* Checks if c is a white space */ #ifndef ISBLANK # define ISBLANK(c) (((c) == ' ') || ((c) == '\t')) #endif /* * Connect retries: */ #define YIFF_CONNECT_RETRIES 5 /* * Default connection argument: */ #define DEF_CONNECT_ARG "127.0.0.1:9433" #define DEF_PORT 9433 #define MAX(a,b) (((a) > (b)) ? (a) : (b)) #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #define CLIP(a,l,h) (MIN(MAX((a),(l)),(h))) #define ABSOLUTE(x) (((x) < 0) ? ((x) * -1) : (x)) /* * Number of times to retry fetching events for responses in * functions that need to recieve data from server. * * Retries count as blocked event fetches but with the wrong event * received. */ #define DEF_EVENT_FETCH_RETRIES 30 /* From yclientnet.c */ extern void YSetEventToBeDisconnect(YEvent *event); /* * Connects to the specified address and port. * * Returns the socket or -1 on error. */ static int YIFF_CONNECT_TO(const char *address, int port) { int s; struct hostent *host; /* Host pointer */ struct sockaddr_in haddr; if(address == NULL) return(-2); /* Get host address data */ host = gethostbyname(address); if(host == NULL) return(-1); /* Create a new socket */ s = socket(AF_INET, SOCK_STREAM, 0); if(s < 0) return(-1); haddr.sin_family = AF_INET; /* In hbo */ haddr.sin_port = htons(port); /* In nbo */ haddr.sin_addr = *((struct in_addr *)host->h_addr); memset(&haddr.sin_zero, 0x00, 8); /* Zero the rest of struct */ /* Connect */ if(connect( s, (struct sockaddr *)&haddr, sizeof(struct sockaddr) ) == -1) return(-1); #if 0 /* Set socket to non-blocking */ fcntl(s, F_SETFL, O_NONBLOCK); #endif return(s); } /* * Coppies the information from YEvent src to tar. * * Only the information pertainant to what type of * event src is will be coppied. */ static void YIFF_COPY_EVENTS(YEvent *tar, YEvent *src) { if((tar == NULL) || (src == NULL) || (tar == src)) return; else memcpy(tar, src, sizeof(YEvent)); } /* * Deallocates connection structure and its resources. * * Warning, does NOT close the connection! */ static void YIFF_FREE_CON(YConnection *con) { if(con == NULL) return; free(con->queued_event); free(con->buf); free(con); } /* * Shuts down the socket. */ void YShutdownSocket(int socket, int how, time_t timeout) { if(socket < 0) return; shutdown(socket, how); } /* * Calls recv() until it returns 0 and then closes the socket. */ void YCloseSocketWait(int socket, time_t timeout) { char buf[1024]; if(socket < 0) return; while(recv( socket, buf, sizeof(buf), MSG_NOSIGNAL ) > 0); close(socket); } /* * Attempts to connect to the Y server at the address and port * specified by con_arg. * * con_arg is a null terminated string of the format * "
:", ie "127.0.0.1:9433". * * If the connect failed and start_arg is not NULL, then * start_arg will be executed as the command to start the * server. After which a connect attempt will be made again. * * Returns a pointer to a YConnection structure or NULL on * error. To disconnect and deallocate the YConnection structure, * pass it to YIFFDisconnect(). */ YConnection *YOpenConnection(const char *start_arg, const char *con_arg) { int fd; char addr[1024]; int port; int retry_count; int total_retries = YIFF_CONNECT_RETRIES; int yiff_we_started_server = 0; char *strptr; YConnection *con; /* Is the connection argument string NULL? */ if(con_arg == NULL) { /* Look for the "RECORDER" enviroment variable */ strptr = getenv("RECORDER"); if(strptr == NULL) { /* "RECORDER" enviroment variable not set, then all * else use default connect argument. */ con_arg = DEF_CONNECT_ARG; } else { con_arg = strptr; } } /* Parse address from connect string */ strncpy(addr, con_arg, 1024); addr[1024 - 1] = '\0'; strptr = strchr(addr, ':'); if(strptr != NULL) { /* Null terminate address string */ (*strptr) = '\0'; /* Increment strptr to get port number */ strptr++; while(ISBLANK(*strptr)) strptr++; /* Get port number */ port = atoi(strptr); } else { /* Set default port number */ port = DEF_PORT; } /* Address and port number should be fetched or atleast contain * default values. */ /* Attempt to connect to Y server (initial try) */ fd = YIFF_CONNECT_TO(addr, port); if(fd < 0) { /* Connect failed, implying that the Y server may not be * running. So run the server if and only if the start * argument start_arg is not NULL. */ if(start_arg == NULL) { /* Start argument was NULL and we were unable to connect * to the Y server on the first try, so give up. */ return(NULL); } else { /* Run the Y server */ Exec(start_arg); } /* Try connecting to Y server (this is the second try), * keep trying for a total of total_retries times. */ for(retry_count = 0; retry_count < total_retries; retry_count++) { fd = YIFF_CONNECT_TO(addr, port); if(fd > -1) { /* Y server was probably restarted because we are * able to connect. Mark that we (this process) * started the Y server. */ yiff_we_started_server = 1; break; } /* Pause for one second in between tries */ sleep(1); } /* Were we able to connect after trying to restart the Y * server? */ if(fd < 0) { /* Could not establish connection after trying to restart * the Y server. */ return(NULL); } } /* Connection to Y server has been made. If this process * started the Y server then yiff_we_started_server will * be set to 1. */ /* Allocate a new YConnection structure */ con = (YConnection *)calloc(1, sizeof(YConnection)); if(con == NULL) { YShutdownSocket(fd, SHUT_RDWR, 30l); YCloseSocketWait(fd, 30l); return(NULL); } /* Set up connection values */ con->fd = fd; con->we_started_server = yiff_we_started_server; con->prev_generated_yid = YIDNULL; /* Reset queued events array */ con->total_queued_events = 0; con->queued_event = NULL; /* Allocate recieve buffer */ con->buf_len = YNetRecvBufLen; con->buf_cont = 0; con->buf = (u_int8_t *)calloc( con->buf_len, sizeof(u_int8_t) ); if(con->buf == NULL) con->buf_len = 0; return(con); } /* * Changes the Y server's Audio values. * * Note that not all combinations ov values may work, since the * Y server may not support certain combinations of values. It is * recommended that you use YChangeAudioModePreset() whenever * possible since it uses preconfigured values that are known to * work. * * Returns -1 on failure, and 0 on success. */ int YSetAudioModeValues( YConnection *con, int sample_size, /* 8 or 16 */ int channels, /* 1 or 2 */ int sample_rate, /* In hz */ int direction, /* 0 for play, 1 for record */ int allow_fragmenting, int num_fragments, int fragment_size /* In bytes */ ) { int i; YEvent event; if(con == NULL) return(-1); if(con->fd < 0) return(-1); /* Send mode change values */ if(YNetSendAudioChangeValues( con, sample_size, channels, sample_rate, direction, allow_fragmenting, num_fragments, fragment_size ) <= 0) return(-1); /* Wait for response */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if(event.type == YAudioChange) { /* No need to be picky about Preset or Values * type mode change, the mode *did* change is * what we care about. */ break; } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return(-1); } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } return(0); } /* * Changes the Y server's Audio mode to the values specified by * the preset Audio mode who matches the given name. * * Returns -1 on failure, and 0 on success. */ int YChangeAudioModePreset(YConnection *con, const char *name) { int i; YEvent event; if((con == NULL) || (name == NULL)) return(-1); if(con->fd < 0) return(-1); /* Send change audio mode */ if(YNetSendAudioChangePreset(con, name) <= 0) return(-1); /* Wait for response */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if(event.type == YAudioChange) { /* If audio mode name is different then that * implies a failure. */ if(strcasecmp(event.audio.mode_name, name)) return(-1); break; } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return(-1); } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } return(0); } /* * Gets list of preset audio modes and their values. * * Can return NULL on error. */ YAudioModeValuesStruct **YGetAudioModes(YConnection *con, int *count) { int i, n; YEvent event; YAudioModeValuesStruct *amv_ptr; YAudioModeValuesStruct **ptr = NULL; if(count == NULL) return(NULL); else *count = 0; if(con == NULL) return(NULL); if(con->fd < 0) return(NULL); /* Send get audio modes listing */ if(YNetSendListAudioModes(con) <= 0) return(NULL); /* Wait for response to get listing */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if(event.type == YListAudioModes) { if(event.audio.mode_name[0] == '\0') { /* An audio mode listing with no name marks the * end of the listing. */ break; } else { n = *count; *count = n + 1; ptr = (YAudioModeValuesStruct **)realloc( ptr, (*count) * sizeof(YAudioModeValuesStruct *) ); if(ptr == NULL) { *count = 0; return(NULL); } ptr[n] = (YAudioModeValuesStruct *)malloc( sizeof(YAudioModeValuesStruct) ); if(ptr[n] == NULL) { *count = n; return(ptr); } amv_ptr = ptr[n]; strncpy( amv_ptr->name, event.audio.mode_name, YAudioNameMax ); amv_ptr->name[YAudioNameMax - 1] = '\0'; amv_ptr->sample_rate = event.audio.sample_rate; amv_ptr->channels = event.audio.channels; amv_ptr->sample_size = event.audio.sample_size; amv_ptr->fragment_size_bytes = event.audio.fragment_size_bytes; amv_ptr->direction = event.audio.direction; amv_ptr->allow_fragmenting = event.audio.allow_fragmenting; amv_ptr->num_fragments = event.audio.num_fragments; /* Reset loop count to 0, we have more events * to fetch. */ i = 0; continue; } } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return(ptr); } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } return(ptr); } /* * Deletes the list of Audio modes. */ void YFreeAudioModesList(YAudioModeValuesStruct **list, int count) { int i; for(i = 0; i < count; i++) free(list[i]); free(list); } /* * Matches an Audio mode with the given values. * * If an Audio mode is matched then buf will be set with the * values of the matched Audio mode and True is returned, * otherwise False is returned. */ Boolean YMatchAudioModeValues( YConnection *con, YAudioModeValuesStruct *buf, int sample_rate, int sample_size, int channels, int direction /* 0 = play, 1 = record */ ) { int i, n; const YAudioModeValuesStruct *m; YAudioModeValuesStruct **mode = YGetAudioModes(con, &n); if(mode == NULL) return(False); /* Iterate through Audio modes */ for(i = 0; i < n; i++) { m = mode[i]; if(m == NULL) continue; /* This Audio mode's values match the given values? */ if((m->sample_rate == sample_rate) && (m->sample_size == sample_size) && (m->channels == channels) && (m->direction == direction) && (buf != NULL) ) { strncpy(buf->name, m->name, YAudioNameMax); buf->name[YAudioNameMax - 1] = '\0'; buf->sample_rate = m->sample_rate; buf->channels = m->channels; buf->sample_size = m->sample_size; buf->fragment_size_bytes = m->fragment_size_bytes; buf->direction = m->direction; buf->allow_fragmenting = m->allow_fragmenting; buf->num_fragments = m->num_fragments; break; } } /* Delete Audio modes list */ YFreeAudioModesList(mode, n); /* If an Audio mode was matched then return True */ return((i < n) ? True : False); } /* * Gets Y server statistics and stores them in the given * YEventServerStats buffer. * * Returns -1 on error and 0 on success. */ int YGetServerStats(YConnection *con, YEventServerStats *buf) { int i; YEvent event; if((con == NULL) || (buf == NULL) ) return(-1); if(con->fd < 0) return(-1); /* Send request for server stats */ if(YNetSendGetServerStats(con) <= 0) return(-1); /* Wait for server stats event to come in */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if(event.type == YServerStats) { memcpy( buf, &event.serverstats, sizeof(YEventServerStats) ); break; } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return(-1); } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } return(0); } /* * Calculates the theoretical cycle (in microseconds) for * the Y server given the Audio values. The Audio values need * not be the current Audio values of the recorder. * * If the connection pointer con is NULL, then a general * recommendation answer is returned. This value may not * work with the recorder that you are connected to. */ long YCalculateCycle( YConnection *con, int sample_rate, int channels, int sample_size, int fragment_size ) { if((sample_rate <= 0) || (channels <= 0) || (sample_size <= 0) || (fragment_size <= 0) ) return(0); return((long)( (Coefficient)1000000 / (Coefficient)( sample_rate * channels * ((sample_size == 16) ? 2 : 1) ) * (Coefficient)fragment_size / 1.5 )); } /* * Set cycle in microseconds. * * Returns -1 on error and 0 on success. */ int YSetCycle(YConnection *con, long us) { int i; YEvent event; if(con == NULL) return(-1); if(con->fd < 0) return(-1); if(us < 1) us = 1; /* Send set cycle */ if(YNetSendCycleChange(con, us) <= 0) return(-1); /* Wait for response */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if(event.type == YCycleChange) { break; } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return(-1); } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } return(0); } /* * Syncs all of the Y server's resources, both the Y server and the * client will also be synced. * * If block is True then this operation will block (in finite time) * to ensure that the Y server is up to sync. */ void YSyncAll(YConnection *con, Boolean block) { int i; YEvent event; if(con == NULL) return; if(con->fd < 0) return; if(YNetSendSync(con, 0) <= 0) return; if(!block) return; /* Wait for server sync response */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if(event.type == YSync) { break; } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return; } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } } /* * Gets audio device statistics and stores them in * the YEventAudioStats buffer. * * Returns -1 on failure, and 0 on success. */ int YGetAudioStats(YConnection *con, YEventAudioStats *buf) { int i; YEvent event; if((con == NULL) || (buf == NULL)) return(-1); if(con->fd < 0) return(-1); /* Send request for audio stats */ if(YNetSendGetAudioStats(con) <= 0) return(-1); /* Wait for audio stats event to come in */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if(event.type == YAudioStats) { memcpy( buf, &event.audiostats, sizeof(YEventAudioStats) ); break; } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return(-1); } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } return(0); } /* * Adds the IP address specified by ip to the Y server's access list. * * Returns -1 on failure, and 0 on success. */ int YAddHost(YConnection *con, YIPUnion *ip) { int i; YEvent event; if(con == NULL) return(-1); if(con->fd < 0) return(-1); if(ip == NULL) return(-1); /* Send add host */ if(YNetSendSetHost(con, YSetHostAdd, ip) <= 0) return(-1); /* Wait for response */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if(event.type == YSetHost) { break; } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return(-1); } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } return(0); } /* * Removes the IP address specified by ip from the Y server's access * list. If the specified ip does not exist in the Y server's access * list then no operation will be performed. * * Returns -1 on failure, and 0 on success. */ int YRemoveHost(YConnection *con, YIPUnion *ip) { int i; YEvent event; if(con == NULL) return(-1); if(con->fd < 0) return(-1); if(ip == NULL) return(-1); /* Send remove host */ if(YNetSendSetHost(con, YSetHostRemove, ip) <= 0) return(-1); /* Wait for response */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if(event.type == YSetHost) { break; } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return(-1); } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } return(0); } /* * Sets specified mixer channel values. */ int YSetMixerChannel( YConnection *con, int mixer_channel_code, Coefficient value1, Coefficient value2 ) { if(con == NULL) return(-1); if(con->fd < 0) return(-1); /* Sanitize values */ if(mixer_channel_code < 0) return(-1); if(value1 < 0) value1 = 0; if(value1 > 1) value1 = 1; if(value2 < 0) value2 = 0; if(value2 > 1) value2 = 1; /* Send set mixer device */ if(YNetSendSetMixerChannel( con, mixer_channel_code, value1, value2, 0.0, 0.0 ) <= 0) return(-1); /* Do not catch response, calling function may want to have it */ return(0); } /* * Same as YSetMixerChannel(), except that it accepts four mixer * channels. */ int YSetMixerChannelQuad( YConnection *con, int mixer_channel_code, Coefficient value1, Coefficient value2, Coefficient value3, Coefficient value4 ) { if(con == NULL) return(-1); if(con->fd < 0) return(-1); /* Sanitize values */ if(mixer_channel_code < 0) return(-1); if(value1 < 0) value1 = 0; if(value1 > 1) value1 = 1; if(value2 < 0) value2 = 0; if(value2 > 1) value2 = 1; if(value3 < 0) value3 = 0; if(value3 > 1) value3 = 1; if(value4 < 0) value4 = 0; if(value4 > 1) value4 = 1; /* Send set mixer device */ if(YNetSendSetMixerChannel( con, mixer_channel_code, value1, value2, value3, value4 ) <= 0) return(-1); /* Do not catch response, calling function may want to have it */ return(0); } /* * Gets specified mixer channel values. */ int YGetMixerChannel( YConnection *con, int mixer_channel_code, Coefficient *value1, Coefficient *value2 ) { int i; YEvent event; if(con == NULL) return(-1); if(con->fd < 0) return(-1); /* Send request for mixer device values */ if(YNetSendGetMixerChannel(con, mixer_channel_code) <= 0) return(-1); /* Wait for response */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if(event.type == YMixerChannel) { if(event.mixer.code != mixer_channel_code) return(-1); if((value1 != NULL) && (YMixerValues > 0)) *value1 = event.mixer.value[0]; if((value2 != NULL) && (YMixerValues > 1)) *value2 = event.mixer.value[1]; break; } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return(-1); } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } return(0); } /* * Same as YGetMixerChannelQuad(), except that up to four * channels can be fetched. */ int YGetMixerChannelQuad( YConnection *con, int mixer_channel_code, Coefficient *value1, Coefficient *value2, Coefficient *value3, Coefficient *value4 ) { int i; YEvent event; if(con == NULL) return(-1); if(con->fd < 0) return(-1); /* Send request for mixer device values */ if(YNetSendGetMixerChannel(con, mixer_channel_code) <= 0) return(-1); /* Wait for response */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if(event.type == YMixerChannel) { if(event.mixer.code != mixer_channel_code) return(-1); if((value1 != NULL) && (YMixerValues > 0)) *value1 = event.mixer.value[0]; if((value2 != NULL) && (YMixerValues > 1)) *value2 = event.mixer.value[1]; if((value3 != NULL) && (YMixerValues > 2)) *value3 = event.mixer.value[2]; if((value4 != NULL) && (YMixerValues > 3)) *value4 = event.mixer.value[3]; break; } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return(-1); } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } return(0); } /* * Starts playing the sound object specified by path, returns * the YID identifying the sound object being played or YIDNULL on * failure. */ YID YStartPlaySoundObjectSimple(YConnection *con, const char *path) { YEventSoundPlay value; value.flags = YPlayValuesFlagYID | YPlayValuesFlagPosition | YPlayValuesFlagLength | YPlayValuesFlagRepeats | YPlayValuesFlagTotalRepeats | YPlayValuesFlagVolume | YPlayValuesFlagSampleRate; value.yid = YIDNULL; /* Ignored by server */ value.position = 0; /* Ignored by server */ value.length = 0; /* Ignored by server */ value.repeats = 0; /* Ignored by server */ value.total_repeats = 1; value.left_volume = 1.0; value.right_volume = 1.0; value.sample_rate = 0; return(YStartPlaySoundObject(con, path, &value)); } /* * Starts playing the sound object specified by path, returns * the YID indentifying the sound object being played or YIDNULL on * failure. * * The only criteria for a success here is that the sound object * exist. */ YID YStartPlaySoundObject( YConnection *con, const char *path, /* Path on disk on server's computer */ YEventSoundPlay *value ) { int i; YVolumeStruct volume; YID yid; YEvent event; YEventSoundPlay v2; if((con == NULL) || (path == NULL) || (value == NULL)) return(YIDNULL); if(con->fd < 0) return(YIDNULL); /* Set up default play values, copy input value structure * values to the structure v2 (which will be passed to * YNetSendSoundPlay(). */ v2.flags = YPlayValuesFlagYID | YPlayValuesFlagPosition | YPlayValuesFlagLength | YPlayValuesFlagRepeats | YPlayValuesFlagTotalRepeats | YPlayValuesFlagVolume | YPlayValuesFlagSampleRate; v2.yid = YIDNULL; /* Ignored by server */ v2.position = 0; /* Ignored by server */ v2.length = 0; /* Ignored by server */ v2.repeats = 0; /* Ignored by server */ v2.total_repeats = 1; v2.left_volume = 1.0; v2.right_volume = 1.0; v2.back_left_volume = 1.0; v2.back_right_volume = 1.0; v2.sample_rate = 0; if(value->flags & YPlayValuesFlagPosition) { v2.position = value->position; if(v2.position < 0) v2.position = 0; } if(value->flags & YPlayValuesFlagTotalRepeats) { v2.total_repeats = value->total_repeats; if(v2.total_repeats <= 0) v2.total_repeats = -1; } if(value->flags & YPlayValuesFlagVolume) { v2.left_volume = CLIP(value->left_volume, 0.0, 1.0); v2.right_volume = CLIP(value->right_volume, 0.0, 1.0); v2.back_left_volume = CLIP(value->back_left_volume, 0.0, 1.0); v2.back_right_volume = CLIP(value->back_right_volume, 0.0, 1.0); } /* Set volume structure */ volume.left = (Coefficient)v2.left_volume * (Coefficient)((u_int16_t)-1); volume.right = (Coefficient)v2.right_volume * (Coefficient)((u_int16_t)-1); volume.back_left = (Coefficient)v2.back_left_volume * (Coefficient)((u_int16_t)-1); volume.back_right = (Coefficient)v2.back_right_volume * (Coefficient)((u_int16_t)-1); if(value->flags & YPlayValuesFlagSampleRate) { v2.sample_rate = MAX(value->sample_rate, 0); } /* Increment yid dispense number and get new yid */ con->prev_generated_yid++; yid = con->prev_generated_yid; /* Send request to start playing */ if(YNetSendSoundPlay( con, yid, path, v2.position, &volume, v2.sample_rate, v2.total_repeats ) <= 0) return(YIDNULL); /* Wait for response */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if(event.type == YSoundObjectPlay) { if(event.play.yid == YIDNULL) { /* Confirmation recieved but indicated that the * play failed. */ return(YIDNULL); } else { /* Put back event so client can receive it */ YPutBackEvent(con, &event); } break; } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return(YIDNULL); } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } /* Return with the yid of the play object */ return(yid); } /* * Gets sound object attributes, returns -1 on error or * 0 on success. * * Note, the sound object need not be playing in order to * fetch its attributes. */ int YGetSoundObjectAttributes( YConnection *con, const char *path, YEventSoundObjectAttributes *buf ) { int i; YEvent event; if((con == NULL) || (path == NULL) ) return(-1); if(con->fd < 0) return(-1); /* Send request for sound object attributes */ if(YNetSendGetSoundObjectAttributes(con, path) <= 0) return(-1); /* Wait for response */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if(event.type == YSoundObjectAttributes) { const char *ev_path_ptr = event.sndobjattributes.path; if(*ev_path_ptr == '\0') { /* Empth path implies failed */ return(-1); } /* Returned path stored in the event needs to * match what we sent or else we should * interprite this as an error. */ else if(!strcmp(ev_path_ptr, path)) { memcpy( buf, &event.sndobjattributes, sizeof(YEventSoundObjectAttributes) ); } else { /* Returned path in event does mot match the path * that was sent, so consider this an error. */ return(-1); } break; } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return(-1); } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } return(0); } /* * Modifies the play values of the sound object specified by yid * that is already playing. */ void YSetPlaySoundObjectValues( YConnection *con, YID yid, YEventSoundPlay *value ) { if((con == NULL) || (yid == YIDNULL) || (value == NULL)) return; if(con->fd < 0) return; value->flags |= YPlayValuesFlagYID; value->yid = yid; /* Send set play sound object values */ if(YNetSendSetSoundObjectPlayValues(con, value) <= 0) return; /* Do not wait for response, calling functions may want to * recieve it. */ } /* * Gets the play values of the sound object specified by yid * that is already playing. * * Returns 0 on success or non-zero on error. */ int YGetPlaySoundObjectValues( YConnection *con, YID yid, YEventSoundPlay *value ) { int i; YEvent event; if((con == NULL) || (yid == YIDNULL) || (value == NULL)) return(-1); if(con->fd < 0) return(-1); /* Send request for sound object values */ if(YNetSendGetSoundObjectPlayValues(con, yid) <= 0) return(-1); /* Wait for response */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if(event.type == YSoundObjectPlayValues) { const YEventSoundPlay *play = &event.play; if(play->yid == YIDNULL) return(-1); memcpy(value, play, sizeof(YEventSoundPlay)); break; } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return(-1); } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } return(0); } /* * Destroys (stops/kills/terminates) the sound object * being played, indetified by yid. If the yid does not * match any sound object currently being played, no * operation will be performed. */ void YDestroyPlaySoundObject(YConnection *con, YID yid) { if((con == NULL) || (yid == YIDNULL)) return; if(con->fd < 0) return; if(YNetSendSoundKill(con, yid) <= 0) return; /* Do not catch event response for this, calling function * may want to have it. */ } /* * Sends a client message. * * Returns -1 on failure, and 0 on success. */ int YSendClientMessage( YConnection *con, Boolean notify_self, /* Send ourself this YClientMessage event too */ int format, /* One of YClientMessageFormat* */ int type, /* One of YClientMessageType*, note that * clients can also define their own types. * so unexpected values may appear. */ const char *message, /* Message */ int length /* Length of message in bytes */ ) { if(con == NULL) return(-1); if(con->fd < 0) return(-1); /* Send client message */ if(YNetSendClientMessage( con, notify_self, format, type, message, length ) <= 0) return(-1); return(0); } /* * Opens the shared memory segment of the sound buffer on the Y * server. * * Returns the pointer to the memory segment, the length of the * buffer in bytes, and the shared memory segment id. * * The returned pointer must be closed by calling YSHMClose(). * * Can return NULL on error. */ void *YSHMOpenSound(YConnection *con, int *length, int *id) { int i; YEvent event; void *ptr = NULL; if(length != NULL) *length = 0; if(id != NULL) *id = -1; if(con == NULL) return(NULL); if(con->fd < 0) return(NULL); /* Send request for sound object attributes */ if(YNetSendYSHMSoundOpen(con) <= 0) return(NULL); /* Wait for response */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if(event.type == YSHMSound) { YEventYSHMSound *yshmsound = &event.yshmsound; /* Open? */ if(yshmsound->minor_op_code == YSHMSoundOpen) { int _id = yshmsound->shm_id; int _length; if(_id > -1) { ptr = SHMRef(_id, &_length); if(ptr != NULL) { if(length != NULL) *length = _length; if(id != NULL) *id = _id; } } } break; } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return(NULL); } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } return(ptr); } /* * Closes the shared memory segment of the sound buffer that was * previously opened by a call to YSHMOpenSound(). */ void YSHMCloseSound(YConnection *con, void *ptr) { if(con == NULL) return; if(con->fd < 0) return; SHMUnref(ptr); } /* * Plays audio CD track specified by track_number. * * Returns -1 on failure, and 0 on success. */ int YPlayAudioCDTrack(YConnection *con, int track_number) { if(con == NULL) return(-1); if(con->fd < 0) return(-1); /* Send play audio CD track */ if(YNetSendAudioCDPlayTrack(con, track_number) <= 0) return(-1); /* Do not catch event response for this, calling function * may want to have it. */ return(0); } /* * Stops audio CD playing. * * Returns -1 on failure, and 0 on success. */ int YStopAudioCD(YConnection *con) { if(con == NULL) return(-1); if(con->fd < 0) return(-1); /* Send stop audio CD */ if(YNetSendAudioCDStop(con) <= 0) return(-1); /* Do not catch event response for this, calling function * may want to have it. */ return(0); } /* * Ejects audio CD. * * Returns -1 on failure, and 0 on success. */ int YEjectAudioCD(YConnection *con) { if(con == NULL) return(-1); if(con->fd < 0) return(-1); /* Send eject audio CD */ if(YNetSendAudioCDEject(con) <= 0) return(-1); /* Do not catch event response for this, calling function * may want to have it. */ return(0); } /* * Gets list of audio CD tracks. * * Can return NULL on error. */ YAudioCDTrackStruct **YGetAudioCDTracks( YConnection *con, int *count ) { int i, n; YEvent event; YAudioCDTrackStruct *t, **ptr = NULL; if(count == NULL) return(NULL); else *count = 0; if(con == NULL) return(NULL); if(con->fd < 0) return(NULL); /* Send get audio CD tracks listing */ if(YNetSendListAudioCDTracks(con) <= 0) return(NULL); /* Wait for response to get listing */ for(i = 0; i < DEF_EVENT_FETCH_RETRIES; i++) { if(YGetNextEvent(con, &event, True) > 0) { if((event.type == YAudioCDTracksList) && (event.audiocdstats.minor_op_code == YAudioCDTracksListSet) ) { YEventAudioCDStats *acd_stats = &event.audiocdstats; if(acd_stats->track_number <= 0) { /* End of listing */ break; } else { n = *count; *count = n + 1; ptr = (YAudioCDTrackStruct **)realloc( ptr, (*count) * sizeof(YAudioCDTrackStruct *) ); if(ptr == NULL) { *count = 0; return(NULL); } ptr[n] = t = (YAudioCDTrackStruct *)malloc( sizeof(YAudioCDTrackStruct) ); if(t == NULL) { *count = n; return(ptr); } t->number = acd_stats->track_number; t->start_time = acd_stats->track_start_time; t->length = acd_stats->track_length; strncpy( t->name, acd_stats->track_name, YAudioCDTrackNameMax ); t->name[YAudioCDTrackNameMax - 1] = '\0'; /* Reset loop count to 0, we have more events * to fetch. */ i = 0; continue; } } else if((event.type == YDisconnect) || (event.type == YShutdown) ) { return(ptr); } /* Put back event and discount this loop */ YPutBackEvent(con, &event); i--; } } return(ptr); } /* * Deallocates the list of Audio CD track structures and the * pointer array. */ void YFreeAudioCDTracksList(YAudioCDTrackStruct **list, int count) { int i; for(i = 0; i < count; i++) free(list[i]); free(list); } /* * Disconnects from YIFF server and frees all allocated * resources. * * The pointer con may no longer be used after a call to this * function. */ void YCloseConnection(YConnection *con, Boolean no_shutdown) { int i; YEvent event; if(con == NULL) return; /* If we started the server, then we should shut it down */ if(con->we_started_server && !no_shutdown ) { if(con->fd > -1) { YNetSendShutdown(con, 0); /* Wait for shutdown event (for 30 seconds) */ for(i = 0; i < 60; i++) { if(YGetNextEvent(con, &event, False) > 0) { /* Is it a YShutdown or YDisconnect event? */ if((event.type == YShutdown) || (event.type == YDisconnect) ) { break; } } /* Resend shutdown command */ YNetSendShutdown(con, 0); usleep(500000); } /* Close the connection */ YShutdownSocket(con->fd, SHUT_WR, 30l); YCloseSocketWait(con->fd, 30l); con->fd = -1; } } else { if(con->fd > -1) { YNetSendDisconnect(con, 0); /* Close the connection */ YShutdownSocket(con->fd, SHUT_WR, 30l); YCloseSocketWait(con->fd, 30l); con->fd = -1; } } /* Delete the connection */ YIFF_FREE_CON(con); con = NULL; } /* * Shuts down the YIFF server, thus disconnecting and * freeing all allocated resources. * * The pointer con may no longer be used after a call to this * function. */ void YShutdownServer(YConnection *con) { int i; YEvent event; if(con == NULL) return; /* Is connected? */ if(con->fd > -1) { YNetSendShutdown(con, 0); /* Wait for YShutdown or YDisconnect event (for 30 seconds) */ for(i = 0; i < 60; i++) { if(YGetNextEvent(con, &event, False) > 0) { /* Is it a YShutdown or YDisconnect event? */ if((event.type == YShutdown) || (event.type == YDisconnect) ) { break; } } /* Resend shutdown command */ YNetSendShutdown(con, 0); usleep(500000); /* Wait half a second */ } /* Close the connection */ YShutdownSocket(con->fd, SHUT_WR, 30l); YCloseSocketWait(con->fd, 30l); con->fd = -1; } /* Delete the connection */ YIFF_FREE_CON(con); con = NULL; } /* * Get next (oldest) event from the server and stores it on * the pointer to the YEvent event. * * If block is set to True, then execution will be * blocked untill next event is recieved. * * If the recieved event is of type YDisconnect or YShutdown then * the given con structure's connection descriptor will be closed * and reset to -1. */ int YGetNextEvent(YConnection *con, YEvent *event, Boolean block) { int i, status; YEvent *src_event_ptr, *tar_event_ptr; if((con == NULL) || (event == NULL)) return(0); if(con->fd < 0) { /* Connection descriptor is -1 so return event as a * YDisconnect event */ YSetEventToBeDisconnect(event); return(1); } /* Sanitize total queued events */ if(con->total_queued_events < 0) con->total_queued_events = 0; do { /* Get new events and put them into the queue. Note that * this function may close the connection if a YDisconnect * or YShutdown event is recieved. In which case the con->fd * will be set to -1. */ status = YNetRecv(con); /* Non-error (got 0 or more events) and connection still * valid? */ if((status < 0) || (con->fd < 0)) { /* Either a serious recieve error occured or the * connection has been disconnected by YNetRecv(). * * The socket should now be -1, force close if it is * not. */ /* Close the connection */ YShutdownSocket(con->fd, SHUT_RDWR, 30l); YCloseSocketWait(con->fd, 30l); con->fd = -1; /* We need to make sure the event returned is either * a YDisconnect or YShutdown. */ /* Are there any queued events to be reported? */ if(con->total_queued_events > 0) { /* Yes we got atleast one */ /* Copy OLDEST queued event to event structure */ YIFF_COPY_EVENTS(event, &(con->queued_event[0])); /* Don't shift events in queue, instead dealloate * all queued events. */ free(con->queued_event); con->queued_event = NULL; con->total_queued_events = 0; /* Event not a YDisconnect and YShutdown? */ if((event->type != YDisconnect) && (event->type != YShutdown) ) YSetEventToBeDisconnect(event); } else { /* No events queued, so report a YDisconnect */ YSetEventToBeDisconnect(event); } return(1); } /* Got atleast one complete event? */ if(con->total_queued_events > 0) { /* Yes we got atleast one */ /* Copy OLDEST queued event to event structure */ YIFF_COPY_EVENTS(event, &(con->queued_event[0])); /* Decrement number of and shift queued events */ con->total_queued_events--; tar_event_ptr = &(con->queued_event[0]); /* src_event_ptr may be invalid but if so then we do * not access it. */ src_event_ptr = &(con->queued_event[1]); for(i = 0; i < con->total_queued_events; i++, tar_event_ptr++, src_event_ptr++ ) YIFF_COPY_EVENTS( tar_event_ptr, src_event_ptr ); /* Reallocate queued event pointers */ if(con->total_queued_events > 0) { con->queued_event = (YEvent *)realloc( con->queued_event, con->total_queued_events * sizeof(YEvent) ); if(con->queued_event == NULL) { con->total_queued_events = 0; } } else { free(con->queued_event); con->queued_event = NULL; con->total_queued_events = 0; } /* Report that we got event */ return(1); } else { if(block) usleep(100); } } while(block); return(0); } /* * Puts back the event onto the queue as the newest (highest * index) event. * * So all other older events (if any) will be returned before this * one when YGetNextEvent() is called. */ void YPutBackEvent( YConnection *con, YEvent *event ) { int n; if((con == NULL) || (event == NULL) ) return; if(con->fd < 0) return; /* Sanitize total queued events */ if(con->total_queued_events < 0) con->total_queued_events = 0; /* Increment total */ n = con->total_queued_events; con->total_queued_events++; /* Check if exceeded maximum allowed queued events */ if(con->total_queued_events > YQueuedEventsMax) { fprintf(stderr, "YPutBackEvent(): Connection 0x%.8x: Limit of %i queued events exceeded.\n", (u_int32_t)con, YQueuedEventsMax ); con->total_queued_events = YQueuedEventsMax; return; } /* Allocate a new queued event */ con->queued_event = (YEvent *)realloc( con->queued_event, con->total_queued_events * sizeof(YEvent) ); if(con->queued_event == NULL) { con->total_queued_events = 0; return; } /* Copy input event values to new queued event position */ YIFF_COPY_EVENTS(&con->queued_event[n], event); }