# HG changeset patch # User Mark Doliner # Date 1033873727 0 # Node ID 1e60a05c7482a1b54563d579e64e505f68e2fbe4 # Parent 17e6dd05666aba8a259067ae2cf4c55001f10b77 [gaim-migrate @ 3697] This adds le non-direct connect typing notification for oscar. How it works, in a nutshell: -AIM servers append a flag telling remote clients you support typing notification -You tell the AIM servers you support typing notification when you sign on (by changing the "flags" passed to aim_seticbmparam()) -Clients send little typing notification messages to other clients, but only when they know that the other client supports it (so after you've received an IM from the other person) I also fixed a compile warning or two dealing with SSI stuff. Things to look out for: -ICQ states doing weird things -Typing notification works with MacAIM clients (iChat and the official one) -Typing notification doesn't try to send stuff to clients that don't support (I have no idea how you're supposed to look out for this. It should be ok) committer: Tailor Script diff -r 17e6dd05666a -r 1e60a05c7482 src/protocols/oscar/aim.h --- a/src/protocols/oscar/aim.h Sun Oct 06 03:07:59 2002 +0000 +++ b/src/protocols/oscar/aim.h Sun Oct 06 03:08:47 2002 +0000 @@ -168,6 +168,17 @@ "en", \ } +#define CLIENTINFO_AIM_5_0_2938 { \ + "AOL Instant Messenger, version 5.0.2938/WIN32", \ + 0x0109, \ + 0x0005, \ + 0x0000, \ + 0x0000, \ + 0x0b7a, \ + "us", \ + "en", \ +} + #define CLIENTINFO_ICQ_4_65_3281 { \ "ICQ Inc. - Product of ICQ (TM) 2000b.4.65.1.3281.85", \ 0x010a, \ @@ -715,6 +726,7 @@ #define AIM_IMFLAGS_CUSTOMCHARSET 0x0200 /* charset fields set */ #define AIM_IMFLAGS_MULTIPART 0x0400 /* ->mpmsg section valid */ #define AIM_IMFLAGS_OFFLINE 0x0800 /* send to offline user */ +#define AIM_IMFLAGS_TYPINGNOT 0x1000 /* typing notification */ /* * Multipart message structures. @@ -884,6 +896,8 @@ faim_export aim_conn_t *aim_sendfile_initiate(aim_session_t *, const char *destsn, const char *filename, fu16_t numfiles, fu32_t totsize); faim_export int aim_send_im_ch4(aim_session_t *sess, char *sn, fu16_t type, fu8_t *message); +faim_export int aim_mtn_send(aim_session_t *sess, fu16_t type1, char *sn, fu16_t type2); + faim_export aim_conn_t *aim_getfile_initiate(aim_session_t *sess, aim_conn_t *conn, const char *destsn); faim_export int aim_oft_getfile_request(aim_session_t *sess, aim_conn_t *conn, const char *name, int size); faim_export int aim_oft_getfile_ack(aim_session_t *sess, aim_conn_t *conn); @@ -1072,7 +1086,7 @@ faim_export int aim_ssi_cleanlist(aim_session_t *sess, aim_conn_t *conn); faim_export int aim_ssi_addbuddies(aim_session_t *sess, aim_conn_t *conn, const char *gn, const char **sn, unsigned int num); faim_export int aim_ssi_addmastergroup(aim_session_t *sess, aim_conn_t *conn); -faim_export int aim_ssi_addgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num); +faim_export int aim_ssi_addgroups(aim_session_t *sess, aim_conn_t *conn, const char **gn, unsigned int num); faim_export int aim_ssi_addpord(aim_session_t *sess, aim_conn_t *conn, const char **sn, unsigned int num, fu16_t type); faim_export int aim_ssi_movebuddy(aim_session_t *sess, aim_conn_t *conn, char *oldgn, char *newgn, char *sn); faim_export int aim_ssi_rename_group(aim_session_t *sess, aim_conn_t *conn, char *oldgn, char *newgn); diff -r 17e6dd05666a -r 1e60a05c7482 src/protocols/oscar/aim_cbtypes.h --- a/src/protocols/oscar/aim_cbtypes.h Sun Oct 06 03:07:59 2002 +0000 +++ b/src/protocols/oscar/aim_cbtypes.h Sun Oct 06 03:08:47 2002 +0000 @@ -99,6 +99,7 @@ #define AIM_CB_MSG_MISSEDCALL 0x000a #define AIM_CB_MSG_CLIENTAUTORESP 0x000b #define AIM_CB_MSG_ACK 0x000c +#define AIM_CB_MSG_MTN 0x0014 #define AIM_CB_MSG_DEFAULT 0xffff /* diff -r 17e6dd05666a -r 1e60a05c7482 src/protocols/oscar/im.c --- a/src/protocols/oscar/im.c Sun Oct 06 03:07:59 2002 +0000 +++ b/src/protocols/oscar/im.c Sun Oct 06 03:08:47 2002 +0000 @@ -1,7 +1,5 @@ /* - * aim_im.c - * - * The routines for sending/receiving Instant Messages. + * Family 0x0004 - Routines for sending/receiving Instant Messages. * * Note the term ICBM (Inter-Client Basic Message) which blankets * all types of genericly routed through-server messages. Within @@ -10,7 +8,8 @@ * what would commonly be called an "instant message". Channel 2 * is used for negotiating "rendezvous". These transactions end in * something more complex happening, such as a chat invitation, or - * a file transfer. + * a file transfer. Channel 4 is used for various ICQ messages. + * Examples are normal messages, URLs, and old-style authorization. * * In addition to the channel, every ICBM contains a cookie. For * standard IMs, these are only used for error messages. However, @@ -36,6 +35,8 @@ * 0501 0003 0101 0201 01 WinAIM 2.0.847, 2.1.1187, 3.0.1464, * 4.3.2229, 4.4.2286 * 0501 0004 0101 0102 0101 WinAIM 4.1.2010, libfaim (right here) + * 0501 0003 0101 02 WinAIM 5 + * 0501 0001 01 iChat x.x * 0501 0001 0101 01 AOL v6.0, CompuServe 2000 v6.0, any * TOC client * @@ -194,7 +195,6 @@ msgtlvlen += 4 /* charset */ + args->msglen; } - if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, msgtlvlen+128))) return -ENOMEM; @@ -314,6 +314,8 @@ /* * Set the I HAVE A REALLY PURTY ICON flag. + * XXX - This should really only be sent on initial + * IMs and when you change your icon. */ if (args->flags & AIM_IMFLAGS_HASICON) { aimbs_put16(&fr->data, 0x0008); @@ -326,6 +328,7 @@ /* * Set the Buddy Icon Requested flag. + * XXX - Everytime? Surely not... */ if (args->flags & AIM_IMFLAGS_BUDDYREQ) { aimbs_put16(&fr->data, 0x0009); @@ -1345,6 +1348,10 @@ args.icbmflags |= AIM_IMFLAGS_BUDDYREQ; + } else if (type == 0x000b) { /* Non-direct connect typing notification */ + + args.icbmflags |= AIM_IMFLAGS_TYPINGNOT; + } else if (type == 0x0017) { args.extdatalen = length; @@ -2069,6 +2076,82 @@ return ret; } +/* + * Send a mini typing notification (mtn) packet. This is supported by winaim 5 and up. + */ +faim_export int aim_mtn_send(aim_session_t *sess, fu16_t type1, char *sn, fu16_t type2) +{ + aim_conn_t *conn; + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0002))) + return -EINVAL; + + if (!sn) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+11+strlen(sn)+2))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0004, 0x0014, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0004, 0x0014, 0x0000, snacid); + + /* + * 8 days of light + * Er, that is to say, 8 bytes of 0's + */ + aimbs_put16(&fr->data, 0x0000); + aimbs_put16(&fr->data, 0x0000); + aimbs_put16(&fr->data, 0x0000); + aimbs_put16(&fr->data, 0x0000); + + /* + * Type 1 (should be 0x0001 for mtn) + */ + aimbs_put16(&fr->data, type1); + + /* + * Dest sn + */ + aimbs_put8(&fr->data, strlen(sn)); + aimbs_putraw(&fr->data, sn, strlen(sn)); + + /* + * Type 2 (should be 0x0000, 0x0001, or 0x0002 for mtn) + */ + aimbs_put16(&fr->data, type2); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * Receive a mini typing notification (mtn) packet. This is supported by winaim5 and up. + */ +static int mtn_receive(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int ret = 0; + aim_rxcallback_t userfunc; + char *sn; + fu8_t snlen; + fu16_t type1, type2; + + aim_bstream_advance(bs, 8); /* Unknown - All 0's */ + type1 = aimbs_get16(bs); + snlen = aimbs_get8(bs); + sn = aimbs_getstr(bs, snlen); + type2 = aimbs_get16(bs); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, type1, sn, type2); + + free(sn); + + return ret; +} + static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { @@ -2084,6 +2167,8 @@ return clientautoresp(sess, mod, rx, snac, bs); else if (snac->subtype == 0x000c) return msgack(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x0014) + return mtn_receive(sess, mod, rx, snac, bs); return 0; } diff -r 17e6dd05666a -r 1e60a05c7482 src/protocols/oscar/oscar.c --- a/src/protocols/oscar/oscar.c Sun Oct 06 03:07:59 2002 +0000 +++ b/src/protocols/oscar/oscar.c Sun Oct 06 03:08:47 2002 +0000 @@ -70,16 +70,17 @@ #define UC_NORMAL 0x10 #define UC_AB 0x20 #define UC_WIRELESS 0x40 +#define UC_TYPINGNOT 0x80 #define AIMHASHDATA "http://gaim.sourceforge.net/aim_data.php3" static int caps_aim = AIM_CAPS_CHAT | AIM_CAPS_BUDDYICON | AIM_CAPS_IMIMAGE; -/* What does AIM_CAPS_ICQ actually mean? -SE */ -/* static int caps_icq = AIM_CAPS_ICQ; */ /* Set AIM caps, because Gaim can still do them over ICQ and * Winicq doesn't mind. */ static int caps_icq = AIM_CAPS_CHAT | AIM_CAPS_BUDDYICON | AIM_CAPS_IMIMAGE; +/* static int caps_icq = AIM_CAPS_ICQ; */ +/* What does AIM_CAPS_ICQ actually mean? -SE */ static fu8_t gaim_features[] = {0x01, 0x01, 0x01, 0x02}; @@ -269,6 +270,7 @@ static int conninitdone_chat (aim_session_t *, aim_frame_t *, ...); static int conninitdone_chatnav (aim_session_t *, aim_frame_t *, ...); static int gaim_parse_msgerr (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_mtn (aim_session_t *, aim_frame_t *, ...); static int gaim_parse_locaterights(aim_session_t *, aim_frame_t *, ...); static int gaim_parse_buddyrights(aim_session_t *, aim_frame_t *, ...); static int gaim_parse_locerr (aim_session_t *, aim_frame_t *, ...); @@ -708,6 +710,7 @@ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOK, AIM_CB_LOK_ERROR, gaim_parse_searcherror, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOK, 0x0003, gaim_parse_searchreply, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_ERROR, gaim_parse_msgerr, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_MTN, gaim_parse_mtn, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_USERINFO, gaim_parse_user_info, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_ACK, gaim_parse_msgack, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_MOTD, gaim_parse_motd, 0); @@ -1186,7 +1189,7 @@ type |= UC_WIRELESS; } if (info->present & AIM_USERINFO_PRESENT_ICQEXTSTATUS) { - type = (info->icqinfo.status << 7); + type = (info->icqinfo.status << 16); if (!(info->icqinfo.status & AIM_ICQ_STATE_CHAT) && (info->icqinfo.status != AIM_ICQ_STATE_NORMAL)) { type |= UC_UNAVAILABLE; @@ -1405,6 +1408,12 @@ } else g_snprintf(tmp, BUF_LONG, "%s", args->msg); + if (args->icbmflags & AIM_IMFLAGS_TYPINGNOT) { + struct buddy *b = find_buddy(gc, userinfo->sn); + if (b) + b->uc |= UC_TYPINGNOT; + } + strip_linefeed(tmp); serv_got_im(gc, userinfo->sn, tmp, flags, time(NULL), -1); g_free(tmp); @@ -1828,6 +1837,41 @@ return 1; } +static int gaim_parse_mtn(aim_session_t *sess, aim_frame_t *fr, ...) { + struct gaim_connection *gc = sess->aux_data; + va_list ap; + fu16_t type1, type2; + char *sn; + + va_start(ap, fr); + type1 = (fu16_t)va_arg(ap, unsigned int); + sn = va_arg(ap, char *); + type2 = (fu16_t)va_arg(ap, unsigned int); + va_end(ap); + + debug_printf("Received an mtn from %s. Type1 is 0x%04d and type2 is 0x%04d.\n", sn, type1, type2); + + switch (type2) { + case 0x0000: { /* Text has been cleared */ + serv_got_typing_stopped(gc, sn); + } break; + + case 0x0001: { /* Paused typing */ + serv_got_typing_stopped(gc, sn); + } break; + + case 0x0002: { /* Typing */ + serv_got_typing(gc, sn, 0); + } break; + + default: { + printf("Received unknown typing notification type.\n"); + } break; + } + + return 1; +} + static int gaim_parse_locerr(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; char *destn; @@ -2413,6 +2457,7 @@ */ /* Maybe senderwarn and recverwarn should be user preferences... */ + params->flags = 0x0000000b; params->maxmsglen = 8000; params->minmsginterval = 0; @@ -2591,16 +2636,16 @@ * an info window because the name will _not_ be in od->evilhack. For getting * only the away message the contact's UIN is put in od->evilhack. */ if ((budlight = find_buddy(gc, who))) { - if ((budlight->uc >> 7) & (AIM_ICQ_STATE_AWAY || AIM_ICQ_STATE_DND || AIM_ICQ_STATE_OUT || AIM_ICQ_STATE_BUSY || AIM_ICQ_STATE_CHAT)) { + if ((budlight->uc >> 16) & (AIM_ICQ_STATE_AWAY || AIM_ICQ_STATE_DND || AIM_ICQ_STATE_OUT || AIM_ICQ_STATE_BUSY || AIM_ICQ_STATE_CHAT)) { if (budlight->caps & AIM_CAPS_ICQSERVERRELAY) g_show_info_text(gc, who, 0, buf, NULL); else { - char *state_msg = gaim_icq_status((budlight->uc & 0xff80) >> 7); + char *state_msg = gaim_icq_status((budlight->uc & 0xffff0000) >> 16); g_show_info_text(gc, who, 2, buf, "Status: ", state_msg, "
\n

