summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWilliam Ehlhardt <williamehlhardt@gmail.com>2007-08-12 20:48:34 +0000
committerWilliam Ehlhardt <williamehlhardt@gmail.com>2007-08-12 20:48:34 +0000
commit57181f609b12e3521000800dc0d9aeeb29274723 (patch)
tree3653228efd75a0f8f2ff5920307ba897cade7205
parent747c895862b5af045627d85a4ecac3635792ea07 (diff)
parenteed8fd54ce9f23bd7d8abc4965302d2157a92ef9 (diff)
downloadpidgin-57181f609b12e3521000800dc0d9aeeb29274723.tar.gz
propagate from branch 'im.pidgin.pidgin' (head 4b396c53da733e23710e7613bd41afc0aabfb83f)
to branch 'im.pidgin.soc.2007.certmgr' (head 62025e265d2452996200a6db69940579dfb9245b)
-rw-r--r--COPYRIGHT3
-rw-r--r--ChangeLog18
-rw-r--r--ChangeLog.API11
-rw-r--r--ChangeLog.win323
-rw-r--r--Doxyfile.in2
-rw-r--r--Makefile.am2
-rw-r--r--Makefile.mingw1
-rw-r--r--configure.ac73
-rw-r--r--doc/ui-ops.dox24
-rw-r--r--finch/Makefile.am2
-rw-r--r--finch/finch.c41
-rw-r--r--finch/finch.h1
-rw-r--r--finch/gntaccount.c16
-rw-r--r--finch/gntconv.h3
-rw-r--r--finch/gntsound.c1041
-rw-r--r--finch/gntui.c5
-rw-r--r--finch/libgnt/configure.ac5
-rw-r--r--finch/libgnt/gnt.h1
-rw-r--r--finch/libgnt/gntbindable.c224
-rw-r--r--finch/libgnt/gntbindable.h28
-rw-r--r--finch/libgnt/gntcombobox.h36
-rw-r--r--finch/libgnt/gntfilesel.c12
-rw-r--r--finch/libgnt/gntline.c66
-rw-r--r--finch/libgnt/gntmain.c19
-rw-r--r--finch/libgnt/gntmenu.c23
-rw-r--r--finch/libgnt/gnttextview.c34
-rw-r--r--finch/libgnt/gnttextview.h4
-rw-r--r--finch/libgnt/gnttree.c16
-rw-r--r--finch/libgnt/gntutils.h1
-rw-r--r--finch/libgnt/gntwm.c154
-rw-r--r--finch/libgnt/pygnt/Makefile.am40
-rw-r--r--finch/libgnt/pygnt/Makefile.make2
-rwxr-xr-xfinch/libgnt/pygnt/dbus-gnt52
-rw-r--r--finch/libgnt/pygnt/example/rss/gnthtml.py87
-rwxr-xr-xfinch/libgnt/pygnt/example/rss/gntrss-ui.py400
-rw-r--r--finch/libgnt/pygnt/example/rss/gntrss.py250
-rwxr-xr-xfinch/libgnt/pygnt/gendef.sh3
-rw-r--r--finch/libgnt/pygnt/gnt.override153
-rw-r--r--finch/libgnt/pygnt/gntbox.override38
-rw-r--r--finch/libgnt/pygnt/gntmodule.c3
-rw-r--r--finch/libgnt/pygnt/gnttree.override79
-rw-r--r--finch/libgnt/pygnt/gntwidget.override36
-rwxr-xr-xfinch/libgnt/pygnt/test.py65
-rw-r--r--finch/libgnt/wms/Makefile.am12
-rw-r--r--finch/libgnt/wms/s.c4
-rw-r--r--libpurple/Makefile.am2
-rw-r--r--libpurple/account.c8
-rw-r--r--libpurple/account.h54
-rw-r--r--libpurple/connection.h41
-rw-r--r--libpurple/conversation.c2
-rw-r--r--libpurple/conversation.h60
-rw-r--r--libpurple/core.c3
-rw-r--r--libpurple/log.c5
-rw-r--r--libpurple/plugins/log_reader.c52
-rw-r--r--libpurple/plugins/perl/perl-handlers.c55
-rw-r--r--libpurple/prefs.c1
-rw-r--r--libpurple/protocols/bonjour/Makefile.am54
-rw-r--r--libpurple/protocols/bonjour/Makefile.mingw4
-rw-r--r--libpurple/protocols/bonjour/bonjour.c61
-rw-r--r--libpurple/protocols/bonjour/buddy.c133
-rw-r--r--libpurple/protocols/bonjour/buddy.h27
-rw-r--r--libpurple/protocols/bonjour/issues.txt1
-rw-r--r--libpurple/protocols/bonjour/jabber.c297
-rw-r--r--libpurple/protocols/bonjour/jabber.h15
-rw-r--r--libpurple/protocols/bonjour/mdns_avahi.c481
-rw-r--r--libpurple/protocols/bonjour/mdns_common.c248
-rw-r--r--libpurple/protocols/bonjour/mdns_common.h16
-rw-r--r--libpurple/protocols/bonjour/mdns_howl.c178
-rw-r--r--libpurple/protocols/bonjour/mdns_howl.h47
-rw-r--r--libpurple/protocols/bonjour/mdns_interface.h (renamed from libpurple/protocols/bonjour/mdns_win32.h)27
-rw-r--r--libpurple/protocols/bonjour/mdns_types.h22
-rw-r--r--libpurple/protocols/bonjour/mdns_win32.c354
-rw-r--r--libpurple/protocols/bonjour/parser.c194
-rw-r--r--libpurple/protocols/bonjour/parser.h33
-rw-r--r--libpurple/protocols/jabber/auth.c6
-rw-r--r--libpurple/protocols/jabber/google.c4
-rw-r--r--libpurple/protocols/jabber/jabber.c4
-rw-r--r--libpurple/protocols/jabber/presence.c8
-rw-r--r--libpurple/protocols/jabber/xdata.c2
-rw-r--r--libpurple/protocols/msn/userlist.c8
-rw-r--r--libpurple/protocols/oscar/family_feedbag.c53
-rw-r--r--libpurple/protocols/oscar/flap_connection.c4
-rw-r--r--libpurple/protocols/oscar/libaim.c2
-rw-r--r--libpurple/protocols/oscar/libicq.c2
-rw-r--r--libpurple/protocols/oscar/oscar.c151
-rw-r--r--libpurple/protocols/oscar/oscar.h7
-rw-r--r--libpurple/protocols/oscar/oscar_data.c2
-rw-r--r--libpurple/protocols/oscar/oscarcommon.h1
-rw-r--r--libpurple/protocols/qq/buddy_opt.c10
-rw-r--r--libpurple/protocols/qq/group.c2
-rw-r--r--libpurple/protocols/qq/group_im.c10
-rw-r--r--libpurple/protocols/qq/group_internal.c2
-rw-r--r--libpurple/protocols/qq/group_join.c14
-rw-r--r--libpurple/protocols/qq/group_opt.c10
-rw-r--r--libpurple/protocols/qq/im.c2
-rw-r--r--libpurple/protocols/qq/keep_alive.c2
-rw-r--r--libpurple/protocols/qq/login_logout.c2
-rw-r--r--libpurple/protocols/qq/qq.c32
-rw-r--r--libpurple/protocols/qq/qq_proxy.c9
-rw-r--r--libpurple/protocols/qq/sys_msg.c17
-rw-r--r--libpurple/protocols/yahoo/Makefile.am2
-rw-r--r--libpurple/protocols/yahoo/Makefile.mingw1
-rw-r--r--libpurple/protocols/yahoo/util.c4
-rw-r--r--libpurple/protocols/yahoo/yahoo.c166
-rw-r--r--libpurple/protocols/yahoo/yahoo_aliases.c245
-rw-r--r--libpurple/protocols/yahoo/yahoo_aliases.h50
-rw-r--r--libpurple/protocols/yahoo/yahoo_doodle.c144
-rw-r--r--libpurple/protocols/yahoo/yahoo_doodle.h21
-rw-r--r--libpurple/protocols/yahoo/yahoo_filexfer.c8
-rw-r--r--libpurple/protocols/yahoo/yahoo_packet.c5
-rw-r--r--libpurple/protocols/yahoo/yahoo_packet.h3
-rw-r--r--libpurple/stringref.h2
-rw-r--r--libpurple/stun.c1
-rw-r--r--libpurple/win32/global.mak1
-rw-r--r--pidgin.spec.in5
-rw-r--r--pidgin/Makefile.am2
-rw-r--r--pidgin/Makefile.mingw1
-rw-r--r--pidgin/gtkaccount.c21
-rw-r--r--pidgin/gtkblist.c2
-rw-r--r--pidgin/gtkblist.h2
-rw-r--r--pidgin/gtkcellrendererexpander.c2
-rw-r--r--pidgin/gtkconv.c328
-rw-r--r--pidgin/gtkdebug.c2
-rw-r--r--pidgin/gtkdocklet.c2
-rw-r--r--pidgin/gtkimhtml.c9
-rw-r--r--pidgin/gtkimhtmltoolbar.c132
-rw-r--r--pidgin/gtkmain.c8
-rw-r--r--pidgin/gtknotify.c3
-rw-r--r--pidgin/gtkprefs.c13
-rw-r--r--pidgin/gtkrequest.c14
-rw-r--r--pidgin/gtksound.c3
-rw-r--r--pidgin/gtkutils.c6
-rw-r--r--pidgin/pidginstock.c4
-rw-r--r--pidgin/pixmaps/Makefile.am2
-rw-r--r--pidgin/pixmaps/emotes/default/22/act-up.pngbin1317 -> 1574 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/alien.pngbin1452 -> 1643 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/angel.pngbin1587 -> 1664 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/angry.pngbin1354 -> 1529 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/arrogant.pngbin1307 -> 1518 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/at-wits-end.pngbin1368 -> 1527 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/bashful.pngbin1281 -> 1549 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/beat-up.pngbin1317 -> 1631 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/beauty.pngbin1363 -> 1636 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/blowkiss.pngbin1283 -> 1532 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/bye.pngbin1316 -> 1619 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/call-me.pngbin1374 -> 1579 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/clap.pngbin1334 -> 1548 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/confused.pngbin1280 -> 1578 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/crying.pngbin1319 -> 1606 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/curl-lip.pngbin1269 -> 1576 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/curse.pngbin1342 -> 1617 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/cute.pngbin1275 -> 1526 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/dance.pngbin1359 -> 1600 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/dazed.pngbin1275 -> 1518 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/default.theme.in230
-rw-r--r--pidgin/pixmaps/emotes/default/22/desire.pngbin1291 -> 1604 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/devil.pngbin1457 -> 1648 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/disapointed.pngbin1225 -> 1549 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/disdain.pngbin1288 -> 1566 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/doh.pngbin1284 -> 1525 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/dont-know.pngbin1265 -> 1522 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/drool.pngbin1301 -> 1605 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/eat.pngbin1335 -> 1644 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/embarrassed.pngbin1284 -> 1586 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/excruciating.pngbin1242 -> 1568 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/eyeroll.pngbin1239 -> 1530 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/fingers-crossed.pngbin1373 -> 1602 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/foot-in-mouth.pngbin1274 -> 1562 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/freaked-out.pngbin1236 -> 1550 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/glasses-cool.pngbin1302 -> 1574 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/glasses-nerdy.pngbin1519 -> 1708 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/go-away.pngbin1324 -> 1576 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/handshake.pngbin1390 -> 1659 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/highfive.pngbin1499 -> 1766 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/hug-left.pngbin1344 -> 1574 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/hug-right.pngbin1354 -> 1565 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/hypnotized.pngbin1315 -> 1598 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/in-love.pngbin1366 -> 1635 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/jump.pngbin1316 -> 1518 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/kiss.pngbin1345 -> 1608 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/lashes.pngbin1341 -> 1586 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/laugh.pngbin1250 -> 1498 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/lying.pngbin1274 -> 1554 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/mean.pngbin1277 -> 1576 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/meeting.pngbin1438 -> 1511 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/moneymouth.pngbin1324 -> 1572 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/nailbiting.pngbin1286 -> 1550 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/neutral.pngbin1245 -> 1554 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/on-the-phone.pngbin1343 -> 1576 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/party.pngbin1488 -> 1679 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/pissed-off.pngbin1342 -> 1527 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/pray.pngbin1392 -> 1598 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/question.pngbin1491 -> 1721 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/quiet.pngbin1248 -> 1534 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/rotfl.pngbin1318 -> 1538 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/sad.pngbin1283 -> 1567 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/sarcastic.pngbin1274 -> 1563 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/secret.pngbin1304 -> 1394 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/shame.pngbin1290 -> 1562 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/shock.pngbin1264 -> 1583 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/shout.pngbin1288 -> 1578 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/shut-mouth.pngbin1306 -> 1596 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/sick.pngbin1309 -> 1581 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/silly.pngbin1266 -> 1567 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/skywalker.pngbin1554 -> 1752 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/sleepy.pngbin1400 -> 1624 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/smile-big.pngbin1235 -> 1483 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/smile.pngbin1271 -> 1574 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/smirk.pngbin1256 -> 1559 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/snicker.pngbin1274 -> 1579 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/soldier.pngbin1402 -> 1578 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/starving.pngbin1341 -> 1628 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/struggle.pngbin1269 -> 1553 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/sweat.pngbin1275 -> 1594 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/teeth.pngbin1268 -> 1550 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/terror.pngbin1259 -> 1448 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/thinking.pngbin1282 -> 1574 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/time-out.pngbin1249 -> 1521 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/tongue.pngbin1260 -> 1580 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/tremble.pngbin1298 -> 1576 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/vampire.pngbin1315 -> 1600 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/victory.pngbin1387 -> 1582 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/waiting.pngbin1251 -> 1552 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/weep.pngbin1267 -> 1552 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/wilt.pngbin1260 -> 1480 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/wink.pngbin1276 -> 1568 bytes
-rw-r--r--pidgin/pixmaps/emotes/default/22/yawn.pngbin1286 -> 1557 bytes
-rw-r--r--pidgin/pixmaps/icons/22/Makefile.am2
-rw-r--r--pidgin/pixmaps/status/16/Makefile.am1
-rw-r--r--pidgin/pixmaps/status/16/message-pending.pngbin482 -> 0 bytes
-rw-r--r--pidgin/plugins/win32/transparency/win2ktrans.c8
-rw-r--r--pidgin/win32/nsis/translations/swedish.nsh55
-rw-r--r--pidgin/win32/winpidgin.c18
-rw-r--r--po/POTFILES.in1
-rw-r--r--share/Makefile.am4
-rw-r--r--share/Makefile.mingw19
-rw-r--r--share/sounds/Makefile.am (renamed from pidgin/sounds/Makefile.am)2
-rw-r--r--share/sounds/Makefile.mingw (renamed from pidgin/sounds/Makefile.mingw)0
-rw-r--r--share/sounds/alert.wav (renamed from pidgin/sounds/alert.wav)bin23314 -> 23314 bytes
-rw-r--r--share/sounds/login.wav (renamed from pidgin/sounds/login.wav)bin192412 -> 192412 bytes
-rw-r--r--share/sounds/logout.wav (renamed from pidgin/sounds/logout.wav)bin155888 -> 155888 bytes
-rw-r--r--share/sounds/receive.wav (renamed from pidgin/sounds/receive.wav)bin55008 -> 55008 bytes
-rw-r--r--share/sounds/send.wav (renamed from pidgin/sounds/send.wav)bin57440 -> 57440 bytes
243 files changed, 6001 insertions, 1487 deletions
diff --git a/COPYRIGHT b/COPYRIGHT
index 0e57f92ca1..8b34a47c02 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -144,6 +144,7 @@ Ike Gingerich
Gustavo Giráldez
Richard Gobeille
Ian Goldberg
+Matthew Goldstein
Michael Golden
Charlie Gordon
Ryan C. Gordon
@@ -245,6 +246,7 @@ Arkadiusz Miskiewicz
Andrew Molloy
Michael Monreal
Benjamin Moody
+John Moody
Tim Mooney
Sergio Moretto
Christian Muise
@@ -276,6 +278,7 @@ Celso Pinto
Joao Luís Marques Pinto
Aleksander Piotrowski
Julien Pivotto
+Eric Polino <aluink@gmail.com>
Ari Pollak
Robey Pointer
Eric Polino
diff --git a/ChangeLog b/ChangeLog
index 5941f01b72..d22cbf1ae8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,23 @@
Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
+version 2.1.1 (??/??/????):
+ libpurple:
+ * Added an account action to open your inbox in the yahoo prpl.
+ * Added support for Unicode status messages in Yahoo.
+ * Server-stored aliases for Yahoo. (John Moody)
+ * Fixed support for Yahoo! doodling.
+ * Bonjour plugin uses native Avahi instead of Howl
+ * Bonjour plugin supports Buddy Icons
+
+ Pidgin:
+ * Show current outgoing conversation formatting on the font label on
+ the toolbar
+ * Slim new redesign of conversation tabs to maximize number of
+ conversations that can fit in a window
+
+ Finch:
+ * Sound support (Eric Polino)
+
version 2.1.0 (07/28/2007):
libpurple:
* Core changes to allow UIs to use second-granularity for scheduling.
diff --git a/ChangeLog.API b/ChangeLog.API
index a7350663b7..ac087d8ba3 100644
--- a/ChangeLog.API
+++ b/ChangeLog.API
@@ -1,5 +1,15 @@
Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
+Version 2.1.1 (x/x/x):
+ libpurple:
+ Changed:
+ * PurpleAccountUiOps.request_authorize's authorize_cb and
+ deny_cb parameters now correctly have type
+ PurpleAccountRequestAuthorizationCb rather than GCallback.
+ (You'll want to change your UI's implementation's signature
+ to avoid warnings, and then remove some now-redundant casts
+ back to the proper type.)
+
version 2.1.0 (7/28/2007):
libpurple:
Added:
@@ -19,7 +29,6 @@ version 2.1.0 (7/28/2007):
* purple_core_ensure_single_instance
This is for UIs to use to ensure only one copy is running.
* purple_dbus_is_owner
- * purple_image_data_calculate_filename
* purple_timeout_add_seconds
Callers should prefer this to purple_timeout_add for timers
longer than 1 second away. Be aware of the rounding, though.
diff --git a/ChangeLog.win32 b/ChangeLog.win32
index 4078683bc8..74043a1e52 100644
--- a/ChangeLog.win32
+++ b/ChangeLog.win32
@@ -9,6 +9,9 @@ version 2.0.2 (6/14/2007):
Apple Bonjour for Windows from:
http://www.apple.com/support/downloads/bonjourforwindows.html
+version 2.0.1 (5/24/2007):
+ * No changes
+
version 2.0.0 (5/3/2007):
* URI Handler support added via `pidgin.exe --protocolhandler=`
* Running a second instance will popup the Buddy List, if possible.
diff --git a/Doxyfile.in b/Doxyfile.in
index cf56ad43f3..d5dbde28ac 100644
--- a/Doxyfile.in
+++ b/Doxyfile.in
@@ -300,7 +300,7 @@ SORT_MEMBER_DOCS = YES
# by member name. If set to NO (the default) the members will appear in
# declaration order.
-SORT_BRIEF_DOCS = YES
+SORT_BRIEF_DOCS = NO
# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
# sorted by fully-qualified names, including namespaces. If set to
diff --git a/Makefile.am b/Makefile.am
index a85a1c1d42..6f9910256f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -42,7 +42,7 @@ if ENABLE_GNT
GNT_DIR=finch
endif
-SUBDIRS = libpurple doc $(GNT_DIR) $(GTK_DIR) m4macros po
+SUBDIRS = libpurple doc $(GNT_DIR) $(GTK_DIR) m4macros po share
docs: Doxyfile
if HAVE_DOXYGEN
diff --git a/Makefile.mingw b/Makefile.mingw
index 3bf4f94d0f..f4030b1681 100644
--- a/Makefile.mingw
+++ b/Makefile.mingw
@@ -67,6 +67,7 @@ install: all $(PIDGIN_INSTALL_DIR)
$(MAKE) -C $(PURPLE_TOP) -f $(MINGW_MAKEFILE) install
$(MAKE) -C $(PIDGIN_TOP) -f $(MINGW_MAKEFILE) install
$(MAKE) -C $(PURPLE_PO_TOP) -f $(MINGW_MAKEFILE) install
+ $(MAKE) -C share -f $(MINGW_MAKEFILE) install
create_release_install_dir: install
rm -rf $(PIDGIN_INSTALL_DIR).release
diff --git a/configure.ac b/configure.ac
index e1d7677e9d..24ceffe0a2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -46,17 +46,17 @@ AC_PREREQ([2.50])
m4_define([purple_lt_current], [1])
m4_define([purple_major_version], [2])
m4_define([purple_minor_version], [1])
-m4_define([purple_micro_version], [0])
-m4_define([purple_version_suffix], [])
+m4_define([purple_micro_version], [1])
+m4_define([purple_version_suffix], [devel])
m4_define([purple_version],
[purple_major_version.purple_minor_version.purple_micro_version])
m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix]))
m4_define([gnt_lt_current], [1])
m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [0])
+m4_define([gnt_minor_version], [1])
m4_define([gnt_micro_version], [0])
-m4_define([gnt_version_suffix], [])
+m4_define([gnt_version_suffix], [devel])
m4_define([gnt_version],
[gnt_major_version.gnt_minor_version.gnt_micro_version])
m4_define([gnt_display_version], gnt_version[]m4_ifdef([gnt_version_suffix],[gnt_version_suffix]))
@@ -593,6 +593,46 @@ AC_SUBST(MEANWHILE_CFLAGS)
AC_SUBST(MEANWHILE_LIBS)
dnl #######################################################################
+dnl # Check for Native Avahi headers (for Bonjour)
+dnl #######################################################################
+AC_ARG_WITH(avahi-client-includes, [AC_HELP_STRING([--with-avahi-client-includes=DIR], [compile the Bonjour plugin against the Avahi Client includes in DIR])], [ac_avahi_client_includes="$withval"], [ac_avahi_client_includes="no"])
+AC_ARG_WITH(avahi-client-libs, [AC_HELP_STRING([--with-avahi-client-libs=DIR], [compile the Bonjour plugin against the Avahi Client libs in DIR])], [ac_avahi_client_libs="$withval"], [ac_avahi_client_libs="no"])
+AVAHI_CFLAGS=""
+AVAHI_LIBS=""
+
+dnl Attempt to autodetect Avahi
+PKG_CHECK_MODULES(AVAHI, [avahi-client avahi-glib], [
+ avahiincludes="yes"
+ avahilibs="yes"
+], [
+ AC_MSG_RESULT(no)
+ avahiincludes="no"
+ avahilibs="no"
+])
+
+dnl Override AVAHI_CFLAGS if the user specified an include dir
+if test "$ac_avahi_client_includes" != "no"; then
+ AVAHI_CFLAGS="-I$ac_avahi_client_includes"
+fi
+CPPFLAGS_save="$CPPFLAGS"
+CPPFLAGS="$CPPFLAGS $AVAHI_CFLAGS"
+AC_CHECK_HEADER(avahi-client/client.h, [avahiincludes=yes], [avahiincludes=no])
+CPPFLAGS="$CPPFLAGS $AVAHI_CFLAGS $GLIB_CFLAGS"
+AC_CHECK_HEADER(avahi-glib/glib-malloc.h, [avahiincludes=yes], [avahiincludes=no])
+CPPFLAGS="$CPPFLAGS_save"
+
+dnl Override AVAHI_LIBS if the user specified a libs dir
+if test "$ac_avahi_client_libs" != "no"; then
+ AVAHI_LIBS="-L$ac_avahi_client_libs -lavahi-common -lavahi-client -lavahi-glib "
+fi
+AC_CHECK_LIB(avahi-client, avahi_client_new, [avahilibs=yes], [avahilibs=no], $AVAHI_LIBS)
+
+AC_SUBST(AVAHI_CFLAGS)
+AC_SUBST(AVAHI_LIBS)
+
+AM_CONDITIONAL(MDNS_AVAHI, test "x$avahiincludes" = "xyes" -a "x$avahilibs" = "xyes")
+
+dnl #######################################################################
dnl # Check for Howl headers (for Bonjour)
dnl #######################################################################
AC_ARG_WITH(howl-includes, [AC_HELP_STRING([--with-howl-includes=DIR], [compile the Bonjour plugin against the Howl includes in DIR])], [ac_howl_includes="$withval"], [ac_howl_includes="no"])
@@ -601,6 +641,7 @@ HOWL_CFLAGS=""
HOWL_LIBS=""
dnl Attempt to autodetect avahi-compat-howl
+dnl TODO: (This should be removed when the native avahi stuff is stable)
PKG_CHECK_MODULES(HOWL, avahi-compat-howl, [
howlincludes="yes"
howllibs="yes"
@@ -640,6 +681,9 @@ AC_CHECK_LIB(howl, sw_discovery_init, [howllibs=yes], [howllibs=no], $HOWL_LIBS)
AC_SUBST(HOWL_CFLAGS)
AC_SUBST(HOWL_LIBS)
+AM_CONDITIONAL(MDNS_HOWL, test "x$howlincludes" = "xyes" -a "x$howllibs" = "xyes")
+
+
dnl #######################################################################
dnl # Check for SILC client includes and libraries
dnl #######################################################################
@@ -819,8 +863,10 @@ fi
if test "x$have_meanwhile" != "xyes" ; then
STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/sametime//'`
fi
-if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then
- STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/bonjour//'`
+if test "x$avahiincludes" != "xyes" -o "x$avahilibs" != "xyes"; then
+ if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then
+ STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/bonjour//'`
+ fi
fi
if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then
STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/silc/silc10/'`
@@ -873,7 +919,7 @@ for i in $STATIC_PRPLS ; do
*) echo "Invalid static protocol $i!!" ; exit ;;
esac
done
-AM_CONDITIONAL(STATIC_BONJOUR, test "x$static_bonjour" = "xyes" -a "x$howlincludes" = "xyes" -a "x$howllibs" = "xyes")
+AM_CONDITIONAL(STATIC_BONJOUR, test "x$static_bonjour" = "xyes")
AM_CONDITIONAL(STATIC_GG, test "x$static_gg" = "xyes")
AM_CONDITIONAL(STATIC_IRC, test "x$static_irc" = "xyes")
AM_CONDITIONAL(STATIC_JABBER, test "x$static_jabber" = "xyes")
@@ -898,8 +944,10 @@ fi
if test "x$have_meanwhile" != "xyes"; then
DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/sametime//'`
fi
-if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then
- DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/bonjour//'`
+if test "x$avahiincludes" != "xyes" -o "x$avahilibs" != "xyes"; then
+ if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then
+ DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/bonjour//'`
+ fi
fi
if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then
DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/silc/silc10/'`
@@ -930,7 +978,7 @@ for i in $DYNAMIC_PRPLS ; do
*) echo "Invalid dynamic protocol $i!!" ; exit ;;
esac
done
-AM_CONDITIONAL(DYNAMIC_BONJOUR, test "x$dynamic_bonjour" = "xyes" -a "x$bonjourincludes" = "xyes" -a "x$bonjourclient" = "xyes")
+AM_CONDITIONAL(DYNAMIC_BONJOUR, test "x$dynamic_bonjour" = "xyes" -a [ [ "x$avahiincludes" = "xyes" -a "x$avahilibs " = "xyes" ] -o [ "x$howlincludes" = "xyes" -a "x$howllibs" = "xyes" ] ] )
AM_CONDITIONAL(DYNAMIC_GG, test "x$dynamic_gg" = "xyes")
AM_CONDITIONAL(DYNAMIC_IRC, test "x$dynamic_irc" = "xyes")
AM_CONDITIONAL(DYNAMIC_JABBER, test "x$dynamic_jabber" = "xyes")
@@ -2038,6 +2086,8 @@ if test "x$enable_debug" = "xyes" ; then
AC_DEFINE(DEBUG, 1, [Define if debugging is enabled.])
fi
+AM_CONDITIONAL(PURPLE_AVAILABLE, true)
+
AC_OUTPUT([Makefile
Doxyfile
doc/Makefile
@@ -2116,7 +2166,6 @@ AC_OUTPUT([Makefile
pidgin/plugins/perl/Makefile
pidgin/plugins/perl/common/Makefile.PL
pidgin/plugins/ticker/Makefile
- pidgin/sounds/Makefile
libpurple/example/Makefile
libpurple/gconf/Makefile
libpurple/purple.pc
@@ -2149,6 +2198,8 @@ AC_OUTPUT([Makefile
libpurple/protocols/zephyr/Makefile
libpurple/tests/Makefile
libpurple/version.h
+ share/Makefile
+ share/sounds/Makefile
finch/Makefile
finch/libgnt/Makefile
finch/libgnt/gnt.pc
diff --git a/doc/ui-ops.dox b/doc/ui-ops.dox
new file mode 100644
index 0000000000..c569b5b2c6
--- /dev/null
+++ b/doc/ui-ops.dox
@@ -0,0 +1,24 @@
+/** @page ui-ops UiOps structures
+
+ When implementing a UI for libpurple, you need to fill in various UiOps
+ structures:
+
+ - #PurpleAccountUiOps
+ - #PurpleBlistUiOps
+ - #PurpleConnectionUiOps
+ - #PurpleConversationUiOps
+ - #PurpleCoreUiOps
+ - #PurpleDebugUiOps
+ - #PurpleDnsQueryUiOps
+ - #PurpleEventLoopUiOps
+ - #PurpleIdleUiOps
+ - #PurpleNotifyUiOps
+ - #PurplePrivacyUiOps
+ - #PurpleRequestUiOps
+ - #PurpleRoomlistUiOps
+ - #PurpleSoundUiOps
+ - #PurpleWhiteboardUiOps
+ - #PurpleXferUiOps
+
+ */
+// vim: ft=c.doxygen
diff --git a/finch/Makefile.am b/finch/Makefile.am
index 15f52be920..39b20723c9 100644
--- a/finch/Makefile.am
+++ b/finch/Makefile.am
@@ -60,6 +60,7 @@ finch_LDADD = \
$(GLIB_LIBS) \
$(LIBXML_LIBS) \
$(GNT_LIBS) \
+ $(GSTREAMER_LIBS) \
./libgnt/libgnt.la \
$(top_builddir)/libpurple/libpurple.la
@@ -77,4 +78,5 @@ AM_CPPFLAGS = \
$(GLIB_CFLAGS) \
$(DBUS_CFLAGS) \
$(LIBXML_CFLAGS) \
+ $(GSTREAMER_CFLAGS) \
$(GNT_CFLAGS)
diff --git a/finch/finch.c b/finch/finch.c
index beaca0bcee..0a433cbcff 100644
--- a/finch/finch.c
+++ b/finch/finch.c
@@ -55,16 +55,10 @@ debug_init()
purple_debug_set_ui_ops(finch_debug_get_ui_ops());
}
-/* XXX: this "leaks" a hashtable on shutdown. I'll let
- * the finch guys decide if they want to go through the trouble
- * of properly freeing it, since their quit function doesn't
- * live in this file */
-
static GHashTable *ui_info = NULL;
-
static GHashTable *finch_ui_get_info()
{
- if(NULL == ui_info) {
+ if (ui_info == NULL) {
ui_info = g_hash_table_new(g_str_hash, g_str_equal);
g_hash_table_insert(ui_info, "name", (char*)_("Finch"));
@@ -74,12 +68,20 @@ static GHashTable *finch_ui_get_info()
return ui_info;
}
+static void
+finch_quit(void)
+{
+ gnt_ui_uninit();
+ if (ui_info)
+ g_hash_table_destroy(ui_info);
+}
+
static PurpleCoreUiOps core_ops =
{
finch_prefs_init,
debug_init,
gnt_ui_init,
- gnt_ui_uninit,
+ finch_quit,
finch_ui_get_info,
/* padding */
@@ -396,18 +398,29 @@ init_libpurple(int argc, char **argv)
return 1;
}
-int main(int argc, char **argv)
+static gboolean gnt_start(int *argc, char ***argv)
+{
+ /* Initialize the libpurple stuff */
+ if (!init_libpurple(*argc, *argv))
+ return FALSE;
+
+ purple_blist_show();
+ return TRUE;
+}
+
+int main(int argc, char *argv[])
{
signal(SIGPIPE, SIG_IGN);
g_set_prgname("Finch");
+#if GLIB_CHECK_VERSION(2,2,0)
g_set_application_name(_("Finch"));
+#endif
+
+ gnt_init();
+
+ gnt_start(&argc, &argv);
- /* Initialize the libpurple stuff */
- if (!init_libpurple(argc, argv))
- return 0;
-
- purple_blist_show();
gnt_main();
#ifdef STANDALONE
diff --git a/finch/finch.h b/finch/finch.h
index 1ec08bd5af..06f9198075 100644
--- a/finch/finch.h
+++ b/finch/finch.h
@@ -27,3 +27,4 @@
#define FINCH_UI "gnt-purple"
+#define FINCH_PREFS_ROOT "/finch"
diff --git a/finch/gntaccount.c b/finch/gntaccount.c
index b7190b31f2..68c96b197e 100644
--- a/finch/gntaccount.c
+++ b/finch/gntaccount.c
@@ -912,9 +912,15 @@ deny_no_add_cb(auth_and_add *aa)
}
static void *
-finch_request_authorize(PurpleAccount *account, const char *remote_user,
- const char *id, const char *alias, const char *message, gboolean on_list,
- GCallback auth_cb, GCallback deny_cb, void *user_data)
+finch_request_authorize(PurpleAccount *account,
+ const char *remote_user,
+ const char *id,
+ const char *alias,
+ const char *message,
+ gboolean on_list,
+ PurpleAccountRequestAuthorizationCb auth_cb,
+ PurpleAccountRequestAuthorizationCb deny_cb,
+ void *user_data)
{
char *buffer;
PurpleConnection *gc;
@@ -941,8 +947,8 @@ finch_request_authorize(PurpleAccount *account, const char *remote_user,
GList *iter;
auth_and_add *aa = g_new(auth_and_add, 1);
- aa->auth_cb = (PurpleAccountRequestAuthorizationCb)auth_cb;
- aa->deny_cb = (PurpleAccountRequestAuthorizationCb)deny_cb;
+ aa->auth_cb = auth_cb;
+ aa->deny_cb = deny_cb;
aa->data = user_data;
aa->username = g_strdup(remote_user);
aa->alias = g_strdup(alias);
diff --git a/finch/gntconv.h b/finch/gntconv.h
index 52389232f9..d7592cbd47 100644
--- a/finch/gntconv.h
+++ b/finch/gntconv.h
@@ -30,6 +30,9 @@
#include "conversation.h"
+/* Grabs the conv out of a PurpleConverstation */
+#define FINCH_CONV(conv) ((FinchConv *)(conv)->ui_data)
+
/***************************************************************************
* @name GNT Conversations API
***************************************************************************/
diff --git a/finch/gntsound.c b/finch/gntsound.c
index 11c5712029..303a8e9831 100644
--- a/finch/gntsound.c
+++ b/finch/gntsound.c
@@ -22,28 +22,1053 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+#include "internal.h"
+#include "finch.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#include <mmsystem.h>
+#endif
+
+#ifdef USE_GSTREAMER
+#include <gst/gst.h>
+#endif /* USE_GSTREAMER */
+
+#include "debug.h"
+#include "notify.h"
+#include "prefs.h"
+#include "sound.h"
+#include "util.h"
+
+#include "gntbox.h"
+#include "gntwindow.h"
+#include "gntcombobox.h"
+#include "gntlabel.h"
+#include "gntconv.h"
#include "gntsound.h"
+#include "gntwidget.h"
+#include "gntentry.h"
+#include "gntcheckbox.h"
+#include "gntline.h"
+#include "gntslider.h"
+#include "gnttree.h"
+#include "gntfilesel.h"
+
+typedef struct {
+ PurpleSoundEventID id;
+ char *label;
+ char *pref;
+ char *def;
+ char *file;
+} FinchSoundEvent;
+
+typedef struct {
+ GntWidget *method;
+ GntWidget *command;
+ GntWidget *conv_focus;
+ GntWidget *while_status;
+ GntWidget *volume;
+ GntWidget *events;
+ GntWidget *window;
+ GntWidget *selector;
+
+ GntWidget *profiles;
+ GntWidget *new_profile;
+ gchar * original_profile;
+} SoundPrefDialog;
+
+#define DEFAULT_PROFILE "default"
+
+static SoundPrefDialog *pref_dialog;
+
+#define PLAY_SOUND_TIMEOUT 15000
+
+static guint mute_login_sounds_timeout = 0;
+static gboolean mute_login_sounds = FALSE;
+
+#ifdef USE_GSTREAMER
+static gboolean gst_init_failed;
+#endif /* USE_GSTREAMER */
+
+static FinchSoundEvent sounds[PURPLE_NUM_SOUNDS] = {
+ {PURPLE_SOUND_BUDDY_ARRIVE, N_("Buddy logs in"), "login", "login.wav", NULL},
+ {PURPLE_SOUND_BUDDY_LEAVE, N_("Buddy logs out"), "logout", "logout.wav", NULL},
+ {PURPLE_SOUND_RECEIVE, N_("Message received"), "im_recv", "receive.wav", NULL},
+ {PURPLE_SOUND_FIRST_RECEIVE, N_("Message received begins conversation"), "first_im_recv", "receive.wav", NULL},
+ {PURPLE_SOUND_SEND, N_("Message sent"), "send_im", "send.wav", NULL},
+ {PURPLE_SOUND_CHAT_JOIN, N_("Person enters chat"), "join_chat", "login.wav", NULL},
+ {PURPLE_SOUND_CHAT_LEAVE, N_("Person leaves chat"), "left_chat", "logout.wav", NULL},
+ {PURPLE_SOUND_CHAT_YOU_SAY, N_("You talk in chat"), "send_chat_msg", "send.wav", NULL},
+ {PURPLE_SOUND_CHAT_SAY, N_("Others talk in chat"), "chat_msg_recv", "receive.wav", NULL},
+ {PURPLE_SOUND_POUNCE_DEFAULT, NULL, "pounce_default", "alert.wav", NULL},
+ {PURPLE_SOUND_CHAT_NICK, N_("Someone says your screen name in chat"), "nick_said", "alert.wav", NULL}
+};
+
+const char *
+finch_sound_get_active_profile()
+{
+ return purple_prefs_get_string(FINCH_PREFS_ROOT "/sound/actprofile");
+}
+
+/* This method creates a pref name based on the current active profile.
+ * So if "Home" is the current active profile the pref name
+ * [FINCH_PREFS_ROOT "/sound/profiles/Home/$NAME"] is created.
+ */
+static gchar *
+make_pref(const char *name)
+{
+ static char pref_string[512];
+ g_snprintf(pref_string, sizeof(pref_string),
+ FINCH_PREFS_ROOT "/sound/profiles/%s%s", finch_sound_get_active_profile(), name);
+ return pref_string;
+}
+
+
+static gboolean
+unmute_login_sounds_cb(gpointer data)
+{
+ mute_login_sounds = FALSE;
+ mute_login_sounds_timeout = 0;
+ return FALSE;
+}
+
+static gboolean
+chat_nick_matches_name(PurpleConversation *conv, const char *aname)
+{
+ PurpleConvChat *chat = NULL;
+ char *nick = NULL;
+ char *name = NULL;
+ gboolean ret = FALSE;
+ chat = purple_conversation_get_chat_data(conv);
+
+ if (chat == NULL)
+ return ret;
+
+ nick = g_strdup(purple_normalize(conv->account, chat->nick));
+ name = g_strdup(purple_normalize(conv->account, aname));
+
+ if (g_utf8_collate(nick, name) == 0)
+ ret = TRUE;
+
+ g_free(nick);
+ g_free(name);
+
+ return ret;
+}
+
+/*
+ * play a sound event for a conversation, honoring make_sound flag
+ * of conversation and checking for focus if conv_focus pref is set
+ */
+static void
+play_conv_event(PurpleConversation *conv, PurpleSoundEventID event)
+{
+ /* If we should not play the sound for some reason, then exit early */
+ if (conv != NULL)
+ {
+ FinchConv *gntconv;
+ gboolean has_focus;
+
+ gntconv = FINCH_CONV(conv);
+
+ has_focus = purple_conversation_has_focus(conv);
+
+ if (has_focus && !purple_prefs_get_bool(make_pref("/conv_focus")))
+ {
+ return;
+ }
+ }
+
+ purple_sound_play_event(event, conv ? purple_conversation_get_account(conv) : NULL);
+}
+
+static void
+buddy_state_cb(PurpleBuddy *buddy, PurpleSoundEventID event)
+{
+ purple_sound_play_event(event, purple_buddy_get_account(buddy));
+}
+
+static void
+im_msg_received_cb(PurpleAccount *account, char *sender,
+ char *message, PurpleConversation *conv,
+ PurpleMessageFlags flags, PurpleSoundEventID event)
+{
+ if (flags & PURPLE_MESSAGE_DELAYED)
+ return;
+
+ if (conv == NULL) {
+ purple_sound_play_event(PURPLE_SOUND_FIRST_RECEIVE, account);
+ } else {
+ play_conv_event(conv, event);
+ }
+}
+
+static void
+im_msg_sent_cb(PurpleAccount *account, const char *receiver,
+ const char *message, PurpleSoundEventID event)
+{
+ PurpleConversation *conv = purple_find_conversation_with_account(
+ PURPLE_CONV_TYPE_ANY, receiver, account);
+ play_conv_event(conv, event);
+}
+
+static void
+chat_buddy_join_cb(PurpleConversation *conv, const char *name,
+ PurpleConvChatBuddyFlags flags, gboolean new_arrival,
+ PurpleSoundEventID event)
+{
+ if (new_arrival && !chat_nick_matches_name(conv, name))
+ play_conv_event(conv, event);
+}
+
+static void
+chat_buddy_left_cb(PurpleConversation *conv, const char *name,
+ const char *reason, PurpleSoundEventID event)
+{
+ if (!chat_nick_matches_name(conv, name))
+ play_conv_event(conv, event);
+}
+
+static void
+chat_msg_sent_cb(PurpleAccount *account, const char *message,
+ int id, PurpleSoundEventID event)
+{
+ PurpleConnection *conn = purple_account_get_connection(account);
+ PurpleConversation *conv = NULL;
+
+ if (conn!=NULL)
+ conv = purple_find_chat(conn, id);
-const char *finch_sound_get_active_profile(void)
+ play_conv_event(conv, event);
+}
+
+static void
+chat_msg_received_cb(PurpleAccount *account, char *sender,
+ char *message, PurpleConversation *conv,
+ PurpleMessageFlags flags, PurpleSoundEventID event)
+{
+ PurpleConvChat *chat;
+
+ if (flags & PURPLE_MESSAGE_DELAYED)
+ return;
+
+ chat = purple_conversation_get_chat_data(conv);
+ g_return_if_fail(chat != NULL);
+
+ if (purple_conv_chat_is_user_ignored(chat, sender))
+ return;
+
+ if (chat_nick_matches_name(conv, sender))
+ return;
+
+ if (flags & PURPLE_MESSAGE_NICK || purple_utf8_has_word(message, chat->nick))
+ play_conv_event(conv, PURPLE_SOUND_CHAT_NICK);
+ else
+ play_conv_event(conv, event);
+}
+
+/*
+ * We mute sounds for the 10 seconds after you log in so that
+ * you don't get flooded with sounds when the blist shows all
+ * your buddies logging in.
+ */
+static void
+account_signon_cb(PurpleConnection *gc, gpointer data)
+{
+ if (mute_login_sounds_timeout != 0)
+ g_source_remove(mute_login_sounds_timeout);
+ mute_login_sounds = TRUE;
+ mute_login_sounds_timeout = purple_timeout_add_seconds(10, unmute_login_sounds_cb, NULL);
+}
+
+static void *
+finch_sound_get_handle()
+{
+ static int handle;
+
+ return &handle;
+}
+
+
+/* This gets called when the active profile changes */
+static void
+initialize_profile(const char *name, PurplePrefType type, gconstpointer val, gpointer null)
{
- return NULL;
+ if (purple_prefs_exists(make_pref("")))
+ return;
+
+ purple_prefs_add_none(make_pref(""));
+ purple_prefs_add_none(make_pref("/enabled"));
+ purple_prefs_add_none(make_pref("/file"));
+ purple_prefs_add_bool(make_pref("/enabled/login"), TRUE);
+ purple_prefs_add_path(make_pref("/file/login"), "");
+ purple_prefs_add_bool(make_pref("/enabled/logout"), TRUE);
+ purple_prefs_add_path(make_pref("/file/logout"), "");
+ purple_prefs_add_bool(make_pref("/enabled/im_recv"), TRUE);
+ purple_prefs_add_path(make_pref("/file/im_recv"), "");
+ purple_prefs_add_bool(make_pref("/enabled/first_im_recv"), FALSE);
+ purple_prefs_add_path(make_pref("/file/first_im_recv"), "");
+ purple_prefs_add_bool(make_pref("/enabled/send_im"), TRUE);
+ purple_prefs_add_path(make_pref("/file/send_im"), "");
+ purple_prefs_add_bool(make_pref("/enabled/join_chat"), FALSE);
+ purple_prefs_add_path(make_pref("/file/join_chat"), "");
+ purple_prefs_add_bool(make_pref("/enabled/left_chat"), FALSE);
+ purple_prefs_add_path(make_pref("/file/left_chat"), "");
+ purple_prefs_add_bool(make_pref("/enabled/send_chat_msg"), FALSE);
+ purple_prefs_add_path(make_pref("/file/send_chat_msg"), "");
+ purple_prefs_add_bool(make_pref("/enabled/chat_msg_recv"), FALSE);
+ purple_prefs_add_path(make_pref("/file/chat_msg_recv"), "");
+ purple_prefs_add_bool(make_pref("/enabled/nick_said"), FALSE);
+ purple_prefs_add_path(make_pref("/file/nick_said"), "");
+ purple_prefs_add_bool(make_pref("/enabled/pounce_default"), TRUE);
+ purple_prefs_add_path(make_pref("/file/pounce_default"), "");
+ purple_prefs_add_bool(make_pref("/conv_focus"), TRUE);
+ purple_prefs_add_bool(make_pref("/mute"), FALSE);
+ purple_prefs_add_path(make_pref("/command"), "");
+ purple_prefs_add_string(make_pref("/method"), "automatic");
+ purple_prefs_add_int(make_pref("/volume"), 50);
}
-void finch_sound_set_active_profile(const char *name)
+static void
+finch_sound_init(void)
{
+ void *gnt_sound_handle = finch_sound_get_handle();
+ void *blist_handle = purple_blist_get_handle();
+ void *conv_handle = purple_conversations_get_handle();
+#ifdef USE_GSTREAMER
+ GError *error = NULL;
+#endif
+
+ purple_signal_connect(purple_connections_get_handle(), "signed-on",
+ gnt_sound_handle, PURPLE_CALLBACK(account_signon_cb),
+ NULL);
+
+ purple_prefs_add_none(FINCH_PREFS_ROOT "/sound");
+ purple_prefs_add_string(FINCH_PREFS_ROOT "/sound/actprofile", DEFAULT_PROFILE);
+ purple_prefs_add_none(FINCH_PREFS_ROOT "/sound/profiles");
+
+ purple_prefs_connect_callback(gnt_sound_handle, FINCH_PREFS_ROOT "/sound/actprofile", initialize_profile, NULL);
+ purple_prefs_trigger_callback(FINCH_PREFS_ROOT "/sound/actprofile");
+
+
+#ifdef USE_GSTREAMER
+ purple_debug_info("sound", "Initializing sound output drivers.\n");
+ if ((gst_init_failed = !gst_init_check(NULL, NULL, &error))) {
+ purple_notify_error(NULL, _("GStreamer Failure"),
+ _("GStreamer failed to initialize."),
+ error ? error->message : "");
+ if (error) {
+ g_error_free(error);
+ error = NULL;
+ }
+ }
+#endif /* USE_GSTREAMER */
+
+ purple_signal_connect(blist_handle, "buddy-signed-on",
+ gnt_sound_handle, PURPLE_CALLBACK(buddy_state_cb),
+ GINT_TO_POINTER(PURPLE_SOUND_BUDDY_ARRIVE));
+ purple_signal_connect(blist_handle, "buddy-signed-off",
+ gnt_sound_handle, PURPLE_CALLBACK(buddy_state_cb),
+ GINT_TO_POINTER(PURPLE_SOUND_BUDDY_LEAVE));
+ purple_signal_connect(conv_handle, "received-im-msg",
+ gnt_sound_handle, PURPLE_CALLBACK(im_msg_received_cb),
+ GINT_TO_POINTER(PURPLE_SOUND_RECEIVE));
+ purple_signal_connect(conv_handle, "sent-im-msg",
+ gnt_sound_handle, PURPLE_CALLBACK(im_msg_sent_cb),
+ GINT_TO_POINTER(PURPLE_SOUND_SEND));
+ purple_signal_connect(conv_handle, "chat-buddy-joined",
+ gnt_sound_handle, PURPLE_CALLBACK(chat_buddy_join_cb),
+ GINT_TO_POINTER(PURPLE_SOUND_CHAT_JOIN));
+ purple_signal_connect(conv_handle, "chat-buddy-left",
+ gnt_sound_handle, PURPLE_CALLBACK(chat_buddy_left_cb),
+ GINT_TO_POINTER(PURPLE_SOUND_CHAT_LEAVE));
+ purple_signal_connect(conv_handle, "sent-chat-msg",
+ gnt_sound_handle, PURPLE_CALLBACK(chat_msg_sent_cb),
+ GINT_TO_POINTER(PURPLE_SOUND_CHAT_YOU_SAY));
+ purple_signal_connect(conv_handle, "received-chat-msg",
+ gnt_sound_handle, PURPLE_CALLBACK(chat_msg_received_cb),
+ GINT_TO_POINTER(PURPLE_SOUND_CHAT_SAY));
}
-GList *finch_sound_get_profiles(void)
+static void
+finch_sound_uninit(void)
{
- return NULL;
+#ifdef USE_GSTREAMER
+ if (!gst_init_failed)
+ gst_deinit();
+#endif
+
+ purple_signals_disconnect_by_handle(finch_sound_get_handle());
}
-PurpleSoundUiOps *finch_sound_get_ui_ops(void)
+#ifdef USE_GSTREAMER
+static gboolean
+bus_call (GstBus *bus, GstMessage *msg, gpointer data)
{
- return NULL;
+ GstElement *play = data;
+ GError *err = NULL;
+
+ switch (GST_MESSAGE_TYPE (msg)) {
+ case GST_MESSAGE_EOS:
+ gst_element_set_state(play, GST_STATE_NULL);
+ gst_object_unref(GST_OBJECT(play));
+ break;
+ case GST_MESSAGE_ERROR:
+ gst_message_parse_error(msg, &err, NULL);
+ purple_debug_error("gstreamer", "%s\n", err->message);
+ g_error_free(err);
+ break;
+ case GST_MESSAGE_WARNING:
+ gst_message_parse_warning(msg, &err, NULL);
+ purple_debug_warning("gstreamer", "%s\n", err->message);
+ g_error_free(err);
+ break;
+ default:
+ break;
+ }
+ return TRUE;
}
+#endif
+
+static void
+finch_sound_play_file(const char *filename)
+{
+ const char *method;
+#ifdef USE_GSTREAMER
+ float volume;
+ char *uri;
+ GstElement *sink = NULL;
+ GstElement *play = NULL;
+ GstBus *bus = NULL;
+#endif
+ if (purple_prefs_get_bool(make_pref("/mute")))
+ return;
+
+ method = purple_prefs_get_string(make_pref("/method"));
+
+ if (!strcmp(method, "none")) {
+ return;
+ } else if (!strcmp(method, "beep")) {
+ beep();
+ return;
+ }
+
+ if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
+ purple_debug_error("gntsound", "sound file (%s) does not exist.\n", filename);
+ return;
+ }
+
+#ifndef _WIN32
+ if (!strcmp(method, "custom")) {
+ const char *sound_cmd;
+ char *command;
+ char *esc_filename;
+ GError *error = NULL;
+
+ sound_cmd = purple_prefs_get_path(make_pref("/command"));
+
+ if (!sound_cmd || *sound_cmd == '\0') {
+ purple_debug_error("gntsound",
+ "'Command' sound method has been chosen, "
+ "but no command has been set.");
+ return;
+ }
+
+ esc_filename = g_shell_quote(filename);
+
+ if (strstr(sound_cmd, "%s"))
+ command = purple_strreplace(sound_cmd, "%s", esc_filename);
+ else
+ command = g_strdup_printf("%s %s", sound_cmd, esc_filename);
+
+ if (!g_spawn_command_line_async(command, &error)) {
+ purple_debug_error("gntsound", "sound command could not be launched: %s\n", error->message);
+ g_error_free(error);
+ }
+
+ g_free(esc_filename);
+ g_free(command);
+ return;
+ }
+#ifdef USE_GSTREAMER
+ if (gst_init_failed) /* Perhaps do beep instead? */
+ return;
+ volume = (float)(CLAMP(purple_prefs_get_int(make_pref("/volume")), 0, 100)) / 50;
+ if (!strcmp(method, "automatic")) {
+ if (purple_running_gnome()) {
+ sink = gst_element_factory_make("gconfaudiosink", "sink");
+ }
+ if (!sink)
+ sink = gst_element_factory_make("autoaudiosink", "sink");
+ if (!sink) {
+ purple_debug_error("sound", "Unable to create GStreamer audiosink.\n");
+ return;
+ }
+ } else if (!strcmp(method, "esd")) {
+ sink = gst_element_factory_make("esdsink", "sink");
+ if (!sink) {
+ purple_debug_error("sound", "Unable to create GStreamer audiosink.\n");
+ return;
+ }
+ } else if (!strcmp(method, "alsa")) {
+ sink = gst_element_factory_make("alsasink", "sink");
+ if (!sink) {
+ purple_debug_error("sound", "Unable to create GStreamer audiosink.\n");
+ return;
+ }
+ } else {
+ purple_debug_error("sound", "Unknown sound method '%s'\n", method);
+ return;
+ }
+
+ play = gst_element_factory_make("playbin", "play");
+
+ if (play == NULL) {
+ return;
+ }
+
+ uri = g_strdup_printf("file://%s", filename);
+
+ g_object_set(G_OBJECT(play), "uri", uri,
+ "volume", volume,
+ "audio-sink", sink, NULL);
+
+ bus = gst_pipeline_get_bus(GST_PIPELINE(play));
+ gst_bus_add_watch(bus, bus_call, play);
+
+ gst_element_set_state(play, GST_STATE_PLAYING);
+
+ gst_object_unref(bus);
+ g_free(uri);
+
+#else /* USE_GSTREAMER */
+ beep();
+ return;
+#endif /* USE_GSTREAMER */
+#else /* _WIN32 */
+ purple_debug_info("sound", "Playing %s\n", filename);
+
+ if (G_WIN32_HAVE_WIDECHAR_API ()) {
+ wchar_t *wc_filename = g_utf8_to_utf16(filename,
+ -1, NULL, NULL, NULL);
+ if (!PlaySoundW(wc_filename, NULL, SND_ASYNC | SND_FILENAME))
+ purple_debug(PURPLE_DEBUG_ERROR, "sound", "Error playing sound.\n");
+ g_free(wc_filename);
+ } else {
+ char *l_filename = g_locale_from_utf8(filename,
+ -1, NULL, NULL, NULL);
+ if (!PlaySoundA(l_filename, NULL, SND_ASYNC | SND_FILENAME))
+ purple_debug(PURPLE_DEBUG_ERROR, "sound", "Error playing sound.\n");
+ g_free(l_filename);
+ }
+#endif /* _WIN32 */
+}
+
+static void
+finch_sound_play_event(PurpleSoundEventID event)
+{
+ char *enable_pref;
+ char *file_pref;
+ if ((event == PURPLE_SOUND_BUDDY_ARRIVE) && mute_login_sounds)
+ return;
+
+ if (event >= PURPLE_NUM_SOUNDS) {
+ purple_debug_error("sound", "got request for unknown sound: %d\n", event);
+ return;
+ }
+
+ enable_pref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s",
+ finch_sound_get_active_profile(),
+ sounds[event].pref);
+ file_pref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s", finch_sound_get_active_profile(), sounds[event].pref);
+
+ /* check NULL for sounds that don't have an option, ie buddy pounce */
+ if (purple_prefs_get_bool(enable_pref)) {
+ char *filename = g_strdup(purple_prefs_get_path(file_pref));
+ if (!filename || !strlen(filename)) {
+ g_free(filename);
+ /* XXX Consider creating a constant for "sounds/purple" to be shared with Pidgin */
+ filename = g_build_filename(DATADIR, "sounds", "purple", sounds[event].def, NULL);
+ }
+
+ purple_sound_play_file(filename, NULL);
+ g_free(filename);
+ }
+
+ g_free(enable_pref);
+ g_free(file_pref);
+}
+
+GList *
+finch_sound_get_profiles()
+{
+ GList *list = NULL, *iter;
+ iter = purple_prefs_get_children_names(FINCH_PREFS_ROOT "/sound/profiles");
+ while (iter) {
+ list = g_list_append(list, g_strdup(strrchr(iter->data, '/') + 1));
+ g_free(iter->data);
+ iter = g_list_delete_link(iter, iter);
+ }
+ return list;
+}
+
+/* This will also create it if it doesn't exist */
+void
+finch_sound_set_active_profile(const char *name)
+{
+ purple_prefs_set_string(FINCH_PREFS_ROOT "/sound/actprofile", name);
+}
+
+static gboolean
+finch_sound_profile_exists(const char *name)
+{
+ gchar * tmp;
+ gboolean ret = purple_prefs_exists(tmp = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s", name));
+ g_free(tmp);
+ return ret;
+}
+
+static void
+save_cb(GntWidget *button, gpointer win)
+{
+ GList * itr;
+
+ purple_prefs_set_string(make_pref("/method"), gnt_combo_box_get_selected_data(GNT_COMBO_BOX(pref_dialog->method)));
+ purple_prefs_set_path(make_pref("/command"), gnt_entry_get_text(GNT_ENTRY(pref_dialog->command)));
+ purple_prefs_set_bool(make_pref("/conv_focus"), gnt_check_box_get_checked(GNT_CHECK_BOX(pref_dialog->conv_focus)));
+ purple_prefs_set_int("/purple/sound/while_status", GPOINTER_TO_INT(gnt_combo_box_get_selected_data(GNT_COMBO_BOX(pref_dialog->while_status))));
+ purple_prefs_set_int(make_pref("/volume"), gnt_slider_get_value(GNT_SLIDER(pref_dialog->volume)));
+
+ for (itr = gnt_tree_get_rows(GNT_TREE(pref_dialog->events)); itr; itr = itr->next) {
+ FinchSoundEvent * event = &sounds[GPOINTER_TO_INT(itr->data)];
+ char * filepref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s", finch_sound_get_active_profile(), event->pref);
+ char * boolpref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s", finch_sound_get_active_profile(), event->pref);
+ purple_prefs_set_bool(boolpref, gnt_tree_get_choice(GNT_TREE(pref_dialog->events), itr->data));
+ purple_prefs_set_path(filepref, event->file ? event->file : "");
+ g_free(filepref);
+ g_free(boolpref);
+ }
+ gnt_widget_destroy(GNT_WIDGET(win));
+}
+
+static void
+file_cb(GntFileSel *w, const char *path, const char *file, gpointer data)
+{
+ FinchSoundEvent *event = data;
+
+ g_free(event->file);
+ event->file = g_strdup(path);
+
+ gnt_tree_change_text(GNT_TREE(pref_dialog->events), GINT_TO_POINTER(event->id), 1, file);
+ gnt_tree_set_choice(GNT_TREE(pref_dialog->events), GINT_TO_POINTER(event->id), TRUE);
+
+ gnt_widget_destroy(GNT_WIDGET(w));
+}
+
+static void
+test_cb(GntWidget *button, gpointer null)
+{
+ PurpleSoundEventID id = GPOINTER_TO_INT(gnt_tree_get_selection_data(GNT_TREE(pref_dialog->events)));
+ FinchSoundEvent * event = &sounds[id];
+ char *enabled, *file, *tmpfile;
+ gboolean temp_value;
+
+ enabled = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s",
+ finch_sound_get_active_profile(), event->pref);
+ file = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s",
+ finch_sound_get_active_profile(), event->pref);
+
+ temp_value = purple_prefs_get_bool(enabled);
+ tmpfile = g_strdup(purple_prefs_get_string(file));
+
+ purple_prefs_set_string(file, event->file);
+ if (!temp_value) purple_prefs_set_bool(enabled, TRUE);
+
+ purple_sound_play_event(id, NULL);
+
+ if (!temp_value) purple_prefs_set_bool(enabled, FALSE);
+ purple_prefs_set_string(file, tmpfile);
+
+ g_free(enabled);
+ g_free(file);
+ g_free(tmpfile);
+}
+
+static void
+reset_cb(GntWidget *button, gpointer null)
+{
+ /* Don't dereference this pointer ! */
+ gpointer key = gnt_tree_get_selection_data(GNT_TREE(pref_dialog->events));
+
+ FinchSoundEvent * event = &sounds[GPOINTER_TO_INT(key)];
+ g_free(event->file);
+ event->file = NULL;
+ gnt_tree_change_text(GNT_TREE(pref_dialog->events), key, 1, _("(default)"));
+}
+
+
+static void
+choose_cb(GntWidget *button, gpointer null)
+{
+ GntWidget *w = gnt_file_sel_new();
+ GntFileSel *sel = GNT_FILE_SEL(w);
+ PurpleSoundEventID id = GPOINTER_TO_INT(gnt_tree_get_selection_data(GNT_TREE(pref_dialog->events)));
+ FinchSoundEvent * event = &sounds[id];
+ char *path = NULL;
+
+ gnt_box_set_title(GNT_BOX(w), _("Select Sound File ..."));
+ gnt_file_sel_set_current_location(sel,
+ (event && event->file) ? (path = g_path_get_dirname(event->file))
+ : purple_home_dir());
+
+ g_signal_connect_swapped(G_OBJECT(sel->cancel), "activate", G_CALLBACK(gnt_widget_destroy), sel);
+ g_signal_connect(G_OBJECT(sel), "file_selected", G_CALLBACK(file_cb), event);
+ g_signal_connect_swapped(G_OBJECT(sel), "destroy", G_CALLBACK(g_nullify_pointer), &pref_dialog->selector);
+
+ /* If there's an already open file-selector, close that one. */
+ if (pref_dialog->selector)
+ gnt_widget_destroy(pref_dialog->selector);
+
+ pref_dialog->selector = w;
+
+ gnt_widget_show(w);
+ g_free(path);
+}
+
+static void
+release_pref_dialog(GntBindable *data, gpointer null)
+{
+ GList * itr;
+ for (itr = gnt_tree_get_rows(GNT_TREE(pref_dialog->events)); itr; itr = itr->next) {
+ PurpleSoundEventID id = GPOINTER_TO_INT(itr->data);
+ FinchSoundEvent * e = &sounds[id];
+ g_free(e->file);
+ e->file = NULL;
+ }
+ if (pref_dialog->selector)
+ gnt_widget_destroy(pref_dialog->selector);
+ g_free(pref_dialog);
+ pref_dialog = NULL;
+}
+
+static void
+load_pref_window(const char * profile)
+{
+ gint i;
+
+ finch_sound_set_active_profile(profile);
+
+ gnt_combo_box_set_selected(GNT_COMBO_BOX(pref_dialog->method), (gchar *)purple_prefs_get_string(make_pref("/method")));
+
+ gnt_entry_set_text(GNT_ENTRY(pref_dialog->command), purple_prefs_get_path(make_pref("/command")));
+
+ gnt_check_box_set_checked(GNT_CHECK_BOX(pref_dialog->conv_focus), purple_prefs_get_bool(make_pref("/conv_focus")));
+
+ gnt_combo_box_set_selected(GNT_COMBO_BOX(pref_dialog->while_status), GINT_TO_POINTER(purple_prefs_get_int("/purple" "/sound/while_status")));
+
+ gnt_slider_set_value(GNT_SLIDER(pref_dialog->volume), CLAMP(purple_prefs_get_int(make_pref("/volume")), 0, 100));
+
+ for (i = 0; i < PURPLE_NUM_SOUNDS; i++) {
+ FinchSoundEvent * event = &sounds[i];
+ gchar *boolpref;
+ gchar *filepref, *basename = NULL;
+ const char * profile = finch_sound_get_active_profile();
+
+ filepref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s", profile, event->pref);
+
+ g_free(event->file);
+ event->file = g_strdup(purple_prefs_get_path(filepref));
+
+ g_free(filepref);
+ if (event->label == NULL) {
+ continue;
+ }
+
+ boolpref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s", profile, event->pref);
+
+ gnt_tree_change_text(GNT_TREE(pref_dialog->events), GINT_TO_POINTER(i), 0, event->label);
+ gnt_tree_change_text(GNT_TREE(pref_dialog->events), GINT_TO_POINTER(i), 1,
+ event->file[0] ? (basename = g_path_get_basename(event->file)) : _("(default)"));
+ g_free(basename);
+
+ gnt_tree_set_choice(GNT_TREE(pref_dialog->events), GINT_TO_POINTER(i), purple_prefs_get_bool(boolpref));
+
+ g_free(boolpref);
+ }
+
+ gnt_tree_set_selected(GNT_TREE(pref_dialog->profiles), (gchar *)finch_sound_get_active_profile());
+
+ gnt_widget_draw(pref_dialog->window);
+}
+
+static void
+reload_pref_window(const char *profile)
+{
+ if (!strcmp(profile, finch_sound_get_active_profile()))
+ return;
+ load_pref_window(profile);
+}
+
+static void
+prof_del_cb(GntWidget *button, gpointer null)
+{
+ const char * profile = gnt_entry_get_text(GNT_ENTRY(pref_dialog->new_profile));
+ gchar * pref;
+
+ if (!strcmp(profile, DEFAULT_PROFILE))
+ return;
+
+ pref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s", profile);
+ purple_prefs_remove(pref);
+ g_free(pref);
+
+ if (!strcmp(pref_dialog->original_profile, profile)) {
+ g_free(pref_dialog->original_profile);
+ pref_dialog->original_profile = g_strdup(DEFAULT_PROFILE);
+ }
+
+ if(!strcmp(profile, finch_sound_get_active_profile()))
+ reload_pref_window(DEFAULT_PROFILE);
+
+ gnt_tree_remove(GNT_TREE(pref_dialog->profiles), (gchar *) profile);
+}
+
+static void
+prof_add_cb(GntButton *button, GntEntry * entry)
+{
+ const char * profile = gnt_entry_get_text(entry);
+ GntTreeRow * row;
+ if (!finch_sound_profile_exists(profile)) {
+ gpointer key = g_strdup(profile);
+ row = gnt_tree_create_row(GNT_TREE(pref_dialog->profiles), profile);
+ gnt_tree_add_row_after(GNT_TREE(pref_dialog->profiles), key,
+ row,
+ NULL, NULL);
+ gnt_entry_set_text(entry, "");
+ gnt_tree_set_selected(GNT_TREE(pref_dialog->profiles), key);
+ finch_sound_set_active_profile(key);
+ } else
+ reload_pref_window(profile);
+}
+
+static void
+prof_load_cb(GntTree *tree, gpointer oldkey, gpointer newkey, gpointer null)
+{
+ reload_pref_window(newkey);
+}
+
+static void
+cancel_cb(GntButton *button, gpointer win)
+{
+ finch_sound_set_active_profile(pref_dialog->original_profile);
+ gnt_widget_destroy(GNT_WIDGET(win));
+}
+
+void
+finch_sounds_show_all(void)
+{
+ GntWidget *box, *tmpbox, *splitbox, *cmbox, *slider;
+ GntWidget *entry;
+ GntWidget *chkbox;
+ GntWidget *button;
+ GntWidget *label;
+ GntWidget *tree;
+ GntWidget *win;
+
+ gint i;
+ GList *itr, *list;
+
+ if (pref_dialog) {
+ gnt_window_present(pref_dialog->window);
+ return;
+ }
+
+ pref_dialog = g_new0(SoundPrefDialog, 1);
+
+ pref_dialog->original_profile = g_strdup(finch_sound_get_active_profile());
+
+ pref_dialog->window = win = gnt_window_box_new(FALSE, TRUE);
+ gnt_box_set_pad(GNT_BOX(win), 0);
+ gnt_box_set_toplevel(GNT_BOX(win), TRUE);
+ gnt_box_set_title(GNT_BOX(win), _("Sound Preferences"));
+ gnt_box_set_fill(GNT_BOX(win), TRUE);
+ gnt_box_set_alignment(GNT_BOX(win), GNT_ALIGN_MID);
+
+ /* Profiles */
+ splitbox = gnt_hbox_new(FALSE);
+ gnt_box_set_pad(GNT_BOX(splitbox), 0);
+ gnt_box_set_alignment(GNT_BOX(splitbox), GNT_ALIGN_TOP);
+
+ box = gnt_vbox_new(FALSE);
+ gnt_box_set_pad(GNT_BOX(box), 0);
+ gnt_box_add_widget(GNT_BOX(box), gnt_label_new_with_format(_("Profiles"), GNT_TEXT_FLAG_BOLD));
+ pref_dialog->profiles = tree = gnt_tree_new();
+ gnt_tree_set_hash_fns(GNT_TREE(tree), g_str_hash, g_str_equal, g_free);
+ gnt_tree_set_compare_func(GNT_TREE(tree), (GCompareFunc)g_ascii_strcasecmp);
+ g_signal_connect(G_OBJECT(tree), "selection-changed", G_CALLBACK(prof_load_cb), NULL);
+
+ itr = list = finch_sound_get_profiles();
+ for (; itr; itr = itr->next) {
+ /* Do not free itr->data. It's the stored as a key for the tree, and will
+ * be freed when the tree is destroyed. */
+ gnt_tree_add_row_after(GNT_TREE(tree), itr->data,
+ gnt_tree_create_row(GNT_TREE(tree), itr->data), NULL, NULL);
+ }
+ g_list_free(list);
+
+ gnt_box_add_widget(GNT_BOX(box), tree);
+
+ pref_dialog->new_profile = entry = gnt_entry_new("");
+ gnt_box_add_widget(GNT_BOX(box), entry);
+
+ tmpbox = gnt_hbox_new(FALSE);
+ button = gnt_button_new("Add");
+ g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(prof_add_cb), entry);
+ gnt_box_add_widget(GNT_BOX(tmpbox), button);
+ button = gnt_button_new("Delete");
+ g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(prof_del_cb), NULL);
+ gnt_box_add_widget(GNT_BOX(tmpbox), button);
+ gnt_box_add_widget(GNT_BOX(box), tmpbox);
+ gnt_box_add_widget(GNT_BOX(splitbox), box);
+
+ gnt_box_add_widget(GNT_BOX(splitbox), gnt_vline_new());
+
+ /* Sound method */
+
+ box = gnt_vbox_new(FALSE);
+ gnt_box_set_pad(GNT_BOX(box), 0);
+
+ pref_dialog->method = cmbox = gnt_combo_box_new();
+ gnt_tree_set_hash_fns(GNT_TREE(GNT_COMBO_BOX(cmbox)->dropdown), g_str_hash, g_str_equal, NULL);
+ gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "automatic", _("Automatic"));
+ gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "alsa", "ALSA");
+ gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "esd", "ESD");
+ gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "beep", _("Console Beep"));
+ gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "custom", _("Command"));
+ gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "nosound", _("No Sound"));
+
+ label = gnt_label_new_with_format(_("Sound Method"), GNT_TEXT_FLAG_BOLD);
+ gnt_box_add_widget(GNT_BOX(box), label);
+ tmpbox = gnt_hbox_new(TRUE);
+ gnt_box_set_fill(GNT_BOX(tmpbox), FALSE);
+ gnt_box_set_pad(GNT_BOX(tmpbox), 0);
+ gnt_box_add_widget(GNT_BOX(tmpbox), gnt_label_new(_("Method: ")));
+ gnt_box_add_widget(GNT_BOX(tmpbox), cmbox);
+ gnt_box_add_widget(GNT_BOX(box), tmpbox);
+
+ tmpbox = gnt_hbox_new(TRUE);
+ gnt_box_set_pad(GNT_BOX(tmpbox), 0);
+ gnt_box_set_fill(GNT_BOX(tmpbox), FALSE);
+ gnt_box_add_widget(GNT_BOX(tmpbox), gnt_label_new(_("Sound Command\n(%s for filename)")));
+ pref_dialog->command = entry = gnt_entry_new("");
+ gnt_box_add_widget(GNT_BOX(tmpbox), entry);
+ gnt_box_add_widget(GNT_BOX(box), tmpbox);
+
+ gnt_box_add_widget(GNT_BOX(box), gnt_line_new(FALSE));
+
+ /* Sound options */
+ gnt_box_add_widget(GNT_BOX(box), gnt_label_new_with_format(_("Sound Options"), GNT_TEXT_FLAG_BOLD));
+ pref_dialog->conv_focus = chkbox = gnt_check_box_new(_("Sounds when conversation has focus"));
+ gnt_box_add_widget(GNT_BOX(box), chkbox);
+
+ tmpbox = gnt_hbox_new(TRUE);
+ gnt_box_set_pad(GNT_BOX(tmpbox), 0);
+ gnt_box_set_fill(GNT_BOX(tmpbox), FALSE);
+ gnt_box_add_widget(GNT_BOX(tmpbox), gnt_label_new("Enable Sounds:"));
+ pref_dialog->while_status = cmbox = gnt_combo_box_new();
+ gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), GINT_TO_POINTER(3), _("Always"));
+ gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), GINT_TO_POINTER(1), _("Only when available"));
+ gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), GINT_TO_POINTER(2), _("Only when not available"));
+ gnt_box_add_widget(GNT_BOX(tmpbox), cmbox);
+ gnt_box_add_widget(GNT_BOX(box), tmpbox);
+
+ tmpbox = gnt_hbox_new(TRUE);
+ gnt_box_set_pad(GNT_BOX(tmpbox), 0);
+ gnt_box_set_fill(GNT_BOX(tmpbox), FALSE);
+ gnt_box_add_widget(GNT_BOX(tmpbox), gnt_label_new(_("Volume(0-100):")));
+
+ pref_dialog->volume = slider = gnt_slider_new(FALSE, 100, 0);
+ gnt_slider_set_step(GNT_SLIDER(slider), 5);
+ label = gnt_label_new("");
+ gnt_slider_reflect_label(GNT_SLIDER(slider), GNT_LABEL(label));
+ gnt_box_set_pad(GNT_BOX(tmpbox), 1);
+ gnt_box_add_widget(GNT_BOX(tmpbox), slider);
+ gnt_box_add_widget(GNT_BOX(tmpbox), label);
+ gnt_box_add_widget(GNT_BOX(box), tmpbox);
+ gnt_box_add_widget(GNT_BOX(splitbox), box);
+
+ gnt_box_add_widget(GNT_BOX(win), splitbox);
+
+ gnt_box_add_widget(GNT_BOX(win), gnt_hline_new());
+
+ /* Sound events */
+ gnt_box_add_widget(GNT_BOX(win), gnt_label_new_with_format(_("Sound Events"), GNT_TEXT_FLAG_BOLD));
+ pref_dialog->events = tree = gnt_tree_new_with_columns(2);
+ gnt_tree_set_column_titles(GNT_TREE(tree), _("Event"), _("File"));
+ gnt_tree_set_show_title(GNT_TREE(tree), TRUE);
+
+ for (i = 0; i < PURPLE_NUM_SOUNDS; i++) {
+ FinchSoundEvent * event = &sounds[i];
+
+ if (event->label == NULL) {
+ continue;
+ }
+
+ gnt_tree_add_choice(GNT_TREE(tree), GINT_TO_POINTER(i),
+ gnt_tree_create_row(GNT_TREE(tree), "", ""),
+ NULL, NULL);
+ }
+
+ gnt_tree_adjust_columns(GNT_TREE(tree));
+ gnt_box_add_widget(GNT_BOX(win), tree);
+
+ box = gnt_hbox_new(FALSE);
+ button = gnt_button_new(_("Test"));
+ g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(test_cb), NULL);
+ gnt_box_add_widget(GNT_BOX(box), button);
+ button = gnt_button_new(_("Reset"));
+ g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(reset_cb), NULL);
+ gnt_box_add_widget(GNT_BOX(box), button);
+ button = gnt_button_new(_("Choose..."));
+ g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(choose_cb), NULL);
+ gnt_box_add_widget(GNT_BOX(box), button);
+ gnt_box_add_widget(GNT_BOX(win), box);
+
+ gnt_box_add_widget(GNT_BOX(win), gnt_line_new(FALSE));
+
+ /* Add new stuff before this */
+ box = gnt_hbox_new(FALSE);
+ gnt_box_set_fill(GNT_BOX(box), TRUE);
+ button = gnt_button_new(_("Save"));
+ g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(save_cb), win);
+ gnt_box_add_widget(GNT_BOX(box), button);
+ button = gnt_button_new(_("Cancel"));
+ g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(cancel_cb), win);
+ gnt_box_add_widget(GNT_BOX(box), button);
+ gnt_box_add_widget(GNT_BOX(win), box);
+
+ g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(release_pref_dialog), NULL);
+
+ load_pref_window(finch_sound_get_active_profile());
+
+ gnt_widget_show(win);
+}
+
+static PurpleSoundUiOps sound_ui_ops =
+{
+ finch_sound_init,
+ finch_sound_uninit,
+ finch_sound_play_file,
+ finch_sound_play_event,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
-void finch_sounds_show_all(void)
+PurpleSoundUiOps *
+finch_sound_get_ui_ops(void)
{
+ return &sound_ui_ops;
}
diff --git a/finch/gntui.c b/finch/gntui.c
index 429c0349ae..c69b2c7949 100644
--- a/finch/gntui.c
+++ b/finch/gntui.c
@@ -35,6 +35,7 @@
#include "gntprefs.h"
#include "gntrequest.h"
#include "gntstatus.h"
+#include "gntsound.h"
#include <prefs.h>
@@ -58,6 +59,9 @@ void gnt_ui_init()
finch_blist_init();
purple_blist_set_ui_ops(finch_blist_get_ui_ops());
+ /* Initialize sound */
+ purple_sound_set_ui_ops(finch_sound_get_ui_ops());
+
/* Now the conversations */
finch_conversation_init();
purple_conversations_set_ui_ops(finch_conv_get_ui_ops());
@@ -80,6 +84,7 @@ void gnt_ui_init()
gnt_register_action(_("Debug Window"), finch_debug_window_show);
gnt_register_action(_("File Transfers"), finch_xfer_dialog_show);
gnt_register_action(_("Plugins"), finch_plugins_show_all);
+ gnt_register_action(_("Sounds"), finch_sounds_show_all);
gnt_register_action(_("Preferences"), finch_prefs_show_all);
gnt_register_action(_("Statuses"), finch_savedstatus_show_all);
diff --git a/finch/libgnt/configure.ac b/finch/libgnt/configure.ac
index 7fa570f703..d6e1048b9d 100644
--- a/finch/libgnt/configure.ac
+++ b/finch/libgnt/configure.ac
@@ -26,7 +26,7 @@ AC_PREREQ([2.50])
m4_define([gnt_lt_current], [1])
m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [0])
+m4_define([gnt_minor_version], [1])
m4_define([gnt_micro_version], [0])
m4_define([gnt_version_suffix], [devel])
m4_define([gnt_version],
@@ -36,6 +36,7 @@ m4_define([gnt_display_version], gnt_version[]m4_ifdef([gnt_version_suffix],[gnt
AC_INIT([libgnt], [gnt_display_version], [devel@pidgin.im])
AC_CANONICAL_SYSTEM
AM_CONFIG_HEADER(config.h)
+AC_CONFIG_AUX_DIR([.])
AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION)
GNT_MAJOR_VERSION=gnt_major_version
@@ -314,6 +315,8 @@ if test "x$have_libxml" = "xno"; then
AC_DEFINE(NO_LIBXML, 1, [Do not have libxml2.])
fi
+AM_CONDITIONAL(PURPLE_AVAILABLE, false)
+
AC_OUTPUT([Makefile
gnt.pc
wms/Makefile
diff --git a/finch/libgnt/gnt.h b/finch/libgnt/gnt.h
index ea47b0945d..5828665276 100644
--- a/finch/libgnt/gnt.h
+++ b/finch/libgnt/gnt.h
@@ -157,4 +157,3 @@ gboolean gnt_giveup_console(const char *wd, char **argv, char **envp,
void (*callback)(int status, gpointer data), gpointer data);
gboolean gnt_is_refugee(void);
-
diff --git a/finch/libgnt/gntbindable.c b/finch/libgnt/gntbindable.c
index 374fa5ffd1..54c3868056 100644
--- a/finch/libgnt/gntbindable.c
+++ b/finch/libgnt/gntbindable.c
@@ -20,13 +20,182 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+#include <string.h>
+
#include "gntbindable.h"
#include "gntstyle.h"
#include "gnt.h"
#include "gntutils.h"
+#include "gnttextview.h"
+#include "gnttree.h"
+#include "gntbox.h"
+#include "gntbutton.h"
+#include "gntwindow.h"
+#include "gntlabel.h"
static GObjectClass *parent_class = NULL;
+static struct
+{
+ char * okeys; /* Old keystrokes */
+ char * keys; /* New Keystrokes being bound to the action */
+ GntBindableClass * klass; /* Class of the object that's getting keys rebound */
+ char * name; /* The name of the action */
+ GList * params; /* The list of paramaters */
+} rebind_info;
+
+static void
+gnt_bindable_free_rebind_info()
+{
+ g_free(rebind_info.name);
+ g_free(rebind_info.keys);
+ g_free(rebind_info.okeys);
+}
+
+static void
+gnt_bindable_rebinding_cancel(GntWidget *button, gpointer data)
+{
+ gnt_bindable_free_rebind_info();
+ gnt_widget_destroy(GNT_WIDGET(data));
+}
+
+static void
+gnt_bindable_rebinding_rebind(GntWidget *button, gpointer data)
+{
+ if (rebind_info.keys) {
+ gnt_bindable_register_binding(rebind_info.klass,
+ NULL,
+ rebind_info.okeys,
+ rebind_info.params);
+ gnt_bindable_register_binding(rebind_info.klass,
+ rebind_info.name,
+ rebind_info.keys,
+ rebind_info.params);
+ }
+ gnt_bindable_free_rebind_info();
+ gnt_widget_destroy(GNT_WIDGET(data));
+}
+
+static gboolean
+gnt_bindable_rebinding_grab_key(GntBindable *bindable, const char *text, gpointer data)
+{
+ GntTextView *textview = GNT_TEXT_VIEW(data);
+ char *new_text;
+ const char *tmp;
+
+ if (text && *text) {
+ /* Rebinding tab or enter for something is probably not that great an idea */
+ if (!strcmp(text, GNT_KEY_CTRL_I) || !strcmp(text, GNT_KEY_ENTER)) {
+ return FALSE;
+ }
+
+ tmp = gnt_key_lookup(text);
+ new_text = g_strdup_printf("KEY: \"%s\"", tmp);
+ gnt_text_view_clear(textview);
+ gnt_text_view_append_text_with_flags(textview, new_text, GNT_TEXT_FLAG_NORMAL);
+ g_free(new_text);
+
+ g_free(rebind_info.keys);
+ rebind_info.keys = g_strdup(text);
+
+ return TRUE;
+ }
+ return FALSE;
+}
+static void
+gnt_bindable_rebinding_activate(GntBindable *data, gpointer bindable)
+{
+ const char *widget_name = g_type_name(G_OBJECT_TYPE(bindable));
+ char *keys;
+ GntWidget *key_textview;
+ GntWidget *label;
+ GntWidget *bind_button, *cancel_button;
+ GntWidget *button_box;
+ GList *current_row_data;
+ char *tmp;
+ GntWidget *win = gnt_window_new();
+ GntTree *tree = GNT_TREE(data);
+ GntWidget *vbox = gnt_box_new(FALSE, TRUE);
+
+ rebind_info.klass = GNT_BINDABLE_GET_CLASS(bindable);
+
+ current_row_data = gnt_tree_get_selection_text_list(tree);
+ rebind_info.name = g_strdup(g_list_nth_data(current_row_data, 1));
+
+ keys = gnt_tree_get_selection_data(tree);
+ rebind_info.okeys = g_strdup(gnt_key_translate(keys));
+
+ rebind_info.params = NULL;
+
+ g_list_foreach(current_row_data, (GFunc)g_free, NULL);
+ g_list_free(current_row_data);
+
+ gnt_box_set_alignment(GNT_BOX(vbox), GNT_ALIGN_MID);
+
+ gnt_box_set_title(GNT_BOX(win), "Key Capture");
+
+ tmp = g_strdup_printf("Type the new bindings for %s in a %s.", rebind_info.name, widget_name);
+ label = gnt_label_new(tmp);
+ g_free(tmp);
+ gnt_box_add_widget(GNT_BOX(vbox), label);
+
+ tmp = g_strdup_printf("KEY: \"%s\"", keys);
+ key_textview = gnt_text_view_new();
+ gnt_widget_set_size(key_textview, key_textview->priv.x, 2);
+ gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(key_textview), tmp, GNT_TEXT_FLAG_NORMAL);
+ g_free(tmp);
+ gnt_widget_set_name(key_textview, "keystroke");
+ gnt_box_add_widget(GNT_BOX(vbox), key_textview);
+
+ g_signal_connect(G_OBJECT(win), "key_pressed", G_CALLBACK(gnt_bindable_rebinding_grab_key), key_textview);
+
+ button_box = gnt_box_new(FALSE, FALSE);
+
+ bind_button = gnt_button_new("BIND");
+ gnt_widget_set_name(bind_button, "bind");
+ gnt_box_add_widget(GNT_BOX(button_box), bind_button);
+
+ cancel_button = gnt_button_new("Cancel");
+ gnt_widget_set_name(cancel_button, "cancel");
+ gnt_box_add_widget(GNT_BOX(button_box), cancel_button);
+
+ g_signal_connect(G_OBJECT(bind_button), "activate", G_CALLBACK(gnt_bindable_rebinding_rebind), win);
+ g_signal_connect(G_OBJECT(cancel_button), "activate", G_CALLBACK(gnt_bindable_rebinding_cancel), win);
+
+ gnt_box_add_widget(GNT_BOX(vbox), button_box);
+
+ gnt_box_add_widget(GNT_BOX(win), vbox);
+ gnt_widget_show(win);
+}
+
+typedef struct
+{
+ GHashTable *hash;
+ GntTree *tree;
+} BindingView;
+
+static void
+add_binding(gpointer key, gpointer value, gpointer data)
+{
+ BindingView *bv = data;
+ GntBindableActionParam *act = value;
+ const char *name = g_hash_table_lookup(bv->hash, act->action);
+ if (name && *name) {
+ const char *k = gnt_key_lookup(key);
+ if (!k)
+ k = key;
+ gnt_tree_add_row_after(bv->tree, (gpointer)k,
+ gnt_tree_create_row(bv->tree, k, name), NULL, NULL);
+ }
+}
+
+static void
+add_action(gpointer key, gpointer value, gpointer data)
+{
+ BindingView *bv = data;
+ g_hash_table_insert(bv->hash, value, key);
+}
+
static void
gnt_bindable_class_init(GntBindableClass *klass)
{
@@ -88,7 +257,7 @@ gnt_bindable_get_gtype(void)
{
static GType type = 0;
- if(type == 0) {
+ if (type == 0) {
static const GTypeInfo info = {
sizeof(GntBindableClass),
(GBaseInitFunc)duplicate_hashes, /* base_init */
@@ -184,7 +353,7 @@ register_binding(GntBindableClass *klass, const char *name, const char *trigger,
action = g_hash_table_lookup(klass->actions, name);
if (!action) {
- g_printerr("GntWidget: Invalid action name %s for %s\n",
+ g_printerr("GntBindable: Invalid action name %s for %s\n",
name, g_type_name(G_OBJECT_CLASS_TYPE(klass)));
if (list)
g_list_free(list);
@@ -251,4 +420,55 @@ void gnt_bindable_action_param_free(GntBindableActionParam *param)
g_free(param);
}
+GntBindable * gnt_bindable_bindings_view(GntBindable *bind)
+{
+ GntBindable *tree = GNT_BINDABLE(gnt_tree_new_with_columns(2));
+ GntBindableClass *klass = GNT_BINDABLE_CLASS(GNT_BINDABLE_GET_CLASS(bind));
+ GHashTable *hash = g_hash_table_new(g_direct_hash, g_direct_equal);
+ BindingView bv = {hash, GNT_TREE(tree)};
+
+ gnt_tree_set_compare_func(bv.tree, (GCompareFunc)g_utf8_collate);
+ g_hash_table_foreach(klass->actions, add_action, &bv);
+ g_hash_table_foreach(klass->bindings, add_binding, &bv);
+ if (GNT_TREE(tree)->list == NULL) {
+ gnt_widget_destroy(GNT_WIDGET(tree));
+ tree = NULL;
+ } else
+ gnt_tree_adjust_columns(bv.tree);
+ g_hash_table_destroy(hash);
+
+ return tree;
+}
+
+static void
+reset_binding_window(GntBindableClass *window, gpointer k)
+{
+ GntBindableClass *klass = GNT_BINDABLE_CLASS(k);
+ klass->help_window = NULL;
+}
+
+gboolean
+gnt_bindable_build_help_window(GntBindable *bindable)
+{
+ GntWidget *tree;
+ GntBindableClass *klass = GNT_BINDABLE_GET_CLASS(bindable);
+ char *title;
+
+ tree = GNT_WIDGET(gnt_bindable_bindings_view(bindable));
+
+ klass->help_window = GNT_BINDABLE(gnt_window_new());
+ title = g_strdup_printf("Bindings for %s", g_type_name(G_OBJECT_TYPE(bindable)));
+ gnt_box_set_title(GNT_BOX(klass->help_window), title);
+ if (tree) {
+ g_signal_connect(G_OBJECT(tree), "activate", G_CALLBACK(gnt_bindable_rebinding_activate), bindable);
+ gnt_box_add_widget(GNT_BOX(klass->help_window), tree);
+ } else
+ gnt_box_add_widget(GNT_BOX(klass->help_window), gnt_label_new("This widget has no customizable bindings."));
+
+ g_signal_connect(G_OBJECT(klass->help_window), "destroy", G_CALLBACK(reset_binding_window), klass);
+ gnt_widget_show(GNT_WIDGET(klass->help_window));
+ g_free(title);
+
+ return TRUE;
+}
diff --git a/finch/libgnt/gntbindable.h b/finch/libgnt/gntbindable.h
index 3d23a46e33..28fc89796d 100644
--- a/finch/libgnt/gntbindable.h
+++ b/finch/libgnt/gntbindable.h
@@ -57,7 +57,8 @@ struct _GntBindableClass
GHashTable *actions; /* name -> Action */
GHashTable *bindings; /* key -> ActionParam */
- void (*gnt_reserved1)(void);
+ GntBindable * help_window;
+
void (*gnt_reserved2)(void);
void (*gnt_reserved3)(void);
void (*gnt_reserved4)(void);
@@ -150,6 +151,31 @@ gboolean gnt_bindable_perform_action_key(GntBindable *bindable, const char *keys
*/
gboolean gnt_bindable_perform_action_named(GntBindable *bindable, const char *name, ...);
+/**
+* Returns a GntTree populated with "key" -> "binding" for the widget.
+*/
+/**
+*
+* @param widget
+*
+* @return
+*/
+GntBindable * gnt_bindable_bindings_view(GntBindable *bind);
+
+/**
+ *
+ * Builds a window that list the key bindings for a GntBindable object. From this window a user can select a listing to rebind a new key for the given action.
+ *
+ */
+/**
+ *
+ * @param bindable
+ *
+ * @return
+ */
+
+gboolean gnt_bindable_build_help_window(GntBindable *bindable);
+
G_END_DECLS
#endif /* GNT_BINDABLE_H */
diff --git a/finch/libgnt/gntcombobox.h b/finch/libgnt/gntcombobox.h
index 1920f49f2e..65e2b3fc87 100644
--- a/finch/libgnt/gntcombobox.h
+++ b/finch/libgnt/gntcombobox.h
@@ -69,7 +69,8 @@ struct _GntComboBoxClass
G_BEGIN_DECLS
/**
- *
+ *
+ * Get the GType for GntComboBox
*
* @return
*/
@@ -77,44 +78,55 @@ GType gnt_combo_box_get_gtype(void);
/**
*
+ * Create a new GntComboBox
*
- * @return
+ * @return A new GntComboBox
*/
GntWidget * gnt_combo_box_new(void);
/**
*
- * @param box
- * @param key
- * @param text
+ * Add an entry
+ *
+ * @param box The GntComboBox
+ * @param key The data
+ * @param text The text to display
*/
void gnt_combo_box_add_data(GntComboBox *box, gpointer key, const char *text);
/**
+ *
+ * Remove an entry
*
- * @param box
- * @param key
+ * @param box The GntComboBox
+ * @param key The data to be removed
*/
void gnt_combo_box_remove(GntComboBox *box, gpointer key);
/**
*
- * @param box
+ * Remove all entries
+ *
+ * @param box The GntComboBox
*/
void gnt_combo_box_remove_all(GntComboBox *box);
/**
*
- * @param box
+ * Get the data that is currently selected
*
- * @return
+ * @param box The GntComboBox
+ *
+ * @return The data of the currently selected entry
*/
gpointer gnt_combo_box_get_selected_data(GntComboBox *box);
/**
*
- * @param box
- * @param key
+ * Set the current selection to a specific entry
+ *
+ * @param box The GntComboBox
+ * @param key The data to be set to
*/
void gnt_combo_box_set_selected(GntComboBox *box, gpointer key);
diff --git a/finch/libgnt/gntfilesel.c b/finch/libgnt/gntfilesel.c
index 8382be560e..ed5e702e94 100644
--- a/finch/libgnt/gntfilesel.c
+++ b/finch/libgnt/gntfilesel.c
@@ -355,6 +355,7 @@ dir_key_pressed(GntTree *tree, const char *key, GntFileSel *sel)
} else if (strcmp(str, "..") == 0) {
gnt_tree_set_selected(tree, dir);
}
+ gnt_bindable_perform_action_named(GNT_BINDABLE(tree), "end-search", NULL);
g_free(dir);
g_free(str);
g_free(path);
@@ -495,10 +496,11 @@ toggle_tag_selection(GntBindable *bind, GList *null)
if (!sel->multiselect)
return FALSE;
tree = sel->dirsonly ? sel->dirs : sel->files;
- if (!gnt_widget_has_focus(tree))
+ if (!gnt_widget_has_focus(tree) ||
+ gnt_tree_is_searching(GNT_TREE(tree)))
return FALSE;
- file = gnt_tree_get_selection_data(sel->dirsonly ? GNT_TREE(sel->dirs) : GNT_TREE(sel->files));
+ file = gnt_tree_get_selection_data(GNT_TREE(tree));
str = gnt_file_sel_get_selected_file(sel);
if ((find = g_list_find_custom(sel->tags, str, (GCompareFunc)g_utf8_collate)) != NULL) {
@@ -526,7 +528,8 @@ clear_tags(GntBindable *bind, GList *null)
if (!sel->multiselect)
return FALSE;
tree = sel->dirsonly ? sel->dirs : sel->files;
- if (!gnt_widget_has_focus(tree))
+ if (!gnt_widget_has_focus(tree) ||
+ gnt_tree_is_searching(GNT_TREE(tree)))
return FALSE;
g_list_foreach(sel->tags, (GFunc)g_free, NULL);
@@ -547,6 +550,9 @@ up_directory(GntBindable *bind, GList *null)
if (!gnt_widget_has_focus(sel->dirs) &&
!gnt_widget_has_focus(sel->files))
return FALSE;
+ if (gnt_tree_is_searching(GNT_TREE(sel->dirs)) ||
+ gnt_tree_is_searching(GNT_TREE(sel->files)))
+ return FALSE;
path = g_build_filename(sel->current, "..", NULL);
dir = g_path_get_basename(sel->current);
diff --git a/finch/libgnt/gntline.c b/finch/libgnt/gntline.c
index d2d079b043..626f907539 100644
--- a/finch/libgnt/gntline.c
+++ b/finch/libgnt/gntline.c
@@ -24,6 +24,12 @@
enum
{
+ PROP_0,
+ PROP_VERTICAL
+};
+
+enum
+{
SIGS = 1,
};
@@ -65,14 +71,57 @@ gnt_line_map(GntWidget *widget)
}
static void
+gnt_line_set_property(GObject *obj, guint prop_id, const GValue *value,
+ GParamSpec *spec)
+{
+ GntLine *line = GNT_LINE(obj);
+ switch (prop_id) {
+ case PROP_VERTICAL:
+ line->vertical = g_value_get_boolean(value);
+ if (line->vertical) {
+ GNT_WIDGET_SET_FLAGS(line, GNT_WIDGET_GROW_Y);
+ } else {
+ GNT_WIDGET_SET_FLAGS(line, GNT_WIDGET_GROW_X);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+gnt_line_get_property(GObject *obj, guint prop_id, GValue *value,
+ GParamSpec *spec)
+{
+ GntLine *line = GNT_LINE(obj);
+ switch (prop_id) {
+ case PROP_VERTICAL:
+ g_value_set_boolean(value, line->vertical);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
gnt_line_class_init(GntLineClass *klass)
{
+ GObjectClass *gclass = G_OBJECT_CLASS(klass);
parent_class = GNT_WIDGET_CLASS(klass);
parent_class->draw = gnt_line_draw;
parent_class->map = gnt_line_map;
parent_class->size_request = gnt_line_size_request;
- GNTDEBUG;
+ gclass->set_property = gnt_line_set_property;
+ gclass->get_property = gnt_line_get_property;
+ g_object_class_install_property(gclass,
+ PROP_VERTICAL,
+ g_param_spec_boolean("vertical", "Vertical",
+ "Whether it's a vertical line or a horizontal one.",
+ TRUE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
+ )
+ );
}
static void
@@ -118,20 +167,7 @@ gnt_line_get_gtype(void)
GntWidget *gnt_line_new(gboolean vertical)
{
- GntWidget *widget = g_object_new(GNT_TYPE_LINE, NULL);
- GntLine *line = GNT_LINE(widget);
-
- line->vertical = vertical;
-
- if (vertical)
- {
- GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_Y);
- }
- else
- {
- GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X);
- }
-
+ GntWidget *widget = g_object_new(GNT_TYPE_LINE, "vertical", vertical, NULL);
return widget;
}
diff --git a/finch/libgnt/gntmain.c b/finch/libgnt/gntmain.c
index 8d0932ff31..3e0d1c2583 100644
--- a/finch/libgnt/gntmain.c
+++ b/finch/libgnt/gntmain.c
@@ -252,6 +252,18 @@ io_invoke(GIOChannel *source, GIOCondition cond, gpointer null)
if (HOLDING_ESCAPE)
keys[0] = '\033';
k = keys;
+
+#if 0
+ /* I am not sure what's happening here. If this actually does something,
+ * then this needs to go in gnt_keys_refine. */
+ if (*k < 0) { /* Alt not sending ESC* */
+ *(k + 1) = 128 - *k;
+ *k = 27;
+ *(k + 2) = 0;
+ rd++;
+ }
+#endif
+
while (rd) {
char back;
int p;
@@ -517,7 +529,8 @@ void gnt_screen_occupy(GntWidget *widget)
void gnt_screen_release(GntWidget *widget)
{
- gnt_wm_window_close(wm, widget);
+ if (wm)
+ gnt_wm_window_close(wm, widget);
}
void gnt_screen_update(GntWidget *widget)
@@ -564,7 +577,9 @@ void gnt_widget_set_urgent(GntWidget *widget)
void gnt_quit()
{
- g_hash_table_destroy(wm->nodes); /* XXX: */
+ g_object_unref(G_OBJECT(wm));
+ wm = NULL;
+
update_panels();
doupdate();
gnt_uninit_colors();
diff --git a/finch/libgnt/gntmenu.c b/finch/libgnt/gntmenu.c
index ba3da675c9..9e11bd043f 100644
--- a/finch/libgnt/gntmenu.c
+++ b/finch/libgnt/gntmenu.c
@@ -247,11 +247,14 @@ gnt_menu_key_pressed(GntWidget *widget, const char *text)
int current = menu->selected;
if (menu->submenu) {
- do menu = menu->submenu; while (menu->submenu);
- return (gnt_widget_key_pressed(GNT_WIDGET(menu), text));
+ GntMenu *sub = menu;
+ do sub = sub->submenu; while (sub->submenu);
+ if (gnt_widget_key_pressed(GNT_WIDGET(sub), text))
+ return TRUE;
}
- if (text[0] == 27 && text[1] == 0) {
+ if ((text[0] == 27 && text[1] == 0) ||
+ (menu->type != GNT_MENU_TOPLEVEL && strcmp(text, GNT_KEY_LEFT) == 0)) {
/* Escape closes menu */
GntMenu *par = menu->parentmenu;
if (par != NULL) {
@@ -271,11 +274,17 @@ gnt_menu_key_pressed(GntWidget *widget, const char *text)
menu->selected++;
if (menu->selected >= g_list_length(menu->list))
menu->selected = 0;
- } else if (strcmp(text, GNT_KEY_ENTER) == 0) {
+ } else if (strcmp(text, GNT_KEY_ENTER) == 0 ||
+ strcmp(text, GNT_KEY_DOWN) == 0) {
gnt_widget_activate(widget);
}
if (current != menu->selected) {
+ GntMenu *sub = menu->submenu;
+ while (sub) {
+ gnt_widget_hide(GNT_WIDGET(sub));
+ sub = sub->submenu;
+ }
gnt_widget_draw(widget);
return TRUE;
}
@@ -283,6 +292,12 @@ gnt_menu_key_pressed(GntWidget *widget, const char *text)
if (text[1] == '\0') {
if (check_for_trigger(menu, text[0]))
return TRUE;
+ } else if (strcmp(text, GNT_KEY_RIGHT) == 0) {
+ GntMenuItem *item = gnt_tree_get_selection_data(GNT_TREE(menu));
+ if (item && item->submenu) {
+ menuitem_activate(menu, item);
+ return TRUE;
+ }
}
return org_key_pressed(widget, text);
}
diff --git a/finch/libgnt/gnttextview.c b/finch/libgnt/gnttextview.c
index 165010ddb6..74f77d2298 100644
--- a/finch/libgnt/gnttextview.c
+++ b/finch/libgnt/gnttextview.c
@@ -68,17 +68,31 @@ gnt_text_view_draw(GntWidget *widget)
int i = 0;
GList *lines;
int rows, scrcol;
+ int comp = 0; /* Used for top-aligned text */
gboolean has_scroll = !(view->flags & GNT_TEXT_VIEW_NO_SCROLL);
wbkgd(widget->window, COLOR_PAIR(GNT_COLOR_NORMAL));
werase(widget->window);
+ if ((view->flags & GNT_TEXT_VIEW_TOP_ALIGN) &&
+ g_list_length(view->list) < widget->priv.height) {
+ GList *now = view->list;
+ comp = widget->priv.height - g_list_length(view->list);
+ view->list = g_list_nth_prev(view->list, comp);
+ if (!view->list) {
+ view->list = g_list_first(now);
+ comp = widget->priv.height - g_list_length(view->list);
+ } else {
+ comp = 0;
+ }
+ }
+
for (i = 0, lines = view->list; i < widget->priv.height && lines; i++, lines = lines->next)
{
GList *iter;
GntTextLine *line = lines->data;
- wmove(widget->window, widget->priv.height - 1 - i, 0);
+ wmove(widget->window, widget->priv.height - 1 - i - comp, 0);
for (iter = line->segments; iter; iter = iter->next)
{
@@ -398,7 +412,7 @@ gnt_text_view_reflow(GntTextView *view)
static void
gnt_text_view_size_changed(GntWidget *widget, int w, int h)
{
- if (w != widget->priv.width) {
+ if (w != widget->priv.width && GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_MAPPED)) {
gnt_text_view_reflow(GNT_TEXT_VIEW(widget));
}
}
@@ -422,11 +436,16 @@ static void
gnt_text_view_init(GTypeInstance *instance, gpointer class)
{
GntWidget *widget = GNT_WIDGET(instance);
-
- GNT_WIDGET_SET_FLAGS(GNT_WIDGET(instance), GNT_WIDGET_GROW_Y | GNT_WIDGET_GROW_X);
+ GntTextView *view = GNT_TEXT_VIEW(widget);
+ GntTextLine *line = g_new0(GntTextLine, 1);
+ GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW |
+ GNT_WIDGET_GROW_Y | GNT_WIDGET_GROW_X);
widget->priv.minw = 5;
widget->priv.minh = 2;
+ view->string = g_string_new(NULL);
+ view->list = g_list_append(view->list, line);
+
GNTDEBUG;
}
@@ -464,13 +483,6 @@ gnt_text_view_get_gtype(void)
GntWidget *gnt_text_view_new()
{
GntWidget *widget = g_object_new(GNT_TYPE_TEXT_VIEW, NULL);
- GntTextView *view = GNT_TEXT_VIEW(widget);
- GntTextLine *line = g_new0(GntTextLine, 1);
-
- GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW);
-
- view->string = g_string_new(NULL);
- view->list = g_list_append(view->list, line);
return widget;
}
diff --git a/finch/libgnt/gnttextview.h b/finch/libgnt/gnttextview.h
index 80c81a7729..feeddeb11e 100644
--- a/finch/libgnt/gnttextview.h
+++ b/finch/libgnt/gnttextview.h
@@ -47,9 +47,11 @@ typedef struct _GntTextView GntTextView;
typedef struct _GntTextViewPriv GntTextViewPriv;
typedef struct _GntTextViewClass GntTextViewClass;
-typedef enum _GntTextViewFlag {
+typedef enum
+{
GNT_TEXT_VIEW_NO_SCROLL = 1 << 0,
GNT_TEXT_VIEW_WRAP_CHAR = 1 << 1,
+ GNT_TEXT_VIEW_TOP_ALIGN = 1 << 2,
} GntTextViewFlag;
struct _GntTextView
diff --git a/finch/libgnt/gnttree.c b/finch/libgnt/gnttree.c
index 268737b7c4..62a0ce07fa 100644
--- a/finch/libgnt/gnttree.c
+++ b/finch/libgnt/gnttree.c
@@ -450,7 +450,7 @@ redraw_tree(GntTree *tree)
if (COLUMN_INVISIBLE(tree, i)) {
continue;
}
- mvwaddstr(widget->window, pos, x + 1, tree->columns[i].title);
+ mvwaddnstr(widget->window, pos, x + (x != pos), tree->columns[i].title, tree->columns[i].width);
NEXT_X;
}
if (pos)
@@ -768,6 +768,7 @@ end_search(GntTree *tree)
g_string_free(tree->priv->search, TRUE);
tree->priv->search = NULL;
tree->priv->search_timeout = 0;
+ GNT_WIDGET_UNSET_FLAGS(GNT_WIDGET(tree), GNT_WIDGET_DISABLE_ACTIONS);
}
}
@@ -984,7 +985,11 @@ gnt_tree_class_init(GntTreeClass *klass)
g_param_spec_int("columns", "Columns",
"Number of columns in the tree.",
1, G_MAXINT, 1,
+#if GLIB_CHECK_VERSION(2,8,0)
G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
+#else
+ G_PARAM_READWRITE|G_PARAM_PRIVATE
+#endif
)
);
@@ -1049,7 +1054,9 @@ gnt_tree_init(GTypeInstance *instance, gpointer class)
GntTree *tree = GNT_TREE(widget);
tree->show_separator = TRUE;
tree->priv = g_new0(GntTreePriv, 1);
- GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X | GNT_WIDGET_GROW_Y | GNT_WIDGET_CAN_TAKE_FOCUS);
+ GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X | GNT_WIDGET_GROW_Y |
+ GNT_WIDGET_CAN_TAKE_FOCUS | GNT_WIDGET_NO_SHADOW);
+ gnt_widget_set_take_focus(widget, TRUE);
widget->priv.minw = 4;
widget->priv.minh = 1;
GNTDEBUG;
@@ -1090,7 +1097,7 @@ static void
free_tree_col(gpointer data)
{
GntTreeCol *col = data;
- if (col->isbinary)
+ if (!col->isbinary)
g_free(col->text);
g_free(col);
}
@@ -1601,9 +1608,6 @@ GntWidget *gnt_tree_new_with_columns(int col)
"columns", col,
NULL);
- GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_SHADOW);
- gnt_widget_set_take_focus(widget, TRUE);
-
return widget;
}
diff --git a/finch/libgnt/gntutils.h b/finch/libgnt/gntutils.h
index b5c6f40f33..ddc8c39439 100644
--- a/finch/libgnt/gntutils.h
+++ b/finch/libgnt/gntutils.h
@@ -40,6 +40,7 @@ typedef gpointer (*GDupFunc)(gconstpointer data);
*/
void gnt_util_get_text_bound(const char *text, int *width, int *height);
+/* excluding *end */
/**
* Get the onscreen width of a string, or a substring.
*
diff --git a/finch/libgnt/gntwm.c b/finch/libgnt/gntwm.c
index 5496e01987..7cb4f9a799 100644
--- a/finch/libgnt/gntwm.c
+++ b/finch/libgnt/gntwm.c
@@ -39,6 +39,8 @@
#include "gntmarshal.h"
#include "gnt.h"
#include "gntbox.h"
+#include "gntbutton.h"
+#include "gntentry.h"
#include "gntlabel.h"
#include "gntmenu.h"
#include "gnttextview.h"
@@ -84,6 +86,7 @@ static int write_timeout;
static time_t last_active_time;
static gboolean idle_update;
static GList *act = NULL; /* list of WS with unseen activitiy */
+static gboolean ignore_keys = FALSE;
static GList *
g_list_bring_to_front(GList *list, gpointer data)
@@ -485,35 +488,6 @@ window_close(GntBindable *bindable, GList *null)
return TRUE;
}
-static gboolean
-help_for_widget(GntBindable *bindable, GList *null)
-{
- GntWM *wm = GNT_WM(bindable);
- GntWidget *widget, *tree, *win, *active;
- char *title;
-
- if (!wm->cws->ordered)
- return TRUE;
-
- widget = wm->cws->ordered->data;
- if (!GNT_IS_BOX(widget))
- return TRUE;
- active = GNT_BOX(widget)->active;
-
- tree = gnt_widget_bindings_view(active);
- win = gnt_window_new();
- title = g_strdup_printf("Bindings for %s", g_type_name(G_OBJECT_TYPE(active)));
- gnt_box_set_title(GNT_BOX(win), title);
- if (tree)
- gnt_box_add_widget(GNT_BOX(win), tree);
- else
- gnt_box_add_widget(GNT_BOX(win), gnt_label_new("This widget has no customizable bindings."));
-
- gnt_widget_show(win);
-
- return TRUE;
-}
-
static void
destroy__list(GntWidget *widget, GntWM *wm)
{
@@ -684,6 +658,8 @@ dump_screen(GntBindable *bindable, GList *null)
{'j', "&#x2518;"},
{'a', "&#x2592;"},
{'n', "&#x253c;"},
+ {'w', "&#x252c;"},
+ {'v', "&#x2534;"},
{'\0', NULL}
};
@@ -841,6 +817,7 @@ static gboolean
shift_right(GntBindable *bindable, GList *null)
{
GntWM *wm = GNT_WM(bindable);
+
if (wm->_list.window)
return TRUE;
@@ -1136,10 +1113,106 @@ workspace_new(GntBindable *bindable, GList *null)
return TRUE;
}
+static gboolean
+ignore_keys_start(GntBindable *bindable, GList *n)
+{
+ GntWM *wm = GNT_WM(bindable);
+
+ if(!wm->menu && !wm->_list.window && wm->mode == GNT_KP_MODE_NORMAL){
+ ignore_keys = TRUE;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+ignore_keys_end(GntBindable *bindable, GList *n)
+{
+ return ignore_keys ? !(ignore_keys = FALSE) : FALSE;
+}
+
+static gboolean
+help_for_bindable(GntWM *wm, GntBindable *bindable)
+{
+ gboolean ret = TRUE;
+ GntBindableClass *klass = GNT_BINDABLE_GET_CLASS(bindable);
+
+ if (klass->help_window) {
+ gnt_wm_raise_window(wm, GNT_WIDGET(klass->help_window));
+ } else {
+ ret = gnt_bindable_build_help_window(bindable);
+ }
+ return ret;
+}
+
+static gboolean
+help_for_wm(GntBindable *bindable, GList *null)
+{
+ return help_for_bindable(GNT_WM(bindable),bindable);
+}
+
+static gboolean
+help_for_window(GntBindable *bindable, GList *null)
+{
+ GntWM *wm = GNT_WM(bindable);
+ GntWidget *widget;
+
+ if(!wm->cws->ordered)
+ return FALSE;
+
+ widget = wm->cws->ordered->data;
+
+ return help_for_bindable(wm,GNT_BINDABLE(widget));
+}
+
+static gboolean
+help_for_widget(GntBindable *bindable, GList *null)
+{
+ GntWM *wm = GNT_WM(bindable);
+ GntWidget *widget;
+
+ if (!wm->cws->ordered)
+ return TRUE;
+
+ widget = wm->cws->ordered->data;
+ if (!GNT_IS_BOX(widget))
+ return TRUE;
+
+ return help_for_bindable(wm, GNT_BINDABLE(GNT_BOX(widget)->active));
+}
+
+static void
+accumulate_windows(gpointer window, gpointer node, gpointer p)
+{
+ GList *list = *(GList**)p;
+ list = g_list_prepend(list, window);
+ *(GList**)p = list;
+}
+
+static void
+gnt_wm_destroy(GObject *obj)
+{
+ GntWM *wm = GNT_WM(obj);
+ GList *list = NULL;
+ g_hash_table_foreach(wm->nodes, accumulate_windows, &list);
+ g_list_foreach(list, (GFunc)gnt_widget_destroy, NULL);
+ g_list_free(list);
+ g_hash_table_destroy(wm->nodes);
+ wm->nodes = NULL;
+
+ while (wm->workspaces) {
+ g_object_unref(wm->workspaces->data);
+ wm->workspaces = g_list_delete_link(wm->workspaces, wm->workspaces);
+ }
+}
+
static void
gnt_wm_class_init(GntWMClass *klass)
{
int i;
+ GObjectClass *gclass = G_OBJECT_CLASS(klass);
+
+ gclass->dispose = gnt_wm_destroy;
klass->new_window = gnt_wm_new_window_real;
klass->decorate_window = NULL;
@@ -1291,8 +1364,16 @@ gnt_wm_class_init(GntWMClass *klass)
"\033" "T", NULL);
gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "workspace-list", workspace_list,
"\033" "s", NULL);
- gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "toggle-clipboard",
- toggle_clipboard, "\033" "C", NULL);
+ gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "toggle-clipboard", toggle_clipboard,
+ "\033" "C", NULL);
+ gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "help-for-wm", help_for_wm,
+ "\033" "\\", NULL);
+ gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "help-for-window", help_for_window,
+ "\033" "|", NULL);
+ gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "ignore-keys-start", ignore_keys_start,
+ GNT_KEY_CTRL_G, NULL);
+ gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "ignore-keys-end", ignore_keys_end,
+ "\033" GNT_KEY_CTRL_G, NULL);
gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass));
@@ -1679,6 +1760,14 @@ gboolean gnt_wm_process_input(GntWM *wm, const char *keys)
keys = gnt_bindable_remap_keys(GNT_BINDABLE(wm), keys);
idle_update = TRUE;
+ if(ignore_keys){
+ if(keys && !strcmp(keys, "\033" GNT_KEY_CTRL_G)){
+ if(gnt_bindable_perform_action_key(GNT_BINDABLE(wm), keys)){
+ return TRUE;
+ }
+ }
+ return wm->cws->ordered ? gnt_widget_key_pressed(GNT_WIDGET(wm->cws->ordered->data), keys) : FALSE;
+ }
if (gnt_bindable_perform_action_key(GNT_BINDABLE(wm), keys)) {
return TRUE;
@@ -1897,7 +1986,7 @@ gnt_wm_give_focus(GntWM *wm, GntWidget *widget)
if (!node)
return;
-
+
if (widget != wm->_list.window && !GNT_IS_MENU(widget) &&
wm->cws->ordered->data != widget) {
GntWidget *w = wm->cws->ordered->data;
@@ -1978,3 +2067,4 @@ void gnt_wm_set_event_stack(GntWM *wm, gboolean set)
wm->event_stack = set;
}
+
diff --git a/finch/libgnt/pygnt/Makefile.am b/finch/libgnt/pygnt/Makefile.am
new file mode 100644
index 0000000000..54b0e7fade
--- /dev/null
+++ b/finch/libgnt/pygnt/Makefile.am
@@ -0,0 +1,40 @@
+EXTRA_DIST = gendef.sh
+
+pg_LTLIBRARIES = gnt.la
+
+pgdir = $(libdir)
+
+sources = \
+ gnt.def \
+ gnt.override \
+ gntbox.override \
+ gntfilesel.override \
+ gnttree.override \
+ gntwidget.override
+
+gnt_la_SOURCES = gnt.c common.c common.h gntmodule.c
+
+gnt_la_LDFLAGS = -module -avoid-version \
+ `pkg-config --libs pygobject-2.0`
+
+gnt_la_LIBADD = \
+ $(GLIB_LIBS) \
+ ../libgnt.la
+
+AM_CPPFLAGS = \
+ -I../ \
+ $(GLIB_CFLAGS) \
+ $(GNT_CFLAGS) \
+ -I/usr/include/python2.4 \
+ `pkg-config --cflags pygobject-2.0`
+
+CLEANFILES = gnt.def gnt.c gnt.defe
+
+gnt.def: $(srcdir)/../*.h
+ $(srcdir)/gendef.sh
+
+gnt.c: $(sources)
+ pygtk-codegen-2.0 --prefix gnt \
+ --override gnt.override \
+ gnt.def > $@
+
diff --git a/finch/libgnt/pygnt/Makefile.make b/finch/libgnt/pygnt/Makefile.make
index e15c493fe2..a8e1a4f294 100644
--- a/finch/libgnt/pygnt/Makefile.make
+++ b/finch/libgnt/pygnt/Makefile.make
@@ -10,5 +10,7 @@ gnt.c: gnt.def *.override common.c common.h
--override gnt.override \
gnt.def > $@
+#python codegen/codegen.py --prefix gnt \
+
clean:
@rm *.so *.o gnt.c
diff --git a/finch/libgnt/pygnt/dbus-gnt b/finch/libgnt/pygnt/dbus-gnt
index a3da5bee26..8c0d3bfa22 100755
--- a/finch/libgnt/pygnt/dbus-gnt
+++ b/finch/libgnt/pygnt/dbus-gnt
@@ -17,13 +17,14 @@ from time import strftime
convwins = {}
-def buddysignedon():
+def buddysignedon(buddy):
pass
def conv_closed(conv):
key = get_dict_key(conv)
stuff = convwins[key]
stuff[0].destroy()
+ # if a conv window is closed, then reopened, this thing crashes
convwins[key] = None
def wrote_msg(account, who, msg, conv, flags):
@@ -31,10 +32,13 @@ def wrote_msg(account, who, msg, conv, flags):
tv = stuff[1]
tv.append_text_with_flags("\n", 0)
tv.append_text_with_flags(strftime("(%X) "), 8)
- tv.append_text_with_flags(who + ": ", 1)
- tv.append_text_with_flags(msg, 0)
+ if flags & 3:
+ tv.append_text_with_flags(who + ": ", 1)
+ tv.append_text_with_flags(msg, 0)
+ stuff[0].set_urgent()
+ else:
+ tv.append_text_with_flags(msg, 8)
tv.scroll(0)
- stuff[0].set_urgent()
bus = dbus.SessionBus()
obj = bus.get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject")
@@ -72,6 +76,9 @@ def send_im_cb(entry, key, conv):
purple.PurpleConvChatSend(chatdata, entry.get_text())
entry.clear()
+def conv_window_destroyed(win, key):
+ del convwins[key]
+
def show_conversation(conv):
key = get_dict_key(conv)
if key in convwins:
@@ -91,27 +98,28 @@ def show_conversation(conv):
tv.clear()
win.show()
convwins[key] = [win, tv, entry]
+ win.connect("destroy", conv_window_destroyed, key)
return convwins[key]
def show_buddylist():
- win = gnt.Window()
- tree = gnt.Tree()
- tree.set_property("columns", 1)
- win.add_widget(tree)
- node = purple.PurpleBlistGetRoot()
- while node:
- if purple.PurpleBlistNodeIsGroup(node):
- sys.stderr.write(str(node) + "\n")
- tree.add_row_after(str(node), ["asd", ""], None, None)
- #tree.add_row_after(node, [str(purple.PurpleGroupGetName(node)), ""], None, None)
- #tree.add_row_after(node, ["aasd", ""], None, None)
- elif purple.PurpleBlistNodeIsContact(node):
- buddy = purple.PurpleContactGetPriorityBuddy(node)
- group = purple.PurpleBuddyGetGroup(buddy)
- #tree.add_row_after(node, [str(purple.PurpleBuddyGetName(buddy)), ""], group, None)
-
- node = purple.PurpleBlistNodeNext(node, False)
- win.show()
+ win = gnt.Window()
+ tree = gnt.Tree()
+ tree.set_property("columns", 1)
+ win.add_widget(tree)
+ node = purple.PurpleBlistGetRoot()
+ while node:
+ if purple.PurpleBlistNodeIsGroup(node):
+ sys.stderr.write(str(node) + "\n")
+ tree.add_row_after(str(node), ["asd", ""], None, None)
+ #tree.add_row_after(node, [str(purple.PurpleGroupGetName(node)), ""], None, None)
+ #tree.add_row_after(node, ["aasd", ""], None, None)
+ elif purple.PurpleBlistNodeIsContact(node):
+ buddy = purple.PurpleContactGetPriorityBuddy(node)
+ group = purple.PurpleBuddyGetGroup(buddy)
+ #tree.add_row_after(node, [str(purple.PurpleBuddyGetName(buddy)), ""], group, None)
+
+ node = purple.PurpleBlistNodeNext(node, False)
+ win.show()
gnt.gnt_init()
diff --git a/finch/libgnt/pygnt/example/rss/gnthtml.py b/finch/libgnt/pygnt/example/rss/gnthtml.py
new file mode 100644
index 0000000000..7c93900ee9
--- /dev/null
+++ b/finch/libgnt/pygnt/example/rss/gnthtml.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+
+"""
+gr - An RSS-reader built using libgnt and feedparser.
+
+Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>
+
+This application is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This application is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this application; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+"""
+
+"""
+This file defines GParser, which is a simple HTML parser to display HTML
+in a GntTextView nicely.
+"""
+
+import sgmllib
+import gnt
+
+class GParser(sgmllib.SGMLParser):
+ def __init__(self, view):
+ sgmllib.SGMLParser.__init__(self, False)
+ self.link = None
+ self.view = view
+ self.flag = gnt.TEXT_FLAG_NORMAL
+
+ def parse(self, s):
+ self.feed(s)
+ self.close()
+
+ def unknown_starttag(self, tag, attrs):
+ if tag in ["b", "i", "blockquote", "strong"]:
+ self.flag = self.flag | gnt.TEXT_FLAG_BOLD
+ elif tag in ["p", "hr", "br"]:
+ self.view.append_text_with_flags("\n", self.flag)
+ else:
+ print tag
+
+ def unknown_endtag(self, tag):
+ if tag in ["b", "i", "blockquote", "strong"]:
+ self.flag = self.flag & ~gnt.TEXT_FLAG_BOLD
+ elif tag in ["p", "hr", "br"]:
+ self.view.append_text_with_flags("\n", self.flag)
+ else:
+ print tag
+
+ def start_u(self, attrs):
+ self.flag = self.flag | gnt.TEXT_FLAG_UNDERLINE
+
+ def end_u(self):
+ self.flag = self.flag & ~gnt.TEXT_FLAG_UNDERLINE
+
+ def start_a(self, attributes):
+ for name, value in attributes:
+ if name == "href":
+ self.link = value
+
+ def do_img(self, attrs):
+ for name, value in attrs:
+ if name == 'src':
+ self.view.append_text_with_flags("[img:" + value + "]", self.flag)
+
+ def end_a(self):
+ if not self.link:
+ return
+ self.view.append_text_with_flags(" (", self.flag)
+ self.view.append_text_with_flags(self.link, self.flag | gnt.TEXT_FLAG_UNDERLINE)
+ self.view.append_text_with_flags(")", self.flag)
+ self.link = None
+
+ def handle_data(self, data):
+ if len(data.strip()) == 0:
+ return
+ self.view.append_text_with_flags(data, self.flag)
+
diff --git a/finch/libgnt/pygnt/example/rss/gntrss-ui.py b/finch/libgnt/pygnt/example/rss/gntrss-ui.py
new file mode 100755
index 0000000000..69504938b7
--- /dev/null
+++ b/finch/libgnt/pygnt/example/rss/gntrss-ui.py
@@ -0,0 +1,400 @@
+#!/usr/bin/env python
+
+"""
+gr - An RSS-reader built using libgnt and feedparser.
+
+Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>
+
+This application is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This application is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this application; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+"""
+
+"""
+This file deals with the UI part (gnt) of the application
+
+TODO:
+ - Allow showing feeds of only selected 'category' and/or 'priority'. A different
+ window should be used to change such filtering.
+ - Display details of each item in its own window.
+ - Add search capability, and allow searching only in title/body. Also allow
+ filtering in the search results.
+ - Show the data and time for feed items (probably in a separate column .. perhaps not)
+ - Have a simple way to add a feed.
+ - Allow renaming a feed.
+"""
+
+import gntrss
+import gnthtml
+import gnt
+import gobject
+import sys
+
+__version__ = "0.0.1alpha"
+__author__ = "Sadrul Habib Chowdhury (sadrul@pidgin.im)"
+__copyright__ = "Copyright 2007, Sadrul Habib Chowdhury"
+__license__ = "GPL" # see full license statement above
+
+gnt.gnt_init()
+
+class RssTree(gnt.Tree):
+ __gsignals__ = {
+ 'active_changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,))
+ }
+
+ __gntbindings__ = {
+ 'jump-next-unread' : ('jump_next_unread', 'J')
+ }
+
+ def jump_next_unread(self, null):
+ first = None
+ next = None
+ all = self.get_rows()
+ for item in all:
+ if item.unread:
+ if next:
+ first = item
+ break
+ elif not first and self.active != item:
+ first = item
+ if self.active == item:
+ next = item
+ if first:
+ self.set_active(first)
+ self.set_selected(first)
+ return True
+
+ def __init__(self):
+ self.active = None
+ gnt.Tree.__init__(self)
+ gnt.set_flag(self, 8) # remove borders
+ self.connect('key_pressed', self.do_key_pressed)
+
+ def set_active(self, active):
+ if self.active == active:
+ return
+ if self.active:
+ flag = gnt.TEXT_FLAG_NORMAL
+ if self.active.unread:
+ flag = flag | gnt.TEXT_FLAG_BOLD
+ self.set_row_flags(self.active, flag)
+ old = self.active
+ self.active = active
+ flag = gnt.TEXT_FLAG_UNDERLINE
+ if self.active.unread:
+ flag = flag | gnt.TEXT_FLAG_BOLD
+ self.set_row_flags(self.active, flag)
+ self.emit('active_changed', old)
+
+ def do_key_pressed(self, null, text):
+ if text == '\r':
+ now = self.get_selection_data()
+ self.set_active(now)
+ return True
+ return False
+
+gobject.type_register(RssTree)
+gnt.register_bindings(RssTree)
+
+win = gnt.Box(homo = False, vert = True)
+win.set_toplevel(True)
+win.set_title("GntRss")
+win.set_pad(0)
+
+#
+# [[[ Generic feed/item callbacks
+#
+def feed_item_added(feed, item):
+ add_feed_item(item)
+
+def add_feed(feed):
+ if not feed.get_data('gntrss-connected'):
+ feed.connect('added', feed_item_added)
+ feed.connect('notify', update_feed_title)
+ feed.set_data('gntrss-connected', True)
+ feeds.add_row_after(feed, [feed.title, str(feed.unread)], None, None)
+
+def remove_item(item, feed):
+ items.remove(item)
+
+def update_feed_item(item, property):
+ if property.name == 'unread':
+ if feeds.active == item.parent:
+ flag = 0
+ if item == items.active:
+ flag = gnt.TEXT_FLAG_UNDERLINE
+ if item.unread:
+ flag = flag | gnt.TEXT_FLAG_BOLD
+ else:
+ flag = flag | gnt.TEXT_FLAG_NORMAL
+ items.set_row_flags(item, flag)
+
+ unread = item.parent.unread
+ if item.unread:
+ unread = unread + 1
+ else:
+ unread = unread - 1
+ item.parent.set_property('unread', unread)
+
+def add_feed_item(item):
+ currentfeed = feeds.active
+ if item.parent != currentfeed:
+ return
+ months = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+ dt = str(item.date_parsed[2]) + "." + months[item.date_parsed[1]] + "." + str(item.date_parsed[0])
+ items.add_row_after(item, [str(item.title), dt], None, None)
+ if item.unread:
+ items.set_row_flags(item, gnt.TEXT_FLAG_BOLD)
+ if not item.get_data('gntrss-connected'):
+ item.set_data('gntrss-connected', True)
+ # this needs to happen *without* having to add the item in the tree
+ item.connect('notify', update_feed_item)
+ item.connect('delete', remove_item)
+
+#
+# ]]] Generic feed/item callbacks
+#
+
+
+####
+# [[[ The list of feeds
+###
+
+# 'Add Feed' dialog
+add_feed_win = None
+def add_feed_win_closed(win):
+ global add_feed_win
+ add_feed_win = None
+
+def add_new_feed():
+ global add_feed_win
+
+ if add_feed_win:
+ gnt.gnt_window_present(add_feed_win)
+ return
+ win = gnt.Window()
+ win.set_title("New Feed")
+
+ box = gnt.Box(False, False)
+ label = gnt.Label("Link")
+ box.add_widget(label)
+ entry = gnt.Entry("")
+ entry.set_size(40, 1)
+ box.add_widget(entry)
+
+ win.add_widget(box)
+ win.show()
+ add_feed_win = win
+ add_feed_win.connect("destroy", add_feed_win_closed)
+
+#
+# The active row in the feed-list has changed. Update the feed-item table.
+def feed_active_changed(tree, old):
+ items.remove_all()
+ if not tree.active:
+ return
+ update_items_title()
+ for item in tree.active.items:
+ add_feed_item(item)
+ win.give_focus_to_child(items)
+
+#
+# Check for the action keys and decide how to deal with them.
+def feed_key_pressed(tree, text):
+ if tree.is_searching():
+ return
+ if text == 'r':
+ feed = tree.get_selection_data()
+ tree.perform_action_key('j')
+ #tree.perform_action('move-down')
+ feed.refresh()
+ elif text == 'R':
+ feeds = tree.get_rows()
+ for feed in feeds:
+ feed.refresh()
+ elif text == 'm':
+ feed = tree.get_selection_data()
+ if feed:
+ feed.mark_read()
+ feed.set_property('unread', 0)
+ elif text == 'a':
+ add_new_feed()
+ else:
+ return False
+ return True
+
+feeds = RssTree()
+feeds.set_property('columns', 2)
+feeds.set_col_width(0, 20)
+feeds.set_col_width(1, 6)
+feeds.set_column_resizable(0, False)
+feeds.set_column_resizable(1, False)
+feeds.set_column_is_right_aligned(1, True)
+feeds.set_show_separator(False)
+feeds.set_column_title(0, "Feeds")
+feeds.set_show_title(True)
+
+feeds.connect('active_changed', feed_active_changed)
+feeds.connect('key_pressed', feed_key_pressed)
+gnt.unset_flag(feeds, 256) # Fix the width
+
+####
+# ]]] The list of feeds
+###
+
+####
+# [[[ The list of items in the feed
+####
+
+#
+# The active item in the feed-item list has changed. Update the
+# summary content.
+def item_active_changed(tree, old):
+ details.clear()
+ if not tree.active:
+ return
+ item = tree.active
+ details.append_text_with_flags(str(item.title) + "\n", gnt.TEXT_FLAG_BOLD)
+ details.append_text_with_flags("Link: ", gnt.TEXT_FLAG_BOLD)
+ details.append_text_with_flags(str(item.link) + "\n", gnt.TEXT_FLAG_UNDERLINE)
+ details.append_text_with_flags("Date: ", gnt.TEXT_FLAG_BOLD)
+ details.append_text_with_flags(str(item.date) + "\n", gnt.TEXT_FLAG_NORMAL)
+ details.append_text_with_flags("\n", gnt.TEXT_FLAG_NORMAL)
+ parser = gnthtml.GParser(details)
+ parser.parse(str(item.summary))
+ item.mark_unread(False)
+
+ if old and old.unread: # If the last selected item is marked 'unread', then make sure it's bold
+ items.set_row_flags(old, gnt.TEXT_FLAG_BOLD)
+
+#
+# Look for action keys in the feed-item list.
+def item_key_pressed(tree, text):
+ if tree.is_searching():
+ return
+ current = tree.get_selection_data()
+ if text == 'M': # Mark all of the items 'read'
+ feed = feeds.active
+ if feed:
+ feed.mark_read()
+ elif text == 'm': # Mark the current item 'read'
+ current.mark_unread(False)
+ tree.perform_action_key('j')
+ elif text == 'U': # Mark the current item 'unread'
+ current.mark_unread(True)
+ elif text == 'd':
+ current.remove()
+ tree.perform_action_key('j')
+ else:
+ return False
+ return True
+
+items = RssTree()
+items.set_property('columns', 2)
+items.set_col_width(0, 40)
+items.set_col_width(1, 11)
+items.set_column_resizable(1, False)
+items.set_column_title(0, "Items")
+items.set_column_title(1, "Date")
+items.set_show_title(True)
+items.connect('key_pressed', item_key_pressed)
+items.connect('active_changed', item_active_changed)
+
+####
+# ]]] The list of items in the feed
+####
+
+#
+# Update the title of the items list depending on the selection in the feed list
+def update_items_title():
+ feed = feeds.active
+ if feed:
+ items.set_column_title(0, str(feed.title) + ": " + str(feed.unread) + "(" + str(len(feed.items)) + ")")
+ else:
+ items.set_column_title(0, "Items")
+ items.draw()
+
+# The container on the top
+line = gnt.Line(vertical = False)
+
+# The textview to show the details of a feed
+details = gnt.TextView()
+details.set_take_focus(True)
+details.set_flag(gnt.TEXT_VIEW_TOP_ALIGN)
+details.attach_scroll_widget(details)
+
+# Make it look nice
+s = feeds.get_size()
+size = gnt.screen_size()
+size[0] = size[0] - s[0]
+items.set_size(size[0], size[1] / 2)
+details.set_size(size[0], size[1] / 2)
+
+# Category tree
+cat = gnt.Tree()
+cat.set_property('columns', 1)
+cat.set_column_title(0, 'Category')
+cat.set_show_title(True)
+gnt.set_flag(cat, 8) # remove borders
+
+box = gnt.Box(homo = False, vert = False)
+box.set_pad(0)
+
+vbox = gnt.Box(homo = False, vert = True)
+vbox.set_pad(0)
+vbox.add_widget(feeds)
+vbox.add_widget(gnt.Line(False))
+vbox.add_widget(cat)
+box.add_widget(vbox)
+
+box.add_widget(gnt.Line(True))
+
+vbox = gnt.Box(homo = False, vert = True)
+vbox.set_pad(0)
+vbox.add_widget(items)
+vbox.add_widget(gnt.Line(False))
+vbox.add_widget(details)
+box.add_widget(vbox)
+
+win.add_widget(box)
+win.show()
+
+def update_feed_title(feed, property):
+ if property.name == 'title':
+ if feed.customtitle:
+ title = feed.customtitle
+ else:
+ title = feed.title
+ feeds.change_text(feed, 0, title)
+ elif property.name == 'unread':
+ feeds.change_text(feed, 1, str(feed.unread) + "(" + str(len(feed.items)) + ")")
+ flag = 0
+ if feeds.active == feed:
+ flag = gnt.TEXT_FLAG_UNDERLINE
+ update_items_title()
+ if feed.unread > 0:
+ flag = flag | gnt.TEXT_FLAG_BOLD
+ feeds.set_row_flags(feed, flag)
+
+# populate everything
+for feed in gntrss.feeds:
+ feed.refresh()
+ feed.set_auto_refresh(True)
+ add_feed(feed)
+
+gnt.gnt_register_action("Stuff", add_new_feed)
+gnt.gnt_main()
+
+gnt.gnt_quit()
+
diff --git a/finch/libgnt/pygnt/example/rss/gntrss.py b/finch/libgnt/pygnt/example/rss/gntrss.py
new file mode 100644
index 0000000000..62b4110a68
--- /dev/null
+++ b/finch/libgnt/pygnt/example/rss/gntrss.py
@@ -0,0 +1,250 @@
+#!/usr/bin/env python
+
+"""
+gr - An RSS-reader built using libgnt and feedparser.
+
+Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>
+
+This application is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This application is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this application; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+"""
+
+"""
+This file deals with the rss parsing part (feedparser) of the application
+"""
+
+import os
+import tempfile, urllib2
+import feedparser
+import gobject
+import sys
+import time
+
+##
+# The FeedItem class. It will update emit 'delete' signal when it's
+# destroyed.
+##
+class FeedItem(gobject.GObject):
+ __gproperties__ = {
+ 'unread' : (gobject.TYPE_BOOLEAN, 'read',
+ 'The unread state of the item.',
+ False, gobject.PARAM_READWRITE)
+ }
+ __gsignals__ = {
+ 'delete' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,))
+ }
+ def __init__(self, item, parent):
+ self.__gobject_init__()
+ try:
+ "Apparently some feed items don't have any dates in them"
+ self.date = item['date']
+ self.date_parsed = item['date_parsed']
+ except:
+ item['date'] = self.date = time.ctime()
+ self.date_parsed = feedparser._parse_date(self.date)
+
+ self.title = item['title'].encode('utf8')
+ self.summary = item['summary'].encode('utf8')
+ self.link = item['link']
+ self.parent = parent
+ self.unread = True
+
+ def remove(self):
+ self.emit('delete', self.parent)
+ if self.unread:
+ self.parent.set_property('unread', self.parent.unread - 1)
+
+ def do_set_property(self, property, value):
+ if property.name == 'unread':
+ self.unread = value
+
+ def mark_unread(self, unread):
+ if self.unread == unread:
+ return
+ self.set_property('unread', unread)
+
+gobject.type_register(FeedItem)
+
+def item_hash(item):
+ return str(item['title'])
+
+"""
+The Feed class. It will update the 'link', 'title', 'desc' and 'items'
+attributes if/when they are updated (triggering 'notify::<attr>' signal)
+
+TODO:
+ - Add a 'count' attribute
+ - Each feed will have a 'uidata', which will be its display window
+ - Look into 'category'. Is it something that feed defines, or the user?
+ - Have separate refresh times for each feed.
+ - Have 'priority' for each feed. (somewhat like category, perhaps?)
+"""
+class Feed(gobject.GObject):
+ __gproperties__ = {
+ 'link' : (gobject.TYPE_STRING, 'link',
+ 'The web page this feed is associated with.',
+ '...', gobject.PARAM_READWRITE),
+ 'title' : (gobject.TYPE_STRING, 'title',
+ 'The title of the feed.',
+ '...', gobject.PARAM_READWRITE),
+ 'desc' : (gobject.TYPE_STRING, 'description',
+ 'The description for the feed.',
+ '...', gobject.PARAM_READWRITE),
+ 'items' : (gobject.TYPE_POINTER, 'items',
+ 'The items in the feed.', gobject.PARAM_READWRITE),
+ 'unread' : (gobject.TYPE_INT, 'unread',
+ 'Number of unread items in the feed.', 0, 10000, 0, gobject.PARAM_READWRITE)
+ }
+ __gsignals__ = {
+ 'added' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,))
+ }
+
+ def __init__(self, feed):
+ self.__gobject_init__()
+ url = feed['link']
+ name = feed['name']
+ self.url = url # The url of the feed itself
+ self.link = url # The web page associated with the feed
+ self.desc = url
+ self.title = (name, url)[not name]
+ self.customtitle = name
+ self.unread = 0
+ self.items = []
+ self.hash = {}
+ self.pending = False
+ self._refresh = {'time' : 30, 'id' : 0}
+
+ def do_set_property(self, property, value):
+ if property.name == 'link':
+ self.link = value
+ elif property.name == 'desc':
+ self.desc = value
+ elif property.name == 'title':
+ self.title = value
+ elif property.name == 'unread':
+ self.unread = value
+ pass
+
+ def set_result(self, result):
+ # XXX Look at result['bozo'] first, and emit some signal that the UI can use
+ # to indicate (dim the row?) that the feed has invalid XML format or something
+
+ try:
+ channel = result['channel']
+ self.set_property('link', channel['link'])
+ self.set_property('desc', channel['description'])
+ self.set_property('title', channel['title'])
+ items = result['items']
+ except:
+ items = ()
+
+ tmp = {}
+ for item in self.items:
+ tmp[hash(item)] = item
+
+ unread = self.unread
+ for item in items:
+ try:
+ exist = self.hash[item_hash(item)]
+ del tmp[hash(exist)]
+ except:
+ itm = FeedItem(item, self)
+ self.items.append(itm)
+ self.emit('added', itm)
+ self.hash[item_hash(item)] = itm
+ unread = unread + 1
+
+ if unread != self.unread:
+ self.set_property('unread', unread)
+
+ for hv in tmp:
+ self.items.remove(tmp[hv])
+ tmp[hv].remove()
+ "Also notify the UI about the count change"
+
+ self.pending = False
+ return False
+
+ def refresh(self):
+ if self.pending:
+ return
+ self.pending = True
+ FeedReader(self).run()
+ return True
+
+ def mark_read(self):
+ for item in self.items:
+ item.mark_unread(False)
+
+ def set_auto_refresh(self, auto):
+ if auto:
+ if self._refresh['id']:
+ return
+ if self._refresh['time'] < 1:
+ self._refresh['time'] = 1
+ self.id = gobject.timeout_add(self._refresh['time'] * 1000 * 60, self.refresh)
+ else:
+ if not self._refresh['id']:
+ return
+ gobject.source_remove(self._refresh['id'])
+ self._refresh['id'] = 0
+
+gobject.type_register(Feed)
+
+"""
+The FeedReader updates a Feed. It fork()s off a child to avoid blocking.
+"""
+class FeedReader:
+ def __init__(self, feed):
+ self.feed = feed
+
+ def reap_child(self, pid, status):
+ result = feedparser.parse(self.tmpfile.name)
+ self.tmpfile.close()
+ self.feed.set_result(result)
+
+ def run(self):
+ self.tmpfile = tempfile.NamedTemporaryFile()
+ self.pid = os.fork()
+ if self.pid == 0:
+ tmp = urllib2.urlopen(self.feed.url)
+ content = tmp.read()
+ tmp.close()
+ self.tmpfile.write(content)
+ self.tmpfile.flush()
+ # Do NOT close tmpfile here
+ os._exit(os.EX_OK)
+ gobject.child_watch_add(self.pid, self.reap_child)
+
+feeds = []
+urls = (
+ {'name': '/.',
+ 'link': "http://rss.slashdot.org/Slashdot/slashdot"},
+ {'name': 'KernelTrap',
+ 'link': "http://kerneltrap.org/node/feed"},
+ {'name': None,
+ 'link': "http://pidgin.im/rss.php"},
+ {'name': "F1",
+ 'link': "http://www.formula1.com/rss/news/latest.rss"},
+ {'name': "Freshmeat",
+ 'link': "http://www.pheedo.com/f/freshmeatnet_announcements_unix"},
+ {'name': "Cricinfo",
+ 'link': "http://www.cricinfo.com/rss/livescores.xml"}
+)
+
+for url in urls:
+ feed = Feed(url)
+ feeds.append(feed)
+
diff --git a/finch/libgnt/pygnt/gendef.sh b/finch/libgnt/pygnt/gendef.sh
index 2536cff143..c50caff155 100755
--- a/finch/libgnt/pygnt/gendef.sh
+++ b/finch/libgnt/pygnt/gendef.sh
@@ -28,7 +28,7 @@ FILES="
gnt.h"
# Generate the def file
-rm gnt.def
+rm -f gnt.def
for file in $FILES
do
python /usr/share/pygtk/2.0/codegen/h2def.py ../$file >> gnt.def
@@ -43,6 +43,7 @@ GNT_TYPE_STYLE
GNT_TYPE_KEY_PRESS_MODE
GNT_TYPE_ENTRY_FLAG
GNT_TYPE_TEXT_FORMAT_FLAGS
+GNT_TYPE_TEXT_VIEW_FLAG
"
for enum in $ENUMS
diff --git a/finch/libgnt/pygnt/gnt.override b/finch/libgnt/pygnt/gnt.override
index 90d9de010a..1ba48369d2 100644
--- a/finch/libgnt/pygnt/gnt.override
+++ b/finch/libgnt/pygnt/gnt.override
@@ -29,8 +29,10 @@ headers
#include "common.h"
%%
include
+ gntbox.override
gntfilesel.override
gnttree.override
+ gntwidget.override
%%
modulename gnt
%%
@@ -39,3 +41,154 @@ import gobject.GObject as PyGObject_Type
ignore-glob
*_get_gtype
%%
+define set_flag
+static PyObject *
+_wrap_set_flag(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = {"flags", NULL};
+ PyGObject *widget;
+ int flags;
+
+ if (!PyArg_ParseTuple(args, "O!i:gnt.set_flag", &PyGntWidget_Type, &widget,
+ &flags)) {
+ return NULL;
+ }
+
+ GNT_WIDGET_SET_FLAGS(widget->obj, flags);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+%%
+define unset_flag
+static PyObject *
+_wrap_unset_flag(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = {"flags", NULL};
+ PyGObject *widget;
+ int flags;
+
+ if (!PyArg_ParseTuple(args, "O!i:gnt.unset_flag", &PyGntWidget_Type, &widget,
+ &flags)) {
+ return NULL;
+ }
+
+ GNT_WIDGET_UNSET_FLAGS(widget->obj, flags);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+%%
+define screen_size noargs
+static PyObject *
+_wrap_screen_size(PyObject *self)
+{
+ PyObject *list = PyList_New(0);
+
+ if (list == NULL)
+ return NULL;
+
+ PyList_Append(list, PyInt_FromLong((long)getmaxx(stdscr)));
+ PyList_Append(list, PyInt_FromLong((long)getmaxy(stdscr)));
+
+ return list;
+}
+%%
+override gnt_register_action
+static GHashTable *actions;
+
+
+
+static PyObject *
+_wrap_gnt_register_action(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = {"name", "callback", NULL};
+ PyGObject *callback;
+ GClosure *closure;
+ char *name;
+
+ if (!PyArg_ParseTuple(args, "sO:gnt.gnt_register_action", &name, &callback)) {
+ return NULL;
+ }
+
+ if (!PyCallable_Check(callback)) {
+ PyErr_SetString(PyExc_TypeError, "the callback must be callable ... doh!");
+ return NULL;
+ }
+
+ gnt_register_action(name, callback->obj);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+%%
+define register_bindings
+
+static gboolean
+pygnt_binding_callback(GntBindable *bindable, GList *list)
+{
+ PyObject *wrapper = pygobject_new(G_OBJECT(bindable));
+ PyObject_CallMethod(wrapper, list->data, "O", Py_None);
+ Py_DECREF(wrapper);
+ return TRUE;
+}
+
+static PyObject *
+_wrap_register_bindings(PyObject *self, PyObject *args)
+{
+ PyTypeObject *class;
+ int pos = 0;
+ PyObject *key, *value, *gbindings;
+ GntBindableClass *bindable;
+
+ if (!PyArg_ParseTuple(args, "O!:gnt.register_bindings",
+ &PyType_Type, &class)) {
+ /* Make sure it's a GntBindableClass subclass */
+ PyErr_SetString(PyExc_TypeError,
+ "argument must be a GntBindable subclass");
+ return NULL;
+ }
+
+ gbindings = PyDict_GetItemString(class->tp_dict, "__gntbindings__");
+ if (!gbindings)
+ goto end;
+
+ if (!PyDict_Check(gbindings)) {
+ PyErr_SetString(PyExc_TypeError,
+ "__gntbindings__ attribute not a dict!");
+ return NULL;
+ }
+
+ bindable = g_type_class_ref(pyg_type_from_object((PyObject *)class));
+ while (PyDict_Next(gbindings, &pos, &key, &value)) {
+ const char *trigger, *callback, *name;
+ GList *list = NULL;
+
+ if (!PyString_Check(key)) {
+ PyErr_SetString(PyExc_TypeError,
+ "__gntbindings__ keys must be strings");
+ g_type_class_unref(bindable);
+ return NULL;
+ }
+ name = PyString_AsString(key);
+
+ if (!PyTuple_Check(value) ||
+ !PyArg_ParseTuple(value, "ss", &callback, &trigger)) {
+ PyErr_SetString(PyExc_TypeError,
+ "__gntbindings__ values must be (callback, trigger) tupples");
+ g_type_class_unref(bindable);
+ return NULL;
+ }
+
+ gnt_bindable_class_register_action(bindable, name, pygnt_binding_callback,
+ trigger, g_strdup(callback), NULL);
+ }
+ if (gbindings)
+ PyDict_DelItemString(class->tp_dict, "__gntbindings__");
+ g_type_class_unref(bindable);
+
+end:
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
diff --git a/finch/libgnt/pygnt/gntbox.override b/finch/libgnt/pygnt/gntbox.override
new file mode 100644
index 0000000000..db9bb1a14c
--- /dev/null
+++ b/finch/libgnt/pygnt/gntbox.override
@@ -0,0 +1,38 @@
+/**
+ * pygnt- Python bindings for the GNT toolkit.
+ * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>
+ *
+ * gntbox.override: overrides for the box widget.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+%%
+override gnt_box_add_widget kwargs
+static PyObject *
+_wrap_gnt_box_add_widget(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = { "widget", NULL };
+ PyGObject *widget;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!:GntBox.add_widget", kwlist, &PyGntWidget_Type, &widget))
+ return NULL;
+
+ gnt_box_add_widget(GNT_BOX(self->obj), GNT_WIDGET(widget->obj));
+ Py_INCREF(widget);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
diff --git a/finch/libgnt/pygnt/gntmodule.c b/finch/libgnt/pygnt/gntmodule.c
index 7ba6e8e89c..84292fe7bd 100644
--- a/finch/libgnt/pygnt/gntmodule.c
+++ b/finch/libgnt/pygnt/gntmodule.c
@@ -9,11 +9,12 @@ initgnt(void)
PyObject *m, *d;
init_pygobject ();
-
+
m = Py_InitModule ("gnt", gnt_functions);
d = PyModule_GetDict (m);
gnt_register_classes (d);
+ gnt_add_constants(m, "GNT_");
if (PyErr_Occurred ()) {
Py_FatalError ("can't initialise module sad");
diff --git a/finch/libgnt/pygnt/gnttree.override b/finch/libgnt/pygnt/gnttree.override
index ee1a106ea6..8d6c23d739 100644
--- a/finch/libgnt/pygnt/gnttree.override
+++ b/finch/libgnt/pygnt/gnttree.override
@@ -20,7 +20,7 @@
* USA
*/
%%
-headrs
+headers
#include "common.h"
%%
ignore
@@ -49,7 +49,7 @@ _wrap_gnt_tree_get_rows(PyGObject *self)
return NULL;
}
while (list) {
- PyObject *obj = pyg_pointer_new(G_TYPE_POINTER, list->data);
+ PyObject *obj = list->data;
PyList_Append(py_list, obj);
Py_DECREF(obj);
list = list->next;
@@ -100,4 +100,79 @@ _wrap_gnt_tree_add_row_after(PyGObject *self, PyObject *args)
Py_INCREF(Py_None);
return Py_None;
}
+%%
+override gnt_tree_get_selection_data noargs
+static PyObject *
+_wrap_gnt_tree_get_selection_data(PyGObject *self)
+{
+ PyObject *ret = gnt_tree_get_selection_data(GNT_TREE(self->obj));
+ if (!ret)
+ ret = Py_None;
+ Py_INCREF(ret);
+ return ret;
+}
+%%
+override gnt_tree_change_text
+static PyObject *
+_wrap_gnt_tree_change_text(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = { "key", "colno", "text", NULL };
+ char *text;
+ int colno;
+ gpointer key;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,"Ois:GntTree.change_text", kwlist, &key, &colno, &text))
+ return NULL;
+
+ gnt_tree_change_text(GNT_TREE(self->obj), key, colno, text);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+%%
+override gnt_tree_set_row_flags
+static PyObject *
+_wrap_gnt_tree_set_row_flags(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = { "key", "flag", NULL };
+ int flag;
+ gpointer key;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,"Oi:GntTree.set_row_flags", kwlist, &key, &flag))
+ return NULL;
+
+ gnt_tree_set_row_flags(GNT_TREE(self->obj), key, flag);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+%%
+override gnt_tree_remove
+static PyObject *
+_wrap_gnt_tree_remove(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = { "key", NULL };
+ gpointer key;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,"O:GntTree.remove", kwlist, &key))
+ return NULL;
+
+ gnt_tree_remove(GNT_TREE(self->obj), key);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+%%
+override gnt_tree_set_selected
+static PyObject *
+_wrap_gnt_tree_set_selected(PyGObject *self, PyObject *args)
+{
+ gpointer key;
+ if (!PyArg_ParseTuple(args, "O:GntTree.set_selected", &key)) {
+ return NULL;
+ }
+ gnt_tree_set_selected(GNT_TREE(self->obj), key);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
diff --git a/finch/libgnt/pygnt/gntwidget.override b/finch/libgnt/pygnt/gntwidget.override
new file mode 100644
index 0000000000..752c6fa865
--- /dev/null
+++ b/finch/libgnt/pygnt/gntwidget.override
@@ -0,0 +1,36 @@
+/**
+ * pygnt- Python bindings for the GNT toolkit.
+ * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>
+ *
+ * gntwidget.override: overrides for generic widgets.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+%%
+override gnt_widget_get_size args
+static PyObject *
+_wrap_gnt_widget_get_size(PyGObject *self)
+{
+ PyObject *list = PyList_New(0);
+ int x = 0, y = 0;
+
+ gnt_widget_get_size(GNT_WIDGET(self->obj), &x, &y);
+ PyList_Append(list, PyInt_FromLong((long)x));
+ PyList_Append(list, PyInt_FromLong((long)y));
+
+ return list;
+}
+
diff --git a/finch/libgnt/pygnt/test.py b/finch/libgnt/pygnt/test.py
index 30d9d45dc0..9bb63884df 100755
--- a/finch/libgnt/pygnt/test.py
+++ b/finch/libgnt/pygnt/test.py
@@ -1,18 +1,59 @@
#!/usr/bin/python
+import gobject
import gnt
+class MyObject(gobject.GObject):
+ __gproperties__ = {
+ 'mytype': (gobject.TYPE_INT, 'mytype', 'the type of the object',
+ 0, 10000, 0, gobject.PARAM_READWRITE),
+ 'string': (gobject.TYPE_STRING, 'string property', 'the string',
+ None, gobject.PARAM_READWRITE),
+ 'gobject': (gobject.TYPE_OBJECT, 'object property', 'the object',
+ gobject.PARAM_READWRITE),
+ }
+
+ def __init__(self, type = 'string', value = None):
+ self.__gobject_init__()
+ self.set_property(type, value)
+
+ def do_set_property(self, pspec, value):
+ if pspec.name == 'string':
+ self.string = value
+ self.type = gobject.TYPE_STRING
+ elif pspec.name == 'gobject':
+ self.gobject = value
+ self.type = gobject.TYPE_OBJECT
+ else:
+ raise AttributeError, 'unknown property %s' % pspec.name
+ def do_get_property(self, pspec):
+ if pspec.name == 'string':
+ return self.string
+ elif pspec.name == 'gobject':
+ return self.gobject
+ elif pspec.name == 'mytype':
+ return self.type
+ else:
+ raise AttributeError, 'unknown property %s' % pspec.name
+gobject.type_register(MyObject)
+
def button_activate(button, tree):
- list = tree.get_selection_text_list()
- str = ""
- for i in list:
- str = str + i
- entry.set_text("clicked!!!" + str)
+ list = tree.get_selection_text_list()
+ ent = tree.get_selection_data()
+ if ent.type == gobject.TYPE_STRING:
+ str = ""
+ for i in list:
+ str = str + i
+ entry.set_text("clicked!!!" + str)
+ elif ent.type == gobject.TYPE_OBJECT:
+ ent.gobject.set_text("mwhahaha!!!")
gnt.gnt_init()
win = gnt.Window()
entry = gnt.Entry("")
+obj = MyObject()
+obj.set_property('gobject', entry)
win.add_widget(entry)
win.set_title("Entry")
@@ -27,12 +68,20 @@ win.add_widget(tree)
# so random non-string values can be used as the key for a row in a GntTree!
last = None
for i in range(1, 100):
- tree.add_row_after(i, [str(i), ""], None, i-1)
-tree.add_row_after(entry, ["asd"], None, None)
-tree.add_row_after("b", ["123", ""], entry, None)
+ key = MyObject('string', str(i))
+ tree.add_row_after(key, [str(i)], None, last)
+ last = key
+
+tree.add_row_after(MyObject('gobject', entry), ["asd"], None, None)
+tree.add_row_after(MyObject('string', "b"), ["123"], MyObject('gobject', entry), None)
button.connect("activate", button_activate, tree)
+tv = gnt.TextView()
+
+win.add_widget(tv)
+tv.append_text_with_flags("What up!!", gnt.TEXT_FLAG_BOLD)
+
win.show()
gnt.gnt_main()
diff --git a/finch/libgnt/wms/Makefile.am b/finch/libgnt/wms/Makefile.am
index 7bd28fa94e..7d7a19f534 100644
--- a/finch/libgnt/wms/Makefile.am
+++ b/finch/libgnt/wms/Makefile.am
@@ -1,10 +1,16 @@
+if PURPLE_AVAILABLE
+# These custom wms depend on libpurple
+purple_wms = s.la irssi.la
+else
+purple_wms =
+endif
+
s_la_LDFLAGS = -module -avoid-version
irssi_la_LDFLAGS = -module -avoid-version
plugin_LTLIBRARIES = \
- s.la \
- irssi.la
-
+ $(purple_wms)
+
plugindir = $(libdir)/gnt
irssi_la_SOURCES = irssi.c
diff --git a/finch/libgnt/wms/s.c b/finch/libgnt/wms/s.c
index 3ef9e69d79..3813fc4346 100644
--- a/finch/libgnt/wms/s.c
+++ b/finch/libgnt/wms/s.c
@@ -15,6 +15,10 @@
#define TYPE_S (s_get_gtype())
+#ifdef _S
+#undef _S
+#endif
+
typedef struct _S
{
GntWM inherit;
diff --git a/libpurple/Makefile.am b/libpurple/Makefile.am
index 6913ce6f55..8e6548ffd1 100644
--- a/libpurple/Makefile.am
+++ b/libpurple/Makefile.am
@@ -150,7 +150,7 @@ CLEANFILES = \
# purple dbus server
dbus_sources = dbus-server.c dbus-useful.c
-dbus_headers = dbus-bindings.h dbus-purple.h dbus-server.h dbus-useful.h dbus-define-api.h
+dbus_headers = dbus-bindings.h dbus-purple.h dbus-server.h dbus-useful.h dbus-define-api.h dbus-types.h
dbus_exported = dbus-useful.h dbus-define-api.h account.h blist.h buddyicon.h \
connection.h conversation.h core.h ft.h log.h notify.h prefs.h roomlist.h \
diff --git a/libpurple/account.c b/libpurple/account.c
index dc57096a3b..65f6dc5842 100644
--- a/libpurple/account.c
+++ b/libpurple/account.c
@@ -1130,8 +1130,8 @@ purple_account_request_close(void *ui_handle)
void *
purple_account_request_authorization(PurpleAccount *account, const char *remote_user,
- const char *id, const char *alias, const char *message, gboolean on_list,
- GCallback auth_cb, GCallback deny_cb, void *user_data)
+ const char *id, const char *alias, const char *message, gboolean on_list,
+ PurpleAccountRequestAuthorizationCb auth_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data)
{
PurpleAccountUiOps *ui_ops;
PurpleAccountRequestInfo *info;
@@ -1146,8 +1146,8 @@ purple_account_request_authorization(PurpleAccount *account, const char *remote_
info->type = PURPLE_ACCOUNT_REQUEST_AUTHORIZATION;
info->account = account;
info->ui_handle = ui_ops->request_authorize(account, remote_user, id, alias, message,
- on_list, auth_cb, deny_cb, user_data);
-
+ on_list, auth_cb, deny_cb, user_data);
+
handles = g_list_append(handles, info);
return info->ui_handle;
}
diff --git a/libpurple/account.h b/libpurple/account.h
index 6ce5631d37..23f0d5ed3b 100644
--- a/libpurple/account.h
+++ b/libpurple/account.h
@@ -51,20 +51,50 @@ typedef enum
PURPLE_ACCOUNT_REQUEST_AUTHORIZATION = 0 /* Account authorization request */
} PurpleAccountRequestType;
+
+/** Account UI operations, used to notify the user of status changes and when
+ * buddies add this account to their buddy lists.
+ */
struct _PurpleAccountUiOps
{
- /* A buddy we already have added us to their buddy list. */
- void (*notify_added)(PurpleAccount *account, const char *remote_user,
- const char *id, const char *alias,
+ /** A buddy who is already on this account's buddy list added this account
+ * to their buddy list.
+ */
+ void (*notify_added)(PurpleAccount *account,
+ const char *remote_user,
+ const char *id,
+ const char *alias,
const char *message);
- void (*status_changed)(PurpleAccount *account, PurpleStatus *status);
- /* Someone we don't have on our list added us. Will prompt to add them. */
- void (*request_add)(PurpleAccount *account, const char *remote_user,
- const char *id, const char *alias,
+
+ /** This account's status changed. */
+ void (*status_changed)(PurpleAccount *account,
+ PurpleStatus *status);
+
+ /** Someone we don't have on our list added us; prompt to add them. */
+ void (*request_add)(PurpleAccount *account,
+ const char *remote_user,
+ const char *id,
+ const char *alias,
const char *message);
- void *(*request_authorize)(PurpleAccount *account, const char *remote_user, const char *id,
- const char *alias, const char *message, gboolean on_list,
- GCallback authorize_cb, GCallback deny_cb, void *user_data);
+
+ /** Prompt for authorization when someone adds this account to their buddy
+ * list. To authorize them to see this account's presence, call \a
+ * authorize_cb (\a user_data); otherwise call \a deny_cb (\a user_data);
+ * @return a UI-specific handle, as passed to #close_account_request.
+ */
+ void *(*request_authorize)(PurpleAccount *account,
+ const char *remote_user,
+ const char *id,
+ const char *alias,
+ const char *message,
+ gboolean on_list,
+ PurpleAccountRequestAuthorizationCb authorize_cb,
+ PurpleAccountRequestAuthorizationCb deny_cb,
+ void *user_data);
+
+ /** Close a pending request for authorization. \a ui_handle is a handle
+ * as returned by #request_authorize.
+ */
void (*close_account_request)(void *ui_handle);
void (*_purple_reserved1)(void);
@@ -193,7 +223,7 @@ void purple_account_request_add(PurpleAccount *account, const char *remote_user,
/**
* Notifies the user that a remote user has wants to add the local user
- * to his or her buddy list and requires authorization to d oso.
+ * to his or her buddy list and requires authorization to do so.
*
* This will present a dialog informing the user of this and ask if the
* user authorizes or denies the remote user from adding him.
@@ -212,7 +242,7 @@ void purple_account_request_add(PurpleAccount *account, const char *remote_user,
*/
void *purple_account_request_authorization(PurpleAccount *account, const char *remote_user,
const char *id, const char *alias, const char *message, gboolean on_list,
- GCallback auth_cb, GCallback deny_cb, void *user_data);
+ PurpleAccountRequestAuthorizationCb auth_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data);
/**
* Close account requests registered for the given PurpleAccount
diff --git a/libpurple/connection.h b/libpurple/connection.h
index 56f586ccce..859a090cea 100644
--- a/libpurple/connection.h
+++ b/libpurple/connection.h
@@ -60,15 +60,52 @@ typedef enum
#include "plugin.h"
#include "status.h"
+/** Connection UI operations. Used to notify the user of changes to
+ * connections, such as being disconnected, and to respond to the
+ * underlying network connection appearing and disappearing. UIs should
+ * call #purple_connections_set_ui_ops() with an instance of this struct.
+ *
+ * @see @ref ui-ops
+ */
typedef struct
{
- void (*connect_progress)(PurpleConnection *gc, const char *text,
- size_t step, size_t step_count);
+ /** When an account is connecting, this operation is called to notify
+ * the UI of what is happening, as well as which @a step out of @a
+ * step_count has been reached (which might be displayed as a progress
+ * bar).
+ */
+ void (*connect_progress)(PurpleConnection *gc,
+ const char *text,
+ size_t step,
+ size_t step_count);
+ /** Called when a connection is established (just before the
+ * @ref signed-on signal).
+ */
void (*connected)(PurpleConnection *gc);
+ /** Called when a connection is ended (between the @ref signing-off
+ * and @ref signed-off signals).
+ */
void (*disconnected)(PurpleConnection *gc);
+ /** Used to display connection-specific notices. (Pidgin's Gtk user
+ * interface implements this as a no-op; #purple_connection_notice(),
+ * which uses this operation, is not used by any of the protocols
+ * shipped with libpurple.)
+ */
void (*notice)(PurpleConnection *gc, const char *text);
+ /** Called when an error causes a connection to be disconnected.
+ * Called before #disconnected.
+ * @param text a localized error message.
+ */
void (*report_disconnect)(PurpleConnection *gc, const char *text);
+ /** Called when libpurple discovers that the computer's network
+ * connection is active. On Linux, this uses Network Manager if
+ * available; on Windows, it uses Win32's network change notification
+ * infrastructure.
+ */
void (*network_connected)();
+ /** Called when libpurple discovers that the computer's network
+ * connection has gone away.
+ */
void (*network_disconnected)();
void (*_purple_reserved1)(void);
diff --git a/libpurple/conversation.c b/libpurple/conversation.c
index 53432fc18a..f596e86a51 100644
--- a/libpurple/conversation.c
+++ b/libpurple/conversation.c
@@ -1106,7 +1106,7 @@ purple_conv_im_write(PurpleConvIm *im, const char *who, const char *message,
c = purple_conv_im_get_conversation(im);
- /* Raise the window, if specified in prefs. */
+ /* Pass this on to either the ops structure or the default write func. */
if (c->ui_ops != NULL && c->ui_ops->write_im != NULL)
c->ui_ops->write_im(c, who, message, flags, mtime);
else
diff --git a/libpurple/conversation.h b/libpurple/conversation.h
index b860e24304..97b37b1f8e 100644
--- a/libpurple/conversation.h
+++ b/libpurple/conversation.h
@@ -149,27 +149,74 @@ typedef enum
*/
struct _PurpleConversationUiOps
{
+ /** Called when @a conv is created (but before the @ref
+ * conversation-created signal is emitted).
+ */
void (*create_conversation)(PurpleConversation *conv);
+
+ /** Called just before @a conv is freed. */
void (*destroy_conversation)(PurpleConversation *conv);
+ /** Write a message to a chat. If this field is @c NULL, libpurple will
+ * fall back to using #write_conv.
+ * @see purple_conv_chat_write()
+ */
void (*write_chat)(PurpleConversation *conv, const char *who,
const char *message, PurpleMessageFlags flags,
time_t mtime);
+ /** Write a message to an IM conversation. If this field is @c NULL,
+ * libpurple will fall back to using #write_conv.
+ * @see purple_conv_im_write()
+ */
void (*write_im)(PurpleConversation *conv, const char *who,
const char *message, PurpleMessageFlags flags,
time_t mtime);
- void (*write_conv)(PurpleConversation *conv, const char *name, const char *alias,
- const char *message, PurpleMessageFlags flags,
+ /** Write a message to a conversation. This is used rather than
+ * the chat- or im-specific ops for generic messages, such as system
+ * messages like "x is now know as y".
+ * @see purple_conversation_write()
+ */
+ void (*write_conv)(PurpleConversation *conv,
+ const char *name,
+ const char *alias,
+ const char *message,
+ PurpleMessageFlags flags,
time_t mtime);
- void (*chat_add_users)(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals);
-
+ /** Add @a cbuddies to a chat.
+ * @param cbuddies A @C GList of #PurpleConvChatBuddy structs.
+ * @param new_arrivals Whether join notices should be shown.
+ * (Join notices are actually written to the
+ * conversation by #purple_conv_chat_add_users().)
+ */
+ void (*chat_add_users)(PurpleConversation *conv,
+ GList *cbuddies,
+ gboolean new_arrivals);
+ /** Rename the user in this chat named @a old_name to @a new_name. (The
+ * rename message is written to the conversation by libpurple.)
+ * @param new_alias @a new_name's new alias, if they have one.
+ * @see purple_conv_chat_add_users()
+ */
void (*chat_rename_user)(PurpleConversation *conv, const char *old_name,
const char *new_name, const char *new_alias);
+ /** Remove @a users from a chat.
+ * @param users A @C GList of <tt>const char *</tt>s.
+ * @see purple_conv_chat_rename_user()
+ */
void (*chat_remove_users)(PurpleConversation *conv, GList *users);
+ /** Called when a user's flags are changed.
+ * @see purple_conv_chat_user_set_flags()
+ */
void (*chat_update_user)(PurpleConversation *conv, const char *user);
+ /** Present this conversation to the user; for example, by displaying
+ * the IM dialog.
+ */
void (*present)(PurpleConversation *conv);
+ /** If this UI has a concept of focus (as in a windowing system) and
+ * this conversation has the focus, return @c TRUE; otherwise, return
+ * @c FALSE.
+ */
gboolean (*has_focus)(PurpleConversation *conv);
/* Custom Smileys */
@@ -178,6 +225,11 @@ struct _PurpleConversationUiOps
const guchar *data, gsize size);
void (*custom_smiley_close)(PurpleConversation *conv, const char *smile);
+ /** Prompt the user for confirmation to send @a message. This function
+ * should arrange for the message to be sent if the user accepts. If
+ * this field is @c NULL, libpurple will fall back to using
+ * #purple_request_action().
+ */
void (*send_confirm)(PurpleConversation *conv, const char *message);
void (*_purple_reserved1)(void);
diff --git a/libpurple/core.c b/libpurple/core.c
index 956eaaa173..5f1e31739f 100644
--- a/libpurple/core.c
+++ b/libpurple/core.c
@@ -161,8 +161,9 @@ purple_core_init(const char *ui)
/*
* Call this early on to try to auto-detect our IP address and
* hopefully save some time later.
+ * TODO: do this here after purple_prefs_load() has been moved into purple_prefs_init()
*/
- purple_network_get_my_ip(-1);
+ /*purple_network_get_my_ip(-1);*/
if (ops != NULL && ops->ui_init != NULL)
ops->ui_init();
diff --git a/libpurple/log.c b/libpurple/log.c
index 166ebc7a80..8d8788daa2 100644
--- a/libpurple/log.c
+++ b/libpurple/log.c
@@ -718,9 +718,9 @@ convert_image_tags(const PurpleLog *log, const char *msg)
if (tmp < start)
g_string_append_len(newmsg, tmp, start - tmp);
- idstr = g_datalist_get_data(&attributes, "id");
+ if ((idstr = g_datalist_get_data(&attributes, "id")) != NULL)
+ imgid = atoi(idstr);
- imgid = atoi(idstr);
if (imgid != 0)
{
FILE *image_file;
@@ -735,6 +735,7 @@ convert_image_tags(const PurpleLog *log, const char *msg)
if (image == NULL)
{
/* This should never happen. */
+ /* This *does* happen for failed Direct-IMs -DAA */
g_string_free(newmsg, TRUE);
g_return_val_if_reached((char *)msg);
}
diff --git a/libpurple/plugins/log_reader.c b/libpurple/plugins/log_reader.c
index a8ebdfc5e3..0e5f692e58 100644
--- a/libpurple/plugins/log_reader.c
+++ b/libpurple/plugins/log_reader.c
@@ -60,14 +60,14 @@ static GList *adium_logger_list(PurpleLogType type, const char *sn, PurpleAccoun
char *path;
GDir *dir;
- g_return_val_if_fail(sn != NULL, list);
- g_return_val_if_fail(account != NULL, list);
+ g_return_val_if_fail(sn != NULL, NULL);
+ g_return_val_if_fail(account != NULL, NULL);
logdir = purple_prefs_get_string("/plugins/core/log_reader/adium/log_directory");
/* By clearing the log directory path, this logger can be (effectively) disabled. */
- if (!*logdir)
- return list;
+ if (!logdir || !*logdir)
+ return NULL;
plugin = purple_find_prpl(purple_account_get_protocol_id(account));
if (!plugin)
@@ -236,7 +236,8 @@ static char *adium_logger_read (PurpleLog *log, PurpleLogReadFlags *flags)
/* XXX: TODO: We probably want to set PURPLE_LOG_READ_NO_NEWLINE
* XXX: TODO: for HTML logs. */
- *flags = 0;
+ if (flags != NULL)
+ *flags = 0;
g_return_val_if_fail(log != NULL, g_strdup(""));
@@ -625,17 +626,17 @@ static GList *msn_logger_list(PurpleLogType type, const char *sn, PurpleAccount
const char *old_session_id = "";
struct msn_logger_data *data = NULL;
- g_return_val_if_fail(sn != NULL, list);
- g_return_val_if_fail(account != NULL, list);
+ g_return_val_if_fail(sn != NULL, NULL);
+ g_return_val_if_fail(account != NULL, NULL);
if (strcmp(account->protocol_id, "prpl-msn"))
- return list;
+ return NULL;
logdir = purple_prefs_get_string("/plugins/core/log_reader/msn/log_directory");
/* By clearing the log directory path, this logger can be (effectively) disabled. */
- if (!*logdir)
- return list;
+ if (!logdir || !*logdir)
+ return NULL;
buddy = purple_find_buddy(account, sn);
@@ -874,7 +875,8 @@ static char * msn_logger_read (PurpleLog *log, PurpleLogReadFlags *flags)
GString *text = NULL;
xmlnode *message;
- *flags = PURPLE_LOG_READ_NO_NEWLINE;
+ if (flags != NULL)
+ *flags = PURPLE_LOG_READ_NO_NEWLINE;
g_return_val_if_fail(log != NULL, g_strdup(""));
data = log->logger_data;
@@ -1119,7 +1121,7 @@ static char * msn_logger_read (PurpleLog *log, PurpleLogReadFlags *flags)
if (name_guessed != NAME_GUESS_UNKNOWN)
text = g_string_append(text, "</span>");
- style = xmlnode_get_attrib(text_node, "Style");
+ style = xmlnode_get_attrib(text_node, "Style");
tmp = xmlnode_get_data(text_node);
if (style && *style) {
@@ -1209,14 +1211,14 @@ static GList *trillian_logger_list(PurpleLogType type, const char *sn, PurpleAcc
gchar *line;
gchar *c;
- g_return_val_if_fail(sn != NULL, list);
- g_return_val_if_fail(account != NULL, list);
+ g_return_val_if_fail(sn != NULL, NULL);
+ g_return_val_if_fail(account != NULL, NULL);
logdir = purple_prefs_get_string("/plugins/core/log_reader/trillian/log_directory");
/* By clearing the log directory path, this logger can be (effectively) disabled. */
- if (!*logdir)
- return list;
+ if (!logdir || !*logdir)
+ return NULL;
plugin = purple_find_prpl(purple_account_get_protocol_id(account));
if (!plugin)
@@ -1427,7 +1429,9 @@ static char * trillian_logger_read (PurpleLog *log, PurpleLogReadFlags *flags)
char *c;
const char *line;
- *flags = PURPLE_LOG_READ_NO_NEWLINE;
+ if (flags != NULL)
+ *flags = PURPLE_LOG_READ_NO_NEWLINE;
+
g_return_val_if_fail(log != NULL, g_strdup(""));
data = log->logger_data;
@@ -1772,18 +1776,18 @@ static GList *qip_logger_list(PurpleLogType type, const char *sn, PurpleAccount
int offset = 0;
GError *error;
- g_return_val_if_fail(sn != NULL, list);
- g_return_val_if_fail(account != NULL, list);
+ g_return_val_if_fail(sn != NULL, NULL);
+ g_return_val_if_fail(account != NULL, NULL);
/* QIP only supports ICQ. */
if (strcmp(account->protocol_id, "prpl-icq"))
- return list;
+ return NULL;
logdir = purple_prefs_get_string("/plugins/core/log_reader/qip/log_directory");
/* By clearing the log directory path, this logger can be (effectively) disabled. */
- if (!*logdir)
- return list;
+ if (!logdir || !*logdir)
+ return NULL;
plugin = purple_find_prpl(purple_account_get_protocol_id(account));
if (!plugin)
@@ -1923,7 +1927,9 @@ static char *qip_logger_read(PurpleLog *log, PurpleLogReadFlags *flags)
char *utf8_string;
FILE *file;
- *flags = PURPLE_LOG_READ_NO_NEWLINE;
+ if (flags != NULL)
+ *flags = PURPLE_LOG_READ_NO_NEWLINE;
+
g_return_val_if_fail(log != NULL, g_strdup(""));
data = log->logger_data;
diff --git a/libpurple/plugins/perl/perl-handlers.c b/libpurple/plugins/perl/perl-handlers.c
index 614dbb1f85..a786672f44 100644
--- a/libpurple/plugins/perl/perl-handlers.c
+++ b/libpurple/plugins/perl/perl-handlers.c
@@ -22,6 +22,7 @@ purple_perl_plugin_action_cb(PurplePluginAction *action)
gchar *hvname;
PurplePlugin *plugin;
PurplePerlScript *gps;
+ STRLEN na;
dSP;
plugin = action->plugin;
@@ -45,9 +46,16 @@ purple_perl_plugin_action_cb(PurplePluginAction *action)
XPUSHs(purple_perl_bless_object(gps->plugin, "Purple::Plugin"));
PUTBACK;
- call_sv(*callback, G_VOID | G_DISCARD);
+ call_sv(*callback, G_EVAL | G_VOID | G_DISCARD);
+
SPAGAIN;
+ if (SvTRUE(ERRSV)) {
+ purple_debug_error("perl",
+ "Perl plugin action function exited abnormally: %s\n",
+ SvPV(ERRSV, na));
+ }
+
PUTBACK;
FREETMPS;
LEAVE;
@@ -59,6 +67,7 @@ purple_perl_plugin_actions(PurplePlugin *plugin, gpointer context)
GList *l = NULL;
PurplePerlScript *gps;
int i = 0, count = 0;
+ STRLEN na;
dSP;
gps = (PurplePerlScript *)plugin->info->extra_info;
@@ -77,10 +86,16 @@ purple_perl_plugin_actions(PurplePlugin *plugin, gpointer context)
XPUSHs(&PL_sv_undef);
PUTBACK;
- count = call_pv(gps->plugin_action_sub, G_ARRAY);
+ count = call_pv(gps->plugin_action_sub, G_EVAL | G_ARRAY);
SPAGAIN;
+ if (SvTRUE(ERRSV)) {
+ purple_debug_error("perl",
+ "Perl plugin actions lookup exited abnormally: %s\n",
+ SvPV(ERRSV, na));
+ }
+
if (count == 0)
croak("The plugin_actions sub didn't return anything.\n");
@@ -113,6 +128,7 @@ purple_perl_gtk_get_plugin_frame(PurplePlugin *plugin)
MAGIC *mg;
GtkWidget *ret;
PurplePerlScript *gps;
+ STRLEN na;
dSP;
gps = (PurplePerlScript *)plugin->info->extra_info;
@@ -120,13 +136,19 @@ purple_perl_gtk_get_plugin_frame(PurplePlugin *plugin)
ENTER;
SAVETMPS;
- count = call_pv(gps->gtk_prefs_sub, G_SCALAR | G_NOARGS);
+ count = call_pv(gps->gtk_prefs_sub, G_EVAL | G_SCALAR | G_NOARGS);
if (count != 1)
croak("call_pv: Did not return the correct number of values.\n");
/* the frame was created in a perl sub and is returned */
SPAGAIN;
+ if (SvTRUE(ERRSV)) {
+ purple_debug_error("perl",
+ "Perl gtk plugin frame init exited abnormally: %s\n",
+ SvPV(ERRSV, na));
+ }
+
/* We have a Gtk2::Frame on top of the stack */
sv = POPs;
@@ -150,6 +172,7 @@ purple_perl_get_plugin_frame(PurplePlugin *plugin)
int count;
PurplePerlScript *gps;
PurplePluginPrefFrame *ret_frame;
+ STRLEN na;
dSP;
gps = (PurplePerlScript *)plugin->info->extra_info;
@@ -161,10 +184,16 @@ purple_perl_get_plugin_frame(PurplePlugin *plugin)
PUSHMARK(SP);
PUTBACK;
- count = call_pv(gps->prefs_sub, G_SCALAR | G_NOARGS);
+ count = call_pv(gps->prefs_sub, G_EVAL | G_SCALAR | G_NOARGS);
SPAGAIN;
+ if (SvTRUE(ERRSV)) {
+ purple_debug_error("perl",
+ "Perl plugin prefs frame init exited abnormally: %s\n",
+ SvPV(ERRSV, na));
+ }
+
if (count != 1)
croak("call_pv: Did not return the correct number of values.\n");
/* the frame was created in a perl sub and is returned */
@@ -215,6 +244,7 @@ perl_timeout_cb(gpointer data)
{
PurplePerlTimeoutHandler *handler = (PurplePerlTimeoutHandler *)data;
gboolean ret = FALSE;
+ STRLEN na;
dSP;
ENTER;
@@ -225,6 +255,12 @@ perl_timeout_cb(gpointer data)
call_sv(handler->callback, G_EVAL | G_SCALAR);
SPAGAIN;
+ if (SvTRUE(ERRSV)) {
+ purple_debug_error("perl",
+ "Perl timeout function exited abnormally: %s\n",
+ SvPV(ERRSV, na));
+ }
+
ret = POPi;
PUTBACK;
@@ -285,7 +321,7 @@ perl_signal_cb(va_list args, void *data)
else
ret_val = purple_perl_data_from_sv(ret_value, POPs);
} else {
- call_sv(handler->callback, G_SCALAR);
+ call_sv(handler->callback, G_EVAL | G_SCALAR);
SPAGAIN;
}
@@ -501,6 +537,7 @@ perl_cmd_cb(PurpleConversation *conv, const gchar *command,
gchar **args, gchar **error, void *data)
{
int i = 0, count, ret_value = PURPLE_CMD_RET_OK;
+ STRLEN na;
SV *cmdSV, *tmpSV, *convSV;
PurplePerlCmdHandler *handler = (PurplePerlCmdHandler *)data;
@@ -532,11 +569,17 @@ perl_cmd_cb(PurpleConversation *conv, const gchar *command,
}
PUTBACK;
- count = call_sv(handler->callback, G_EVAL|G_SCALAR);
+ count = call_sv(handler->callback, G_EVAL | G_SCALAR);
if (count != 1)
croak("call_sv: Did not return the correct number of values.\n");
+ if (SvTRUE(ERRSV)) {
+ purple_debug_error("perl",
+ "Perl plugin command function exited abnormally: %s\n",
+ SvPV(ERRSV, na));
+ }
+
SPAGAIN;
ret_value = POPi;
diff --git a/libpurple/prefs.c b/libpurple/prefs.c
index 369c2a7a09..212adf32da 100644
--- a/libpurple/prefs.c
+++ b/libpurple/prefs.c
@@ -1341,7 +1341,6 @@ purple_prefs_get_children_names(const char *name)
list = g_list_append(list, g_strdup_printf("%s%s%s", name, sep, child->name));
}
return list;
-
}
void
diff --git a/libpurple/protocols/bonjour/Makefile.am b/libpurple/protocols/bonjour/Makefile.am
index 2e1cf18e75..bb33e01116 100644
--- a/libpurple/protocols/bonjour/Makefile.am
+++ b/libpurple/protocols/bonjour/Makefile.am
@@ -1,7 +1,7 @@
EXTRA_DIST = \
- mdns_win32.c \
- mdns_win32.h \
- Makefile.mingw
+ mdns_win32.c \
+ dns_sd_proxy.h \
+ Makefile.mingw
pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
@@ -10,16 +10,24 @@ BONJOURSOURCES = \
bonjour.h \
buddy.c \
buddy.h \
- dns_sd_proxy.h \
jabber.c \
jabber.h \
mdns_common.c \
mdns_common.h \
- mdns_howl.c \
- mdns_howl.h \
- mdns_types.h
+ mdns_interface.h \
+ mdns_types.h \
+ parser.c \
+ parser.h
-AM_CFLAGS = $(st) -DUSE_BONJOUR_HOWL
+if MDNS_AVAHI
+ BONJOURSOURCES += mdns_avahi.c
+else
+if MDNS_HOWL
+ BONJOURSOURCES += mdns_howl.c
+endif
+endif
+
+AM_CFLAGS = $(st)
libbonjour_la_LDFLAGS = -module -avoid-version
@@ -29,14 +37,29 @@ st = -DPURPLE_STATIC_PRPL
noinst_LIBRARIES = libbonjour.a
libbonjour_a_SOURCES = $(BONJOURSOURCES)
libbonjour_a_CFLAGS = $(AM_CFLAGS)
-libbonjour_a_LIBADD = $(HOWL_LIBS)
+libbonjour_a_LIBADD =
+
+if MDNS_AVAHI
+ libbonjour_a_LIBADD += $(AVAHI_LIBS)
+else
+if MDNS_HOWL
+ libbonjour_a_LIBADD += $(HOWL_LIBS)
+endif
+endif
else
st =
pkg_LTLIBRARIES = libbonjour.la
libbonjour_la_SOURCES = $(BONJOURSOURCES)
-libbonjour_la_LIBADD = $(GLIB_LIBS) $(HOWL_LIBS)
+libbonjour_la_LIBADD = $(GLIB_LIBS) $(LIBXML_LIBS)
+if MDNS_AVAHI
+ libbonjour_la_LIBADD += $(AVAHI_LIBS)
+else
+if MDNS_HOWL
+ libbonjour_la_LIBADD += $(HOWL_LIBS)
+endif
+endif
endif
@@ -46,4 +69,13 @@ AM_CPPFLAGS = \
-I$(top_builddir)/libpurple \
$(GLIB_CFLAGS) \
$(DEBUG_CFLAGS) \
- $(HOWL_CFLAGS)
+ $(LIBXML_CFLAGS)
+
+if MDNS_AVAHI
+ AM_CPPFLAGS += $(AVAHI_CFLAGS)
+else
+if MDNS_HOWL
+ AM_CPPFLAGS += $(HOWL_CFLAGS)
+endif
+endif
+
diff --git a/libpurple/protocols/bonjour/Makefile.mingw b/libpurple/protocols/bonjour/Makefile.mingw
index 2a8050906a..765277091a 100644
--- a/libpurple/protocols/bonjour/Makefile.mingw
+++ b/libpurple/protocols/bonjour/Makefile.mingw
@@ -30,12 +30,14 @@ INCLUDE_PATHS += -I. \
-I$(GTK_TOP)/include/glib-2.0 \
-I$(GTK_TOP)/lib/glib-2.0/include \
-I$(BONJOUR_TOP)/include \
+ -I$(LIBXML2_TOP)/include \
-I$(PURPLE_TOP) \
-I$(PURPLE_TOP)/win32 \
-I$(PIDGIN_TREE_TOP)
LIB_PATHS += -L$(GTK_TOP)/lib \
-L$(BONJOUR_TOP)/lib \
+ -L$(LIBXML2_TOP)/lib \
-L$(PURPLE_TOP)
##
@@ -45,6 +47,7 @@ C_SRC = bonjour.c \
buddy.c \
mdns_common.c \
mdns_win32.c \
+ parser.c \
jabber.c
OBJECTS = $(C_SRC:%.c=%.o)
@@ -58,6 +61,7 @@ LIBS = \
-lintl \
-ldnssd \
-lnetapi32 \
+ -lxml2 \
-lpurple
include $(PIDGIN_COMMON_RULES)
diff --git a/libpurple/protocols/bonjour/bonjour.c b/libpurple/protocols/bonjour/bonjour.c
index 00b23176b6..27f284424c 100644
--- a/libpurple/protocols/bonjour/bonjour.c
+++ b/libpurple/protocols/bonjour/bonjour.c
@@ -138,6 +138,8 @@ bonjour_login(PurpleAccount *account)
return;
}
+ bonjour_dns_sd_update_buddy_icon(bd->dns_sd_data);
+
/* Create a group for bonjour buddies */
bonjour_group = purple_group_new(BONJOUR_GROUP_NAME);
purple_blist_add_group(bonjour_group, NULL);
@@ -283,17 +285,33 @@ bonjour_convo_closed(PurpleConnection *connection, const char *who)
bb->conversation = NULL;
}
+static
+void bonjour_set_buddy_icon(PurpleConnection *conn, PurpleStoredImage *img)
+{
+ BonjourData *bd = conn->proto_data;
+ bonjour_dns_sd_update_buddy_icon(bd->dns_sd_data);
+}
+
+
static char *
bonjour_status_text(PurpleBuddy *buddy)
{
- PurplePresence *presence;
+ const PurplePresence *presence;
+ const PurpleStatus *status;
+ const char *message;
+ gchar *ret = NULL;
presence = purple_buddy_get_presence(buddy);
+ status = purple_presence_get_active_status(presence);
- if (purple_presence_is_online(presence) && !purple_presence_is_available(presence))
- return g_strdup(_("Away"));
+ message = purple_status_get_attr_string(status, "message");
- return NULL;
+ if (message != NULL) {
+ ret = g_markup_escape_text(message, -1);
+ purple_util_chrreplace(ret, '\n', ' ');
+ }
+
+ return ret;
}
static void
@@ -301,6 +319,7 @@ bonjour_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboole
{
PurplePresence *presence;
PurpleStatus *status;
+ BonjourBuddy *bb = buddy->proto_data;
const char *status_description;
const char *message;
@@ -318,6 +337,23 @@ bonjour_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboole
purple_notify_user_info_add_pair(user_info, _("Status"), status_description);
if (message != NULL)
purple_notify_user_info_add_pair(user_info, _("Message"), message);
+
+ /* Only show first/last name if there is a nickname set (to avoid duplication) */
+ if (bb->nick != NULL) {
+ if (bb->first != NULL)
+ purple_notify_user_info_add_pair(user_info, _("First name"), bb->first);
+ if (bb->first != NULL)
+ purple_notify_user_info_add_pair(user_info, _("Last name"), bb->last);
+ }
+
+ if (bb->email != NULL)
+ purple_notify_user_info_add_pair(user_info, _("E-Mail"), bb->email);
+
+ if (bb->AIM != NULL)
+ purple_notify_user_info_add_pair(user_info, _("AIM Account"), bb->AIM);
+
+ if (bb->jid!= NULL)
+ purple_notify_user_info_add_pair(user_info, _("XMPP Account"), bb->jid);
}
static gboolean
@@ -339,10 +375,9 @@ static PurplePluginProtocolInfo prpl_info =
OPT_PROTO_NO_PASSWORD,
NULL, /* user_splits */
NULL, /* protocol_options */
- /* {"png", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_DISPLAY}, */ /* icon_spec */
- NO_BUDDY_ICONS, /* not yet */ /* icon_spec */
+ {"png,gif,jpeg", 0, 0, 96, 96, 65535, PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
bonjour_list_icon, /* list_icon */
- NULL, /* list_emblem */
+ NULL, /* list_emblem */
bonjour_status_text, /* status_text */
bonjour_tooltip_text, /* tooltip_text */
bonjour_status_types, /* status_types */
@@ -384,7 +419,7 @@ static PurplePluginProtocolInfo prpl_info =
NULL, /* buddy_free */
bonjour_convo_closed, /* convo_closed */
NULL, /* normalize */
- NULL, /* set_buddy_icon */
+ bonjour_set_buddy_icon, /* set_buddy_icon */
NULL, /* remove_group */
NULL, /* get_cb_real_name */
NULL, /* set_chat_topic */
@@ -412,11 +447,11 @@ static PurplePluginInfo info =
PURPLE_PLUGIN_MAGIC,
PURPLE_MAJOR_VERSION,
PURPLE_MINOR_VERSION,
- PURPLE_PLUGIN_PROTOCOL, /**< type */
+ PURPLE_PLUGIN_PROTOCOL, /**< type */
NULL, /**< ui_requirement */
0, /**< flags */
NULL, /**< dependencies */
- PURPLE_PRIORITY_DEFAULT, /**< priority */
+ PURPLE_PRIORITY_DEFAULT, /**< priority */
"prpl-bonjour", /**< id */
"Bonjour", /**< name */
@@ -426,10 +461,10 @@ static PurplePluginInfo info =
/** description */
N_("Bonjour Protocol Plugin"),
NULL, /**< author */
- PURPLE_WEBSITE, /**< homepage */
+ PURPLE_WEBSITE, /**< homepage */
NULL, /**< load */
- plugin_unload, /**< unload */
+ plugin_unload, /**< unload */
NULL, /**< destroy */
NULL, /**< ui_info */
@@ -533,7 +568,7 @@ initialize_default_account_values()
{
default_firstname = g_strndup(fullname, splitpoint - fullname);
tmp = &splitpoint[1];
-
+
/* The last name may be followed by a comma and additional data.
* Only use the last name itself.
*/
diff --git a/libpurple/protocols/bonjour/buddy.c b/libpurple/protocols/bonjour/buddy.c
index 91c300bbdc..6901b77aad 100644
--- a/libpurple/protocols/bonjour/buddy.c
+++ b/libpurple/protocols/bonjour/buddy.c
@@ -18,10 +18,12 @@
#include <stdlib.h>
#include "internal.h"
+#include "cipher.h"
#include "buddy.h"
#include "account.h"
#include "blist.h"
#include "bonjour.h"
+#include "mdns_interface.h"
#include "debug.h"
/**
@@ -35,9 +37,31 @@ bonjour_buddy_new(const gchar *name, PurpleAccount* account)
buddy->account = account;
buddy->name = g_strdup(name);
+ _mdns_init_buddy(buddy);
+
return buddy;
}
+#define _B_CLR(x) g_free(x); x = NULL;
+
+void clear_bonjour_buddy_values(BonjourBuddy *buddy) {
+
+ _B_CLR(buddy->first)
+ _B_CLR(buddy->email);
+ _B_CLR(buddy->ext);
+ _B_CLR(buddy->jid);
+ _B_CLR(buddy->last);
+ _B_CLR(buddy->msg);
+ _B_CLR(buddy->nick);
+ _B_CLR(buddy->node);
+ _B_CLR(buddy->phsh);
+ _B_CLR(buddy->status);
+ _B_CLR(buddy->vc);
+ _B_CLR(buddy->ver);
+ _B_CLR(buddy->AIM);
+
+}
+
void
set_bonjour_buddy_value(BonjourBuddy* buddy, const char *record_key, const char *value, uint32_t len){
gchar **fld = NULL;
@@ -102,11 +126,11 @@ bonjour_buddy_add_to_purple(BonjourBuddy *bonjour_buddy)
{
PurpleBuddy *buddy;
PurpleGroup *group;
- const char *status_id, *first, *last;
- gchar *alias;
+ PurpleAccount *account = bonjour_buddy->account;
+ const char *status_id, *old_hash, *new_hash;
/* Translate between the Bonjour status and the Purple status */
- if (g_ascii_strcasecmp("dnd", bonjour_buddy->status) == 0)
+ if (bonjour_buddy->status != NULL && g_ascii_strcasecmp("dnd", bonjour_buddy->status) == 0)
status_id = BONJOUR_STATUS_ID_AWAY;
else
status_id = BONJOUR_STATUS_ID_AVAILABLE;
@@ -116,44 +140,95 @@ bonjour_buddy_add_to_purple(BonjourBuddy *bonjour_buddy)
* field from the DNS SD.
*/
- /* Create the alias for the buddy using the first and the last name */
- first = bonjour_buddy->first;
- last = bonjour_buddy->last;
- alias = g_strdup_printf("%s%s%s",
- (first && *first ? first : ""),
- (first && *first && last && *last ? " " : ""),
- (last && *last ? last : ""));
-
/* Make sure the Bonjour group exists in our buddy list */
group = purple_find_group(BONJOUR_GROUP_NAME); /* Use the buddy's domain, instead? */
- if (group == NULL)
- {
+ if (group == NULL) {
group = purple_group_new(BONJOUR_GROUP_NAME);
purple_blist_add_group(group, NULL);
}
/* Make sure the buddy exists in our buddy list */
- buddy = purple_find_buddy(bonjour_buddy->account, bonjour_buddy->name);
+ buddy = purple_find_buddy(account, bonjour_buddy->name);
- if (buddy == NULL)
- {
- buddy = purple_buddy_new(bonjour_buddy->account, bonjour_buddy->name, alias);
+ if (buddy == NULL) {
+ buddy = purple_buddy_new(account, bonjour_buddy->name, NULL);
buddy->proto_data = bonjour_buddy;
purple_blist_node_set_flags((PurpleBlistNode *)buddy, PURPLE_BLIST_NODE_FLAG_NO_SAVE);
purple_blist_add_buddy(buddy, NULL, group, NULL);
}
+ /* Create the alias for the buddy using the first and the last name */
+ if (bonjour_buddy->nick)
+ serv_got_alias(purple_account_get_connection(account), buddy->name, bonjour_buddy->nick);
+ else {
+ gchar *alias = NULL;
+ const char *first, *last;
+ first = bonjour_buddy->first;
+ last = bonjour_buddy->last;
+ if ((first && *first) || (last && *last))
+ alias = g_strdup_printf("%s%s%s",
+ (first && *first ? first : ""),
+ (first && *first && last && *last ? " " : ""),
+ (last && *last ? last : ""));
+ serv_got_alias(purple_account_get_connection(account), buddy->name, alias);
+ g_free(alias);
+ }
+
/* Set the user's status */
if (bonjour_buddy->msg != NULL)
- purple_prpl_got_user_status(bonjour_buddy->account, buddy->name, status_id,
- "message", bonjour_buddy->msg,
- NULL);
+ purple_prpl_got_user_status(account, buddy->name, status_id,
+ "message", bonjour_buddy->msg, NULL);
else
- purple_prpl_got_user_status(bonjour_buddy->account, buddy->name, status_id,
- NULL);
- purple_prpl_got_user_idle(bonjour_buddy->account, buddy->name, FALSE, 0);
+ purple_prpl_got_user_status(account, buddy->name, status_id, NULL);
- g_free(alias);
+ purple_prpl_got_user_idle(account, buddy->name, FALSE, 0);
+
+ /* TODO: Because we don't save Bonjour buddies in blist.xml,
+ * we will always have to look up the buddy icon at login time.
+ * I think we should figure out a way to do something about this. */
+
+ /* Deal with the buddy icon */
+ old_hash = purple_buddy_icons_get_checksum_for_user(buddy);
+ new_hash = (bonjour_buddy->phsh && *(bonjour_buddy->phsh)) ? bonjour_buddy->phsh : NULL;
+ if (new_hash && (!old_hash || strcmp(old_hash, new_hash) != 0)) {
+ /* Look up the new icon data */
+ /* TODO: Make sure the hash assigned to the retrieved buddy icon is the same
+ * as what we looked up. */
+ bonjour_dns_sd_retrieve_buddy_icon(bonjour_buddy);
+ } else if (!new_hash)
+ purple_buddy_icons_set_for_user(account, buddy->name, NULL, 0, NULL);
+}
+
+/**
+ * We got the buddy icon data; deal with it
+ */
+void bonjour_buddy_got_buddy_icon(BonjourBuddy *buddy, gconstpointer data, gsize len) {
+ /* Recalculate the hash instead of using the current phsh to make sure it is accurate for the icon. */
+ int i;
+ gchar *enc;
+ char *p, hash[41];
+ unsigned char hashval[20];
+
+ if (data == NULL || len == 0)
+ return;
+
+ enc = purple_base64_encode(data, len);
+
+ purple_cipher_digest_region("sha1", data,
+ len, sizeof(hashval),
+ hashval, NULL);
+
+ p = hash;
+ for(i=0; i<20; i++, p+=2)
+ snprintf(p, 3, "%02x", hashval[i]);
+
+ purple_debug_info("bonjour", "Got buddy icon for %s icon hash='%s' phsh='%s'.\n", buddy->name,
+ hash, buddy->phsh ? buddy->phsh : "(null)");
+
+ purple_buddy_icons_set_for_user(buddy->account, buddy->name,
+ g_memdup(data, len), len, hash);
+
+ g_free(enc);
}
/**
@@ -164,7 +239,6 @@ bonjour_buddy_delete(BonjourBuddy *buddy)
{
g_free(buddy->name);
g_free(buddy->ip);
-
g_free(buddy->first);
g_free(buddy->phsh);
g_free(buddy->status);
@@ -182,13 +256,8 @@ bonjour_buddy_delete(BonjourBuddy *buddy)
bonjour_jabber_close_conversation(buddy->conversation);
buddy->conversation = NULL;
-#ifdef USE_BONJOUR_APPLE
- if (buddy->txt_query != NULL)
- {
- purple_input_remove(buddy->txt_query_fd);
- DNSServiceRefDeallocate(buddy->txt_query);
- }
-#endif
+ /* Clean up any mdns implementation data */
+ _mdns_delete_buddy(buddy);
g_free(buddy);
}
diff --git a/libpurple/protocols/bonjour/buddy.h b/libpurple/protocols/bonjour/buddy.h
index 003c20f5c0..2093efba5e 100644
--- a/libpurple/protocols/bonjour/buddy.h
+++ b/libpurple/protocols/bonjour/buddy.h
@@ -19,21 +19,15 @@
#include <glib.h>
-#include "config.h"
#include "account.h"
#include "jabber.h"
-#ifdef USE_BONJOUR_APPLE
-#include "dns_sd_proxy.h"
-#else /* USE_BONJOUR_HOWL */
-#include <howl.h>
-#endif
-
typedef struct _BonjourBuddy
{
PurpleAccount *account;
gchar *name;
+ /* TODO: Remove and just use the hostname */
gchar *ip;
gint port_p2pj;
@@ -53,11 +47,7 @@ typedef struct _BonjourBuddy
BonjourJabberConversation *conversation;
-#ifdef USE_BONJOUR_APPLE
- DNSServiceRef txt_query;
- int txt_query_fd;
-#endif
-
+ gpointer mdns_impl_data;
} BonjourBuddy;
static const char *const buddy_TXT_records[] = {
@@ -85,9 +75,15 @@ static const char *const buddy_TXT_records[] = {
BonjourBuddy *bonjour_buddy_new(const gchar *name, PurpleAccount *account);
/**
+ * Clear any existing values from the buddy.
+ * This is called before updating so that we can notice removals
+ */
+void clear_bonjour_buddy_values(BonjourBuddy *buddy);
+
+/**
* Sets a value in the BonjourBuddy struct, destroying the old value
*/
-void set_bonjour_buddy_value(BonjourBuddy* buddy, const char *record_key, const char *value, uint32_t len);
+void set_bonjour_buddy_value(BonjourBuddy *buddy, const char *record_key, const char *value, uint32_t len);
/**
* Check if all the compulsory buddy data is present.
@@ -100,6 +96,11 @@ gboolean bonjour_buddy_check(BonjourBuddy *buddy);
void bonjour_buddy_add_to_purple(BonjourBuddy *buddy);
/**
+ * We got the buddy icon data; deal with it
+ */
+void bonjour_buddy_got_buddy_icon(BonjourBuddy *buddy, gconstpointer data, gsize len);
+
+/**
* Deletes a buddy from memory.
*/
void bonjour_buddy_delete(BonjourBuddy *buddy);
diff --git a/libpurple/protocols/bonjour/issues.txt b/libpurple/protocols/bonjour/issues.txt
index c80c1a5876..2222394449 100644
--- a/libpurple/protocols/bonjour/issues.txt
+++ b/libpurple/protocols/bonjour/issues.txt
@@ -3,6 +3,5 @@
==========================================
* Status changes don't work
-* Avatars
* File transfers
* Typing notifications
diff --git a/libpurple/protocols/bonjour/jabber.c b/libpurple/protocols/bonjour/jabber.c
index d32c010ace..91cdb2caf3 100644
--- a/libpurple/protocols/bonjour/jabber.c
+++ b/libpurple/protocols/bonjour/jabber.c
@@ -42,6 +42,7 @@
#include "util.h"
#include "jabber.h"
+#include "parser.h"
#include "bonjour.h"
#include "buddy.h"
@@ -109,9 +110,10 @@ _font_size_ichat_to_purple(int size)
}
static void
-_jabber_parse_and_write_message_to_ui(xmlnode *message_node, PurpleConnection *connection, PurpleBuddy *pb)
+_jabber_parse_and_write_message_to_ui(xmlnode *message_node, PurpleBuddy *pb)
{
xmlnode *body_node, *html_node, *events_node;
+ PurpleConnection *gc = pb->account->gc;
char *body, *html_body = NULL;
const char *ichat_balloon_color = NULL;
const char *ichat_text_color = NULL;
@@ -186,7 +188,7 @@ _jabber_parse_and_write_message_to_ui(xmlnode *message_node, PurpleConnection *c
/* TODO: Should we do something with "composing_event" here? */
/* Send the message to the UI */
- serv_got_im(connection, pb->name, body, 0, time(NULL));
+ serv_got_im(gc, pb->name, body, 0, time(NULL));
g_free(body);
g_free(html_body);
@@ -218,37 +220,6 @@ _check_buddy_by_address(gpointer key, gpointer value, gpointer data)
}
}
-static gint
-_read_data(gint socket, char **message)
-{
- GString *data = g_string_new("");
- char partial_data[512];
- gint total_message_length = 0;
- gint partial_message_length = 0;
-
- /* Read chunks of 512 bytes till the end of the data */
- while ((partial_message_length = recv(socket, partial_data, 512, 0)) > 0)
- {
- g_string_append_len(data, partial_data, partial_message_length);
- total_message_length += partial_message_length;
- }
-
- if (partial_message_length == -1)
- {
- if (errno != EAGAIN)
- purple_debug_warning("bonjour", "receive error: %s\n", strerror(errno));
- if (total_message_length == 0) {
- return -1;
- }
- }
-
- *message = g_string_free(data, FALSE);
- if (total_message_length != 0)
- purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", *message, total_message_length);
-
- return total_message_length;
-}
-
static void
_send_data_write_cb(gpointer data, gint source, PurpleInputCondition cond)
{
@@ -303,7 +274,8 @@ _send_data(PurpleBuddy *pb, char *message)
/* If we're not ready to actually send, append it to the buffer */
if (bconv->tx_handler != -1
|| bconv->connect_data != NULL
- || !bconv->stream_started
+ || !bconv->sent_stream_start
+ || !bconv->recv_stream_start
|| purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) {
ret = -1;
errno = EAGAIN;
@@ -341,20 +313,32 @@ _send_data(PurpleBuddy *pb, char *message)
return ret;
}
+void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet) {
+ if (!strcmp(packet->name, "message"))
+ _jabber_parse_and_write_message_to_ui(packet, pb);
+ else
+ purple_debug_warning("bonjour", "Unknown packet: %s\n",
+ packet->name);
+}
+
+
static void
_client_socket_handler(gpointer data, gint socket, PurpleInputCondition condition)
{
- char *message = NULL;
- gint message_length;
PurpleBuddy *pb = data;
- PurpleAccount *account = pb->account;
- BonjourBuddy *bb = pb->proto_data;
- gboolean closed_conversation = FALSE;
+ gint len, message_length;
+ static char message[4096];
+
+ /*TODO: use a static buffer */
/* Read the data from the socket */
- if ((message_length = _read_data(socket, &message)) == -1) {
+ if ((len = recv(socket, message, sizeof(message) - 1, 0)) == -1) {
/* There have been an error reading from the socket */
if (errno != EAGAIN) {
+ BonjourBuddy *bb = pb->proto_data;
+
+ purple_debug_warning("bonjour", "receive error: %s\n", strerror(errno));
+
bonjour_jabber_close_conversation(bb->conversation);
bb->conversation = NULL;
@@ -362,65 +346,71 @@ _client_socket_handler(gpointer data, gint socket, PurpleInputCondition conditio
* If they try to send another message it'll reconnect */
}
return;
- } else if (message_length == 0) { /* The other end has closed the socket */
- closed_conversation = TRUE;
+ } else if (len == 0) { /* The other end has closed the socket */
+ purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", pb->name);
+ bonjour_jabber_stream_ended(pb);
+ return;
} else {
+ message_length = len;
message[message_length] = '\0';
- while (g_ascii_iscntrl(message[message_length - 1])) {
+ while (message_length > 0 && g_ascii_iscntrl(message[message_length - 1])) {
message[message_length - 1] = '\0';
message_length--;
}
}
- /*
- * Check that this is not the end of the conversation. This is
- * using a magic string, but xmlnode won't play nice when just
- * parsing an end tag
- */
- if (closed_conversation || purple_str_has_prefix(message, STREAM_END)) {
- PurpleConversation *conv;
+ purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", message, len);
- /* Close the socket, clear the watcher and free memory */
- bonjour_jabber_close_conversation(bb->conversation);
- bb->conversation = NULL;
+ bonjour_parser_process(pb, message, message_length);
+}
- /* Inform the user that the conversation has been closed */
- conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, account);
- if (conv != NULL) {
- char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name);
- purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
- g_free(tmp);
- }
- } else {
- xmlnode *message_node;
+void bonjour_jabber_stream_ended(PurpleBuddy *pb) {
+ BonjourBuddy *bb = pb->proto_data;
+ PurpleConversation *conv;
- /* Parse the message into an XMLnode for analysis */
- message_node = xmlnode_from_str(message, strlen(message));
+ purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", pb->name);
- if (message_node != NULL) {
- /* Parse the message to get the data and send to the ui */
- _jabber_parse_and_write_message_to_ui(message_node, account->gc, pb);
- xmlnode_free(message_node);
- } else {
- /* TODO: Deal with receiving only a partial message */
- }
+ /* Close the socket, clear the watcher and free memory */
+ bonjour_jabber_close_conversation(bb->conversation);
+ bb->conversation = NULL;
+
+ /* Inform the user that the conversation has been closed */
+ conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, pb->account);
+ if (conv != NULL) {
+ char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name);
+ purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+ g_free(tmp);
+ }
+}
+
+void bonjour_jabber_stream_started(PurpleBuddy *pb) {
+ BonjourBuddy *bb = pb->proto_data;
+ BonjourJabberConversation *bconv = bb->conversation;
+
+ /* If the stream has been completely started, we can start doing stuff */
+ if (bconv->sent_stream_start && bconv->recv_stream_start && purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) {
+ /* Watch for when we can write the buffered messages */
+ bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE,
+ _send_data_write_cb, pb);
+ /* We can probably write the data right now. */
+ _send_data_write_cb(pb, bconv->socket, PURPLE_INPUT_WRITE);
}
- g_free(message);
}
struct _stream_start_data {
char *msg;
- PurpleInputFunction tx_handler_cb;
};
+
static void
_start_stream(gpointer data, gint source, PurpleInputCondition condition)
{
PurpleBuddy *pb = data;
BonjourBuddy *bb = pb->proto_data;
- struct _stream_start_data *ss = bb->conversation->stream_data;
+ BonjourJabberConversation *bconv = bb->conversation;
+ struct _stream_start_data *ss = bconv->stream_data;
int len, ret;
len = strlen(ss->msg);
@@ -443,7 +433,7 @@ _start_stream(gpointer data, gint source, PurpleInputCondition condition)
_("Unable to send the message, the conversation couldn't be started."),
PURPLE_MESSAGE_SYSTEM, time(NULL));
- bonjour_jabber_close_conversation(bb->conversation);
+ bonjour_jabber_close_conversation(bconv);
bb->conversation = NULL;
return;
@@ -457,22 +447,67 @@ _start_stream(gpointer data, gint source, PurpleInputCondition condition)
return;
}
- /* Stream started; process the send buffer if there is one*/
- purple_input_remove(bb->conversation->tx_handler);
- bb->conversation->tx_handler= -1;
-
- bb->conversation->stream_started = TRUE;
-
g_free(ss->msg);
g_free(ss);
- bb->conversation->stream_data = NULL;
+ bconv->stream_data = NULL;
+
+ /* Stream started; process the send buffer if there is one */
+ purple_input_remove(bconv->tx_handler);
+ bconv->tx_handler= -1;
+ bconv->sent_stream_start = TRUE;
+
+ bonjour_jabber_stream_started(pb);
+
+}
- if (ss->tx_handler_cb) {
- bb->conversation->tx_handler = purple_input_add(source, PURPLE_INPUT_WRITE,
- ss->tx_handler_cb, pb);
- /* We can probably write the data now. */
- (ss->tx_handler_cb)(pb, source, PURPLE_INPUT_WRITE);
+static gboolean bonjour_jabber_stream_init(PurpleBuddy *pb, int client_socket)
+{
+ int ret, len;
+ char *stream_start;
+ BonjourBuddy *bb = pb->proto_data;
+
+ stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account),
+ purple_buddy_get_name(pb));
+ len = strlen(stream_start);
+
+ /* Start the stream */
+ ret = send(client_socket, stream_start, len, 0);
+
+ if (ret == -1 && errno == EAGAIN)
+ ret = 0;
+ else if (ret <= 0) {
+ const char *err = strerror(errno);
+
+ purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
+ purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+
+ close(client_socket);
+ g_free(stream_start);
+
+ return FALSE;
}
+
+ /* This is unlikely to happen */
+ if (ret < len) {
+ struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
+ ss->msg = g_strdup(stream_start + ret);
+ bb->conversation->stream_data = ss;
+ /* Finish sending the stream start */
+ bb->conversation->tx_handler = purple_input_add(client_socket,
+ PURPLE_INPUT_WRITE, _start_stream, pb);
+ } else
+ bb->conversation->sent_stream_start = TRUE;
+
+ g_free(stream_start);
+
+ /* setup the parser fresh for each stream */
+ bonjour_parser_setup(bb->conversation);
+
+ bb->conversation->socket = client_socket;
+ bb->conversation->rx_handler = purple_input_add(client_socket,
+ PURPLE_INPUT_READ, _client_socket_handler, pb);
+
+ return TRUE;
}
static void
@@ -498,6 +533,7 @@ _server_socket_handler(gpointer data, int server_socket, PurpleInputCondition co
/* Look for the buddy that has opened the conversation and fill information */
address_text = inet_ntoa(their_addr.sin_addr);
+ purple_debug_info("bonjour", "Received incoming connection from %s\n.", address_text);
cbba = g_new0(struct _check_buddy_by_address_t, 1);
cbba->address = address_text;
cbba->pb = &pb;
@@ -515,49 +551,15 @@ _server_socket_handler(gpointer data, int server_socket, PurpleInputCondition co
/* Check if the conversation has been previously started */
if (bb->conversation == NULL)
{
- int ret, len;
- char *stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account),
- purple_buddy_get_name(pb));
-
- len = strlen(stream_start);
-
- /* Start the stream */
- ret = send(client_socket, stream_start, len, 0);
-
- if (ret == -1 && errno == EAGAIN)
- ret = 0;
- else if (ret <= 0) {
- const char *err = strerror(errno);
-
- purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
- purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+ bb->conversation = bonjour_jabber_conv_new();
+ if (!bonjour_jabber_stream_init(pb, client_socket)) {
close(client_socket);
- g_free(stream_start);
-
return;
}
- bb->conversation = bonjour_jabber_conv_new();
- bb->conversation->socket = client_socket;
- bb->conversation->rx_handler = purple_input_add(client_socket,
- PURPLE_INPUT_READ, _client_socket_handler, pb);
-
- /* This is unlikely to happen */
- if (ret < len) {
- struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
- ss->msg = g_strdup(stream_start + ret);
- ss->tx_handler_cb = NULL; /* We have nothing to write yet */
- bb->conversation->stream_data = ss;
- /* Finish sending the stream start */
- bb->conversation->tx_handler = purple_input_add(client_socket,
- PURPLE_INPUT_WRITE, _start_stream, pb);
- } else {
- bb->conversation->stream_started = TRUE;
- }
-
- g_free(stream_start);
} else {
+ purple_debug_warning("bonjour", "Ignoring incoming connection because an existing connection exists.\n");
close(client_socket);
}
}
@@ -639,8 +641,6 @@ _connected_to_buddy(gpointer data, gint source, const gchar *error)
{
PurpleBuddy *pb = data;
BonjourBuddy *bb = pb->proto_data;
- int len, ret;
- char *stream_start;
bb->conversation->connect_data = NULL;
@@ -661,15 +661,7 @@ _connected_to_buddy(gpointer data, gint source, const gchar *error)
return;
}
- stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account), purple_buddy_get_name(pb));
- len = strlen(stream_start);
-
- /* Start the stream and send queued messages */
- ret = send(source, stream_start, len, 0);
-
- if (ret == -1 && errno == EAGAIN)
- ret = 0;
- else if (ret <= 0) {
+ if (!bonjour_jabber_stream_init(pb, source)) {
const char *err = strerror(errno);
PurpleConversation *conv;
@@ -685,37 +677,8 @@ _connected_to_buddy(gpointer data, gint source, const gchar *error)
close(source);
bonjour_jabber_close_conversation(bb->conversation);
bb->conversation = NULL;
-
- g_free(stream_start);
-
return;
}
-
- bb->conversation->socket = source;
- bb->conversation->rx_handler = purple_input_add(source,
- PURPLE_INPUT_READ, _client_socket_handler, pb);
-
- /* This is unlikely to happen */
- if (ret < len) {
- struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
- ss->msg = g_strdup(stream_start + ret);
- ss->tx_handler_cb = _send_data_write_cb;
- bb->conversation->stream_data = ss;
- /* Finish sending the stream start */
- bb->conversation->tx_handler = purple_input_add(source,
- PURPLE_INPUT_WRITE, _start_stream, pb);
- }
- /* Process the send buffer */
- else {
- bb->conversation->stream_started = TRUE;
- /* Watch for when we can write the buffered messages */
- bb->conversation->tx_handler = purple_input_add(source, PURPLE_INPUT_WRITE,
- _send_data_write_cb, pb);
- /* We can probably write the data now. */
- _send_data_write_cb(pb, source, PURPLE_INPUT_WRITE);
- }
-
- g_free(stream_start);
}
int
@@ -809,7 +772,7 @@ bonjour_jabber_close_conversation(BonjourJabberConversation *bconv)
/* Close the socket and remove the watcher */
if (bconv->socket >= 0) {
/* Send the end of the stream to the other end of the conversation */
- if (bconv->stream_started)
+ if (bconv->sent_stream_start)
send(bconv->socket, STREAM_END, strlen(STREAM_END), 0);
/* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */
close(bconv->socket);
@@ -828,6 +791,10 @@ bonjour_jabber_close_conversation(BonjourJabberConversation *bconv)
g_free(ss->msg);
g_free(ss);
}
+
+ if (bconv->context != NULL)
+ bonjour_parser_setup(bconv);
+
g_free(bconv);
}
}
diff --git a/libpurple/protocols/bonjour/jabber.h b/libpurple/protocols/bonjour/jabber.h
index a8bc07894c..ab5b9a13d8 100644
--- a/libpurple/protocols/bonjour/jabber.h
+++ b/libpurple/protocols/bonjour/jabber.h
@@ -26,6 +26,10 @@
#ifndef _BONJOUR_JABBER_H_
#define _BONJOUR_JABBER_H_
+#include <libxml/parser.h>
+
+#include "xmlnode.h"
+
#include "account.h"
#include "circbuffer.h"
@@ -43,9 +47,12 @@ typedef struct _BonjourJabberConversation
guint rx_handler;
guint tx_handler;
PurpleCircBuffer *tx_buf;
- gboolean stream_started;
+ gboolean sent_stream_start;
+ gboolean recv_stream_start;
PurpleProxyConnectData *connect_data;
gpointer stream_data;
+ xmlParserCtxt *context;
+ xmlnode *current;
} BonjourJabberConversation;
/**
@@ -60,6 +67,12 @@ int bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gcha
void bonjour_jabber_close_conversation(BonjourJabberConversation *bconv);
+void bonjour_jabber_stream_started(PurpleBuddy *pb);
+
+void bonjour_jabber_stream_ended(PurpleBuddy *pb);
+
+void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet);
+
void bonjour_jabber_stop(BonjourJabber *data);
#endif /* _BONJOUR_JABBER_H_ */
diff --git a/libpurple/protocols/bonjour/mdns_avahi.c b/libpurple/protocols/bonjour/mdns_avahi.c
new file mode 100644
index 0000000000..0dc560e0f6
--- /dev/null
+++ b/libpurple/protocols/bonjour/mdns_avahi.c
@@ -0,0 +1,481 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "internal.h"
+
+#include "mdns_interface.h"
+#include "debug.h"
+#include "buddy.h"
+#include "bonjour.h"
+
+#include <avahi-client/client.h>
+#include <avahi-client/lookup.h>
+#include <avahi-client/publish.h>
+
+#include <avahi-common/address.h>
+#include <avahi-common/malloc.h>
+#include <avahi-common/error.h>
+#include <avahi-common/strlst.h>
+
+#include <avahi-glib/glib-malloc.h>
+#include <avahi-glib/glib-watch.h>
+
+/* For some reason, this is missing from the Avahi type defines */
+#ifndef AVAHI_DNS_TYPE_NULL
+#define AVAHI_DNS_TYPE_NULL 0x0A
+#endif
+
+/* data used by avahi bonjour implementation */
+typedef struct _avahi_session_impl_data {
+ AvahiClient *client;
+ AvahiGLibPoll *glib_poll;
+ AvahiServiceBrowser *sb;
+ AvahiEntryGroup *group;
+ AvahiEntryGroup *buddy_icon_group;
+} AvahiSessionImplData;
+
+typedef struct _avahi_buddy_impl_data {
+ AvahiServiceResolver *resolver;
+ AvahiRecordBrowser *buddy_icon_rec_browser;
+} AvahiBuddyImplData;
+
+static void
+_resolver_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol,
+ AvahiResolverEvent event, const char *name, const char *type, const char *domain,
+ const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt,
+ AvahiLookupResultFlags flags, void *userdata) {
+
+ BonjourBuddy *buddy;
+ PurpleAccount *account = userdata;
+ AvahiStringList *l;
+ size_t size;
+ char *key, *value;
+ int ret;
+
+ g_return_if_fail(r != NULL);
+
+ switch (event) {
+ case AVAHI_RESOLVER_FAILURE:
+ purple_debug_error("bonjour", "_resolve_callback - Failure: %s\n",
+ avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
+ avahi_service_resolver_free(r);
+ break;
+ case AVAHI_RESOLVER_FOUND:
+ /* create a buddy record */
+ buddy = bonjour_buddy_new(name, account);
+
+ ((AvahiBuddyImplData *)buddy->mdns_impl_data)->resolver = r;
+
+ /* Get the ip as a string */
+ buddy->ip = g_malloc(AVAHI_ADDRESS_STR_MAX);
+ avahi_address_snprint(buddy->ip, AVAHI_ADDRESS_STR_MAX, a);
+
+ buddy->port_p2pj = port;
+
+ /* Obtain the parameters from the text_record */
+ clear_bonjour_buddy_values(buddy);
+ l = txt;
+ while (l != NULL) {
+ ret = avahi_string_list_get_pair(l, &key, &value, &size);
+ l = l->next;
+ if (ret < 0)
+ continue;
+ set_bonjour_buddy_value(buddy, key, value, size);
+ /* TODO: Since we're using the glib allocator, I think we
+ * can use the values instead of re-copying them */
+ avahi_free(key);
+ avahi_free(value);
+ }
+
+ if (!bonjour_buddy_check(buddy))
+ bonjour_buddy_delete(buddy);
+ else
+ /* Add or update the buddy in our buddy list */
+ bonjour_buddy_add_to_purple(buddy);
+
+ break;
+ default:
+ purple_debug_info("bonjour", "Unrecognized Service Resolver event: %d.\n", event);
+ }
+
+}
+
+static void
+_browser_callback(AvahiServiceBrowser *b, AvahiIfIndex interface,
+ AvahiProtocol protocol, AvahiBrowserEvent event,
+ const char *name, const char *type, const char *domain,
+ AvahiLookupResultFlags flags, void *userdata) {
+
+ PurpleAccount *account = userdata;
+ PurpleBuddy *gb = NULL;
+
+ switch (event) {
+ case AVAHI_BROWSER_FAILURE:
+ purple_debug_error("bonjour", "_browser_callback - Failure: %s\n",
+ avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
+ /* TODO: This is an error that should be handled. */
+ break;
+ case AVAHI_BROWSER_NEW:
+ /* A new peer has joined the network and uses iChat bonjour */
+ purple_debug_info("bonjour", "_browser_callback - new service\n");
+ /* Make sure it isn't us */
+ if (g_ascii_strcasecmp(name, account->username) != 0) {
+ if (!avahi_service_resolver_new(avahi_service_browser_get_client(b),
+ interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC,
+ 0, _resolver_callback, account)) {
+ purple_debug_warning("bonjour", "_browser_callback -- Error initiating resolver: %s\n",
+ avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
+ }
+ }
+ break;
+ case AVAHI_BROWSER_REMOVE:
+ purple_debug_info("bonjour", "_browser_callback - Remove service\n");
+ gb = purple_find_buddy(account, name);
+ if (gb != NULL) {
+ bonjour_buddy_delete(gb->proto_data);
+ purple_blist_remove_buddy(gb);
+ }
+ break;
+ case AVAHI_BROWSER_ALL_FOR_NOW:
+ case AVAHI_BROWSER_CACHE_EXHAUSTED:
+ purple_debug_warning("bonjour", "(Browser) %s\n",
+ event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
+ break;
+ default:
+ purple_debug_info("bonjour", "Unrecognized Service browser event: %d.\n", event);
+ }
+}
+
+static void
+_buddy_icon_group_cb(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
+ BonjourDnsSd *data = userdata;
+ AvahiSessionImplData *idata = data->mdns_impl_data;
+
+ g_return_if_fail(g == idata->buddy_icon_group || idata->buddy_icon_group == NULL);
+
+ switch(state) {
+ case AVAHI_ENTRY_GROUP_ESTABLISHED:
+ purple_debug_info("bonjour", "Successfully registered buddy icon data.\n");
+ case AVAHI_ENTRY_GROUP_COLLISION:
+ purple_debug_error("bonjour", "Collision registering buddy icon data.\n");
+ break;
+ case AVAHI_ENTRY_GROUP_FAILURE:
+ purple_debug_error("bonjour", "Error registering buddy icon data: %s\n.",
+ avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
+ break;
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ break;
+ }
+
+}
+
+static void
+_entry_group_cb(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
+ AvahiSessionImplData *idata = userdata;
+
+ g_return_if_fail(g == idata->group || idata->group == NULL);
+
+ switch(state) {
+ case AVAHI_ENTRY_GROUP_ESTABLISHED:
+ purple_debug_info("bonjour", "Successfully registered service.\n");
+ break;
+ case AVAHI_ENTRY_GROUP_COLLISION:
+ purple_debug_error("bonjour", "Collision registering entry group.\n");
+ /* TODO: Handle error - this should log out the account. (Possibly with "wants to die")*/
+ break;
+ case AVAHI_ENTRY_GROUP_FAILURE:
+ purple_debug_error("bonjour", "Error registering entry group: %s\n.",
+ avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
+ /* TODO: Handle error - this should log out the account.*/
+ break;
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ break;
+ }
+
+}
+
+static void
+_buddy_icon_record_cb(AvahiRecordBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol,
+ AvahiBrowserEvent event, const char *name, uint16_t clazz, uint16_t type,
+ const void *rdata, size_t size, AvahiLookupResultFlags flags, void *userdata) {
+ BonjourBuddy *buddy = userdata;
+ AvahiBuddyImplData *idata = buddy->mdns_impl_data;
+
+ switch (event) {
+ case AVAHI_BROWSER_NEW:
+ bonjour_buddy_got_buddy_icon(buddy, rdata, size);
+ break;
+ case AVAHI_BROWSER_REMOVE:
+ case AVAHI_BROWSER_CACHE_EXHAUSTED:
+ case AVAHI_BROWSER_ALL_FOR_NOW:
+ case AVAHI_BROWSER_FAILURE:
+ purple_debug_error("bonjour", "Error rerieving buddy icon record: %s\n",
+ avahi_strerror(avahi_client_errno(avahi_record_browser_get_client(b))));
+ break;
+ }
+
+ /* Stop listening */
+ avahi_record_browser_free(idata->buddy_icon_rec_browser);
+ idata->buddy_icon_rec_browser = NULL;
+}
+
+/****************************
+ * mdns_interface functions *
+ ****************************/
+
+gboolean _mdns_init_session(BonjourDnsSd *data) {
+ AvahiSessionImplData *idata = g_new0(AvahiSessionImplData, 1);
+ const AvahiPoll *poll_api;
+ int error;
+
+ /* Tell avahi to use g_malloc and g_free */
+ avahi_set_allocator (avahi_glib_allocator ());
+
+ /* This currently depends on the glib mainloop,
+ * we should make it use the libpurple abstraction */
+
+ idata->glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT);
+
+ poll_api = avahi_glib_poll_get(idata->glib_poll);
+
+ idata->client = avahi_client_new(poll_api, 0, NULL, data, &error);
+
+ if (idata->client == NULL) {
+ purple_debug_error("bonjour", "Error initializing Avahi: %s", avahi_strerror(error));
+ avahi_glib_poll_free(idata->glib_poll);
+ g_free(idata);
+ return FALSE;
+ }
+
+ data->mdns_impl_data = idata;
+
+ return TRUE;
+}
+
+gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records) {
+ int publish_result = 0;
+ AvahiSessionImplData *idata = data->mdns_impl_data;
+ AvahiStringList *lst = NULL;
+
+ g_return_val_if_fail(idata != NULL, FALSE);
+
+ if (!idata->group) {
+ idata->group = avahi_entry_group_new(idata->client,
+ _entry_group_cb, idata);
+ if (!idata->group) {
+ purple_debug_error("bonjour",
+ "Unable to initialize the data for the mDNS (%s).\n",
+ avahi_strerror(avahi_client_errno(idata->client)));
+ return FALSE;
+ }
+ }
+
+ while (records) {
+ PurpleKeyValuePair *kvp = records->data;
+ lst = avahi_string_list_add_pair(lst, kvp->key, kvp->value);
+ records = records->next;
+ }
+
+ /* Publish the service */
+ switch (type) {
+ case PUBLISH_START:
+ publish_result = avahi_entry_group_add_service_strlst(
+ idata->group, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC, 0,
+ purple_account_get_username(data->account),
+ ICHAT_SERVICE, NULL, NULL, data->port_p2pj, lst);
+ break;
+ case PUBLISH_UPDATE:
+ publish_result = avahi_entry_group_update_service_txt_strlst(
+ idata->group, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC, 0,
+ purple_account_get_username(data->account),
+ ICHAT_SERVICE, NULL, lst);
+ break;
+ }
+
+ /* Free the memory used by temp data */
+ avahi_string_list_free(lst);
+
+ if (publish_result < 0) {
+ purple_debug_error("bonjour",
+ "Failed to add the " ICHAT_SERVICE " service. Error: %s\n",
+ avahi_strerror(publish_result));
+ return FALSE;
+ }
+
+ if (type == PUBLISH_START
+ && (publish_result = avahi_entry_group_commit(idata->group)) < 0) {
+ purple_debug_error("bonjour",
+ "Failed to commit " ICHAT_SERVICE " service. Error: %s\n",
+ avahi_strerror(publish_result));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean _mdns_browse(BonjourDnsSd *data) {
+ AvahiSessionImplData *idata = data->mdns_impl_data;
+
+ g_return_val_if_fail(idata != NULL, FALSE);
+
+ idata->sb = avahi_service_browser_new(idata->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, ICHAT_SERVICE, NULL, 0, _browser_callback, data->account);
+ if (!idata->sb) {
+
+ purple_debug_error("bonjour",
+ "Unable to initialize service browser. Error: %s\n.",
+ avahi_strerror(avahi_client_errno(idata->client)));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) {
+ AvahiSessionImplData *idata = data->mdns_impl_data;
+
+ if (idata == NULL || idata->client == NULL)
+ return FALSE;
+
+ if (avatar_data != NULL) {
+ gboolean new_group = FALSE;
+ gchar *svc_name;
+ int ret;
+ AvahiPublishFlags flags = 0;
+
+ if (idata->buddy_icon_group == NULL) {
+ purple_debug_info("bonjour", "Setting new buddy icon.\n");
+ new_group = TRUE;
+
+ idata->buddy_icon_group = avahi_entry_group_new(idata->client,
+ _buddy_icon_group_cb, data);
+ } else {
+ purple_debug_info("bonjour", "Updating existing buddy icon.\n");
+ flags |= AVAHI_PUBLISH_UPDATE;
+ }
+
+ if (idata->buddy_icon_group == NULL) {
+ purple_debug_error("bonjour",
+ "Unable to initialize the buddy icon group (%s).\n",
+ avahi_strerror(avahi_client_errno(idata->client)));
+ return FALSE;
+ }
+
+ svc_name = g_strdup_printf("%s." ICHAT_SERVICE "local",
+ purple_account_get_username(data->account));
+
+ ret = avahi_entry_group_add_record(idata->buddy_icon_group, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC, flags, svc_name,
+ AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 120, avatar_data, avatar_len);
+
+ g_free(svc_name);
+
+ if (ret < 0) {
+ purple_debug_error("bonjour",
+ "Failed to register buddy icon. Error: %s\n", avahi_strerror(ret));
+ if (new_group) {
+ avahi_entry_group_free(idata->buddy_icon_group);
+ idata->buddy_icon_group = NULL;
+ }
+ return FALSE;
+ }
+
+ if (new_group && (ret = avahi_entry_group_commit(idata->buddy_icon_group)) < 0) {
+ purple_debug_error("bonjour",
+ "Failed to commit buddy icon group. Error: %s\n", avahi_strerror(ret));
+ if (new_group) {
+ avahi_entry_group_free(idata->buddy_icon_group);
+ idata->buddy_icon_group = NULL;
+ }
+ return FALSE;
+ }
+ } else if (idata->buddy_icon_group != NULL) {
+ purple_debug_info("bonjour", "Removing existing buddy icon.\n");
+ avahi_entry_group_free(idata->buddy_icon_group);
+ idata->buddy_icon_group = NULL;
+ }
+
+ return TRUE;
+}
+
+void _mdns_stop(BonjourDnsSd *data) {
+ AvahiSessionImplData *idata = data->mdns_impl_data;
+
+ if (idata == NULL || idata->client == NULL)
+ return;
+
+ if (idata->sb != NULL)
+ avahi_service_browser_free(idata->sb);
+
+ avahi_client_free(idata->client);
+ avahi_glib_poll_free(idata->glib_poll);
+
+ g_free(idata);
+
+ data->mdns_impl_data = NULL;
+}
+
+void _mdns_init_buddy(BonjourBuddy *buddy) {
+ buddy->mdns_impl_data = g_new0(AvahiBuddyImplData, 1);
+}
+
+void _mdns_delete_buddy(BonjourBuddy *buddy) {
+ AvahiBuddyImplData *idata = buddy->mdns_impl_data;
+
+ g_return_if_fail(idata != NULL);
+
+ if (idata->buddy_icon_rec_browser != NULL)
+ avahi_record_browser_free(idata->buddy_icon_rec_browser);
+
+ if (idata->resolver != NULL)
+ avahi_service_resolver_free(idata->resolver);
+
+ g_free(idata);
+
+ buddy->mdns_impl_data = NULL;
+}
+
+void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) {
+ PurpleConnection *conn = purple_account_get_connection(buddy->account);
+ BonjourData *bd = conn->proto_data;
+ AvahiSessionImplData *session_idata = bd->dns_sd_data->mdns_impl_data;
+ AvahiBuddyImplData *idata = buddy->mdns_impl_data;
+ gchar *name;
+
+ g_return_if_fail(idata != NULL);
+
+ if (idata->buddy_icon_rec_browser != NULL)
+ avahi_record_browser_free(idata->buddy_icon_rec_browser);
+
+ purple_debug_info("bonjour", "Retrieving buddy icon for '%s'.\n", buddy->name);
+
+ name = g_strdup_printf("%s." ICHAT_SERVICE "local", buddy->name);
+ idata->buddy_icon_rec_browser = avahi_record_browser_new(session_idata->client, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC, name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 0,
+ _buddy_icon_record_cb, buddy);
+ g_free(name);
+
+ if (!idata->buddy_icon_rec_browser) {
+ purple_debug_error("bonjour",
+ "Unable to initialize buddy icon record browser. Error: %s\n.",
+ avahi_strerror(avahi_client_errno(session_idata->client)));
+ }
+
+}
+
diff --git a/libpurple/protocols/bonjour/mdns_common.c b/libpurple/protocols/bonjour/mdns_common.c
index 6ae6717426..e70d3fc643 100644
--- a/libpurple/protocols/bonjour/mdns_common.c
+++ b/libpurple/protocols/bonjour/mdns_common.c
@@ -17,30 +17,27 @@
#include <string.h>
#include "internal.h"
-#include "config.h"
+#include "cipher.h"
+#include "debug.h"
+
#include "mdns_common.h"
+#include "mdns_interface.h"
#include "bonjour.h"
#include "buddy.h"
-#include "debug.h"
/**
* Allocate space for the dns-sd data.
*/
-BonjourDnsSd *
-bonjour_dns_sd_new()
-{
+BonjourDnsSd * bonjour_dns_sd_new() {
BonjourDnsSd *data = g_new0(BonjourDnsSd, 1);
-
return data;
}
/**
* Deallocate the space of the dns-sd data.
*/
-void
-bonjour_dns_sd_free(BonjourDnsSd *data)
-{
+void bonjour_dns_sd_free(BonjourDnsSd *data) {
g_free(data->first);
g_free(data->last);
g_free(data->phsh);
@@ -50,12 +47,90 @@ bonjour_dns_sd_free(BonjourDnsSd *data)
g_free(data);
}
+static GSList *generate_presence_txt_records(BonjourDnsSd *data) {
+ GSList *ret = NULL;
+ PurpleKeyValuePair *kvp;
+ char portstring[6];
+ const char *jid, *aim, *email;
+
+ /* Convert the port to a string */
+ snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj);
+
+ jid = purple_account_get_string(data->account, "jid", NULL);
+ aim = purple_account_get_string(data->account, "AIM", NULL);
+ email = purple_account_get_string(data->account, "email", NULL);
+
+#define _M_ADD_R(k, v) \
+ kvp = g_new0(PurpleKeyValuePair, 1); \
+ kvp->key = g_strdup(k); \
+ kvp->value = g_strdup(v); \
+ ret = g_slist_prepend(ret, kvp); \
+
+ /* We should try to follow XEP-0174, but some clients have "issues", so we humor them.
+ * See http://telepathy.freedesktop.org/wiki/SalutInteroperability
+ */
+
+ /* Needed by iChat */
+ _M_ADD_R("txtvers", "1")
+ /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
+ _M_ADD_R("1st", data->first)
+ /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
+ _M_ADD_R("last", data->last)
+ /* Needed by Adium */
+ _M_ADD_R("port.p2pj", portstring)
+ /* Needed by iChat, Gaim/Pidgin <= 2.0.1 */
+ _M_ADD_R("status", data->status)
+ _M_ADD_R("node", "libpurple")
+ _M_ADD_R("ver", VERSION)
+ /* Currently always set to "!" since we don't support AV and wont ever be in a conference */
+ _M_ADD_R("vc", data->vc)
+ if (email != NULL && *email != '\0') {
+ _M_ADD_R("email", email)
+ }
+ if (jid != NULL && *jid != '\0') {
+ _M_ADD_R("jid", jid)
+ }
+ /* Nonstandard, but used by iChat */
+ if (aim != NULL && *aim != '\0') {
+ _M_ADD_R("AIM", aim)
+ }
+ if (data->msg != NULL && *data->msg != '\0') {
+ _M_ADD_R("msg", data->msg)
+ }
+ if (data->phsh != NULL && *data->phsh != '\0') {
+ _M_ADD_R("phsh", data->phsh)
+ }
+
+ /* TODO: ext, nick */
+ return ret;
+}
+
+static void free_presence_txt_records(GSList *lst) {
+ PurpleKeyValuePair *kvp;
+ while(lst) {
+ kvp = lst->data;
+ g_free(kvp->key);
+ g_free(kvp->value);
+ g_free(kvp);
+ lst = g_slist_remove(lst, lst->data);
+ }
+}
+
+static gboolean publish_presence(BonjourDnsSd *data, PublishType type) {
+ GSList *txt_records;
+ gboolean ret;
+
+ txt_records = generate_presence_txt_records(data);
+ ret = _mdns_publish(data, type, txt_records);
+ free_presence_txt_records(txt_records);
+
+ return ret;
+}
+
/**
* Send a new dns-sd packet updating our status.
*/
-void
-bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message)
-{
+void bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message) {
g_free(data->status);
g_free(data->msg);
@@ -63,76 +138,86 @@ bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *s
data->msg = g_strdup(status_message);
/* Update our text record with the new status */
- _mdns_publish(data, PUBLISH_UPDATE); /* <--We must control the errors */
+ publish_presence(data, PUBLISH_UPDATE);
}
/**
- * Advertise our presence within the dns-sd daemon and start browsing
- * for other bonjour peers.
+ * Retrieve the buddy icon blob
*/
-gboolean
-bonjour_dns_sd_start(BonjourDnsSd *data)
-{
- PurpleAccount *account;
- PurpleConnection *gc;
- gint dns_sd_socket;
- gpointer opaque_data;
+void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
+ _mdns_retrieve_buddy_icon(buddy);
+}
-#ifdef USE_BONJOUR_HOWL
- sw_discovery_oid session_id;
-#endif
+void bonjour_dns_sd_update_buddy_icon(BonjourDnsSd *data) {
+ PurpleStoredImage *img;
+
+ if ((img = purple_buddy_icons_find_account_icon(data->account))) {
+ gconstpointer avatar_data;
+ gsize avatar_len;
+
+ avatar_data = purple_imgstore_get_data(img);
+ avatar_len = purple_imgstore_get_size(img);
+
+ if (_mdns_set_buddy_icon_data(data, avatar_data, avatar_len)) {
+ int i;
+ gchar *enc;
+ char *p, hash[41];
+ unsigned char hashval[20];
+
+ enc = purple_base64_encode(avatar_data, avatar_len);
+
+ purple_cipher_digest_region("sha1", avatar_data,
+ avatar_len, sizeof(hashval),
+ hashval, NULL);
+
+ p = hash;
+ for(i=0; i<20; i++, p+=2)
+ snprintf(p, 3, "%02x", hashval[i]);
+
+ g_free(data->phsh);
+ data->phsh = g_strdup(hash);
+
+ g_free(enc);
+
+ /* Update our TXT record */
+ publish_presence(data, PUBLISH_UPDATE);
+ }
+
+ purple_imgstore_unref(img);
+ } else {
+ /* We need to do this regardless of whether data->phsh is set so that we
+ * cancel any icons that are currently in the process of being set */
+ _mdns_set_buddy_icon_data(data, NULL, 0);
+ if (data->phsh != NULL) {
+ /* Clear the buddy icon */
+ g_free(data->phsh);
+ data->phsh = NULL;
+ /* Update our TXT record */
+ publish_presence(data, PUBLISH_UPDATE);
+ }
+ }
+}
- account = data->account;
- gc = purple_account_get_connection(account);
+/**
+ * Advertise our presence within the dns-sd daemon and start browsing
+ * for other bonjour peers.
+ */
+gboolean bonjour_dns_sd_start(BonjourDnsSd *data) {
/* Initialize the dns-sd data and session */
-#ifndef USE_BONJOUR_APPLE
- if (sw_discovery_init(&data->session) != SW_OKAY)
- {
- purple_debug_error("bonjour", "Unable to initialize an mDNS session.\n");
-
- /* In Avahi, sw_discovery_init frees data->session but doesn't clear it */
- data->session = NULL;
-
+ if (!_mdns_init_session(data))
return FALSE;
- }
-#endif
/* Publish our bonjour IM client at the mDNS daemon */
-
- if (0 != _mdns_publish(data, PUBLISH_START))
- {
+ if (!publish_presence(data, PUBLISH_START))
return FALSE;
- }
/* Advise the daemon that we are waiting for connections */
-
-#ifdef USE_BONJOUR_APPLE
- if (DNSServiceBrowse(&data->browser, 0, 0, ICHAT_SERVICE, NULL, _mdns_service_browse_callback, account)
- != kDNSServiceErr_NoError)
-#else /* USE_BONJOUR_HOWL */
- if (sw_discovery_browse(data->session, 0, ICHAT_SERVICE, NULL, _browser_reply,
- account, &session_id) != SW_OKAY)
-#endif
- {
+ if (!_mdns_browse(data)) {
purple_debug_error("bonjour", "Unable to get service.");
return FALSE;
}
- /* Get the socket that communicates with the mDNS daemon and bind it to a */
- /* callback that will handle the dns_sd packets */
-
-#ifdef USE_BONJOUR_APPLE
- dns_sd_socket = DNSServiceRefSockFD(data->browser);
- opaque_data = data->browser;
-#else /* USE_BONJOUR_HOWL */
- dns_sd_socket = sw_discovery_socket(data->session);
- opaque_data = data->session;
-#endif
-
- gc->inpa = purple_input_add(dns_sd_socket, PURPLE_INPUT_READ,
- _mdns_handle_event, opaque_data);
-
return TRUE;
}
@@ -140,37 +225,6 @@ bonjour_dns_sd_start(BonjourDnsSd *data)
* Unregister the "_presence._tcp" service at the mDNS daemon.
*/
-void
-bonjour_dns_sd_stop(BonjourDnsSd *data)
-{
- PurpleAccount *account;
- PurpleConnection *gc;
-
-#ifdef USE_BONJOUR_APPLE
- if (data->advertisement == NULL || data->browser == NULL)
-#else /* USE_BONJOUR_HOWL */
- if (data->session == NULL)
-#endif
- return;
-
-#ifdef USE_BONJOUR_HOWL
- sw_discovery_cancel(data->session, data->session_id);
-#endif
-
- account = data->account;
- gc = purple_account_get_connection(account);
- purple_input_remove(gc->inpa);
-
-#ifdef USE_BONJOUR_APPLE
- /* hack: for win32, we need to stop listening to the advertisement pipe too */
- purple_input_remove(data->advertisement_handler);
-
- DNSServiceRefDeallocate(data->advertisement);
- DNSServiceRefDeallocate(data->browser);
- data->advertisement = NULL;
- data->browser = NULL;
-#else /* USE_BONJOUR_HOWL */
- g_free(data->session);
- data->session = NULL;
-#endif
+void bonjour_dns_sd_stop(BonjourDnsSd *data) {
+ _mdns_stop(data);
}
diff --git a/libpurple/protocols/bonjour/mdns_common.h b/libpurple/protocols/bonjour/mdns_common.h
index 0ee547be27..aac3e4481c 100644
--- a/libpurple/protocols/bonjour/mdns_common.h
+++ b/libpurple/protocols/bonjour/mdns_common.h
@@ -19,11 +19,7 @@
#include "mdns_types.h"
-#ifdef USE_BONJOUR_APPLE
-#include "mdns_win32.h"
-#elif defined USE_BONJOUR_HOWL
-#include "mdns_howl.h"
-#endif
+#include "buddy.h"
/**
* Allocate space for the dns-sd data.
@@ -41,6 +37,16 @@ void bonjour_dns_sd_free(BonjourDnsSd *data);
void bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message);
/**
+ * Retrieve the buddy icon blob
+ */
+void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy);
+
+/**
+ * Deal with a buddy icon update
+ */
+void bonjour_dns_sd_update_buddy_icon(BonjourDnsSd *data);
+
+/**
* Advertise our presence within the dns-sd daemon and start
* browsing for other bonjour peers.
*/
diff --git a/libpurple/protocols/bonjour/mdns_howl.c b/libpurple/protocols/bonjour/mdns_howl.c
index 9f38a92ef2..8843c07510 100644
--- a/libpurple/protocols/bonjour/mdns_howl.c
+++ b/libpurple/protocols/bonjour/mdns_howl.c
@@ -14,12 +14,22 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-#include "mdns_howl.h"
+#include "internal.h"
+#include "mdns_interface.h"
#include "debug.h"
#include "buddy.h"
-sw_result HOWL_API
+#include <howl.h>
+
+/* data used by howl bonjour implementation */
+typedef struct _howl_impl_data {
+ sw_discovery session;
+ sw_discovery_oid session_id;
+ guint session_handler;
+} HowlSessionImplData;
+
+static sw_result HOWL_API
_publish_reply(sw_discovery discovery, sw_discovery_oid oid,
sw_discovery_publish_status status, sw_opaque extra)
{
@@ -45,7 +55,7 @@ _publish_reply(sw_discovery discovery, sw_discovery_oid oid,
return SW_OKAY;
}
-sw_result HOWL_API
+static sw_result HOWL_API
_resolve_reply(sw_discovery discovery, sw_discovery_oid oid,
sw_uint32 interface_index, sw_const_string name,
sw_const_string type, sw_const_string domain,
@@ -75,6 +85,7 @@ _resolve_reply(sw_discovery discovery, sw_discovery_oid oid,
/* Obtain the parameters from the text_record */
if ((text_record_len > 0) && (text_record) && (*text_record != '\0'))
{
+ clear_bonjour_buddy_values(buddy);
sw_text_record_iterator_init(&iterator, text_record, text_record_len);
while (sw_text_record_iterator_next(iterator, key, (sw_octet *)value, &value_length) == SW_OKAY)
set_bonjour_buddy_value(buddy, key, value, value_length);
@@ -94,7 +105,7 @@ _resolve_reply(sw_discovery discovery, sw_discovery_oid oid,
return SW_OKAY;
}
-sw_result HOWL_API
+static sw_result HOWL_API
_browser_reply(sw_discovery discovery, sw_discovery_oid oid,
sw_discovery_browse_status status,
sw_uint32 interface_index, sw_const_string name,
@@ -136,7 +147,7 @@ _browser_reply(sw_discovery discovery, sw_discovery_oid oid,
break;
case SW_DISCOVERY_BROWSE_REMOVE_SERVICE:
purple_debug_info("bonjour", "_browser_reply --> Remove service\n");
- gb = purple_find_buddy((PurpleAccount*)extra, name);
+ gb = purple_find_buddy(account, name);
if (gb != NULL)
{
bonjour_buddy_delete(gb->proto_data);
@@ -153,86 +164,125 @@ _browser_reply(sw_discovery discovery, sw_discovery_oid oid,
return SW_OKAY;
}
-int
-_mdns_publish(BonjourDnsSd *data, PublishType type)
+static void
+_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition)
{
+ sw_discovery_read_socket((sw_discovery)data);
+}
+
+/****************************
+ * mdns_interface functions *
+ ****************************/
+
+gboolean _mdns_init_session(BonjourDnsSd *data) {
+ HowlSessionImplData *idata = g_new0(HowlSessionImplData, 1);
+
+ if (sw_discovery_init(&idata->session) != SW_OKAY) {
+ purple_debug_error("bonjour", "Unable to initialize an mDNS session.\n");
+
+ /* In Avahi, sw_discovery_init frees data->session but doesn't clear it */
+ idata->session = NULL;
+
+ g_free(idata);
+
+ return FALSE;
+ }
+
+ data->mdns_impl_data = idata;
+
+ return TRUE;
+}
+
+
+gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records) {
sw_text_record dns_data;
sw_result publish_result = SW_OKAY;
- char portstring[6];
- const char *jid, *aim, *email;
+ HowlSessionImplData *idata = data->mdns_impl_data;
+
+ g_return_val_if_fail(idata != NULL, FALSE);
/* Fill the data for the service */
- if (sw_text_record_init(&dns_data) != SW_OKAY)
- {
+ if (sw_text_record_init(&dns_data) != SW_OKAY) {
purple_debug_error("bonjour", "Unable to initialize the data for the mDNS.\n");
- return -1;
+ return FALSE;
}
- /* Convert the port to a string */
- snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj);
-
- jid = purple_account_get_string(data->account, "jid", NULL);
- aim = purple_account_get_string(data->account, "AIM", NULL);
- email = purple_account_get_string(data->account, "email", NULL);
-
- /* We should try to follow XEP-0174, but some clients have "issues", so we humor them.
- * See http://telepathy.freedesktop.org/wiki/SalutInteroperability
- */
-
- /* Needed by iChat */
- sw_text_record_add_key_and_string_value(dns_data, "txtvers", "1");
- /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
- sw_text_record_add_key_and_string_value(dns_data, "1st", data->first);
- /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
- sw_text_record_add_key_and_string_value(dns_data, "last", data->last);
- /* Needed by Adium */
- sw_text_record_add_key_and_string_value(dns_data, "port.p2pj", portstring);
- /* Needed by iChat, Gaim/Pidgin <= 2.0.1 */
- sw_text_record_add_key_and_string_value(dns_data, "status", data->status);
- /* Currently always set to "!" since we don't support AV and wont ever be in a conference */
- sw_text_record_add_key_and_string_value(dns_data, "vc", data->vc);
- sw_text_record_add_key_and_string_value(dns_data, "ver", VERSION);
- if (email != NULL && *email != '\0')
- sw_text_record_add_key_and_string_value(dns_data, "email", email);
- if (jid != NULL && *jid != '\0')
- sw_text_record_add_key_and_string_value(dns_data, "jid", jid);
- /* Nonstandard, but used by iChat */
- if (aim != NULL && *aim != '\0')
- sw_text_record_add_key_and_string_value(dns_data, "AIM", aim);
- if (data->msg != NULL && *data->msg != '\0')
- sw_text_record_add_key_and_string_value(dns_data, "msg", data->msg);
- if (data->phsh != NULL && *data->phsh != '\0')
- sw_text_record_add_key_and_string_value(dns_data, "phsh", data->phsh);
-
- /* TODO: ext, nick, node */
+ while (records) {
+ PurpleKeyValuePair *kvp = records->data;
+ sw_text_record_add_key_and_string_value(dns_data, kvp->key, kvp->value);
+ records = records->next;
+ }
/* Publish the service */
- switch (type)
- {
+ switch (type) {
case PUBLISH_START:
- publish_result = sw_discovery_publish(data->session, 0, purple_account_get_username(data->account), ICHAT_SERVICE, NULL,
+ publish_result = sw_discovery_publish(idata->session, 0, purple_account_get_username(data->account), ICHAT_SERVICE, NULL,
NULL, data->port_p2pj, sw_text_record_bytes(dns_data), sw_text_record_len(dns_data),
- _publish_reply, NULL, &data->session_id);
+ _publish_reply, NULL, &idata->session_id);
break;
case PUBLISH_UPDATE:
- publish_result = sw_discovery_publish_update(data->session, data->session_id,
+ publish_result = sw_discovery_publish_update(idata->session, idata->session_id,
sw_text_record_bytes(dns_data), sw_text_record_len(dns_data));
break;
}
- if (publish_result != SW_OKAY)
- {
- purple_debug_error("bonjour", "Unable to publish or change the status of the _presence._tcp service.\n");
- return -1;
- }
/* Free the memory used by temp data */
sw_text_record_fina(dns_data);
- return 0;
+ if (publish_result != SW_OKAY) {
+ purple_debug_error("bonjour", "Unable to publish or change the status of the " ICHAT_SERVICE " service.\n");
+ return FALSE;
+ }
+
+ return TRUE;
}
-void
-_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition)
-{
- sw_discovery_read_socket((sw_discovery)data);
+gboolean _mdns_browse(BonjourDnsSd *data) {
+ HowlSessionImplData *idata = data->mdns_impl_data;
+ /* TODO: don't we need to hang onto this to cancel later? */
+ sw_discovery_oid session_id;
+
+ g_return_val_if_fail(idata != NULL, FALSE);
+
+ if (sw_discovery_browse(idata->session, 0, ICHAT_SERVICE, NULL, _browser_reply,
+ data->account, &session_id) == SW_OKAY) {
+ idata->session_handler = purple_input_add(sw_discovery_socket(idata->session),
+ PURPLE_INPUT_READ, _mdns_handle_event, idata->session);
+ return TRUE;
+ }
+
+ return FALSE;
}
+
+gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) {
+ return FALSE;
+}
+
+void _mdns_stop(BonjourDnsSd *data) {
+ HowlSessionImplData *idata = data->mdns_impl_data;
+
+ if (idata == NULL || idata->session == NULL)
+ return;
+
+ sw_discovery_cancel(idata->session, idata->session_id);
+
+ purple_input_remove(idata->session_handler);
+
+ /* TODO: should this really be g_free()'d ??? */
+ g_free(idata->session);
+
+ g_free(idata);
+
+ data->mdns_impl_data = NULL;
+}
+
+void _mdns_init_buddy(BonjourBuddy *buddy) {
+}
+
+void _mdns_delete_buddy(BonjourBuddy *buddy) {
+}
+
+void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) {
+}
+
+
diff --git a/libpurple/protocols/bonjour/mdns_howl.h b/libpurple/protocols/bonjour/mdns_howl.h
deleted file mode 100644
index 61a50783e3..0000000000
--- a/libpurple/protocols/bonjour/mdns_howl.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Library General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- */
-
-#ifndef _BONJOUR_MDNS_HOWL
-#define _BONJOUR_MDNS_HOWL
-
-#include "config.h"
-
-#ifdef USE_BONJOUR_HOWL
-
-#include <howl.h>
-#include <glib.h>
-#include "mdns_types.h"
-
-/* callback functions */
-
-sw_result HOWL_API _publish_reply(sw_discovery discovery, sw_discovery_oid oid, sw_discovery_publish_status status, sw_opaque extra);
-
-sw_result HOWL_API _resolve_reply(sw_discovery discovery, sw_discovery_oid oid, sw_uint32 interface_index, sw_const_string name,
- sw_const_string type, sw_const_string domain, sw_ipv4_address address, sw_port port, sw_octets text_record,
- sw_ulong text_record_len, sw_opaque extra);
-
-sw_result HOWL_API _browser_reply(sw_discovery discovery, sw_discovery_oid oid, sw_discovery_browse_status status,
- sw_uint32 interface_index, sw_const_string name, sw_const_string type, sw_const_string domain, sw_opaque_t extra);
-
-
-/* interface functions */
-
-int _mdns_publish(BonjourDnsSd *data, PublishType type);
-void _mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition);
-
-#endif
-
-#endif
diff --git a/libpurple/protocols/bonjour/mdns_win32.h b/libpurple/protocols/bonjour/mdns_interface.h
index 376bf06f23..eb429b3e7c 100644
--- a/libpurple/protocols/bonjour/mdns_win32.h
+++ b/libpurple/protocols/bonjour/mdns_interface.h
@@ -14,27 +14,26 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-#ifndef _BONJOUR_MDNS_WIN32
-#define _BONJOUR_MDNS_WIN32
+#ifndef _BONJOUR_MDNS_INTERFACE
+#define _BONJOUR_MDNS_INTERFACE
-#ifdef USE_BONJOUR_APPLE
-
-#include <glib.h>
#include "mdns_types.h"
#include "buddy.h"
-#include "dnsquery.h"
-#include "dns_sd_proxy.h"
-/* Bonjour async callbacks */
+gboolean _mdns_init_session(BonjourDnsSd *data);
-void DNSSD_API _mdns_service_browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
- DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context);
+gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records);
-/* interface functions */
+gboolean _mdns_browse(BonjourDnsSd *data);
-int _mdns_publish(BonjourDnsSd *data, PublishType type);
-void _mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition);
+void _mdns_stop(BonjourDnsSd *data);
-#endif
+gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len);
+
+void _mdns_init_buddy(BonjourBuddy *buddy);
+
+void _mdns_delete_buddy(BonjourBuddy *buddy);
+
+void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy);
#endif
diff --git a/libpurple/protocols/bonjour/mdns_types.h b/libpurple/protocols/bonjour/mdns_types.h
index e81b447067..c239969522 100644
--- a/libpurple/protocols/bonjour/mdns_types.h
+++ b/libpurple/protocols/bonjour/mdns_types.h
@@ -19,31 +19,14 @@
#include <glib.h>
#include "account.h"
-#include "config.h"
-
-#ifdef USE_BONJOUR_APPLE
-#include "dns_sd_proxy.h"
-#else /* USE_BONJOUR_HOWL */
-#include <howl.h>
-#endif
#define ICHAT_SERVICE "_presence._tcp."
/**
* Data to be used by the dns-sd connection.
*/
-typedef struct _BonjourDnsSd
-{
-#ifdef USE_BONJOUR_APPLE
- DNSServiceRef advertisement;
- DNSServiceRef browser;
-
- int advertisement_handler; /* hack... windows bonjour is broken, so we have to have this */
-#else /* USE_BONJOUR_HOWL */
- sw_discovery session;
- sw_discovery_oid session_id;
-#endif
-
+typedef struct _BonjourDnsSd {
+ gpointer mdns_impl_data;
PurpleAccount *account;
gchar *first;
gchar *last;
@@ -59,5 +42,4 @@ typedef enum _PublishType {
PUBLISH_UPDATE
} PublishType;
-
#endif
diff --git a/libpurple/protocols/bonjour/mdns_win32.c b/libpurple/protocols/bonjour/mdns_win32.c
index c0dfbcc5a4..7b60633584 100644
--- a/libpurple/protocols/bonjour/mdns_win32.c
+++ b/libpurple/protocols/bonjour/mdns_win32.c
@@ -15,22 +15,48 @@
*/
#include "internal.h"
-#include "mdns_win32.h"
-
#include "debug.h"
+#include "buddy.h"
+#include "mdns_interface.h"
+#include "dns_sd_proxy.h"
+#include "dnsquery.h"
+#include "mdns_common.h"
+
+
/* data structure for the resolve callback */
-typedef struct _ResolveCallbackArgs
-{
+typedef struct _ResolveCallbackArgs {
DNSServiceRef resolver;
- int resolver_fd;
+ guint resolver_handler;
+ gchar *full_service_name;
PurpleDnsQueryData *query;
- gchar *fqn;
BonjourBuddy* buddy;
} ResolveCallbackArgs;
+/* data used by win32 bonjour implementation */
+typedef struct _win32_session_impl_data {
+ DNSServiceRef presence_svc;
+ DNSServiceRef browser_svc;
+ DNSRecordRef buddy_icon_rec;
+
+ guint presence_handler;
+ guint browser_handler;
+} Win32SessionImplData;
+
+typedef struct _win32_buddy_impl_data {
+ DNSServiceRef txt_query;
+ guint txt_query_handler;
+ DNSServiceRef null_query;
+ guint null_query_handler;
+} Win32BuddyImplData;
+
+static void
+_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition) {
+ DNSServiceProcessResult((DNSServiceRef) data);
+}
+
static void
_mdns_parse_text_record(BonjourBuddy* buddy, const char* record, uint16_t record_len)
{
@@ -38,6 +64,7 @@ _mdns_parse_text_record(BonjourBuddy* buddy, const char* record, uint16_t record
uint8_t txt_len;
int i;
+ clear_bonjour_buddy_values(buddy);
for (i = 0; buddy_TXT_records[i] != NULL; i++) {
txt_entry = TXTRecordGetValuePtr(record_len, record, buddy_TXT_records[i], &txt_len);
if (txt_entry != NULL)
@@ -46,18 +73,36 @@ _mdns_parse_text_record(BonjourBuddy* buddy, const char* record, uint16_t record
}
static void DNSSD_API
-_mdns_text_record_query_callback(DNSServiceRef DNSServiceRef, DNSServiceFlags flags,
+_mdns_record_query_callback(DNSServiceRef DNSServiceRef, DNSServiceFlags flags,
uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullname,
uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata,
uint32_t ttl, void *context)
{
+
if (kDNSServiceErr_NoError != errorCode)
- purple_debug_error("bonjour", "text record query - callback error.\n");
+ purple_debug_error("bonjour", "record query - callback error.\n");
else if (flags & kDNSServiceFlagsAdd)
{
- BonjourBuddy *buddy = (BonjourBuddy*)context;
- _mdns_parse_text_record(buddy, rdata, rdlen);
- bonjour_buddy_add_to_purple(buddy);
+ if (rrtype == kDNSServiceType_TXT) {
+ /* New Buddy */
+ BonjourBuddy *buddy = (BonjourBuddy*) context;
+ _mdns_parse_text_record(buddy, rdata, rdlen);
+ bonjour_buddy_add_to_purple(buddy);
+ } else if (rrtype == kDNSServiceType_NULL) {
+ /* Buddy Icon response */
+ BonjourBuddy *buddy = (BonjourBuddy*) context;
+ Win32BuddyImplData *idata = buddy->mdns_impl_data;
+
+ g_return_if_fail(idata != NULL);
+
+ bonjour_buddy_got_buddy_icon(buddy, rdata, rdlen);
+
+ /* We've got what we need; stop listening */
+ purple_input_remove(idata->null_query_handler);
+ idata->null_query_handler = -1;
+ DNSServiceRefDeallocate(idata->null_query);
+ idata->null_query = NULL;
+ }
}
}
@@ -68,24 +113,28 @@ _mdns_resolve_host_callback(GSList *hosts, gpointer data, const char *error_mess
if (!hosts || !hosts->data)
purple_debug_error("bonjour", "host resolution - callback error.\n");
- else
- {
+ else {
struct sockaddr_in *addr = (struct sockaddr_in*)g_slist_nth_data(hosts, 1);
BonjourBuddy* buddy = args->buddy;
+ Win32BuddyImplData *idata = buddy->mdns_impl_data;
+
+ g_return_if_fail(idata != NULL);
buddy->ip = g_strdup(inet_ntoa(addr->sin_addr));
/* finally, set up the continuous txt record watcher, and add the buddy to purple */
- if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&buddy->txt_query, 0, 0, args->fqn,
- kDNSServiceType_TXT, kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy))
- {
- gint fd = DNSServiceRefSockFD(buddy->txt_query);
- buddy->txt_query_fd = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, buddy->txt_query);
+ if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, kDNSServiceFlagsLongLivedQuery,
+ kDNSServiceInterfaceIndexAny, args->full_service_name, kDNSServiceType_TXT,
+ kDNSServiceClass_IN, _mdns_record_query_callback, buddy)) {
+
+ purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", buddy->name, buddy->ip, buddy->port_p2pj);
+
+ idata->txt_query_handler = purple_input_add(DNSServiceRefSockFD(idata->txt_query),
+ PURPLE_INPUT_READ, _mdns_handle_event, idata->txt_query);
bonjour_buddy_add_to_purple(buddy);
- }
- else
+ } else
bonjour_buddy_delete(buddy);
}
@@ -95,7 +144,7 @@ _mdns_resolve_host_callback(GSList *hosts, gpointer data, const char *error_mess
/* free the remaining args memory */
purple_dnsquery_destroy(args->query);
- g_free(args->fqn);
+ g_free(args->full_service_name);
g_free(args);
}
@@ -106,7 +155,7 @@ _mdns_service_resolve_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint3
ResolveCallbackArgs *args = (ResolveCallbackArgs*)context;
/* remove the input fd and destroy the service ref */
- purple_input_remove(args->resolver_fd);
+ purple_input_remove(args->resolver_handler);
DNSServiceRefDeallocate(args->resolver);
if (kDNSServiceErr_NoError != errorCode)
@@ -123,14 +172,14 @@ _mdns_service_resolve_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint3
_mdns_parse_text_record(args->buddy, txtRecord, txtLen);
/* set more arguments, and start the host resolver */
- args->fqn = g_strdup(fullname);
+ args->full_service_name = g_strdup(fullname);
if (!(args->query =
purple_dnsquery_a(hosttarget, port, _mdns_resolve_host_callback, args)))
{
purple_debug_error("bonjour", "service resolver - host resolution failed.\n");
bonjour_buddy_delete(args->buddy);
- g_free(args->fqn);
+ g_free(args->full_service_name);
g_free(args);
}
}
@@ -139,16 +188,16 @@ _mdns_service_resolve_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint3
static void DNSSD_API
_mdns_service_register_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
- const char *name, const char *regtype, const char *domain, void *context)
-{
- /* we don't actually care about anything said in this callback - this is only here because Bonjour for windows is broken */
+ const char *name, const char *regtype, const char *domain, void *context) {
+
+ /* TODO: deal with collision */
if (kDNSServiceErr_NoError != errorCode)
- purple_debug_error("bonjour", "service advertisement - callback error.\n");
+ purple_debug_error("bonjour", "service advertisement - callback error (%d).\n", errorCode);
else
purple_debug_info("bonjour", "service advertisement - callback.\n");
}
-void DNSSD_API
+static void DNSSD_API
_mdns_service_browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context)
{
@@ -157,131 +206,93 @@ _mdns_service_browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32
if (kDNSServiceErr_NoError != errorCode)
purple_debug_error("bonjour", "service browser - callback error");
- else if (flags & kDNSServiceFlagsAdd)
- {
+ else if (flags & kDNSServiceFlagsAdd) {
/* A presence service instance has been discovered... check it isn't us! */
- if (g_ascii_strcasecmp(serviceName, account->username) != 0)
- {
+ if (g_ascii_strcasecmp(serviceName, account->username) != 0) {
/* OK, lets go ahead and resolve it to add to the buddy list */
ResolveCallbackArgs *args = g_new0(ResolveCallbackArgs, 1);
args->buddy = bonjour_buddy_new(serviceName, account);
- if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype, replyDomain, _mdns_service_resolve_callback, args))
- {
+ if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype, replyDomain, _mdns_service_resolve_callback, args)) {
bonjour_buddy_delete(args->buddy);
g_free(args);
purple_debug_error("bonjour", "service browser - failed to resolve service.\n");
- }
- else
- {
+ } else {
/* get a file descriptor for this service ref, and add it to the input list */
- gint resolver_fd = DNSServiceRefSockFD(args->resolver);
- args->resolver_fd = purple_input_add(resolver_fd, PURPLE_INPUT_READ, _mdns_handle_event, args->resolver);
+ gint fd = DNSServiceRefSockFD(args->resolver);
+ args->resolver_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, args->resolver);
}
}
- }
- else
- {
+ } else {
/* A peer has sent a goodbye packet, remove them from the buddy list */
purple_debug_info("bonjour", "service browser - remove notification\n");
gb = purple_find_buddy(account, serviceName);
- if (gb != NULL)
- {
+ if (gb != NULL) {
bonjour_buddy_delete(gb->proto_data);
purple_blist_remove_buddy(gb);
}
}
}
-int
-_mdns_publish(BonjourDnsSd *data, PublishType type)
-{
+/****************************
+ * mdns_interface functions *
+ ****************************/
+
+gboolean _mdns_init_session(BonjourDnsSd *data) {
+ data->mdns_impl_data = g_new0(Win32SessionImplData, 1);
+ return TRUE;
+}
+
+gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records) {
TXTRecordRef dns_data;
- char portstring[6];
- int ret = 0;
- const char *jid, *aim, *email;
- DNSServiceErrorType set_ret;
+ gboolean ret = TRUE;
+ DNSServiceErrorType set_ret = kDNSServiceErr_NoError;
+ Win32SessionImplData *idata = data->mdns_impl_data;
+
+ g_return_val_if_fail(idata != NULL, FALSE);
TXTRecordCreate(&dns_data, 256, NULL);
- /* Convert the port to a string */
- snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj);
-
- jid = purple_account_get_string(data->account, "jid", NULL);
- aim = purple_account_get_string(data->account, "AIM", NULL);
- email = purple_account_get_string(data->account, "email", NULL);
-
- /* We should try to follow XEP-0174, but some clients have "issues", so we humor them.
- * See http://telepathy.freedesktop.org/wiki/SalutInteroperability
- */
-
- /* Needed by iChat */
- set_ret = TXTRecordSetValue(&dns_data, "txtvers", 1, "1");
- /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
- if (set_ret == kDNSServiceErr_NoError)
- set_ret = TXTRecordSetValue(&dns_data, "1st", strlen(data->first), data->first);
- /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
- if (set_ret == kDNSServiceErr_NoError)
- set_ret = TXTRecordSetValue(&dns_data, "last", strlen(data->last), data->last);
- /* Needed by Adium */
- if (set_ret == kDNSServiceErr_NoError)
- set_ret = TXTRecordSetValue(&dns_data, "port.p2pj", strlen(portstring), portstring);
- /* Needed by iChat, Gaim/Pidgin <= 2.0.1 */
- if (set_ret == kDNSServiceErr_NoError)
- set_ret = TXTRecordSetValue(&dns_data, "status", strlen(data->status), data->status);
- if (set_ret == kDNSServiceErr_NoError)
- set_ret = TXTRecordSetValue(&dns_data, "ver", strlen(VERSION), VERSION);
- /* Currently always set to "!" since we don't support AV and wont ever be in a conference */
- if (set_ret == kDNSServiceErr_NoError)
- set_ret = TXTRecordSetValue(&dns_data, "vc", strlen(data->vc), data->vc);
- if (set_ret == kDNSServiceErr_NoError && email != NULL && *email != '\0')
- set_ret = TXTRecordSetValue(&dns_data, "email", strlen(email), email);
- if (set_ret == kDNSServiceErr_NoError && jid != NULL && *jid != '\0')
- set_ret = TXTRecordSetValue(&dns_data, "jid", strlen(jid), jid);
- /* Nonstandard, but used by iChat */
- if (set_ret == kDNSServiceErr_NoError && aim != NULL && *aim != '\0')
- set_ret = TXTRecordSetValue(&dns_data, "AIM", strlen(aim), aim);
- if (set_ret == kDNSServiceErr_NoError && data->msg != NULL && *data->msg != '\0')
- set_ret = TXTRecordSetValue(&dns_data, "msg", strlen(data->msg), data->msg);
- if (set_ret == kDNSServiceErr_NoError && data->phsh != NULL && *data->phsh != '\0')
- set_ret = TXTRecordSetValue(&dns_data, "phsh", strlen(data->phsh), data->phsh);
-
- /* TODO: ext, nick, node */
-
- if (set_ret != kDNSServiceErr_NoError)
- {
- purple_debug_error("bonjour", "Unable to allocate memory for text record.\n");
- ret = -1;
+ while (records) {
+ PurpleKeyValuePair *kvp = records->data;
+ set_ret = TXTRecordSetValue(&dns_data, kvp->key, strlen(kvp->value), kvp->value);
+ if (set_ret != kDNSServiceErr_NoError)
+ break;
+ records = records->next;
}
- else
- {
+
+ if (set_ret != kDNSServiceErr_NoError) {
+ purple_debug_error("bonjour", "Unable to allocate memory for text record.\n");
+ ret = FALSE;
+ } else {
DNSServiceErrorType err = kDNSServiceErr_NoError;
/* OK, we're done constructing the text record, (re)publish the service */
- switch (type)
- {
+ switch (type) {
case PUBLISH_START:
- err = DNSServiceRegister(&data->advertisement, 0, 0, purple_account_get_username(data->account), ICHAT_SERVICE,
+ purple_debug_info("bonjour", "Registering presence on port %d\n", data->port_p2pj);
+ err = DNSServiceRegister(&idata->presence_svc, 0, 0, purple_account_get_username(data->account), ICHAT_SERVICE,
NULL, NULL, htons(data->port_p2pj), TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data),
_mdns_service_register_callback, NULL);
break;
case PUBLISH_UPDATE:
- err = DNSServiceUpdateRecord(data->advertisement, NULL, 0, TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), 0);
+ purple_debug_info("bonjour", "Updating presence.\n");
+ err = DNSServiceUpdateRecord(idata->presence_svc, NULL, 0, TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), 0);
break;
}
- if (kDNSServiceErr_NoError != err)
- {
+ if (err != kDNSServiceErr_NoError) {
purple_debug_error("bonjour", "Failed to publish presence service.\n");
- ret = -1;
- }
- else if (PUBLISH_START == type)
- {
- /* hack: Bonjour on windows is broken. We don't care about the callback but we have to listen anyway */
- gint advertisement_fd = DNSServiceRefSockFD(data->advertisement);
- data->advertisement_handler = purple_input_add(advertisement_fd, PURPLE_INPUT_READ, _mdns_handle_event, data->advertisement);
+ ret = FALSE;
+ } else if (type == PUBLISH_START) {
+ /* We need to do this because according to the Apple docs:
+ * "the client is responsible for ensuring that DNSServiceProcessResult() is called
+ * whenever there is a reply from the daemon - the daemon may terminate its connection
+ * with a client that does not process the daemon's responses */
+ idata->presence_handler = purple_input_add(DNSServiceRefSockFD(idata->presence_svc),
+ PURPLE_INPUT_READ, _mdns_handle_event, idata->presence_svc);
}
}
@@ -290,8 +301,113 @@ _mdns_publish(BonjourDnsSd *data, PublishType type)
return ret;
}
-void
-_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition)
-{
- DNSServiceProcessResult((DNSServiceRef)data);
+gboolean _mdns_browse(BonjourDnsSd *data) {
+ Win32SessionImplData *idata = data->mdns_impl_data;
+
+ g_return_val_if_fail(idata != NULL, FALSE);
+
+ if (DNSServiceBrowse(&idata->browser_svc, 0, 0, ICHAT_SERVICE, NULL,
+ _mdns_service_browse_callback, data->account)
+ == kDNSServiceErr_NoError) {
+ idata->browser_handler = purple_input_add(DNSServiceRefSockFD(idata->browser_svc),
+ PURPLE_INPUT_READ, _mdns_handle_event, idata->browser_svc);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void _mdns_stop(BonjourDnsSd *data) {
+ Win32SessionImplData *idata = data->mdns_impl_data;
+
+ if (idata == NULL)
+ return;
+
+ if (idata->presence_svc != NULL) {
+ purple_input_remove(idata->presence_handler);
+ DNSServiceRefDeallocate(idata->presence_svc);
+ }
+
+ if (idata->browser_svc != NULL) {
+ purple_input_remove(idata->browser_handler);
+ DNSServiceRefDeallocate(idata->browser_svc);
+ }
+
+ g_free(idata);
+
+ data->mdns_impl_data = NULL;
+}
+
+gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) {
+ Win32SessionImplData *idata = data->mdns_impl_data;
+ DNSServiceErrorType err = kDNSServiceErr_NoError;
+
+ g_return_val_if_fail(idata != NULL, FALSE);
+
+ if (avatar_data != NULL && idata->buddy_icon_rec == NULL) {
+ purple_debug_info("bonjour", "Setting new buddy icon.\n");
+ err = DNSServiceAddRecord(idata->presence_svc, &idata->buddy_icon_rec,
+ 0, kDNSServiceType_NULL, avatar_len, avatar_data, 0);
+ } else if (avatar_data != NULL) {
+ purple_debug_info("bonjour", "Updating existing buddy icon.\n");
+ err = DNSServiceUpdateRecord(idata->presence_svc, idata->buddy_icon_rec,
+ 0, avatar_len, avatar_data, 0);
+ } else if (idata->buddy_icon_rec != NULL) {
+ purple_debug_info("bonjour", "Removing existing buddy icon.\n");
+ DNSServiceRemoveRecord(idata->presence_svc, idata->buddy_icon_rec, 0);
+ idata->buddy_icon_rec = NULL;
+ }
+
+ if (err != kDNSServiceErr_NoError)
+ purple_debug_error("bonjour", "Error (%d) setting buddy icon record.\n", err);
+
+ return (err == kDNSServiceErr_NoError);
+}
+
+void _mdns_init_buddy(BonjourBuddy *buddy) {
+ buddy->mdns_impl_data = g_new0(Win32BuddyImplData, 1);
+}
+
+void _mdns_delete_buddy(BonjourBuddy *buddy) {
+ Win32BuddyImplData *idata = buddy->mdns_impl_data;
+
+ g_return_if_fail(idata != NULL);
+
+ if (idata->txt_query != NULL) {
+ purple_input_remove(idata->txt_query_handler);
+ DNSServiceRefDeallocate(idata->txt_query);
+ }
+
+ if (idata->null_query != NULL) {
+ purple_input_remove(idata->null_query_handler);
+ DNSServiceRefDeallocate(idata->null_query);
+ }
+
+ g_free(idata);
+
+ buddy->mdns_impl_data = NULL;
}
+
+void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) {
+ Win32BuddyImplData *idata = buddy->mdns_impl_data;
+ char svc_name[kDNSServiceMaxDomainName];
+
+ g_return_if_fail(idata != NULL);
+
+ /* Cancel any existing query */
+ if (idata->null_query != NULL) {
+ purple_input_remove(idata->null_query_handler);
+ idata->null_query_handler = 0;
+ DNSServiceRefDeallocate(idata->null_query);
+ idata->null_query = NULL;
+ }
+
+ DNSServiceConstructFullName(svc_name, buddy->name, ICHAT_SERVICE, "local");
+ if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->null_query, 0, kDNSServiceInterfaceIndexAny, svc_name,
+ kDNSServiceType_NULL, kDNSServiceClass_IN, _mdns_record_query_callback, buddy)) {
+ idata->null_query_handler = purple_input_add(DNSServiceRefSockFD(idata->null_query),
+ PURPLE_INPUT_READ, _mdns_handle_event, idata->null_query);
+ }
+
+}
+
diff --git a/libpurple/protocols/bonjour/parser.c b/libpurple/protocols/bonjour/parser.c
new file mode 100644
index 0000000000..2fc2f11017
--- /dev/null
+++ b/libpurple/protocols/bonjour/parser.c
@@ -0,0 +1,194 @@
+/*
+ * purple - Bonjour Jabber XML parser stuff
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include "internal.h"
+
+#include <libxml/parser.h>
+
+#include "connection.h"
+#include "debug.h"
+#include "jabber.h"
+#include "parser.h"
+#include "util.h"
+#include "xmlnode.h"
+
+static void
+bonjour_parser_element_start_libxml(void *user_data,
+ const xmlChar *element_name, const xmlChar *prefix, const xmlChar *namespace,
+ int nb_namespaces, const xmlChar **namespaces,
+ int nb_attributes, int nb_defaulted, const xmlChar **attributes)
+{
+ PurpleBuddy *pb = user_data;
+ BonjourBuddy *bb = pb->proto_data;
+ BonjourJabberConversation *bconv = bb->conversation;
+
+ xmlnode *node;
+ int i;
+
+ if(!element_name) {
+ return;
+ } else if(!xmlStrcmp(element_name, (xmlChar*) "stream")) {
+ bconv->recv_stream_start = TRUE;
+ bonjour_jabber_stream_started(pb);
+ } else {
+
+ if(bconv->current)
+ node = xmlnode_new_child(bconv->current, (const char*) element_name);
+ else
+ node = xmlnode_new((const char*) element_name);
+ xmlnode_set_namespace(node, (const char*) namespace);
+
+ for(i=0; i < nb_attributes * 5; i+=5) {
+ char *txt;
+ int attrib_len = attributes[i+4] - attributes[i+3];
+ char *attrib = g_malloc(attrib_len + 1);
+ char *attrib_ns = NULL;
+
+ if (attributes[i+2]) {
+ attrib_ns = g_strdup((char*)attributes[i+2]);;
+ }
+
+ memcpy(attrib, attributes[i+3], attrib_len);
+ attrib[attrib_len] = '\0';
+
+ txt = attrib;
+ attrib = purple_unescape_html(txt);
+ g_free(txt);
+ xmlnode_set_attrib_with_namespace(node, (const char*) attributes[i], attrib_ns, attrib);
+ g_free(attrib);
+ g_free(attrib_ns);
+ }
+
+ bconv->current = node;
+ }
+}
+
+static void
+bonjour_parser_element_end_libxml(void *user_data, const xmlChar *element_name,
+ const xmlChar *prefix, const xmlChar *namespace)
+{
+ PurpleBuddy *pb = user_data;
+ BonjourBuddy *bb = pb->proto_data;
+ BonjourJabberConversation *bconv = bb->conversation;
+
+ if(!bconv->current) {
+ /* We don't keep a reference to the start stream xmlnode,
+ * so we have to check for it here to close the conversation */
+ if(!xmlStrcmp(element_name, (xmlChar*) "stream")) {
+ bonjour_jabber_stream_ended(pb);
+ }
+ return;
+ }
+
+ if(bconv->current->parent) {
+ if(!xmlStrcmp((xmlChar*) bconv->current->name, element_name))
+ bconv->current = bconv->current->parent;
+ } else {
+ xmlnode *packet = bconv->current;
+ bconv->current = NULL;
+ bonjour_jabber_process_packet(pb, packet);
+ xmlnode_free(packet);
+ }
+}
+
+static void
+bonjour_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len)
+{
+ PurpleBuddy *pb = user_data;
+ BonjourBuddy *bb = pb->proto_data;
+ BonjourJabberConversation *bconv = bb->conversation;
+
+ if(!bconv->current)
+ return;
+
+ if(!text || !text_len)
+ return;
+
+ xmlnode_insert_data(bconv->current, (const char*) text, text_len);
+}
+
+static xmlSAXHandler bonjour_parser_libxml = {
+ .internalSubset = NULL,
+ .isStandalone = NULL,
+ .hasInternalSubset = NULL,
+ .hasExternalSubset = NULL,
+ .resolveEntity = NULL,
+ .getEntity = NULL,
+ .entityDecl = NULL,
+ .notationDecl = NULL,
+ .attributeDecl = NULL,
+ .elementDecl = NULL,
+ .unparsedEntityDecl = NULL,
+ .setDocumentLocator = NULL,
+ .startDocument = NULL,
+ .endDocument = NULL,
+ .startElement = NULL,
+ .endElement = NULL,
+ .reference = NULL,
+ .characters = bonjour_parser_element_text_libxml,
+ .ignorableWhitespace = NULL,
+ .processingInstruction = NULL,
+ .comment = NULL,
+ .warning = NULL,
+ .error = NULL,
+ .fatalError = NULL,
+ .getParameterEntity = NULL,
+ .cdataBlock = NULL,
+ .externalSubset = NULL,
+ .initialized = XML_SAX2_MAGIC,
+ ._private = NULL,
+ .startElementNs = bonjour_parser_element_start_libxml,
+ .endElementNs = bonjour_parser_element_end_libxml,
+ .serror = NULL
+};
+
+void
+bonjour_parser_setup(BonjourJabberConversation *bconv)
+{
+
+ /* This seems backwards, but it makes sense. The libxml code creates
+ * the parser context when you try to use it (this way, it can figure
+ * out the encoding at creation time. So, setting up the parser is
+ * just a matter of destroying any current parser. */
+ if (bconv->context) {
+ xmlParseChunk(bconv->context, NULL,0,1);
+ xmlFreeParserCtxt(bconv->context);
+ bconv->context = NULL;
+ }
+}
+
+
+void bonjour_parser_process(PurpleBuddy *pb, const char *buf, int len)
+{
+ BonjourBuddy *bb = pb->proto_data;
+
+ if (bb->conversation->context == NULL) {
+ /* libxml inconsistently starts parsing on creating the
+ * parser, so do a ParseChunk right afterwards to force it. */
+ bb->conversation->context = xmlCreatePushParserCtxt(&bonjour_parser_libxml, pb, buf, len, NULL);
+ xmlParseChunk(bb->conversation->context, "", 0, 0);
+ } else if (xmlParseChunk(bb->conversation->context, buf, len, 0) < 0) {
+ /* TODO: What should we do here - I assume we should display an error or something (maybe just print something to the conv?) */
+ purple_debug_error("bonjour", "Error parsing xml.\n");
+ }
+}
+
diff --git a/libpurple/protocols/bonjour/parser.h b/libpurple/protocols/bonjour/parser.h
new file mode 100644
index 0000000000..a2cddb0036
--- /dev/null
+++ b/libpurple/protocols/bonjour/parser.h
@@ -0,0 +1,33 @@
+/**
+ * @file parser.h Bonjour Jabber XML parser functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _PURPLE_BONJOUR_PARSER_H_
+#define _PURPLE_BONJOUR_PARSER_H_
+
+#include "buddy.h"
+#include "jabber.h"
+
+void bonjour_parser_setup(BonjourJabberConversation *bconv);
+void bonjour_parser_process(PurpleBuddy *pb, const char *buf, int len);
+
+#endif /* _PURPLE_BONJOUR_PARSER_H_ */
diff --git a/libpurple/protocols/jabber/auth.c b/libpurple/protocols/jabber/auth.c
index 7a513a1099..447bbaca35 100644
--- a/libpurple/protocols/jabber/auth.c
+++ b/libpurple/protocols/jabber/auth.c
@@ -296,7 +296,7 @@ static void jabber_auth_start_cyrus(JabberStream *js)
purple_request_yes_no(js->gc, _("Plaintext Authentication"),
_("Plaintext Authentication"),
msg,
- 2, js->gc->account, NULL, NULL, NULL,
+ 2, js->gc->account, NULL, NULL, js->gc->account,
allow_cyrus_plaintext_auth,
disallow_plaintext_auth);
g_free(msg);
@@ -682,11 +682,11 @@ generate_response_value(JabberID *jid, const char *passwd, const char *nonce,
gchar *a1, *convnode=NULL, *convpasswd = NULL, *ha1, *ha2, *kd, *x, *z;
- if((convnode = g_convert(jid->node, strlen(jid->node), "iso-8859-1", "utf-8",
+ if((convnode = g_convert(jid->node, -1, "iso-8859-1", "utf-8",
NULL, NULL, NULL)) == NULL) {
convnode = g_strdup(jid->node);
}
- if(passwd && ((convpasswd = g_convert(passwd, strlen(passwd), "iso-8859-1",
+ if(passwd && ((convpasswd = g_convert(passwd, -1, "iso-8859-1",
"utf-8", NULL, NULL, NULL)) == NULL)) {
convpasswd = g_strdup(passwd);
}
diff --git a/libpurple/protocols/jabber/google.c b/libpurple/protocols/jabber/google.c
index 9c6fe9115f..f7c230dcde 100644
--- a/libpurple/protocols/jabber/google.c
+++ b/libpurple/protocols/jabber/google.c
@@ -119,8 +119,8 @@ jabber_gmail_parse(JabberStream *js, xmlnode *packet, gpointer nul)
g_free(to_name);
g_free(tos);
g_free(froms);
- for (; i >= 0; i--)
- g_free(subjects[i]);
+ for (; i > 0; i--)
+ g_free(subjects[i - 1]);
g_free(subjects);
g_free(urls);
diff --git a/libpurple/protocols/jabber/jabber.c b/libpurple/protocols/jabber/jabber.c
index 50a05c6354..c5498112fe 100644
--- a/libpurple/protocols/jabber/jabber.c
+++ b/libpurple/protocols/jabber/jabber.c
@@ -1709,7 +1709,7 @@ static PurpleCmdRet jabber_cmd_chat_affiliate(PurpleConversation *conv,
static PurpleCmdRet jabber_cmd_chat_role(PurpleConversation *conv,
const char *cmd, char **args, char **error, void *data)
{
- JabberChat *chat;
+ JabberChat *chat = jabber_chat_find_by_conv(conv);
if (!chat || !args || !args[0] || !args[1])
return PURPLE_CMD_RET_FAILED;
@@ -1722,8 +1722,6 @@ static PurpleCmdRet jabber_cmd_chat_role(PurpleConversation *conv,
return PURPLE_CMD_RET_FAILED;
}
- chat = jabber_chat_find_by_conv(conv);
-
if (!jabber_chat_role_user(chat, args[0], args[1])) {
*error = g_strdup_printf(_("Unable to set role \"%s\" for user: %s"),
args[1], args[0]);
diff --git a/libpurple/protocols/jabber/presence.c b/libpurple/protocols/jabber/presence.c
index 5e8ef7a5a3..0253d992b7 100644
--- a/libpurple/protocols/jabber/presence.c
+++ b/libpurple/protocols/jabber/presence.c
@@ -193,16 +193,18 @@ struct _jabber_add_permit {
char *who;
};
-static void authorize_add_cb(struct _jabber_add_permit *jap)
+static void authorize_add_cb(gpointer data)
{
+ struct _jabber_add_permit *jap = data;
jabber_presence_subscription_set(jap->gc->proto_data, jap->who,
"subscribed");
g_free(jap->who);
g_free(jap);
}
-static void deny_add_cb(struct _jabber_add_permit *jap)
+static void deny_add_cb(gpointer data)
{
+ struct _jabber_add_permit *jap = data;
jabber_presence_subscription_set(jap->gc->proto_data, jap->who,
"unsubscribed");
@@ -305,7 +307,7 @@ void jabber_presence_parse(JabberStream *js, xmlnode *packet)
jap->js = js;
purple_account_request_authorization(purple_connection_get_account(js->gc), from, NULL, NULL, NULL, onlist,
- G_CALLBACK(authorize_add_cb), G_CALLBACK(deny_add_cb), jap);
+ authorize_add_cb, deny_add_cb, jap);
jabber_id_free(jid);
return;
} else if(type && !strcmp(type, "subscribed")) {
diff --git a/libpurple/protocols/jabber/xdata.c b/libpurple/protocols/jabber/xdata.c
index b85dda8b37..9763bdb2c2 100644
--- a/libpurple/protocols/jabber/xdata.c
+++ b/libpurple/protocols/jabber/xdata.c
@@ -256,7 +256,7 @@ void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb
continue;
if(!(lbl = xmlnode_get_attrib(optnode, "label")))
- label = value;
+ lbl = value;
data->values = g_slist_prepend(data->values, value);
diff --git a/libpurple/protocols/msn/userlist.c b/libpurple/protocols/msn/userlist.c
index 6f7d7922a1..4fac5d8aa6 100644
--- a/libpurple/protocols/msn/userlist.c
+++ b/libpurple/protocols/msn/userlist.c
@@ -38,8 +38,9 @@ typedef struct
* Callbacks
**************************************************************************/
static void
-msn_accept_add_cb(MsnPermitAdd *pa)
+msn_accept_add_cb(gpointer data)
{
+ MsnPermitAdd *pa = data;
MsnSession *session = pa->gc->proto_data;
MsnUserList *userlist = session->userlist;
@@ -51,8 +52,9 @@ msn_accept_add_cb(MsnPermitAdd *pa)
}
static void
-msn_cancel_add_cb(MsnPermitAdd *pa)
+msn_cancel_add_cb(gpointer data)
{
+ MsnPermitAdd *pa = data;
MsnSession *session = pa->gc->proto_data;
MsnUserList *userlist = session->userlist;
@@ -75,7 +77,7 @@ got_new_entry(PurpleConnection *gc, const char *passport, const char *friendly)
purple_account_request_authorization(purple_connection_get_account(gc), passport, NULL, friendly, NULL,
purple_find_buddy(purple_connection_get_account(gc), passport) != NULL,
- G_CALLBACK(msn_accept_add_cb), G_CALLBACK(msn_cancel_add_cb), pa);
+ msn_accept_add_cb, msn_cancel_add_cb, pa);
}
/**************************************************************************
diff --git a/libpurple/protocols/oscar/family_feedbag.c b/libpurple/protocols/oscar/family_feedbag.c
index 698b04c3e0..986a12b7d0 100644
--- a/libpurple/protocols/oscar/family_feedbag.c
+++ b/libpurple/protocols/oscar/family_feedbag.c
@@ -695,18 +695,6 @@ int aim_ssi_cleanlist(OscarData *od)
cur = cur->next;
}
- /* Check if there are empty groups and delete them */
- cur = od->ssi.local;
- while (cur) {
- next = cur->next;
- if (cur->type == AIM_SSI_TYPE_GROUP) {
- aim_tlv_t *tlv = aim_tlv_gettlv(cur->data, 0x00c8, 1);
- if (!tlv || !tlv->length)
- aim_ssi_itemlist_del(&od->ssi.local, cur);
- }
- cur = next;
- }
-
/* Check if the master group is empty */
if ((cur = aim_ssi_itemlist_find(od->ssi.local, 0x0000, 0x0000)) && (!cur->data))
aim_ssi_itemlist_del(&od->ssi.local, cur);
@@ -841,18 +829,39 @@ int aim_ssi_delbuddy(OscarData *od, const char *name, const char *group)
/* Modify the parent group */
aim_ssi_itemlist_rebuildgroup(od->ssi.local, group);
- /* Check if we should delete the parent group */
- if ((del = aim_ssi_itemlist_finditem(od->ssi.local, group, NULL, AIM_SSI_TYPE_GROUP)) && (!del->data)) {
- aim_ssi_itemlist_del(&od->ssi.local, del);
+ /* Sync our local list with the server list */
+ return aim_ssi_sync(od);
+}
- /* Modify the parent group */
- aim_ssi_itemlist_rebuildgroup(od->ssi.local, NULL);
+/**
+ * Deletes a group from the list.
+ *
+ * @param od The oscar odion.
+ * @param group The name of the group.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_delgroup(OscarData *od, const char *group)
+{
+ struct aim_ssi_item *del;
+ aim_tlv_t *tlv;
- /* Check if we should delete the parent's parent (the master group) */
- if ((del = aim_ssi_itemlist_find(od->ssi.local, 0x0000, 0x0000)) && (!del->data)) {
- aim_ssi_itemlist_del(&od->ssi.local, del);
- }
- }
+ if (!od)
+ return -EINVAL;
+
+ /* Find the group */
+ if (!(del = aim_ssi_itemlist_finditem(od->ssi.local, group, NULL, AIM_SSI_TYPE_GROUP)))
+ return -EINVAL;
+
+ /* Don't delete the group if it's not empty */
+ tlv = aim_tlv_gettlv(del->data, 0x00c8, 1);
+ if (tlv && tlv->length > 0)
+ return -EINVAL;
+
+ /* Remove the item from the list */
+ aim_ssi_itemlist_del(&od->ssi.local, del);
+
+ /* Modify the parent group */
+ aim_ssi_itemlist_rebuildgroup(od->ssi.local, group);
/* Sync our local list with the server list */
return aim_ssi_sync(od);
diff --git a/libpurple/protocols/oscar/flap_connection.c b/libpurple/protocols/oscar/flap_connection.c
index 2b7983d392..f163989862 100644
--- a/libpurple/protocols/oscar/flap_connection.c
+++ b/libpurple/protocols/oscar/flap_connection.c
@@ -129,8 +129,8 @@ static gboolean flap_connection_send_queued(gpointer data)
new_current = rateclass_get_new_current(conn, rateclass, &now);
+ /* (Add 100ms padding to account for inaccuracies in the calculation) */
if (new_current < rateclass->alert + 100)
- /* (Add 100ms padding to account for inaccuracies in the calculation) */
/* Not ready to send this SNAC yet--keep waiting. */
return TRUE;
@@ -186,9 +186,9 @@ flap_connection_send_snac(OscarData *od, FlapConnection *conn, guint16 family, g
gettimeofday(&now, NULL);
new_current = rateclass_get_new_current(conn, rateclass, &now);
+ /* (Add 100ms padding to account for inaccuracies in the calculation) */
if (new_current < rateclass->alert + 100)
{
- /* (Add 100ms padding to account for inaccuracies in the calculation) */
enqueue = TRUE;
}
else
diff --git a/libpurple/protocols/oscar/libaim.c b/libpurple/protocols/oscar/libaim.c
index 806299b4fa..3511671458 100644
--- a/libpurple/protocols/oscar/libaim.c
+++ b/libpurple/protocols/oscar/libaim.c
@@ -78,7 +78,7 @@ static PurplePluginProtocolInfo prpl_info =
oscar_convo_closed, /* convo_closed */
oscar_normalize, /* normalize */
oscar_set_icon, /* set_buddy_icon */
- NULL, /* remove_group */
+ oscar_remove_group, /* remove_group */
NULL, /* get_cb_real_name */
NULL, /* set_chat_topic */
NULL, /* find_blist_chat */
diff --git a/libpurple/protocols/oscar/libicq.c b/libpurple/protocols/oscar/libicq.c
index 6bfa45051e..c73d97983d 100644
--- a/libpurple/protocols/oscar/libicq.c
+++ b/libpurple/protocols/oscar/libicq.c
@@ -78,7 +78,7 @@ static PurplePluginProtocolInfo prpl_info =
oscar_convo_closed, /* convo_closed */
oscar_normalize, /* normalize */
oscar_set_icon, /* set_buddy_icon */
- NULL, /* remove_group */
+ oscar_remove_group, /* remove_group */
NULL, /* get_cb_real_name */
NULL, /* set_chat_topic */
NULL, /* find_blist_chat */
diff --git a/libpurple/protocols/oscar/oscar.c b/libpurple/protocols/oscar/oscar.c
index cd4691692a..2ff25168af 100644
--- a/libpurple/protocols/oscar/oscar.c
+++ b/libpurple/protocols/oscar/oscar.c
@@ -161,7 +161,6 @@ static int purple_conv_chat_leave (OscarData *, FlapConnection *, FlapFram
static int purple_conv_chat_info_update (OscarData *, FlapConnection *, FlapFrame *, ...);
static int purple_conv_chat_incoming_msg(OscarData *, FlapConnection *, FlapFrame *, ...);
static int purple_email_parseupdate(OscarData *, FlapConnection *, FlapFrame *, ...);
-static int purple_icon_error (OscarData *, FlapConnection *, FlapFrame *, ...);
static int purple_icon_parseicon (OscarData *, FlapConnection *, FlapFrame *, ...);
static int oscar_icon_req (OscarData *, FlapConnection *, FlapFrame *, ...);
static int purple_parse_msgack (OscarData *, FlapConnection *, FlapFrame *, ...);
@@ -195,7 +194,7 @@ static int purple_ssi_authrequest (OscarData *, FlapConnection *, FlapFrame *,
static int purple_ssi_authreply (OscarData *, FlapConnection *, FlapFrame *, ...);
static int purple_ssi_gotadded (OscarData *, FlapConnection *, FlapFrame *, ...);
-static gboolean purple_icon_timerfunc(gpointer data);
+static void purple_icons_fetch(PurpleConnection *gc);
static void recent_buddies_cb(const char *name, PurplePrefType type, gconstpointer value, gpointer data);
void oscar_set_info(PurpleConnection *gc, const char *info);
@@ -483,7 +482,7 @@ purple_plugin_oscar_convert_to_best_encoding(PurpleConnection *gc,
/* Attempt to send as ASCII */
if (oscar_charset_check(from) == AIM_CHARSET_ASCII) {
- *msg = g_convert(from, strlen(from), "ASCII", "UTF-8", NULL, &msglen, NULL);
+ *msg = g_convert(from, -1, "ASCII", "UTF-8", NULL, &msglen, NULL);
*charset = AIM_CHARSET_ASCII;
*charsubset = 0x0000;
*msglen_int = msglen;
@@ -505,7 +504,7 @@ purple_plugin_oscar_convert_to_best_encoding(PurpleConnection *gc,
b = purple_find_buddy(account, destsn);
if ((b != NULL) && (PURPLE_BUDDY_IS_ONLINE(b)))
{
- *msg = g_convert(from, strlen(from), "UCS-2BE", "UTF-8", NULL, &msglen, NULL);
+ *msg = g_convert(from, -1, "UCS-2BE", "UTF-8", NULL, &msglen, NULL);
if (*msg != NULL)
{
*charset = AIM_CHARSET_UNICODE;
@@ -528,7 +527,7 @@ purple_plugin_oscar_convert_to_best_encoding(PurpleConnection *gc,
* XXX - We need a way to only attempt to convert if we KNOW "from"
* can be converted to "charsetstr"
*/
- *msg = g_convert(from, strlen(from), charsetstr, "UTF-8", NULL, &msglen, NULL);
+ *msg = g_convert(from, -1, charsetstr, "UTF-8", NULL, &msglen, NULL);
if (*msg != NULL) {
*charset = AIM_CHARSET_CUSTOM;
*charsubset = 0x0000;
@@ -539,7 +538,7 @@ purple_plugin_oscar_convert_to_best_encoding(PurpleConnection *gc,
/*
* Nothing else worked, so send as UCS-2BE.
*/
- *msg = g_convert(from, strlen(from), "UCS-2BE", "UTF-8", NULL, &msglen, &err);
+ *msg = g_convert(from, -1, "UCS-2BE", "UTF-8", NULL, &msglen, &err);
if (*msg != NULL) {
*charset = AIM_CHARSET_UNICODE;
*charsubset = 0x0000;
@@ -1156,8 +1155,7 @@ flap_connection_established_bart(OscarData *od, FlapConnection *conn)
od->iconconnecting = FALSE;
- if (od->icontimer == 0)
- od->icontimer = purple_timeout_add(100, purple_icon_timerfunc, gc);
+ purple_icons_fetch(gc);
}
static int
@@ -1203,7 +1201,6 @@ oscar_login(PurpleAccount *account)
oscar_data_addhandler(od, SNAC_FAMILY_AUTH, 0x0003, purple_parse_auth_resp, 0);
oscar_data_addhandler(od, SNAC_FAMILY_AUTH, 0x0007, purple_parse_login, 0);
oscar_data_addhandler(od, SNAC_FAMILY_AUTH, SNAC_SUBTYPE_AUTH_SECURID_REQUEST, purple_parse_auth_securid_request, 0);
- oscar_data_addhandler(od, SNAC_FAMILY_BART, SNAC_SUBTYPE_BART_ERROR, purple_icon_error, 0);
oscar_data_addhandler(od, SNAC_FAMILY_BART, SNAC_SUBTYPE_BART_RESPONSE, purple_icon_parseicon, 0);
oscar_data_addhandler(od, SNAC_FAMILY_BOS, 0x0001, purple_parse_genericerr, 0);
oscar_data_addhandler(od, SNAC_FAMILY_BOS, 0x0003, purple_bosrights, 0);
@@ -1873,13 +1870,12 @@ static int purple_parse_oncoming(OscarData *od, FlapConnection *conn, FlapFrame
saved_b16 = purple_buddy_icons_get_checksum_for_user(b);
if (!b16 || !saved_b16 || strcmp(b16, saved_b16)) {
- GSList *cur = od->requesticon;
- while (cur && aim_sncmp((char *)cur->data, info->sn))
- cur = cur->next;
- if (!cur) {
- od->requesticon = g_slist_append(od->requesticon, g_strdup(purple_normalize(account, info->sn)));
- if (od->icontimer == 0)
- od->icontimer = purple_timeout_add(500, purple_icon_timerfunc, gc);
+ if (g_slist_find_custom(od->requesticon, info->sn,
+ (GCompareFunc)aim_sncmp) == NULL)
+ {
+ od->requesticon = g_slist_prepend(od->requesticon,
+ g_strdup(purple_normalize(account, info->sn)));
+ purple_icons_fetch(gc);
}
}
g_free(b16);
@@ -2265,8 +2261,9 @@ purple_auth_sendrequest_menu(PurpleBlistNode *node, gpointer ignored)
/* When other people ask you for authorization */
static void
-purple_auth_grant(struct name_data *data)
+purple_auth_grant(gpointer cbdata)
{
+ struct name_data *data = cbdata;
PurpleConnection *gc = data->gc;
OscarData *od = gc->proto_data;
@@ -2286,8 +2283,9 @@ purple_auth_dontgrant(struct name_data *data, char *msg)
}
static void
-purple_auth_dontgrant_msgprompt(struct name_data *data)
+purple_auth_dontgrant_msgprompt(gpointer cbdata)
{
+ struct name_data *data = cbdata;
purple_request_input(data->gc, NULL, _("Authorization Denied Message:"),
NULL, _("No reason given."), TRUE, FALSE, NULL,
_("_OK"), G_CALLBACK(purple_auth_dontgrant),
@@ -2408,8 +2406,8 @@ incomingim_chan4(OscarData *od, FlapConnection *conn, aim_userinfo_t *userinfo,
purple_account_request_authorization(account, sn, NULL, NULL,
reason, purple_find_buddy(account, sn) != NULL,
- G_CALLBACK(purple_auth_grant),
- G_CALLBACK(purple_auth_dontgrant_msgprompt), data);
+ purple_auth_grant,
+ purple_auth_dontgrant_msgprompt, data);
g_free(reason);
}
} break;
@@ -3237,24 +3235,8 @@ static int purple_email_parseupdate(OscarData *od, FlapConnection *conn, FlapFra
return 1;
}
-static int purple_icon_error(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) {
- PurpleConnection *gc = od->gc;
- char *sn;
-
- sn = od->requesticon->data;
- purple_debug_misc("oscar", "removing %s from hash table\n", sn);
- od->requesticon = g_slist_remove(od->requesticon, sn);
- g_free(sn);
-
- if (od->icontimer == 0)
- od->icontimer = purple_timeout_add(500, purple_icon_timerfunc, gc);
-
- return 1;
-}
-
static int purple_icon_parseicon(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) {
PurpleConnection *gc = od->gc;
- GSList *cur;
va_list ap;
char *sn;
guint8 iconcsumtype, *iconcsum, *icon;
@@ -3280,38 +3262,23 @@ static int purple_icon_parseicon(OscarData *od, FlapConnection *conn, FlapFrame
g_free(b16);
}
- cur = od->requesticon;
- while (cur) {
- char *cursn = cur->data;
- if (!aim_sncmp(cursn, sn)) {
- od->requesticon = g_slist_remove(od->requesticon, cursn);
- g_free(cursn);
- cur = od->requesticon;
- } else
- cur = cur->next;
- }
-
- if (od->icontimer == 0)
- od->icontimer = purple_timeout_add(250, purple_icon_timerfunc, gc);
-
return 1;
}
-static gboolean purple_icon_timerfunc(gpointer data) {
- PurpleConnection *gc = data;
+static void
+purple_icons_fetch(PurpleConnection *gc)
+{
OscarData *od = gc->proto_data;
aim_userinfo_t *userinfo;
FlapConnection *conn;
- od->icontimer = 0;
-
conn = flap_connection_getbytype(od, SNAC_FAMILY_BART);
if (!conn) {
if (!od->iconconnecting) {
aim_srv_requestnew(od, SNAC_FAMILY_BART);
od->iconconnecting = TRUE;
}
- return FALSE;
+ return;
}
if (od->set_icon) {
@@ -3322,32 +3289,24 @@ static gboolean purple_icon_timerfunc(gpointer data) {
} else {
purple_debug_info("oscar",
"Uploading icon to icon server\n");
- aim_bart_upload(od, purple_imgstore_get_data(img),
+ aim_bart_upload(od, purple_imgstore_get_data(img),
purple_imgstore_get_size(img));
purple_imgstore_unref(img);
}
od->set_icon = FALSE;
}
- if (!od->requesticon) {
- purple_debug_misc("oscar",
- "no more icons to request\n");
- return FALSE;
- }
+ while (od->requesticon != NULL)
+ {
+ userinfo = aim_locate_finduserinfo(od, (char *)od->requesticon->data);
+ if ((userinfo != NULL) && (userinfo->iconcsumlen > 0))
+ aim_bart_request(od, od->requesticon->data, userinfo->iconcsumtype, userinfo->iconcsum, userinfo->iconcsumlen);
- userinfo = aim_locate_finduserinfo(od, (char *)od->requesticon->data);
- if ((userinfo != NULL) && (userinfo->iconcsumlen > 0)) {
- aim_bart_request(od, od->requesticon->data, userinfo->iconcsumtype, userinfo->iconcsum, userinfo->iconcsumlen);
- return FALSE;
- } else {
- gchar *sn = od->requesticon->data;
- od->requesticon = g_slist_remove(od->requesticon, sn);
- g_free(sn);
+ g_free(od->requesticon->data);
+ od->requesticon = g_slist_delete_link(od->requesticon, od->requesticon);
}
- od->icontimer = purple_timeout_add(100, purple_icon_timerfunc, gc);
-
- return FALSE;
+ purple_debug_misc("oscar", "no more icons to request\n");
}
/*
@@ -4389,10 +4348,10 @@ gchar *purple_prpl_oscar_convert_to_infotext(const gchar *str, gsize *ret_len, c
charset = oscar_charset_check(str);
if (charset == AIM_CHARSET_UNICODE) {
- encoded = g_convert(str, strlen(str), "UCS-2BE", "UTF-8", NULL, ret_len, NULL);
+ encoded = g_convert(str, -1, "UCS-2BE", "UTF-8", NULL, ret_len, NULL);
*encoding = "unicode-2-0";
} else if (charset == AIM_CHARSET_CUSTOM) {
- encoded = g_convert(str, strlen(str), "ISO-8859-1", "UTF-8", NULL, ret_len, NULL);
+ encoded = g_convert(str, -1, "ISO-8859-1", "UTF-8", NULL, ret_len, NULL);
*encoding = "iso-8859-1";
} else {
encoded = g_strdup(str);
@@ -4523,9 +4482,9 @@ oscar_set_info_and_status(PurpleAccount *account, gboolean setinfo, const char *
{
status_text = purple_markup_strip_html(status_html);
/* If the status_text is longer than 60 character then truncate it */
- if (strlen(status_text) > 60)
+ if (strlen(status_text) > MAXAVAILMSGLEN)
{
- char *tmp = g_utf8_find_prev_char(status_text, &status_text[58]);
+ char *tmp = g_utf8_find_prev_char(status_text, &status_text[MAXAVAILMSGLEN - 2]);
strcpy(tmp, "...");
}
}
@@ -4709,6 +4668,11 @@ void oscar_rename_group(PurpleConnection *gc, const char *old_name, PurpleGroup
}
}
+void oscar_remove_group(PurpleConnection *gc, PurpleGroup *group)
+{
+ aim_ssi_delgroup(gc->proto_data, group->name);
+}
+
static gboolean purple_ssi_rerequestdata(gpointer data) {
OscarData *od = data;
@@ -4902,29 +4866,34 @@ static int purple_ssi_parselist(OscarData *od, FlapConnection *conn, FlapFrame *
/* Add from server list to local list */
for (curitem=od->ssi.local; curitem; curitem=curitem->next) {
- if ((curitem->name == NULL) || (g_utf8_validate(curitem->name, -1, NULL)))
+ if ((curitem->name == NULL) || (g_utf8_validate(curitem->name, -1, NULL)))
switch (curitem->type) {
case 0x0000: { /* Buddy */
if (curitem->name) {
- char *gname = aim_ssi_itemlist_findparentname(od->ssi.local, curitem->name);
+ struct aim_ssi_item *groupitem = aim_ssi_itemlist_find(od->ssi.local, curitem->gid, 0x0000);
+ char *gname = groupitem ? groupitem->name : NULL;
char *gname_utf8 = gname ? oscar_utf8_try_convert(gc->account, gname) : NULL;
char *alias = aim_ssi_getalias(od->ssi.local, gname, curitem->name);
char *alias_utf8;
+ g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans"));
+ if (g == NULL) {
+ g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans"));
+ purple_blist_add_group(g, NULL);
+ }
+
if (alias != NULL)
{
if (g_utf8_validate(alias, -1, NULL))
alias_utf8 = g_strdup(alias);
else
alias_utf8 = oscar_utf8_try_convert(account, alias);
+ g_free(alias);
}
else
alias_utf8 = NULL;
- b = purple_find_buddy(gc->account, curitem->name);
- /* Should gname be freed here? -- elb */
- /* Not with the current code, but that might be cleaner -- med */
- g_free(alias);
+ b = purple_find_buddy_in_group(gc->account, curitem->name, g);
if (b) {
/* Get server stored alias */
if (alias_utf8) {
@@ -4934,13 +4903,8 @@ static int purple_ssi_parselist(OscarData *od, FlapConnection *conn, FlapFrame *
} else {
b = purple_buddy_new(gc->account, curitem->name, alias_utf8);
- if (!(g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans")))) {
- g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans"));
- purple_blist_add_group(g, NULL);
- }
-
purple_debug_info("oscar",
- "ssi: adding buddy %s to group %s to local list\n", curitem->name, gname_utf8 ? gname_utf8 : _("Orphans"));
+ "ssi: adding buddy %s to group %s to local list\n", curitem->name, g->name);
purple_blist_add_buddy(b, NULL, g, NULL);
}
if (!aim_sncmp(curitem->name, account->username)) {
@@ -4957,7 +4921,12 @@ static int purple_ssi_parselist(OscarData *od, FlapConnection *conn, FlapFrame *
} break;
case 0x0001: { /* Group */
- /* Shouldn't add empty groups */
+ char *gname = curitem->name;
+ char *gname_utf8 = gname ? oscar_utf8_try_convert(gc->account, gname) : NULL;
+ if (gname_utf8 != NULL && purple_find_group(gname_utf8) == NULL) {
+ g = purple_group_new(gname_utf8);
+ purple_blist_add_group(g, NULL);
+ }
} break;
case 0x0002: { /* Permit buddy */
@@ -5203,8 +5172,8 @@ static int purple_ssi_authrequest(OscarData *od, FlapConnection *conn, FlapFrame
purple_account_request_authorization(account, sn, NULL,
(buddy ? purple_buddy_get_alias_only(buddy) : NULL),
- reason, buddy != NULL, G_CALLBACK(purple_auth_grant),
- G_CALLBACK(purple_auth_dontgrant_msgprompt), data);
+ reason, buddy != NULL, purple_auth_grant,
+ purple_auth_dontgrant_msgprompt, data);
g_free(reason);
return 1;
diff --git a/libpurple/protocols/oscar/oscar.h b/libpurple/protocols/oscar/oscar.h
index fb8d3b2cb8..86a8b2da48 100644
--- a/libpurple/protocols/oscar/oscar.h
+++ b/libpurple/protocols/oscar/oscar.h
@@ -114,6 +114,11 @@ extern "C" {
*/
#define MAXCHATMSGLEN 512
+/*
+ * Found by trial and error.
+ */
+#define MAXAVAILMSGLEN 251
+
/**
* Maximum length for the password of an ICQ account
*/
@@ -444,7 +449,6 @@ struct _OscarData
GSList *requesticon;
gboolean icq;
- guint icontimer;
guint getblisttimer;
guint getinfotimer;
@@ -1211,6 +1215,7 @@ int aim_ssi_addbuddy(OscarData *od, const char *name, const char *group, GSList
int aim_ssi_addpermit(OscarData *od, const char *name);
int aim_ssi_adddeny(OscarData *od, const char *name);
int aim_ssi_delbuddy(OscarData *od, const char *name, const char *group);
+int aim_ssi_delgroup(OscarData *od, const char *group);
int aim_ssi_delpermit(OscarData *od, const char *name);
int aim_ssi_deldeny(OscarData *od, const char *name);
int aim_ssi_movebuddy(OscarData *od, const char *oldgn, const char *newgn, const char *sn);
diff --git a/libpurple/protocols/oscar/oscar_data.c b/libpurple/protocols/oscar/oscar_data.c
index 375d1ef293..ece1515c7e 100644
--- a/libpurple/protocols/oscar/oscar_data.c
+++ b/libpurple/protocols/oscar/oscar_data.c
@@ -95,8 +95,6 @@ oscar_data_destroy(OscarData *od)
g_free(od->email);
g_free(od->newp);
g_free(od->oldp);
- if (od->icontimer > 0)
- purple_timeout_remove(od->icontimer);
if (od->getblisttimer > 0)
purple_timeout_remove(od->getblisttimer);
if (od->getinfotimer > 0)
diff --git a/libpurple/protocols/oscar/oscarcommon.h b/libpurple/protocols/oscar/oscarcommon.h
index 94363ef04e..35e3fddc33 100644
--- a/libpurple/protocols/oscar/oscarcommon.h
+++ b/libpurple/protocols/oscar/oscarcommon.h
@@ -82,6 +82,7 @@ void oscar_rename_group(PurpleConnection *gc, const char *old_name, PurpleGroup
void oscar_convo_closed(PurpleConnection *gc, const char *who);
const char *oscar_normalize(const PurpleAccount *account, const char *str);
void oscar_set_icon(PurpleConnection *gc, PurpleStoredImage *img);
+void oscar_remove_group(PurpleConnection *gc, PurpleGroup *group);
gboolean oscar_can_receive_file(PurpleConnection *gc, const char *who);
void oscar_send_file(PurpleConnection *gc, const char *who, const char *file);
PurpleXfer *oscar_new_xfer(PurpleConnection *gc, const char *who);
diff --git a/libpurple/protocols/qq/buddy_opt.c b/libpurple/protocols/qq/buddy_opt.c
index c12dee9bd6..60e2eb85f4 100644
--- a/libpurple/protocols/qq/buddy_opt.c
+++ b/libpurple/protocols/qq/buddy_opt.c
@@ -270,11 +270,11 @@ void qq_process_add_buddy_auth_reply(guint8 *buf, gint buf_len, PurpleConnection
if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
read_packet_b(data, &cursor, len, &reply);
if (reply != QQ_ADD_BUDDY_AUTH_REPLY_OK) {
- purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Add buddy with auth request fails\n");
+ purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Add buddy with auth request failed\n");
if (NULL == (segments = split_data(data, len, "\x1f", 2)))
return;
msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT);
- purple_notify_error(gc, NULL, _("Add buddy with auth request fails"), msg_utf8);
+ purple_notify_error(gc, NULL, _("Add buddy with auth request failed"), msg_utf8);
g_free(msg_utf8);
} else {
purple_debug(PURPLE_DEBUG_INFO, "QQ", "Add buddy with auth request OK\n");
@@ -305,6 +305,7 @@ void qq_process_remove_buddy_reply(guint8 *buf, gint buf_len, PurpleConnection *
purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Remove buddy fails\n");
} else { /* if reply */
purple_debug(PURPLE_DEBUG_INFO, "QQ", "Remove buddy OK\n");
+ /* TODO: We don't really need to notify the user about this, do we? */
purple_notify_info(gc, NULL, _("You have successfully removed a buddy"), NULL);
}
} else {
@@ -333,7 +334,8 @@ void qq_process_remove_self_reply(guint8 *buf, gint buf_len, PurpleConnection *g
purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Remove self fails\n");
else { /* if reply */
purple_debug(PURPLE_DEBUG_INFO, "QQ", "Remove self from a buddy OK\n");
- purple_notify_info(gc, NULL, _("You have successfully removed yourself from a buddy"), NULL);
+ /* TODO: Does the user really need to be notified about this? */
+ purple_notify_info(gc, NULL, _("You have successfully removed yourself from your friend's buddy list"), NULL);
}
} else {
purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt remove self reply\n");
@@ -413,7 +415,7 @@ void qq_process_add_buddy_reply(guint8 *buf, gint buf_len, guint16 seq, PurpleCo
g_free(nombre);
} else { /* add OK */
qq_add_buddy_by_recv_packet(gc, for_uid, TRUE, TRUE);
- msg = g_strdup_printf(_("You have added %d in buddy list"), for_uid);
+ msg = g_strdup_printf(_("You have added %d to buddy list"), for_uid);
purple_notify_info(gc, NULL, msg, NULL);
g_free(msg);
}
diff --git a/libpurple/protocols/qq/group.c b/libpurple/protocols/qq/group.c
index 25d834dc19..517c0a9366 100644
--- a/libpurple/protocols/qq/group.c
+++ b/libpurple/protocols/qq/group.c
@@ -117,7 +117,7 @@ PurpleRoomlist *qq_roomlist_get_list(PurpleConnection *gc)
purple_roomlist_set_in_progress(qd->roomlist, TRUE);
purple_request_input(gc, _("QQ Qun"),
- _("Please input external group ID"),
+ _("Please enter external group ID"),
_("You can only search for permanent QQ groups\n"),
NULL, FALSE, FALSE, NULL,
_("Search"), G_CALLBACK(_qq_group_search_callback),
diff --git a/libpurple/protocols/qq/group_im.c b/libpurple/protocols/qq/group_im.c
index 653b5203a0..41f81695dc 100644
--- a/libpurple/protocols/qq/group_im.c
+++ b/libpurple/protocols/qq/group_im.c
@@ -123,7 +123,7 @@ void qq_process_recv_group_im_apply_join
convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT);
- msg = g_strdup_printf(_("User %d applied to join group %d"), user_uid, external_group_id);
+ msg = g_strdup_printf(_("User %d requested to join group %d"), user_uid, external_group_id);
reason = g_strdup_printf(_("Reason: %s"), reason_utf8);
g = g_new0(group_member_opt, 1);
@@ -177,7 +177,7 @@ void qq_process_recv_group_im_been_rejected
convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT);
msg = g_strdup_printf
- (_("You request to join group %d has been rejected by admin %d"), external_group_id, admin_uid);
+ (_("Your request to join group %d has been rejected by admin %d"), external_group_id, admin_uid);
reason = g_strdup_printf(_("Reason: %s"), reason_utf8);
purple_notify_warning(gc, _("QQ Qun Operation"), msg, reason);
@@ -218,7 +218,7 @@ void qq_process_recv_group_im_been_approved
convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT);
msg = g_strdup_printf
- (_("You request to join group %d has been approved by admin %d"), external_group_id, admin_uid);
+ (_("Your request to join group %d has been approved by admin %d"), external_group_id, admin_uid);
purple_notify_warning(gc, _("QQ Qun Operation"), msg, NULL);
@@ -254,7 +254,7 @@ void qq_process_recv_group_im_been_removed
g_return_if_fail(external_group_id > 0 && uid > 0);
- msg = g_strdup_printf(_("You [%d] has exit group \"%d\""), uid, external_group_id);
+ msg = g_strdup_printf(_("You [%d] have left group \"%d\""), uid, external_group_id);
purple_notify_info(gc, _("QQ Qun Operation"), msg, NULL);
group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
@@ -288,7 +288,7 @@ void qq_process_recv_group_im_been_added
g_return_if_fail(external_group_id > 0 && uid > 0);
- msg = g_strdup_printf(_("You [%d] has been added by group \"%d\""), uid, external_group_id);
+ msg = g_strdup_printf(_("You [%d] have been added to group \"%d\""), uid, external_group_id);
purple_notify_info(gc, _("QQ Qun Operation"), msg, _("This group has been added to your buddy list"));
group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
diff --git a/libpurple/protocols/qq/group_internal.c b/libpurple/protocols/qq/group_internal.c
index ab354da96b..e6f6ead374 100644
--- a/libpurple/protocols/qq/group_internal.c
+++ b/libpurple/protocols/qq/group_internal.c
@@ -38,7 +38,7 @@ static gchar *_qq_group_set_my_status_desc(qq_group *group)
switch (group->my_status) {
case QQ_GROUP_MEMBER_STATUS_NOT_MEMBER:
- status_desc = _("I am not member");
+ status_desc = _("I am not a member");
break;
case QQ_GROUP_MEMBER_STATUS_IS_MEMBER:
status_desc = _("I am a member");
diff --git a/libpurple/protocols/qq/group_join.c b/libpurple/protocols/qq/group_join.c
index fd037d9bbd..c0740dda2e 100644
--- a/libpurple/protocols/qq/group_join.c
+++ b/libpurple/protocols/qq/group_join.c
@@ -230,7 +230,7 @@ void qq_process_group_cmd_exit_group(guint8 *data, guint8 **cursor, gint len, Pu
purple_blist_remove_chat(chat);
qq_group_delete_internal_record(qd, internal_group_id);
}
- purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully exited the group"), NULL);
+ purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully left the group"), NULL);
} else {
purple_debug(PURPLE_DEBUG_ERROR, "QQ",
"Invalid exit group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes);
@@ -254,8 +254,8 @@ void qq_process_group_cmd_join_group_auth(guint8 *data, guint8 **cursor, gint le
if (bytes == expected_bytes)
purple_notify_info
- (gc, _("QQ Group Auth"),
- _("Your authorization operation has been accepted by the QQ server"), NULL);
+ (gc, _("QQ Group Auth"),
+ _("Your authorization request has been accepted by the QQ server"), NULL);
else
purple_debug(PURPLE_DEBUG_ERROR, "QQ",
"Invalid join group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes);
@@ -325,8 +325,8 @@ void qq_group_join(PurpleConnection *gc, GHashTable *data)
errno = 0;
external_group_id = strtol(external_group_id_ptr, NULL, 10);
if (errno != 0) {
- purple_notify_error(gc, _("Error"),
- _("You inputted a group id outside the acceptable range"), NULL);
+ purple_notify_error(gc, _("Error"),
+ _("You entered a group ID outside the acceptable range"), NULL);
return;
}
@@ -357,12 +357,12 @@ void qq_group_exit(PurpleConnection *gc, GHashTable *data)
g->uid = internal_group_id;
purple_request_action(gc, _("QQ Qun Operation"),
- _("Are you sure to exit this Qun?"),
+ _("Are you sure you want to leave this Qun?"),
_
("Note, if you are the creator, \nthis operation will eventually remove this Qun."),
1,
purple_connection_get_account(gc), NULL, NULL,
g, 2, _("Cancel"),
G_CALLBACK(qq_do_nothing_with_gc_and_uid),
- _("Go ahead"), G_CALLBACK(_qq_group_exit_with_gc_and_id));
+ _("Continue"), G_CALLBACK(_qq_group_exit_with_gc_and_id));
}
diff --git a/libpurple/protocols/qq/group_opt.c b/libpurple/protocols/qq/group_opt.c
index 83b817126a..57391696fb 100644
--- a/libpurple/protocols/qq/group_opt.c
+++ b/libpurple/protocols/qq/group_opt.c
@@ -120,8 +120,8 @@ void qq_group_search_application_with_struct(group_member_opt *g)
{
g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0);
- qq_send_packet_get_info(g->gc, g->member, TRUE); /* we wanna see window */
- purple_request_action(g->gc, NULL, _("Do you wanna approve the request?"), "", 2,
+ qq_send_packet_get_info(g->gc, g->member, TRUE); /* we want to see window */
+ purple_request_action(g->gc, NULL, _("Do you want to approve the request?"), "", 2,
purple_connection_get_account(g->gc), NULL, NULL,
g, 2,
_("Reject"), G_CALLBACK(qq_group_reject_application_with_struct),
@@ -134,7 +134,7 @@ void qq_group_reject_application_with_struct(group_member_opt *g)
g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0);
msg1 = g_strdup_printf(_("You rejected %d's request"), g->member);
- msg2 = g_strdup(_("Input your reason:"));
+ msg2 = g_strdup(_("Enter your reason:"));
nombre = uid_to_purple_name(g->member);
purple_request_input(g->gc, /* title */ NULL, msg1, msg2,
@@ -232,7 +232,7 @@ void qq_group_process_modify_members_reply(guint8 *data, guint8 **cursor, gint l
purple_debug(PURPLE_DEBUG_INFO, "QQ", "Succeed in modify members for Qun %d\n", group->external_group_id);
- purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modify Qun member"), NULL);
+ purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modified Qun member"), NULL);
}
void qq_group_modify_info(PurpleConnection *gc, qq_group *group)
@@ -302,7 +302,7 @@ void qq_group_process_modify_info_reply(guint8 *data, guint8 **cursor, gint len,
purple_debug(PURPLE_DEBUG_INFO, "QQ", "Succeed in modify info for Qun %d\n", group->external_group_id);
qq_group_refresh(gc, group);
- purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modify Qun information"), NULL);
+ purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modified Qun information"), NULL);
}
/* we create a very simple group first, and then let the user to modify */
diff --git a/libpurple/protocols/qq/im.c b/libpurple/protocols/qq/im.c
index 856cd10176..ddf893edc5 100644
--- a/libpurple/protocols/qq/im.c
+++ b/libpurple/protocols/qq/im.c
@@ -573,7 +573,7 @@ void qq_process_send_im_reply(guint8 *buf, gint buf_len, PurpleConnection *gc)
read_packet_b(data, &cursor, len, &reply);
if (reply != QQ_SEND_IM_REPLY_OK) {
purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Send IM fail\n");
- purple_notify_error(gc, _("Server ACK"), _("Failed to send IM."), NULL);
+ purple_notify_error(gc, _("Error"), _("Failed to send IM."), NULL);
}
else
purple_debug(PURPLE_DEBUG_INFO, "QQ", "IM ACK OK\n");
diff --git a/libpurple/protocols/qq/keep_alive.c b/libpurple/protocols/qq/keep_alive.c
index 5b3ca816cf..eae0266f01 100644
--- a/libpurple/protocols/qq/keep_alive.c
+++ b/libpurple/protocols/qq/keep_alive.c
@@ -84,7 +84,7 @@ void qq_process_keep_alive_reply(guint8 *buf, gint buf_len, PurpleConnection *gc
/* segments[0] and segment[1] are all 0x30 ("0") */
qd->all_online = strtol(segments[2], NULL, 10);
if(0 == qd->all_online)
- purple_connection_error(gc, _("Keep alive error, seems connection lost!"));
+ purple_connection_error(gc, _("Keep alive error"));
g_free(qd->my_ip);
qd->my_ip = g_strdup(segments[3]);
qd->my_port = strtol(segments[4], NULL, 10);
diff --git a/libpurple/protocols/qq/login_logout.c b/libpurple/protocols/qq/login_logout.c
index 24c82a386e..234af3c439 100644
--- a/libpurple/protocols/qq/login_logout.c
+++ b/libpurple/protocols/qq/login_logout.c
@@ -405,7 +405,7 @@ void qq_process_request_login_token_reply(guint8 *buf, gint buf_len, PurpleConne
">>> %d bytes -> [default] decrypt and dump\n%s",
buf_len, hex_dump);
try_dump_as_gbk(buf, buf_len);
- purple_connection_error(gc, _("Request login token error!"));
+ purple_connection_error(gc, _("Error requesting login token"));
}
g_free(hex_dump);
}
diff --git a/libpurple/protocols/qq/qq.c b/libpurple/protocols/qq/qq.c
index 2593f15eb8..32c565f8eb 100644
--- a/libpurple/protocols/qq/qq.c
+++ b/libpurple/protocols/qq/qq.c
@@ -195,7 +195,8 @@ static void _qq_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gb
{
qq_buddy *q_bud;
gchar *ip_str;
- char *tmp, *tmp2;
+ char *tmp;
+ const char *tmp2;
g_return_if_fail(b != NULL);
@@ -206,11 +207,12 @@ static void _qq_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gb
{
ip_str = gen_ip_str(q_bud->ip);
if (strlen(ip_str) != 0) {
- tmp = g_strdup_printf(_("%s Address"),
- ((q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE) ? "TCP" : "UDP"));
- tmp2 = g_strdup_printf("%s:%d", ip_str, q_bud->port);
- purple_notify_user_info_add_pair(user_info, tmp, tmp2);
- g_free(tmp2);
+ if (q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE)
+ tmp2 = _("TCP Address");
+ else
+ tmp2 = _("UDP Address");
+ tmp = g_strdup_printf("%s:%d", ip_str, q_bud->port);
+ purple_notify_user_info_add_pair(user_info, tmp2, tmp);
g_free(tmp);
}
g_free(ip_str);
@@ -238,7 +240,7 @@ static void _qq_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gb
if (q_bud->level) {
tmp = g_strdup_printf("%d", q_bud->level);
purple_notify_user_info_add_pair(user_info, _("Level"), tmp);
- g_free(tmp);
+ g_free(tmp);
}
/* For debugging */
/*
@@ -275,19 +277,19 @@ static GList *_qq_away_states(PurpleAccount *ga)
GList *types = NULL;
status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE,
- "available", _("QQ: Available"), FALSE, TRUE, FALSE);
+ "available", _("Available"), FALSE, TRUE, FALSE);
types = g_list_append(types, status);
status = purple_status_type_new_full(PURPLE_STATUS_AWAY,
- "away", _("QQ: Away"), FALSE, TRUE, FALSE);
+ "away", _("Away"), FALSE, TRUE, FALSE);
types = g_list_append(types, status);
status = purple_status_type_new_full(PURPLE_STATUS_INVISIBLE,
- "invisible", _("QQ: Invisible"), FALSE, TRUE, FALSE);
+ "invisible", _("Invisible"), FALSE, TRUE, FALSE);
types = g_list_append(types, status);
status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE,
- "offline", _("QQ: Offline"), FALSE, TRUE, FALSE);
+ "offline", _("Offline"), FALSE, TRUE, FALSE);
types = g_list_append(types, status);
status = purple_status_type_new_full(PURPLE_STATUS_MOBILE,
@@ -416,7 +418,7 @@ static void _qq_menu_block_buddy(PurpleBlistNode * node)
g->uid = uid;
purple_request_action(gc, _("Block Buddy"),
- _("Are you sure to block this buddy?"), NULL,
+ _("Are you sure you want to block this buddy?"), NULL,
1, g, 2,
_("Cancel"),
G_CALLBACK(qq_do_nothing_with_gc_and_uid),
@@ -470,7 +472,7 @@ static void _qq_menu_create_permanent_group(PurplePluginAction * action)
PurpleConnection *gc = (PurpleConnection *) action->context;
purple_request_input(gc, _("Create QQ Qun"),
_("Input Qun name here"),
- _("Only QQ member can create permanent Qun"),
+ _("Only QQ members can create permanent Qun"),
"OpenQ", FALSE, FALSE, NULL,
_("Create"), G_CALLBACK(qq_group_create_with_name), _("Cancel"), NULL, gc);
}
@@ -528,7 +530,7 @@ static GList *_qq_actions(PurplePlugin *plugin, gpointer context)
PurplePluginAction *act;
m = NULL;
- act = purple_plugin_action_new(_("Modify My Information"), _qq_menu_modify_my_info);
+ act = purple_plugin_action_new(_("Set My Information"), _qq_menu_modify_my_info);
m = g_list_append(m, act);
act = purple_plugin_action_new(_("Change Password"), _qq_menu_change_password);
@@ -555,7 +557,7 @@ static GList *_qq_chat_menu(PurpleBlistNode *node)
PurpleMenuAction *act;
m = NULL;
- act = purple_menu_action_new(_("Exit this QQ Qun"), PURPLE_CALLBACK(_qq_menu_unsubscribe_group), NULL, NULL);
+ act = purple_menu_action_new(_("Leave this QQ Qun"), PURPLE_CALLBACK(_qq_menu_unsubscribe_group), NULL, NULL);
m = g_list_append(m, act);
/* TODO: enable this
diff --git a/libpurple/protocols/qq/qq_proxy.c b/libpurple/protocols/qq/qq_proxy.c
index 882d47f505..3153dec0b8 100644
--- a/libpurple/protocols/qq/qq_proxy.c
+++ b/libpurple/protocols/qq/qq_proxy.c
@@ -493,13 +493,8 @@ gint qq_proxy_write(qq_data *qd, guint8 *data, gint len)
errno = 0;
ret = send(qd->fd, data, len, 0);
}
- if (ret == -1) {
- purple_connection_error(qd->gc, _("Socket send error"));
- return ret;
- } else if (errno == ECONNREFUSED) {
- purple_connection_error(qd->gc, _("Connection refused"));
- return ret;
- }
+ if (ret == -1)
+ purple_connection_error(qd->gc, strerror(errno));
return ret;
}
diff --git a/libpurple/protocols/qq/sys_msg.c b/libpurple/protocols/qq/sys_msg.c
index a4272c6e1b..82abf05f31 100644
--- a/libpurple/protocols/qq/sys_msg.c
+++ b/libpurple/protocols/qq/sys_msg.c
@@ -80,12 +80,11 @@ static void _qq_search_before_auth_with_gc_and_uid(gc_and_uid *g)
uid = g->uid;
g_return_if_fail(gc != 0 && uid != 0);
- qq_send_packet_get_info(gc, uid, TRUE); /* we wanna see window */
+ qq_send_packet_get_info(gc, uid, TRUE); /* we want to see window */
nombre = uid_to_purple_name(uid);
- /* TODO: 'wanna' is not an appropriate word for this string. Fix after string freeze. */
purple_request_action
- (gc, NULL, _("Do you wanna approve the request?"), "", 2,
+ (gc, NULL, _("Do you want to approve the request?"), "", 2,
purple_connection_get_account(gc), nombre, NULL,
g, 2,
_("Reject"), G_CALLBACK(qq_reject_add_request_with_gc_and_uid),
@@ -105,11 +104,10 @@ static void _qq_search_before_add_with_gc_and_uid(gc_and_uid *g)
uid = g->uid;
g_return_if_fail(gc != 0 && uid != 0);
- qq_send_packet_get_info(gc, uid, TRUE); /* we wanna see window */
- /* TODO: 'wanna' is not an appropriate word for this string. Fix after string freeze. */
+ qq_send_packet_get_info(gc, uid, TRUE); /* we want to see window */
nombre = uid_to_purple_name(uid);
purple_request_action
- (gc, NULL, _("Do you wanna add this buddy?"), "", 2,
+ (gc, NULL, _("Do you want to add this buddy?"), "", 2,
purple_connection_get_account(gc), nombre, NULL,
g, 2,
_("Cancel"), NULL,
@@ -175,7 +173,7 @@ static void _qq_process_msg_sys_being_added(PurpleConnection *gc, gchar *from, g
_("Add"), G_CALLBACK(qq_add_buddy_with_gc_and_uid),
_("Search"), G_CALLBACK(_qq_search_before_add_with_gc_and_uid));
} else {
- message = g_strdup_printf(_("%s has added you [%s]"), from, to);
+ message = g_strdup_printf(_("%s has added you [%s] to his or her buddy list"), from, to);
_qq_sys_msg_log_write(gc, message, from);
purple_notify_info(gc, NULL, message, NULL);
}
@@ -211,7 +209,7 @@ static void _qq_process_msg_sys_add_contact_approved(PurpleConnection *gc, gchar
qd = (qq_data *) gc->proto_data;
qq_add_buddy_by_recv_packet(gc, strtol(from, NULL, 10), TRUE, TRUE);
- message = g_strdup_printf(_("User %s has approved your request"), from);
+ message = g_strdup_printf(_("User %s approved your request"), from);
_qq_sys_msg_log_write(gc, message, from);
purple_notify_info(gc, NULL, message, NULL);
@@ -236,9 +234,8 @@ static void _qq_process_msg_sys_add_contact_request(PurpleConnection *gc, gchar
name = uid_to_purple_name(uid);
- /* TODO: 'wanna' is not an appropriate word for this string. Fix after string freeze */
/* TODO: this should go through purple_account_request_authorization() */
- message = g_strdup_printf(_("%s wanna add you [%s] as friends"), from, to);
+ message = g_strdup_printf(_("%s wants to add you [%s] as a friend"), from, to);
reason = g_strdup_printf(_("Message: %s"), msg_utf8);
_qq_sys_msg_log_write(gc, message, from);
diff --git a/libpurple/protocols/yahoo/Makefile.am b/libpurple/protocols/yahoo/Makefile.am
index 63c020daa9..5d831a7afa 100644
--- a/libpurple/protocols/yahoo/Makefile.am
+++ b/libpurple/protocols/yahoo/Makefile.am
@@ -9,6 +9,8 @@ YAHOOSOURCES = \
yahoo.h \
yahoochat.h \
yahoochat.c \
+ yahoo_aliases.c \
+ yahoo_alisaes.h \
yahoo_auth.c \
yahoo_auth.h \
yahoo_crypt.h \
diff --git a/libpurple/protocols/yahoo/Makefile.mingw b/libpurple/protocols/yahoo/Makefile.mingw
index 9f6f6cac19..34a36491b6 100644
--- a/libpurple/protocols/yahoo/Makefile.mingw
+++ b/libpurple/protocols/yahoo/Makefile.mingw
@@ -40,6 +40,7 @@ LIB_PATHS += -L$(GTK_TOP)/lib \
C_SRC = util.c \
yahoo.c \
yahoochat.c \
+ yahoo_aliases.c \
yahoo_auth.c \
yahoo_crypt.c \
yahoo_doodle.c \
diff --git a/libpurple/protocols/yahoo/util.c b/libpurple/protocols/yahoo/util.c
index 25bb638d5d..5568c6c406 100644
--- a/libpurple/protocols/yahoo/util.c
+++ b/libpurple/protocols/yahoo/util.c
@@ -61,7 +61,7 @@ char *yahoo_string_encode(PurpleConnection *gc, const char *str, gboolean *utf8)
else
to_codeset = purple_account_get_string(purple_connection_get_account(gc), "local_charset", "ISO-8859-1");
- ret = g_convert_with_fallback(str, strlen(str), to_codeset, "UTF-8", "?", NULL, NULL, NULL);
+ ret = g_convert_with_fallback(str, -1, to_codeset, "UTF-8", "?", NULL, NULL, NULL);
if (ret)
return ret;
else
@@ -92,7 +92,7 @@ char *yahoo_string_decode(PurpleConnection *gc, const char *str, gboolean utf8)
else
from_codeset = purple_account_get_string(purple_connection_get_account(gc), "local_charset", "ISO-8859-1");
- ret = g_convert_with_fallback(str, strlen(str), "UTF-8", from_codeset, NULL, NULL, NULL, NULL);
+ ret = g_convert_with_fallback(str, -1, "UTF-8", from_codeset, NULL, NULL, NULL, NULL);
if (ret)
return ret;
diff --git a/libpurple/protocols/yahoo/yahoo.c b/libpurple/protocols/yahoo/yahoo.c
index 450a6c6b17..91d2753682 100644
--- a/libpurple/protocols/yahoo/yahoo.c
+++ b/libpurple/protocols/yahoo/yahoo.c
@@ -41,6 +41,7 @@
#include "yahoo.h"
#include "yahoochat.h"
+#include "yahoo_aliases.h"
#include "yahoo_auth.h"
#include "yahoo_crypt.h"
#include "yahoo_doodle.h"
@@ -196,6 +197,8 @@ static void yahoo_process_status(PurpleConnection *gc, struct yahoo_packet *pkt)
GSList *l = pkt->hash;
YahooFriend *f = NULL;
char *name = NULL;
+ gboolean unicode = FALSE;
+ char *message = NULL;
if (pkt->service == YAHOO_SERVICE_LOGOFF && pkt->status == -1) {
gc->wants_to_die = TRUE;
@@ -269,7 +272,7 @@ static void yahoo_process_status(PurpleConnection *gc, struct yahoo_packet *pkt)
break;
case 19: /* custom message */
if (f)
- yahoo_friend_set_status_message(f, yahoo_string_decode(gc, pair->value, FALSE));
+ message = pair->value;
break;
case 11: /* this is the buddy's session id */
break;
@@ -380,6 +383,10 @@ static void yahoo_process_status(PurpleConnection *gc, struct yahoo_packet *pkt)
g_free(tmp);
}
break;
+ case 97: /* Unicode status message */
+ unicode = !strcmp(pair->value, "1");
+ break;
+
default:
purple_debug(PURPLE_DEBUG_ERROR, "yahoo",
"Unknown status key %d\n", pair->key);
@@ -389,6 +396,9 @@ static void yahoo_process_status(PurpleConnection *gc, struct yahoo_packet *pkt)
l = l->next;
}
+ if (message && f)
+ yahoo_friend_set_status_message(f, yahoo_string_decode(gc, message, unicode));
+
if (name && f) /* update the last buddy */
yahoo_update_status(gc, name, f);
}
@@ -484,7 +494,8 @@ static void yahoo_process_cookie(struct yahoo_data *yd, char *c)
if (yd->cookie_t)
g_free(yd->cookie_t);
yd->cookie_t = _getcookie(c);
- }
+ } else
+ purple_debug_info("yahoo", "Ignoring unrecognized cookie '%c'\n", c[0]);
}
static void yahoo_process_list_15(PurpleConnection *gc, struct yahoo_packet *pkt)
@@ -508,7 +519,7 @@ static void yahoo_process_list_15(PurpleConnection *gc, struct yahoo_packet *pkt
l = l->next;
switch (pair->key) {
- case 302:
+ case 302:
/* This is always 318 before a group, 319 before the first s/n in a group, 320 before any ignored s/n.
* It is not sent for s/n's in a group after the first.
* All ignored s/n's are listed last, so when we see a 320 we clear the group and begin marking the
@@ -548,7 +559,7 @@ static void yahoo_process_list_15(PurpleConnection *gc, struct yahoo_packet *pkt
} else {
/* This buddy is on the ignore list (and therefore in no group) */
- purple_privacy_deny_add(account, norm_bud, 1);
+ purple_privacy_deny_add(account, norm_bud, 1);
}
break;
case 241: /* another protocol user */
@@ -702,6 +713,8 @@ static void yahoo_process_list(PurpleConnection *gc, struct yahoo_packet *pkt)
yd->tmp_serv_plist = NULL;
}
+ /* Now that we've got the list, request aliases */
+ yahoo_fetch_aliases(gc);
}
static void yahoo_process_notify(PurpleConnection *gc, struct yahoo_packet *pkt)
@@ -816,7 +829,7 @@ static void yahoo_process_message(PurpleConnection *gc, struct yahoo_packet *pkt
{
g_hash_table_replace(yd->imvironments, g_strdup(im->from), g_strdup(imv));
- if (strcmp(imv, "doodle;11") == 0)
+ if (strstr(imv, "doodle;") != NULL)
{
PurpleWhiteboard *wb;
@@ -825,6 +838,8 @@ static void yahoo_process_message(PurpleConnection *gc, struct yahoo_packet *pkt
return;
}
+ /* I'm not sure the following ever happens -DAA */
+
wb = purple_whiteboard_get_session(gc->account, im->from);
/* If a Doodle session doesn't exist between this user */
@@ -868,17 +883,17 @@ static void yahoo_process_message(PurpleConnection *gc, struct yahoo_packet *pkt
PurpleAccount *account;
PurpleConversation *c;
char *username, *str;
-
+
account = purple_connection_get_account(gc);
c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, im->from);
-
+
if ((buddy = purple_find_buddy(account, im->from)) != NULL)
username = g_markup_escape_text(purple_buddy_get_alias(buddy), -1);
else
username = g_markup_escape_text(im->from, -1);
-
+
str = g_strdup_printf(_("%s just sent you a Buzz!"), username);
-
+
purple_conversation_write(c, NULL, str, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, im->time);
g_free(username);
@@ -938,7 +953,8 @@ struct yahoo_add_request {
};
static void
-yahoo_buddy_add_authorize_cb(struct yahoo_add_request *add_req) {
+yahoo_buddy_add_authorize_cb(gpointer data) {
+ struct yahoo_add_request *add_req = data;
g_free(add_req->id);
g_free(add_req->who);
g_free(add_req->msg);
@@ -982,9 +998,10 @@ yahoo_buddy_add_deny_noreason_cb(struct yahoo_add_request *add_req, const char*m
}
static void
-yahoo_buddy_add_deny_reason_cb(struct yahoo_add_request *add_req) {
+yahoo_buddy_add_deny_reason_cb(gpointer data) {
+ struct yahoo_add_request *add_req = data;
purple_request_input(add_req->gc, NULL, _("Authorization denied message:"),
- NULL, _("No reason given."), TRUE, FALSE, NULL,
+ NULL, _("No reason given."), TRUE, FALSE, NULL,
_("OK"), G_CALLBACK(yahoo_buddy_add_deny_cb),
_("Cancel"), G_CALLBACK(yahoo_buddy_add_deny_noreason_cb),
purple_connection_get_account(add_req->gc), add_req->who, NULL,
@@ -1027,8 +1044,8 @@ static void yahoo_buddy_added_us(PurpleConnection *gc, struct yahoo_packet *pkt)
*/
purple_account_request_authorization(purple_connection_get_account(gc), add_req->who, add_req->id,
NULL, add_req->msg, purple_find_buddy(purple_connection_get_account(gc),add_req->who) != NULL,
- G_CALLBACK(yahoo_buddy_add_authorize_cb),
- G_CALLBACK(yahoo_buddy_add_deny_reason_cb),
+ yahoo_buddy_add_authorize_cb,
+ yahoo_buddy_add_deny_reason_cb,
add_req);
} else {
g_free(add_req->id);
@@ -1152,10 +1169,10 @@ static void yahoo_process_mail(PurpleConnection *gc, struct yahoo_packet *pkt)
{
PurpleAccount *account = purple_connection_get_account(gc);
struct yahoo_data *yd = gc->proto_data;
- char *who = NULL;
- char *email = NULL;
- char *subj = NULL;
- char *yahoo_mail_url = (yd->jp? YAHOOJP_MAIL_URL: YAHOO_MAIL_URL);
+ const char *who = NULL;
+ const char *email = NULL;
+ const char *subj = NULL;
+ const char *yahoo_mail_url = (yd->jp? YAHOOJP_MAIL_URL: YAHOO_MAIL_URL);
int count = 0;
GSList *l = pkt->hash;
@@ -3242,11 +3259,79 @@ static void yahoo_act_id(PurpleConnection *gc, const char *entry)
purple_connection_set_display_name(gc, entry);
}
+static void
+yahoo_get_inbox_token_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
+ const gchar *token, size_t len, const gchar *error_message)
+{
+ PurpleConnection *gc = user_data;
+ gboolean set_cookie = FALSE;
+ char *url;
+
+ g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
+
+ if (error_message != NULL)
+ purple_debug_error("yahoo", "Requesting mail login token failed: %s\n", error_message);
+ else if (len > 0 && token && *token) {
+ /* Should we not be hardcoding the rd url? */
+ url = g_strdup_printf(
+ "http://login.yahoo.com/config/reset_cookies_token?"
+ ".token=%s"
+ "&.done=http://us.rd.yahoo.com/messenger/client/%%3fhttp://mail.yahoo.com/",
+ token);
+ set_cookie = TRUE;
+ }
+
+ if (!set_cookie) {
+ struct yahoo_data *yd = gc->proto_data;
+ purple_debug_error("yahoo", "No mail login token; forwarding to login screen.");
+ url = g_strdup(yd->jp ? YAHOOJP_MAIL_URL : YAHOO_MAIL_URL);
+ }
+
+ /* Open the mailbox with the parsed url data */
+ purple_notify_uri(gc, url);
+
+ g_free(url);
+}
+
+
+static void yahoo_show_inbox(PurplePluginAction *action)
+{
+ /* Setup a cookie that can be used by the browser */
+ /* XXX I have no idea how this will work with Yahoo! Japan. */
+
+ PurpleConnection *gc = action->context;
+ struct yahoo_data *yd = gc->proto_data;
+
+ PurpleUtilFetchUrlData *url_data;
+ const char* base_url = "http://login.yahoo.com";
+ char *request = g_strdup_printf(
+ "POST /config/cookie_token HTTP/1.0\r\n"
+ "Cookie: T=%s; path=/; domain=.yahoo.com; Y=%s;\r\n"
+ "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\n"
+ "Host: login.yahoo.com\r\n"
+ "Content-Length: 0\r\n\r\n",
+ yd->cookie_t, yd->cookie_y);
+
+ url_data = purple_util_fetch_url_request(base_url, FALSE,
+ "Mozilla/4.0 (compatible; MSIE 5.5)", TRUE, request, FALSE,
+ yahoo_get_inbox_token_cb, gc);
+
+ g_free(request);
+
+ if (url_data == NULL) {
+ const char *yahoo_mail_url = (yd->jp ? YAHOOJP_MAIL_URL : YAHOO_MAIL_URL);
+ purple_debug_error("yahoo",
+ "Unable to request mail login token; forwarding to login screen.");
+ purple_notify_uri(gc, yahoo_mail_url);
+ }
+
+}
+
+
static void yahoo_show_act_id(PurplePluginAction *action)
{
PurpleConnection *gc = (PurpleConnection *) action->context;
- /* XXX Typo: This should be _("Activate which ID?") - fix after string freeze is over */
- purple_request_input(gc, NULL, _("Active which ID?"), NULL,
+ purple_request_input(gc, NULL, _("Activate which ID?"), NULL,
purple_connection_get_display_name(gc), FALSE, FALSE, NULL,
_("OK"), G_CALLBACK(yahoo_act_id),
_("Cancel"), NULL,
@@ -3277,6 +3362,11 @@ static GList *yahoo_actions(PurplePlugin *plugin, gpointer context) {
yahoo_show_chat_goto);
m = g_list_append(m, act);
+ m = g_list_append(m, NULL);
+ act = purple_plugin_action_new(_("Open Inbox"),
+ yahoo_show_inbox);
+ m = g_list_append(m, act);
+
return m;
}
@@ -3315,7 +3405,7 @@ static int yahoo_send_im(PurpleConnection *gc, const char *who, const char *what
*/
wb = purple_whiteboard_get_session(gc->account, who);
if (wb)
- yahoo_packet_hash_str(pkt, 63, "doodle;11");
+ yahoo_packet_hash_str(pkt, 63, DOODLE_IMV_KEY);
else
{
const char *imv;
@@ -3377,6 +3467,7 @@ static void yahoo_set_status(PurpleAccount *account, PurpleStatus *status)
const char *msg = NULL;
char *tmp = NULL;
char *conv_msg = NULL;
+ gboolean utf8 = TRUE;
if (!purple_status_is_active(status))
return;
@@ -3393,13 +3484,13 @@ static void yahoo_set_status(PurpleAccount *account, PurpleStatus *status)
msg = purple_status_get_attr_string(status, "message");
if (purple_status_is_available(status)) {
- tmp = yahoo_string_encode(gc, msg, NULL);
+ tmp = yahoo_string_encode(gc, msg, &utf8);
conv_msg = purple_markup_strip_html(tmp);
g_free(tmp);
} else {
if ((msg == NULL) || (*msg == '\0'))
msg = _("Away");
- tmp = yahoo_string_encode(gc, msg, NULL);
+ tmp = yahoo_string_encode(gc, msg, &utf8);
conv_msg = purple_markup_strip_html(tmp);
g_free(tmp);
}
@@ -3417,6 +3508,7 @@ static void yahoo_set_status(PurpleAccount *account, PurpleStatus *status)
yahoo_packet_hash_int(pkt, 10, yd->current_status);
if (yd->current_status == YAHOO_STATUS_CUSTOM) {
+ yahoo_packet_hash_str(pkt, 97, utf8 ? "1" : 0);
yahoo_packet_hash_str(pkt, 19, conv_msg);
} else {
yahoo_packet_hash_str(pkt, 19, "");
@@ -3591,8 +3683,18 @@ static void yahoo_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGrou
group2 = yahoo_string_encode(gc, group, NULL);
pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YAHOO_STATUS_AVAILABLE, 0);
- yahoo_packet_hash(pkt, "ssss", 1, purple_connection_get_display_name(gc),
- 7, buddy->name, 65, group2, 14, "");
+ yahoo_packet_hash(pkt, "ssssssssss",
+ 14, "",
+ 65, group2,
+ 97, "1",
+ 1, purple_connection_get_display_name(gc),
+ 302, "319",
+ 300, "319",
+ 7, buddy->name,
+ 334, "0",
+ 301, "319",
+ 303, "319"
+ );
yahoo_packet_send_and_free(pkt, yd);
g_free(group2);
}
@@ -3729,16 +3831,12 @@ static void yahoo_change_buddys_group(PurpleConnection *gc, const char *who,
return;
}
- /* Step 1: Add buddy to new group. */
- pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YAHOO_STATUS_AVAILABLE, 0);
- yahoo_packet_hash(pkt, "ssss", 1, purple_connection_get_display_name(gc),
- 7, who, 65, gpn, 14, "");
+ pkt = yahoo_packet_new(YAHOO_SERVICE_CHGRP_15, YAHOO_STATUS_AVAILABLE, 0);
+ yahoo_packet_hash(pkt, "ssssssss", 1, purple_connection_get_display_name(gc),
+ 302, "240", 300, "240", 7, who, 224, gpo, 264, gpn, 301,
+ "240", 303, "240");
yahoo_packet_send_and_free(pkt, yd);
- /* Step 2: Remove buddy from old group */
- pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, 0);
- yahoo_packet_hash(pkt, "sss", 1, purple_connection_get_display_name(gc), 7, who, 65, gpo);
- yahoo_packet_send_and_free(pkt, yd);
g_free(gpn);
g_free(gpo);
}
@@ -4017,7 +4115,7 @@ static PurplePluginProtocolInfo prpl_info =
NULL, /* register_user */
NULL, /* get_cb_info */
NULL, /* get_cb_away */
- NULL, /* alias_buddy */
+ yahoo_update_alias, /* alias_buddy */
yahoo_change_buddys_group,
yahoo_rename_group,
NULL, /* buddy_free */
diff --git a/libpurple/protocols/yahoo/yahoo_aliases.c b/libpurple/protocols/yahoo/yahoo_aliases.c
new file mode 100644
index 0000000000..d271ca822f
--- /dev/null
+++ b/libpurple/protocols/yahoo/yahoo_aliases.c
@@ -0,0 +1,245 @@
+/*
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+
+#include "internal.h"
+
+#include "account.h"
+#include "accountopt.h"
+#include "blist.h"
+#include "debug.h"
+#include "util.h"
+#include "version.h"
+#include "yahoo.h"
+#include "yahoo_aliases.h"
+#include "yahoo_packet.h"
+
+/* I hate hardcoding this stuff, but Yahoo never sends us anything to use. Someone in the know may be able to tweak this URL */
+#define YAHOO_ALIAS_FETCH_URL "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us&diffs=1&t=0&tags=short&rt=0&prog-ver=8.1.0.249&useutf8=1&legenc=codepage-1252"
+#define YAHOO_ALIAS_UPDATE_URL "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us&sync=1&tags=short&noclear=1&useutf8=1&legenc=codepage-1252"
+
+void yahoo_update_alias(PurpleConnection *gc, const char *who, const char *alias);
+
+/**
+ * Stuff we want passed to the callback function
+ */
+struct callback_data {
+ PurpleConnection *gc;
+ const char *id;
+};
+
+
+/**************************************************************************
+ * Alias Fetch Functions
+ **************************************************************************/
+
+static void
+yahoo_fetch_aliases_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,const gchar *url_text, size_t len, const gchar *error_message)
+{
+ if (len == 0) {
+ purple_debug_info("yahoo","No Aliases to process\n");
+ } else {
+ const char *yid, *full_name, *nick_name, *alias, *id, *fn, *ln, *nn;
+ struct callback_data *cb = user_data;
+ PurpleBuddy *b = NULL;
+ xmlnode *item, *contacts;
+
+ /* Put our web response into a xmlnode for easy management */
+ contacts = xmlnode_from_str(url_text, -1);
+
+ if (contacts == NULL) {
+ purple_debug_error("yahoo_aliases","Badly formed XML\n");
+ return;
+ }
+ purple_debug_info("yahoo", "Fetched %i bytes of alias data\n", len);
+
+ /* Loop around and around and around until we have gone through all the received aliases */
+ for(item = xmlnode_get_child(contacts, "ct"); item; item = xmlnode_get_next_twin(item)) {
+ /* Yahoo replies with two types of contact (ct) record, we are only interested in the alias ones */
+ if ((yid = xmlnode_get_attrib(item, "yi"))) {
+ /* Grab all the bits of information we can */
+ fn = xmlnode_get_attrib(item,"fn");
+ ln = xmlnode_get_attrib(item,"ln");
+ nn = xmlnode_get_attrib(item,"nn");
+ id = xmlnode_get_attrib(item,"id");
+
+ /* Yahoo stores first and last names separately, lets put them together into a full name */
+ full_name = g_strstrip(g_strdup_printf("%s %s", (fn != NULL ? fn : "") , (ln != NULL ? ln : "")));
+ nick_name = (nn != NULL ? g_strstrip(g_strdup_printf("%s", nn)) : NULL);
+
+ if (nick_name != NULL)
+ alias = nick_name; /* If we have a nickname from Yahoo, let's use it */
+ else if (strlen(full_name) != 0)
+ alias = full_name; /* If no Yahoo nickname, we can use the full_name created above */
+ else
+ alias = NULL; /* No nickname, first name or last name, then you get no alias !! */
+
+ /* Find the local buddy that matches */
+ b = purple_find_buddy(cb->gc->account, yid);
+
+ /* If we don't find a matching buddy, ignore the alias !! */
+ if (b != NULL) {
+ /* Create an object that we can attach to the buddies proto_data pointer */
+ struct YahooUser *yu;
+ yu = g_new0(struct YahooUser, 1);
+ yu->id = g_strdup(id);
+ yu->firstname = g_strdup(fn);
+ yu->lastname = g_strdup(ln);
+ yu->nickname = g_strdup(nn);
+ b->proto_data=yu;
+
+ /* Finally, if we received an alias, we better update the buddy list */
+ if (alias != NULL) {
+ serv_got_alias(cb->gc, yid, alias);
+ purple_debug_info("yahoo","Fetched alias '%s' (%s)\n",alias,id);
+ } else if (g_strcasecmp((alias!=NULL?alias:""),(b->alias!=NULL?b->alias:"")) != 0) {
+ /* Or if we have an alias that Yahoo doesn't, send it up */
+ yahoo_update_alias(cb->gc, yid, b->alias);
+ purple_debug_info("yahoo","Sent alias '%s'\n", b->alias);
+ }
+ } else {
+ purple_debug_info("yahoo", "Bizarre, received alias for %s, but they are not on your list...\n", yid);
+ }
+ }
+ }
+ xmlnode_free(contacts);
+ g_free(cb);
+ }
+}
+
+void
+yahoo_fetch_aliases(PurpleConnection *gc)
+{
+ struct yahoo_data *yd = gc->proto_data;
+ struct callback_data *cb;
+ char *url, *request, *webpage, *webaddress, *strtmp;
+ int inttmp;
+
+ /* Using callback_data so I have access to gc in the callback function */
+ cb = g_new0(struct callback_data, 1);
+ cb->gc = gc;
+
+ /* Build all the info to make the web request */
+ url = g_strdup(YAHOO_ALIAS_FETCH_URL);
+ purple_url_parse(url, &webaddress, &inttmp, &webpage, &strtmp, &strtmp);
+ request = g_strdup_printf("GET /%s HTTP/1.1\r\n"
+ "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\n"
+ "Cookie: T=%s; Y=%s\r\n"
+ "Host: %s\r\n"
+ "Cache-Control: no-cache\r\n\r\n",
+ webpage, yd->cookie_t,yd->cookie_y, webaddress);
+
+ /* We have a URL and some header information, let's connect and get some aliases */
+ purple_util_fetch_url_request(url, FALSE, NULL, TRUE, request, FALSE, yahoo_fetch_aliases_cb, cb);
+
+ g_free(url);
+ g_free(request);
+}
+
+/**************************************************************************
+ * Alias Update Functions
+ **************************************************************************/
+
+static void
+yahoo_update_alias_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,const gchar *url_text, size_t len, const gchar *error_message)
+{
+ xmlnode *node, *result;
+ struct callback_data *cb = user_data;
+
+ result = xmlnode_from_str(url_text, -1);
+
+ purple_debug_info("yahoo", "ID: %s, Return data: %s\n",cb->id, url_text);
+
+ if (result == NULL) {
+ purple_debug_error("yahoo","Alias update faild: Badly formed response\n");
+ return;
+ }
+
+ if ((node = xmlnode_get_child(result, "ct"))) {
+ if (g_ascii_strncasecmp(xmlnode_get_attrib(node, "id"), cb->id, strlen(cb->id))==0)
+ purple_debug_info("yahoo", "Alias update succeeded\n");
+ else
+ purple_debug_error("yahoo", "Alias update failed (Contact record return mismatch)\n");
+ } else {
+ purple_debug_info("yahoo", "Alias update failed (No contact record returned)\n");
+ }
+
+ g_free(cb);
+ xmlnode_free(result);
+}
+
+void
+yahoo_update_alias(PurpleConnection *gc, const char *who, const char *alias)
+{
+ struct yahoo_data *yd;
+ struct YahooUser *yu;
+ char *content, *url, *request, *webpage, *webaddress, *strtmp;
+ int inttmp;
+ struct callback_data *cb;
+ PurpleBuddy *buddy;
+
+ g_return_if_fail(alias!= NULL);
+ g_return_if_fail(who!=NULL);
+ g_return_if_fail(gc!=NULL);
+
+ purple_debug_info("yahoo", "Sending '%s' as new alias for user '%s'.\n",alias, who);
+
+ buddy = purple_find_buddy(gc->account, who);
+ if (buddy->proto_data == NULL) {
+ purple_debug_info("yahoo", "Missing proto_data (get_yahoo_aliases must have failed), bailing out\n");
+ return;
+ }
+
+ yd = gc->proto_data;
+ yu = buddy->proto_data;
+
+ /* Using callback_data so I have access to gc in the callback function */
+ cb = g_new0(struct callback_data, 1);
+ cb->id = g_strdup(yu->id);
+
+ /* Build all the info to make the web request */
+ url = g_strdup(YAHOO_ALIAS_UPDATE_URL);
+ purple_url_parse(url, &webaddress, &inttmp, &webpage, &strtmp, &strtmp);
+
+ content = g_strdup_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?><ab k=\"%s\" cc=\"1\">\n"
+ "<ct e=\"1\" yi='%s' id='%s' nn='%s' pr='0' />\n</ab>\r\n",
+ gc->account->username, who, yu->id, g_markup_escape_text(alias, strlen(alias)));
+
+ request = g_strdup_printf("POST /%s HTTP/1.1\r\n"
+ "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\n"
+ "Cookie: T=%s; Y=%s\r\n"
+ "Host: %s\r\n"
+ "Content-Length: %" G_GSIZE_FORMAT "\r\n"
+ "Cache-Control: no-cache\r\n\r\n"
+ "%s",
+ webpage, yd->cookie_t,yd->cookie_y, webaddress,
+ strlen(content), content);
+
+ /* We have a URL and some header information, let's connect and update the alias */
+ purple_util_fetch_url_request(url, FALSE, NULL, TRUE, request, FALSE, yahoo_update_alias_cb, cb);
+
+ g_free(content);
+ g_free(url);
+ g_free(request);
+}
+
diff --git a/libpurple/protocols/yahoo/yahoo_aliases.h b/libpurple/protocols/yahoo/yahoo_aliases.h
new file mode 100644
index 0000000000..f754c3b440
--- /dev/null
+++ b/libpurple/protocols/yahoo/yahoo_aliases.h
@@ -0,0 +1,50 @@
+/*
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+
+#include "internal.h"
+
+#include "account.h"
+#include "accountopt.h"
+#include "blist.h"
+#include "debug.h"
+#include "util.h"
+#include "version.h"
+#include "yahoo.h"
+#include "yahoo_packet.h"
+
+
+/**
+ * The additional protocol specific info attached to each buddy. We need
+ * to store the unique numeric id number to allow us to push alias changes.
+ */
+struct YahooUser
+{
+ const char *id; /* The yahoo accountid for this buddy (not YahooID but numeric value) */
+ char *firstname; /* Storing this information for no real reason, just because */
+ char *lastname; /* Storing this information for no real reason, just because */
+ char *nickname; /* Storing this information for no real reason, just because */
+};
+
+void yahoo_update_alias(PurpleConnection *gc, const char *who, const char *alias);
+void yahoo_fetch_aliases(PurpleConnection *gc);
diff --git a/libpurple/protocols/yahoo/yahoo_doodle.c b/libpurple/protocols/yahoo/yahoo_doodle.c
index 5ae2ad8120..686d303ee4 100644
--- a/libpurple/protocols/yahoo/yahoo_doodle.c
+++ b/libpurple/protocols/yahoo/yahoo_doodle.c
@@ -125,47 +125,12 @@ void yahoo_doodle_initiate(PurpleConnection *gc, const char *name)
* sessions
*/
- yahoo_doodle_command_send_request(gc, to);
yahoo_doodle_command_send_ready(gc, to);
+ yahoo_doodle_command_send_request(gc, to);
}
-void yahoo_doodle_process(PurpleConnection *gc, const char *me, const char *from,
- const char *command, const char *message)
-{
- if(!command)
- return;
-
- /* Now check to see what sort of Doodle message it is */
- switch(atoi(command))
- {
- case DOODLE_CMD_REQUEST:
- yahoo_doodle_command_got_request(gc, from);
- break;
-
- case DOODLE_CMD_READY:
- yahoo_doodle_command_got_ready(gc, from);
- break;
-
- case DOODLE_CMD_CLEAR:
- yahoo_doodle_command_got_clear(gc, from);
- break;
-
- case DOODLE_CMD_DRAW:
- yahoo_doodle_command_got_draw(gc, from, message);
- break;
-
- case DOODLE_CMD_EXTRA:
- yahoo_doodle_command_got_extra(gc, from, message);
- break;
-
- case DOODLE_CMD_CONFIRM:
- yahoo_doodle_command_got_confirm(gc, from);
- break;
- }
-}
-
-void yahoo_doodle_command_got_request(PurpleConnection *gc, const char *from)
+static void yahoo_doodle_command_got_request(PurpleConnection *gc, const char *from)
{
PurpleAccount *account;
PurpleWhiteboard *wb;
@@ -197,7 +162,7 @@ void yahoo_doodle_command_got_request(PurpleConnection *gc, const char *from)
purple_whiteboard_create(account, from, DOODLE_STATE_REQUESTED);
- yahoo_doodle_command_send_request(gc, from);
+ yahoo_doodle_command_send_ready(gc, from);
}
/* TODO Might be required to clear the canvas of an existing doodle
@@ -205,12 +170,12 @@ void yahoo_doodle_command_got_request(PurpleConnection *gc, const char *from)
*/
}
-void yahoo_doodle_command_got_ready(PurpleConnection *gc, const char *from)
+static void yahoo_doodle_command_got_ready(PurpleConnection *gc, const char *from)
{
PurpleAccount *account;
PurpleWhiteboard *wb;
- purple_debug_info("yahoo", "doodle: Got Ready (%s)\n", from);
+ purple_debug_info("yahoo", "doodle: Got Ready(%s)\n", from);
account = purple_connection_get_account(gc);
@@ -230,8 +195,7 @@ void yahoo_doodle_command_got_ready(PurpleConnection *gc, const char *from)
yahoo_doodle_command_send_confirm(gc, from);
}
-
- if(wb->state == DOODLE_STATE_ESTABLISHED)
+ else if(wb->state == DOODLE_STATE_ESTABLISHED)
{
/* TODO Ask whether to save picture too */
purple_whiteboard_clear(wb);
@@ -239,16 +203,16 @@ void yahoo_doodle_command_got_ready(PurpleConnection *gc, const char *from)
/* NOTE Not sure about this... I am trying to handle if the remote user
* already thinks we're in a session with them (when their chat message
- * contains the doodle;11 imv key)
+ * contains the doodle imv key)
*/
- if(wb->state == DOODLE_STATE_REQUESTED)
+ else if(wb->state == DOODLE_STATE_REQUESTED)
{
/* purple_whiteboard_start(wb); */
- yahoo_doodle_command_send_request(gc, from);
+ yahoo_doodle_command_send_ready(gc, from);
}
}
-void yahoo_doodle_command_got_draw(PurpleConnection *gc, const char *from, const char *message)
+static void yahoo_doodle_command_got_draw(PurpleConnection *gc, const char *from, const char *message)
{
PurpleAccount *account;
PurpleWhiteboard *wb;
@@ -304,7 +268,8 @@ void yahoo_doodle_command_got_draw(PurpleConnection *gc, const char *from, const
g_list_free(d_list);
}
-void yahoo_doodle_command_got_clear(PurpleConnection *gc, const char *from)
+
+static void yahoo_doodle_command_got_clear(PurpleConnection *gc, const char *from)
{
PurpleAccount *account;
PurpleWhiteboard *wb;
@@ -329,7 +294,8 @@ void yahoo_doodle_command_got_clear(PurpleConnection *gc, const char *from)
}
}
-void
+
+static void
yahoo_doodle_command_got_extra(PurpleConnection *gc, const char *from, const char *message)
{
purple_debug_info("yahoo", "doodle: Got Extra (%s)\n", from);
@@ -340,7 +306,7 @@ yahoo_doodle_command_got_extra(PurpleConnection *gc, const char *from, const cha
yahoo_doodle_command_send_extra(gc, from, DOODLE_EXTRA_NONE);
}
-void yahoo_doodle_command_got_confirm(PurpleConnection *gc, const char *from)
+static void yahoo_doodle_command_got_confirm(PurpleConnection *gc, const char *from)
{
PurpleAccount *account;
PurpleWhiteboard *wb;
@@ -361,14 +327,14 @@ void yahoo_doodle_command_got_confirm(PurpleConnection *gc, const char *from)
/* TODO Combine the following IF's? */
/* Check if we requested a doodle session */
- if(wb->state == DOODLE_STATE_REQUESTING)
+ /*if(wb->state == DOODLE_STATE_REQUESTING)
{
wb->state = DOODLE_STATE_ESTABLISHED;
purple_whiteboard_start(wb);
yahoo_doodle_command_send_confirm(gc, from);
- }
+ }*/
/* Check if we accepted a request for a doodle session */
if(wb->state == DOODLE_STATE_REQUESTED)
@@ -395,25 +361,21 @@ void yahoo_doodle_command_got_shutdown(PurpleConnection *gc, const char *from)
*/
wb = purple_whiteboard_get_session(account, from);
- /* TODO Ask if user wants to save picture before the session is closed */
-
- /* If this session doesn't exist, don't try and kill it */
if(wb == NULL)
return;
- else
- {
- purple_whiteboard_destroy(wb);
- /* yahoo_doodle_command_send_shutdown(gc, from); */
- }
+ /* TODO Ask if user wants to save picture before the session is closed */
+
+ wb->state = DOODLE_STATE_CANCELED;
+ purple_whiteboard_destroy(wb);
}
static void yahoo_doodle_command_send_generic(const char *type,
PurpleConnection *gc,
const char *to,
const char *message,
- const char *thirteen,
- const char *sixtythree,
+ int command,
+ const char *imv,
const char *sixtyfour)
{
struct yahoo_data *yd;
@@ -428,48 +390,48 @@ static void yahoo_doodle_command_send_generic(const char *type,
yahoo_packet_hash_str(pkt, 49, "IMVIRONMENT");
yahoo_packet_hash_str(pkt, 1, purple_account_get_username(gc->account));
yahoo_packet_hash_str(pkt, 14, message);
- yahoo_packet_hash_str(pkt, 13, thirteen);
+ yahoo_packet_hash_int(pkt, 13, command);
yahoo_packet_hash_str(pkt, 5, to);
- yahoo_packet_hash_str(pkt, 63, sixtythree ? sixtythree : "doodle;11");
+ yahoo_packet_hash_str(pkt, 63, imv ? imv : DOODLE_IMV_KEY);
yahoo_packet_hash_str(pkt, 64, sixtyfour);
yahoo_packet_hash_str(pkt, 1002, "1");
yahoo_packet_send_and_free(pkt, yd);
}
-void yahoo_doodle_command_send_request(PurpleConnection *gc, const char *to)
+void yahoo_doodle_command_send_ready(PurpleConnection *gc, const char *to)
{
- yahoo_doodle_command_send_generic("Request", gc, to, "1", "1", NULL, "1");
+ yahoo_doodle_command_send_generic("Ready", gc, to, "1", DOODLE_CMD_READY, NULL, "1");
}
-void yahoo_doodle_command_send_ready(PurpleConnection *gc, const char *to)
+void yahoo_doodle_command_send_request(PurpleConnection *gc, const char *to)
{
- yahoo_doodle_command_send_generic("Ready", gc, to, "", "0", NULL, "0");
+ yahoo_doodle_command_send_generic("Request", gc, to, "", DOODLE_CMD_REQUEST, NULL, "0");
}
void yahoo_doodle_command_send_draw(PurpleConnection *gc, const char *to, const char *message)
{
- yahoo_doodle_command_send_generic("Draw", gc, to, message, "3", NULL, "1");
+ yahoo_doodle_command_send_generic("Draw", gc, to, message, DOODLE_CMD_DRAW, NULL, "1");
}
void yahoo_doodle_command_send_clear(PurpleConnection *gc, const char *to)
{
- yahoo_doodle_command_send_generic("Clear", gc, to, " ", "2", NULL, "1");
+ yahoo_doodle_command_send_generic("Clear", gc, to, " ", DOODLE_CMD_CLEAR, NULL, "1");
}
void yahoo_doodle_command_send_extra(PurpleConnection *gc, const char *to, const char *message)
{
- yahoo_doodle_command_send_generic("Extra", gc, to, message, "4", NULL, "1");
+ yahoo_doodle_command_send_generic("Extra", gc, to, message, DOODLE_CMD_EXTRA, NULL, "1");
}
void yahoo_doodle_command_send_confirm(PurpleConnection *gc, const char *to)
{
- yahoo_doodle_command_send_generic("Confirm", gc, to, "1", "5", NULL, "1");
+ yahoo_doodle_command_send_generic("Confirm", gc, to, "1", DOODLE_CMD_CONFIRM, NULL, "1");
}
void yahoo_doodle_command_send_shutdown(PurpleConnection *gc, const char *to)
{
- yahoo_doodle_command_send_generic("Shutdown", gc, to, "", "0", ";0", "0");
+ yahoo_doodle_command_send_generic("Shutdown", gc, to, "", DOODLE_CMD_SHUTDOWN, ";0", "0");
}
void yahoo_doodle_start(PurpleWhiteboard *wb)
@@ -491,7 +453,7 @@ void yahoo_doodle_end(PurpleWhiteboard *wb)
/* g_debug_debug("yahoo", "doodle: yahoo_doodle_end()\n"); */
- if (gc)
+ if (gc && wb->state != DOODLE_STATE_CANCELED)
yahoo_doodle_command_send_shutdown(gc, wb->who);
g_free(wb->proto_data);
@@ -530,7 +492,7 @@ void yahoo_doodle_send_draw_list(PurpleWhiteboard *wb, GList *draw_list)
g_return_if_fail(draw_list != NULL);
message = yahoo_doodle_build_draw_string(ds, draw_list);
- yahoo_doodle_command_send_draw(wb->account->gc, wb->who, message);
+ yahoo_doodle_command_send_draw(wb->account->gc, wb->who, message);
g_free(message);
}
@@ -604,3 +566,37 @@ void yahoo_doodle_set_brush(PurpleWhiteboard *wb, int size, int color)
purple_whiteboard_set_brush(wb, size, color);
}
+void yahoo_doodle_process(PurpleConnection *gc, const char *me, const char *from,
+ const char *command, const char *message)
+{
+ if(!command)
+ return;
+
+ /* Now check to see what sort of Doodle message it is */
+ switch(atoi(command))
+ {
+ case DOODLE_CMD_REQUEST:
+ yahoo_doodle_command_got_request(gc, from);
+ break;
+
+ case DOODLE_CMD_READY:
+ yahoo_doodle_command_got_ready(gc, from);
+ break;
+
+ case DOODLE_CMD_CLEAR:
+ yahoo_doodle_command_got_clear(gc, from);
+ break;
+
+ case DOODLE_CMD_DRAW:
+ yahoo_doodle_command_got_draw(gc, from, message);
+ break;
+
+ case DOODLE_CMD_EXTRA:
+ yahoo_doodle_command_got_extra(gc, from, message);
+ break;
+
+ case DOODLE_CMD_CONFIRM:
+ yahoo_doodle_command_got_confirm(gc, from);
+ break;
+ }
+}
diff --git a/libpurple/protocols/yahoo/yahoo_doodle.h b/libpurple/protocols/yahoo/yahoo_doodle.h
index cee9e952f0..33ea5dca7a 100644
--- a/libpurple/protocols/yahoo/yahoo_doodle.h
+++ b/libpurple/protocols/yahoo/yahoo_doodle.h
@@ -31,17 +31,19 @@
#include "whiteboard.h"
#include "cmds.h"
+#define DOODLE_IMV_KEY "doodle;103"
+
/******************************************************************************
* Defines
*****************************************************************************/
/* Doodle communication commands */
/* TODO: Should be an enum. */
-#define DOODLE_CMD_REQUEST 0
-#define DOODLE_CMD_READY 1
-#define DOODLE_CMD_CLEAR 2
-#define DOODLE_CMD_DRAW 3
-#define DOODLE_CMD_EXTRA 4
-#define DOODLE_CMD_CONFIRM 5
+#define DOODLE_CMD_REQUEST 0
+#define DOODLE_CMD_CLEAR 1
+#define DOODLE_CMD_DRAW 2
+#define DOODLE_CMD_EXTRA 3
+#define DOODLE_CMD_READY 4
+#define DOODLE_CMD_CONFIRM 5
/* Doodle communication command for shutting down (also 0) */
#define DOODLE_CMD_SHUTDOWN 0
@@ -54,6 +56,7 @@
#define DOODLE_STATE_REQUESTING 0
#define DOODLE_STATE_REQUESTED 1
#define DOODLE_STATE_ESTABLISHED 2
+#define DOODLE_STATE_CANCELED 3
/* Doodle canvas dimensions */
#define DOODLE_CANVAS_WIDTH 368
@@ -104,12 +107,6 @@ void yahoo_doodle_process(PurpleConnection *gc, const char *me, const char *from
const char *command, const char *message);
void yahoo_doodle_initiate(PurpleConnection *gc, const char *to);
-void yahoo_doodle_command_got_request(PurpleConnection *gc, const char *from);
-void yahoo_doodle_command_got_ready(PurpleConnection *gc, const char *from);
-void yahoo_doodle_command_got_draw(PurpleConnection *gc, const char *from, const char *message);
-void yahoo_doodle_command_got_clear(PurpleConnection *gc, const char *from);
-void yahoo_doodle_command_got_extra(PurpleConnection *gc, const char *from, const char *message);
-void yahoo_doodle_command_got_confirm(PurpleConnection *gc, const char *from);
void yahoo_doodle_command_got_shutdown(PurpleConnection *gc, const char *from);
void yahoo_doodle_command_send_request(PurpleConnection *gc, const char *to);
diff --git a/libpurple/protocols/yahoo/yahoo_filexfer.c b/libpurple/protocols/yahoo/yahoo_filexfer.c
index 737aa9fbc3..5cea440feb 100644
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.c
@@ -484,7 +484,7 @@ void yahoo_process_p2pfilexfer(PurpleConnection *gc, struct yahoo_packet *pkt)
if(service != NULL && imv != NULL && !strcmp(service, "IMVIRONMENT"))
{
/* Check for a Doodle packet and handle it accordingly */
- if(!strcmp(imv, "doodle;11"))
+ if(strstr(imv, "doodle;") != NULL)
yahoo_doodle_process(gc, me, from, command, message);
/* If an IMVIRONMENT packet comes without a specific imviroment name */
@@ -622,12 +622,12 @@ PurpleXfer *yahoo_new_xfer(PurpleConnection *gc, const char *who)
{
PurpleXfer *xfer;
struct yahoo_xfer_data *xfer_data;
-
+
g_return_val_if_fail(who != NULL, NULL);
-
+
xfer_data = g_new0(struct yahoo_xfer_data, 1);
xfer_data->gc = gc;
-
+
/* Build the file transfer handle. */
xfer = purple_xfer_new(gc->account, PURPLE_XFER_SEND, who);
if (xfer)
diff --git a/libpurple/protocols/yahoo/yahoo_packet.c b/libpurple/protocols/yahoo/yahoo_packet.c
index 057b422634..aad6d03625 100644
--- a/libpurple/protocols/yahoo/yahoo_packet.c
+++ b/libpurple/protocols/yahoo/yahoo_packet.c
@@ -223,6 +223,11 @@ void yahoo_packet_write(struct yahoo_packet *pkt, guchar *data)
GSList *l = pkt->hash;
int pos = 0;
+ /* This is only called from one place, and the list is
+ * always backwards */
+
+ l = g_slist_reverse(l);
+
while (l) {
struct yahoo_pair *pair = l->data;
gchar buf[100];
diff --git a/libpurple/protocols/yahoo/yahoo_packet.h b/libpurple/protocols/yahoo/yahoo_packet.h
index 1994c6cc31..445650f23a 100644
--- a/libpurple/protocols/yahoo/yahoo_packet.h
+++ b/libpurple/protocols/yahoo/yahoo_packet.h
@@ -98,6 +98,7 @@ enum yahoo_service { /* these are easier to see in hex */
YAHOO_SERVICE_AVATAR_UPDATE = 0xc7,
YAHOO_SERVICE_VERIFY_ID_EXISTS = 0xc8,
YAHOO_SERVICE_AUDIBLE = 0xd0,
+ YAHOO_SERVICE_CHGRP_15 = 0xe7,
YAHOO_SERVICE_STATUS_15 = 0xf0,
YAHOO_SERVICE_LIST_15 = 0Xf1,
YAHOO_SERVICE_WEBLOGIN = 0x0226,
@@ -117,7 +118,7 @@ struct yahoo_packet {
};
#define YAHOO_WEBMESSENGER_PROTO_VER 0x0065
-#define YAHOO_PROTO_VER 0x000c
+#define YAHOO_PROTO_VER 0x000f
#define YAHOO_PROTO_VER_JAPAN 0x000c
#define YAHOO_PACKET_HDRLEN (4 + 2 + 2 + 2 + 2 + 4 + 4)
diff --git a/libpurple/stringref.h b/libpurple/stringref.h
index f4aab45275..c899c30152 100644
--- a/libpurple/stringref.h
+++ b/libpurple/stringref.h
@@ -1,3 +1,5 @@
+/* TODO: Can we just replace this whole thing with a GCache */
+
/**
* @file stringref.h Reference-counted immutable strings
* @ingroup core
diff --git a/libpurple/stun.c b/libpurple/stun.c
index 76f7188c91..19340d5ca6 100644
--- a/libpurple/stun.c
+++ b/libpurple/stun.c
@@ -429,5 +429,4 @@ PurpleStunNatDiscovery *purple_stun_discover(StunCallback cb) {
void purple_stun_init() {
purple_prefs_add_string("/purple/network/stun_server", "");
- purple_stun_discover(NULL);
}
diff --git a/libpurple/win32/global.mak b/libpurple/win32/global.mak
index 1fe45b5ec6..a082832fd0 100644
--- a/libpurple/win32/global.mak
+++ b/libpurple/win32/global.mak
@@ -40,7 +40,6 @@ PIDGIN_TOP := $(PIDGIN_TREE_TOP)/pidgin
PIDGIN_IDLETRACK_TOP := $(PIDGIN_TOP)/win32/IdleTracker
PIDGIN_PIXMAPS_TOP := $(PIDGIN_TOP)/pixmaps
PIDGIN_PLUGINS_TOP := $(PIDGIN_TOP)/plugins
-PIDGIN_SOUNDS_TOP := $(PIDGIN_TOP)/sounds
PURPLE_PO_TOP := $(PIDGIN_TREE_TOP)/po
PURPLE_PROTOS_TOP := $(PURPLE_TOP)/protocols
diff --git a/pidgin.spec.in b/pidgin.spec.in
index bc0bc78a38..30216b0654 100644
--- a/pidgin.spec.in
+++ b/pidgin.spec.in
@@ -244,6 +244,7 @@ make DESTDIR=$RPM_BUILD_ROOT install
# Delete files that we don't want to put in any of the RPMs
rm -f $RPM_BUILD_ROOT%{_libdir}/finch/*.la
+rm -f $RPM_BUILD_ROOT%{_libdir}/gnt/*.la
rm -f $RPM_BUILD_ROOT%{_libdir}/pidgin/*.la
rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/*.la
rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/liboscar.so
@@ -297,8 +298,6 @@ find $RPM_BUILD_ROOT%{_libdir}/finch -xtype f -print | \
# files -f file can only take one filename :(
cat %{name}.lang >> %{name}-%{version}-purpleplugins
-cat %{name}.lang >> %{name}-%{version}-pidginplugins
-cat %{name}.lang >> %{name}-%{version}-finchplugins
%clean
rm -rf %{buildroot}
@@ -442,6 +441,8 @@ touch --no-create %{_datadir}/icons/hicolor || :
%doc %{_mandir}/man1/finch.*
%{_bindir}/finch
%{_libdir}/libgnt.so.*
+%{_libdir}/gnt/irssi.so
+%{_libdir}/gnt/s.so
%files -n finch-devel
%defattr(-, root, root)
diff --git a/pidgin/Makefile.am b/pidgin/Makefile.am
index 9c06f60890..a25e371b93 100644
--- a/pidgin/Makefile.am
+++ b/pidgin/Makefile.am
@@ -65,7 +65,7 @@ if ENABLE_GTK
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = pidgin.pc
-SUBDIRS = pixmaps plugins sounds
+SUBDIRS = pixmaps plugins
bin_PROGRAMS = pidgin
diff --git a/pidgin/Makefile.mingw b/pidgin/Makefile.mingw
index e7238cffe3..87a10fd3b2 100644
--- a/pidgin/Makefile.mingw
+++ b/pidgin/Makefile.mingw
@@ -146,7 +146,6 @@ install_shallow: $(PIDGIN_INSTALL_DIR) $(EXE_TARGET).exe $(PIDGIN_TARGET).dll
install: install_shallow all
$(MAKE) -C $(PIDGIN_PLUGINS_TOP) -f $(MINGW_MAKEFILE) install
$(MAKE) -C $(PIDGIN_PIXMAPS_TOP) -f $(MINGW_MAKEFILE) install
- $(MAKE) -C $(PIDGIN_SOUNDS_TOP) -f $(MINGW_MAKEFILE) install
$(MAKE) -C $(PIDGIN_IDLETRACK_TOP) -f $(MINGW_MAKEFILE) install
win32/pidgin_dll_rc.rc: win32/pidgin_dll_rc.rc.in $(PIDGIN_TREE_TOP)/VERSION
diff --git a/pidgin/gtkaccount.c b/pidgin/gtkaccount.c
index 36162db625..0f9c6db4b5 100644
--- a/pidgin/gtkaccount.c
+++ b/pidgin/gtkaccount.c
@@ -1162,6 +1162,11 @@ ok_account_prefs_cb(GtkWidget *w, AccountPrefsDialog *dialog)
{
const char *screenname;
+ if (purple_accounts_get_all() == NULL) {
+ /* We're adding our first account. Be polite and show the buddy list */
+ purple_blist_set_visible(TRUE);
+ }
+
screenname = gtk_entry_get_text(GTK_ENTRY(dialog->screenname_entry));
account = purple_account_new(screenname, dialog->protocol_id);
new = TRUE;
@@ -2541,9 +2546,15 @@ deny_no_add_cb(struct auth_and_add *aa)
}
static void *
-pidgin_accounts_request_authorization(PurpleAccount *account, const char *remote_user,
- const char *id, const char *alias, const char *message, gboolean on_list,
- GCallback auth_cb, GCallback deny_cb, void *user_data)
+pidgin_accounts_request_authorization(PurpleAccount *account,
+ const char *remote_user,
+ const char *id,
+ const char *alias,
+ const char *message,
+ gboolean on_list,
+ PurpleAccountRequestAuthorizationCb auth_cb,
+ PurpleAccountRequestAuthorizationCb deny_cb,
+ void *user_data)
{
char *buffer;
PurpleConnection *gc;
@@ -2569,8 +2580,8 @@ pidgin_accounts_request_authorization(PurpleAccount *account, const char *remote
if (!on_list) {
struct auth_and_add *aa = g_new0(struct auth_and_add, 1);
- aa->auth_cb = (PurpleAccountRequestAuthorizationCb)auth_cb;
- aa->deny_cb = (PurpleAccountRequestAuthorizationCb)deny_cb;
+ aa->auth_cb = auth_cb;
+ aa->deny_cb = deny_cb;
aa->data = user_data;
aa->username = g_strdup(remote_user);
aa->alias = g_strdup(alias);
diff --git a/pidgin/gtkblist.c b/pidgin/gtkblist.c
index d404356b1f..4eb04170db 100644
--- a/pidgin/gtkblist.c
+++ b/pidgin/gtkblist.c
@@ -3340,7 +3340,7 @@ gchar *pidgin_blist_get_name_markup(PurpleBuddy *b, gboolean selected, gboolean
presence = purple_buddy_get_presence(b);
- if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"))
+ if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons") && aliased)
{
if (!selected && purple_presence_is_idle(presence))
{
diff --git a/pidgin/gtkblist.h b/pidgin/gtkblist.h
index 2f583799b3..ff5a462c8e 100644
--- a/pidgin/gtkblist.h
+++ b/pidgin/gtkblist.h
@@ -365,7 +365,7 @@ void pidgin_blist_set_headline(const char *text, GdkPixbuf *pixbuf, GCallback ca
*
* @param buddy The buddy to return markup from
* @param selected Whether this buddy is selected. If TRUE, the markup will not change the color.
- * @param aliased TRUE to return the appropriate alias of this buddy, FALSE to return its screenname
+ * @param aliased TRUE to return the appropriate alias of this buddy, FALSE to return its screenname and status information
* @return The markup for this buddy
*/
gchar *pidgin_blist_get_name_markup(PurpleBuddy *buddy, gboolean selected, gboolean aliased);
diff --git a/pidgin/gtkcellrendererexpander.c b/pidgin/gtkcellrendererexpander.c
index b8ac37c832..a82f7f6454 100644
--- a/pidgin/gtkcellrendererexpander.c
+++ b/pidgin/gtkcellrendererexpander.c
@@ -285,5 +285,5 @@ static gboolean pidgin_cell_renderer_expander_activate(GtkCellRenderer *r,
else
gtk_tree_view_expand_row(GTK_TREE_VIEW(widget),path,FALSE);
gtk_tree_path_free(path);
- return TRUE;
+ return FALSE;
}
diff --git a/pidgin/gtkconv.c b/pidgin/gtkconv.c
index 3f9dbac0eb..4c0b6317ef 100644
--- a/pidgin/gtkconv.c
+++ b/pidgin/gtkconv.c
@@ -187,6 +187,9 @@ static void focus_out_from_menubar(GtkWidget *wid, PidginWindow *win);
static void pidgin_conv_tab_pack(PidginWindow *win, PidginConversation *gtkconv);
static gboolean infopane_press_cb(GtkWidget *widget, GdkEventButton *e, PidginConversation *conv);
+static void pidgin_conv_set_position_size(PidginWindow *win, int x, int y,
+ int width, int height);
+
static GdkColor *get_nick_color(PidginConversation *gtkconv, const char *name) {
static GdkColor col;
GtkStyle *style = gtk_widget_get_style(gtkconv->imhtml);
@@ -210,8 +213,8 @@ static GdkColor *get_nick_color(PidginConversation *gtkconv, const char *name) {
* Callbacks
**************************************************************************/
-static gint
-close_conv_cb(GtkWidget *w, PidginConversation *gtkconv)
+static gboolean
+close_conv_cb(GtkWidget *w, GdkEventButton *event, PidginConversation *gtkconv)
{
GList *list = g_list_copy(gtkconv->convs);
@@ -1328,7 +1331,7 @@ menu_close_conv_cb(gpointer data, guint action, GtkWidget *widget)
{
PidginWindow *win = data;
- close_conv_cb(NULL, PIDGIN_CONVERSATION(pidgin_conv_window_get_active_conversation(win)));
+ close_conv_cb(NULL, NULL, PIDGIN_CONVERSATION(pidgin_conv_window_get_active_conversation(win)));
}
static void
@@ -5290,7 +5293,7 @@ pidgin_conv_write_conv(PurpleConversation *conv, const char *name, const char *a
gtk_font_options_all |= GTK_IMHTML_USE_SMOOTHSCROLLING;
if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml))))
- gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", gtk_font_options_all);
+ gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", gtk_font_options_all | GTK_IMHTML_NO_SCROLL);
/* First message in a conversation. */
if (gtkconv->newday == 0)
@@ -5478,7 +5481,7 @@ pidgin_conv_write_conv(PurpleConversation *conv, const char *name, const char *a
color, sml_attrib ? sml_attrib : "", mdate, str);
}
- gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all);
+ gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all | GTK_IMHTML_NO_SCROLL);
if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
!(flags & PURPLE_MESSAGE_SEND)) {
@@ -6253,13 +6256,15 @@ pidgin_conv_update_fields(PurpleConversation *conv, PidginConvFields fields)
(fields & PIDGIN_CONV_SET_TITLE) ||
(fields & PIDGIN_CONV_TOPIC))
{
- char *title;
+ char *title, *truncate = NULL, truncchar = '\0';
PurpleConvIm *im = NULL;
PurpleAccount *account = purple_conversation_get_account(conv);
+ PurpleBuddy *buddy = NULL;
+ PurplePresence *p = NULL;
char *markup = NULL;
AtkObject *accessibility_obj;
/* I think this is a little longer than it needs to be but I'm lazy. */
- char style[51];
+ char *style, *status_style;
if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM)
im = PURPLE_CONV_IM(conv);
@@ -6272,12 +6277,20 @@ pidgin_conv_update_fields(PurpleConversation *conv, PidginConvFields fields)
else
title = g_strdup(purple_conversation_get_title(conv));
+ if ((truncate = strchr(title, ' ')) ||
+ (truncate = strchr(title, '@'))) {
+ truncchar = *truncate;
+ *truncate = '\0';
+ }
+
if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) {
- PurpleBuddy *buddy = purple_find_buddy(account, conv->name);
- if (buddy)
+ buddy = purple_find_buddy(account, conv->name);
+ if (buddy) {
+ p = purple_buddy_get_presence(buddy);
markup = pidgin_blist_get_name_markup(buddy, FALSE, FALSE);
- else
+ } else {
markup = title;
+ }
} else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
PurpleConvChat *chat = PURPLE_CONV_CHAT(conv);
const char *topic = purple_conv_chat_get_topic(chat);
@@ -6293,54 +6306,56 @@ pidgin_conv_update_fields(PurpleConversation *conv, PidginConvFields fields)
if (title != markup)
g_free(markup);
- *style = '\0';
-
if (!GTK_WIDGET_REALIZED(gtkconv->tab_label))
gtk_widget_realize(gtkconv->tab_label);
accessibility_obj = gtk_widget_get_accessible(gtkconv->tab_cont);
if (im != NULL &&
- purple_conv_im_get_typing_state(im) == PURPLE_TYPING)
- {
+ purple_conv_im_get_typing_state(im) == PURPLE_TYPING) {
atk_object_set_description(accessibility_obj, _("Typing"));
- strncpy(style, "color=\"#4e9a06\"", sizeof(style));
- }
- else if (im != NULL &&
- purple_conv_im_get_typing_state(im) == PURPLE_TYPED)
- {
+ style = "color=\"#4e9a06\"";
+ } else if (im != NULL &&
+ purple_conv_im_get_typing_state(im) == PURPLE_TYPED) {
atk_object_set_description(accessibility_obj, _("Stopped Typing"));
- strncpy(style, "color=\"#c4a000\"", sizeof(style));
- }
- else if (gtkconv->unseen_state == PIDGIN_UNSEEN_NICK)
- {
+ style = "color=\"#c4a000\"";
+ } else if (gtkconv->unseen_state == PIDGIN_UNSEEN_NICK) {
atk_object_set_description(accessibility_obj, _("Nick Said"));
- strncpy(style, "color=\"#204a87\" style=\"italic\" weight=\"bold\"", sizeof(style));
- }
- else if (gtkconv->unseen_state == PIDGIN_UNSEEN_TEXT)
- {
+ style = "color=\"#204a87\" weight=\"bold\"";
+ } else if (gtkconv->unseen_state == PIDGIN_UNSEEN_TEXT) {
atk_object_set_description(accessibility_obj, _("Unread Messages"));
- strncpy(style, "color=\"#cc0000\" weight=\"bold\"", sizeof(style));
- }
- else if (gtkconv->unseen_state == PIDGIN_UNSEEN_EVENT)
- {
+ style = "color=\"#cc0000\" weight=\"bold\"";
+ } else if (gtkconv->unseen_state == PIDGIN_UNSEEN_EVENT) {
atk_object_set_description(accessibility_obj, _("New Event"));
- strncpy(style, "color=\"#888a85\" style=\"italic\"", sizeof(style));
+ style = "color=\"#888a85\" weight=\"bold\"";
+ } else {
+ style = "";
+ }
+
+ if (p && purple_presence_is_status_primitive_active(p, PURPLE_STATUS_OFFLINE)) {
+ status_style = "strikethrough='true'";
+ } else if (p && !purple_presence_is_status_primitive_active(p, PURPLE_STATUS_AVAILABLE) &&
+ !purple_presence_is_status_primitive_active(p, PURPLE_STATUS_INVISIBLE)) {
+ status_style = "style='italic'";
+ } else {
+ status_style = "";
}
- if (*style != '\0')
+ if (*style != '\0' || *status_style != '\0')
{
char *html_title,*label;
html_title = g_markup_escape_text(title, -1);
-
- label = g_strdup_printf("<span %s>%s</span>",
- style, html_title);
+ label = g_strdup_printf("<span %s %s>%s</span>",
+ style, status_style, html_title);
g_free(html_title);
gtk_label_set_markup(GTK_LABEL(gtkconv->tab_label), label);
g_free(label);
}
else
gtk_label_set_text(GTK_LABEL(gtkconv->tab_label), title);
+
+ if (truncate)
+ *truncate = truncchar;
if (pidgin_conv_window_is_active_conversation(conv))
update_typing_icon(gtkconv);
@@ -6584,8 +6599,15 @@ pidgin_conv_update_buddy_icon(PurpleConversation *conv)
event = gtk_event_box_new();
gtk_container_add(GTK_CONTAINER(gtkconv->u.im->icon_container), event);
+ gtk_event_box_set_visible_window(GTK_EVENT_BOX(event), FALSE);
+ gtk_widget_add_events(event,
+ GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
g_signal_connect(G_OBJECT(event), "button-press-event",
G_CALLBACK(icon_menu), gtkconv);
+ g_signal_connect(G_OBJECT(event), "motion-notify-event",
+ G_CALLBACK(pidgin_conv_motion_cb), gtkconv);
+ g_signal_connect(G_OBJECT(event), "leave-notify-event",
+ G_CALLBACK(pidgin_conv_leave_cb), gtkconv);
gtk_widget_show(event);
gtkconv->u.im->icon = gtk_image_new_from_pixbuf(scale);
@@ -7151,30 +7173,37 @@ pidgin_conversations_init(void)
purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/tabs", TRUE);
purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/tab_side", GTK_POS_TOP);
purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/scrollback_lines", 4000);
- purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/x", 0);
- purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/y", 0);
purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/use_theme_font", TRUE);
purple_prefs_add_string(PIDGIN_PREFS_ROOT "/conversations/custom_font", "");
/* Conversations -> Chat */
purple_prefs_add_none(PIDGIN_PREFS_ROOT "/conversations/chat");
- purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_width", 410);
- purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_height", 160);
purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/entry_height", 50);
purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/userlist_width", 80);
+ purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/x", 0);
+ purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/y", 0);
+ purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/width", 0);
+ purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/height", 0);
+
/* Conversations -> IM */
purple_prefs_add_none(PIDGIN_PREFS_ROOT "/conversations/im");
+ purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/x", 0);
+ purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/y", 0);
+ purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/width", 0);
+ purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/height", 0);
purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/im/animate_buddy_icons", TRUE);
- purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/default_width", 410);
- purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/default_height", 160);
purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/entry_height", 50);
purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons", TRUE);
purple_prefs_add_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new", "never");
+#ifdef _WIN32
+ purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/win32/minimize_new_convs", FALSE);
+#endif
+
/* Connect callbacks. */
purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/close_on_tabs",
close_on_tabs_pref_cb, NULL);
@@ -7467,7 +7496,7 @@ build_warn_close_dialog(PidginWindow *gtkwin)
_("Confirm close"),
GTK_WINDOW(gtkwin->window), GTK_DIALOG_MODAL,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
- PIDGIN_STOCK_CLOSE_TABS, GTK_RESPONSE_OK, NULL);
+ GTK_STOCK_CLOSE, GTK_RESPONSE_OK, NULL);
gtk_dialog_set_default_response(GTK_DIALOG(warn_close_dialog),
GTK_RESPONSE_OK);
@@ -7759,7 +7788,7 @@ notebook_press_cb(GtkWidget *widget, GdkEventButton *e, PidginWindow *win)
return FALSE;
gtkconv = pidgin_conv_window_get_gtkconv_at_index(win, tab_clicked);
- close_conv_cb(NULL, gtkconv);
+ close_conv_cb(NULL, NULL, gtkconv);
return TRUE;
}
@@ -8018,7 +8047,7 @@ close_others_cb(GtkWidget *w, GObject *menu)
if (gconv != gtkconv)
{
- close_conv_cb(NULL, gconv);
+ close_conv_cb(NULL, NULL, gconv);
}
}
}
@@ -8030,7 +8059,7 @@ static void close_tab_cb(GtkWidget *w, GObject *menu)
gtkconv = g_object_get_data(menu, "clicked_tab");
if (gtkconv)
- close_conv_cb(NULL, gtkconv);
+ close_conv_cb(NULL, NULL, gtkconv);
}
static gboolean
@@ -8308,13 +8337,6 @@ static gboolean gtk_conv_configure_cb(GtkWidget *w, GdkEventConfigure *event, gp
if (gdk_window_get_state(w->window) & GDK_WINDOW_STATE_MAXIMIZED)
return FALSE;
- /* don't save if nothing changed */
- if (x == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/x") &&
- y == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/y") &&
- event->width == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_width") &&
- event->height == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_height"))
- return FALSE; /* carry on normally */
-
/* don't save off-screen positioning */
if (x + event->width < 0 ||
y + event->height < 0 ||
@@ -8322,9 +8344,9 @@ static gboolean gtk_conv_configure_cb(GtkWidget *w, GdkEventConfigure *event, gp
y > gdk_screen_height())
return FALSE; /* carry on normally */
- /* store the position */
- purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/x", x);
- purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/y", y);
+ /* store the position */
+ purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/x", x);
+ purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/y", y);
purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/width", event->width);
purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/height", event->height);
@@ -8334,35 +8356,38 @@ static gboolean gtk_conv_configure_cb(GtkWidget *w, GdkEventConfigure *event, gp
}
static void
-pidgin_conv_restore_position(PidginWindow *win) {
- int conv_x, conv_y, conv_width, conv_height;
-
- conv_width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/width");
-
+pidgin_conv_set_position_size(PidginWindow *win, int conv_x, int conv_y,
+ int conv_width, int conv_height)
+{
/* if the window exists, is hidden, we're saving positions, and the
* position is sane... */
- if (win && win->window &&
- !GTK_WIDGET_VISIBLE(win->window) && conv_width != 0) {
-
- conv_x = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/x");
- conv_y = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/y");
- conv_height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/height");
-
- /* ...check position is on screen... */
- if (conv_x >= gdk_screen_width())
- conv_x = gdk_screen_width() - 100;
- else if (conv_x + conv_width < 0)
- conv_x = 100;
-
- if (conv_y >= gdk_screen_height())
- conv_y = gdk_screen_height() - 100;
- else if (conv_y + conv_height < 0)
- conv_y = 100;
-
- /* ...and move it back. */
- gtk_window_move(GTK_WINDOW(win->window), conv_x, conv_y);
- gtk_window_resize(GTK_WINDOW(win->window), conv_width, conv_height);
- }
+ if (win && win->window &&
+ !GTK_WIDGET_VISIBLE(win->window) && conv_width != 0) {
+
+ /* ...check position is on screen... */
+ if (conv_x >= gdk_screen_width())
+ conv_x = gdk_screen_width() - 100;
+ else if (conv_x + conv_width < 0)
+ conv_x = 100;
+
+ if (conv_y >= gdk_screen_height())
+ conv_y = gdk_screen_height() - 100;
+ else if (conv_y + conv_height < 0)
+ conv_y = 100;
+
+ /* ...and move it back. */
+ gtk_window_move(GTK_WINDOW(win->window), conv_x, conv_y);
+ gtk_window_resize(GTK_WINDOW(win->window), conv_width, conv_height);
+ }
+}
+
+static void
+pidgin_conv_restore_position(PidginWindow *win) {
+ pidgin_conv_set_position_size(win,
+ purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/x"),
+ purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/y"),
+ purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/width"),
+ purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/height"));
}
PidginWindow *
@@ -8387,8 +8412,6 @@ pidgin_conv_window_new()
g_signal_connect(G_OBJECT(win->window), "delete_event",
G_CALLBACK(close_win_cb), win);
- g_signal_connect(G_OBJECT(win->window), "configure_event",
- G_CALLBACK(gtk_conv_configure_cb), NULL);
g_signal_connect(G_OBJECT(win->window), "focus_in_event",
G_CALLBACK(focus_win_cb), win);
@@ -8517,6 +8540,20 @@ pidgin_conv_window_switch_gtkconv(PidginWindow *win, PidginConversation *gtkconv
gtkconv->tab_cont));
}
+static gboolean
+close_button_left_cb(GtkWidget *widget, GdkEventCrossing *event, GtkLabel *label)
+{
+ gtk_label_set_markup(label, "×");
+ return FALSE;
+}
+
+static gboolean
+close_button_entered_cb(GtkWidget *widget, GdkEventCrossing *event, GtkLabel *label)
+{
+ gtk_label_set_markup(label, "<b>×</b>");
+ return FALSE;
+}
+
void
pidgin_conv_window_add_gtkconv(PidginWindow *win, PidginConversation *gtkconv)
{
@@ -8526,7 +8563,6 @@ pidgin_conv_window_add_gtkconv(PidginWindow *win, PidginConversation *gtkconv)
GtkWidget *close_image;
PurpleConversationType conv_type;
const gchar *tmp_lab;
- gint close_button_width, close_button_height, focus_width, focus_pad;
conv_type = purple_conversation_get_type(conv);
@@ -8538,29 +8574,19 @@ pidgin_conv_window_add_gtkconv(PidginWindow *win, PidginConversation *gtkconv)
/* Close button. */
- gtkconv->close = gtk_button_new();
- gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &close_button_width, &close_button_height);
- if (gtk_check_version(2, 4, 2) == NULL) {
- /* Need to account for extra padding around the gtkbutton */
- gtk_widget_style_get(GTK_WIDGET(gtkconv->close),
- "focus-line-width", &focus_width,
- "focus-padding", &focus_pad,
- NULL);
- close_button_width += (focus_width + focus_pad) * 2;
- close_button_height += (focus_width + focus_pad) * 2;
- }
- gtk_widget_set_size_request(GTK_WIDGET(gtkconv->close),
- close_button_width, close_button_height);
-
- gtk_button_set_relief(GTK_BUTTON(gtkconv->close), GTK_RELIEF_NONE);
- close_image = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
+ gtkconv->close = gtk_event_box_new();
+ gtk_event_box_set_visible_window(GTK_EVENT_BOX(gtkconv->close), FALSE);
+ gtk_widget_set_events(gtkconv->close, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
+ close_image = gtk_label_new("×");
+ g_signal_connect(G_OBJECT(gtkconv->close), "enter-notify-event", G_CALLBACK(close_button_entered_cb), close_image);
+ g_signal_connect(G_OBJECT(gtkconv->close), "leave-notify-event", G_CALLBACK(close_button_left_cb), close_image);
gtk_widget_show(close_image);
gtk_container_add(GTK_CONTAINER(gtkconv->close), close_image);
gtk_tooltips_set_tip(gtkconv->tooltips, gtkconv->close,
_("Close conversation"), NULL);
- g_signal_connect(G_OBJECT(gtkconv->close), "clicked",
- G_CALLBACK(close_conv_cb), gtkconv);
+ g_signal_connect(G_OBJECT(gtkconv->close), "button-press-event",
+ G_CALLBACK(close_conv_cb), gtkconv);
#if !GTK_CHECK_VERSION(2,6,0)
/*
@@ -8581,7 +8607,7 @@ pidgin_conv_window_add_gtkconv(PidginWindow *win, PidginConversation *gtkconv)
gtkconv->tab_label = gtk_label_new(tmp_lab = purple_conversation_get_title(conv));
gtkconv->menu_tabby = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
- gtkconv->menu_label = gtk_label_new(purple_conversation_get_title(gtkconv->active_conv));
+ gtkconv->menu_label = gtk_label_new(tmp_lab);
gtk_box_pack_start(GTK_BOX(gtkconv->menu_tabby), gtkconv->menu_icon, FALSE, FALSE, 0);
gtk_widget_show_all(gtkconv->menu_icon);
@@ -8605,9 +8631,6 @@ pidgin_conv_window_add_gtkconv(PidginWindow *win, PidginConversation *gtkconv)
if (pidgin_conv_window_get_gtkconv_count(win) == 1) {
/* Er, bug in notebooks? Switch to the page manually. */
gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), 0);
-
- gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook),
- purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/tabs"));
} else {
gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), TRUE);
}
@@ -8650,8 +8673,8 @@ pidgin_conv_tab_pack(PidginWindow *win, PidginConversation *gtkconv)
MIN(g_utf8_strlen(gtk_label_get_text(GTK_LABEL(gtkconv->tab_label)), -1), 12)
);
}
- if (angle)
- gtk_label_set_angle(GTK_LABEL(gtkconv->tab_label), angle);
+
+ gtk_label_set_angle(GTK_LABEL(gtkconv->tab_label), angle);
#endif
#if 0
@@ -8706,8 +8729,14 @@ pidgin_conv_tab_pack(PidginWindow *win, PidginConversation *gtkconv)
!tabs_side && !angle && pidgin_conv_window_get_gtkconv_count(win) > 1,
TRUE, GTK_PACK_START);
+ if (pidgin_conv_window_get_gtkconv_count(win) == 1)
+ gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook),
+ !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons") ||
+ purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/tab_side") == GTK_POS_LEFT ||
+ purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/tab_side") == GTK_POS_RIGHT);
+
/* show the widgets */
- gtk_widget_show(gtkconv->icon);
+/* gtk_widget_show(gtkconv->icon); */
gtk_widget_show(gtkconv->tab_label);
if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/close_on_tabs"))
gtk_widget_show(gtkconv->close);
@@ -8729,12 +8758,6 @@ pidgin_conv_window_remove_gtkconv(PidginWindow *win, PidginConversation *gtkconv
gtk_notebook_remove_page(GTK_NOTEBOOK(win->notebook), index);
- /* go back to tabless */
- if (pidgin_conv_window_get_gtkconv_count(win) <= 2) {
- gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook),
- purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/tabs"));
- }
-
win->gtkconvs = g_list_remove(win->gtkconvs, gtkconv);
if (win->gtkconvs && win->gtkconvs->next == NULL)
@@ -8914,6 +8937,9 @@ conv_placement_last_created_win(PidginConversation *conv)
if (win == NULL) {
win = pidgin_conv_window_new();
+ g_signal_connect(G_OBJECT(win->window), "configure_event",
+ G_CALLBACK(gtk_conv_configure_cb), NULL);
+
pidgin_conv_window_add_gtkconv(win, conv);
pidgin_conv_window_show(win);
} else {
@@ -8922,6 +8948,53 @@ conv_placement_last_created_win(PidginConversation *conv)
}
/* This one places conversations in the last made window of the same type. */
+static gboolean
+conv_placement_last_created_win_type_configured_cb(GtkWidget *w,
+ GdkEventConfigure *event, PidginConversation *conv)
+{
+ int x, y;
+ PurpleConversationType type = purple_conversation_get_type(conv->active_conv);
+ GList *all;
+
+ if (GTK_WIDGET_VISIBLE(w))
+ gtk_window_get_position(GTK_WINDOW(w), &x, &y);
+ else
+ return FALSE; /* carry on normally */
+
+ /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
+ * when the window is being maximized */
+ if (gdk_window_get_state(w->window) & GDK_WINDOW_STATE_MAXIMIZED)
+ return FALSE;
+
+ /* don't save off-screen positioning */
+ if (x + event->width < 0 ||
+ y + event->height < 0 ||
+ x > gdk_screen_width() ||
+ y > gdk_screen_height())
+ return FALSE; /* carry on normally */
+
+ for (all = conv->convs; all != NULL; all = all->next) {
+ if (type != purple_conversation_get_type(all->data)) {
+ /* this window has different types of conversation, don't save */
+ return FALSE;
+ }
+ }
+
+ if (type == PURPLE_CONV_TYPE_IM) {
+ purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/x", x);
+ purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/y", y);
+ purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/width", event->width);
+ purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/height", event->height);
+ } else if (type == PURPLE_CONV_TYPE_CHAT) {
+ purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/x", x);
+ purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/y", y);
+ purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/width", event->width);
+ purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/height", event->height);
+ }
+
+ return FALSE;
+}
+
static void
conv_placement_last_created_win_type(PidginConversation *conv)
{
@@ -8932,8 +9005,26 @@ conv_placement_last_created_win_type(PidginConversation *conv)
if (win == NULL) {
win = pidgin_conv_window_new();
+ if (PURPLE_CONV_TYPE_IM == purple_conversation_get_type(conv->active_conv) ||
+ purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/width") == 0) {
+ pidgin_conv_set_position_size(win,
+ purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/x"),
+ purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/y"),
+ purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/width"),
+ purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/height"));
+ } else if (PURPLE_CONV_TYPE_CHAT == purple_conversation_get_type(conv->active_conv)) {
+ pidgin_conv_set_position_size(win,
+ purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/x"),
+ purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/y"),
+ purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/width"),
+ purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/height"));
+ }
+
pidgin_conv_window_add_gtkconv(win, conv);
pidgin_conv_window_show(win);
+
+ g_signal_connect(G_OBJECT(win->window), "configure_event",
+ G_CALLBACK(conv_placement_last_created_win_type_configured_cb), conv);
} else
pidgin_conv_window_add_gtkconv(win, conv);
}
@@ -8946,6 +9037,9 @@ conv_placement_new_window(PidginConversation *conv)
win = pidgin_conv_window_new();
+ g_signal_connect(G_OBJECT(win->window), "configure_event",
+ G_CALLBACK(gtk_conv_configure_cb), NULL);
+
pidgin_conv_window_add_gtkconv(win, conv);
pidgin_conv_window_show(win);
diff --git a/pidgin/gtkdebug.c b/pidgin/gtkdebug.c
index 5d6f12af63..7a52fa7b85 100644
--- a/pidgin/gtkdebug.c
+++ b/pidgin/gtkdebug.c
@@ -183,7 +183,7 @@ find_cb(GtkWidget *w, DebugWindow *win)
gtk_container_add(GTK_CONTAINER(GTK_DIALOG(win->find)->vbox),
hbox);
img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION,
- GTK_ICON_SIZE_DIALOG);
+ gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
diff --git a/pidgin/gtkdocklet.c b/pidgin/gtkdocklet.c
index 66de817e16..5b4e09d5fc 100644
--- a/pidgin/gtkdocklet.c
+++ b/pidgin/gtkdocklet.c
@@ -636,7 +636,7 @@ pidgin_docklet_init()
purple_prefs_add_none(PIDGIN_PREFS_ROOT "/docklet");
purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/docklet/blink", FALSE);
- purple_prefs_add_string(PIDGIN_PREFS_ROOT "/docklet/show", "pending");
+ purple_prefs_add_string(PIDGIN_PREFS_ROOT "/docklet/show", "always");
purple_prefs_connect_callback(docklet_handle, PIDGIN_PREFS_ROOT "/docklet/show",
docklet_show_pref_changed_cb, NULL);
diff --git a/pidgin/gtkimhtml.c b/pidgin/gtkimhtml.c
index ca6afb66d8..42e4dac3a9 100644
--- a/pidgin/gtkimhtml.c
+++ b/pidgin/gtkimhtml.c
@@ -1037,7 +1037,7 @@ static void paste_plaintext_received_cb (GtkClipboard *clipboard, const gchar *t
{
char *tmp;
- if (text == NULL)
+ if (text == NULL || !(*text))
return;
tmp = g_markup_escape_text(text, -1);
@@ -1053,7 +1053,7 @@ static void paste_received_cb (GtkClipboard *clipboard, GtkSelectionData *select
if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml)))
return;
- if (selection_data->length < 0) {
+ if (imhtml->wbfo || selection_data->length <= 0) {
gtk_clipboard_request_text(clipboard, paste_plaintext_received_cb, imhtml);
return;
} else {
@@ -2270,10 +2270,11 @@ static gboolean scroll_cb(gpointer data)
GtkIMHtml *imhtml = data;
GtkAdjustment *adj = GTK_TEXT_VIEW(imhtml)->vadjustment;
gdouble max_val = adj->upper - adj->page_size;
+ gdouble scroll_val = gtk_adjustment_get_value(adj) + ((max_val - gtk_adjustment_get_value(adj)) / 3);
g_return_val_if_fail(imhtml->scroll_time != NULL, FALSE);
- if (g_timer_elapsed(imhtml->scroll_time, NULL) > MAX_SCROLL_TIME) {
+ if (g_timer_elapsed(imhtml->scroll_time, NULL) > MAX_SCROLL_TIME || scroll_val >= max_val) {
/* time's up. jump to the end and kill the timer */
gtk_adjustment_set_value(adj, max_val);
g_timer_destroy(imhtml->scroll_time);
@@ -2282,7 +2283,7 @@ static gboolean scroll_cb(gpointer data)
}
/* scroll by 1/3rd the remaining distance */
- gtk_adjustment_set_value(adj, gtk_adjustment_get_value(adj) + ((max_val - gtk_adjustment_get_value(adj)) / 3));
+ gtk_adjustment_set_value(adj, scroll_val);
return TRUE;
}
diff --git a/pidgin/gtkimhtmltoolbar.c b/pidgin/gtkimhtmltoolbar.c
index 4b152b3dcd..bc46fdfb2e 100644
--- a/pidgin/gtkimhtmltoolbar.c
+++ b/pidgin/gtkimhtmltoolbar.c
@@ -815,6 +815,9 @@ static void update_buttons(GtkIMHtmlToolbar *toolbar)
gboolean bold, italic, underline;
char *tmp;
char *tmp2;
+ GtkLabel *label = g_object_get_data(G_OBJECT(toolbar), "font_label");
+
+ gtk_label_set_label(label, _("_Font"));
gtk_imhtml_get_current_format(GTK_IMHTML(toolbar->imhtml),
&bold, &italic, &underline);
@@ -822,7 +825,6 @@ static void update_buttons(GtkIMHtmlToolbar *toolbar)
if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->bold)) != bold)
toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->bold), bold,
toolbar);
-
if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->italic)) != italic)
toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->italic), italic,
toolbar);
@@ -835,20 +837,57 @@ static void update_buttons(GtkIMHtmlToolbar *toolbar)
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->smaller_size), FALSE);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->larger_size), FALSE);
+ if (bold) {
+ gchar *markup = g_strdup_printf("<b>%s</b>",
+ gtk_label_get_label(label));
+ gtk_label_set_markup_with_mnemonic(label, markup);
+ g_free(markup);
+ }
+ if (italic) {
+ gchar *markup = g_strdup_printf("<i>%s</i>",
+ gtk_label_get_label(label));
+ gtk_label_set_markup_with_mnemonic(label, markup);
+ g_free(markup);
+ }
+ if (underline) {
+ gchar *markup = g_strdup_printf("<u>%s</u>",
+ gtk_label_get_label(label));
+ gtk_label_set_markup_with_mnemonic(label, markup);
+ g_free(markup);
+ }
+
tmp = gtk_imhtml_get_current_fontface(GTK_IMHTML(toolbar->imhtml));
toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->font),
(tmp != NULL), toolbar);
+ if (tmp != NULL) {
+ gchar *markup = g_strdup_printf("<span font_desc=\"%s\">%s</span>",
+ tmp, gtk_label_get_label(label));
+ gtk_label_set_markup_with_mnemonic(label, markup);
+ g_free(markup);
+ }
g_free(tmp);
tmp = gtk_imhtml_get_current_forecolor(GTK_IMHTML(toolbar->imhtml));
toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->fgcolor),
(tmp != NULL), toolbar);
+ if (tmp != NULL) {
+ gchar *markup = g_strdup_printf("<span foreground=\"%s\">%s</span>",
+ tmp, gtk_label_get_label(label));
+ gtk_label_set_markup_with_mnemonic(label, markup);
+ g_free(markup);
+ }
g_free(tmp);
tmp = gtk_imhtml_get_current_backcolor(GTK_IMHTML(toolbar->imhtml));
tmp2 = gtk_imhtml_get_current_background(GTK_IMHTML(toolbar->imhtml));
toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->bgcolor),
(tmp != NULL || tmp2 != NULL), toolbar);
+ if (tmp != NULL) {
+ gchar *markup = g_strdup_printf("<span background=\"%s\">%s</span>",
+ tmp, gtk_label_get_label(label));
+ gtk_label_set_markup_with_mnemonic(label, markup);
+ g_free(markup);
+ }
g_free(tmp);
g_free(tmp2);
}
@@ -920,6 +959,7 @@ static void
gtk_imhtmltoolbar_finalize (GObject *object)
{
GtkIMHtmlToolbar *toolbar = GTK_IMHTMLTOOLBAR(object);
+ GtkWidget *menu;
if (toolbar->image_dialog != NULL)
{
@@ -944,6 +984,13 @@ gtk_imhtmltoolbar_finalize (GObject *object)
free(toolbar->sml);
gtk_object_sink(GTK_OBJECT(toolbar->tooltips));
+ menu = g_object_get_data(object, "font_menu");
+ if (menu)
+ gtk_widget_destroy(menu);
+ menu = g_object_get_data(object, "insert_menu");
+ if (menu)
+ gtk_widget_destroy(menu);
+
G_OBJECT_CLASS(parent_class)->finalize (object);
}
@@ -1027,6 +1074,12 @@ static void gtk_imhtmltoolbar_create_old_buttons(GtkIMHtmlToolbar *toolbar)
g_signal_connect(G_OBJECT(button), "clicked",
G_CALLBACK(insert_smiley_cb), toolbar);
toolbar->smiley = button;
+
+ /* Reset formatting */
+ button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_SMILEY);
+ g_signal_connect(G_OBJECT(button), "clicked",
+ G_CALLBACK(clear_formatting_cb), toolbar);
+ toolbar->clear = button;
}
static void
@@ -1043,6 +1096,13 @@ update_menuitem(GtkToggleButton *button, GtkCheckMenuItem *item)
g_signal_handlers_unblock_by_func(G_OBJECT(item), G_CALLBACK(gtk_button_clicked), button);
}
+static void
+enable_markup(GtkWidget *widget, gpointer null)
+{
+ if (GTK_IS_LABEL(widget))
+ g_object_set(G_OBJECT(widget), "use-markup", TRUE, NULL);
+}
+
static void gtk_imhtmltoolbar_init (GtkIMHtmlToolbar *toolbar)
{
GtkWidget *hbox = GTK_WIDGET(toolbar);
@@ -1054,25 +1114,29 @@ static void gtk_imhtmltoolbar_init (GtkIMHtmlToolbar *toolbar)
GtkWidget *font_menu;
GtkWidget *insert_menu;
GtkWidget *menuitem;
- GtkWidget *button;
GtkWidget *sep;
int i;
struct {
const char *label;
GtkWidget **button;
+ gboolean check;
} buttons[] = {
- {_("_Bold"), &toolbar->bold},
- {_("_Italic"), &toolbar->italic},
- {_("_Underline"), &toolbar->underline},
- {_("_Larger"), &toolbar->larger_size},
+ {_("<b>_Bold</b>"), &toolbar->bold, TRUE},
+ {_("<i>_Italic</i>"), &toolbar->italic, TRUE},
+ {_("<u>_Underline</u>"), &toolbar->underline, TRUE},
+ {_("<span size='larger'>_Larger</span>"), &toolbar->larger_size, TRUE},
#if 0
- {_("_Normal"), &toolbar->normal_size},
+ {_("_Normal"), &toolbar->normal_size, TRUE},
#endif
- {_("_Smaller"), &toolbar->smaller_size},
- {_("_Font face"), &toolbar->font},
- {_("_Foreground color"), &toolbar->fgcolor},
- {_("_Background color"), &toolbar->bgcolor},
- {NULL, NULL}
+ {_("<span size='smaller'>_Smaller</span>"), &toolbar->smaller_size, TRUE},
+ /* If we want to show the formatting for the following items, we would
+ * need to update them when formatting changes. The above items don't need
+ * no updating nor nothin' */
+ {_("_Font face"), &toolbar->font, TRUE},
+ {_("Foreground _color"), &toolbar->fgcolor, TRUE},
+ {_("Bac_kground color"), &toolbar->bgcolor, TRUE},
+ {_("_Reset formatting"), &toolbar->clear, FALSE},
+ {NULL, NULL, FALSE}
};
@@ -1098,47 +1162,35 @@ static void gtk_imhtmltoolbar_init (GtkIMHtmlToolbar *toolbar)
image = gtk_image_new_from_stock(GTK_STOCK_BOLD, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0);
label = gtk_label_new_with_mnemonic(_("_Font"));
+ gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
+ g_object_set_data(G_OBJECT(hbox), "font_label", label);
gtk_box_pack_start(GTK_BOX(bbox), label, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(hbox), font_button, FALSE, FALSE, 0);
gtk_widget_show_all(font_button);
font_menu = gtk_menu_new();
+ g_object_set_data(G_OBJECT(toolbar), "font_menu", font_menu);
-
for (i = 0; buttons[i].label; i++) {
GtkWidget *old = *buttons[i].button;
- menuitem = gtk_check_menu_item_new_with_mnemonic(buttons[i].label);
+ if (buttons[i].check) {
+ menuitem = gtk_check_menu_item_new_with_mnemonic(buttons[i].label);
+ g_signal_connect_after(G_OBJECT(old), "toggled",
+ G_CALLBACK(update_menuitem), menuitem);
+ } else {
+ menuitem = gtk_menu_item_new_with_mnemonic(buttons[i].label);
+ }
g_signal_connect_swapped(G_OBJECT(menuitem), "activate",
G_CALLBACK(gtk_button_clicked), old);
- g_signal_connect_after(G_OBJECT(old), "toggled",
- G_CALLBACK(update_menuitem), menuitem);
gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), menuitem);
g_signal_connect(G_OBJECT(old), "notify::sensitive",
G_CALLBACK(button_sensitiveness_changed), menuitem);
+ gtk_container_foreach(GTK_CONTAINER(menuitem), (GtkCallback)enable_markup, NULL);
}
-
- g_signal_connect(G_OBJECT(font_button), "clicked", G_CALLBACK(pidgin_menu_clicked), font_menu);
- g_signal_connect(G_OBJECT(font_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), font_button);
-
- /* Sep */
- sep = gtk_vseparator_new();
- gtk_box_pack_start(GTK_BOX(hbox), sep, FALSE, FALSE, 0);
- gtk_widget_show_all(sep);
- /* Reset Formatting */
- button = gtk_toggle_button_new();
- gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
- bbox = gtk_hbox_new(FALSE, 3);
- gtk_container_add(GTK_CONTAINER(button), bbox);
- image = gtk_image_new_from_stock(PIDGIN_STOCK_CLEAR, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
- gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0);
- label = gtk_label_new_with_mnemonic(_("_Reset font"));
- gtk_box_pack_start(GTK_BOX(bbox), label, FALSE, FALSE, 0);
- gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
- gtk_widget_show_all(button);
- g_signal_connect(G_OBJECT(button), "clicked",
- G_CALLBACK(clear_formatting_cb), toolbar);
- toolbar->clear = button;
+ g_signal_connect_swapped(G_OBJECT(font_button), "button-press-event", G_CALLBACK(gtk_widget_activate), font_button);
+ g_signal_connect(G_OBJECT(font_button), "activate", G_CALLBACK(pidgin_menu_clicked), font_menu);
+ g_signal_connect(G_OBJECT(font_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), font_button);
/* Sep */
sep = gtk_vseparator_new();
@@ -1158,6 +1210,7 @@ static void gtk_imhtmltoolbar_init (GtkIMHtmlToolbar *toolbar)
gtk_widget_show_all(insert_button);
insert_menu = gtk_menu_new();
+ g_object_set_data(G_OBJECT(toolbar), "insert_menu", insert_menu);
menuitem = gtk_menu_item_new_with_mnemonic(_("_Smiley"));
g_signal_connect_swapped(G_OBJECT(menuitem), "activate", G_CALLBACK(gtk_button_clicked), toolbar->smiley);
@@ -1177,7 +1230,8 @@ static void gtk_imhtmltoolbar_init (GtkIMHtmlToolbar *toolbar)
g_signal_connect(G_OBJECT(toolbar->link), "notify::sensitive",
G_CALLBACK(button_sensitiveness_changed), menuitem);
- g_signal_connect(G_OBJECT(insert_button), "clicked", G_CALLBACK(pidgin_menu_clicked), insert_menu);
+ g_signal_connect_swapped(G_OBJECT(insert_button), "button-press-event", G_CALLBACK(gtk_widget_activate), insert_button);
+ g_signal_connect(G_OBJECT(insert_button), "activate", G_CALLBACK(pidgin_menu_clicked), insert_menu);
g_signal_connect(G_OBJECT(insert_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), insert_button);
toolbar->sml = NULL;
}
diff --git a/pidgin/gtkmain.c b/pidgin/gtkmain.c
index 26df20e46b..e7006802f3 100644
--- a/pidgin/gtkmain.c
+++ b/pidgin/gtkmain.c
@@ -31,6 +31,7 @@
#include "eventloop.h"
#include "ft.h"
#include "log.h"
+#include "network.h"
#include "notify.h"
#include "prefs.h"
#include "prpl.h"
@@ -694,7 +695,9 @@ int main(int argc, char *argv[])
return 1;
}
+#if GLIB_CHECK_VERSION(2,2,0)
g_set_application_name(_("Pidgin"));
+#endif /* glib-2.0 >= 2.2.0 */
#ifdef _WIN32
winpidgin_init(hint);
@@ -760,7 +763,6 @@ int main(int argc, char *argv[])
#endif
return 0;
}
-
/* TODO: Move blist loading into purple_blist_init() */
purple_set_blist(purple_blist_new());
@@ -777,6 +779,10 @@ int main(int argc, char *argv[])
/* TODO: Move pounces loading into purple_pounces_init() */
purple_pounces_load();
+ /* Call this early on to try to auto-detect our IP address and
+ * hopefully save some time later.
+ * TODO: move this (back) into purple_core_init() when purple_prefs_load() is in purple_prefs_init() */
+ purple_network_get_my_ip(-1);
/* HACK BY SEANEGAN:
* We've renamed prpl-oscar to prpl-aim and prpl-icq, accordingly.
diff --git a/pidgin/gtknotify.c b/pidgin/gtknotify.c
index 556ea706fc..7e580164f1 100644
--- a/pidgin/gtknotify.c
+++ b/pidgin/gtknotify.c
@@ -274,6 +274,7 @@ pidgin_notify_message(PurpleNotifyMsgType type, const char *title,
gtk_label_set_markup(GTK_LABEL(label), label_text);
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+ gtk_label_set_selectable(GTK_LABEL(label), TRUE);
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
@@ -609,6 +610,7 @@ pidgin_notify_formatted(const char *title, const char *primary,
gtk_label_set_markup(GTK_LABEL(label), label_text);
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+ gtk_label_set_selectable(GTK_LABEL(label), TRUE);
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
gtk_widget_show(label);
@@ -626,6 +628,7 @@ pidgin_notify_formatted(const char *title, const char *primary,
button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
gtk_widget_show(button);
+ gtk_widget_grab_focus(button);
g_signal_connect_swapped(G_OBJECT(button), "clicked",
G_CALLBACK(gtk_widget_destroy), window);
diff --git a/pidgin/gtkprefs.c b/pidgin/gtkprefs.c
index 8432d14cbb..1c149a3692 100644
--- a/pidgin/gtkprefs.c
+++ b/pidgin/gtkprefs.c
@@ -994,7 +994,7 @@ conv_page()
pidgin_prefs_checkbox(_("Show _formatting on incoming messages"),
PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting", vbox);
- iconpref1 = pidgin_prefs_checkbox(strchr(_("/Buddies/Show Buddy _Details")+1,'/')+1,
+ iconpref1 = pidgin_prefs_checkbox(_("Show _detailed information"),
PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons", vbox);
iconpref2 = pidgin_prefs_checkbox(_("Enable buddy ic_on animation"),
PIDGIN_PREFS_ROOT "/conversations/im/animate_buddy_icons", vbox);
@@ -1947,7 +1947,7 @@ away_page()
"/purple/away/away_when_idle", vbox);
select = pidgin_prefs_labeled_spin_button(vbox,
- _("_Minutes before changing status:"), "/purple/away/mins_before_away",
+ _("_Minutes before becoming idle:"), "/purple/away/mins_before_away",
1, 24 * 60, sg);
g_signal_connect(G_OBJECT(button), "clicked",
G_CALLBACK(pidgin_toggle_sensitive), select);
@@ -2236,4 +2236,13 @@ void pidgin_prefs_update_old()
purple_prefs_remove(PIDGIN_PREFS_ROOT "/away/queue_messages");
purple_prefs_remove(PIDGIN_PREFS_ROOT "/away");
purple_prefs_remove("/plugins/gtk/docklet/queue_messages");
+
+ purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/chat/default_width");
+ purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/chat/default_height");
+ purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/im/default_width");
+ purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/im/default_height");
+ purple_prefs_rename(PIDGIN_PREFS_ROOT "/conversations/x",
+ PIDGIN_PREFS_ROOT "/conversations/im/x");
+ purple_prefs_rename(PIDGIN_PREFS_ROOT "/conversations/y",
+ PIDGIN_PREFS_ROOT "/conversations/im/y");
}
diff --git a/pidgin/gtkrequest.c b/pidgin/gtkrequest.c
index 678e621737..4641f69619 100644
--- a/pidgin/gtkrequest.c
+++ b/pidgin/gtkrequest.c
@@ -1003,9 +1003,6 @@ create_list_field(PurpleRequestField *field)
if (purple_request_field_list_get_multi_select(field))
gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
- g_signal_connect(G_OBJECT(sel), "changed",
- G_CALLBACK(list_field_select_changed_cb), field);
-
column = gtk_tree_view_column_new();
gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1);
@@ -1028,6 +1025,17 @@ create_list_field(PurpleRequestField *field)
gtk_tree_selection_select_iter(sel, &iter);
}
+ /*
+ * We only want to catch changes made by the user, so it's important
+ * that we wait until after the list is created to connect this
+ * handler. If we connect the handler before the loop above and
+ * there are multiple items selected, then selecting the first iter
+ * in the tree causes list_field_select_changed_cb to be triggered
+ * which clears out the rest of the list of selected items.
+ */
+ g_signal_connect(G_OBJECT(sel), "changed",
+ G_CALLBACK(list_field_select_changed_cb), field);
+
gtk_container_add(GTK_CONTAINER(sw), treeview);
gtk_widget_show(treeview);
diff --git a/pidgin/gtksound.c b/pidgin/gtksound.c
index 9e899c8f6b..650dcf9faa 100644
--- a/pidgin/gtksound.c
+++ b/pidgin/gtksound.c
@@ -543,7 +543,8 @@ pidgin_sound_play_event(PurpleSoundEventID event)
char *filename = g_strdup(purple_prefs_get_path(file_pref));
if(!filename || !strlen(filename)) {
g_free(filename);
- filename = g_build_filename(DATADIR, "sounds", "pidgin", sounds[event].def, NULL);
+ /* XXX Consider creating a constant for "sounds/purple" to be shared with Finch */
+ filename = g_build_filename(DATADIR, "sounds", "purple", sounds[event].def, NULL);
}
purple_sound_play_file(filename, NULL);
diff --git a/pidgin/gtkutils.c b/pidgin/gtkutils.c
index bd64ef8baf..64d8a6e165 100644
--- a/pidgin/gtkutils.c
+++ b/pidgin/gtkutils.c
@@ -2047,11 +2047,11 @@ add_completion_list(PidginCompletionData *data)
entry.entry.buddy->account,
entry.entry.buddy->name
);
- }
#else
- item->data = g_strdup(buddy->name);
- g_completion_add_items(data->completion, item);
+ item->data = g_strdup(entry.entry.buddy->name);
+ g_completion_add_items(data->completion, item);
#endif /* NEW_STYLE_COMPLETION */
+ }
}
}
}
diff --git a/pidgin/pidginstock.c b/pidgin/pidginstock.c
index fb886822bf..8ab57f274d 100644
--- a/pidgin/pidginstock.c
+++ b/pidgin/pidginstock.c
@@ -111,7 +111,7 @@ static struct SizedStockIcon {
{ PIDGIN_STOCK_STATUS_LOGOUT, "status", "log-out.png", TRUE, TRUE, TRUE, TRUE, FALSE, FALSE , NULL },
{ PIDGIN_STOCK_STATUS_OFFLINE, "status", "offline.png", TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, PIDGIN_STOCK_STATUS_OFFLINE_I },
{ PIDGIN_STOCK_STATUS_PERSON, "status", "person.png", TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL },
- { PIDGIN_STOCK_STATUS_MESSAGE, "status", "message-pending.png",TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL },
+ { PIDGIN_STOCK_STATUS_MESSAGE, "toolbar", "message-new.png",TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL },
{ PIDGIN_STOCK_STATUS_IGNORED, "emblems", "blocked.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL },
{ PIDGIN_STOCK_STATUS_FOUNDER, "emblems", "founder.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL },
@@ -155,7 +155,7 @@ static struct SizedStockIcon {
{ PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE, "toolbar", "insert-image.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL },
{ PIDGIN_STOCK_TOOLBAR_INSERT_LINK, "toolbar", "insert-link.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL },
{ PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW, "toolbar", "message-new.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL },
- { PIDGIN_STOCK_TOOLBAR_PENDING, "status", "message-pending.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL },
+ { PIDGIN_STOCK_TOOLBAR_PENDING, "toolbar", "message-new.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL },
{ PIDGIN_STOCK_TOOLBAR_PLUGINS, "toolbar", "plugins.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL },
{ PIDGIN_STOCK_TOOLBAR_TYPING, "toolbar", "typing.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL },
{ PIDGIN_STOCK_TOOLBAR_UNBLOCK, "toolbar", "unblock.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL },
diff --git a/pidgin/pixmaps/Makefile.am b/pidgin/pixmaps/Makefile.am
index 93fdc7bb2c..bc0a90d6ca 100644
--- a/pidgin/pixmaps/Makefile.am
+++ b/pidgin/pixmaps/Makefile.am
@@ -12,7 +12,7 @@ EXTRA_DIST = \
pidgin.ico
pidginbuttonpixdir = $(datadir)/pixmaps/pidgin/buttons
-pidginbuttonpix_DATA = edit.png pause.png
+pidginbuttonpix_DATA = edit.png pause.png
pidgindistpixdir = $(datadir)/pixmaps/pidgin
pidgindistpix_DATA = logo.png arrow-down.xpm arrow-left.xpm arrow-right.xpm arrow-up.xpm
diff --git a/pidgin/pixmaps/emotes/default/22/act-up.png b/pidgin/pixmaps/emotes/default/22/act-up.png
index a73b4f526f..573801323e 100644
--- a/pidgin/pixmaps/emotes/default/22/act-up.png
+++ b/pidgin/pixmaps/emotes/default/22/act-up.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/alien.png b/pidgin/pixmaps/emotes/default/22/alien.png
index ca83124f83..6f0f9cfa31 100644
--- a/pidgin/pixmaps/emotes/default/22/alien.png
+++ b/pidgin/pixmaps/emotes/default/22/alien.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/angel.png b/pidgin/pixmaps/emotes/default/22/angel.png
index ad98a51ca0..21424512a8 100644
--- a/pidgin/pixmaps/emotes/default/22/angel.png
+++ b/pidgin/pixmaps/emotes/default/22/angel.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/angry.png b/pidgin/pixmaps/emotes/default/22/angry.png
index 8558f46117..d4cbd1503e 100644
--- a/pidgin/pixmaps/emotes/default/22/angry.png
+++ b/pidgin/pixmaps/emotes/default/22/angry.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/arrogant.png b/pidgin/pixmaps/emotes/default/22/arrogant.png
index 42b85ff14c..71cefda886 100644
--- a/pidgin/pixmaps/emotes/default/22/arrogant.png
+++ b/pidgin/pixmaps/emotes/default/22/arrogant.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/at-wits-end.png b/pidgin/pixmaps/emotes/default/22/at-wits-end.png
index 2ef6c717e7..e013d97af9 100644
--- a/pidgin/pixmaps/emotes/default/22/at-wits-end.png
+++ b/pidgin/pixmaps/emotes/default/22/at-wits-end.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/bashful.png b/pidgin/pixmaps/emotes/default/22/bashful.png
index c0cca0b1be..91cbedc8bf 100644
--- a/pidgin/pixmaps/emotes/default/22/bashful.png
+++ b/pidgin/pixmaps/emotes/default/22/bashful.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/beat-up.png b/pidgin/pixmaps/emotes/default/22/beat-up.png
index ac1b463aec..42fae7c3ae 100644
--- a/pidgin/pixmaps/emotes/default/22/beat-up.png
+++ b/pidgin/pixmaps/emotes/default/22/beat-up.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/beauty.png b/pidgin/pixmaps/emotes/default/22/beauty.png
index 30411f60d8..3dab983592 100644
--- a/pidgin/pixmaps/emotes/default/22/beauty.png
+++ b/pidgin/pixmaps/emotes/default/22/beauty.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/blowkiss.png b/pidgin/pixmaps/emotes/default/22/blowkiss.png
index 461204fa85..f31cec3afc 100644
--- a/pidgin/pixmaps/emotes/default/22/blowkiss.png
+++ b/pidgin/pixmaps/emotes/default/22/blowkiss.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/bye.png b/pidgin/pixmaps/emotes/default/22/bye.png
index 7e4e7de992..9b9e4e893c 100644
--- a/pidgin/pixmaps/emotes/default/22/bye.png
+++ b/pidgin/pixmaps/emotes/default/22/bye.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/call-me.png b/pidgin/pixmaps/emotes/default/22/call-me.png
index 230da3e180..83fe47b641 100644
--- a/pidgin/pixmaps/emotes/default/22/call-me.png
+++ b/pidgin/pixmaps/emotes/default/22/call-me.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/clap.png b/pidgin/pixmaps/emotes/default/22/clap.png
index 80b0ae1b22..1463d1ae50 100644
--- a/pidgin/pixmaps/emotes/default/22/clap.png
+++ b/pidgin/pixmaps/emotes/default/22/clap.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/confused.png b/pidgin/pixmaps/emotes/default/22/confused.png
index e1a76fc38a..fb3f806b8f 100644
--- a/pidgin/pixmaps/emotes/default/22/confused.png
+++ b/pidgin/pixmaps/emotes/default/22/confused.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/crying.png b/pidgin/pixmaps/emotes/default/22/crying.png
index 016b82eb5c..46ccd67771 100644
--- a/pidgin/pixmaps/emotes/default/22/crying.png
+++ b/pidgin/pixmaps/emotes/default/22/crying.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/curl-lip.png b/pidgin/pixmaps/emotes/default/22/curl-lip.png
index bf12d34c8d..c7c8af2a60 100644
--- a/pidgin/pixmaps/emotes/default/22/curl-lip.png
+++ b/pidgin/pixmaps/emotes/default/22/curl-lip.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/curse.png b/pidgin/pixmaps/emotes/default/22/curse.png
index f21227af69..bb1ee6c50c 100644
--- a/pidgin/pixmaps/emotes/default/22/curse.png
+++ b/pidgin/pixmaps/emotes/default/22/curse.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/cute.png b/pidgin/pixmaps/emotes/default/22/cute.png
index ca6b4018a8..0a69897ee8 100644
--- a/pidgin/pixmaps/emotes/default/22/cute.png
+++ b/pidgin/pixmaps/emotes/default/22/cute.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/dance.png b/pidgin/pixmaps/emotes/default/22/dance.png
index 709701d9b4..4608f0abcf 100644
--- a/pidgin/pixmaps/emotes/default/22/dance.png
+++ b/pidgin/pixmaps/emotes/default/22/dance.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/dazed.png b/pidgin/pixmaps/emotes/default/22/dazed.png
index db277fbf46..e35a5d6f58 100644
--- a/pidgin/pixmaps/emotes/default/22/dazed.png
+++ b/pidgin/pixmaps/emotes/default/22/dazed.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/default.theme.in b/pidgin/pixmaps/emotes/default/22/default.theme.in
index 7c5d72331b..c040e417ab 100644
--- a/pidgin/pixmaps/emotes/default/22/default.theme.in
+++ b/pidgin/pixmaps/emotes/default/22/default.theme.in
@@ -29,7 +29,7 @@ shout.png >:o >:O
smile.png :-) :)
wink.png ;-) ;)
sad.png :-( :(
-tongue.png :-P :P
+tongue.png :-P :P :-p :p
shock.png =-O
kiss.png :-*
shout.png >:o
@@ -90,7 +90,7 @@ girl.png (X) (x)
good.png (Y) (y)
bad.png (N) (n)
vampire.png :[ :-[
-goat.png (nah)
+goat.png (nah)
sun.png (#)
rainbow.png (R) (r)
quiet.png :-#
@@ -122,141 +122,143 @@ thunder.png (li)
party.png <:o)
eyeroll.png 8-)
yawn.png |-)
-goat.png (nah)
! skywalker.png C:-) c:-) C:) c:)
! monkey.png :-(|)
+
+### Hidden MSN emotes
sigarette.png (ci) (CI)
handcuffs.png (%)
-console.png (xx) (XX)
+console.png (xx) (XX)
fingers-crossed.png (yn) (YN)
### Following QQ 2006
[QQ]
-shock.png /:O /jy /surprised
-curl-lip.png /:~ /pz /curl_lip
-desire.png /:* /se /desire
-dazed.png /:| /dazed
-party.png /8-) /dy /revel
-crying.png /:< /ll /cry
-bashful.png /:$ /hx /bashful
-shut-mouth.png /:X /bz /shut_mouth
-sleepy.png /:Z /shui /sleep
-weep.png /:'( /dk /weep
-embarrassed.png /:-| /gg /embarassed
-pissed-off.png /:@ /fn /pissed_off
-act-up.png /:P /tp /act_up
-smile-big.png /:D /cy /toothy_smile
-smile.png /:) /wx /small_smile
-sad.png /:( /ng /sad
-glasses-cool.png /:+ /kuk /cool
-doctor.png /:# /feid /SARS
-silly.png /:Q /zk /crazy
-sick.png /:T /tu /vomit
-snicker.png /;p /tx /titter
-cute.png /;-D /ka /cute
-disdain.png /;d /by /disdain
-arrogant.png /;o /am /arrogant
-starving.png /:g /jie /starving
-yawn.png /|-) /kun /sleepy
-terror.png /:! /jk /terror
-sweat.png /:L /sweat
-smirk.png /:> /hanx /smirk
-soldier.png /:; /db /soldier
-struggle.png /;f /fendou /struggle
-curse.png /:-S /zhm /curse
-question.png /? /yiw /question
-quiet.png /;x /xu /shh
-hypnotized.png /;@ /yun /dizzy
-excruciating.png /:8 /zhem /excrutiating
-freaked-out.png /;! /shuai /freaked_out
-skeleton.png /!!! /kl /skeleton
-hammer.png /xx /qiao /hammer
-bye.png /bye /zj /bye
-go-away.png /go /shan /go
-tremble.png /shake /fad /shake
-in-love.png /love /aiq /love
-jump.png /jump /tiao /jump
-search.png /find /zhao /search
-lashes.png /& /mm /beautiful_eyebrows
-pig.png /pig /zt /pig
-cat.png /cat /mm /cat
-dog.png /dog /xg /dog
-hug-left.png /hug /yb /hug
-coins.png /$ /qianc /money
-lamp.png /! /dp /lightbulb
-bowl.png /cup /bei /cup
-cake.png /cake /dg /cake
-thunder.png /li /shd /lightning
-bomb.png /bome /zhd /bomb
-knife.png /kn /dao /knife
-soccerball.png /footb /zq /soccer
-musical-note.png /music /yy /music
-poop.png /shit /bb /shit
-coffee.png /coffee /kf /coffee
-eat.png /eat /fan /eat
-pill.png /pill /yw /pill
-rose.png /rose /mg /rose
-wilt.png /fade /dx /wilt
-kiss.png /kiss /wen /kiss
-love.png /heart /xin /heart
-love-over.png /break /xs /broken_heart
-meeting.png /meeting /hy /meeting
-present.png /gift /lw /gift
-phone.png /phone /dh /phone
-clock.png /time /sj /time
-mail.png /email /yj /email
-tv.png /TV /ds /TV
-sun.png /sun /ty /sun
-moon.png /moon /yl /moon
-good.png /strong /qiang /thumbs_up
-bad.png /weak /ruo /thumbs_down
-handshake.png /share /ws /handshake
-victory.png /v /shl /victory
-beauty.png /<J> /mn /beauty
-qq.png /<QQ> /qz /qq
-blowkiss.png /<L> /fw /blow_kiss
-angry.png /<O> /oh /angry
-liquor.png /<B> /bj /baijiu
-can.png /<U> /qsh /soda
-watermelon.png /<W> /xigua /watermelon
-rain.png /<!!> /xy /rain
-cloudy.png /<~> /duoy /cloudy
-snowman.png /<Z> /xr /snowman
-star.png /<*> /xixing /star
-girl.png /<00> /nv /woman
-boy.png /<11> /nan /man
+shock.png /:O /jy /surprised
+curl-lip.png /:~ /pz /curl_lip
+desire.png /:* /se /desire
+dazed.png /:| /dazed
+party.png /8-) /dy /revel
+crying.png /:< /ll /cry
+bashful.png /:$ /hx /bashful
+shut-mouth.png /:X /bz /shut_mouth
+sleepy.png /:Z /shui /sleep
+weep.png /:'( /dk /weep
+embarrassed.png /:-| /gg /embarassed
+pissed-off.png /:@ /fn /pissed_off
+act-up.png /:P /tp /act_up
+smile-big.png /:D /cy /toothy_smile
+smile.png /:) /wx /small_smile
+sad.png /:( /ng /sad
+glasses-cool.png /:+ /kuk /cool
+doctor.png /:# /feid /SARS
+silly.png /:Q /zk /crazy
+sick.png /:T /tu /vomit
+snicker.png /;p /tx /titter
+cute.png /;-D /ka /cute
+disdain.png /;d /by /disdain
+arrogant.png /;o /am /arrogant
+starving.png /:g /jie /starving
+yawn.png /|-) /kun /sleepy
+terror.png /:! /jk /terror
+sweat.png /:L /sweat
+smirk.png /:> /hanx /smirk
+soldier.png /:; /db /soldier
+struggle.png /;f /fendou /struggle
+curse.png /:-S /zhm /curse
+question.png /? /yiw /question
+quiet.png /;x /xu /shh
+hypnotized.png /;@ /yun /dizzy
+excruciating.png /:8 /zhem /excrutiating
+freaked-out.png /;! /shuai /freaked_out
+skeleton.png /!!! /kl /skeleton
+hammer.png /xx /qiao /hammer
+bye.png /bye /zj /bye
+go-away.png /go /shan /go
+tremble.png /shake /fad /shake
+in-love.png /love /aiq /love
+jump.png /jump /tiao /jump
+search.png /find /zhao /search
+lashes.png /& /mm /beautiful_eyebrows
+pig.png /pig /zt /pig
+cat.png /cat /mm /cat
+dog.png /dog /xg /dog
+hug-left.png /hug /yb /hug
+coins.png /$ /qianc /money
+lamp.png /! /dp /lightbulb
+bowl.png /cup /bei /cup
+cake.png /cake /dg /cake
+thunder.png /li /shd /lightning
+bomb.png /bome /zhd /bomb
+knife.png /kn /dao /knife
+soccerball.png /footb /zq /soccer
+musical-note.png /music /yy /music
+poop.png /shit /bb /shit
+coffee.png /coffee /kf /coffee
+eat.png /eat /fan /eat
+pill.png /pill /yw /pill
+rose.png /rose /mg /rose
+wilt.png /fade /dx /wilt
+kiss.png /kiss /wen /kiss
+love.png /heart /xin /heart
+love-over.png /break /xs /broken_heart
+meeting.png /meeting /hy /meeting
+present.png /gift /lw /gift
+phone.png /phone /dh /phone
+clock.png /time /sj /time
+mail.png /email /yj /email
+tv.png /TV /ds /TV
+sun.png /sun /ty /sun
+moon.png /moon /yl /moon
+good.png /strong /qiang /thumbs_up
+bad.png /weak /ruo /thumbs_down
+handshake.png /share /ws /handshake
+victory.png /v /shl /victory
+beauty.png /<J> /mn /beauty
+qq.png /<QQ> /qz /qq
+blowkiss.png /<L> /fw /blow_kiss
+angry.png /<O> /oh /angry
+liquor.png /<B> /bj /baijiu
+can.png /<U> /qsh /soda
+watermelon.png /<W> /xigua /watermelon
+rain.png /<!!> /xy /rain
+cloudy.png /<~> /duoy /cloudy
+snowman.png /<Z> /xr /snowman
+star.png /<*> /xixing /star
+girl.png /<00> /nv /woman
+boy.png /<11> /nan /man
! skywalker.png C:-) c:-) C:) c:)
! monkey.png :-(|)
-### Following ICQ 5.1
+### Following ICQ 6.0
[ICQ]
smile.png :-) :)
+neutral.png :-$
sad.png :-( :(
+shock.png =-O
wink.png ;-) ;)
-tongue.png :-P :P
+tongue.png :-P :P :-p :p
+#[:-}
laugh.png *JOKINGLY*
+sleepy.png *TIRED*
crying.png :'(
+sick.png :-!
#*KISSED*
+#*STOP*
kiss.png :-*
+#*KISSING*
embarrassed.png :-[
+devil.png ]:->
angel.png O:-)
-shut-mouth.png :-X :X
+rose.png @}->--
+shut-mouth.png :-X :X :-x :x
+bomb.png @=
thinking.png :-\\ :-/
+good.png *THUMBS\ UP*
shout.png >:o >:O
+beer.png *DRINK*
smile-big.png :-D :D
moneymouth.png :-$
-shock.png =-O
glasses-cool.png 8-)
-#[:-}
-sleepy.png *TIRED*
-sick.png :-!
-#*STOP*
-#*KISSING*
-devil.png ]:->
-rose.png @}->--
-bomb.png @=
-good.png *THUMBS\ UP*
-beer.png *DRINK*
in-love.png *IN\ LOVE*
! skywalker.png C:-) c:-) C:) c:)
! monkey.png :-(|)
@@ -312,7 +314,7 @@ hug-left.png >:D< >:d<
love-over.png =((
sweat.png #:-S #:-s
rotfl.png =))
-loser.png L-) l-)
+#loser L-) l-) MISSING/YAHOO 6: "Loser!"
party.png <:-P <:-p
nailbiting.png :-SS :-Ss :-sS :-ss
cowboy.png <):)
@@ -349,8 +351,8 @@ star.png (*)
#youkiddingme.png :-j :-J
### These only work in a certain IMvironment
-male-fighter.png o-> O->
-#malefighter2.png o=> O=>
+#male-fighter1.png o-> O->
+#male-fighter2.png o=> O=>
female-fighter.png o-+ O-+
yin-yang.png (%)
diff --git a/pidgin/pixmaps/emotes/default/22/desire.png b/pidgin/pixmaps/emotes/default/22/desire.png
index b6a9174fa7..0c480b109d 100644
--- a/pidgin/pixmaps/emotes/default/22/desire.png
+++ b/pidgin/pixmaps/emotes/default/22/desire.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/devil.png b/pidgin/pixmaps/emotes/default/22/devil.png
index 3e043d07b6..0263ad2dc3 100644
--- a/pidgin/pixmaps/emotes/default/22/devil.png
+++ b/pidgin/pixmaps/emotes/default/22/devil.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/disapointed.png b/pidgin/pixmaps/emotes/default/22/disapointed.png
index b2aced9a37..6ce530fe8b 100644
--- a/pidgin/pixmaps/emotes/default/22/disapointed.png
+++ b/pidgin/pixmaps/emotes/default/22/disapointed.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/disdain.png b/pidgin/pixmaps/emotes/default/22/disdain.png
index bca43ccdd7..1e5a1f110e 100644
--- a/pidgin/pixmaps/emotes/default/22/disdain.png
+++ b/pidgin/pixmaps/emotes/default/22/disdain.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/doh.png b/pidgin/pixmaps/emotes/default/22/doh.png
index d5cd84cc84..ddd90d0a67 100644
--- a/pidgin/pixmaps/emotes/default/22/doh.png
+++ b/pidgin/pixmaps/emotes/default/22/doh.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/dont-know.png b/pidgin/pixmaps/emotes/default/22/dont-know.png
index df2b647ca0..de2e7c08cb 100644
--- a/pidgin/pixmaps/emotes/default/22/dont-know.png
+++ b/pidgin/pixmaps/emotes/default/22/dont-know.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/drool.png b/pidgin/pixmaps/emotes/default/22/drool.png
index b9463eab78..232744bd1d 100644
--- a/pidgin/pixmaps/emotes/default/22/drool.png
+++ b/pidgin/pixmaps/emotes/default/22/drool.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/eat.png b/pidgin/pixmaps/emotes/default/22/eat.png
index 6dfa2ccdbc..b4a8f8f616 100644
--- a/pidgin/pixmaps/emotes/default/22/eat.png
+++ b/pidgin/pixmaps/emotes/default/22/eat.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/embarrassed.png b/pidgin/pixmaps/emotes/default/22/embarrassed.png
index 93c1c2624c..79b9da8263 100644
--- a/pidgin/pixmaps/emotes/default/22/embarrassed.png
+++ b/pidgin/pixmaps/emotes/default/22/embarrassed.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/excruciating.png b/pidgin/pixmaps/emotes/default/22/excruciating.png
index 816bf4fdbd..63202ad679 100644
--- a/pidgin/pixmaps/emotes/default/22/excruciating.png
+++ b/pidgin/pixmaps/emotes/default/22/excruciating.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/eyeroll.png b/pidgin/pixmaps/emotes/default/22/eyeroll.png
index ff4aa97706..0a7965c667 100644
--- a/pidgin/pixmaps/emotes/default/22/eyeroll.png
+++ b/pidgin/pixmaps/emotes/default/22/eyeroll.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/fingers-crossed.png b/pidgin/pixmaps/emotes/default/22/fingers-crossed.png
index 6494707c5d..88d4379638 100644
--- a/pidgin/pixmaps/emotes/default/22/fingers-crossed.png
+++ b/pidgin/pixmaps/emotes/default/22/fingers-crossed.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/foot-in-mouth.png b/pidgin/pixmaps/emotes/default/22/foot-in-mouth.png
index 888c587665..8ea6a114b0 100644
--- a/pidgin/pixmaps/emotes/default/22/foot-in-mouth.png
+++ b/pidgin/pixmaps/emotes/default/22/foot-in-mouth.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/freaked-out.png b/pidgin/pixmaps/emotes/default/22/freaked-out.png
index 3bb6d58e5e..231c05bb95 100644
--- a/pidgin/pixmaps/emotes/default/22/freaked-out.png
+++ b/pidgin/pixmaps/emotes/default/22/freaked-out.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/glasses-cool.png b/pidgin/pixmaps/emotes/default/22/glasses-cool.png
index 6fdb5fe858..0313d6f886 100644
--- a/pidgin/pixmaps/emotes/default/22/glasses-cool.png
+++ b/pidgin/pixmaps/emotes/default/22/glasses-cool.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/glasses-nerdy.png b/pidgin/pixmaps/emotes/default/22/glasses-nerdy.png
index 04af3c73ac..63ddfb290d 100644
--- a/pidgin/pixmaps/emotes/default/22/glasses-nerdy.png
+++ b/pidgin/pixmaps/emotes/default/22/glasses-nerdy.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/go-away.png b/pidgin/pixmaps/emotes/default/22/go-away.png
index efc98af5a4..28279bd152 100644
--- a/pidgin/pixmaps/emotes/default/22/go-away.png
+++ b/pidgin/pixmaps/emotes/default/22/go-away.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/handshake.png b/pidgin/pixmaps/emotes/default/22/handshake.png
index 5c6d6a5319..32c2b91839 100644
--- a/pidgin/pixmaps/emotes/default/22/handshake.png
+++ b/pidgin/pixmaps/emotes/default/22/handshake.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/highfive.png b/pidgin/pixmaps/emotes/default/22/highfive.png
index d49f02fc43..a20524442e 100644
--- a/pidgin/pixmaps/emotes/default/22/highfive.png
+++ b/pidgin/pixmaps/emotes/default/22/highfive.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/hug-left.png b/pidgin/pixmaps/emotes/default/22/hug-left.png
index 3e84f685b6..c1ac173061 100644
--- a/pidgin/pixmaps/emotes/default/22/hug-left.png
+++ b/pidgin/pixmaps/emotes/default/22/hug-left.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/hug-right.png b/pidgin/pixmaps/emotes/default/22/hug-right.png
index 9c70403031..55179d87ba 100644
--- a/pidgin/pixmaps/emotes/default/22/hug-right.png
+++ b/pidgin/pixmaps/emotes/default/22/hug-right.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/hypnotized.png b/pidgin/pixmaps/emotes/default/22/hypnotized.png
index 62823ce729..d962b90d23 100644
--- a/pidgin/pixmaps/emotes/default/22/hypnotized.png
+++ b/pidgin/pixmaps/emotes/default/22/hypnotized.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/in-love.png b/pidgin/pixmaps/emotes/default/22/in-love.png
index 7fdedb5db9..d565f94a5e 100644
--- a/pidgin/pixmaps/emotes/default/22/in-love.png
+++ b/pidgin/pixmaps/emotes/default/22/in-love.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/jump.png b/pidgin/pixmaps/emotes/default/22/jump.png
index ad00de17cc..492ad84cf4 100644
--- a/pidgin/pixmaps/emotes/default/22/jump.png
+++ b/pidgin/pixmaps/emotes/default/22/jump.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/kiss.png b/pidgin/pixmaps/emotes/default/22/kiss.png
index 8d0d2e8b13..c8de944c4e 100644
--- a/pidgin/pixmaps/emotes/default/22/kiss.png
+++ b/pidgin/pixmaps/emotes/default/22/kiss.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/lashes.png b/pidgin/pixmaps/emotes/default/22/lashes.png
index 7fc816bad2..f303d7f56b 100644
--- a/pidgin/pixmaps/emotes/default/22/lashes.png
+++ b/pidgin/pixmaps/emotes/default/22/lashes.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/laugh.png b/pidgin/pixmaps/emotes/default/22/laugh.png
index fdb4f50c03..b9d1e940b1 100644
--- a/pidgin/pixmaps/emotes/default/22/laugh.png
+++ b/pidgin/pixmaps/emotes/default/22/laugh.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/lying.png b/pidgin/pixmaps/emotes/default/22/lying.png
index eff40d08d2..1705582358 100644
--- a/pidgin/pixmaps/emotes/default/22/lying.png
+++ b/pidgin/pixmaps/emotes/default/22/lying.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/mean.png b/pidgin/pixmaps/emotes/default/22/mean.png
index a6b1651cb5..4fc6c862d1 100644
--- a/pidgin/pixmaps/emotes/default/22/mean.png
+++ b/pidgin/pixmaps/emotes/default/22/mean.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/meeting.png b/pidgin/pixmaps/emotes/default/22/meeting.png
index f2a0e5e9bd..d569dfe98a 100644
--- a/pidgin/pixmaps/emotes/default/22/meeting.png
+++ b/pidgin/pixmaps/emotes/default/22/meeting.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/moneymouth.png b/pidgin/pixmaps/emotes/default/22/moneymouth.png
index ef2acf1459..eb4a88777c 100644
--- a/pidgin/pixmaps/emotes/default/22/moneymouth.png
+++ b/pidgin/pixmaps/emotes/default/22/moneymouth.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/nailbiting.png b/pidgin/pixmaps/emotes/default/22/nailbiting.png
index bd82680155..5e7515a9d0 100644
--- a/pidgin/pixmaps/emotes/default/22/nailbiting.png
+++ b/pidgin/pixmaps/emotes/default/22/nailbiting.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/neutral.png b/pidgin/pixmaps/emotes/default/22/neutral.png
index c9fc9260fc..54c1a520a7 100644
--- a/pidgin/pixmaps/emotes/default/22/neutral.png
+++ b/pidgin/pixmaps/emotes/default/22/neutral.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/on-the-phone.png b/pidgin/pixmaps/emotes/default/22/on-the-phone.png
index 6423a8ec72..b30459bbac 100644
--- a/pidgin/pixmaps/emotes/default/22/on-the-phone.png
+++ b/pidgin/pixmaps/emotes/default/22/on-the-phone.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/party.png b/pidgin/pixmaps/emotes/default/22/party.png
index bcc7a4ffee..573f23e5e0 100644
--- a/pidgin/pixmaps/emotes/default/22/party.png
+++ b/pidgin/pixmaps/emotes/default/22/party.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/pissed-off.png b/pidgin/pixmaps/emotes/default/22/pissed-off.png
index a1c8c8c64c..163ca3f193 100644
--- a/pidgin/pixmaps/emotes/default/22/pissed-off.png
+++ b/pidgin/pixmaps/emotes/default/22/pissed-off.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/pray.png b/pidgin/pixmaps/emotes/default/22/pray.png
index c7f5e8768c..6cbc9bb52f 100644
--- a/pidgin/pixmaps/emotes/default/22/pray.png
+++ b/pidgin/pixmaps/emotes/default/22/pray.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/question.png b/pidgin/pixmaps/emotes/default/22/question.png
index fc657b46ad..a936b1220b 100644
--- a/pidgin/pixmaps/emotes/default/22/question.png
+++ b/pidgin/pixmaps/emotes/default/22/question.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/quiet.png b/pidgin/pixmaps/emotes/default/22/quiet.png
index 9e6f0385c1..737b9164f1 100644
--- a/pidgin/pixmaps/emotes/default/22/quiet.png
+++ b/pidgin/pixmaps/emotes/default/22/quiet.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/rotfl.png b/pidgin/pixmaps/emotes/default/22/rotfl.png
index 9f985a2e65..7c15bc49ea 100644
--- a/pidgin/pixmaps/emotes/default/22/rotfl.png
+++ b/pidgin/pixmaps/emotes/default/22/rotfl.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/sad.png b/pidgin/pixmaps/emotes/default/22/sad.png
index 643fd43792..90624b96cf 100644
--- a/pidgin/pixmaps/emotes/default/22/sad.png
+++ b/pidgin/pixmaps/emotes/default/22/sad.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/sarcastic.png b/pidgin/pixmaps/emotes/default/22/sarcastic.png
index ece6dd522c..2206698670 100644
--- a/pidgin/pixmaps/emotes/default/22/sarcastic.png
+++ b/pidgin/pixmaps/emotes/default/22/sarcastic.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/secret.png b/pidgin/pixmaps/emotes/default/22/secret.png
index 30703eaab7..bf0b120e46 100644
--- a/pidgin/pixmaps/emotes/default/22/secret.png
+++ b/pidgin/pixmaps/emotes/default/22/secret.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/shame.png b/pidgin/pixmaps/emotes/default/22/shame.png
index fcdf9421db..2692b56fcb 100644
--- a/pidgin/pixmaps/emotes/default/22/shame.png
+++ b/pidgin/pixmaps/emotes/default/22/shame.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/shock.png b/pidgin/pixmaps/emotes/default/22/shock.png
index 2befb024d8..4744cb9d57 100644
--- a/pidgin/pixmaps/emotes/default/22/shock.png
+++ b/pidgin/pixmaps/emotes/default/22/shock.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/shout.png b/pidgin/pixmaps/emotes/default/22/shout.png
index c7aa7a9d70..1fe2fa9dbd 100644
--- a/pidgin/pixmaps/emotes/default/22/shout.png
+++ b/pidgin/pixmaps/emotes/default/22/shout.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/shut-mouth.png b/pidgin/pixmaps/emotes/default/22/shut-mouth.png
index c98a29780f..4222c44a9a 100644
--- a/pidgin/pixmaps/emotes/default/22/shut-mouth.png
+++ b/pidgin/pixmaps/emotes/default/22/shut-mouth.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/sick.png b/pidgin/pixmaps/emotes/default/22/sick.png
index 777e6fd847..14111f9dae 100644
--- a/pidgin/pixmaps/emotes/default/22/sick.png
+++ b/pidgin/pixmaps/emotes/default/22/sick.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/silly.png b/pidgin/pixmaps/emotes/default/22/silly.png
index b944b7e9f9..851631ce66 100644
--- a/pidgin/pixmaps/emotes/default/22/silly.png
+++ b/pidgin/pixmaps/emotes/default/22/silly.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/skywalker.png b/pidgin/pixmaps/emotes/default/22/skywalker.png
index 227cd08f45..15020c3015 100644
--- a/pidgin/pixmaps/emotes/default/22/skywalker.png
+++ b/pidgin/pixmaps/emotes/default/22/skywalker.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/sleepy.png b/pidgin/pixmaps/emotes/default/22/sleepy.png
index 25449039c5..fb001206b8 100644
--- a/pidgin/pixmaps/emotes/default/22/sleepy.png
+++ b/pidgin/pixmaps/emotes/default/22/sleepy.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/smile-big.png b/pidgin/pixmaps/emotes/default/22/smile-big.png
index 7ae3a5b22c..109612f318 100644
--- a/pidgin/pixmaps/emotes/default/22/smile-big.png
+++ b/pidgin/pixmaps/emotes/default/22/smile-big.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/smile.png b/pidgin/pixmaps/emotes/default/22/smile.png
index 7fa977fc54..ea7c975b35 100644
--- a/pidgin/pixmaps/emotes/default/22/smile.png
+++ b/pidgin/pixmaps/emotes/default/22/smile.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/smirk.png b/pidgin/pixmaps/emotes/default/22/smirk.png
index 77e97ee5cd..eef583f85d 100644
--- a/pidgin/pixmaps/emotes/default/22/smirk.png
+++ b/pidgin/pixmaps/emotes/default/22/smirk.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/snicker.png b/pidgin/pixmaps/emotes/default/22/snicker.png
index 67e0936329..a40c462610 100644
--- a/pidgin/pixmaps/emotes/default/22/snicker.png
+++ b/pidgin/pixmaps/emotes/default/22/snicker.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/soldier.png b/pidgin/pixmaps/emotes/default/22/soldier.png
index 0f38d16c31..0a88444db3 100644
--- a/pidgin/pixmaps/emotes/default/22/soldier.png
+++ b/pidgin/pixmaps/emotes/default/22/soldier.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/starving.png b/pidgin/pixmaps/emotes/default/22/starving.png
index d023d35b47..ec233efdf7 100644
--- a/pidgin/pixmaps/emotes/default/22/starving.png
+++ b/pidgin/pixmaps/emotes/default/22/starving.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/struggle.png b/pidgin/pixmaps/emotes/default/22/struggle.png
index 59b22a45c0..2a26aa295a 100644
--- a/pidgin/pixmaps/emotes/default/22/struggle.png
+++ b/pidgin/pixmaps/emotes/default/22/struggle.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/sweat.png b/pidgin/pixmaps/emotes/default/22/sweat.png
index 6cde5ce31d..1d4f55c3f0 100644
--- a/pidgin/pixmaps/emotes/default/22/sweat.png
+++ b/pidgin/pixmaps/emotes/default/22/sweat.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/teeth.png b/pidgin/pixmaps/emotes/default/22/teeth.png
index 8b20c00e36..42ae1d558b 100644
--- a/pidgin/pixmaps/emotes/default/22/teeth.png
+++ b/pidgin/pixmaps/emotes/default/22/teeth.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/terror.png b/pidgin/pixmaps/emotes/default/22/terror.png
index 104d01252b..351dacc0ae 100644
--- a/pidgin/pixmaps/emotes/default/22/terror.png
+++ b/pidgin/pixmaps/emotes/default/22/terror.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/thinking.png b/pidgin/pixmaps/emotes/default/22/thinking.png
index 9d1d752b10..deca319d4d 100644
--- a/pidgin/pixmaps/emotes/default/22/thinking.png
+++ b/pidgin/pixmaps/emotes/default/22/thinking.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/time-out.png b/pidgin/pixmaps/emotes/default/22/time-out.png
index 1407b82d3c..1090b3077f 100644
--- a/pidgin/pixmaps/emotes/default/22/time-out.png
+++ b/pidgin/pixmaps/emotes/default/22/time-out.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/tongue.png b/pidgin/pixmaps/emotes/default/22/tongue.png
index 144318dd1d..5d67a4c771 100644
--- a/pidgin/pixmaps/emotes/default/22/tongue.png
+++ b/pidgin/pixmaps/emotes/default/22/tongue.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/tremble.png b/pidgin/pixmaps/emotes/default/22/tremble.png
index 3a39e8796c..e3ad5ffbc9 100644
--- a/pidgin/pixmaps/emotes/default/22/tremble.png
+++ b/pidgin/pixmaps/emotes/default/22/tremble.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/vampire.png b/pidgin/pixmaps/emotes/default/22/vampire.png
index a2ae6e34ac..cab3a0b73b 100644
--- a/pidgin/pixmaps/emotes/default/22/vampire.png
+++ b/pidgin/pixmaps/emotes/default/22/vampire.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/victory.png b/pidgin/pixmaps/emotes/default/22/victory.png
index d0b635e9b8..8205c6f673 100644
--- a/pidgin/pixmaps/emotes/default/22/victory.png
+++ b/pidgin/pixmaps/emotes/default/22/victory.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/waiting.png b/pidgin/pixmaps/emotes/default/22/waiting.png
index ec7d9de6d3..bc1fe57cd1 100644
--- a/pidgin/pixmaps/emotes/default/22/waiting.png
+++ b/pidgin/pixmaps/emotes/default/22/waiting.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/weep.png b/pidgin/pixmaps/emotes/default/22/weep.png
index 51219c1984..4000f110d3 100644
--- a/pidgin/pixmaps/emotes/default/22/weep.png
+++ b/pidgin/pixmaps/emotes/default/22/weep.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/wilt.png b/pidgin/pixmaps/emotes/default/22/wilt.png
index bdcf85ca69..9e87688b82 100644
--- a/pidgin/pixmaps/emotes/default/22/wilt.png
+++ b/pidgin/pixmaps/emotes/default/22/wilt.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/wink.png b/pidgin/pixmaps/emotes/default/22/wink.png
index 662baa8ce0..2eba489391 100644
--- a/pidgin/pixmaps/emotes/default/22/wink.png
+++ b/pidgin/pixmaps/emotes/default/22/wink.png
Binary files differ
diff --git a/pidgin/pixmaps/emotes/default/22/yawn.png b/pidgin/pixmaps/emotes/default/22/yawn.png
index 65f7c77896..3c63d12de5 100644
--- a/pidgin/pixmaps/emotes/default/22/yawn.png
+++ b/pidgin/pixmaps/emotes/default/22/yawn.png
Binary files differ
diff --git a/pidgin/pixmaps/icons/22/Makefile.am b/pidgin/pixmaps/icons/22/Makefile.am
index c0913b4a6a..2705013a14 100644
--- a/pidgin/pixmaps/icons/22/Makefile.am
+++ b/pidgin/pixmaps/icons/22/Makefile.am
@@ -2,7 +2,7 @@ SUBDIRS = scalable
EXTRA_DIST = pidgin.png
-pidginiconspixdir = $(datadir)/icons/hicolor/24x24/apps
+pidginiconspixdir = $(datadir)/icons/hicolor/22x22/apps
pidginiconspix_DATA = $(EXTRA_DIST)
diff --git a/pidgin/pixmaps/status/16/Makefile.am b/pidgin/pixmaps/status/16/Makefile.am
index f3b210d9c2..dbdbe8242c 100644
--- a/pidgin/pixmaps/status/16/Makefile.am
+++ b/pidgin/pixmaps/status/16/Makefile.am
@@ -8,7 +8,6 @@ EXTRA_DIST = available.png \
invisible.png \
log-in.png \
log-out.png \
- message-pending.png \
offline.png \
person.png
diff --git a/pidgin/pixmaps/status/16/message-pending.png b/pidgin/pixmaps/status/16/message-pending.png
deleted file mode 100644
index 705ca9c0b0..0000000000
--- a/pidgin/pixmaps/status/16/message-pending.png
+++ /dev/null
Binary files differ
diff --git a/pidgin/plugins/win32/transparency/win2ktrans.c b/pidgin/plugins/win32/transparency/win2ktrans.c
index 933eb140b3..49700eb704 100644
--- a/pidgin/plugins/win32/transparency/win2ktrans.c
+++ b/pidgin/plugins/win32/transparency/win2ktrans.c
@@ -274,9 +274,11 @@ static void add_slider(GtkWidget *win) {
gtk_window_get_size(GTK_WINDOW(win), &width, &height);
gtk_box_pack_start(GTK_BOX(vbox),
slider_box, FALSE, FALSE, 0);
+#if 0 /*Now that we save window sizes, don't resize it or else it causes windows to grow*/
/* Make window taller so we don't slowly collapse its message area */
gtk_window_resize(GTK_WINDOW(win), width,
(height + slidereq.height));
+#endif
/* Add window to list, to track that it has a slider */
slidwin = g_new0(slider_win, 1);
slidwin->win = win;
@@ -292,6 +294,7 @@ static void remove_sliders() {
slider_win *slidwin = (slider_win*) tmp->data;
if (slidwin != NULL &&
GTK_IS_WINDOW(slidwin->win)) {
+#if 0
GtkRequisition slidereq;
gint width, height;
/* Figure out how tall the slider was */
@@ -300,12 +303,13 @@ static void remove_sliders() {
gtk_window_get_size(
GTK_WINDOW(slidwin->win),
&width, &height);
-
+#endif
gtk_widget_destroy(slidwin->slider);
-
+#if 0
gtk_window_resize(
GTK_WINDOW(slidwin->win),
width, (height - slidereq.height));
+#endif
}
g_free(slidwin);
tmp = tmp->next;
diff --git a/pidgin/win32/nsis/translations/swedish.nsh b/pidgin/win32/nsis/translations/swedish.nsh
index d8e12229ec..b7a07cd49b 100644
--- a/pidgin/win32/nsis/translations/swedish.nsh
+++ b/pidgin/win32/nsis/translations/swedish.nsh
@@ -1,4 +1,4 @@
-;;
+;;
;; swedish.nsh
;;
;; Swedish language strings for the Windows Pidgin NSIS installer.
@@ -8,56 +8,53 @@
;; Author: Peter Hjalmarsson <xake@telia.com>, 2005.
;; Version 3
-; Make sure to update the PIDGIN_MACRO_LANGUAGEFILE_END macro in
-; langmacros.nsh when updating this file
-
; Startup Checks
-!define INSTALLER_IS_RUNNING "Installationsprogrammet körs redan."
-!define PIDGIN_IS_RUNNING "En instans av Pidgin körs redan. Avsluta Pidgin och försök igen."
-!define GTK_INSTALLER_NEEDED "Körmiljön GTK+ är antingen inte installerat eller behöver uppgraderas.$\rVar god installera v${GTK_MIN_VERSION} eller högre av GTK+-körmiljön."
+!define INSTALLER_IS_RUNNING "Installationsprogrammet körs redan."
+!define PIDGIN_IS_RUNNING "En instans av Pidgin körs redan. Avsluta Pidgin och försök igen."
+!define GTK_INSTALLER_NEEDED "Körmiljön GTK+ är antingen inte installerat eller behöver uppgraderas.$\rVar god installera v${GTK_MIN_VERSION} eller högre av GTK+-körmiljön."
; License Page
-!define PIDGIN_LICENSE_BUTTON "Nästa >"
-!define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) är utgivet under GPL. Licensen finns tillgänglig här för informationssyften enbart. $_CLICK"
+!define PIDGIN_LICENSE_BUTTON "Nästa >"
+!define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) är utgivet under GPL. Licensen finns tillgänglig här för informationssyften enbart. $_CLICK"
; Components Page
!define PIDGIN_SECTION_TITLE "Pidgin Snabbmeddelandeklient (obligatorisk)"
-!define GTK_SECTION_TITLE "GTK+-körmiljö (obligatorisk)"
-!define PIDGIN_SHORTCUTS_SECTION_TITLE "Genvägar"
+!define GTK_SECTION_TITLE "GTK+-körmiljö (obligatorisk)"
+!define PIDGIN_SHORTCUTS_SECTION_TITLE "Genvägar"
!define PIDGIN_DESKTOP_SHORTCUT_SECTION_TITLE "Skrivbord"
!define PIDGIN_STARTMENU_SHORTCUT_SECTION_TITLE "Startmeny"
-!define PIDGIN_SECTION_DESCRIPTION "Pidgins kärnfiler och DLL:er"
-!define GTK_SECTION_DESCRIPTION "En GUI-verktygsuppsättning för flera olika plattformar som Pidgin använder."
+!define PIDGIN_SECTION_DESCRIPTION "Pidgins kärnfiler och DLL:er"
+!define GTK_SECTION_DESCRIPTION "En GUI-verktygsuppsättning för flera olika plattformar som Pidgin använder."
-!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION "Genvägar för att starta Pidgin"
-!define PIDGIN_DESKTOP_SHORTCUT_DESC "Skapar en genväg till Pidgin på skrivbordet"
-!define PIDGIN_STARTMENU_SHORTCUT_DESC "Skapar ett tillägg i startmenyn för Pidgin"
+!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION "Genvägar för att starta Pidgin"
+!define PIDGIN_DESKTOP_SHORTCUT_DESC "Skapar en genväg till Pidgin på skrivbordet"
+!define PIDGIN_STARTMENU_SHORTCUT_DESC "Skapar ett tillägg i startmenyn för Pidgin"
; GTK+ Directory Page
-!define GTK_UPGRADE_PROMPT "En äldre version av GTK+ runtime hittades, vill du uppgradera den?$\rOBS! $(^Name) kommer kanske inte att fungera om du inte uppgraderar."
+!define GTK_UPGRADE_PROMPT "En äldre version av GTK+ runtime hittades, vill du uppgradera den?$\rOBS! $(^Name) kommer kanske inte att fungera om du inte uppgraderar."
; Installer Finish Page
-!define PIDGIN_FINISH_VISIT_WEB_SITE "Besök Windows-Pidgin hemsida"
+!define PIDGIN_FINISH_VISIT_WEB_SITE "Besök Windows-Pidgin hemsida"
; Pidgin Section Prompts and Texts
-!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Kunde inte avinstallera den nuvarande versionen av Pidgin. Den nya versionen kommer att installeras utan att ta bort den för närvarande installerade versionen."
+!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Kunde inte avinstallera den nuvarande versionen av Pidgin. Den nya versionen kommer att installeras utan att ta bort den för närvarande installerade versionen."
; GTK+ Section Prompts
!define GTK_INSTALL_ERROR "Fel vid installation av GTK+ runtime."
-!define GTK_BAD_INSTALL_PATH "Den sökväg du angivit går inte att komma åt eller skapa."
+!define GTK_BAD_INSTALL_PATH "Den sökväg du angivit går inte att komma åt eller skapa."
; URL Handler section
!define URI_HANDLERS_SECTION_TITLE "URI Hanterare"
; Uninstall Section Prompts
-!define un.PIDGIN_UNINSTALL_ERROR_1 "Avinstalleraren kunde inte hitta registervärden för Pidgin.$\rAntagligen har en annan användare installerat applikationen."
-!define un.PIDGIN_UNINSTALL_ERROR_2 "Du har inte rättigheter att avinstallera den här applikationen."
+!define un.PIDGIN_UNINSTALL_ERROR_1 "Avinstalleraren kunde inte hitta registervärden för Pidgin.$\rAntagligen har en annan användare installerat applikationen."
+!define un.PIDGIN_UNINSTALL_ERROR_2 "Du har inte rättigheter att avinstallera den här applikationen."
; Spellcheck Section Prompts
-!define PIDGIN_SPELLCHECK_SECTION_TITLE "Stöd för rättstavning"
-!define PIDGIN_SPELLCHECK_ERROR "Fel vid installation för rättstavning"
-!define PIDGIN_SPELLCHECK_DICT_ERROR "Fel vid installation av rättstavningsordlista"
-!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION "Stöd för Rättstavning. (Internetanslutning krävs för installation)"
+!define PIDGIN_SPELLCHECK_SECTION_TITLE "Stöd för rättstavning"
+!define PIDGIN_SPELLCHECK_ERROR "Fel vid installation för rättstavning"
+!define PIDGIN_SPELLCHECK_DICT_ERROR "Fel vid installation av rättstavningsordlista"
+!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION "Stöd för Rättstavning. (Internetanslutning krävs för installation)"
!define ASPELL_INSTALL_FAILED "Installationen misslyckades"
!define PIDGIN_SPELLCHECK_BRETON "Bretonska"
!define PIDGIN_SPELLCHECK_CATALAN "Katalanska"
@@ -69,14 +66,14 @@
!define PIDGIN_SPELLCHECK_ENGLISH "Engelska"
!define PIDGIN_SPELLCHECK_ESPERANTO "Esperanto"
!define PIDGIN_SPELLCHECK_SPANISH "Spanska"
-!define PIDGIN_SPELLCHECK_FAROESE "Färöiska"
+!define PIDGIN_SPELLCHECK_FAROESE "Färöiska"
!define PIDGIN_SPELLCHECK_FRENCH "Franska"
!define PIDGIN_SPELLCHECK_ITALIAN "Italienska"
-!define PIDGIN_SPELLCHECK_DUTCH "Nederländska"
+!define PIDGIN_SPELLCHECK_DUTCH "Nederländska"
!define PIDGIN_SPELLCHECK_NORWEGIAN "Norska"
!define PIDGIN_SPELLCHECK_POLISH "Polska"
!define PIDGIN_SPELLCHECK_PORTUGUESE "Portugisiska"
-!define PIDGIN_SPELLCHECK_ROMANIAN "Rumänska"
+!define PIDGIN_SPELLCHECK_ROMANIAN "Rumänska"
!define PIDGIN_SPELLCHECK_RUSSIAN "Ryska"
!define PIDGIN_SPELLCHECK_SLOVAK "Slovakiska"
!define PIDGIN_SPELLCHECK_SWEDISH "Svenska"
diff --git a/pidgin/win32/winpidgin.c b/pidgin/win32/winpidgin.c
index cc97f7e198..1c2338fa86 100644
--- a/pidgin/win32/winpidgin.c
+++ b/pidgin/win32/winpidgin.c
@@ -332,10 +332,10 @@ static char* winpidgin_lcid_to_posix(LCID lcid) {
break;
case LANG_ROMANIAN: posix = "ro"; break;
case LANG_RUSSIAN: posix = "ru"; break;
- /* LANG_CROATIAN == LANG_SERBIAN == LANG_BOSNIAN */
case LANG_SLOVAK: posix = "sk"; break;
case LANG_SLOVENIAN: posix = "sl"; break;
case LANG_ALBANIAN: posix = "sq"; break;
+ /* LANG_CROATIAN == LANG_SERBIAN == LANG_BOSNIAN */
case LANG_SERBIAN:
switch (sub_id) {
case SUBLANG_SERBIAN_LATIN:
@@ -538,6 +538,8 @@ WinMain (struct HINSTANCE__ *hInstance, struct HINSTANCE__ *hPrevInstance,
char exe_name[MAX_PATH];
HMODULE hmod;
char *tmp;
+ int pidgin_argc = __argc;
+ char **pidgin_argv = __argv;
/* If debug or help or version flag used, create console for output */
if (strstr(lpszCmdLine, "-d") || strstr(lpszCmdLine, "-h") || strstr(lpszCmdLine, "-v")) {
@@ -601,8 +603,20 @@ WinMain (struct HINSTANCE__ *hInstance, struct HINSTANCE__ *hPrevInstance,
/* Determine if we're running in portable mode */
if (strstr(lpszCmdLine, "--portable-mode")
|| (exe_name != NULL && strstr(exe_name, "-portable.exe"))) {
+ int i = 0, c = 0;
+
printf("Running in PORTABLE mode.\n");
portable_mode = TRUE;
+
+ /* Remove the --portable-mode arg from the args passed to pidgin so it doesn't choke */
+ pidgin_argv = malloc(sizeof(char*) * pidgin_argc);
+ for (; i < __argc; i++) {
+ if (strstr(__argv[i], "--portable-mode") == NULL) {
+ pidgin_argv[c] = __argv[i];
+ c++;
+ } else
+ pidgin_argc--;
+ }
}
if (portable_mode)
@@ -635,5 +649,5 @@ WinMain (struct HINSTANCE__ *hInstance, struct HINSTANCE__ *hPrevInstance,
return 0;
}
- return pidgin_main(hInstance, __argc, __argv);
+ return pidgin_main(hInstance, pidgin_argc, pidgin_argv);
}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index f7510a47b9..e67ef4a527 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -200,6 +200,7 @@ pidgin/plugins/gevolution/eds-utils.c
pidgin/plugins/gevolution/gevolution.c
pidgin/plugins/gevolution/gevo-util.c
pidgin/plugins/gevolution/new_person_dialog.c
+pidgin/plugins/gtkbuddynote.c
pidgin/plugins/gtk-signals-test.c
pidgin/plugins/history.c
pidgin/plugins/iconaway.c
diff --git a/share/Makefile.am b/share/Makefile.am
new file mode 100644
index 0000000000..59471a4288
--- /dev/null
+++ b/share/Makefile.am
@@ -0,0 +1,4 @@
+
+SUBDIRS = sounds
+
+EXTRA_DIST = Makefile.mingw
diff --git a/share/Makefile.mingw b/share/Makefile.mingw
new file mode 100644
index 0000000000..e7bbc4dad1
--- /dev/null
+++ b/share/Makefile.mingw
@@ -0,0 +1,19 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for win32 (mingw) version
+#
+
+PIDGIN_TREE_TOP := ..
+include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+
+include ./Makefile.am
+
+.PHONY: install clean
+
+install:
+ if test '$(SUBDIRS)'; then \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ $(MAKE) -C $$subdir -f $(MINGW_MAKEFILE) install || exit 1 ;\
+ done; \
+ fi;
diff --git a/pidgin/sounds/Makefile.am b/share/sounds/Makefile.am
index ff6cc80c9b..0f668154b6 100644
--- a/pidgin/sounds/Makefile.am
+++ b/share/sounds/Makefile.am
@@ -1,4 +1,4 @@
-soundsdir = $(datadir)/sounds/pidgin
+soundsdir = $(datadir)/sounds/purple
sounds_DATA = \
alert.wav \
login.wav \
diff --git a/pidgin/sounds/Makefile.mingw b/share/sounds/Makefile.mingw
index 18310a955a..18310a955a 100644
--- a/pidgin/sounds/Makefile.mingw
+++ b/share/sounds/Makefile.mingw
diff --git a/pidgin/sounds/alert.wav b/share/sounds/alert.wav
index e456f4ae56..e456f4ae56 100644
--- a/pidgin/sounds/alert.wav
+++ b/share/sounds/alert.wav
Binary files differ
diff --git a/pidgin/sounds/login.wav b/share/sounds/login.wav
index ec06433ef4..ec06433ef4 100644
--- a/pidgin/sounds/login.wav
+++ b/share/sounds/login.wav
Binary files differ
diff --git a/pidgin/sounds/logout.wav b/share/sounds/logout.wav
index 69af2462d0..69af2462d0 100644
--- a/pidgin/sounds/logout.wav
+++ b/share/sounds/logout.wav
Binary files differ
diff --git a/pidgin/sounds/receive.wav b/share/sounds/receive.wav
index dce135e0f0..dce135e0f0 100644
--- a/pidgin/sounds/receive.wav
+++ b/share/sounds/receive.wav
Binary files differ
diff --git a/pidgin/sounds/send.wav b/share/sounds/send.wav
index c77beb7f8f..c77beb7f8f 100644
--- a/pidgin/sounds/send.wav
+++ b/share/sounds/send.wav
Binary files differ