/* * vchat-client */ #include #include #include #include #include #include #include #include "vchat.h" #include "vchat-user.h" /* version of this module */ char *vchat_us_version = "$Id$"; typedef struct { char *nick; enum { UL_NONE = 0x00, UL_ME = 0x01, UL_IN_MY_CHAN = 0x02, UL_NOT_IN_LIST = 0x04 } flags; uint64_t last_public; uint64_t last_private; } user; static user *g_users; //< all users, incl self static size_t g_users_count; //< number of users in list static char *g_nick; //< own nick static int g_channel; //< own channel unsigned int ul_case_first = 0; static char **g_dict; static size_t g_dict_len; static int ul_nick_lookup( const char *nick, int *exact_match ) { int i; *exact_match = 1; for( i=0; i= 0 ) { g_users[base].flags |= UL_ME; g_nick = g_users[base].nick; } } else ul_del( g_nick ); setstroption(CF_NICK, nick); } void own_channel_set( int channel ) { if( channel != g_channel ) { /* Remove all users from my chan, will be re-set on join message */ int i; for( i=0; i= 0 ) { free( g_users[base].nick ); g_users[base].nick = strdup( newname ); if( g_users[base].flags & UL_ME ) g_nick = g_users[base].nick; if( g_users[base].flags & UL_IN_MY_CHAN ) ul_public_action(newname); } return base; } void ul_clear() { int i; for( i=0; i= 0 ) ul_public_action(name); /* Reflect in UI */ if( own_nick_check( name ) ) ownjoin( g_channel ); } void ul_private_action(char *name) { /* Ensure user and keep channel state */ int base = ul_add(name, -1); if( base >= 0 ) g_users[base].last_private = ul_now(); } void ul_public_action(char *name) { /* Ensure user and put him on the channel */ int base = ul_add(name, 1); if( base >= 0 ) g_users[base].last_public = ul_now(); } void ul_add_to_dict(char *dict_items) { char *i; for(i=strtok(dict_items," ");i;i=strtok(0," ")) { g_dict = realloc( g_dict, sizeof(char*) * ( 1 + g_dict_len ) ); if( !g_dict ) exit(1); g_dict[g_dict_len++] = strdup(i); } } /* Finding users ul_finduser? */ char * ul_match_user(char *regex) { char *dest = tmpstr; int i; regex_t preg; *dest = 0; if( !regcomp( &preg, regex, REG_ICASE | REG_EXTENDED | REG_NEWLINE)) { /* does the username match? */ /* XXX overflow for too many matches */ for( i=0; ilast_private > _b->last_private ) return -1; return 1; } static int ul_compare_begin_of_line_ncase( const void *a, const void *b ) { const user *_a = (const user *)a, *_b = (const user *)b; size_t tmpstr_len; int a_i, b_i; /* First ensure that users in current channel win */ if( !(_a->flags & UL_IN_MY_CHAN ) ) return 1; if( !(_b->flags & UL_IN_MY_CHAN ) ) return -1; tmpstr_len = strlen( tmpstr ); a_i = strncasecmp( _a->nick, tmpstr, tmpstr_len ); b_i = strncasecmp( _b->nick, tmpstr, tmpstr_len ); if( a_i && b_i ) return 0; // Both nicks dont match if( !a_i && b_i ) return -1; // a matches insensitive, b doesnt if( a_i && !b_i ) return 1; // b matches insensitive, a doesnt /* From here both nicks match the prefix, ensure that own_nick always appears last */ if( _a->flags & UL_ME ) return 1; if( _b->flags & UL_ME ) return -1; /* Now the user with the most recent public activity wins */ if( _a->last_public > _b->last_public ) return -1; return 1; } static int ul_compare_begin_of_line_case( const void *a, const void *b ) { const user *_a = (const user *)a, *_b = (const user *)b; size_t tmpstr_len; int a_i, b_i, a_s, b_s; /* First ensure that users in current channel win */ if( !(_a->flags & UL_IN_MY_CHAN ) ) return 1; if( !(_b->flags & UL_IN_MY_CHAN ) ) return -1; tmpstr_len = strlen( tmpstr ); a_i = strncasecmp( _a->nick, tmpstr, tmpstr_len ); a_s = strncmp ( _a->nick, tmpstr, tmpstr_len ); b_i = strncasecmp( _b->nick, tmpstr, tmpstr_len ); b_s = strncmp ( _b->nick, tmpstr, tmpstr_len ); if( a_i && b_i ) return 0; // Both nicks dont match at all if( !a_i && b_i ) return -1; // a matches insensitive, b doesnt if( a_i && !b_i ) return 1; // b matches insensitive, a doesnt if( !a_s && b_s ) return -1; // a matches sensitive, b doesnt if( a_s && !b_s ) return 1; // b matches sensitive, a doesnt /* From now we know that both match with same quality, ensure that own nick always appears last */ if( _a->flags & UL_ME ) return 1; if( _b->flags & UL_ME ) return -1; /* Now the user with the most recent public activity wins */ if( _a->last_public > _b->last_public ) return -1; return 1; } static int ul_compare_middle_ncase( const void *a, const void *b ) { const user *_a = (const user *)a, *_b = (const user *)b; /* Ensure that own nick appears last in list */ if( _a->flags & UL_ME ) return 1; if( _b->flags & UL_ME ) return -1; return strcasecmp( _a->nick, _b->nick ); } static int ul_compare_middle_case( const void *a, const void *b ) { const user *_a = (const user *)a, *_b = (const user *)b; size_t tmpstr_len; int a_s, b_s; /* Ensure that own nick appears last in list */ if( _a->flags & UL_ME ) return 1; if( _b->flags & UL_ME ) return -1; tmpstr_len = strlen( tmpstr ); a_s = strncmp( _a->nick, tmpstr, tmpstr_len ); b_s = strncmp( _b->nick, tmpstr, tmpstr_len ); if( !a_s && b_s ) return -1; // a matches sensitive, b doesnt if( a_s && !b_s ) return 1; // b matches sensitive, a doesnt /* From now both strings either both or both dont match decide their position by case insensitive match */ return strcasecmp( _a->nick, _b->nick ); } /* Nick completion function for readline */ char **ul_complete_user(char *text, int start, int end ) { char **result = 0; int i, result_count = 0, dict_result_count = 0; /* Never want readline to complete filenames */ rl_attempted_completion_over = 1; /* Check for amount of custom dict matches */ if( end && ( start != end ) ) for( i=0; i 0 ) { /* Completion on begin of line with some chars already typed yields a list of everyone in channel, matching prefix, sorted by last public activity */ snprintf( tmpstr, end + 1, "%s", text ); if( ul_case_first ) qsort( g_users, g_users_count, sizeof(user), ul_compare_begin_of_line_case ); else qsort( g_users, g_users_count, sizeof(user), ul_compare_begin_of_line_ncase ); for( i=0; i and thus should complete all users, sorted alphabetically without preferences. */ snprintf( tmpstr, end - start + 1, "%s", text ); if( ul_case_first ) qsort( g_users, g_users_count, sizeof(user), ul_compare_middle_case ); else qsort( g_users, g_users_count, sizeof(user), ul_compare_middle_ncase ); for( i=0; i