Remote client does not support sending status messages.
\n", NULL); free(state_msg); } } else { - char *state_msg = gaim_icq_status((budlight->uc & 0xff80) >> 7); + char *state_msg = gaim_icq_status((budlight->uc & 0xffff0000) >> 16); g_show_info_text(gc, who, 2, buf, "Status: ", state_msg, NULL); free(state_msg); } @@ -2726,9 +2771,17 @@ static int oscar_send_typing(struct gaim_connection *gc, char *name, int typing) { struct oscar_data *odata = (struct oscar_data *)gc->proto_data; struct direct_im *dim = find_direct_im(odata, name); - if (!dim) - return 0; - aim_send_typing(odata->sess, dim->conn, typing); + if (dim) + aim_send_typing(odata->sess, dim->conn, typing); + else { + struct buddy *b = find_buddy(gc, name); + if (b && (b->uc & UC_TYPINGNOT)) { + if (typing) + aim_mtn_send(odata->sess, 0x0001, name, 0x0002); + else + aim_mtn_send(odata->sess, 0x0001, name, 0x0000); + } + } return 0; } static void oscar_ask_direct_im(struct gaim_connection *gc, char *name); @@ -2825,9 +2878,9 @@ if (odata->icq) { struct buddy *budlight = find_buddy(g, who); if (budlight) - if ((budlight->uc & 0xff80) >> 7) + if ((budlight->uc & 0xffff0000) >> 16) if (budlight->caps & AIM_CAPS_ICQSERVERRELAY) - aim_send_im_ch2_geticqmessage(odata->sess, who, (budlight->uc & 0xff80) >> 7); + aim_send_im_ch2_geticqmessage(odata->sess, who, (budlight->uc & 0xffff0000) >> 16); else debug_printf("Error: Remote client does not support retrieval of status messages.\n"); else @@ -3026,7 +3079,7 @@ sns[tmp] = (char *)((struct buddy*)curbud->data)->name; tmp++; } - aim_ssi_addbuddies(odata->sess, odata->conn, ((struct group*)curgrp->data)->name, sns, tmp); + aim_ssi_addbuddies(odata->sess, odata->conn, ((struct group*)curgrp->data)->name, (const char**)sns, tmp); free(sns); } } @@ -3448,8 +3501,8 @@ static char **oscar_list_icon(int uc) { if (uc == 0) return (char **)icon_online_xpm; - if (uc & 0xff80) { - uc >>= 7; + if (uc & 0xffff0000) { + uc >>= 16; if (uc & AIM_ICQ_STATE_INVISIBLE) return icon_offline_xpm; if (uc & AIM_ICQ_STATE_CHAT) @@ -3654,18 +3707,18 @@ if (od->icq) { struct buddy *budlight = find_buddy(gc, who); if (budlight) - if ((budlight->uc >> 7) & (AIM_ICQ_STATE_AWAY || AIM_ICQ_STATE_DND || AIM_ICQ_STATE_OUT || AIM_ICQ_STATE_BUSY || AIM_ICQ_STATE_CHAT)) + if ((budlight->uc >> 16) & (AIM_ICQ_STATE_AWAY || AIM_ICQ_STATE_DND || AIM_ICQ_STATE_OUT || AIM_ICQ_STATE_BUSY || AIM_ICQ_STATE_CHAT)) if (budlight->caps & AIM_CAPS_ICQSERVERRELAY) - aim_send_im_ch2_geticqmessage(od->sess, who, (budlight->uc & 0xff80) >> 7); + aim_send_im_ch2_geticqmessage(od->sess, who, (budlight->uc & 0xffff0000) >> 16); else { - char *state_msg = gaim_icq_status((budlight->uc & 0xff80) >> 7); + char *state_msg = gaim_icq_status((budlight->uc & 0xffff0000) >> 16); char *dialog_msg = g_strdup_printf(_("UIN: %s
Status: %s


