/* * 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 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(); } /* 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( const void *a, const void *b ) { const user *_a = (const user *)a, *_b = (const user *)b; return strcasecmp( _b->nick, _a->nick ); } /* Nick completion function for readline */ char **ul_complete_user(char *text, int start, int end ) { char **result = 0; int i, result_count = 0; /* Never want readline to complete filenames */ rl_attempted_completion_over = 1; /* Prepare return array ... of max g_users_count (char*) Plus least common prefix in [0] and null terminator */ result = malloc( sizeof(char*) * ( 2 + g_users_count ) ); if( !result ) return 0; if( start == 0 && end == 0 ) { /* Completion on begin of line yields list of everyone we were in private conversation, sorted by time of last .m */ qsort( g_users, g_users_count, sizeof(user), ul_compare_private ); 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 ); qsort( g_users, g_users_count, sizeof(user), ul_compare_middle ); for( i=0; i