Remote client does not support sending status messages.
"), who, state_msg); g_show_info_text(gc, who, 2, dialog_msg, NULL); free(state_msg); free(dialog_msg); } else { - char *state_msg = gaim_icq_status((budlight->uc & 0xff80) >> 7); + char *state_msg = gaim_icq_status((budlight->uc & 0xffff0000) >> 16); char *dialog_msg = g_strdup_printf(_("UIN: %s
Status: %s


User has no status message.
"), who, state_msg); g_show_info_text(gc, who, 2, dialog_msg, NULL); free(state_msg); @@ -3813,7 +3866,7 @@ } else { debug_printf("ssi: About to delete a permit\n"); if (od->sess->ssi.received_data) - aim_ssi_delpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_PERMIT); + aim_ssi_delpord(od->sess, od->conn, (const char **) &who, 1, AIM_SSI_TYPE_PERMIT); } } @@ -3825,7 +3878,7 @@ } else { debug_printf("ssi: About to delete a deny\n"); if (od->sess->ssi.received_data) - aim_ssi_delpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_DENY); + aim_ssi_delpord(od->sess, od->conn, (const char **) &who, 1, AIM_SSI_TYPE_DENY); } } diff -r 17e6dd05666a -r 1e60a05c7482 src/protocols/oscar/ssi.c --- a/src/protocols/oscar/ssi.c Sun Oct 06 03:07:59 2002 +0000 +++ b/src/protocols/oscar/ssi.c Sun Oct 06 03:08:47 2002 +0000 @@ -416,7 +416,7 @@ char *newgroup; newgroup = (char*)malloc(strlen("Unknown")*sizeof(char)); strcpy(newgroup, "Unknown"); - aim_ssi_addgroups(sess, conn, &newgroup, 1); + aim_ssi_addgroups(sess, conn, (const char**)&newgroup, 1); } } @@ -581,7 +581,7 @@ * @param num The number of groups names you are adding (size of the sn array). * @return Return 0 if no errors, otherwise return the error number. */ -faim_export int aim_ssi_addgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num) +faim_export int aim_ssi_addgroups(aim_session_t *sess, aim_conn_t *conn, const char **gn, unsigned int num) { struct aim_ssi_item *parentgroup, **newitems; fu16_t i; @@ -714,7 +714,7 @@ /* Look up the new parent group */ if (!(groups[1] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, newgn, AIM_SSI_TYPE_GROUP))) { - aim_ssi_addgroups(sess, conn, &newgn, 1); + aim_ssi_addgroups(sess, conn, (const char**)&newgn, 1); if (!(groups[1] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, newgn, AIM_SSI_TYPE_GROUP))) { free(groups); return -ENOMEM;