Mercurial > pidgin
changeset 24943:4d752d07a126
propagate from branch 'im.pidgin.pidgin' (head 25e27ca0f73c4714d17fcdf83d0e4ce7e63bc55d)
to branch 'im.pidgin.maiku.vv' (head b9da65a837c05d63e2515eca9d9e118499672003)
author | Mike Ruprecht <maiku@soc.pidgin.im> |
---|---|
date | Sat, 27 Sep 2008 04:44:17 +0000 |
parents | c3dbdb98a119 (current diff) 097b3f23dc01 (diff) |
children | 8bfa1f01f035 |
files | finch/gntpounce.c libpurple/protocols/jabber/Makefile.am libpurple/protocols/jabber/buddy.c libpurple/protocols/jabber/buddy.h libpurple/protocols/jabber/iq.c libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/libxmpp.c libpurple/protocols/msn/msn.c libpurple/protocols/null/nullprpl.c libpurple/protocols/qq/qq.c libpurple/protocols/yahoo/yahoo.c pidgin/artwork/art-tools/clean-svg-definitions.sh pidgin/artwork/art-tools/render-pidgin-emotes.rb pidgin/artwork/hicolor/16x16/actions/pidgin-change-bgcolor.png pidgin/artwork/hicolor/16x16/actions/pidgin-change-fgcolor.png pidgin/artwork/hicolor/16x16/actions/pidgin-drag-down.png pidgin/artwork/hicolor/16x16/actions/pidgin-drag-left.png pidgin/artwork/hicolor/16x16/actions/pidgin-drag-right.png pidgin/artwork/hicolor/16x16/actions/pidgin-drag-up.png pidgin/artwork/hicolor/16x16/actions/pidgin-emote-select.png pidgin/artwork/hicolor/16x16/actions/pidgin-font-face.png pidgin/artwork/hicolor/16x16/actions/pidgin-font-size-down.png pidgin/artwork/hicolor/16x16/actions/pidgin-font-size-up.png pidgin/artwork/hicolor/16x16/actions/pidgin-get-attention.png pidgin/artwork/hicolor/16x16/actions/pidgin-insert-image.png pidgin/artwork/hicolor/16x16/actions/pidgin-insert-link.png pidgin/artwork/hicolor/16x16/actions/pidgin-insert.png pidgin/artwork/hicolor/16x16/actions/pidgin-message-new.png pidgin/artwork/hicolor/16x16/actions/pidgin-send-file.png pidgin/artwork/hicolor/16x16/actions/pidgin-unblock.png pidgin/artwork/hicolor/16x16/actions/pidgin-view-plugins.png pidgin/artwork/hicolor/16x16/actions/pidgin-view-transfers.png pidgin/artwork/hicolor/16x16/animations/process-working0.png pidgin/artwork/hicolor/16x16/animations/process-working1.png pidgin/artwork/hicolor/16x16/animations/process-working10.png pidgin/artwork/hicolor/16x16/animations/process-working11.png pidgin/artwork/hicolor/16x16/animations/process-working12.png pidgin/artwork/hicolor/16x16/animations/process-working13.png pidgin/artwork/hicolor/16x16/animations/process-working14.png pidgin/artwork/hicolor/16x16/animations/process-working15.png pidgin/artwork/hicolor/16x16/animations/process-working16.png pidgin/artwork/hicolor/16x16/animations/process-working17.png pidgin/artwork/hicolor/16x16/animations/process-working18.png pidgin/artwork/hicolor/16x16/animations/process-working19.png pidgin/artwork/hicolor/16x16/animations/process-working2.png pidgin/artwork/hicolor/16x16/animations/process-working20.png pidgin/artwork/hicolor/16x16/animations/process-working21.png pidgin/artwork/hicolor/16x16/animations/process-working22.png pidgin/artwork/hicolor/16x16/animations/process-working23.png pidgin/artwork/hicolor/16x16/animations/process-working24.png pidgin/artwork/hicolor/16x16/animations/process-working25.png pidgin/artwork/hicolor/16x16/animations/process-working26.png pidgin/artwork/hicolor/16x16/animations/process-working27.png pidgin/artwork/hicolor/16x16/animations/process-working28.png pidgin/artwork/hicolor/16x16/animations/process-working29.png pidgin/artwork/hicolor/16x16/animations/process-working3.png pidgin/artwork/hicolor/16x16/animations/process-working30.png pidgin/artwork/hicolor/16x16/animations/process-working4.png pidgin/artwork/hicolor/16x16/animations/process-working5.png pidgin/artwork/hicolor/16x16/animations/process-working6.png pidgin/artwork/hicolor/16x16/animations/process-working7.png pidgin/artwork/hicolor/16x16/animations/process-working8.png pidgin/artwork/hicolor/16x16/animations/process-working9.png pidgin/artwork/hicolor/16x16/animations/typing0.png pidgin/artwork/hicolor/16x16/animations/typing1.png pidgin/artwork/hicolor/16x16/animations/typing2.png pidgin/artwork/hicolor/16x16/animations/typing3.png pidgin/artwork/hicolor/16x16/animations/typing4.png pidgin/artwork/hicolor/16x16/animations/typing5.png pidgin/artwork/hicolor/16x16/apps/pidgin-aim.png pidgin/artwork/hicolor/16x16/apps/pidgin-bonjour.png pidgin/artwork/hicolor/16x16/apps/pidgin-facebook.png pidgin/artwork/hicolor/16x16/apps/pidgin-gadu-gadu.png pidgin/artwork/hicolor/16x16/apps/pidgin-google-talk.png pidgin/artwork/hicolor/16x16/apps/pidgin-icq.png pidgin/artwork/hicolor/16x16/apps/pidgin-irc.png pidgin/artwork/hicolor/16x16/apps/pidgin-jabber.png pidgin/artwork/hicolor/16x16/apps/pidgin-meanwhile.png pidgin/artwork/hicolor/16x16/apps/pidgin-msn.png pidgin/artwork/hicolor/16x16/apps/pidgin-myspace.png pidgin/artwork/hicolor/16x16/apps/pidgin-novell.png pidgin/artwork/hicolor/16x16/apps/pidgin-qq.png pidgin/artwork/hicolor/16x16/apps/pidgin-silc.png pidgin/artwork/hicolor/16x16/apps/pidgin-simple.png pidgin/artwork/hicolor/16x16/apps/pidgin-yahoo.png pidgin/artwork/hicolor/16x16/apps/pidgin-zephyr.png pidgin/artwork/hicolor/16x16/apps/pidgin.png pidgin/artwork/hicolor/16x16/devices/pidgin-hiptop.png pidgin/artwork/hicolor/16x16/devices/pidgin-mobile.png pidgin/artwork/hicolor/16x16/emblems/pidgin-aol-client.png pidgin/artwork/hicolor/16x16/emblems/pidgin-birthday.png pidgin/artwork/hicolor/16x16/emblems/pidgin-blocked.png pidgin/artwork/hicolor/16x16/emblems/pidgin-bot.png pidgin/artwork/hicolor/16x16/emblems/pidgin-external.png pidgin/artwork/hicolor/16x16/emblems/pidgin-female.png pidgin/artwork/hicolor/16x16/emblems/pidgin-founder.png pidgin/artwork/hicolor/16x16/emblems/pidgin-free-for-chat.png pidgin/artwork/hicolor/16x16/emblems/pidgin-game.png pidgin/artwork/hicolor/16x16/emblems/pidgin-half-operator.png pidgin/artwork/hicolor/16x16/emblems/pidgin-male.png pidgin/artwork/hicolor/16x16/emblems/pidgin-music.png pidgin/artwork/hicolor/16x16/emblems/pidgin-not-authorized.png pidgin/artwork/hicolor/16x16/emblems/pidgin-operator.png pidgin/artwork/hicolor/16x16/emblems/pidgin-qq-member.png pidgin/artwork/hicolor/16x16/emblems/pidgin-secure.png pidgin/artwork/hicolor/16x16/emblems/pidgin-unavailable.png pidgin/artwork/hicolor/16x16/emblems/pidgin-video.png pidgin/artwork/hicolor/16x16/emblems/pidgin-voice.png pidgin/artwork/hicolor/16x16/status/pidgin-available.png pidgin/artwork/hicolor/16x16/status/pidgin-away.png pidgin/artwork/hicolor/16x16/status/pidgin-busy.png pidgin/artwork/hicolor/16x16/status/pidgin-chatroom.png pidgin/artwork/hicolor/16x16/status/pidgin-contact.png pidgin/artwork/hicolor/16x16/status/pidgin-dialog-auth.png pidgin/artwork/hicolor/16x16/status/pidgin-dialog-error.png pidgin/artwork/hicolor/16x16/status/pidgin-dialog-info.png pidgin/artwork/hicolor/16x16/status/pidgin-dialog-mail.png pidgin/artwork/hicolor/16x16/status/pidgin-dialog-question.png pidgin/artwork/hicolor/16x16/status/pidgin-extended-away.png pidgin/artwork/hicolor/16x16/status/pidgin-invisible.png pidgin/artwork/hicolor/16x16/status/pidgin-offline.png pidgin/artwork/hicolor/16x16/status/pidgin-tray-available.png pidgin/artwork/hicolor/16x16/status/pidgin-tray-away.png pidgin/artwork/hicolor/16x16/status/pidgin-tray-busy.png pidgin/artwork/hicolor/16x16/status/pidgin-tray-extended-away.png pidgin/artwork/hicolor/16x16/status/pidgin-tray-invisible.png pidgin/artwork/hicolor/16x16/status/pidgin-tray-message-pending.png pidgin/artwork/hicolor/16x16/status/pidgin-tray-new-im.png pidgin/artwork/hicolor/16x16/status/pidgin-tray-offline.png pidgin/artwork/hicolor/22x22/apps/pidgin.png pidgin/artwork/hicolor/22x22/status/pidgin-available.png pidgin/artwork/hicolor/22x22/status/pidgin-away.png pidgin/artwork/hicolor/22x22/status/pidgin-busy.png pidgin/artwork/hicolor/22x22/status/pidgin-chatroom.png pidgin/artwork/hicolor/22x22/status/pidgin-contact.png pidgin/artwork/hicolor/22x22/status/pidgin-extended-away.png pidgin/artwork/hicolor/22x22/status/pidgin-invisible.png pidgin/artwork/hicolor/22x22/status/pidgin-offline.png pidgin/artwork/hicolor/22x22/status/pidgin-tray-available.png pidgin/artwork/hicolor/22x22/status/pidgin-tray-away.png pidgin/artwork/hicolor/22x22/status/pidgin-tray-busy.png pidgin/artwork/hicolor/22x22/status/pidgin-tray-extended-away.png pidgin/artwork/hicolor/22x22/status/pidgin-tray-invisible.png pidgin/artwork/hicolor/22x22/status/pidgin-tray-message-pending.png pidgin/artwork/hicolor/22x22/status/pidgin-tray-new-im.png pidgin/artwork/hicolor/22x22/status/pidgin-tray-offline.png pidgin/artwork/hicolor/24x24/apps/pidgin-aim.png pidgin/artwork/hicolor/24x24/apps/pidgin-bonjour.png pidgin/artwork/hicolor/24x24/apps/pidgin-facebook.png pidgin/artwork/hicolor/24x24/apps/pidgin-gadu-gadu.png pidgin/artwork/hicolor/24x24/apps/pidgin-google-talk.png pidgin/artwork/hicolor/24x24/apps/pidgin-icq.png pidgin/artwork/hicolor/24x24/apps/pidgin-irc.png pidgin/artwork/hicolor/24x24/apps/pidgin-jabber.png pidgin/artwork/hicolor/24x24/apps/pidgin-meanwhile.png pidgin/artwork/hicolor/24x24/apps/pidgin-msn.png pidgin/artwork/hicolor/24x24/apps/pidgin-myspace.png pidgin/artwork/hicolor/24x24/apps/pidgin-novell.png pidgin/artwork/hicolor/24x24/apps/pidgin-qq.png pidgin/artwork/hicolor/24x24/apps/pidgin-silc.png pidgin/artwork/hicolor/24x24/apps/pidgin-simple.png pidgin/artwork/hicolor/24x24/apps/pidgin-yahoo.png pidgin/artwork/hicolor/24x24/apps/pidgin-zephyr.png pidgin/artwork/hicolor/24x24/apps/pidgin.png pidgin/artwork/hicolor/24x24/emotes/Makefile.am pidgin/artwork/hicolor/24x24/emotes/Makefile.mingw pidgin/artwork/hicolor/24x24/emotes/default.theme.in pidgin/artwork/hicolor/24x24/emotes/none/Makefile.am pidgin/artwork/hicolor/24x24/emotes/none/Makefile.mingw pidgin/artwork/hicolor/24x24/emotes/none/none.theme.in pidgin/artwork/hicolor/24x24/emotes/none/theme pidgin/artwork/hicolor/24x24/emotes/pidgin-act-up.png pidgin/artwork/hicolor/24x24/emotes/pidgin-airplane.png pidgin/artwork/hicolor/24x24/emotes/pidgin-alien.png pidgin/artwork/hicolor/24x24/emotes/pidgin-angel.png pidgin/artwork/hicolor/24x24/emotes/pidgin-angry.png pidgin/artwork/hicolor/24x24/emotes/pidgin-arrogant.png pidgin/artwork/hicolor/24x24/emotes/pidgin-at-wits-end.png pidgin/artwork/hicolor/24x24/emotes/pidgin-bad.png pidgin/artwork/hicolor/24x24/emotes/pidgin-bashful.png pidgin/artwork/hicolor/24x24/emotes/pidgin-beat-up.png pidgin/artwork/hicolor/24x24/emotes/pidgin-beauty.png pidgin/artwork/hicolor/24x24/emotes/pidgin-beer.png pidgin/artwork/hicolor/24x24/emotes/pidgin-blowkiss.png pidgin/artwork/hicolor/24x24/emotes/pidgin-bomb.png pidgin/artwork/hicolor/24x24/emotes/pidgin-bowl.png pidgin/artwork/hicolor/24x24/emotes/pidgin-boy.png pidgin/artwork/hicolor/24x24/emotes/pidgin-brb.png pidgin/artwork/hicolor/24x24/emotes/pidgin-bulgy-eyes.png pidgin/artwork/hicolor/24x24/emotes/pidgin-bunny.png pidgin/artwork/hicolor/24x24/emotes/pidgin-bye.png pidgin/artwork/hicolor/24x24/emotes/pidgin-cake.png pidgin/artwork/hicolor/24x24/emotes/pidgin-call-me.png pidgin/artwork/hicolor/24x24/emotes/pidgin-camera.png pidgin/artwork/hicolor/24x24/emotes/pidgin-can.png pidgin/artwork/hicolor/24x24/emotes/pidgin-car.png pidgin/artwork/hicolor/24x24/emotes/pidgin-cat.png pidgin/artwork/hicolor/24x24/emotes/pidgin-chicken.png pidgin/artwork/hicolor/24x24/emotes/pidgin-cigarette.png pidgin/artwork/hicolor/24x24/emotes/pidgin-clap.png pidgin/artwork/hicolor/24x24/emotes/pidgin-clock.png pidgin/artwork/hicolor/24x24/emotes/pidgin-cloudy.png pidgin/artwork/hicolor/24x24/emotes/pidgin-clover.png pidgin/artwork/hicolor/24x24/emotes/pidgin-clown.png pidgin/artwork/hicolor/24x24/emotes/pidgin-coffee.png pidgin/artwork/hicolor/24x24/emotes/pidgin-coins.png pidgin/artwork/hicolor/24x24/emotes/pidgin-computer.png pidgin/artwork/hicolor/24x24/emotes/pidgin-confused.png pidgin/artwork/hicolor/24x24/emotes/pidgin-console.png pidgin/artwork/hicolor/24x24/emotes/pidgin-cow.png pidgin/artwork/hicolor/24x24/emotes/pidgin-cowboy.png pidgin/artwork/hicolor/24x24/emotes/pidgin-crying.png pidgin/artwork/hicolor/24x24/emotes/pidgin-curl-lip.png pidgin/artwork/hicolor/24x24/emotes/pidgin-curse.png pidgin/artwork/hicolor/24x24/emotes/pidgin-cute.png pidgin/artwork/hicolor/24x24/emotes/pidgin-cyclops.png pidgin/artwork/hicolor/24x24/emotes/pidgin-dance.png pidgin/artwork/hicolor/24x24/emotes/pidgin-dazed.png pidgin/artwork/hicolor/24x24/emotes/pidgin-desire.png pidgin/artwork/hicolor/24x24/emotes/pidgin-devil.png pidgin/artwork/hicolor/24x24/emotes/pidgin-disappointed.png pidgin/artwork/hicolor/24x24/emotes/pidgin-disdain.png pidgin/artwork/hicolor/24x24/emotes/pidgin-doctor.png pidgin/artwork/hicolor/24x24/emotes/pidgin-dog.png pidgin/artwork/hicolor/24x24/emotes/pidgin-doh.png pidgin/artwork/hicolor/24x24/emotes/pidgin-dont-know.png pidgin/artwork/hicolor/24x24/emotes/pidgin-drink.png pidgin/artwork/hicolor/24x24/emotes/pidgin-drool.png pidgin/artwork/hicolor/24x24/emotes/pidgin-eat.png pidgin/artwork/hicolor/24x24/emotes/pidgin-embarrassed.png pidgin/artwork/hicolor/24x24/emotes/pidgin-excruciating.png pidgin/artwork/hicolor/24x24/emotes/pidgin-eyeroll.png pidgin/artwork/hicolor/24x24/emotes/pidgin-female-fighter.png pidgin/artwork/hicolor/24x24/emotes/pidgin-film.png pidgin/artwork/hicolor/24x24/emotes/pidgin-fingers-crossed.png pidgin/artwork/hicolor/24x24/emotes/pidgin-flag.png pidgin/artwork/hicolor/24x24/emotes/pidgin-foot-in-mouth.png pidgin/artwork/hicolor/24x24/emotes/pidgin-freaked-out.png pidgin/artwork/hicolor/24x24/emotes/pidgin-ghost.png pidgin/artwork/hicolor/24x24/emotes/pidgin-giggle.png pidgin/artwork/hicolor/24x24/emotes/pidgin-girl.png pidgin/artwork/hicolor/24x24/emotes/pidgin-glasses-cool.png pidgin/artwork/hicolor/24x24/emotes/pidgin-glasses-nerdy.png pidgin/artwork/hicolor/24x24/emotes/pidgin-go-away.png pidgin/artwork/hicolor/24x24/emotes/pidgin-goat.png pidgin/artwork/hicolor/24x24/emotes/pidgin-good.png pidgin/artwork/hicolor/24x24/emotes/pidgin-hammer.png pidgin/artwork/hicolor/24x24/emotes/pidgin-handcuffs.png pidgin/artwork/hicolor/24x24/emotes/pidgin-handshake.png pidgin/artwork/hicolor/24x24/emotes/pidgin-highfive.png pidgin/artwork/hicolor/24x24/emotes/pidgin-hug-left.png pidgin/artwork/hicolor/24x24/emotes/pidgin-hug-right.png pidgin/artwork/hicolor/24x24/emotes/pidgin-hypnotized.png pidgin/artwork/hicolor/24x24/emotes/pidgin-in-love.png pidgin/artwork/hicolor/24x24/emotes/pidgin-island.png pidgin/artwork/hicolor/24x24/emotes/pidgin-jump.png pidgin/artwork/hicolor/24x24/emotes/pidgin-kiss.png pidgin/artwork/hicolor/24x24/emotes/pidgin-kissed.png pidgin/artwork/hicolor/24x24/emotes/pidgin-kissing.png pidgin/artwork/hicolor/24x24/emotes/pidgin-knife.png pidgin/artwork/hicolor/24x24/emotes/pidgin-lamp.png pidgin/artwork/hicolor/24x24/emotes/pidgin-lashes.png pidgin/artwork/hicolor/24x24/emotes/pidgin-laugh.png pidgin/artwork/hicolor/24x24/emotes/pidgin-liquor.png pidgin/artwork/hicolor/24x24/emotes/pidgin-loser.png pidgin/artwork/hicolor/24x24/emotes/pidgin-love-over.png pidgin/artwork/hicolor/24x24/emotes/pidgin-love.png pidgin/artwork/hicolor/24x24/emotes/pidgin-lying.png pidgin/artwork/hicolor/24x24/emotes/pidgin-mad-tongue.png pidgin/artwork/hicolor/24x24/emotes/pidgin-mail.png pidgin/artwork/hicolor/24x24/emotes/pidgin-male-fighter1.png pidgin/artwork/hicolor/24x24/emotes/pidgin-male-fighter2.png pidgin/artwork/hicolor/24x24/emotes/pidgin-mean.png pidgin/artwork/hicolor/24x24/emotes/pidgin-meeting.png pidgin/artwork/hicolor/24x24/emotes/pidgin-messed.png pidgin/artwork/hicolor/24x24/emotes/pidgin-mobile.png pidgin/artwork/hicolor/24x24/emotes/pidgin-mohawk.png pidgin/artwork/hicolor/24x24/emotes/pidgin-moneymouth.png pidgin/artwork/hicolor/24x24/emotes/pidgin-monkey.png pidgin/artwork/hicolor/24x24/emotes/pidgin-moon.png pidgin/artwork/hicolor/24x24/emotes/pidgin-msn-away.png pidgin/artwork/hicolor/24x24/emotes/pidgin-msn-busy.png pidgin/artwork/hicolor/24x24/emotes/pidgin-msn.png pidgin/artwork/hicolor/24x24/emotes/pidgin-msn_online.png pidgin/artwork/hicolor/24x24/emotes/pidgin-music.png pidgin/artwork/hicolor/24x24/emotes/pidgin-musical-note.png pidgin/artwork/hicolor/24x24/emotes/pidgin-nailbiting.png pidgin/artwork/hicolor/24x24/emotes/pidgin-neutral.png pidgin/artwork/hicolor/24x24/emotes/pidgin-on-the-phone.png pidgin/artwork/hicolor/24x24/emotes/pidgin-party.png pidgin/artwork/hicolor/24x24/emotes/pidgin-peace.png pidgin/artwork/hicolor/24x24/emotes/pidgin-phone.png pidgin/artwork/hicolor/24x24/emotes/pidgin-pig.png pidgin/artwork/hicolor/24x24/emotes/pidgin-pill.png pidgin/artwork/hicolor/24x24/emotes/pidgin-pirate.png pidgin/artwork/hicolor/24x24/emotes/pidgin-pissed-off.png pidgin/artwork/hicolor/24x24/emotes/pidgin-pizza.png pidgin/artwork/hicolor/24x24/emotes/pidgin-plate.png pidgin/artwork/hicolor/24x24/emotes/pidgin-poop.png pidgin/artwork/hicolor/24x24/emotes/pidgin-pray.png pidgin/artwork/hicolor/24x24/emotes/pidgin-present.png pidgin/artwork/hicolor/24x24/emotes/pidgin-pumpkin.png pidgin/artwork/hicolor/24x24/emotes/pidgin-qq.png pidgin/artwork/hicolor/24x24/emotes/pidgin-question.png pidgin/artwork/hicolor/24x24/emotes/pidgin-quiet.png pidgin/artwork/hicolor/24x24/emotes/pidgin-rain.png pidgin/artwork/hicolor/24x24/emotes/pidgin-rainbow.png pidgin/artwork/hicolor/24x24/emotes/pidgin-rose-dead.png pidgin/artwork/hicolor/24x24/emotes/pidgin-rose.png pidgin/artwork/hicolor/24x24/emotes/pidgin-rotfl.png pidgin/artwork/hicolor/24x24/emotes/pidgin-sad.png pidgin/artwork/hicolor/24x24/emotes/pidgin-sarcastic.png pidgin/artwork/hicolor/24x24/emotes/pidgin-search.png pidgin/artwork/hicolor/24x24/emotes/pidgin-secret.png pidgin/artwork/hicolor/24x24/emotes/pidgin-shame.png pidgin/artwork/hicolor/24x24/emotes/pidgin-sheep.png pidgin/artwork/hicolor/24x24/emotes/pidgin-shock.png pidgin/artwork/hicolor/24x24/emotes/pidgin-shout.png pidgin/artwork/hicolor/24x24/emotes/pidgin-shut-mouth.png pidgin/artwork/hicolor/24x24/emotes/pidgin-sick.png pidgin/artwork/hicolor/24x24/emotes/pidgin-sidefrown.png pidgin/artwork/hicolor/24x24/emotes/pidgin-silly.png pidgin/artwork/hicolor/24x24/emotes/pidgin-sinister.png pidgin/artwork/hicolor/24x24/emotes/pidgin-skeleton.png pidgin/artwork/hicolor/24x24/emotes/pidgin-skywalker.png pidgin/artwork/hicolor/24x24/emotes/pidgin-sleepy.png pidgin/artwork/hicolor/24x24/emotes/pidgin-smile-big.png pidgin/artwork/hicolor/24x24/emotes/pidgin-smile.png pidgin/artwork/hicolor/24x24/emotes/pidgin-smirk.png pidgin/artwork/hicolor/24x24/emotes/pidgin-snail.png pidgin/artwork/hicolor/24x24/emotes/pidgin-snicker.png pidgin/artwork/hicolor/24x24/emotes/pidgin-snowman.png pidgin/artwork/hicolor/24x24/emotes/pidgin-soccerball.png pidgin/artwork/hicolor/24x24/emotes/pidgin-soldier.png pidgin/artwork/hicolor/24x24/emotes/pidgin-star.png pidgin/artwork/hicolor/24x24/emotes/pidgin-starving.png pidgin/artwork/hicolor/24x24/emotes/pidgin-stop.png pidgin/artwork/hicolor/24x24/emotes/pidgin-struggle.png pidgin/artwork/hicolor/24x24/emotes/pidgin-sun.png pidgin/artwork/hicolor/24x24/emotes/pidgin-sweat.png pidgin/artwork/hicolor/24x24/emotes/pidgin-talktohand.png pidgin/artwork/hicolor/24x24/emotes/pidgin-teeth.png pidgin/artwork/hicolor/24x24/emotes/pidgin-terror.png pidgin/artwork/hicolor/24x24/emotes/pidgin-thinking.png pidgin/artwork/hicolor/24x24/emotes/pidgin-thunder.png pidgin/artwork/hicolor/24x24/emotes/pidgin-time-out.png pidgin/artwork/hicolor/24x24/emotes/pidgin-tongue.png pidgin/artwork/hicolor/24x24/emotes/pidgin-tremble.png pidgin/artwork/hicolor/24x24/emotes/pidgin-turtle.png pidgin/artwork/hicolor/24x24/emotes/pidgin-tv.png pidgin/artwork/hicolor/24x24/emotes/pidgin-umbrella.png pidgin/artwork/hicolor/24x24/emotes/pidgin-vampire.png pidgin/artwork/hicolor/24x24/emotes/pidgin-victory.png pidgin/artwork/hicolor/24x24/emotes/pidgin-waiting.png pidgin/artwork/hicolor/24x24/emotes/pidgin-watermelon.png pidgin/artwork/hicolor/24x24/emotes/pidgin-waving.png pidgin/artwork/hicolor/24x24/emotes/pidgin-weep.png pidgin/artwork/hicolor/24x24/emotes/pidgin-wilt.png pidgin/artwork/hicolor/24x24/emotes/pidgin-wink.png pidgin/artwork/hicolor/24x24/emotes/pidgin-worship.png pidgin/artwork/hicolor/24x24/emotes/pidgin-yawn.png pidgin/artwork/hicolor/24x24/emotes/pidgin-yin-yang.png pidgin/artwork/hicolor/24x24/emotes/theme pidgin/artwork/hicolor/24x24/status/pidgin-available.png pidgin/artwork/hicolor/24x24/status/pidgin-away.png pidgin/artwork/hicolor/24x24/status/pidgin-busy.png pidgin/artwork/hicolor/24x24/status/pidgin-chatroom.png pidgin/artwork/hicolor/24x24/status/pidgin-contact.png pidgin/artwork/hicolor/24x24/status/pidgin-extended-away.png pidgin/artwork/hicolor/24x24/status/pidgin-invisible.png pidgin/artwork/hicolor/24x24/status/pidgin-offline.png pidgin/artwork/hicolor/24x24/status/pidgin-tray-available.png pidgin/artwork/hicolor/24x24/status/pidgin-tray-away.png pidgin/artwork/hicolor/24x24/status/pidgin-tray-busy.png pidgin/artwork/hicolor/24x24/status/pidgin-tray-extended-away.png pidgin/artwork/hicolor/24x24/status/pidgin-tray-invisible.png pidgin/artwork/hicolor/24x24/status/pidgin-tray-message-pending.png pidgin/artwork/hicolor/24x24/status/pidgin-tray-new-im.png pidgin/artwork/hicolor/24x24/status/pidgin-tray-offline.png pidgin/artwork/hicolor/32x32/actions/pidgin-select-avatar.png pidgin/artwork/hicolor/32x32/apps/pidgin.png pidgin/artwork/hicolor/32x32/status/pidgin-available.png pidgin/artwork/hicolor/32x32/status/pidgin-away.png pidgin/artwork/hicolor/32x32/status/pidgin-busy.png pidgin/artwork/hicolor/32x32/status/pidgin-extended-away.png pidgin/artwork/hicolor/32x32/status/pidgin-invisible.png pidgin/artwork/hicolor/32x32/status/pidgin-offline.png pidgin/artwork/hicolor/32x32/status/pidgin-tray-available.png pidgin/artwork/hicolor/32x32/status/pidgin-tray-away.png pidgin/artwork/hicolor/32x32/status/pidgin-tray-busy.png pidgin/artwork/hicolor/32x32/status/pidgin-tray-extended-away.png pidgin/artwork/hicolor/32x32/status/pidgin-tray-invisible.png pidgin/artwork/hicolor/32x32/status/pidgin-tray-message-pending.png pidgin/artwork/hicolor/32x32/status/pidgin-tray-new-im.png pidgin/artwork/hicolor/32x32/status/pidgin-tray-offline.png pidgin/artwork/hicolor/48x48/apps/pidgin-aim.png pidgin/artwork/hicolor/48x48/apps/pidgin-bonjour.png pidgin/artwork/hicolor/48x48/apps/pidgin-facebook.png pidgin/artwork/hicolor/48x48/apps/pidgin-gadu-gadu.png pidgin/artwork/hicolor/48x48/apps/pidgin-icq.png pidgin/artwork/hicolor/48x48/apps/pidgin-irc.png pidgin/artwork/hicolor/48x48/apps/pidgin-jabber.png pidgin/artwork/hicolor/48x48/apps/pidgin-meanwhile.png pidgin/artwork/hicolor/48x48/apps/pidgin-msn.png pidgin/artwork/hicolor/48x48/apps/pidgin-myspace.png pidgin/artwork/hicolor/48x48/apps/pidgin-novell.png pidgin/artwork/hicolor/48x48/apps/pidgin-qq.png pidgin/artwork/hicolor/48x48/apps/pidgin-silc.png pidgin/artwork/hicolor/48x48/apps/pidgin-simple.png pidgin/artwork/hicolor/48x48/apps/pidgin-yahoo.png pidgin/artwork/hicolor/48x48/apps/pidgin-zephyr.png pidgin/artwork/hicolor/48x48/apps/pidgin.png pidgin/artwork/hicolor/48x48/status/pidgin-available.png pidgin/artwork/hicolor/48x48/status/pidgin-away.png pidgin/artwork/hicolor/48x48/status/pidgin-busy.png pidgin/artwork/hicolor/48x48/status/pidgin-dialog-auth.png pidgin/artwork/hicolor/48x48/status/pidgin-dialog-cool.png pidgin/artwork/hicolor/48x48/status/pidgin-dialog-dialog.png pidgin/artwork/hicolor/48x48/status/pidgin-dialog-error.png pidgin/artwork/hicolor/48x48/status/pidgin-dialog-info.png pidgin/artwork/hicolor/48x48/status/pidgin-dialog-mail.png pidgin/artwork/hicolor/48x48/status/pidgin-extended-away.png pidgin/artwork/hicolor/48x48/status/pidgin-invisible.png pidgin/artwork/hicolor/48x48/status/pidgin-offline.png pidgin/artwork/hicolor/48x48/status/pidgin-tray-available.png pidgin/artwork/hicolor/48x48/status/pidgin-tray-away.png pidgin/artwork/hicolor/48x48/status/pidgin-tray-busy.png pidgin/artwork/hicolor/48x48/status/pidgin-tray-extended-away.png pidgin/artwork/hicolor/48x48/status/pidgin-tray-invisible.png pidgin/artwork/hicolor/48x48/status/pidgin-tray-message-pending.png pidgin/artwork/hicolor/48x48/status/pidgin-tray-new-im.png pidgin/artwork/hicolor/48x48/status/pidgin-tray-offline.png pidgin/artwork/hicolor/48x48/status/question.png pidgin/artwork/hicolor/48x48/status/warning.png pidgin/artwork/hicolor/scalable/apps/pidgin.svg pidgin/artwork/hicolor/scalable/status/pidgin-auth.svg pidgin/artwork/hicolor/scalable/status/pidgin-cool.svg pidgin/artwork/hicolor/scalable/status/pidgin-dialog.svg pidgin/artwork/hicolor/scalable/status/pidgin-error.svg pidgin/artwork/hicolor/scalable/status/pidgin-info.svg pidgin/artwork/hicolor/scalable/status/pidgin-mail.svg pidgin/artwork/hicolor/scalable/status/pidgin-question.svg pidgin/artwork/hicolor/scalable/status/pidgin-warning.svg pidgin/artwork/pixmaps/logo.png pidgin/artwork/pixmaps/pidgin.ico pidgin/gtkconv.c pidgin/gtkdialogs.c pidgin/gtkimhtmltoolbar.c pidgin/gtkprefs.c po/POTFILES.in |
diffstat | 104 files changed, 6463 insertions(+), 140 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog.API Fri Sep 26 16:26:56 2008 +0000 +++ b/ChangeLog.API Sat Sep 27 04:44:17 2008 +0000 @@ -1,5 +1,11 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version ?.?.? (??/??/????): + libpurple: + Added: + * PurpleMedia API + * xmlnode_get_parent + version 2.5.0 (08/18/2008): libpurple: Added:
--- a/Doxyfile.in Fri Sep 26 16:26:56 2008 +0000 +++ b/Doxyfile.in Sat Sep 27 04:44:17 2008 +0000 @@ -1070,7 +1070,7 @@ # undefined via #undef or recursively expanded use the := operator # instead of the = operator. -PREDEFINED = +PREDEFINED = USE_VV # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded.
--- a/configure.ac Fri Sep 26 16:26:56 2008 +0000 +++ b/configure.ac Sat Sep 27 04:44:17 2008 +0000 @@ -47,7 +47,7 @@ m4_define([purple_major_version], [2]) m4_define([purple_minor_version], [5]) m4_define([purple_micro_version], [2]) -m4_define([purple_version_suffix], [devel]) +m4_define([purple_version_suffix], [vv-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])) @@ -56,7 +56,7 @@ m4_define([gnt_major_version], [2]) m4_define([gnt_minor_version], [5]) m4_define([gnt_micro_version], [2]) -m4_define([gnt_version_suffix], [devel]) +m4_define([gnt_version_suffix], [vv-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])) @@ -324,6 +324,9 @@ AC_SUBST(GLIB_CFLAGS) AC_SUBST(GLIB_LIBS) +GLIB_GENMARSHAL=`pkg-config --variable=glib_genmarshal glib-2.0` +AC_SUBST(GLIB_GENMARSHAL) + AC_ARG_WITH([extraversion], AC_HELP_STRING([--with-extraversion=STRING], [extra version number to be displayed in Help->About and --help (for packagers)]), @@ -745,6 +748,56 @@ fi dnl ####################################################################### +dnl # Check for Farsight +dnl ####################################################################### +AC_ARG_ENABLE(farsight, + [AC_HELP_STRING([--disable-farsight], [compile without farsight support])], + enable_farsight="$enableval", enable_farsight="yes") +if test "x$enable_farsight" != "xno"; then + PKG_CHECK_MODULES(FARSIGHT, [farsight2-0.10 >= 0.0.3 gstreamer-0.10 gstreamer-plugins-base-0.10 libxml-2.0], [ + AC_DEFINE(USE_FARSIGHT, 1, [Use Farsight for voice and video]) + AC_SUBST(FARSIGHT_CFLAGS) + AC_SUBST(FARSIGHT_LIBS) + ], [ + enable_farsight="no" + ]) +fi + +dnl ####################################################################### +dnl # Check for GStreamer-properties +dnl ####################################################################### +AC_ARG_ENABLE(gstprops, + [AC_HELP_STRING([--disable-gstprops], [compile without gstreamer props])], + enable_gstprops="$enableval", enable_gstprops="yes") +if test "x$enable_gstprops" != "xno"; +then + dnl gstreamer-libs-$GST_MAJORMINOR + dnl gstreamer-gconf-$GST_MAJORMINOR + PKG_CHECK_MODULES(GSTPROPS, [gstreamer-0.10 gstreamer-plugins-base-0.10 libxml-2.0], [ + GSTPROPS_LIBS="$GSTPROPS_LIBS -lgstinterfaces-0.10" + AC_DEFINE(USE_GSTPROPS, 1, [Use GStreamer property probe for finding devices]) + AC_SUBST(GSTPROPS_LIBS) + AC_SUBST(GSTPROPS_CFLAGS) + ], [ + enable_gstprops="no" + ]) +fi + +dnl ####################################################################### +dnl # Check for Voice and Video support +dnl ####################################################################### +AC_ARG_ENABLE(vv, + [AC_HELP_STRING([--disable-vv], [compile without voice and video support])], + enable_vv="$enableval", enable_vv="yes") +if test "x$enable_vv" != "xno"; then + if test "x$enable_farsight" != "xno" -a "x$enable_gstprops" != "xno"; then + AC_DEFINE(USE_VV, 1, [Use voice and video]) + else + enable_vv="no" + fi +fi + +dnl ####################################################################### dnl # Check for Meanwhile headers (for Sametime) dnl ####################################################################### AC_ARG_ENABLE(meanwhile, @@ -2452,6 +2505,7 @@ echo echo Build with GStreamer support.. : $enable_gst echo Build with D-Bus support...... : $enable_dbus +echo Build with voice and video.... : $enable_vv if test "x$enable_dbus" = "xyes" ; then eval eval echo D-Bus services directory...... : $DBUS_SERVICES_DIR fi
--- a/finch/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/finch/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -26,6 +26,7 @@ finch.c \ gntidle.c \ gntlog.c \ + gntmedia.c \ gntnotify.c \ gntplugin.c \ gntpounce.c \ @@ -47,6 +48,7 @@ finch.h \ gntidle.h \ gntlog.h \ + gntmedia.h \ gntnotify.h \ gntplugin.h \ gntpounce.h \ @@ -69,6 +71,7 @@ $(INTLLIBS) \ $(GLIB_LIBS) \ $(LIBXML_LIBS) \ + $(FARSIGHT_LIBS) \ $(GNT_LIBS) \ $(GSTREAMER_LIBS) \ ./libgnt/libgnt.la \ @@ -88,5 +91,6 @@ $(GLIB_CFLAGS) \ $(DBUS_CFLAGS) \ $(LIBXML_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(GSTREAMER_CFLAGS) \ $(GNT_CFLAGS)
--- a/finch/gntaccount.c Fri Sep 26 16:26:56 2008 +0000 +++ b/finch/gntaccount.c Sat Sep 27 04:44:17 2008 +0000 @@ -1097,3 +1097,4 @@ return &ui_ops; } +
--- a/finch/gntdebug.c Fri Sep 26 16:26:56 2008 +0000 +++ b/finch/gntdebug.c Sat Sep 27 04:44:17 2008 +0000 @@ -23,6 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include "util.h" + #include <gnt.h> #include <gntbox.h> #include <gntbutton.h> @@ -36,7 +38,6 @@ #include "gntdebug.h" #include "finch.h" #include "notify.h" -#include "util.h" #include <stdio.h> #include <string.h> @@ -347,6 +348,13 @@ #ifdef USE_GSTREAMER REGISTER_G_LOG_HANDLER("GStreamer"); #endif +#ifdef USE_VV +#ifdef USE_FARSIGHT + REGISTER_G_LOG_HANDLER("farsight"); + REGISTER_G_LOG_HANDLER("farsight-transmitter"); + REGISTER_G_LOG_HANDLER("farsight-rtp"); +#endif +#endif g_set_print_handler(print_stderr); /* Redirect the debug messages to stderr */ if (!purple_debug_is_enabled())
--- a/finch/gntft.c Fri Sep 26 16:26:56 2008 +0000 +++ b/finch/gntft.c Sat Sep 27 04:44:17 2008 +0000 @@ -25,6 +25,12 @@ */ #include "finch.h" +#include "debug.h" +#include "notify.h" +#include "ft.h" +#include "prpl.h" +#include "util.h" + #include <gnt.h> #include <gntbox.h> #include <gntbutton.h> @@ -32,12 +38,6 @@ #include <gntlabel.h> #include <gnttree.h> -#include "debug.h" -#include "notify.h" -#include "ft.h" -#include "prpl.h" -#include "util.h" - #include "gntft.h" #include "prefs.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/gntmedia.c Sat Sep 27 04:44:17 2008 +0000 @@ -0,0 +1,428 @@ +/** + * @file gntmedia.c GNT Media API + * @ingroup finch + */ + +/* finch + * + * Finch 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "finch.h" +#include "mediamanager.h" + +#include "gntconv.h" +#include "gntmedia.h" + +#include "gnt.h" +#include "gntbutton.h" +#include "gntbox.h" +#include "gntlabel.h" + +#include "cmds.h" +#include "conversation.h" +#include "debug.h" + +/* An incredibly large part of the following is from gtkmedia.c */ +#ifdef USE_VV + +#undef hangup + +struct _FinchMediaPrivate +{ + PurpleMedia *media; + GstElement *send_level; + GstElement *recv_level; + + GntWidget *accept; + GntWidget *reject; + GntWidget *hangup; + GntWidget *calling; + + PurpleConversation *conv; +}; + +#define FINCH_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), FINCH_TYPE_MEDIA, FinchMediaPrivate)) + +static void finch_media_class_init (FinchMediaClass *klass); +static void finch_media_init (FinchMedia *media); +static void finch_media_finalize (GObject *object); +static void finch_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); +static void finch_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); + +static GntBoxClass *parent_class = NULL; + +enum { + MESSAGE, + LAST_SIGNAL +}; +static guint finch_media_signals[LAST_SIGNAL] = {0}; + +enum { + PROP_0, + PROP_MEDIA, + PROP_SEND_LEVEL, + PROP_RECV_LEVEL +}; + +GType +finch_media_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(FinchMediaClass), + NULL, + NULL, + (GClassInitFunc) finch_media_class_init, + NULL, + NULL, + sizeof(FinchMedia), + 0, + (GInstanceInitFunc) finch_media_init, + NULL + }; + type = g_type_register_static(GNT_TYPE_BOX, "FinchMedia", &info, 0); + } + return type; +} + + +static void +finch_media_class_init (FinchMediaClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass*)klass; + parent_class = g_type_class_peek_parent(klass); + + gobject_class->finalize = finch_media_finalize; + gobject_class->set_property = finch_media_set_property; + gobject_class->get_property = finch_media_get_property; + + g_object_class_install_property(gobject_class, PROP_MEDIA, + g_param_spec_object("media", + "PurpleMedia", + "The PurpleMedia associated with this media.", + PURPLE_TYPE_MEDIA, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + g_object_class_install_property(gobject_class, PROP_SEND_LEVEL, + g_param_spec_object("send-level", + "Send level", + "The GstElement of this media's send 'level'", + GST_TYPE_ELEMENT, + G_PARAM_READWRITE)); + g_object_class_install_property(gobject_class, PROP_RECV_LEVEL, + g_param_spec_object("recv-level", + "Receive level", + "The GstElement of this media's recv 'level'", + GST_TYPE_ELEMENT, + G_PARAM_READWRITE)); + + finch_media_signals[MESSAGE] = g_signal_new("message", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + g_type_class_add_private(klass, sizeof(FinchMediaPrivate)); +} + + +static void +finch_media_init (FinchMedia *media) +{ + media->priv = FINCH_MEDIA_GET_PRIVATE(media); + + media->priv->calling = gnt_label_new(_("Calling ... ")); + media->priv->hangup = gnt_button_new(_("Hangup")); + media->priv->accept = gnt_button_new(_("Accept")); + media->priv->reject = gnt_button_new(_("Reject")); + + gnt_box_set_alignment(GNT_BOX(media), GNT_ALIGN_MID); + + gnt_box_add_widget(GNT_BOX(media), media->priv->accept); + gnt_box_add_widget(GNT_BOX(media), media->priv->reject); +} + +static void +finch_media_finalize (GObject *media) +{ + FinchMedia *gntmedia = FINCH_MEDIA(media); + purple_debug_info("gntmedia", "finch_media_finalize\n"); + if (gntmedia->priv->media) + g_object_unref(gntmedia->priv->media); +} + +static void +finch_media_emit_message(FinchMedia *gntmedia, const char *msg) +{ + g_signal_emit(gntmedia, finch_media_signals[MESSAGE], 0, msg); +} + +static void +finch_media_ready_cb(PurpleMedia *media, FinchMedia *gntmedia) +{ + GstElement *sendbin, *sendlevel; + GstElement *recvbin, *recvlevel; + + GList *sessions = purple_media_get_session_names(media); + + purple_media_audio_init_src(&sendbin, &sendlevel); + purple_media_audio_init_recv(&recvbin, &recvlevel); + + for (; sessions; sessions = sessions->next) { + purple_media_set_src(media, sessions->data, sendbin); + purple_media_set_sink(media, sessions->data, recvbin); + } + g_list_free(sessions); + + g_object_set(gntmedia, "send-level", sendlevel, + "recv-level", recvlevel, + NULL); +} + +static void +finch_media_accept_cb(PurpleMedia *media, FinchMedia *gntmedia) +{ + GntWidget *parent; + GstElement *sendbin = NULL, *recvbin = NULL; + + finch_media_emit_message(gntmedia, _("Call in progress.")); + + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->accept); + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->reject); + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->hangup); + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->calling); + + gnt_box_add_widget(GNT_BOX(gntmedia), gntmedia->priv->hangup); + + gnt_widget_destroy(gntmedia->priv->accept); + gnt_widget_destroy(gntmedia->priv->reject); + gnt_widget_destroy(gntmedia->priv->calling); + gntmedia->priv->accept = NULL; + gntmedia->priv->reject = NULL; + gntmedia->priv->calling = NULL; + + parent = GNT_WIDGET(gntmedia); + while (parent->parent) + parent = parent->parent; + gnt_box_readjust(GNT_BOX(parent)); + gnt_widget_draw(parent); + + purple_media_get_elements(media, &sendbin, &recvbin, NULL, NULL); + gst_element_set_state(GST_ELEMENT(sendbin), GST_STATE_PLAYING); + gst_element_set_state(GST_ELEMENT(recvbin), GST_STATE_PLAYING); +} + +static void +finch_media_wait_cb(PurpleMedia *media, FinchMedia *gntmedia) +{ + GntWidget *parent; + + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->accept); + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->reject); + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->hangup); + gnt_box_remove(GNT_BOX(gntmedia), gntmedia->priv->calling); + + gnt_box_add_widget(GNT_BOX(gntmedia), gntmedia->priv->calling); + gnt_box_add_widget(GNT_BOX(gntmedia), gntmedia->priv->hangup); + + parent = GNT_WIDGET(gntmedia); + while (parent->parent) + parent = parent->parent; + gnt_box_readjust(GNT_BOX(parent)); + gnt_widget_draw(parent); +} + +static void +finch_media_hangup_cb(PurpleMedia *media, FinchMedia *gntmedia) +{ + finch_media_emit_message(gntmedia, _("You have ended the call.")); + finch_conversation_set_info_widget(gntmedia->priv->conv, NULL); + gnt_widget_destroy(GNT_WIDGET(gntmedia)); + /* XXX: This shouldn't have to be here to free the FinchMedia widget */ + g_object_unref(gntmedia); +} + +static void +finch_media_got_hangup_cb(PurpleMedia *media, FinchMedia *gntmedia) +{ + finch_media_emit_message(gntmedia, _("The call has been terminated.")); + finch_conversation_set_info_widget(gntmedia->priv->conv, NULL); + gnt_widget_destroy(GNT_WIDGET(gntmedia)); + /* XXX: This shouldn't have to be here to free the FinchMedia widget */ + g_object_unref(gntmedia); +} + +static void +finch_media_reject_cb(PurpleMedia *media, FinchMedia *gntmedia) +{ + finch_media_emit_message(gntmedia, _("You have rejected the call.")); + finch_conversation_set_info_widget(gntmedia->priv->conv, NULL); + gnt_widget_destroy(GNT_WIDGET(gntmedia)); + /* XXX: This shouldn't have to be here to free the FinchMedia widget */ + g_object_unref(gntmedia); +} + +static void +finch_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + FinchMedia *media; + g_return_if_fail(FINCH_IS_MEDIA(object)); + + media = FINCH_MEDIA(object); + switch (prop_id) { + case PROP_MEDIA: + if (media->priv->media) + g_object_unref(media->priv->media); + media->priv->media = g_value_get_object(value); + g_object_ref(media->priv->media); + g_signal_connect_swapped(G_OBJECT(media->priv->accept), "activate", + G_CALLBACK(purple_media_accept), media->priv->media); + g_signal_connect_swapped(G_OBJECT(media->priv->reject), "activate", + G_CALLBACK(purple_media_reject), media->priv->media); + g_signal_connect_swapped(G_OBJECT(media->priv->hangup), "activate", + G_CALLBACK(purple_media_hangup), media->priv->media); + + g_signal_connect(G_OBJECT(media->priv->media), "accepted", + G_CALLBACK(finch_media_accept_cb), media); + g_signal_connect(G_OBJECT(media->priv->media) ,"ready", + G_CALLBACK(finch_media_ready_cb), media); + g_signal_connect(G_OBJECT(media->priv->media), "wait", + G_CALLBACK(finch_media_wait_cb), media); + g_signal_connect(G_OBJECT(media->priv->media), "hangup", + G_CALLBACK(finch_media_hangup_cb), media); + g_signal_connect(G_OBJECT(media->priv->media), "reject", + G_CALLBACK(finch_media_reject_cb), media); + g_signal_connect(G_OBJECT(media->priv->media), "got-hangup", + G_CALLBACK(finch_media_got_hangup_cb), media); + g_signal_connect(G_OBJECT(media->priv->media), "got-accept", + G_CALLBACK(finch_media_accept_cb), media); + break; + case PROP_SEND_LEVEL: + if (media->priv->send_level) + gst_object_unref(media->priv->send_level); + media->priv->send_level = g_value_get_object(value); + g_object_ref(media->priv->send_level); + break; + case PROP_RECV_LEVEL: + if (media->priv->recv_level) + gst_object_unref(media->priv->recv_level); + media->priv->recv_level = g_value_get_object(value); + g_object_ref(media->priv->recv_level); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +finch_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + FinchMedia *media; + g_return_if_fail(FINCH_IS_MEDIA(object)); + + media = FINCH_MEDIA(object); + + switch (prop_id) { + case PROP_MEDIA: + g_value_set_object(value, media->priv->media); + break; + case PROP_SEND_LEVEL: + g_value_set_object(value, media->priv->send_level); + break; + case PROP_RECV_LEVEL: + g_value_set_object(value, media->priv->recv_level); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +GntWidget * +finch_media_new(PurpleMedia *media) +{ + return GNT_WIDGET(g_object_new(finch_media_get_type(), + "media", media, + "vertical", FALSE, + "homogeneous", FALSE, + NULL)); +} + +static void +gntmedia_message_cb(FinchMedia *gntmedia, const char *msg, PurpleConversation *conv) +{ + if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) { + purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, msg, PURPLE_MESSAGE_SYSTEM, time(NULL)); + } +} + +static gboolean +finch_new_media(PurpleMediaManager *manager, PurpleMedia *media, gpointer null) +{ + GntWidget *gntmedia; + PurpleConversation *conv; + + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, + purple_connection_get_account(purple_media_get_connection(media)), + purple_media_get_screenname(media)); + + gntmedia = finch_media_new(media); + g_signal_connect(G_OBJECT(gntmedia), "message", G_CALLBACK(gntmedia_message_cb), conv); + FINCH_MEDIA(gntmedia)->priv->conv = conv; + finch_conversation_set_info_widget(conv, gntmedia); + return TRUE; +} + +static PurpleCmdRet +call_cmd_cb(PurpleConversation *conv, const char *cmd, char **args, + char **eror, gpointer data) +{ + PurpleAccount *account = purple_conversation_get_account(conv); + + PurpleMedia *media = purple_prpl_initiate_media(account, + purple_conversation_get_name(conv), + PURPLE_MEDIA_AUDIO); + + if (!media) + return PURPLE_CMD_STATUS_FAILED; + + purple_media_wait(media); + return PURPLE_CMD_STATUS_OK; +} + +void finch_media_manager_init(void) +{ + PurpleMediaManager *manager = purple_media_manager_get(); + g_signal_connect(G_OBJECT(manager), "init-media", G_CALLBACK(finch_new_media), NULL); + purple_cmd_register("call", "", PURPLE_CMD_P_DEFAULT, + PURPLE_CMD_FLAG_IM, NULL, + call_cmd_cb, _("call: Make an audio call."), NULL); +} + +void finch_media_manager_uninit(void) +{ + PurpleMediaManager *manager = purple_media_manager_get(); + g_signal_handlers_disconnect_by_func(G_OBJECT(manager), + G_CALLBACK(finch_new_media), NULL); +} + +#endif /* USE_VV */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/gntmedia.h Sat Sep 27 04:44:17 2008 +0000 @@ -0,0 +1,77 @@ +/** + * @file gntmedia.h GNT Media API + * @ingroup finch + */ + +/* finch + * + * Finch 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef GNT_MEDIA_H +#define GNT_MEDIA_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef USE_VV + +#include <glib-object.h> +#include "gntbox.h" + +G_BEGIN_DECLS + +#define FINCH_TYPE_MEDIA (finch_media_get_type()) +#define FINCH_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FINCH_TYPE_MEDIA, FinchMedia)) +#define FINCH_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FINCH_TYPE_MEDIA, FinchMediaClass)) +#define FINCH_IS_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FINCH_TYPE_MEDIA)) +#define FINCH_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FINCH_TYPE_MEDIA)) +#define FINCH_MEDIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FINCH_TYPE_MEDIA, FinchMediaClass)) + +typedef struct _FinchMedia FinchMedia; +typedef struct _FinchMediaClass FinchMediaClass; +typedef struct _FinchMediaPrivate FinchMediaPrivate; +typedef enum _FinchMediaState FinchMediaState; + +struct _FinchMediaClass +{ + GntBoxClass parent_class; +}; + +struct _FinchMedia +{ + GntBox parent; + FinchMediaPrivate *priv; +}; + +GType finch_media_get_type(void); + +GntWidget *finch_media_new(PurpleMedia *media); + +void finch_media_manager_init(void); + +void finch_media_manager_uninit(void); + +G_END_DECLS + +#endif /* USE_VV */ + +#endif /* GNT_MEDIA_H */ +
--- a/finch/gntnotify.c Fri Sep 26 16:26:56 2008 +0000 +++ b/finch/gntnotify.c Sat Sep 27 04:44:17 2008 +0000 @@ -23,6 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include <util.h> + #include <gnt.h> #include <gntbox.h> #include <gntbutton.h> @@ -33,7 +35,6 @@ #include "finch.h" -#include <util.h> #include "gntnotify.h" #include "debug.h"
--- a/finch/gntplugin.c Fri Sep 26 16:26:56 2008 +0000 +++ b/finch/gntplugin.c Sat Sep 27 04:44:17 2008 +0000 @@ -23,6 +23,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include "notify.h" +#include "request.h" + #include <gnt.h> #include <gntbox.h> #include <gntbutton.h> @@ -34,8 +37,6 @@ #include "finch.h" #include "debug.h" -#include "notify.h" -#include "request.h" #include "gntplugin.h" #include "gntrequest.h"
--- a/finch/gntpounce.c Fri Sep 26 16:26:56 2008 +0000 +++ b/finch/gntpounce.c Sat Sep 27 04:44:17 2008 +0000 @@ -24,6 +24,16 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ +#include "internal.h" +#include "account.h" +#include "conversation.h" +#include "debug.h" +#include "notify.h" +#include "prpl.h" +#include "request.h" +#include "server.h" +#include "util.h" + #include <gnt.h> #include <gntbox.h> #include <gntbutton.h> @@ -37,15 +47,6 @@ #include "finch.h" -#include "account.h" -#include "conversation.h" -#include "debug.h" -#include "notify.h" -#include "prpl.h" -#include "request.h" -#include "server.h" -#include "util.h" - #include "gntpounce.h"
--- a/finch/gntrequest.c Fri Sep 26 16:26:56 2008 +0000 +++ b/finch/gntrequest.c Sat Sep 27 04:44:17 2008 +0000 @@ -23,6 +23,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ + +#include "util.h" + #include <gnt.h> #include <gntbox.h> #include <gntbutton.h> @@ -37,7 +40,6 @@ #include "finch.h" #include "gntrequest.h" #include "debug.h" -#include "util.h" typedef struct {
--- a/finch/gntstatus.c Fri Sep 26 16:26:56 2008 +0000 +++ b/finch/gntstatus.c Sat Sep 27 04:44:17 2008 +0000 @@ -23,6 +23,10 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ + +#include <notify.h> +#include <request.h> + #include <gnt.h> #include <gntbox.h> #include <gntbutton.h> @@ -35,9 +39,6 @@ #include "finch.h" -#include <notify.h> -#include <request.h> - #include "gntstatus.h" static struct
--- a/finch/gntui.c Fri Sep 26 16:26:56 2008 +0000 +++ b/finch/gntui.c Sat Sep 27 04:44:17 2008 +0000 @@ -19,9 +19,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include <prefs.h> #include "finch.h" -#include "gntui.h" #include "gntaccount.h" #include "gntblist.h" @@ -31,6 +31,7 @@ #include "gntdebug.h" #include "gntft.h" #include "gntlog.h" +#include "gntmedia.h" #include "gntnotify.h" #include "gntplugin.h" #include "gntpounce.h" @@ -40,7 +41,7 @@ #include "gntstatus.h" #include "gntsound.h" -#include <prefs.h> +#include "gntui.h" void gnt_ui_init() { @@ -91,6 +92,11 @@ finch_roomlist_init(); purple_roomlist_set_ui_ops(finch_roomlist_get_ui_ops()); +#ifdef USE_VV + /* Media */ + finch_media_manager_init(); +#endif + gnt_register_action(_("Accounts"), finch_accounts_show_all); gnt_register_action(_("Buddy List"), finch_blist_show); gnt_register_action(_("Buddy Pounces"), finch_pounces_manager_show); @@ -136,6 +142,10 @@ finch_roomlist_uninit(); purple_roomlist_set_ui_ops(NULL); +#ifdef USE_VV + finch_media_manager_uninit(); +#endif + gnt_quit(); #endif }
--- a/finch/libgnt/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/finch/libgnt/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -89,6 +89,7 @@ AM_CPPFLAGS = \ $(GLIB_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(GNT_CFLAGS) \ $(DEBUG_CFLAGS) \ $(LIBXML_CFLAGS) \
--- a/finch/libgnt/gntkeys.h Fri Sep 26 16:26:56 2008 +0000 +++ b/finch/libgnt/gntkeys.h Sat Sep 27 04:44:17 2008 +0000 @@ -165,5 +165,6 @@ #undef lines #undef buttons #undef newline +#undef set_clock #endif
--- a/finch/libgnt/wms/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/finch/libgnt/wms/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -36,5 +36,6 @@ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) \ $(GNT_CFLAGS) \ - $(PLUGIN_CFLAGS) + $(PLUGIN_CFLAGS) \ + $(FARSIGHT_CFLAGS)
--- a/finch/libgnt/wms/s.c Fri Sep 26 16:26:56 2008 +0000 +++ b/finch/libgnt/wms/s.c Sat Sep 27 04:44:17 2008 +0000 @@ -2,6 +2,7 @@ #include <sys/types.h> #include "internal.h" +#include "blist.h" #include "gnt.h" #include "gntbox.h" @@ -11,7 +12,6 @@ #include "gntwindow.h" #include "gntlabel.h" -#include "blist.h" #define TYPE_S (s_get_gtype())
--- a/finch/plugins/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/finch/plugins/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -42,6 +42,7 @@ -I$(top_srcdir)/finch \ -I$(top_srcdir)/finch/libgnt \ $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(GLIB_CFLAGS) \ $(GNT_CFLAGS) \ $(PLUGIN_CFLAGS)
--- a/libpurple/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -1,6 +1,7 @@ EXTRA_DIST = \ dbus-analyze-functions.py \ dbus-analyze-types.py \ + marshallers.list \ purple-notifications-example \ purple-remote \ purple-send \ @@ -51,6 +52,9 @@ idle.c \ imgstore.c \ log.c \ + marshallers.c \ + media.c \ + mediamanager.c \ mime.c \ nat-pmp.c \ network.c \ @@ -104,6 +108,9 @@ idle.h \ imgstore.h \ log.h \ + marshallers.h \ + media.h \ + mediamanager.h \ mime.h \ nat-pmp.h \ network.h \ @@ -137,6 +144,15 @@ purple_builtheaders = purple.h version.h +marshallers.h: marshallers.list + @echo "Generating marshallers.h" + $(GLIB_GENMARSHAL) --prefix=purple_smarshal $(srcdir)/marshallers.list --header > marshallers.h + +marshallers.c: marshallers.list + @echo "Generating marshallers.c" + echo "#include \"marshallers.h\"" > marshallers.c + $(GLIB_GENMARSHAL) --prefix=purple_smarshal $(srcdir)/marshallers.list --body >> marshallers.c + if ENABLE_DBUS CLEANFILES = \ @@ -145,6 +161,8 @@ dbus-client-binding.h \ dbus-types.c \ dbus-types.h \ + marshallers.c \ + marshallers.h \ purple-client-bindings.c \ purple-client-bindings.h \ purple.service @@ -215,6 +233,8 @@ dbus-types.c \ dbus-types.h \ dbus-bindings.c \ + marshallers.c \ + marshallers.h \ purple-client-bindings.c \ purple-client-bindings.h @@ -248,6 +268,9 @@ $(LIBXML_LIBS) \ $(NETWORKMANAGER_LIBS) \ $(INTLLIBS) \ + $(FARSIGHT_LIBS) \ + $(GSTREAMER_LIBS) \ + $(GSTPROPS_LIBS) \ -lm AM_CPPFLAGS = \ @@ -260,6 +283,9 @@ $(DEBUG_CFLAGS) \ $(DBUS_CFLAGS) \ $(LIBXML_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(GSTPROPS_CFLAGS) \ $(NETWORKMANAGER_CFLAGS) # INSTALL_SSL_CERTIFICATES is true when SSL_CERTIFICATES_DIR is empty.
--- a/libpurple/example/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/example/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -8,6 +8,7 @@ $(INTLLIBS) \ $(GLIB_LIBS) \ $(LIBXML_LIBS) \ + $(FARSIGHT_LIBS) \ $(top_builddir)/libpurple/libpurple.la AM_CPPFLAGS = \ @@ -23,4 +24,5 @@ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) \ $(DBUS_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(LIBXML_CFLAGS)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/marshallers.list Sat Sep 27 04:44:17 2008 +0000 @@ -0,0 +1,3 @@ +VOID:BOXED,BOXED +VOID:POINTER,POINTER,OBJECT +BOOLEAN:OBJECT
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/media.c Sat Sep 27 04:44:17 2008 +0000 @@ -0,0 +1,1362 @@ +/** + * @file media.c Media API + * @ingroup core + */ + +/* 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 <string.h> + +#include "internal.h" + +#include "connection.h" +#include "media.h" +#include "marshallers.h" +#include "mediamanager.h" + +#include "debug.h" + +#ifdef USE_VV + +#include <gst/interfaces/propertyprobe.h> +#include <gst/farsight/fs-conference-iface.h> + +struct _PurpleMediaSession +{ + gchar *id; + PurpleMedia *media; + GstElement *src; + GstElement *sink; + FsSession *session; + /* FsStream table. Mapped by participant's name */ + GHashTable *streams; + PurpleMediaSessionType type; + /* GList of FsCandidates table. Mapped by participant's name */ + GHashTable *local_candidates; + + /* + * These will need to be per stream when sessions with multiple + * streams are supported. + */ + FsCandidate *local_candidate; + FsCandidate *remote_candidate; +}; + +struct _PurpleMediaPrivate +{ + FsConference *conference; + + char *name; + PurpleConnection *connection; + + GHashTable *sessions; /* PurpleMediaSession table */ + GHashTable *participants; /* FsParticipant table */ + + GstElement *pipeline; +}; + +#define PURPLE_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA, PurpleMediaPrivate)) + +static void purple_media_class_init (PurpleMediaClass *klass); +static void purple_media_init (PurpleMedia *media); +static void purple_media_finalize (GObject *object); +static void purple_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); +static void purple_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); + +static void purple_media_new_local_candidate_cb(FsStream *stream, + FsCandidate *local_candidate, PurpleMediaSession *session); +static void purple_media_candidates_prepared_cb(FsStream *stream, + PurpleMediaSession *session); +static void purple_media_candidate_pair_established_cb(FsStream *stream, + FsCandidate *native_candidate, FsCandidate *remote_candidate, + PurpleMediaSession *session); + +static GObjectClass *parent_class = NULL; + + + +enum { + READY, + WAIT, + ACCEPTED, + HANGUP, + REJECT, + GOT_REQUEST, + GOT_HANGUP, + GOT_ACCEPT, + NEW_CANDIDATE, + CANDIDATES_PREPARED, + CANDIDATE_PAIR, + CODECS_READY, + LAST_SIGNAL +}; +static guint purple_media_signals[LAST_SIGNAL] = {0}; + +enum { + PROP_0, + PROP_FS_CONFERENCE, + PROP_NAME, + PROP_CONNECTION, +}; + +GType +purple_media_get_type() +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(PurpleMediaClass), + NULL, + NULL, + (GClassInitFunc) purple_media_class_init, + NULL, + NULL, + sizeof(PurpleMedia), + 0, + (GInstanceInitFunc) purple_media_init, + NULL + }; + type = g_type_register_static(G_TYPE_OBJECT, "PurpleMedia", &info, 0); + } + return type; +} + +static void +purple_media_class_init (PurpleMediaClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass*)klass; + parent_class = g_type_class_peek_parent(klass); + + gobject_class->finalize = purple_media_finalize; + gobject_class->set_property = purple_media_set_property; + gobject_class->get_property = purple_media_get_property; + + g_object_class_install_property(gobject_class, PROP_FS_CONFERENCE, + g_param_spec_object("farsight-conference", + "Farsight conference", + "The FsConference associated with this media.", + FS_TYPE_CONFERENCE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_NAME, + g_param_spec_string("screenname", + "Screenname", + "The screenname of the remote user", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property(gobject_class, PROP_CONNECTION, + g_param_spec_pointer("connection", + "Connection", + "The PurpleConnection associated with this session", + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + purple_media_signals[READY] = g_signal_new("ready", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + purple_media_signals[WAIT] = g_signal_new("wait", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + purple_media_signals[ACCEPTED] = g_signal_new("accepted", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + purple_media_signals[HANGUP] = g_signal_new("hangup", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + purple_media_signals[REJECT] = g_signal_new("reject", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + purple_media_signals[GOT_REQUEST] = g_signal_new("got-request", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + purple_media_signals[GOT_HANGUP] = g_signal_new("got-hangup", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + purple_media_signals[GOT_ACCEPT] = g_signal_new("got-accept", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + purple_media_signals[NEW_CANDIDATE] = g_signal_new("new-candidate", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + purple_smarshal_VOID__POINTER_POINTER_OBJECT, + G_TYPE_NONE, 3, G_TYPE_POINTER, + G_TYPE_POINTER, FS_TYPE_CANDIDATE); + purple_media_signals[CANDIDATES_PREPARED] = g_signal_new("candidates-prepared", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + purple_media_signals[CANDIDATE_PAIR] = g_signal_new("candidate-pair", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + purple_smarshal_VOID__BOXED_BOXED, + G_TYPE_NONE, 2, FS_TYPE_CANDIDATE, FS_TYPE_CANDIDATE); + purple_media_signals[CODECS_READY] = g_signal_new("codecs-ready", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + g_type_class_add_private(klass, sizeof(PurpleMediaPrivate)); +} + + +static void +purple_media_init (PurpleMedia *media) +{ + media->priv = PURPLE_MEDIA_GET_PRIVATE(media); + memset(media->priv, 0, sizeof(media->priv)); +} + +static void +purple_media_finalize (GObject *media) +{ + PurpleMediaPrivate *priv = PURPLE_MEDIA_GET_PRIVATE(media); + purple_debug_info("media","purple_media_finalize\n"); + + purple_media_manager_remove_media(purple_media_manager_get(), + PURPLE_MEDIA(media)); + + g_free(priv->name); + + if (priv->sessions) { + GList *sessions = g_hash_table_get_values(priv->sessions); + for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { + PurpleMediaSession *session = sessions->data; + g_free(session->id); + + if (session->streams) { + GList *streams = g_hash_table_get_values(session->streams); + for (; streams; streams = g_list_delete_link(streams, streams)) + g_object_unref(streams->data); + g_hash_table_destroy(session->streams); + } + + if (session->local_candidates) { + GList *candidates = g_hash_table_get_values(session->local_candidates); + for (; candidates; candidates = + g_list_delete_link(candidates, candidates)) + fs_candidate_list_destroy(candidates->data); + g_hash_table_destroy(session->local_candidates); + } + + if (session->local_candidate) + fs_candidate_destroy(session->local_candidate); + if (session->remote_candidate) + fs_candidate_destroy(session->remote_candidate); + + g_free(session); + } + g_hash_table_destroy(priv->sessions); + } + + if (priv->participants) { + GList *participants = g_hash_table_get_values(priv->participants); + for (; participants; participants = g_list_delete_link(participants, participants)) + g_object_unref(participants->data); + g_hash_table_destroy(priv->participants); + } + + if (priv->pipeline) { + GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(priv->pipeline)); + gst_bus_remove_signal_watch(bus); + gst_object_unref(bus); + gst_element_set_state(priv->pipeline, GST_STATE_NULL); + gst_object_unref(priv->pipeline); + } + + gst_object_unref(priv->conference); + + parent_class->finalize(media); +} + +static void +purple_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + PurpleMedia *media; + g_return_if_fail(PURPLE_IS_MEDIA(object)); + + media = PURPLE_MEDIA(object); + + switch (prop_id) { + case PROP_FS_CONFERENCE: + if (media->priv->conference) + g_object_unref(media->priv->conference); + media->priv->conference = g_value_get_object(value); + g_object_ref(media->priv->conference); + break; + case PROP_NAME: + g_free(media->priv->name); + media->priv->name = g_value_dup_string(value); + break; + case PROP_CONNECTION: + media->priv->connection = g_value_get_pointer(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +purple_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + PurpleMedia *media; + g_return_if_fail(PURPLE_IS_MEDIA(object)); + + media = PURPLE_MEDIA(object); + + switch (prop_id) { + case PROP_FS_CONFERENCE: + g_value_set_object(value, media->priv->conference); + break; + case PROP_NAME: + g_value_set_string(value, media->priv->name); + break; + case PROP_CONNECTION: + g_value_set_pointer(value, media->priv->connection); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +FsMediaType +purple_media_to_fs_media_type(PurpleMediaSessionType type) +{ + if (type & PURPLE_MEDIA_AUDIO) + return FS_MEDIA_TYPE_AUDIO; + else if (type & PURPLE_MEDIA_VIDEO) + return FS_MEDIA_TYPE_VIDEO; + else + return 0; +} + +FsStreamDirection +purple_media_to_fs_stream_direction(PurpleMediaSessionType type) +{ + if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_AUDIO || + (type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_VIDEO) + return FS_DIRECTION_BOTH; + else if ((type & PURPLE_MEDIA_SEND_AUDIO) || + (type & PURPLE_MEDIA_SEND_VIDEO)) + return FS_DIRECTION_SEND; + else if ((type & PURPLE_MEDIA_RECV_AUDIO) || + (type & PURPLE_MEDIA_RECV_VIDEO)) + return FS_DIRECTION_RECV; + else + return FS_DIRECTION_NONE; +} + +PurpleMediaSessionType +purple_media_from_fs(FsMediaType type, FsStreamDirection direction) +{ + PurpleMediaSessionType result = PURPLE_MEDIA_NONE; + if (type == FS_MEDIA_TYPE_AUDIO) { + if (direction & FS_DIRECTION_SEND) + result |= PURPLE_MEDIA_SEND_AUDIO; + if (direction & FS_DIRECTION_RECV) + result |= PURPLE_MEDIA_RECV_AUDIO; + } else if (type == FS_MEDIA_TYPE_VIDEO) { + if (direction & FS_DIRECTION_SEND) + result |= PURPLE_MEDIA_SEND_VIDEO; + if (direction & FS_DIRECTION_RECV) + result |= PURPLE_MEDIA_RECV_VIDEO; + } + return result; +} + +PurpleMediaSessionType +purple_media_get_overall_type(PurpleMedia *media) +{ + GList *values = g_hash_table_get_values(media->priv->sessions); + PurpleMediaSessionType type = PURPLE_MEDIA_NONE; + + for (; values; values = g_list_delete_link(values, values)) { + PurpleMediaSession *session = values->data; + type |= session->type; + } + + return type; +} + +static PurpleMediaSession* +purple_media_get_session(PurpleMedia *media, const gchar *sess_id) +{ + return (PurpleMediaSession*) (media->priv->sessions) ? + g_hash_table_lookup(media->priv->sessions, sess_id) : NULL; +} + +static FsParticipant* +purple_media_get_participant(PurpleMedia *media, const gchar *name) +{ + return (FsParticipant*) (media->priv->participants) ? + g_hash_table_lookup(media->priv->participants, name) : NULL; +} + +static FsStream* +purple_media_session_get_stream(PurpleMediaSession *session, const gchar *name) +{ + return (FsStream*) (session->streams) ? + g_hash_table_lookup(session->streams, name) : NULL; +} + +static GList* +purple_media_session_get_local_candidates(PurpleMediaSession *session, const gchar *name) +{ + return (GList*) (session->local_candidates) ? + g_hash_table_lookup(session->local_candidates, name) : NULL; +} + +static void +purple_media_add_session(PurpleMedia *media, PurpleMediaSession *session) +{ + if (!media->priv->sessions) { + purple_debug_info("media", "Creating hash table for sessions\n"); + media->priv->sessions = g_hash_table_new(g_str_hash, g_str_equal); + } + g_hash_table_insert(media->priv->sessions, g_strdup(session->id), session); +} + +static gboolean +purple_media_remove_session(PurpleMedia *media, PurpleMediaSession *session) +{ + return g_hash_table_remove(media->priv->sessions, session->id); +} + +static FsParticipant * +purple_media_add_participant(PurpleMedia *media, const gchar *name) +{ + FsParticipant *participant = purple_media_get_participant(media, name); + GError *err = NULL; + + if (participant) + return participant; + + participant = fs_conference_new_participant(media->priv->conference, + (gchar*)name, &err); + + if (err) { + purple_debug_error("media", "Error creating participant: %s\n", + err->message); + g_error_free(err); + return NULL; + } + + if (!media->priv->participants) { + purple_debug_info("media", "Creating hash table for participants\n"); + media->priv->participants = g_hash_table_new_full(g_str_hash, + g_str_equal, g_free, NULL); + } + + g_hash_table_insert(media->priv->participants, g_strdup(name), participant); + + return participant; +} + +static void +purple_media_insert_stream(PurpleMediaSession *session, const gchar *name, FsStream *stream) +{ + if (!session->streams) { + purple_debug_info("media", "Creating hash table for streams\n"); + session->streams = g_hash_table_new_full(g_str_hash, + g_str_equal, g_free, NULL); + } + + g_hash_table_insert(session->streams, g_strdup(name), stream); +} + +static void +purple_media_insert_local_candidate(PurpleMediaSession *session, const gchar *name, + FsCandidate *candidate) +{ + GList *candidates = purple_media_session_get_local_candidates(session, name); + + candidates = g_list_append(candidates, candidate); + + if (!session->local_candidates) { + purple_debug_info("media", "Creating hash table for local candidates\n"); + session->local_candidates = g_hash_table_new_full(g_str_hash, + g_str_equal, g_free, NULL); + } + + g_hash_table_insert(session->local_candidates, g_strdup(name), candidates); +} + +GList * +purple_media_get_session_names(PurpleMedia *media) +{ + return g_hash_table_get_keys(media->priv->sessions); +} + +void +purple_media_get_elements(PurpleMedia *media, GstElement **audio_src, GstElement **audio_sink, + GstElement **video_src, GstElement **video_sink) +{ + GList *values = g_hash_table_get_values(media->priv->sessions); + + for (; values; values = g_list_delete_link(values, values)) { + PurpleMediaSession *session = (PurpleMediaSession*)values->data; + + if (session->type & PURPLE_MEDIA_SEND_AUDIO && audio_src) + *audio_src = session->src; + if (session->type & PURPLE_MEDIA_RECV_AUDIO && audio_sink) + *audio_sink = session->sink; + if (session->type & PURPLE_MEDIA_SEND_VIDEO && video_src) + *video_src = session->src; + if (session->type & PURPLE_MEDIA_RECV_VIDEO && video_sink) + *video_sink = session->sink; + } +} + +void +purple_media_set_src(PurpleMedia *media, const gchar *sess_id, GstElement *src) +{ + PurpleMediaSession *session = purple_media_get_session(media, sess_id); + GstPad *sinkpad; + GstPad *srcpad; + + if (session->src) + gst_object_unref(session->src); + session->src = src; + gst_bin_add(GST_BIN(purple_media_get_pipeline(media)), + session->src); + + g_object_get(session->session, "sink-pad", &sinkpad, NULL); + srcpad = gst_element_get_static_pad(src, "ghostsrc"); + purple_debug_info("media", "connecting pad: %s\n", + gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK + ? "success" : "failure"); +} + +void +purple_media_set_sink(PurpleMedia *media, const gchar *sess_id, GstElement *sink) +{ + PurpleMediaSession *session = purple_media_get_session(media, sess_id); + if (session->sink) + gst_object_unref(session->sink); + session->sink = sink; + gst_bin_add(GST_BIN(purple_media_get_pipeline(media)), + session->sink); +} + +GstElement * +purple_media_get_src(PurpleMedia *media, const gchar *sess_id) +{ + return purple_media_get_session(media, sess_id)->src; +} + +GstElement * +purple_media_get_sink(PurpleMedia *media, const gchar *sess_id) +{ + return purple_media_get_session(media, sess_id)->sink; +} + +static PurpleMediaSession * +purple_media_session_from_fs_stream(PurpleMedia *media, FsStream *stream) +{ + FsSession *fssession; + GList *values; + + g_object_get(stream, "session", &fssession, NULL); + + values = g_hash_table_get_values(media->priv->sessions); + + for (; values; values = g_list_delete_link(values, values)) { + PurpleMediaSession *session = values->data; + + if (session->session == fssession) { + g_list_free(values); + g_object_unref(fssession); + return session; + } + } + + g_object_unref(fssession); + return NULL; +} + +static gboolean +media_bus_call(GstBus *bus, GstMessage *msg, gpointer media) +{ + switch(GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: + purple_debug_info("media", "End of Stream\n"); + break; + case GST_MESSAGE_ERROR: { + gchar *debug = NULL; + GError *err = NULL; + + gst_message_parse_error(msg, &err, &debug); + + purple_debug_error("media", "gst pipeline error: %s\n", err->message); + g_error_free(err); + + if (debug) { + purple_debug_error("media", "Debug details: %s\n", debug); + g_free (debug); + } + break; + } + case GST_MESSAGE_ELEMENT: { + if (gst_structure_has_name(msg->structure, "farsight-error")) { + FsError error_no; + gst_structure_get_enum(msg->structure, "error-no", + FS_TYPE_ERROR, (gint*)&error_no); + /* + * Unknown CName is only a problem for the + * multicast transmitter which isn't used. + */ + if (error_no != FS_ERROR_UNKNOWN_CNAME) + purple_debug_error("media", "farsight-error: %i: %s\n", error_no, + gst_structure_get_string(msg->structure, "error-msg")); + } else if (gst_structure_has_name(msg->structure, + "farsight-new-local-candidate")) { + FsStream *stream = g_value_get_object(gst_structure_get_value(msg->structure, "stream")); + FsCandidate *local_candidate = g_value_get_boxed(gst_structure_get_value(msg->structure, "candidate")); + PurpleMediaSession *session = purple_media_session_from_fs_stream(media, stream); + purple_media_new_local_candidate_cb(stream, local_candidate, session); + } else if (gst_structure_has_name(msg->structure, + "farsight-local-candidates-prepared")) { + FsStream *stream = g_value_get_object(gst_structure_get_value(msg->structure, "stream")); + PurpleMediaSession *session = purple_media_session_from_fs_stream(media, stream); + purple_media_candidates_prepared_cb(stream, session); + } else if (gst_structure_has_name(msg->structure, + "farsight-new-active-candidate-pair")) { + FsStream *stream = g_value_get_object(gst_structure_get_value(msg->structure, "stream")); + FsCandidate *local_candidate = g_value_get_boxed(gst_structure_get_value(msg->structure, "local-candidate")); + FsCandidate *remote_candidate = g_value_get_boxed(gst_structure_get_value(msg->structure, "remote-candidate")); + PurpleMediaSession *session = purple_media_session_from_fs_stream(media, stream); + purple_media_candidate_pair_established_cb(stream, local_candidate, remote_candidate, session); + } else if (gst_structure_has_name(msg->structure, + "farsight-recv-codecs-changed")) { + GList *codecs = g_value_get_boxed(gst_structure_get_value(msg->structure, "codecs")); + FsCodec *codec = codecs->data; + purple_debug_info("media", "farsight-recv-codecs-changed: %s\n", codec->encoding_name); + + } else if (gst_structure_has_name(msg->structure, + "farsight-component-state-changed")) { + + } else if (gst_structure_has_name(msg->structure, + "farsight-send-codec-changed")) { + + } else if (gst_structure_has_name(msg->structure, + "farsight-codecs-changed")) { + GList *sessions = g_hash_table_get_values(PURPLE_MEDIA(media)->priv->sessions); + FsSession *fssession = g_value_get_object(gst_structure_get_value(msg->structure, "session")); + for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { + PurpleMediaSession *session = sessions->data; + if (session->session == fssession) { + g_signal_emit(session->media, + purple_media_signals[CODECS_READY], + 0, &session->id); + g_list_free(sessions); + break; + } + } + } + break; + } + default: + break; + } + + return TRUE; +} + +GstElement * +purple_media_get_pipeline(PurpleMedia *media) +{ + if (!media->priv->pipeline) { + GstBus *bus; + media->priv->pipeline = gst_pipeline_new(media->priv->name); + bus = gst_pipeline_get_bus(GST_PIPELINE(media->priv->pipeline)); + gst_bus_add_signal_watch(GST_BUS(bus)); + g_signal_connect(G_OBJECT(bus), "message", + G_CALLBACK(media_bus_call), media); + gst_object_unref(bus); + + gst_bin_add(GST_BIN(media->priv->pipeline), GST_ELEMENT(media->priv->conference)); + } + + return media->priv->pipeline; +} + +PurpleConnection * +purple_media_get_connection(PurpleMedia *media) +{ + PurpleConnection *gc; + g_object_get(G_OBJECT(media), "connection", &gc, NULL); + return gc; +} + +char * +purple_media_get_screenname(PurpleMedia *media) +{ + char *ret; + g_object_get(G_OBJECT(media), "screenname", &ret, NULL); + return ret; +} + +void +purple_media_ready(PurpleMedia *media) +{ + g_signal_emit(media, purple_media_signals[READY], 0); +} + +void +purple_media_wait(PurpleMedia *media) +{ + g_signal_emit(media, purple_media_signals[WAIT], 0); +} + +void +purple_media_accept(PurpleMedia *media) +{ + g_signal_emit(media, purple_media_signals[ACCEPTED], 0); +} + +void +purple_media_hangup(PurpleMedia *media) +{ + g_signal_emit(media, purple_media_signals[HANGUP], 0); +} + +void +purple_media_reject(PurpleMedia *media) +{ + g_signal_emit(media, purple_media_signals[REJECT], 0); +} + +void +purple_media_got_request(PurpleMedia *media) +{ + g_signal_emit(media, purple_media_signals[GOT_REQUEST], 0); +} + +void +purple_media_got_hangup(PurpleMedia *media) +{ + g_signal_emit(media, purple_media_signals[GOT_HANGUP], 0); +} + +void +purple_media_got_accept(PurpleMedia *media) +{ + g_signal_emit(media, purple_media_signals[GOT_ACCEPT], 0); +} + +GList* +purple_media_get_devices(const gchar *plugin) +{ + GObjectClass *klass; + GstPropertyProbe *probe; + const GParamSpec *pspec; + GstElement *element = gst_element_factory_make(plugin, NULL); + GstElementFactory *factory; + const gchar *longname = NULL; + GList *ret = NULL; + + if (element == NULL) + return NULL; + + factory = gst_element_get_factory(element); + + longname = gst_element_factory_get_longname(factory); + klass = G_OBJECT_GET_CLASS(element); + + if (!g_object_class_find_property (klass, "device") || + !GST_IS_PROPERTY_PROBE (element) || + !(probe = GST_PROPERTY_PROBE (element)) || + !(pspec = gst_property_probe_get_property (probe, "device"))) { + purple_debug_info("media", "Found source '%s' (%s) - no device\n", + longname, GST_PLUGIN_FEATURE (factory)->name); + } else { + gint n; + gchar *name; + GValueArray *array; + + purple_debug_info("media", "Found devices\n"); + + /* Set autoprobe[-fps] to FALSE to avoid delays when probing. */ + if (g_object_class_find_property (klass, "autoprobe")) { + g_object_set (G_OBJECT (element), "autoprobe", FALSE, NULL); + if (g_object_class_find_property (klass, "autoprobe-fps")) { + g_object_set (G_OBJECT (element), "autoprobe-fps", FALSE, NULL); + } + } + + array = gst_property_probe_probe_and_get_values (probe, pspec); + if (array != NULL) { + for (n = 0 ; n < array->n_values ; n++) { + GValue *device = g_value_array_get_nth (array, n); + + ret = g_list_append(ret, g_value_dup_string(device)); + + g_object_set(G_OBJECT(element), "device", + g_value_get_string(device), NULL); + g_object_get(G_OBJECT(element), "device-name", &name, NULL); + purple_debug_info("media", "Found source '%s' (%s) - device '%s' (%s)\n", + longname, GST_PLUGIN_FEATURE (factory)->name, + name, g_value_get_string(device)); + g_free(name); + } + g_value_array_free(array); + } + + /* Restore autoprobe[-fps] to TRUE. */ + if (g_object_class_find_property (klass, "autoprobe")) { + g_object_set (G_OBJECT (element), "autoprobe", TRUE, NULL); + if (g_object_class_find_property (klass, "autoprobe-fps")) { + g_object_set (G_OBJECT (element), "autoprobe-fps", TRUE, NULL); + } + } + } + + gst_object_unref(element); + return ret; +} + +gchar * +purple_media_element_get_device(GstElement *element) +{ + gchar *device; + g_object_get(G_OBJECT(element), "device", &device, NULL); + return device; +} + +void +purple_media_audio_init_src(GstElement **sendbin, GstElement **sendlevel) +{ + GstElement *src; + GstElement *volume; + GstPad *pad; + GstPad *ghost; + const gchar *audio_device = purple_prefs_get_string("/purple/media/audio/device"); + double input_volume = purple_prefs_get_int("/purple/media/audio/volume/input")/10.0; + + purple_debug_info("media", "purple_media_audio_init_src\n"); + + *sendbin = gst_bin_new("purplesendaudiobin"); + src = gst_element_factory_make("alsasrc", "asrc"); + volume = gst_element_factory_make("volume", "purpleaudioinputvolume"); + g_object_set(volume, "volume", input_volume, NULL); + *sendlevel = gst_element_factory_make("level", "sendlevel"); + gst_bin_add_many(GST_BIN(*sendbin), src, volume, *sendlevel, NULL); + gst_element_link(src, volume); + gst_element_link(volume, *sendlevel); + pad = gst_element_get_pad(*sendlevel, "src"); + ghost = gst_ghost_pad_new("ghostsrc", pad); + gst_element_add_pad(*sendbin, ghost); + g_object_set(G_OBJECT(*sendlevel), "message", TRUE, NULL); + + if (audio_device != NULL && strcmp(audio_device, "")) + g_object_set(G_OBJECT(src), "device", audio_device, NULL); +} + +void +purple_media_video_init_src(GstElement **sendbin) +{ + GstElement *src, *tee, *queue, *local_sink; + GstPad *pad; + GstPad *ghost; + const gchar *video_plugin = purple_prefs_get_string( + "/purple/media/video/plugin"); + const gchar *video_device = purple_prefs_get_string( + "/purple/media/video/device"); + + purple_debug_info("media", "purple_media_video_init_src\n"); + + *sendbin = gst_bin_new("purplesendvideobin"); + src = gst_element_factory_make(video_plugin, "purplevideosource"); + gst_bin_add(GST_BIN(*sendbin), src); + + tee = gst_element_factory_make("tee", "purplevideosrctee"); + gst_bin_add(GST_BIN(*sendbin), tee); + gst_element_link(src, tee); + + queue = gst_element_factory_make("queue", NULL); + gst_bin_add(GST_BIN(*sendbin), queue); + gst_element_link(tee, queue); + + if (!strcmp(video_plugin, "videotestsrc")) { + /* unless is-live is set to true it doesn't throttle videotestsrc */ + g_object_set (G_OBJECT(src), "is-live", TRUE, NULL); + } + + pad = gst_element_get_static_pad(queue, "src"); + ghost = gst_ghost_pad_new("ghostsrc", pad); + gst_object_unref(pad); + gst_element_add_pad(*sendbin, ghost); + + queue = gst_element_factory_make("queue", "purplelocalvideoqueue"); + gst_bin_add(GST_BIN(*sendbin), queue); + /* The queue is linked later, when the local video is ready to be shown */ + + local_sink = gst_element_factory_make("autovideosink", "purplelocalvideosink"); + gst_bin_add(GST_BIN(*sendbin), local_sink); + gst_element_link(queue, local_sink); + + if (video_device != NULL && strcmp(video_device, "")) + g_object_set(G_OBJECT(src), "device", video_device, NULL); +} + +void +purple_media_audio_init_recv(GstElement **recvbin, GstElement **recvlevel) +{ + GstElement *sink, *volume; + GstPad *pad, *ghost; + double output_volume = purple_prefs_get_int( + "/purple/media/audio/volume/output")/10.0; + + purple_debug_info("media", "purple_media_audio_init_recv\n"); + + *recvbin = gst_bin_new("pidginrecvaudiobin"); + sink = gst_element_factory_make("alsasink", "asink"); + g_object_set(G_OBJECT(sink), "sync", FALSE, NULL); + volume = gst_element_factory_make("volume", "purpleaudiooutputvolume"); + g_object_set(volume, "volume", output_volume, NULL); + *recvlevel = gst_element_factory_make("level", "recvlevel"); + gst_bin_add_many(GST_BIN(*recvbin), sink, volume, *recvlevel, NULL); + gst_element_link(*recvlevel, sink); + gst_element_link(volume, *recvlevel); + pad = gst_element_get_pad(volume, "sink"); + ghost = gst_ghost_pad_new("ghostsink", pad); + gst_element_add_pad(*recvbin, ghost); + g_object_set(G_OBJECT(*recvlevel), "message", TRUE, NULL); + + purple_debug_info("media", "purple_media_audio_init_recv end\n"); +} + +void +purple_media_video_init_recv(GstElement **recvbin) +{ + GstElement *sink; + GstPad *pad, *ghost; + + purple_debug_info("media", "purple_media_video_init_recv\n"); + + *recvbin = gst_bin_new("pidginrecvvideobin"); + sink = gst_element_factory_make("autovideosink", "purplevideosink"); + gst_bin_add(GST_BIN(*recvbin), sink); + pad = gst_element_get_pad(sink, "sink"); + ghost = gst_ghost_pad_new("ghostsink", pad); + gst_element_add_pad(*recvbin, ghost); + + purple_debug_info("media", "purple_media_video_init_recv end\n"); +} + +static void +purple_media_new_local_candidate_cb(FsStream *stream, + FsCandidate *local_candidate, + PurpleMediaSession *session) +{ + gchar *name; + FsParticipant *participant; + FsCandidate *candidate; + purple_debug_info("media", "got new local candidate: %s\n", local_candidate->foundation); + g_object_get(stream, "participant", &participant, NULL); + g_object_get(participant, "cname", &name, NULL); + g_object_unref(participant); + + purple_media_insert_local_candidate(session, name, fs_candidate_copy(local_candidate)); + + candidate = fs_candidate_copy(local_candidate); + g_signal_emit(session->media, purple_media_signals[NEW_CANDIDATE], + 0, session->id, name, candidate); + fs_candidate_destroy(candidate); + + g_free(name); +} + +static void +purple_media_candidates_prepared_cb(FsStream *stream, PurpleMediaSession *session) +{ + gchar *name; + FsParticipant *participant; + g_object_get(stream, "participant", &participant, NULL); + g_object_get(participant, "cname", &name, NULL); + g_object_unref(participant); + g_signal_emit(session->media, purple_media_signals[CANDIDATES_PREPARED], 0); + g_free(name); +} + +/* callback called when a pair of transport candidates (local and remote) + * has been established */ +static void +purple_media_candidate_pair_established_cb(FsStream *stream, + FsCandidate *native_candidate, + FsCandidate *remote_candidate, + PurpleMediaSession *session) +{ + FsCandidate *local = fs_candidate_copy(native_candidate); + FsCandidate *remote = fs_candidate_copy(remote_candidate); + + session->local_candidate = fs_candidate_copy(native_candidate); + session->remote_candidate = fs_candidate_copy(remote_candidate); + + purple_debug_info("media", "candidate pair established\n"); + g_signal_emit(session->media, purple_media_signals[CANDIDATE_PAIR], 0, + local, remote); + + fs_candidate_destroy(local); + fs_candidate_destroy(remote); +} + +static void +purple_media_src_pad_added_cb(FsStream *stream, GstPad *srcpad, + FsCodec *codec, PurpleMediaSession *session) +{ + GstPad *sinkpad = gst_element_get_static_pad(session->sink, "ghostsink"); + purple_debug_info("media", "connecting new src pad: %s\n", + gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK ? "success" : "failure"); +} + +static gchar * +purple_media_get_stun_pref_ip() +{ + const gchar *stun_pref = + purple_prefs_get_string("/purple/network/stun_server"); + struct hostent *host; + + if ((host = gethostbyname(stun_pref)) && host->h_addr) { + gchar *stun_ip = g_strdup_printf("%hhu.%hhu.%hhu.%hhu", + host->h_addr[0], host->h_addr[1], + host->h_addr[2], host->h_addr[3]); + purple_debug_info("media", "IP address for %s found: %s\n", + stun_pref, stun_ip); + return stun_ip; + } else { + purple_debug_info("media", "Unable to resolve %s IP address\n", + stun_pref); + return NULL; + } +} + +static gboolean +purple_media_add_stream_internal(PurpleMedia *media, const gchar *sess_id, + const gchar *who, FsMediaType type, + FsStreamDirection type_direction, + const gchar *transmitter, + guint num_params, GParameter *params) +{ + PurpleMediaSession *session = purple_media_get_session(media, sess_id); + FsParticipant *participant = NULL; + FsStream *stream = NULL; + FsStreamDirection *direction = NULL; + + if (!session) { + GError *err = NULL; + GList *codec_conf = NULL; + + session = g_new0(PurpleMediaSession, 1); + + session->session = fs_conference_new_session(media->priv->conference, type, &err); + + if (err != NULL) { + purple_debug_error("media", "Error creating session: %s\n", err->message); + g_error_free(err); + purple_conv_present_error(who, + purple_connection_get_account(purple_media_get_connection(media)), + _("Error creating session.")); + g_free(session); + return FALSE; + } + + /* + * The MPV codec didn't work for me. + * MPV may not work yet as of Farsight2 0.0.3 + */ + codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_DISABLE, + "MPV", FS_MEDIA_TYPE_VIDEO, 90000)); + + /* XXX: SPEEX has a latency of 5 or 6 seconds for me */ +#if 0 + /* SPEEX is added through the configuration */ + codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_ANY, + "SPEEX", FS_MEDIA_TYPE_AUDIO, 8000)); + codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_ANY, + "SPEEX", FS_MEDIA_TYPE_AUDIO, 16000)); +#endif + + fs_session_set_codec_preferences(session->session, codec_conf, NULL); + + /* + * Temporary fix to remove a 5-7 second delay before + * receiving the src-pad-added signal. + * Only works for one-to-one sessions. + * Specific to FsRtpSession. + */ + g_object_set(G_OBJECT(session->session), "no-rtcp-timeout", 0, NULL); + + + fs_codec_list_destroy(codec_conf); + + session->id = g_strdup(sess_id); + session->media = media; + session->type = purple_media_from_fs(type, type_direction); + + purple_media_add_session(media, session); + } + + if (!(participant = purple_media_add_participant(media, who))) { + purple_media_remove_session(media, session); + g_free(session); + return FALSE; + } + + stream = purple_media_session_get_stream(session, who); + + if (!stream) { + GError *err = NULL; + gchar *stun_ip = NULL; + + if (!strcmp(transmitter, "rawudp") && + (stun_ip = purple_media_get_stun_pref_ip())) { + GParameter *param = g_new0(GParameter, num_params+2); + memcpy(param, params, sizeof(GParameter) * num_params); + + param[num_params].name = "stun-ip"; + g_value_init(¶m[num_params].value, G_TYPE_STRING); + g_value_take_string(¶m[num_params].value, stun_ip); + + param[num_params+1].name = "stun-timeout"; + g_value_init(¶m[num_params+1].value, G_TYPE_UINT); + g_value_set_uint(¶m[num_params+1].value, 5); + + stream = fs_session_new_stream(session->session, + participant, type_direction, + transmitter, num_params+2, param, &err); + g_free(param); + g_free(stun_ip); + } else { + stream = fs_session_new_stream(session->session, + participant, type_direction, + transmitter, num_params, params, &err); + } + + if (err) { + purple_debug_error("media", "Error creating stream: %s\n", + err->message); + g_error_free(err); + g_object_unref(participant); + g_hash_table_remove(media->priv->participants, who); + purple_media_remove_session(media, session); + g_free(session); + return FALSE; + } + + purple_media_insert_stream(session, who, stream); + + /* callback for source pad added (new stream source ready) */ + g_signal_connect(G_OBJECT(stream), + "src-pad-added", G_CALLBACK(purple_media_src_pad_added_cb), session); + + } else if (*direction != type_direction) { + /* change direction */ + g_object_set(stream, "direction", type_direction, NULL); + } + + return TRUE; +} + +gboolean +purple_media_add_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who, + PurpleMediaSessionType type, + const gchar *transmitter, + guint num_params, GParameter *params) +{ + FsStreamDirection type_direction; + + if (type & PURPLE_MEDIA_AUDIO) { + type_direction = purple_media_to_fs_stream_direction(type & PURPLE_MEDIA_AUDIO); + + if (!purple_media_add_stream_internal(media, sess_id, who, + FS_MEDIA_TYPE_AUDIO, type_direction, + transmitter, num_params, params)) { + return FALSE; + } + } + else if (type & PURPLE_MEDIA_VIDEO) { + type_direction = purple_media_to_fs_stream_direction(type & PURPLE_MEDIA_VIDEO); + + if (!purple_media_add_stream_internal(media, sess_id, who, + FS_MEDIA_TYPE_VIDEO, type_direction, + transmitter, num_params, params)) { + return FALSE; + } + } + return TRUE; +} + +void +purple_media_remove_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who) +{ + +} + +PurpleMediaSessionType +purple_media_get_session_type(PurpleMedia *media, const gchar *sess_id) +{ + PurpleMediaSession *session = purple_media_get_session(media, sess_id); + return session->type; +} +/* XXX: Should wait until codecs-ready is TRUE before using this function */ +GList * +purple_media_get_local_codecs(PurpleMedia *media, const gchar *sess_id) +{ + GList *codecs; + g_object_get(G_OBJECT(purple_media_get_session(media, sess_id)->session), + "codecs", &codecs, NULL); + return codecs; +} + +GList * +purple_media_get_local_candidates(PurpleMedia *media, const gchar *sess_id, const gchar *name) +{ + PurpleMediaSession *session = purple_media_get_session(media, sess_id); + return fs_candidate_list_copy( + purple_media_session_get_local_candidates(session, name)); +} +/* XXX: Should wait until codecs-ready is TRUE before using this function */ +GList * +purple_media_get_negotiated_codecs(PurpleMedia *media, const gchar *sess_id) +{ + PurpleMediaSession *session = purple_media_get_session(media, sess_id); + GList *codec_intersection; + g_object_get(session->session, "codecs", &codec_intersection, NULL); + return codec_intersection; +} + +void +purple_media_add_remote_candidates(PurpleMedia *media, const gchar *sess_id, + const gchar *name, GList *remote_candidates) +{ + PurpleMediaSession *session = purple_media_get_session(media, sess_id); + FsStream *stream = purple_media_session_get_stream(session, name); + GError *err = NULL; + + fs_stream_set_remote_candidates(stream, remote_candidates, &err); + + if (err) { + purple_debug_error("media", "Error adding remote candidates: %s\n", + err->message); + g_error_free(err); + } +} + +FsCandidate * +purple_media_get_local_candidate(PurpleMedia *media, const gchar *sess_id, const gchar *name) +{ + PurpleMediaSession *session = purple_media_get_session(media, sess_id); + return session->local_candidate; +} + +FsCandidate * +purple_media_get_remote_candidate(PurpleMedia *media, const gchar *sess_id, const gchar *name) +{ + PurpleMediaSession *session = purple_media_get_session(media, sess_id); + return session->remote_candidate; +} + +gboolean +purple_media_set_remote_codecs(PurpleMedia *media, const gchar *sess_id, const gchar *name, GList *codecs) +{ + PurpleMediaSession *session = purple_media_get_session(media, sess_id); + FsStream *stream = purple_media_session_get_stream(session, name); + GError *err = NULL; + + fs_stream_set_remote_codecs(stream, codecs, &err); + + if (err) { + purple_debug_error("media", "Error setting remote codecs: %s\n", + err->message); + g_error_free(err); + return FALSE; + } + return TRUE; +} + +gboolean +purple_media_candidates_prepared(PurpleMedia *media, const gchar *name) +{ + GList *sessions = purple_media_get_session_names(media); + + for (; sessions; sessions = sessions->next) { + const gchar *session = sessions->data; + if (!purple_media_get_local_candidate(media, session, name) || + !purple_media_get_remote_candidate(media, session, name)) + return FALSE; + } + + return TRUE; +} + +gboolean +purple_media_set_send_codec(PurpleMedia *media, const gchar *sess_id, FsCodec *codec) +{ + PurpleMediaSession *session = purple_media_get_session(media, sess_id); + GError *err = NULL; + + fs_session_set_send_codec(session->session, codec, &err); + + if (err) { + purple_debug_error("media", "Error setting send codec\n"); + g_error_free(err); + return FALSE; + } + return TRUE; +} + +gboolean +purple_media_codecs_ready(PurpleMedia *media, const gchar *sess_id) +{ + PurpleMediaSession *session = purple_media_get_session(media, sess_id); + gboolean ret; + g_object_get(session->session, "codecs-ready", &ret, NULL); + return ret; +} + +void purple_media_mute(PurpleMedia *media, gboolean active) +{ + GList *sessions = g_hash_table_get_values(media->priv->sessions); + purple_debug_info("media", "Turning mute %s\n", active ? "on" : "off"); + + for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { + PurpleMediaSession *session = sessions->data; + if (session->type & PURPLE_MEDIA_SEND_AUDIO) { + GstElement *volume = gst_bin_get_by_name( + GST_BIN(session->src), + "purpleaudioinputvolume"); + g_object_set(volume, "mute", active, NULL); + } + } +} + +#endif /* USE_VV */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/media.h Sat Sep 27 04:44:17 2008 +0000 @@ -0,0 +1,501 @@ +/** + * @file media.h Media API + * @ingroup core + */ + +/* 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 __MEDIA_H_ +#define __MEDIA_H_ + +#ifdef USE_VV + +#include <gst/gst.h> +#include <gst/farsight/fs-stream.h> +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define PURPLE_TYPE_MEDIA (purple_media_get_type()) +#define PURPLE_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA, PurpleMedia)) +#define PURPLE_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA, PurpleMediaClass)) +#define PURPLE_IS_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA)) +#define PURPLE_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA)) +#define PURPLE_MEDIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA, PurpleMediaClass)) + +/** @copydoc _PurpleMedia */ +typedef struct _PurpleMedia PurpleMedia; +/** @copydoc _PurpleMediaClass */ +typedef struct _PurpleMediaClass PurpleMediaClass; +/** @copydoc _PurpleMediaPrivate */ +typedef struct _PurpleMediaPrivate PurpleMediaPrivate; +/** @copydoc _PurpleMediaSession */ +typedef struct _PurpleMediaSession PurpleMediaSession; + +#else + +typedef void PurpleMedia; + +#endif /* USE_VV */ + +/** Media session types */ +typedef enum { + PURPLE_MEDIA_NONE = 0, + PURPLE_MEDIA_RECV_AUDIO = 1 << 0, + PURPLE_MEDIA_SEND_AUDIO = 1 << 1, + PURPLE_MEDIA_RECV_VIDEO = 1 << 2, + PURPLE_MEDIA_SEND_VIDEO = 1 << 3, + PURPLE_MEDIA_AUDIO = PURPLE_MEDIA_RECV_AUDIO | PURPLE_MEDIA_SEND_AUDIO, + PURPLE_MEDIA_VIDEO = PURPLE_MEDIA_RECV_VIDEO | PURPLE_MEDIA_SEND_VIDEO +} PurpleMediaSessionType; + +#ifdef USE_VV + +/** The media class */ +struct _PurpleMediaClass +{ + GObjectClass parent_class; /**< The parent class. */ +}; + +/** The media class's private data */ +struct _PurpleMedia +{ + GObject parent; /**< The parent of this object. */ + PurpleMediaPrivate *priv; /**< The private data of this object. */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Gets the media class's GType + * + * @return The media class's GType. + */ +GType purple_media_get_type(void); + +/**************************************************************************/ +/** @name Media Utility Functions */ +/**************************************************************************/ +/*@{*/ + +/** + * Converts a PurpleMediaSessionType to an FsMediaType. + * + * @param type The type to derive FsMediaType from + * + * @return The FsMediaType derived from type + */ +FsMediaType purple_media_to_fs_media_type(PurpleMediaSessionType type); + +/** + * Converts a PurpleMediaSessionType to an FsStreamDirection. + * + * @param type The type to derive FsMediaType from + * + * @return The FsMediaDirection derived from type + */ +FsStreamDirection purple_media_to_fs_stream_direction(PurpleMediaSessionType type); + +/** + * Converts an FsMediaType and FsStreamDirection into a PurpleMediaSessionType. + * + * @param type The type used to construct PurpleMediaSessionType + * @param direction The direction used to construct PurpleMediaSessionType + * + * @return The PurpleMediaSessionType constructed + */ +PurpleMediaSessionType purple_media_from_fs(FsMediaType type, FsStreamDirection direction); + +/*@}*/ + +/** + * Combines all the separate session types into a single PurpleMediaSessionType. + * + * @param media The media session to retrieve session types from. + * + * @return Combined type. + */ +PurpleMediaSessionType purple_media_get_overall_type(PurpleMedia *media); + +/** + * Gets a list of session names. + * + * @param media The media session to retrieve session names from. + * + * @return GList of session names. + */ +GList *purple_media_get_session_names(PurpleMedia *media); + +/** + * Gets an audio and video source and sink from the media session. + * + * Retrieves the first of each element in the media session. + * + * @param media The media session to retreive the sources and sinks from. + * @param audio_src Set to the audio source. + * @param audio_sink Set to the audio sink. + * @param video_src Set to the video source. + * @param video_sink Set to the video sink. + */ +void purple_media_get_elements(PurpleMedia *media, + GstElement **audio_src, GstElement **audio_sink, + GstElement **video_src, GstElement **video_sink); + +/** + * Sets the source on a session. + * + * @param media The media object the session is in. + * @param sess_id The session id of the session to set the source on. + * @param src The source to set the session source to. + */ +void purple_media_set_src(PurpleMedia *media, const gchar *sess_id, GstElement *src); + +/** + * Sets the sink on a session. + * + * @param media The media object the session is in. + * @param sess_id The session id of the session to set the sink on. + * @param sink The source to set the session sink to. + */ +void purple_media_set_sink(PurpleMedia *media, const gchar *sess_id, GstElement *sink); + +/** + * Gets the source from a session + * + * @param media The media object the session is in. + * @param sess_id The session id of the session to get the source from. + * + * @return The source retrieved. + */ +GstElement *purple_media_get_src(PurpleMedia *media, const gchar *sess_id); + +/** + * Gets the sink from a session + * + * @param media The media object the session is in. + * @param sess_id The session id of the session to get the source from. + * + * @return The sink retrieved. + */ +GstElement *purple_media_get_sink(PurpleMedia *media, const gchar *sess_id); + +/** + * Gets the pipeline from the media session. + * + * @param media The media session to retrieve the pipeline from. + * + * @return The pipeline retrieved. + */ +GstElement *purple_media_get_pipeline(PurpleMedia *media); + +/** + * Gets the connection the media session is associated with. + * + * @param media The media object to retrieve the connection from. + * + * @return The retreived connection. + */ +PurpleConnection *purple_media_get_connection(PurpleMedia *media); + +/** + * Gets the screenname of the remote user. + * + * @param media The media object to retrieve the remote user from. + * + * @return The retrieved screenname. + */ +char *purple_media_get_screenname(PurpleMedia *media); + +/** + * Set the media session to the ready state. + * + * @param media The media object to set the state on. + */ +void purple_media_ready(PurpleMedia *media); + +/** + * Set the media session to the wait state. + * + * @param media The media object to set the state on. + */ +void purple_media_wait(PurpleMedia *media); + +/** + * Set the media session to the accepted state. + * + * @param media The media object to set the state on. + */ +void purple_media_accept(PurpleMedia *media); + +/** + * Set the media session to the rejected state. + * + * @param media The media object to set the state on. + */ +void purple_media_reject(PurpleMedia *media); + +/** + * Set the media session to the hangup state. + * + * @param media The media object to set the state on. + */ +void purple_media_hangup(PurpleMedia *media); + +/** + * Set the media session to the got_request state. + * + * @param media The media object to set the state on. + */ +void purple_media_got_request(PurpleMedia *media); + +/** + * Set the media session to the got_hangup state. + * + * @param media The media object to set the state on. + */ +void purple_media_got_hangup(PurpleMedia *media); + +/** + * Set the media session to the got_accept state. + * + * @param media The media object to set the state on. + */ +void purple_media_got_accept(PurpleMedia *media); + +/** + * Enumerates a list of devices. + * + * @param plugin The name of the GStreamer plugin from which to enumerate devices. + * + * @return The list of enumerated devices. + */ +GList *purple_media_get_devices(const gchar *plugin); + +/** + * Gets the device the plugin is currently set to. + * + * @param element The plugin to retrieve the device from. + * + * @return The device retrieved. + */ +gchar *purple_media_element_get_device(GstElement *element); + +/** + * Creates a default audio source. + * + * @param sendbin Set to the newly created audio source. + * @param sendlevel Set to the newly created level within the audio source. + */ +void purple_media_audio_init_src(GstElement **sendbin, + GstElement **sendlevel); + +/** + * Creates a default video source. + * + * @param sendbin Set to the newly created video source. + */ +void purple_media_video_init_src(GstElement **sendbin); + +/** + * Creates a default audio sink. + * + * @param recvbin Set to the newly created audio sink. + * @param recvlevel Set to the newly created level within the audio sink. + */ +void purple_media_audio_init_recv(GstElement **recvbin, GstElement **recvlevel); + +/** + * Creates a default video sink. + * + * @param sendbin Set to the newly created video sink. + */ +void purple_media_video_init_recv(GstElement **sendbin); + +/** + * Adds a stream to a session. + * + * It only adds a stream to one audio session or video session as + * the @c sess_id must be unique between sessions. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to add the stream to. + * @param who The name of the remote user to add the stream for. + * @param type The type of stream to create. + * @param transmitter The transmitter to use for the stream. + * @param num_params The number of parameters to pass to Farsight. + * @param params The parameters to pass to Farsight. + * + * @return @c TRUE The stream was added successfully, @c FALSE otherwise. + */ +gboolean purple_media_add_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who, + PurpleMediaSessionType type, const gchar *transmitter, + guint num_params, GParameter *params); + +/** + * Removes a stream from a session. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to remove the stream from. + * @param who The name of the remote user to remove the stream from. + */ +void purple_media_remove_stream(PurpleMedia *media, const gchar *sess_id, const gchar *who); + +/** + * Gets the session type from a session + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to get the type from. + * + * @return The retreived session type. + */ +PurpleMediaSessionType purple_media_get_session_type(PurpleMedia *media, const gchar *sess_id); + +/** + * Gets the negotiated codecs from a session. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to get the negotiated codecs from. + * + * @return The retreieved codecs. + */ +GList *purple_media_get_negotiated_codecs(PurpleMedia *media, const gchar *sess_id); + +/** + * Gets the local codecs from a session. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to get the local codecs from. + * + * @return The retreieved codecs. + */ +GList *purple_media_get_local_codecs(PurpleMedia *media, const gchar *sess_id); + +/** + * Adds remote candidates to the stream. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session find the stream in. + * @param name The name of the remote user to add the candidates for. + * @param remote_candidates The remote candidates to add. + */ +void purple_media_add_remote_candidates(PurpleMedia *media, + const gchar *sess_id, + const gchar *name, + GList *remote_candidates); + +/** + * Gets the local candidates from a stream. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to find the stream in. + * @param name The name of the remote user to get the candidates from. + */ +GList *purple_media_get_local_candidates(PurpleMedia *media, + const gchar *sess_id, + const gchar *name); + +/** + * Gets the active local candidate for the stream. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to find the stream in. + * @param name The name of the remote user to get the active candidate from. + * + * @return The active candidate retrieved. + */ +FsCandidate *purple_media_get_local_candidate(PurpleMedia *media, const gchar *sess_id, const gchar *name); + +/** + * Gets the active remote candidate for the stream. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to find the stream in. + * @param name The name of the remote user to get the remote candidate from. + * + * @return The remote candidate retrieved. + */ +FsCandidate *purple_media_get_remote_candidate(PurpleMedia *media, const gchar *sess_id, const gchar *name); + +/** + * Gets remote candidates from the stream. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session find the stream in. + * @param name The name of the remote user to get the candidates from. + * + * @return @c TRUE The codecs were set successfully, or @c FALSE otherwise. + */ +gboolean purple_media_set_remote_codecs(PurpleMedia *media, const gchar *sess_id, + const gchar *name, GList *codecs); + +/** + * Returns whether or not the candidates for a remote user are prepared + * + * @param media The media object to find the remote user in. + * @param name The remote user to check for. + * + * @return @c TRUE All streams for the remote user have candidates prepared, @c FALSE otherwise. + */ +gboolean purple_media_candidates_prepared(PurpleMedia *media, const gchar *name); + +/** + * Sets the send codec for the a session. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to set the codec for. + * @param codec The codec to set the session to stream. + * + * @return @c TRUE The codec was successfully changed, or @c FALSE otherwise. + */ +gboolean purple_media_set_send_codec(PurpleMedia *media, const gchar *sess_id, FsCodec *codec); + +/** + * Gets whether a session's codecs are ready to be used. + * + * @param media The media object to find the session in. + * @param sess_id The session id of the session to check. + * + * @return @c TRUE The codecs are ready, or @c FALSE otherwise. + */ +gboolean purple_media_codecs_ready(PurpleMedia *media, const gchar *sess_id); + +/** + * Mutes or unmutes all the audio local audio sources. + * + * @param media The media object to mute or unmute + * @param active @c TRUE to mutes all of the local audio sources, or @c FALSE to unmute. + */ +void purple_media_mute(PurpleMedia *media, gboolean active); + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* USE_VV */ + + +#endif /* __MEDIA_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/mediamanager.c Sat Sep 27 04:44:17 2008 +0000 @@ -0,0 +1,202 @@ +/** + * @file mediamanager.c Media Manager API + * @ingroup core + */ + +/* 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 "connection.h" +#include "debug.h" +#include "marshallers.h" +#include "mediamanager.h" +#include "media.h" + +#ifdef USE_VV + +#include <gst/farsight/fs-conference-iface.h> + +struct _PurpleMediaManagerPrivate +{ + GList *medias; +}; + +#define PURPLE_MEDIA_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManagerPrivate)) + +static void purple_media_manager_class_init (PurpleMediaManagerClass *klass); +static void purple_media_manager_init (PurpleMediaManager *media); +static void purple_media_manager_finalize (GObject *object); + +static GObjectClass *parent_class = NULL; + + + +enum { + INIT_MEDIA, + LAST_SIGNAL +}; +static guint purple_media_manager_signals[LAST_SIGNAL] = {0}; + +enum { + PROP_0, + PROP_FARSIGHT_SESSION, + PROP_NAME, + PROP_CONNECTION, + PROP_MIC_ELEMENT, + PROP_SPEAKER_ELEMENT, +}; + +GType +purple_media_manager_get_type() +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(PurpleMediaManagerClass), + NULL, + NULL, + (GClassInitFunc) purple_media_manager_class_init, + NULL, + NULL, + sizeof(PurpleMediaManager), + 0, + (GInstanceInitFunc) purple_media_manager_init, + NULL + }; + type = g_type_register_static(G_TYPE_OBJECT, "PurpleMediaManager", &info, 0); + } + return type; +} + + +static void +purple_media_manager_class_init (PurpleMediaManagerClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass*)klass; + parent_class = g_type_class_peek_parent(klass); + + gobject_class->finalize = purple_media_manager_finalize; + + purple_media_manager_signals[INIT_MEDIA] = g_signal_new ("init-media", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + purple_smarshal_BOOLEAN__OBJECT, + G_TYPE_BOOLEAN, 1, PURPLE_TYPE_MEDIA); + g_type_class_add_private(klass, sizeof(PurpleMediaManagerPrivate)); +} + +static void +purple_media_manager_init (PurpleMediaManager *media) +{ + media->priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media); + media->priv->medias = NULL; +} + +static void +purple_media_manager_finalize (GObject *media) +{ + PurpleMediaManagerPrivate *priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media); + for (; priv->medias; priv->medias = + g_list_delete_link(priv->medias, priv->medias)) { + g_object_unref(priv->medias->data); + } + parent_class->finalize(media); +} + +PurpleMediaManager * +purple_media_manager_get() +{ + static PurpleMediaManager *manager = NULL; + + if (manager == NULL) + manager = PURPLE_MEDIA_MANAGER(g_object_new(purple_media_manager_get_type(), NULL)); + return manager; +} + +PurpleMedia * +purple_media_manager_create_media(PurpleMediaManager *manager, + PurpleConnection *gc, + const char *conference_type, + const char *remote_user) +{ + PurpleMedia *media; + FsConference *conference = FS_CONFERENCE(gst_element_factory_make(conference_type, NULL)); + GstStateChangeReturn ret; + gboolean signal_ret; + + if (conference == NULL) { + purple_conv_present_error(remote_user, + purple_connection_get_account(gc), + _("Error creating conference.")); + purple_debug_error("media", "Conference == NULL\n"); + return NULL; + } + + media = PURPLE_MEDIA(g_object_new(purple_media_get_type(), + "screenname", remote_user, + "connection", gc, + "farsight-conference", conference, + NULL)); + + ret = gst_element_set_state(purple_media_get_pipeline(media), GST_STATE_PLAYING); + + if (ret == GST_STATE_CHANGE_FAILURE) { + purple_conv_present_error(remote_user, + purple_connection_get_account(gc), + _("Error creating conference.")); + purple_debug_error("media", "Failed to start conference.\n"); + g_object_unref(media); + return NULL; + } + + g_signal_emit(manager, purple_media_manager_signals[INIT_MEDIA], 0, + media, &signal_ret); + + if (signal_ret == FALSE) { + g_object_unref(media); + return NULL; + } + + manager->priv->medias = g_list_append(manager->priv->medias, media); + return media; +} + +GList * +purple_media_manager_get_media(PurpleMediaManager *manager) +{ + return manager->priv->medias; +} + +void +purple_media_manager_remove_media(PurpleMediaManager *manager, + PurpleMedia *media) +{ + GList *list = g_list_find(manager->priv->medias, media); + if (list) + manager->priv->medias = + g_list_delete_link(manager->priv->medias, list); +} + +#endif /* USE_VV */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/mediamanager.h Sat Sep 27 04:44:17 2008 +0000 @@ -0,0 +1,135 @@ +/** + * @file mediamanager.h Media Manager API + * @ingroup core + */ + +/* 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 __MEDIA_MANAGER_H_ +#define __MEDIA_MANAGER_H_ + +#ifdef USE_VV + +#include <glib.h> +#include <glib-object.h> + +#include "connection.h" +#include "media.h" + +G_BEGIN_DECLS + +#define PURPLE_TYPE_MEDIA_MANAGER (purple_media_manager_get_type()) +#define PURPLE_MEDIA_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManager)) +#define PURPLE_MEDIA_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManagerClass)) +#define PURPLE_IS_MEDIA_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_MEDIA_MANAGER)) +#define PURPLE_IS_MEDIA_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA_MANAGER)) +#define PURPLE_MEDIA_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManagerClass)) + +/** @copydoc _PurpleMediaManager */ +typedef struct _PurpleMediaManager PurpleMediaManager; +/** @copydoc _PurpleMediaManagerClass */ +typedef struct _PurpleMediaManagerClass PurpleMediaManagerClass; +/** @copydoc _PurpleMediaManagerPrivate */ +typedef struct _PurpleMediaManagerPrivate PurpleMediaManagerPrivate; + +/** The media manager class. */ +struct _PurpleMediaManagerClass +{ + GObjectClass parent_class; /**< The parent class. */ +}; + +/** The media manager's data. */ +struct _PurpleMediaManager +{ + GObject parent; /**< The parent of this manager. */ + PurpleMediaManagerPrivate *priv; /**< Private data for the manager. */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @cname Media Manager API */ +/**************************************************************************/ +/*@{*/ + +/** + * Gets the media manager's GType. + * + * @return The media manager's GType. + */ +GType purple_media_manager_get_type(void); + +/** + * Gets the "global" media manager object. It's created if it doesn't already exist. + * + * @return The "global" instance of the media manager object. + */ +PurpleMediaManager *purple_media_manager_get(void); + +/** + * Creates a media session. + * + * @param manager The media manager to create the session under. + * @param gc The connection to create the session on. + * @param conference_type The conference type to feed into Farsight2. + * @param remote_user The remote user to initiate the session with. + * + * @return A newly created media session. + */ +PurpleMedia *purple_media_manager_create_media(PurpleMediaManager *manager, + PurpleConnection *gc, + const char *conference_type, + const char *remote_user); + +/** + * Gets all of the media sessions. + * + * @param manager The media manager to get all of the sessions from. + * + * @return A list of all the media sessions. + */ +GList *purple_media_manager_get_media(PurpleMediaManager *manager); + +/** + * Removes a media session from the media manager. + * + * @param manager The media manager to remove the media session from. + * @param media The media session to remove. + */ +void +purple_media_manager_remove_media(PurpleMediaManager *manager, + PurpleMedia *media); + +/*}@*/ + +#ifdef __cplusplus +} +#endif + +G_END_DECLS + +#endif /* USE_VV */ + + +#endif /* __MEDIA_MANAGER_H_ */
--- a/libpurple/plugins/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/plugins/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -140,6 +140,9 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) \ $(GLIB_CFLAGS) \ $(PLUGIN_CFLAGS) \ $(DBUS_CFLAGS)
--- a/libpurple/plugins/perl/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/plugins/perl/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -5,7 +5,7 @@ plugin_LTLIBRARIES = perl.la perl_la_LDFLAGS = -module -avoid-version -perl_la_LIBADD = $(GLIB_LIBS) $(PERL_LIBS) +perl_la_LIBADD = $(GLIB_LIBS) $(PERL_LIBS) $(FARSIGHT_LIBS) perl_la_SOURCES = \ perl.c \ perl-common.c \ @@ -167,4 +167,5 @@ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) \ $(PLUGIN_CFLAGS) \ - $(PERL_CFLAGS) + $(PERL_CFLAGS) \ + $(FARSIGHT_CFLAGS)
--- a/libpurple/plugins/ssl/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/plugins/ssl/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -31,6 +31,9 @@ -I$(top_builddir)/libpurple \ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) \ $(PLUGIN_CFLAGS) ssl_gnutls_la_CFLAGS = $(AM_CPPFLAGS) $(GNUTLS_CFLAGS)
--- a/libpurple/plugins/tcl/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/plugins/tcl/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -7,7 +7,7 @@ tcl_la_SOURCES = tcl.c tcl_glib.c tcl_glib.h tcl_cmds.c tcl_signals.c tcl_purple.h \ tcl_ref.c tcl_cmd.c -tcl_la_LIBADD = $(GLIB_LIBS) $(TCL_LIBS) $(TK_LIBS) +tcl_la_LIBADD = $(GLIB_LIBS) $(TCL_LIBS) $(TK_LIBS) $(FARSIGHT_LIBS) EXTRA_DIST = signal-test.tcl Makefile.mingw @@ -18,5 +18,6 @@ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) \ $(PLUGIN_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(TK_CFLAGS) \ $(TCL_CFLAGS)
--- a/libpurple/protocols/bonjour/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/bonjour/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -51,5 +51,12 @@ $(GLIB_CFLAGS) \ $(DEBUG_CFLAGS) \ $(LIBXML_CFLAGS) \ - $(AVAHI_CFLAGS) + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) +#if MDNS_AVAHI +# AM_CPPFLAGS += $(AVAHI_CFLAGS) +#else +#endif +
--- a/libpurple/protocols/bonjour/bonjour.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Sat Sep 27 04:44:17 2008 +0000 @@ -499,13 +499,13 @@ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ - - /* padding */ - NULL, - NULL, - NULL, - sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ + sizeof(PurplePluginProtocolInfo), /* struct_size */ + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = @@ -725,3 +725,4 @@ } PURPLE_INIT_PLUGIN(bonjour, init_plugin, info); +
--- a/libpurple/protocols/gg/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/gg/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -75,5 +75,8 @@ -I$(top_builddir)/libpurple \ $(INTGG_CFLAGS) \ $(GLIB_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) \ $(DEBUG_CFLAGS)
--- a/libpurple/protocols/gg/gg.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/gg/gg.c Sat Sep 27 04:44:17 2008 +0000 @@ -2165,13 +2165,13 @@ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ - - /* padding */ - NULL, - NULL, - NULL, + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; /* }}} */ @@ -2258,3 +2258,4 @@ PURPLE_INIT_PLUGIN(gg, init_plugin, info); /* vim: set ts=8 sts=0 sw=8 noet: */ +
--- a/libpurple/protocols/irc/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/irc/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -32,4 +32,6 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ $(DEBUG_CFLAGS)
--- a/libpurple/protocols/irc/irc.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/irc/irc.c Sat Sep 27 04:44:17 2008 +0000 @@ -917,13 +917,13 @@ NULL, /* whiteboard_prpl_ops */ irc_send_raw, /* send_raw */ NULL, /* roomlist_room_serialize */ - - /* padding */ - NULL, - NULL, - NULL, - sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ + sizeof(PurplePluginProtocolInfo), /* struct_size */ + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static gboolean load_plugin (PurplePlugin *plugin) {
--- a/libpurple/protocols/jabber/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/jabber/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -21,6 +21,8 @@ iq.h \ jabber.c \ jabber.h \ + jingle.c \ + jingle.h \ jutil.c \ jutil.h \ message.c \ @@ -71,7 +73,7 @@ pkg_LTLIBRARIES = libjabber.la libxmpp.la noinst_LIBRARIES = -libjabber_la_SOURCES = $(JABBERSOURCES) +libjabber_la_SOURCES = $(JABBERSOURCES) libjabber_la_LIBADD = $(GLIB_LIBS) $(SASL_LIBS) $(LIBXML_LIBS) libxmpp_la_SOURCES = libxmpp.c @@ -84,4 +86,6 @@ -I$(top_builddir)/libpurple \ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ $(LIBXML_CFLAGS)
--- a/libpurple/protocols/jabber/buddy.h Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/jabber/buddy.h Sat Sep 27 04:44:17 2008 +0000 @@ -97,6 +97,8 @@ const char *jabber_buddy_get_status_msg(JabberBuddy *jb); void jabber_buddy_get_info(PurpleConnection *gc, const char *who); +gboolean jabber_buddy_has_capability(JabberBuddy *jb, const gchar *cap); + GList *jabber_blist_node_menu(PurpleBlistNode *node); void jabber_set_info(PurpleConnection *gc, const char *info);
--- a/libpurple/protocols/jabber/caps.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/jabber/caps.c Sat Sep 27 04:44:17 2008 +0000 @@ -27,6 +27,7 @@ #include "util.h" #include "iq.h" + #define JABBER_CAPS_FILENAME "xmpp-caps.xml" static GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsValue */
--- a/libpurple/protocols/jabber/disco.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/jabber/disco.c Sat Sep 27 04:44:17 2008 +0000 @@ -88,7 +88,7 @@ void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) { const char *from = xmlnode_get_attrib(packet, "from"); const char *type = xmlnode_get_attrib(packet, "type"); - + if(!from || !type) return; @@ -114,7 +114,7 @@ if(node) xmlnode_set_attrib(query, "node", node); - + if(!node || !strcmp(node, CAPS0115_NODE "#" VERSION)) { identity = xmlnode_new_child(query, "identity"); xmlnode_set_attrib(identity, "category", "client"); @@ -151,6 +151,17 @@ SUPPORT_FEATURE(feat->namespace); } } +#ifdef USE_VV + } else if (node && !strcmp(node, CAPS0115_NODE "#voice-v1")) { + SUPPORT_FEATURE("http://www.google.com/session"); + SUPPORT_FEATURE("http://www.google.com/transport/p2p"); + SUPPORT_FEATURE("http://www.google.com/transport/raw-udp"); + SUPPORT_FEATURE("http://www.google.com/session/phone"); + SUPPORT_FEATURE("urn:xmpp:tmp:jingle"); + SUPPORT_FEATURE("urn:xmpp:tmp:jingle:apps:rtp#audio"); + SUPPORT_FEATURE("urn:xmpp:tmp:jingle:apps:rtp#video"); + SUPPORT_FEATURE("urn:xmpp:tmp:jingle:transports:ice-udp"); +#endif } else { const char *ext = NULL; unsigned pos;
--- a/libpurple/protocols/jabber/google.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/jabber/google.c Sat Sep 27 04:44:17 2008 +0000 @@ -20,6 +20,7 @@ #include "internal.h" #include "debug.h" +#include "mediamanager.h" #include "util.h" #include "privacy.h" @@ -29,6 +30,358 @@ #include "presence.h" #include "iq.h" +#ifdef USE_VV +#include <gst/farsight/fs-conference-iface.h> + +typedef struct { + char *id; + char *initiator; +} GoogleSessionId; + +typedef enum { + UNINIT, + SENT_INITIATE, + RECEIVED_INITIATE, + IN_PRORESS, + TERMINATED +} GoogleSessionState; + +typedef struct { + GoogleSessionId id; + GoogleSessionState state; + PurpleMedia *media; + JabberStream *js; + char *remote_jid; +} GoogleSession; + +GHashTable *sessions = NULL; + +static guint +google_session_id_hash(gconstpointer key) +{ + GoogleSessionId *id = (GoogleSessionId*)key; + + guint id_hash = g_str_hash(id->id); + guint init_hash = g_str_hash(id->initiator); + + return 23 * id_hash + init_hash; +} + +static gboolean +google_session_id_equal(gconstpointer a, gconstpointer b) +{ + GoogleSessionId *c = (GoogleSessionId*)a; + GoogleSessionId *d = (GoogleSessionId*)b; + + return !strcmp(c->id, d->id) && !strcmp(c->initiator, d->initiator); +} + +static void +google_session_destroy(GoogleSession *session) +{ + g_hash_table_remove(sessions, &(session->id)); + g_free(session->id.id); + g_free(session->id.initiator); + g_free(session->remote_jid); + g_object_unref(session->media); + g_free(session); +} + +static xmlnode * +google_session_create_xmlnode(GoogleSession *session, const char *type) +{ + xmlnode *node = xmlnode_new("session"); + xmlnode_set_namespace(node, "http://www.google.com/session"); + xmlnode_set_attrib(node, "id", session->id.id); + xmlnode_set_attrib(node, "initiator", session->id.initiator); + xmlnode_set_attrib(node, "type", type); + return node; +} + +static void +google_session_send_accept(GoogleSession *session) +{ + xmlnode *sess, *desc, *payload; + GList *codecs = purple_media_get_negotiated_codecs(session->media, "google-voice"); + JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET); + + xmlnode_set_attrib(iq->node, "to", session->remote_jid); + sess = google_session_create_xmlnode(session, "accept"); + xmlnode_insert_child(iq->node, sess); + desc = xmlnode_new_child(sess, "description"); + xmlnode_set_namespace(desc, "http://www.google.com/session/phone"); + + for (;codecs; codecs = codecs->next) { + FsCodec *codec = (FsCodec*)codecs->data; + char id[8], clockrate[10]; + payload = xmlnode_new_child(desc, "payload-type"); + g_snprintf(id, sizeof(id), "%d", codec->id); + g_snprintf(clockrate, sizeof(clockrate), "%d", codec->clock_rate); + xmlnode_set_attrib(payload, "name", codec->encoding_name); + xmlnode_set_attrib(payload, "id", id); + xmlnode_set_attrib(payload, "clockrate", clockrate); + } + + fs_codec_list_destroy(codecs); + jabber_iq_send(iq); + gst_element_set_state(purple_media_get_pipeline(session->media), GST_STATE_PLAYING); +} + +static void +google_session_send_terminate(GoogleSession *session) +{ + xmlnode *sess; + JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET); + + xmlnode_set_attrib(iq->node, "to", session->remote_jid); + sess = google_session_create_xmlnode(session, "terminate"); + xmlnode_insert_child(iq->node, sess); + + jabber_iq_send(iq); + google_session_destroy(session); +} + +static void +google_session_send_reject(GoogleSession *session) +{ + xmlnode *sess; + JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET); + + xmlnode_set_attrib(iq->node, "to", session->remote_jid); + sess = google_session_create_xmlnode(session, "reject"); + xmlnode_insert_child(iq->node, sess); + + jabber_iq_send(iq); + google_session_destroy(session); +} + + +static void +google_session_candidates_prepared (PurpleMedia *media, GoogleSession *session) +{ + JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET); + GList *candidates = purple_media_get_local_candidates(session->media, "google-voice", + session->remote_jid); + FsCandidate *transport; + xmlnode *sess; + xmlnode *candidate; + sess = google_session_create_xmlnode(session, "candidates"); + xmlnode_insert_child(iq->node, sess); + xmlnode_set_attrib(iq->node, "to", session->remote_jid); + + for (;candidates;candidates = candidates->next) { + char port[8]; + char pref[8]; + transport = (FsCandidate*)(candidates->data); + + if (!strcmp(transport->ip, "127.0.0.1")) + continue; + + candidate = xmlnode_new("candidate"); + + g_snprintf(port, sizeof(port), "%d", transport->port); + g_snprintf(pref, sizeof(pref), "%d", transport->priority); + + xmlnode_set_attrib(candidate, "address", transport->ip); + xmlnode_set_attrib(candidate, "port", port); + xmlnode_set_attrib(candidate, "name", "rtp"); + xmlnode_set_attrib(candidate, "username", transport->username); + xmlnode_set_attrib(candidate, "password", transport->password); + xmlnode_set_attrib(candidate, "preference", pref); + xmlnode_set_attrib(candidate, "protocol", transport->proto == FS_NETWORK_PROTOCOL_UDP ? "udp" : "tcp"); + xmlnode_set_attrib(candidate, "type", transport->type == FS_CANDIDATE_TYPE_HOST ? "local" : + transport->type == FS_CANDIDATE_TYPE_PRFLX ? "stun" : + transport->type == FS_CANDIDATE_TYPE_RELAY ? "relay" : NULL); + xmlnode_set_attrib(candidate, "generation", "0"); + xmlnode_set_attrib(candidate, "network", "0"); + xmlnode_insert_child(sess, candidate); + + } + jabber_iq_send(iq); +} + +static void +google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess) +{ + JabberIq *result; + GList *codecs = NULL; + xmlnode *desc_element, *codec_element; + FsCodec *codec; + const char *id, *encoding_name, *clock_rate; + + if (session->state != UNINIT) { + purple_debug_error("jabber", "Received initiate for active session.\n"); + return; + } + + session->media = purple_media_manager_create_media(purple_media_manager_get(), js->gc, + "fsrtpconference", session->remote_jid); + + /* "rawudp" will need to be changed to "nice" when libnice is finished */ + /* GTalk will require the NICE_COMPATIBILITY_GOOGLE param */ + purple_media_add_stream(session->media, "google-voice", session->remote_jid, + PURPLE_MEDIA_AUDIO, "rawudp", 0, NULL); + + desc_element = xmlnode_get_child(sess, "description"); + + for (codec_element = xmlnode_get_child(desc_element, "payload-type"); + codec_element; + codec_element = xmlnode_get_next_twin(codec_element)) { + encoding_name = xmlnode_get_attrib(codec_element, "name"); + id = xmlnode_get_attrib(codec_element, "id"); + clock_rate = xmlnode_get_attrib(codec_element, "clockrate"); + + codec = fs_codec_new(atoi(id), encoding_name, FS_MEDIA_TYPE_AUDIO, + clock_rate ? atoi(clock_rate) : 0); + codecs = g_list_append(codecs, codec); + } + + purple_media_set_remote_codecs(session->media, "google-voice", session->remote_jid, codecs); + + g_signal_connect_swapped(G_OBJECT(session->media), "accepted", + G_CALLBACK(google_session_send_accept), session); + g_signal_connect_swapped(G_OBJECT(session->media), "reject", + G_CALLBACK(google_session_send_reject), session); + g_signal_connect_swapped(G_OBJECT(session->media), "hangup", + G_CALLBACK(google_session_send_terminate), session); + g_signal_connect(G_OBJECT(session->media), "candidates-prepared", + G_CALLBACK(google_session_candidates_prepared), session); + purple_media_ready(session->media); + + fs_codec_list_destroy(codecs); + + result = jabber_iq_new(js, JABBER_IQ_RESULT); + jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); + xmlnode_set_attrib(result->node, "to", session->remote_jid); + jabber_iq_send(result); +} + +static void +google_session_handle_candidates(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess) +{ + JabberIq *result; + GList *list = NULL; + xmlnode *cand; + static int name = 0; + char n[4]; + + for (cand = xmlnode_get_child(sess, "candidate"); cand; cand = xmlnode_get_next_twin(cand)) { + FsCandidate *info; + g_snprintf(n, sizeof(n), "S%d", name++); + info = fs_candidate_new(n, FS_COMPONENT_RTP, !strcmp(xmlnode_get_attrib(cand, "type"), "local") ? + FS_CANDIDATE_TYPE_HOST : + !strcmp(xmlnode_get_attrib(cand, "type"), "stun") ? + FS_CANDIDATE_TYPE_PRFLX : + !strcmp(xmlnode_get_attrib(cand, "type"), "relay") ? + FS_CANDIDATE_TYPE_RELAY : FS_CANDIDATE_TYPE_HOST, + !strcmp(xmlnode_get_attrib(cand, "protocol"),"udp") ? + FS_NETWORK_PROTOCOL_UDP : FS_NETWORK_PROTOCOL_TCP, + xmlnode_get_attrib(cand, "address"), atoi(xmlnode_get_attrib(cand, "port"))); + + info->username = g_strdup(xmlnode_get_attrib(cand, "username")); + info->password = g_strdup(xmlnode_get_attrib(cand, "password")); + + list = g_list_append(list, info); + } + + purple_media_add_remote_candidates(session->media, "google-voice", session->remote_jid, list); + fs_candidate_list_destroy(list); + + result = jabber_iq_new(js, JABBER_IQ_RESULT); + jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); + xmlnode_set_attrib(result->node, "to", session->remote_jid); + jabber_iq_send(result); +} + +static void +google_session_handle_reject(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess) +{ + purple_media_got_hangup(session->media); + + google_session_destroy(session); +} + +static void +google_session_handle_terminate(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess) +{ + purple_media_got_hangup(session->media); + + google_session_destroy(session); +} + +static void +google_session_parse_iq(JabberStream *js, GoogleSession *session, xmlnode *packet) +{ + xmlnode *sess = xmlnode_get_child(packet, "session"); + const char *type = xmlnode_get_attrib(sess, "type"); + + if (!strcmp(type, "initiate")) { + google_session_handle_initiate(js, session, packet, sess); + } else if (!strcmp(type, "accept")) { + } else if (!strcmp(type, "reject")) { + google_session_handle_reject(js, session, packet, sess); + } else if (!strcmp(type, "terminate")) { + google_session_handle_terminate(js, session, packet, sess); + } else if (!strcmp(type, "candidates")) { + google_session_handle_candidates(js, session, packet, sess); + } +} +#endif /* USE_VV */ + +void +jabber_google_session_parse(JabberStream *js, xmlnode *packet) +{ +#ifdef USE_VV + GoogleSession *session; + GoogleSessionId id; + + xmlnode *session_node; + xmlnode *desc_node; + + if (strcmp(xmlnode_get_attrib(packet, "type"), "set")) + return; + + session_node = xmlnode_get_child(packet, "session"); + if (!session_node) + return; + + id.id = (gchar*)xmlnode_get_attrib(session_node, "id"); + if (!id.id) + return; + + id.initiator = (gchar*)xmlnode_get_attrib(session_node, "initiator"); + if (!id.initiator) + return; + + if (sessions == NULL) + sessions = g_hash_table_new(google_session_id_hash, google_session_id_equal); + session = (GoogleSession*)g_hash_table_lookup(sessions, &id); + + if (session) { + google_session_parse_iq(js, session, packet); + return; + } + + /* If the session doesn't exist, this has to be an initiate message */ + if (strcmp(xmlnode_get_attrib(session_node, "type"), "initiate")) + return; + desc_node = xmlnode_get_child(session_node, "description"); + if (!desc_node) + return; + session = g_new0(GoogleSession, 1); + session->id.id = g_strdup(id.id); + session->id.initiator = g_strdup(id.initiator); + session->state = UNINIT; + session->js = js; + session->remote_jid = g_strdup(session->id.initiator); + g_hash_table_insert(sessions, &(session->id), session); + + google_session_parse_iq(js, session, packet); +#else + /* TODO: send proper error response */ +#endif /* USE_VV */ +} + static void jabber_gmail_parse(JabberStream *js, xmlnode *packet, gpointer nul) {
--- a/libpurple/protocols/jabber/google.h Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/jabber/google.h Sat Sep 27 04:44:17 2008 +0000 @@ -45,6 +45,7 @@ char *jabber_google_format_to_html(const char *text); +void jabber_google_session_parse(JabberStream *js, xmlnode *node); #endif /* _PURPLE_GOOGLE_H_ */
--- a/libpurple/protocols/jabber/iq.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/jabber/iq.c Sat Sep 27 04:44:17 2008 +0000 @@ -28,6 +28,7 @@ #include "disco.h" #include "google.h" #include "iq.h" +#include "jingle.h" #include "oob.h" #include "roster.h" #include "si.h" @@ -338,6 +339,11 @@ return; } } + + if (xmlnode_get_child_with_namespace(packet, "session", "http://www.google.com/session")) { + jabber_google_session_parse(js, packet); + return; + } if(xmlnode_get_child_with_namespace(packet, "si", "http://jabber.org/protocol/si")) { jabber_si_parse(js, packet); @@ -360,6 +366,13 @@ jabber_data_parse(js, packet); return; } + +#ifdef USE_VV + if (xmlnode_get_child_with_namespace(packet, "jingle", "urn:xmpp:tmp:jingle")) { + jabber_jingle_session_parse(js, packet); + return; + } +#endif /* If we get here, send the default error reply mandated by XMPP-CORE */ if(type && (!strcmp(type, "set") || !strcmp(type, "get"))) { @@ -400,6 +413,9 @@ jabber_iq_register_handler("http://jabber.org/protocol/disco#items", jabber_disco_items_parse); jabber_iq_register_handler("jabber:iq:register", jabber_register_parse); jabber_iq_register_handler("urn:xmpp:ping", urn_xmpp_ping_parse); +#ifdef USE_VV + jabber_iq_register_handler("urn:xmpp:tmp:jingle", jabber_jingle_session_parse); +#endif } void jabber_iq_uninit(void)
--- a/libpurple/protocols/jabber/jabber.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/jabber/jabber.c Sat Sep 27 04:44:17 2008 +0000 @@ -57,7 +57,16 @@ #include "xdata.h" #include "pep.h" #include "adhoccommands.h" +#include "jingle.h" +#ifdef USE_VV +#include <gst/farsight/fs-conference-iface.h> + +#define XEP_0167_AUDIO_CAP "urn:xmpp:tmp:jingle:apps:rtp#audio" +#define XEP_0167_VIDEO_CAP "urn:xmpp:tmp:jingle:apps:rtp#video" +#define GTALK_CAP "http://www.google.com/session/phone" + +#endif #define JABBER_CONNECT_STEPS (js->gsc ? 9 : 5) @@ -661,6 +670,9 @@ js->old_length = 0; js->keepalive_timeout = -1; js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user ? js->user->domain : NULL); +#ifdef USE_VV + js->sessions = NULL; +#endif if(!js->user) { purple_connection_error_reason (gc, @@ -1272,6 +1284,11 @@ { JabberStream *js = gc->proto_data; +#ifdef USE_VV + /* Close all of the open Jingle sessions on this stream */ + jabber_jingle_session_terminate_sessions(js); +#endif + /* Don't perform any actions on the ssl connection * if we were forcibly disconnected because it will crash * on some SSL backends. @@ -1902,10 +1919,13 @@ JabberID *jid; JabberBuddy *jb; JabberBuddyResource *jbr; - + if(!(jid = jabber_id_new(who))) return; +#ifdef USE_VV + jabber_jingle_session_terminate_session_media(js, who); +#endif if((jb = jabber_buddy_find(js, who, TRUE)) && (jbr = jabber_buddy_find_resource(jb, jid->resource))) { if(jbr->thread_id) { @@ -2382,6 +2402,54 @@ { return TRUE; } +#ifdef USE_VV + +PurpleMedia * +jabber_initiate_media(PurpleConnection *gc, const char *who, + PurpleMediaSessionType type) +{ + return jabber_jingle_session_initiate_media(gc->proto_data, who, type); +} + +gboolean jabber_can_do_media(PurpleConnection *gc, const char *who, + PurpleMediaSessionType type) +{ + JabberStream *js = (JabberStream *) gc->proto_data; + JabberBuddy *jb; + + if (!js) { + purple_debug_error("jabber", "jabber_can_do_media: NULL stream\n"); + return FALSE; + } + + jb = jabber_buddy_find(js, who, FALSE); + + if (!jb) { + purple_debug_error("jabber", "Could not find buddy\n"); + return FALSE; + } + /* XMPP will only support two-way media, AFAIK... */ + if (type == (PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO)) { + purple_debug_info("jabber", + "Checking audio/video XEP support for %s\n", who); + return (jabber_buddy_has_capability(jb, XEP_0167_AUDIO_CAP) || + jabber_buddy_has_capability(jb, GTALK_CAP)) && + jabber_buddy_has_capability(jb, XEP_0167_VIDEO_CAP); + } else if (type == (PURPLE_MEDIA_AUDIO)) { + purple_debug_info("jabber", + "Checking audio XEP support for %s\n", who); + return jabber_buddy_has_capability(jb, XEP_0167_AUDIO_CAP) || + jabber_buddy_has_capability(jb, GTALK_CAP); + } else if (type == (PURPLE_MEDIA_VIDEO)) { + purple_debug_info("jabber", + "Checking video XEP support for %s\n", who); + return jabber_buddy_has_capability(jb, XEP_0167_VIDEO_CAP); + } + + return FALSE; +} + +#endif void jabber_register_commands(void) {
--- a/libpurple/protocols/jabber/jabber.h Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/jabber/jabber.h Sat Sep 27 04:44:17 2008 +0000 @@ -53,6 +53,8 @@ #include "circbuffer.h" #include "connection.h" #include "dnssrv.h" +#include "media.h" +#include "mediamanager.h" #include "roomlist.h" #include "sslconn.h" @@ -241,6 +243,11 @@ * for when we lookup buddy icons from a url */ GSList *url_datas; + +#ifdef USE_VV + /* keep a hash table of JingleSessions */ + GHashTable *sessions; +#endif }; typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace); @@ -308,4 +315,9 @@ void jabber_register_commands(void); void jabber_init_plugin(PurplePlugin *plugin); +#ifdef USE_VV +PurpleMedia *jabber_initiate_media(PurpleConnection *gc, const char *who, PurpleMediaSessionType type); +gboolean jabber_can_do_media(PurpleConnection *gc, const char *who, PurpleMediaSessionType type); +#endif + #endif /* _PURPLE_JABBER_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle.c Sat Sep 27 04:44:17 2008 +0000 @@ -0,0 +1,1522 @@ +/* + * purple - Jabber Protocol Plugin + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + */ + +#include "config.h" +#include "purple.h" +#include "jingle.h" +#include "xmlnode.h" +#include "iq.h" + +#include <stdlib.h> +#include <string.h> +#include <glib.h> + +#ifdef USE_VV + +#include <gst/farsight/fs-candidate.h> + +#define JINGLE "urn:xmpp:tmp:jingle" +#define JINGLE_RTP "urn:xmpp:tmp:jingle:apps:rtp" +#define JINGLE_RTP_INFO "urn:xmpp:tmp:jingle:apps:rtp:info" +#define TRANSPORT_ICEUDP "urn:xmpp:tmp:jingle:transports:ice-udp" + +typedef enum { + PENDING, + GOT_ACK, + ACCEPTED, + ACTIVE +} JingleSessionState; + +typedef struct { + char *id; + JabberStream *js; + PurpleMedia *media; + char *remote_jid; + char *initiator; + gboolean is_initiator; + JingleSessionState state; + GHashTable *contents; /* JingleSessionContent table */ +} JingleSession; + +typedef struct { + gchar *name; + JingleSession *session; + gchar *creator; + gchar *sender; + gchar *transport_type; + gchar *type; + gchar *subtype; +} JingleSessionContent; + +static void +jabber_jingle_session_content_create_internal(JingleSession *session, + const gchar *name, + const gchar *creator, + const gchar *sender, + const gchar *transport_type, + const gchar *type, + const gchar *subtype) +{ + JingleSessionContent *content = g_new0(JingleSessionContent, 1); + content->session = session; + content->name = g_strdup(name); + content->creator = g_strdup(creator); + content->sender = g_strdup(sender); + content->transport_type = g_strdup(transport_type); + content->type = g_strdup(type); + content->subtype = g_strdup(subtype); + + if (!session->contents) { + purple_debug_info("jingle", "Creating hash table for contents\n"); + session->contents = g_hash_table_new(g_str_hash, g_str_equal); + } + purple_debug_info("jingle", "inserting content with name == \"%s\" into table\n", + content->name); + g_hash_table_insert(session->contents, content->name, content); +} + +static void +jabber_jingle_session_destroy_content(JingleSessionContent *content) +{ + purple_debug_info("jingle", "destroying content with name == \"%s\"\n", + content->name); + g_hash_table_remove(content->session->contents, content->name); + g_free(content->name); + g_free(content->creator); + g_free(content->sender); + g_free(content->transport_type); + g_free(content->type); + g_free(content->subtype); + g_free(content); +} + +static const gchar * +jabber_jingle_session_content_get_name(const JingleSessionContent *jsc) +{ + return jsc->name; +} + +static JingleSession * +jabber_jingle_session_content_get_session(const JingleSessionContent *jsc) +{ + return jsc->session; +} + +static const gchar * +jabber_jingle_session_content_get_creator(const JingleSessionContent *jsc) +{ + return jsc->creator; +} + +static const gchar * +jabber_jingle_session_content_get_sender(const JingleSessionContent *jsc) +{ + return jsc->sender; +} + +static const gchar * +jabber_jingle_session_content_get_transport_type(const JingleSessionContent *jsc) +{ + return jsc->transport_type; +} + +static gboolean +jabber_jingle_session_content_is_transport_type(const JingleSessionContent *jsc, + const gchar *transport_type) +{ + return !strcmp(jabber_jingle_session_content_get_transport_type(jsc), + transport_type); +} + +static const gchar * +jabber_jingle_session_content_get_type(const JingleSessionContent *jsc) +{ + return jsc->type; +} + +static gboolean +jabber_jingle_session_content_is_type(const JingleSessionContent *jsc, + const gchar *type) +{ + return !strcmp(jabber_jingle_session_content_get_type(jsc), type); +} + +static gchar * +jabber_jingle_session_content_get_subtype(const JingleSessionContent *jsc) +{ + return jsc->subtype; +} + +static gboolean +jabber_jingle_session_content_is_vv_type(const JingleSessionContent *jsc, + const gchar *media_type) +{ + return jabber_jingle_session_content_is_type(jsc, JINGLE_RTP) && + !strcmp(jabber_jingle_session_content_get_subtype(jsc), + media_type); +} + +static void +jabber_jingle_session_content_set_sender(JingleSessionContent *jsc, + const char *sender) +{ + if (jsc->sender) + g_free(jsc->sender); + jsc->sender = g_strdup(sender); +} + +static gboolean +jabber_jingle_session_equal(gconstpointer a, gconstpointer b) +{ + purple_debug_info("jingle", + "jabber_jingle_session_equal, comparing %s and %s\n", + ((JingleSession *)a)->id, + ((JingleSession *)b)->id); + return !strcmp(((JingleSession *) a)->id, ((JingleSession *) b)->id); +} + +static JingleSession * +jabber_jingle_session_create_internal(JabberStream *js, + const char *id) +{ + JingleSession *sess = g_new0(JingleSession, 1); + sess->js = js; + + if (id) { + sess->id = g_strdup(id); + } else if (js) { + /* init the session ID... */ + sess->id = jabber_get_next_id(js); + } + + /* insert it into the hash table */ + if (!js->sessions) { + purple_debug_info("jingle", "Creating hash table for sessions\n"); + js->sessions = g_hash_table_new(g_str_hash, g_str_equal); + } + purple_debug_info("jingle", "inserting session with key: %s into table\n", + sess->id); + g_hash_table_insert(js->sessions, sess->id, sess); + + sess->state = PENDING; + + return sess; +} + +static JabberStream * +jabber_jingle_session_get_js(const JingleSession *sess) +{ + return sess->js; +} + +static JingleSession * +jabber_jingle_session_create(JabberStream *js) +{ + JingleSession *sess = jabber_jingle_session_create_internal(js, NULL); + sess->is_initiator = TRUE; + return sess; +} + +static JingleSession * +jabber_jingle_session_create_by_id(JabberStream *js, const char *id) +{ + JingleSession *sess = jabber_jingle_session_create_internal(js, id); + sess->is_initiator = FALSE; + return sess; +} + +static const char * +jabber_jingle_session_get_id(const JingleSession *sess) +{ + return sess->id; +} + +static void +jabber_jingle_session_destroy(JingleSession *sess) +{ + GList *contents; + g_hash_table_remove(sess->js->sessions, sess->id); + g_free(sess->id); + g_free(sess->remote_jid); + g_free(sess->initiator); + + if (sess->media) + g_object_unref(sess->media); + + for (contents = g_hash_table_get_values(sess->contents); contents; + contents = g_list_delete_link(contents, contents)) + jabber_jingle_session_destroy_content(contents->data); + + g_free(sess); +} + +static JingleSession * +jabber_jingle_session_find_by_id(JabberStream *js, const char *id) +{ + purple_debug_info("jingle", "find_by_id %s\n", id); + purple_debug_info("jingle", "lookup: %p\n", (js->sessions) ? + g_hash_table_lookup(js->sessions, id) : NULL); + return (JingleSession *) (js->sessions) ? + g_hash_table_lookup(js->sessions, id) : NULL; +} + +static JingleSession * +jabber_jingle_session_find_by_jid(JabberStream *js, const char *jid) +{ + GList *values = (js->sessions) ? + g_hash_table_get_values(js->sessions) : NULL; + gboolean use_bare = strchr(jid, '/') == NULL; + + for (; values; values = g_list_delete_link(values, values)) { + JingleSession *session = (JingleSession *)values->data; + gchar *cmp_jid = use_bare ? jabber_get_bare_jid(session->remote_jid) + : g_strdup(session->remote_jid); + if (!strcmp(jid, cmp_jid)) { + g_free(cmp_jid); + g_list_free(values); + return session; + } + g_free(cmp_jid); + } + + return NULL; +} + +static GList * +jabber_jingle_get_codecs(xmlnode *description) +{ + GList *codecs = NULL; + xmlnode *codec_element = NULL; + const char *encoding_name,*id, *clock_rate; + FsCodec *codec; + const gchar *media = xmlnode_get_attrib(description, "media"); + FsMediaType type = !strcmp(media, "video") ? FS_MEDIA_TYPE_VIDEO : + !strcmp(media, "audio") ? FS_MEDIA_TYPE_AUDIO : 0; + + for (codec_element = xmlnode_get_child(description, "payload-type") ; + codec_element ; + codec_element = xmlnode_get_next_twin(codec_element)) { + xmlnode *param; + gchar *codec_str; + encoding_name = xmlnode_get_attrib(codec_element, "name"); + + id = xmlnode_get_attrib(codec_element, "id"); + clock_rate = xmlnode_get_attrib(codec_element, "clockrate"); + + codec = fs_codec_new(atoi(id), encoding_name, + type, + clock_rate ? atoi(clock_rate) : 0); + + for (param = xmlnode_get_child(codec_element, "parameter"); + param; param = xmlnode_get_next_twin(param)) { + fs_codec_add_optional_parameter(codec, + xmlnode_get_attrib(param, "name"), + xmlnode_get_attrib(param, "value")); + } + + codec_str = fs_codec_to_string(codec); + purple_debug_info("jingle", "received codec: %s\n", codec_str); + g_free(codec_str); + + codecs = g_list_append(codecs, codec); + } + return codecs; +} + +static GList * +jabber_jingle_get_candidates(const xmlnode *transport) +{ + GList *candidates = NULL; + xmlnode *candidate = NULL; + FsCandidate *c; + + for (candidate = xmlnode_get_child(transport, "candidate") ; + candidate ; + candidate = xmlnode_get_next_twin(candidate)) { + const char *type = xmlnode_get_attrib(candidate, "type"); + c = fs_candidate_new(xmlnode_get_attrib(candidate, "component"), + atoi(xmlnode_get_attrib(candidate, "component")), + strcmp(type, "host") == 0 ? + FS_CANDIDATE_TYPE_HOST : + strcmp(type, "prflx") == 0 ? + FS_CANDIDATE_TYPE_PRFLX : + strcmp(type, "relay") == 0 ? + FS_CANDIDATE_TYPE_RELAY : + strcmp(type, "srflx") == 0 ? + FS_CANDIDATE_TYPE_SRFLX : 0, + strcmp(xmlnode_get_attrib(candidate, "protocol"), + "udp") == 0 ? + FS_NETWORK_PROTOCOL_UDP : + FS_NETWORK_PROTOCOL_TCP, + xmlnode_get_attrib(candidate, "ip"), + atoi(xmlnode_get_attrib(candidate, "port"))); + candidates = g_list_append(candidates, c); + } + + return candidates; +} + +static JingleSessionContent * +jabber_jingle_session_get_content(const JingleSession *session, + const char *name) +{ + return (JingleSession *) name ? + g_hash_table_lookup(session->contents, name) : NULL; +} + +static GList * +jabber_jingle_session_get_contents(const JingleSession *session) +{ + return g_hash_table_get_values(session->contents); +} + +static PurpleMedia * +jabber_jingle_session_get_media(const JingleSession *sess) +{ + return sess->media; +} + +static void +jabber_jingle_session_set_media(JingleSession *sess, PurpleMedia *media) +{ + sess->media = media; +} + +static const char * +jabber_jingle_session_get_remote_jid(const JingleSession *sess) +{ + return sess->remote_jid; +} + +static void +jabber_jingle_session_set_remote_jid(JingleSession *sess, + const char *remote_jid) +{ + if (sess->remote_jid) + g_free(sess->remote_jid); + sess->remote_jid = g_strdup(remote_jid); +} + +static JingleSessionState +jabber_jingle_session_get_state(JingleSession *sess) +{ + return sess->state; +} + +static void +jabber_jingle_session_set_state(JingleSession *sess, + JingleSessionState state) +{ + sess->state = state; +} + + +static const char * +jabber_jingle_session_get_initiator(const JingleSession *sess) +{ + return sess->initiator; +} + +static void +jabber_jingle_session_set_initiator(JingleSession *sess, + const char *initiator) +{ + if (sess->initiator) + g_free(sess->initiator); + sess->initiator = g_strdup(initiator); +} + +static gboolean +jabber_jingle_session_is_initiator(const JingleSession *sess) +{ + return sess->is_initiator; +} + +static void +jabber_jingle_session_add_payload_types(const JingleSessionContent *jsc, + xmlnode *description, + GList *codecs) +{ + for (; codecs ; codecs = codecs->next) { + FsCodec *codec = (FsCodec*)codecs->data; + GList *iter = codec->optional_params; + char id[8], clockrate[10], channels[10]; + gchar *codec_str; + xmlnode *payload = xmlnode_new_child(description, "payload-type"); + + g_snprintf(id, sizeof(id), "%d", codec->id); + g_snprintf(clockrate, sizeof(clockrate), "%d", codec->clock_rate); + g_snprintf(channels, sizeof(channels), "%d", codec->channels); + + xmlnode_set_attrib(payload, "name", codec->encoding_name); + xmlnode_set_attrib(payload, "id", id); + xmlnode_set_attrib(payload, "clockrate", clockrate); + xmlnode_set_attrib(payload, "channels", channels); + + for (; iter; iter = g_list_next(iter)) { + FsCodecParameter *fsparam = iter->data; + xmlnode *param = xmlnode_new_child(payload, "parameter"); + xmlnode_set_attrib(param, "name", fsparam->name); + xmlnode_set_attrib(param, "value", fsparam->value); + } + + codec_str = fs_codec_to_string(codec); + purple_debug_info("jingle", "adding codec: %s\n", codec_str); + g_free(codec_str); + } +} + +static xmlnode * +jabber_jingle_session_add_description_vv(const JingleSessionContent *jsc, + xmlnode *description) +{ + xmlnode_set_attrib(description, "media", + jabber_jingle_session_content_get_subtype(jsc)); + return description; +} + +static xmlnode * +jabber_jingle_session_add_description(const JingleSessionContent *jsc, + xmlnode *content) +{ + xmlnode *description = xmlnode_new_child(content, "description"); + xmlnode_set_namespace(description, + jabber_jingle_session_content_get_type(jsc)); + + if (jabber_jingle_session_content_is_type(jsc, JINGLE_RTP)) + return jabber_jingle_session_add_description_vv(jsc, description); + else + return description; +} + +static xmlnode * +jabber_jingle_session_add_candidate_iceudp(xmlnode *transport, + FsCandidate *c, + FsCandidate *remote) +{ + char port[8]; + char prio[8]; + char component[8]; + xmlnode *candidate = xmlnode_new_child(transport, "candidate"); + + g_snprintf(port, sizeof(port), "%d", c->port); + g_snprintf(prio, sizeof(prio), "%d", c->priority); + g_snprintf(component, sizeof(component), "%d", c->component_id); + + xmlnode_set_attrib(candidate, "component", component); + xmlnode_set_attrib(candidate, "foundation", "1"); /* what about this? */ + xmlnode_set_attrib(candidate, "generation", "0"); /* ? */ + xmlnode_set_attrib(candidate, "ip", c->ip); + xmlnode_set_attrib(candidate, "network", "0"); /* ? */ + xmlnode_set_attrib(candidate, "port", port); + xmlnode_set_attrib(candidate, "priority", prio); /* Is this correct? */ + xmlnode_set_attrib(candidate, "protocol", + c->proto == FS_NETWORK_PROTOCOL_UDP ? + "udp" : "tcp"); + if (c->username) + xmlnode_set_attrib(transport, "ufrag", c->username); + if (c->password) + xmlnode_set_attrib(transport, "pwd", c->password); + + xmlnode_set_attrib(candidate, "type", + c->type == FS_CANDIDATE_TYPE_HOST ? + "host" : + c->type == FS_CANDIDATE_TYPE_PRFLX ? + "prflx" : + c->type == FS_CANDIDATE_TYPE_RELAY ? + "relay" : + c->type == FS_CANDIDATE_TYPE_SRFLX ? + "srflx" : NULL); + + /* relay */ + if (c->type == FS_CANDIDATE_TYPE_RELAY) { + /* set rel-addr and rel-port? How? */ + } + + if (remote) { + char remote_port[8]; + g_snprintf(remote_port, sizeof(remote_port), "%d", remote->port); + xmlnode_set_attrib(candidate, "rem-addr", remote->ip); + xmlnode_set_attrib(candidate, "rem-port", remote_port); + } + + return candidate; +} + +static xmlnode * +jabber_jingle_session_add_transport(const JingleSessionContent *jsc, + xmlnode *content) +{ + xmlnode *transport = xmlnode_new_child(content, "transport"); + const gchar *transport_type = jabber_jingle_session_content_get_transport_type(jsc); + xmlnode_set_namespace(transport, transport_type); + return transport; +} + +static xmlnode * +jabber_jingle_session_add_content(const JingleSessionContent *jsc, + xmlnode *jingle) +{ + xmlnode *content = xmlnode_new_child(jingle, "content"); + xmlnode_set_attrib(content, "creator", + jabber_jingle_session_content_get_creator(jsc)); + xmlnode_set_attrib(content, "name", + jabber_jingle_session_content_get_name(jsc)); + xmlnode_set_attrib(content, "sender", + jabber_jingle_session_content_get_sender(jsc)); + return content; +} + + +static xmlnode * +jabber_jingle_session_add_jingle(const JingleSession *sess, + JabberIq *iq, const char *action) +{ + xmlnode *jingle = iq ? xmlnode_new_child(iq->node, "jingle") : + xmlnode_new("jingle"); + xmlnode_set_namespace(jingle, JINGLE); + xmlnode_set_attrib(jingle, "action", action); + xmlnode_set_attrib(jingle, "initiator", + jabber_jingle_session_get_initiator(sess)); + if (jabber_jingle_session_is_initiator(sess)) + xmlnode_set_attrib(jingle, "responder", + jabber_jingle_session_get_remote_jid(sess)); + else { + gchar *responder = g_strdup_printf("%s@%s/%s", + sess->js->user->node, + sess->js->user->domain, + sess->js->user->resource); + xmlnode_set_attrib(jingle, "responder", responder); + g_free(responder); + } + xmlnode_set_attrib(jingle, "sid", jabber_jingle_session_get_id(sess)); + + return jingle; +} + +static JabberIq * +jabber_jingle_session_create_ack(JingleSession *session, xmlnode *jingle) +{ + JabberIq *result = jabber_iq_new( + jabber_jingle_session_get_js(session), + JABBER_IQ_RESULT); + xmlnode *packet = xmlnode_get_parent(jingle); + jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); + xmlnode_set_attrib(result->node, "from", xmlnode_get_attrib(packet, "to")); + xmlnode_set_attrib(result->node, "to", xmlnode_get_attrib(packet, "from")); + return result; +} + +static JabberIq * +jabber_jingle_session_create_iq(const JingleSession *session) +{ + JabberIq *result = jabber_iq_new(jabber_jingle_session_get_js(session), + JABBER_IQ_SET); + gchar *from = g_strdup_printf("%s@%s/%s", session->js->user->node, + session->js->user->domain, + session->js->user->resource); + xmlnode_set_attrib(result->node, "from", from); + g_free(from); + xmlnode_set_attrib(result->node, "to", + jabber_jingle_session_get_remote_jid(session)); + return result; +} +#if 0 +static xmlnode * +jabber_jingle_session_create_content_accept(const JingleSession *sess) +{ + xmlnode *jingle = + jabber_jingle_session_add_jingle(sess, NULL, "content-accept"); + + xmlnode *content = xmlnode_new_child(jingle, "content"); + xmlnode *description = jabber_jingle_session_create_description(sess); + + xmlnode_set_attrib(content, "creator", "initiator"); + xmlnode_set_attrib(content, "name", "audio-content"); + xmlnode_set_attrib(content, "profile", "RTP/AVP"); + + xmlnode_insert_child(content, description); + + return jingle; +} + +static xmlnode * +jabber_jingle_session_create_content_replace(const JingleSession *sess, + FsCandidate *native_candidate, + FsCandidate *remote_candidate) +{ + xmlnode *jingle = + jabber_jingle_session_add_jingle(sess, NULL, "content-replace"); + xmlnode *content = NULL; + xmlnode *transport = NULL; + + purple_debug_info("jingle", "creating content-modify for native candidate %s " \ + ", remote candidate %s\n", native_candidate->candidate_id, + remote_candidate->candidate_id); + + content = xmlnode_new_child(jingle, "content"); + xmlnode_set_attrib(content, "creator", "initiator"); + xmlnode_set_attrib(content, "name", "audio-content"); + xmlnode_set_attrib(content, "profile", "RTP/AVP"); + + /* get top codec from codec_intersection to put here... */ + /* later on this should probably handle changing codec */ + + xmlnode_insert_child(content, jabber_jingle_session_create_description(sess)); + + transport = xmlnode_new_child(content, "transport"); + xmlnode_set_namespace(transport, TRANSPORT_ICEUDP); + jabber_jingle_session_add_candidate_iceudp(transport, native_candidate, + remote_candidate); + + purple_debug_info("jingle", "End create content modify\n"); + + return jingle; +} +#endif + +static JabberIq * +jabber_jingle_session_create_session_accept(const JingleSession *session) +{ + PurpleMedia *media = jabber_jingle_session_get_media(session); + const gchar *remote_jid = jabber_jingle_session_get_remote_jid(session); + JabberIq *request = jabber_jingle_session_create_iq(session); + xmlnode *jingle = + jabber_jingle_session_add_jingle(session, request, + "session-accept"); + GList *contents = jabber_jingle_session_get_contents(session); + + for (; contents; contents = contents->next) { + JingleSessionContent *jsc = contents->data; + const gchar *content_name = jabber_jingle_session_content_get_name(jsc); + xmlnode *content = jabber_jingle_session_add_content(jsc, jingle); + xmlnode *description = jabber_jingle_session_add_description(jsc, content); + xmlnode *transport = jabber_jingle_session_add_transport(jsc, content); + if (jabber_jingle_session_content_is_type(jsc, JINGLE_RTP)) { + GList *codecs = purple_media_get_negotiated_codecs(media, + content_name); + jabber_jingle_session_add_payload_types(jsc, description, codecs); + fs_codec_list_destroy(codecs); + } + if (jabber_jingle_session_content_is_transport_type(jsc, TRANSPORT_ICEUDP)) { + jabber_jingle_session_add_candidate_iceudp(transport, + purple_media_get_local_candidate(media, content_name, + remote_jid), + purple_media_get_remote_candidate(media, content_name, + remote_jid)); + } + } + + return request; +} + +static JabberIq * +jabber_jingle_session_create_session_info(const JingleSession *session, + const gchar *type) +{ + JabberIq *request = jabber_jingle_session_create_iq(session); + xmlnode *jingle = + jabber_jingle_session_add_jingle(session, request, + "session-info"); + xmlnode *info = xmlnode_new_child(jingle, type); + xmlnode_set_namespace(info, JINGLE_RTP_INFO); + return request; +} + +static JabberIq * +jabber_jingle_session_create_session_initiate(const JingleSession *session) +{ + JabberIq *request = jabber_jingle_session_create_iq(session); + xmlnode *jingle = + jabber_jingle_session_add_jingle(session, request, + "session-initiate"); + GList *contents = jabber_jingle_session_get_contents(session); + + for (; contents; contents = contents->next) { + JingleSessionContent *jsc = contents->data; + xmlnode *content = jabber_jingle_session_add_content(jsc, jingle); + xmlnode *description = jabber_jingle_session_add_description(jsc, content); + if (jabber_jingle_session_content_is_type(jsc, JINGLE_RTP)) { + PurpleMedia *media = jabber_jingle_session_get_media(session); + const gchar *content_name = + jabber_jingle_session_content_get_name(jsc); + GList *codecs = purple_media_get_local_codecs(media, content_name); + jabber_jingle_session_add_payload_types(jsc, description, codecs); + fs_codec_list_destroy(codecs); + } + jabber_jingle_session_add_transport(jsc, content); + } + + return request; +} + +static JabberIq * +jabber_jingle_session_create_session_terminate(const JingleSession *sess, + const char *reasoncode, + const char *reasontext) +{ + JabberIq *request = jabber_jingle_session_create_iq(sess); + xmlnode *jingle = + jabber_jingle_session_add_jingle(sess, request, + "session-terminate"); + xmlnode *reason = xmlnode_new_child(jingle, "reason"); + xmlnode_new_child(reason, reasoncode); + if (reasontext) { + xmlnode *text = xmlnode_new_child(reason, "text"); + xmlnode_insert_data(text, reasontext, strlen(reasontext)); + } + + return request; +} + +static JabberIq * +jabber_jingle_session_create_transport_info(const JingleSessionContent *jsc, + FsCandidate *candidate) +{ + JingleSession *session = + jabber_jingle_session_content_get_session(jsc); + JabberIq *request = jabber_jingle_session_create_iq(session); + xmlnode *jingle = + jabber_jingle_session_add_jingle(session, request, + "transport-info"); + xmlnode *content = jabber_jingle_session_add_content(jsc, jingle); + xmlnode *transport = jabber_jingle_session_add_transport(jsc, content); + jabber_jingle_session_add_candidate_iceudp(transport, candidate, NULL); + return request; +} +#if 0 +static void +jabber_jingle_session_send_content_accept(JingleSession *session) +{ + JabberIq *result = jabber_iq_new(jabber_jingle_session_get_js(session), + JABBER_IQ_SET); + xmlnode *jingle = jabber_jingle_session_create_content_accept(session); + xmlnode_set_attrib(result->node, "to", + jabber_jingle_session_get_remote_jid(session)); + + xmlnode_insert_child(result->node, jingle); + jabber_iq_send(result); +} +#endif + +static void +jabber_jingle_session_accept(JingleSession *session) +{ + if (jabber_jingle_session_get_state(session) == ACCEPTED && + purple_media_candidates_prepared( + jabber_jingle_session_get_media(session), + jabber_jingle_session_get_remote_jid(session))) { + GList *contents = jabber_jingle_session_get_contents(session); + for (; contents; contents = contents->next) { + JingleSessionContent *jsc = contents->data; + GList *codec_intersection = + purple_media_get_negotiated_codecs(session->media, + jabber_jingle_session_content_get_name(jsc)); + purple_debug_info("jingle", "codec intersection: %i\n", + g_list_length(codec_intersection)); + + if (codec_intersection != NULL) { + gchar *codec_str = fs_codec_to_string(codec_intersection->data); + purple_debug_info("jingle", "Setting send codec: %s\n", codec_str); + g_free(codec_str); + purple_media_set_send_codec( + jabber_jingle_session_get_media(session), + jabber_jingle_session_content_get_name(jsc), + codec_intersection->data); + } + } + + jabber_iq_send(jabber_jingle_session_create_session_accept(session)); + + purple_debug_info("jingle", "Sent session accept.\n"); + jabber_jingle_session_set_state(session, ACTIVE); + } +} + +static void +jabber_jingle_session_send_session_accept(JingleSession *session) +{ + /* create transport-info packages */ + PurpleMedia *media = jabber_jingle_session_get_media(session); + GList *contents = jabber_jingle_session_get_contents(session); + const gchar *remote_jid = jabber_jingle_session_get_remote_jid(session); + for (; contents; contents = contents->next) { + JingleSessionContent *jsc = contents->data; + GList *candidates = purple_media_get_local_candidates( + media, + jabber_jingle_session_content_get_name(jsc), + remote_jid); + purple_debug_info("jingle", + "jabber_session_candidates_prepared: %d candidates\n", + g_list_length(candidates)); + for (; candidates; candidates = candidates->next) { + FsCandidate *candidate = candidates->data; + JabberIq *result = jabber_jingle_session_create_transport_info(jsc, + candidate); + jabber_iq_send(result); + } + fs_candidate_list_destroy(candidates); + } + + jabber_jingle_session_set_state(session, ACCEPTED); + jabber_jingle_session_accept(session); +} + +static void +jabber_jingle_session_send_session_reject(JingleSession *session) +{ + jabber_iq_send(jabber_jingle_session_create_session_terminate(session, + "decline", NULL)); + jabber_jingle_session_destroy(session); +} + +static void +jabber_jingle_session_send_session_terminate(JingleSession *session) +{ + jabber_iq_send(jabber_jingle_session_create_session_terminate(session, + "no-error", NULL)); + jabber_jingle_session_destroy(session); +} + +static void +jabber_jingle_session_content_create_media(JingleSession *session, + PurpleMediaSessionType type) +{ + gchar sender[10] = ""; + + if (type & PURPLE_MEDIA_AUDIO) { + if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_SEND_AUDIO) + strcpy(sender, "initiator"); + else if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_RECV_AUDIO) + strcpy(sender, "responder"); + else + strcpy(sender, "both"); + jabber_jingle_session_content_create_internal(session, + "audio-content", "initiator", sender, + TRANSPORT_ICEUDP, JINGLE_RTP, "audio"); + } + if (type & PURPLE_MEDIA_VIDEO) { + if ((type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_SEND_VIDEO) + strcpy(sender, "initiator"); + else if ((type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_RECV_VIDEO) + strcpy(sender, "responder"); + else + strcpy(sender, "both"); + jabber_jingle_session_content_create_internal(session, + "video-content", "initiator", sender, + TRANSPORT_ICEUDP, JINGLE_RTP, "video"); + } +} + +static void +jabber_jingle_session_content_create_parse(JingleSession *session, + xmlnode *content) +{ + xmlnode *description = xmlnode_get_child(content, "description"); + xmlnode *transport = xmlnode_get_child(content, "transport"); + + const gchar *creator = xmlnode_get_attrib(content, "creator"); + const gchar *sender = xmlnode_get_attrib(content, "sender"); + const gchar *subtype = xmlnode_get_attrib(description, "media"); + + jabber_jingle_session_content_create_internal(session, + xmlnode_get_attrib(content, "name"), + creator ? creator : "initiator", + sender ? sender : "both", + xmlnode_get_namespace(transport), + xmlnode_get_namespace(description), + subtype); +} + +static void +jabber_jingle_session_new_candidate_cb(PurpleMedia *media, + const gchar *session_id, + const gchar *name, + FsCandidate *candidate, + JingleSession *session) +{ + if (jabber_jingle_session_get_state(session) == GOT_ACK || + jabber_jingle_session_get_state(session) == ACTIVE) { + JingleSessionContent *jsc = jabber_jingle_session_get_content(session, + session_id); + jabber_iq_send(jabber_jingle_session_create_transport_info(jsc, + candidate)); + } +} + +/* callback called when a pair of transport candidates (local and remote) + has been established */ +static void +jabber_jingle_session_candidate_pair_established_cb(PurpleMedia *media, + FsCandidate *native_candidate, + FsCandidate *remote_candidate, + JingleSession *session) +{ + if (!jabber_jingle_session_is_initiator(session)) { + jabber_jingle_session_accept(session); + } +} + +static void +jabber_jingle_session_initiate_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +{ + const char *from = xmlnode_get_attrib(packet, "from"); + JingleSession *session = jabber_jingle_session_find_by_jid(js, from); + PurpleMedia *media; + GList *contents; + + if (!session) { + /* respond with an error here */ + purple_debug_error("jingle", "Received session-initiate ack" + " to nonexistent session\n"); + return; + } + + media = session->media; + + if (!strcmp(xmlnode_get_attrib(packet, "type"), "error")) { + purple_media_got_hangup(media); + return; + } + + /* catch errors */ + if (xmlnode_get_child(packet, "error")) { + purple_media_got_hangup(media); + return; + } + + /* create transport-info packages */ + contents = jabber_jingle_session_get_contents(session); + for (; contents; contents = contents->next) { + JingleSessionContent *jsc = contents->data; + GList *candidates = purple_media_get_local_candidates( + jabber_jingle_session_get_media(session), + jabber_jingle_session_content_get_name(jsc), + jabber_jingle_session_get_remote_jid(session)); + purple_debug_info("jingle", + "jabber_session_candidates_prepared: %d candidates\n", + g_list_length(candidates)); + for (; candidates; candidates = candidates->next) { + FsCandidate *candidate = candidates->data; + JabberIq *result = jabber_jingle_session_create_transport_info(jsc, + candidate); + jabber_iq_send(result); + } + fs_candidate_list_destroy(candidates); + } + + jabber_jingle_session_set_state(session, GOT_ACK); +} + +static void +jabber_jingle_session_codecs_ready_cb(PurpleMedia *media, + const gchar *sess_id, + JingleSession *session) +{ + GList *contents = jabber_jingle_session_get_contents(session); + for (; contents; contents = g_list_delete_link(contents, contents)) { + JingleSessionContent *jsc = contents->data; + if (!purple_media_codecs_ready(media, + jabber_jingle_session_content_get_name(jsc))) { + break; + } + } + + if (contents != NULL) + g_list_free(contents); + else if (jabber_jingle_session_is_initiator(session) + && jabber_jingle_session_get_state(session) == PENDING) { + JabberIq *request; + + /* create request */ + request = jabber_jingle_session_create_session_initiate(session); + jabber_iq_set_callback(request, jabber_jingle_session_initiate_result_cb, NULL); + + /* send request to other part */ + jabber_iq_send(request); + } else { + jabber_jingle_session_accept(session); + } +} + +static gboolean +jabber_jingle_session_initiate_media_internal(JingleSession *session, + const char *initiator, + const char *remote_jid) +{ + PurpleMedia *media = NULL; + GList *contents = jabber_jingle_session_get_contents(session); + + media = purple_media_manager_create_media(purple_media_manager_get(), + session->js->gc, "fsrtpconference", remote_jid); + + jabber_jingle_session_set_remote_jid(session, remote_jid); + jabber_jingle_session_set_initiator(session, initiator); + + if (!media) { + purple_debug_error("jingle", "Couldn't create media session\n"); + return FALSE; + } + + jabber_jingle_session_set_media(session, media); + + for (; contents; contents = g_list_delete_link(contents, contents)) { + JingleSessionContent *jsc = contents->data; + gboolean result = FALSE; + const gchar *sender = jabber_jingle_session_content_get_sender(jsc); + FsStreamDirection direction = FS_DIRECTION_NONE; + + if (!strcmp(sender, "initiator")) + direction = FS_DIRECTION_SEND; + else if(!strcmp(sender, "responder")) + direction = FS_DIRECTION_RECV; + else + direction = FS_DIRECTION_BOTH; + + if (!jabber_jingle_session_is_initiator(session) + && direction != FS_DIRECTION_BOTH) { + if (direction == FS_DIRECTION_SEND) + direction = FS_DIRECTION_RECV; + else + direction = FS_DIRECTION_SEND; + } + + /* these will need to be changed to "nice" once the libnice transmitter is finished */ + if (jabber_jingle_session_content_is_vv_type(jsc, "audio")) { + result = purple_media_add_stream(media, "audio-content", remote_jid, + purple_media_from_fs(FS_MEDIA_TYPE_AUDIO, direction), + "rawudp", 0, NULL); + purple_debug_info("jingle", "Created Jingle audio session\n"); + } + else if (jabber_jingle_session_content_is_vv_type(jsc, "video")) { + result = purple_media_add_stream(media, "video-content", remote_jid, + purple_media_from_fs(FS_MEDIA_TYPE_VIDEO, direction), + "rawudp", 0, NULL); + purple_debug_info("jingle", "Created Jingle video session\n"); + } + + if (!result) { + purple_debug_error("jingle", "Couldn't create stream\n"); + purple_media_hangup(media); + return FALSE; + } + } + + /* connect callbacks */ + g_signal_connect_swapped(G_OBJECT(media), "accepted", + G_CALLBACK(jabber_jingle_session_send_session_accept), session); + g_signal_connect_swapped(G_OBJECT(media), "reject", + G_CALLBACK(jabber_jingle_session_send_session_reject), session); + g_signal_connect_swapped(G_OBJECT(media), "hangup", + G_CALLBACK(jabber_jingle_session_send_session_terminate), session); + g_signal_connect(G_OBJECT(media), "new-candidate", + G_CALLBACK(jabber_jingle_session_new_candidate_cb), session); + g_signal_connect(G_OBJECT(media), "candidate-pair", + G_CALLBACK(jabber_jingle_session_candidate_pair_established_cb), session); + g_signal_connect(G_OBJECT(media), "codecs-ready", + G_CALLBACK(jabber_jingle_session_codecs_ready_cb), session); + + purple_media_ready(media); + + return TRUE; +} + +PurpleMedia * +jabber_jingle_session_initiate_media(JabberStream *js, const char *who, + PurpleMediaSessionType type) +{ + /* create content negotiation */ + JingleSession *session; + JabberBuddy *jb; + JabberBuddyResource *jbr; + + char *jid = NULL, *me = NULL; + + /* construct JID to send to */ + jb = jabber_buddy_find(js, who, FALSE); + if (!jb) { + purple_debug_error("jingle", "Could not find Jabber buddy\n"); + return NULL; + } + jbr = jabber_buddy_find_resource(jb, NULL); + if (!jbr) { + purple_debug_error("jingle", "Could not find buddy's resource\n"); + } + + if ((strchr(who, '/') == NULL) && jbr && (jbr->name != NULL)) { + jid = g_strdup_printf("%s/%s", who, jbr->name); + } else { + jid = g_strdup(who); + } + + session = jabber_jingle_session_create(js); + jabber_jingle_session_content_create_media(session, type); + + /* set ourselves as initiator */ + me = g_strdup_printf("%s@%s/%s", js->user->node, js->user->domain, js->user->resource); + + if (!jabber_jingle_session_initiate_media_internal(session, me, jid)) { + g_free(jid); + g_free(me); + jabber_jingle_session_destroy(session); + return NULL; + } + + g_free(jid); + g_free(me); + + return session->media; +} + +void +jabber_jingle_session_terminate_session_media(JabberStream *js, const gchar *who) +{ + JingleSession *session; + + session = jabber_jingle_session_find_by_jid(js, who); + + if (session) + purple_media_hangup(session->media); +} + +void +jabber_jingle_session_terminate_sessions(JabberStream *js) +{ + GList *values = js->sessions ? + g_hash_table_get_values(js->sessions) : NULL; + + for (; values; values = g_list_delete_link(values, values)) { + JingleSession *session = (JingleSession *)values->data; + purple_media_hangup(session->media); + } +} + +static void +jabber_jingle_session_handle_content_replace(JingleSession *session, xmlnode *jingle) +{ +#if 0 + xmlnode *jingle = xmlnode_get_child(packet, "jingle"); + const char *sid = xmlnode_get_attrib(jingle, "sid"); + JingleSession *session = jabber_jingle_session_find_by_id(js, sid); + + if (!jabber_jingle_session_is_initiator(session) && session->session_started) { + JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT); + JabberIq *accept = jabber_iq_new(js, JABBER_IQ_SET); + xmlnode *content_accept = NULL; + + /* send acknowledement */ + xmlnode_set_attrib(result->node, "id", xmlnode_get_attrib(packet, "id")); + xmlnode_set_attrib(result->node, "to", xmlnode_get_attrib(packet, "from")); + jabber_iq_send(result); + + /* send content-accept */ + content_accept = jabber_jingle_session_create_content_accept(session); + xmlnode_set_attrib(accept->node, "id", xmlnode_get_attrib(packet, "id")); + xmlnode_set_attrib(accept->node, "to", xmlnode_get_attrib(packet, "from")); + xmlnode_insert_child(accept->node, content_accept); + + jabber_iq_send(accept); + } +#endif +} + +static void +jabber_jingle_session_handle_session_accept(JingleSession *session, xmlnode *jingle) +{ + xmlnode *content = xmlnode_get_child(jingle, "content"); + const char *action = xmlnode_get_attrib(jingle, "action"); + GList *remote_codecs = NULL; + GList *remote_transports = NULL; + GList *codec_intersection; + FsCodec *top = NULL; + xmlnode *description = NULL; + xmlnode *transport = NULL; + + /* We should probably check validity of the incoming XML... */ + + for (content = xmlnode_get_child(jingle, "content"); content; + content = xmlnode_get_next_twin(content)) { + description = xmlnode_get_child(content, "description"); + transport = xmlnode_get_child(content, "transport"); + + /* fetch codecs from remote party */ + purple_debug_info("jingle", "get codecs from session-accept\n"); + remote_codecs = jabber_jingle_get_codecs(description); + purple_debug_info("jingle", "get transport candidates from session accept\n"); + remote_transports = jabber_jingle_get_candidates(transport); + + purple_debug_info("jingle", "Got %d codecs from responder\n", + g_list_length(remote_codecs)); + purple_debug_info("jingle", "Got %d transport candidates from responder\n", + g_list_length(remote_transports)); + + purple_debug_info("jingle", "Setting remote codecs on stream\n"); + + if (!purple_media_set_remote_codecs(session->media, + xmlnode_get_attrib(content, "name"), + jabber_jingle_session_get_remote_jid(session), + remote_codecs)) { + purple_media_reject(jabber_jingle_session_get_media(session)); + return; + } + + codec_intersection = purple_media_get_negotiated_codecs(session->media, + xmlnode_get_attrib(content, "name")); + purple_debug_info("jingle", "codec_intersection contains %d elems\n", + g_list_length(codec_intersection)); + /* get the top codec */ + if (g_list_length(codec_intersection) > 0) { + top = (FsCodec *) codec_intersection->data; + purple_debug_info("jingle", "Found a suitable codec on stream = %d\n", + top->id); + + /* we have found a suitable codec, but we will not start the stream + just yet, wait for transport negotiation to complete... */ + purple_media_set_send_codec( + jabber_jingle_session_get_media(session), + xmlnode_get_attrib(content, "name"), + codec_intersection->data); + } + /* if we also got transport candidates, add them to our streams + list of known remote candidates */ + if (g_list_length(remote_transports) > 0) { + purple_media_add_remote_candidates(session->media, + xmlnode_get_attrib(content, "name"), + jabber_jingle_session_get_remote_jid(session), + remote_transports); + fs_candidate_list_destroy(remote_transports); + } + if (g_list_length(codec_intersection) == 0 && + g_list_length(remote_transports)) { + /* we didn't get any candidates and the codec intersection is empty, + this means this was not a content-accept message and we couldn't + find any suitable codecs, should return error and hang up */ + + } + + fs_codec_list_destroy(codec_intersection); + + } + + if (!strcmp(action, "session-accept")) { + purple_media_got_accept(jabber_jingle_session_get_media(session)); + purple_debug_info("jingle", "Got session-accept\n"); + } + + jabber_iq_send(jabber_jingle_session_create_ack(session, jingle)); + + jabber_jingle_session_set_state(session, ACTIVE); +} + +static void +jabber_jingle_session_handle_session_info(JingleSession *session, xmlnode *jingle) +{ + purple_debug_info("jingle", "got session-info\n"); + jabber_iq_send(jabber_jingle_session_create_ack(session, jingle)); +} + +static void +jabber_jingle_session_handle_session_initiate(JingleSession *session, xmlnode *jingle) +{ + xmlnode *content = NULL; + xmlnode *description = NULL; + xmlnode *transport = NULL; + const char *initiator = NULL; + GList *codecs = NULL; + + if (!jingle) { + purple_debug_error("jingle", "Malformed request\n"); + return; + } + + initiator = xmlnode_get_attrib(jingle, "initiator"); + + for (content = xmlnode_get_child(jingle, "content"); content; + content = xmlnode_get_next_twin(content)) { + /* init media */ + if (!content) { + purple_debug_error("jingle", "jingle tag must contain content tag\n"); + /* should send error here */ + return; + } + + description = xmlnode_get_child(content, "description"); + + if (!description) { + purple_debug_error("jingle", "content tag must contain description tag\n"); + /* we should create an error iq here */ + return; + } + + transport = xmlnode_get_child(content, "transport"); + + if (!transport) { + purple_debug_error("jingle", "content tag must contain transport tag\n"); + /* we should create an error iq here */ + return; + } + + jabber_jingle_session_content_create_parse(session, content); + } + + if (!jabber_jingle_session_initiate_media_internal(session, initiator, initiator)) { + purple_debug_error("jingle", "Couldn't start media session with %s\n", initiator); + jabber_jingle_session_send_session_reject(session); + return; + } + + for (content = xmlnode_get_child(jingle, "content"); content; + content = xmlnode_get_next_twin(content)) { + /* init media */ + if (!content) { + purple_debug_error("jingle", "jingle tag must contain content tag\n"); + /* should send error here */ + return; + } + + description = xmlnode_get_child(content, "description"); + + if (!description) { + purple_debug_error("jingle", "content tag must contain description tag\n"); + /* we should create an error iq here */ + return; + } + codecs = jabber_jingle_get_codecs(description); + + purple_media_set_remote_codecs(session->media, + xmlnode_get_attrib(content, "name"), + initiator, codecs); + } + jabber_iq_send(jabber_jingle_session_create_ack(session, jingle)); + jabber_iq_send(jabber_jingle_session_create_session_info(session, "ringing")); + + purple_media_got_request(jabber_jingle_session_get_media(session)); +} + +static void +jabber_jingle_session_handle_session_terminate(JingleSession *session, xmlnode *jingle) +{ + if (!session) { + purple_debug_error("jingle", "jabber_handle_session_terminate couldn't find session\n"); + return; + } + + /* maybe we should look at the reasoncode to determine if it was + a hangup or a reject, and call different callbacks to purple_media */ + purple_media_got_hangup(jabber_jingle_session_get_media(session)); + jabber_iq_send(jabber_jingle_session_create_ack(session, jingle)); + jabber_jingle_session_destroy(session); +} + +static void +jabber_jingle_session_handle_transport_info(JingleSession *session, xmlnode *jingle) +{ + xmlnode *content = xmlnode_get_child(jingle, "content"); + xmlnode *transport = xmlnode_get_child(content, "transport"); + GList *remote_candidates = jabber_jingle_get_candidates(transport); + + if (!session) + purple_debug_error("jingle", "jabber_handle_session_candidates couldn't find session\n"); + + /* send acknowledement */ + jabber_iq_send(jabber_jingle_session_create_ack(session, jingle)); + + /* add candidates to our list of remote candidates */ + if (g_list_length(remote_candidates) > 0) { + purple_media_add_remote_candidates(session->media, + xmlnode_get_attrib(content, "name"), + xmlnode_get_attrib(xmlnode_get_parent(jingle), "from"), + remote_candidates); + fs_candidate_list_destroy(remote_candidates); + } +} + +void +jabber_jingle_session_parse(JabberStream *js, xmlnode *packet) +{ + const gchar *type = xmlnode_get_attrib(packet, "type"); + xmlnode *jingle; + const gchar *action; + const char *sid; + JingleSession *session; + + if (!type || strcmp(type, "set")) { + /* send iq error here */ + return; + } + + /* is this a Jingle package? */ + if (!(jingle = xmlnode_get_child(packet, "jingle"))) { + /* send iq error here */ + return; + } + + if (!(action = xmlnode_get_attrib(jingle, "action"))) { + /* send iq error here */ + return; + } + + purple_debug_info("jabber", "got Jingle package action = %s\n", + action); + + if (!(sid = xmlnode_get_attrib(jingle, "sid"))) { + /* send iq error here */ + return; + } + + if (!(session = jabber_jingle_session_find_by_id(js, sid)) + && strcmp(action, "session-initiate")) { + purple_debug_error("jingle", "jabber_jingle_session_parse couldn't find session\n"); + /* send iq error here */ + return; + } + + if (!strcmp(action, "session-initiate")) { + if (session) { + /* This should only happen if you start a session with yourself */ + purple_debug_error("jingle", "Jingle session with " + "id={%s} already exists\n", sid); + /* send iq error */ + } else if ((session = jabber_jingle_session_find_by_jid(js, + xmlnode_get_attrib(packet, "from")))) { + purple_debug_error("jingle", "Jingle session with " + "jid={%s} already exists\n", + xmlnode_get_attrib(packet, "from")); + /* send jingle redirect packet */ + return; + } else { + session = jabber_jingle_session_create_by_id(js, sid); + jabber_jingle_session_handle_session_initiate(session, jingle); + } + } else if (!strcmp(action, "session-accept") + || !strcmp(action, "content-accept")) { + jabber_jingle_session_handle_session_accept(session, jingle); + } else if (!strcmp(action, "session-info")) { + jabber_jingle_session_handle_session_info(session, jingle); + } else if (!strcmp(action, "session-terminate")) { + jabber_jingle_session_handle_session_terminate(session, jingle); + } else if (!strcmp(action, "transport-info")) { + jabber_jingle_session_handle_transport_info(session, jingle); + } else if (!strcmp(action, "content-replace")) { + jabber_jingle_session_handle_content_replace(session, jingle); + } +} + +#endif /* USE_VV */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/jingle.h Sat Sep 27 04:44:17 2008 +0000 @@ -0,0 +1,49 @@ +/* + * 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., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA + */ + +#ifndef JINGLE_H +#define JINGLE_H + +#include "config.h" +#include "jabber.h" +#include "media.h" + +#include <glib.h> +#include <glib-object.h> + +/* + * When Jingle content types other than voice and video are implemented, + * this #ifdef others surrounding Jingle code should be changed to just + * be around the voice and video specific parts. + */ +#ifdef USE_VV + +G_BEGIN_DECLS + +void jabber_jingle_session_parse(JabberStream *js, xmlnode *packet); + +PurpleMedia *jabber_jingle_session_initiate_media(JabberStream *js, + const char *who, + PurpleMediaSessionType type); + +void jabber_jingle_session_terminate_session_media(JabberStream *js, const gchar *who); +void jabber_jingle_session_terminate_sessions(JabberStream *js); + +G_END_DECLS + +#endif /* USE_VV */ + +#endif /* JINGLE_H */
--- a/libpurple/protocols/jabber/libxmpp.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Sat Sep 27 04:44:17 2008 +0000 @@ -116,9 +116,15 @@ jabber_unregister_account, /* unregister_user */ jabber_send_attention, /* send_attention */ jabber_attention_types, /* attention_types */ - sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ +#ifdef USE_VV + jabber_initiate_media, /* initiate_media */ + jabber_can_do_media /* can_do_media */ +#else + NULL, /* initiate_media */ + NULL /* can_do_media */ +#endif }; static gboolean load_plugin(PurplePlugin *plugin) @@ -289,6 +295,9 @@ jabber_custom_smileys_isenabled); jabber_pep_register_handler("avatar", AVATARNAMESPACEMETA, jabber_buddy_avatar_update_metadata); +#ifdef USE_VV + jabber_add_feature("voice-v1", "http://www.xmpp.org/extensions/xep-0167.html#ns", NULL); +#endif }
--- a/libpurple/protocols/jabber/presence.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/jabber/presence.c Sat Sep 27 04:44:17 2008 +0000 @@ -265,6 +265,10 @@ xmlnode_set_namespace(c, "http://jabber.org/protocol/caps"); xmlnode_set_attrib(c, "node", CAPS0115_NODE); xmlnode_set_attrib(c, "ver", VERSION); +#ifdef USE_VV + /* Make sure this is 'voice-v1', or you won't be able to talk to Google Talk */ + xmlnode_set_attrib(c, "ext", "voice-v1"); +#endif if(js != NULL) { /* add the extensions */
--- a/libpurple/protocols/msn/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/msn/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -91,4 +91,7 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ - $(DEBUG_CFLAGS) + $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS)
--- a/libpurple/protocols/msn/msn.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/msn/msn.c Sat Sep 27 04:44:17 2008 +0000 @@ -2531,9 +2531,10 @@ NULL, /* unregister_user */ msn_send_attention, /* send_attention */ msn_attention_types, /* attention_types */ - sizeof(PurplePluginProtocolInfo), /* struct_size */ msn_get_account_text_table, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = @@ -2615,3 +2616,4 @@ } PURPLE_INIT_PLUGIN(msn, init_plugin, info); +
--- a/libpurple/protocols/msnp9/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/msnp9/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -87,4 +87,8 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ - $(DEBUG_CFLAGS) + $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) +
--- a/libpurple/protocols/msnp9/msn.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/msnp9/msn.c Sat Sep 27 04:44:17 2008 +0000 @@ -2276,9 +2276,10 @@ NULL, /* unregister_user */ msn_send_attention, /* send_attention */ msn_attention_types, /* attention_types */ - sizeof(PurplePluginProtocolInfo), /* struct_size */ msn_get_account_text_table, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = @@ -2359,3 +2360,4 @@ } PURPLE_INIT_PLUGIN(msnp9, init_plugin, info); +
--- a/libpurple/protocols/myspace/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/myspace/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -40,4 +40,7 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ - $(DEBUG_CFLAGS) + $(FARSIGHT_CFLAGS) \ + $(DEBUG_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS)
--- a/libpurple/protocols/myspace/myspace.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/myspace/myspace.c Sat Sep 27 04:44:17 2008 +0000 @@ -3146,9 +3146,10 @@ NULL, /* unregister_user */ msim_send_attention, /* send_attention */ msim_attention_types, /* attention_types */ - - sizeof(PurplePluginProtocolInfo), /* struct_size */ + sizeof(PurplePluginProtocolInfo), /* struct_size */ msim_get_account_text_table, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ };
--- a/libpurple/protocols/novell/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/novell/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -54,4 +54,8 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(DEBUG_CFLAGS) \ - $(GLIB_CFLAGS) + $(FARSIGHT_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) +
--- a/libpurple/protocols/novell/novell.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/novell/novell.c Sat Sep 27 04:44:17 2008 +0000 @@ -3512,13 +3512,13 @@ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ - - /* padding */ - NULL, - NULL, - NULL, + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = { @@ -3573,3 +3573,4 @@ } PURPLE_INIT_PLUGIN(novell, init_plugin, info); +
--- a/libpurple/protocols/null/nullprpl.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/null/nullprpl.c Sat Sep 27 04:44:17 2008 +0000 @@ -1126,11 +1126,13 @@ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ - NULL, /* padding... */ - NULL, + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL, - sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static void nullprpl_init(PurplePlugin *plugin)
--- a/libpurple/protocols/oscar/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/oscar/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -76,4 +76,8 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ - $(DEBUG_CFLAGS) + $(FARSIGHT_CFLAGS) \ + $(DEBUG_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) +
--- a/libpurple/protocols/oscar/libaim.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/oscar/libaim.c Sat Sep 27 04:44:17 2008 +0000 @@ -95,9 +95,10 @@ NULL, /* unregister_user */ NULL, /* send_attention */ NULL, /* get_attention_types */ - sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info =
--- a/libpurple/protocols/oscar/libicq.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/oscar/libicq.c Sat Sep 27 04:44:17 2008 +0000 @@ -26,7 +26,6 @@ #include "oscarcommon.h" - static GHashTable * icq_get_account_text_table(PurpleAccount *account) { @@ -107,6 +106,8 @@ sizeof(PurplePluginProtocolInfo), /* struct_size */ icq_get_account_text_table, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info =
--- a/libpurple/protocols/qq/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/qq/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -84,4 +84,8 @@ -I$(top_builddir)/libpurple \ -DQQ_BUDDY_ICON_DIR=\"$(datadir)/pixmaps/purple/buddy_icons/qq\" \ $(DEBUG_CFLAGS) \ - $(GLIB_CFLAGS) + $(FARSIGHT_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) +
--- a/libpurple/protocols/qq/qq.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/qq/qq.c Sat Sep 27 04:44:17 2008 +0000 @@ -785,7 +785,9 @@ NULL, /* get attention_types */ sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = {
--- a/libpurple/protocols/sametime/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/sametime/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -32,7 +32,7 @@ AM_CFLAGS = \ - $(GLIB_CFLAGS) $(MEANWHILE_CFLAGS) \ + $(GLIB_CFLAGS) $(MEANWHILE_CFLAGS) $(FARSIGHT_CFLAGS) \ $(DEBUG_CFLAGS) \ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple
--- a/libpurple/protocols/sametime/sametime.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/sametime/sametime.c Sat Sep 27 04:44:17 2008 +0000 @@ -5188,7 +5188,8 @@ .new_xfer = mw_prpl_new_xfer, .offline_message = NULL, .whiteboard_prpl_ops = NULL, - .send_raw = NULL + .send_raw = NULL, + .struct_size = sizeof(PurplePluginProtocolInfo) };
--- a/libpurple/protocols/silc/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/silc/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -25,7 +25,7 @@ noinst_LIBRARIES = libsilcpurple_la_SOURCES = $(SILCSOURCES) -libsilcpurple_la_LIBADD = $(GLIB_LIBS) $(SILC_LIBS) +libsilcpurple_la_LIBADD = $(GLIB_LIBS) $(SILC_LIBS) $(FARSIGHT_LIBS) endif @@ -33,4 +33,8 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ - $(DEBUG_CFLAGS) + $(FARSIGHT_CFLAGS) \ + $(DEBUG_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) +
--- a/libpurple/protocols/silc/silc.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/silc/silc.c Sat Sep 27 04:44:17 2008 +0000 @@ -2103,13 +2103,13 @@ &silcpurple_wb_ops, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ - - /* padding */ - NULL, - NULL, - NULL, + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = @@ -2243,3 +2243,4 @@ } PURPLE_INIT_PLUGIN(silc, init_plugin, info); +
--- a/libpurple/protocols/silc10/silc.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/silc10/silc.c Sat Sep 27 04:44:17 2008 +0000 @@ -1836,12 +1836,13 @@ &silcpurple_wb_ops, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ - - NULL, - NULL, - NULL, + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info =
--- a/libpurple/protocols/simple/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/simple/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -33,4 +33,8 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ - $(DEBUG_CFLAGS) + $(FARSIGHT_CFLAGS) \ + $(DEBUG_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) +
--- a/libpurple/protocols/simple/simple.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/simple/simple.c Sat Sep 27 04:44:17 2008 +0000 @@ -2069,13 +2069,13 @@ NULL, /* whiteboard_prpl_ops */ simple_send_raw, /* send_raw */ NULL, /* roomlist_room_serialize */ - - /* padding */ - NULL, - NULL, - NULL, + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; @@ -2141,3 +2141,4 @@ } PURPLE_INIT_PLUGIN(simple, _init_plugin, info); +
--- a/libpurple/protocols/yahoo/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/yahoo/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -53,4 +53,8 @@ -I$(top_srcdir)/libpurple \ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ - $(DEBUG_CFLAGS) + $(FARSIGHT_CFLAGS) \ + $(DEBUG_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) +
--- a/libpurple/protocols/yahoo/yahoo.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/yahoo/yahoo.c Sat Sep 27 04:44:17 2008 +0000 @@ -4421,6 +4421,8 @@ sizeof(PurplePluginProtocolInfo), /* struct_size */ yahoo_get_account_text_table, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info =
--- a/libpurple/protocols/zephyr/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/zephyr/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -106,5 +106,9 @@ -I$(top_srcdir)/libpurple/protocols \ -DCONFDIR=\"$(sysconfdir)\" \ $(GLIB_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(KRB4_CFLAGS) \ - $(DEBUG_CFLAGS) + $(DEBUG_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ + $(LIBXML_CFLAGS) +
--- a/libpurple/protocols/zephyr/zephyr.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/protocols/zephyr/zephyr.c Sat Sep 27 04:44:17 2008 +0000 @@ -2915,7 +2915,9 @@ NULL, NULL, sizeof(PurplePluginProtocolInfo), /* struct_size */ - NULL + NULL, /* get_account_text_table */ + NULL, /* initate_media */ + NULL /* can_do_media */ }; static PurplePluginInfo info = {
--- a/libpurple/prpl.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/prpl.c Sat Sep 27 04:44:17 2008 +0000 @@ -494,6 +494,61 @@ got_attention(gc, id, who, type_code); } +PurpleMedia * +purple_prpl_initiate_media(PurpleAccount *account, + const char *who, + PurpleMediaSessionType type) +{ +#ifdef USE_VV + PurpleConnection *gc = NULL; + PurplePlugin *prpl = NULL; + PurplePluginProtocolInfo *prpl_info = NULL; + + if (account) + gc = purple_account_get_connection(account); + if (gc) + prpl = purple_connection_get_prpl(gc); + if (prpl) + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); + + if (prpl_info && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, initiate_media)) { + /* should check that the protocol supports this media type here? */ + return prpl_info->initiate_media(gc, who, type); + } else { + return NULL; + } +#else + return NULL; +#endif +} + +gboolean +purple_prpl_can_do_media(PurpleAccount *account, + const char *who, + PurpleMediaSessionType type) +{ +#ifdef USE_VV + PurpleConnection *gc = NULL; + PurplePlugin *prpl = NULL; + PurplePluginProtocolInfo *prpl_info = NULL; + + if (account) + gc = purple_account_get_connection(account); + if (gc) + prpl = purple_connection_get_prpl(gc); + if (prpl) + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); + + if (prpl_info && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, can_do_media)) { + return prpl_info->can_do_media(gc, who, type); + } else { + return FALSE; + } +#else + return FALSE; +#endif +} + /************************************************************************** * Protocol Plugin Subsystem API **************************************************************************/ @@ -515,3 +570,4 @@ return NULL; } +
--- a/libpurple/prpl.h Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/prpl.h Sat Sep 27 04:44:17 2008 +0000 @@ -65,6 +65,7 @@ #include "conversation.h" #include "ft.h" #include "imgstore.h" +#include "media.h" #include "notify.h" #include "proxy.h" #include "plugin.h" @@ -72,7 +73,6 @@ #include "status.h" #include "whiteboard.h" - /** @copydoc PurpleBuddyIconSpec */ struct _PurpleBuddyIconSpec { /** This is a comma-delimited list of image formats or @c NULL if icons @@ -404,7 +404,7 @@ * reasons. */ void (*unregister_user)(PurpleAccount *, PurpleAccountUnregistrationCb cb, void *user_data); - + /* Attention API for sending & receiving zaps/nudges/buzzes etc. */ gboolean (*send_attention)(PurpleConnection *gc, const char *username, guint type); GList *(*get_attention_types)(PurpleAccount *acct); @@ -440,6 +440,28 @@ * destroyed by the caller when it's no longer needed. */ GHashTable *(*get_account_text_table)(PurpleAccount *account); + + /** + * Initiate a media session with the given contact. + * + * @param conn The connection to initiate the media session on. + * @param who The remote user to initiate the session with. + * @param type The type of media session to initiate. + * @return The newly created media object. + */ + PurpleMedia *(*initiate_media)(PurpleConnection *gc, const char *who, + PurpleMediaSessionType type); + + /** + * Checks to see if the given contact supports the given type of media session. + * + * @param conn The connection the contact is on. + * @param who The remote user to check for media capability with. + * @param type The type of media session to check for. + * @return @c TRUE The contact supports the given media type, or @c FALSE otherwise. + */ + gboolean (*can_do_media)(PurpleConnection *gc, const char *who, + PurpleMediaSessionType type); }; #define PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl, member) \ @@ -728,6 +750,32 @@ */ void purple_prpl_got_attention_in_chat(PurpleConnection *gc, int id, const char *who, guint type_code); +/** + * Determines if the contact supports the given media session type. + * + * @param account The account the user is on. + * @param who The name of the contact to check capabilities for. + * @param type The type of media session to check for. + * + * @return @c TRUE if the contact supports the session type, else @c FALSE. + */ +gboolean purple_prpl_can_do_media(PurpleAccount *account, + const char *who, + PurpleMediaSessionType type); + +/** + * Initiates a media session with the given contact. + * + * @param account The account the user is on. + * @param who The name of the contact to start a session with. + * @param type The type of media session to start. + * + * @return The newly created session object. + */ +PurpleMedia *purple_prpl_initiate_media(PurpleAccount *account, + const char *who, + PurpleMediaSessionType type); + /*@}*/ /**************************************************************************/
--- a/libpurple/server.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/server.c Sat Sep 27 04:44:17 2008 +0000 @@ -37,6 +37,9 @@ #include "server.h" #include "status.h" #include "util.h" +#ifdef USE_VV +#include "media.h" +#endif #define SECS_BEFORE_RESENDING_AUTORESPONSE 600 #define SEX_BEFORE_RESENDING_AUTORESPONSE "Only after you're married" @@ -975,3 +978,4 @@ } } } +
--- a/libpurple/xmlnode.c Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/xmlnode.c Sat Sep 27 04:44:17 2008 +0000 @@ -307,6 +307,12 @@ return node->prefix; } +xmlnode *xmlnode_get_parent(const xmlnode *child) +{ + g_return_val_if_fail(child != NULL, NULL); + return child->parent; +} + void xmlnode_free(xmlnode *node) {
--- a/libpurple/xmlnode.h Fri Sep 26 16:26:56 2008 +0000 +++ b/libpurple/xmlnode.h Sat Sep 27 04:44:17 2008 +0000 @@ -246,6 +246,15 @@ const char *xmlnode_get_prefix(xmlnode *node); /** + * Gets the parent node. + * + * @param child The child node. + * + * @return The parent or NULL. + */ +xmlnode *xmlnode_get_parent(const xmlnode *child); + +/** * Returns the node in a string of xml. * * @param node The starting node to output.
--- a/pidgin/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -99,6 +99,7 @@ gtkimhtmltoolbar.c \ gtklog.c \ gtkmain.c \ + gtkmedia.c \ gtkmenutray.c \ gtknotify.c \ gtkplugin.c \ @@ -151,6 +152,7 @@ gtkimhtml.h \ gtkimhtmltoolbar.h \ gtklog.h \ + gtkmedia.c \ gtkmenutray.h \ gtknickcolors.h \ gtknotify.h \ @@ -196,6 +198,8 @@ $(STARTUP_NOTIFICATION_LIBS) \ $(LIBXML_LIBS) \ $(GTK_LIBS) \ + $(FARSIGHT_LIBS) \ + $(GSTPROPS_LIBS) \ $(top_builddir)/libpurple/libpurple.la if USE_INTERNAL_LIBGADU @@ -220,5 +224,7 @@ $(GTKSPELL_CFLAGS) \ $(STARTUP_NOTIFICATION_CFLAGS) \ $(LIBXML_CFLAGS) \ - $(INTGG_CFLAGS) + $(INTGG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTPROPS_CFLAGS) endif # ENABLE_GTK
--- a/pidgin/gtkconv.c Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/gtkconv.c Sat Sep 27 04:44:17 2008 +0000 @@ -46,6 +46,7 @@ #include "idle.h" #include "imgstore.h" #include "log.h" +#include "mediamanager.h" #include "notify.h" #include "prpl.h" #include "request.h" @@ -60,6 +61,7 @@ #include "gtkimhtml.h" #include "gtkimhtmltoolbar.h" #include "gtklog.h" +#include "gtkmedia.h" #include "gtkmenutray.h" #include "gtkpounce.h" #include "gtkprefs.h" @@ -1194,6 +1196,18 @@ gtk_widget_grab_focus(s->entry); } +#ifdef USE_VV +/* Forward declare this here, because I want to keep VV-related stuff together +for now */ +static void +menu_initiate_audio_call_cb(gpointer data, guint action, GtkWidget *widget); +static void +menu_initiate_video_call_cb(gpointer data, guint action, GtkWidget *widget); +static void +menu_initiate_audio_video_call_cb(gpointer data, guint action, GtkWidget *widget); + +#endif + static void menu_send_file_cb(gpointer data, guint action, GtkWidget *widget) { @@ -3073,6 +3087,17 @@ { "/Conversation/sep1", NULL, NULL, 0, "<Separator>", NULL }, +#ifdef USE_VV + { N_("/Conversation/M_edia"), NULL, NULL, 0, "<Branch>", NULL }, + + { N_("/Conversation/Media/_Audio Call"), NULL, menu_initiate_audio_call_cb, 0, + "<StockItem>", PIDGIN_STOCK_TOOLBAR_AUDIO_CALL }, + { N_("/Conversation/Media/_Video Call"), NULL, menu_initiate_video_call_cb, 0, + "<StockItem>", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL }, + { N_("/Conversation/Media/Audio\\/Video _Call"), NULL, menu_initiate_audio_video_call_cb, 0, + "<StockItem>", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL }, +#endif + { N_("/Conversation/Se_nd File..."), NULL, menu_send_file_cb, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SEND_FILE }, { N_("/Conversation/Add Buddy _Pounce..."), NULL, menu_add_pounce_cb, 0, "<Item>", NULL }, @@ -3383,6 +3408,18 @@ gtk_item_factory_get_widget(win->menu.item_factory, N_("/Conversation/View Log")); +#ifdef USE_VV + win->menu.audio_call = + gtk_item_factory_get_widget(win->menu.item_factory, + N_("/Conversation/Media/Audio Call")); + win->menu.video_call = + gtk_item_factory_get_widget(win->menu.item_factory, + N_("/Conversation/Media/Video Call")); + win->menu.audio_video_call = + gtk_item_factory_get_widget(win->menu.item_factory, + N_("/Conversation/Media/Audio\\/Video Call")); +#endif + /* --- */ win->menu.send_file = @@ -4730,7 +4767,7 @@ static GtkWidget * setup_common_pane(PidginConversation *gtkconv) { - GtkWidget *vbox, *frame, *imhtml_sw, *event_box; + GtkWidget *vbox, *hpaned, *frame, *imhtml_sw, *event_box; GtkCellRenderer *rend; GtkTreePath *path; PurpleConversation *conv = gtkconv->active_conv; @@ -4740,7 +4777,7 @@ int buddyicon_size = 0; /* Setup the top part of the pane */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + gtkconv->topvbox = vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); gtk_widget_show(vbox); /* Setup the info pane */ @@ -4810,23 +4847,21 @@ /* Setup the gtkimhtml widget */ frame = pidgin_create_imhtml(FALSE, >kconv->imhtml, NULL, &imhtml_sw); gtk_widget_set_size_request(gtkconv->imhtml, -1, 0); + + /* Add the gtkimhtml frame */ + gtkconv->middle_hpaned = hpaned = gtk_hpaned_new(); + gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0); + gtk_widget_show(hpaned); + gtk_paned_pack1(GTK_PANED(hpaned), frame, TRUE, TRUE); + if (chat) { - GtkWidget *hpaned; - /* Add the topic */ setup_chat_topic(gtkconv, vbox); - /* Add the gtkimhtml frame */ - hpaned = gtk_hpaned_new(); - gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0); - gtk_widget_show(hpaned); - gtk_paned_pack1(GTK_PANED(hpaned), frame, TRUE, TRUE); - /* Now add the userlist */ setup_chat_userlist(gtkconv, hpaned); - } else { - gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); - } + } + gtk_widget_show(frame); gtk_widget_set_name(gtkconv->imhtml, "pidgin_conv_imhtml"); @@ -6344,6 +6379,37 @@ else buttons &= ~GTK_IMHTML_CUSTOM_SMILEY; +#ifdef USE_VV + /* check if account support voice calls, and if the current buddy + supports it */ + if (account != NULL && purple_conversation_get_type(conv) + == PURPLE_CONV_TYPE_IM + && gtkconv->gtkmedia == NULL) { + gboolean audio = purple_prpl_can_do_media(account, + purple_conversation_get_name(conv), + PURPLE_MEDIA_AUDIO); + gboolean video = purple_prpl_can_do_media(account, + purple_conversation_get_name(conv), + PURPLE_MEDIA_VIDEO); + gboolean av = purple_prpl_can_do_media(account, + purple_conversation_get_name(conv), + PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO); + + gtk_widget_set_sensitive(win->menu.audio_call, audio ? TRUE : FALSE); + gtk_widget_set_sensitive(win->menu.video_call, video ? TRUE : FALSE); + gtk_widget_set_sensitive(win->menu.audio_video_call, av ? TRUE : FALSE); + } else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { + /* for now, don't care about chats... */ + gtk_widget_set_sensitive(win->menu.audio_call, FALSE); + gtk_widget_set_sensitive(win->menu.video_call, FALSE); + gtk_widget_set_sensitive(win->menu.audio_video_call, FALSE); + } else { + gtk_widget_set_sensitive(win->menu.audio_call, FALSE); + gtk_widget_set_sensitive(win->menu.video_call, FALSE); + gtk_widget_set_sensitive(win->menu.audio_video_call, FALSE); + } +#endif + gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->entry), buttons); if (account != NULL) gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(gtkconv->toolbar), purple_account_get_protocol_id(account)); @@ -6881,7 +6947,7 @@ gtk_event_box_set_visible_window(GTK_EVENT_BOX(event), FALSE); #endif gtk_widget_add_events(event, - GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK); + GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK); g_signal_connect(G_OBJECT(event), "button-press-event", G_CALLBACK(icon_menu), gtkconv); @@ -7624,6 +7690,108 @@ return TRUE; } + +#ifdef USE_VV + +static void +pidgin_gtkmedia_message_cb(PidginMedia *media, const char *msg, PurpleConversation *conv) +{ + purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, msg, PURPLE_MESSAGE_SYSTEM, time(NULL)); +} + +static void +menu_initiate_audio_call_cb(gpointer data, guint action, GtkWidget *widget) +{ + PidginWindow *win = (PidginWindow *)data; + PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win); + PurpleAccount *account = purple_conversation_get_account(conv); + + PurpleMedia *media = + purple_prpl_initiate_media(account, + purple_conversation_get_name(conv), + PURPLE_MEDIA_AUDIO); + + if (media) + purple_media_wait(media); +} + +static void +menu_initiate_video_call_cb(gpointer data, guint action, GtkWidget *widget) +{ + PidginWindow *win = (PidginWindow *)data; + PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win); + PurpleAccount *account = purple_conversation_get_account(conv); + + PurpleMedia *media = + purple_prpl_initiate_media(account, + purple_conversation_get_name(conv), + PURPLE_MEDIA_VIDEO); + + if (media) + purple_media_wait(media); +} + +static void +menu_initiate_audio_video_call_cb(gpointer data, guint action, GtkWidget *widget) +{ + PidginWindow *win = (PidginWindow *)data; + PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win); + PurpleAccount *account = purple_conversation_get_account(conv); + + PurpleMedia *media = + purple_prpl_initiate_media(account, + purple_conversation_get_name(conv), + PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO); + + if (media) + purple_media_wait(media); +} + +static void +pidgin_conv_gtkmedia_destroyed(GtkWidget *widget, PidginConversation *gtkconv) +{ + gtk_widget_destroyed(widget, &(gtkconv->gtkmedia)); + gray_stuff_out(gtkconv); +} + +static gboolean +pidgin_conv_new_media_cb(PurpleMediaManager *manager, PurpleMedia *media, gpointer nul) +{ + GtkWidget *gtkmedia; + PurpleConversation *conv; + PidginConversation *gtkconv; + gchar *name = purple_media_get_screenname(media); + + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, + purple_connection_get_account( + purple_media_get_connection(media)), name); + g_free(name); + + gtkconv = PIDGIN_CONVERSATION(conv); + + if (gtkconv->gtkmedia) { + purple_debug_info("gtkconv", "Media session exists for this conversation.\n"); + return FALSE; + } + + gtkmedia = pidgin_media_new(media); + gtk_box_pack_start(GTK_BOX(gtkconv->topvbox), gtkmedia, FALSE, FALSE, 0); + gtk_widget_show(gtkmedia); + g_signal_connect(G_OBJECT(gtkmedia), "message", G_CALLBACK(pidgin_gtkmedia_message_cb), conv); + + gtkconv->gtkmedia = gtkmedia; + g_signal_connect(G_OBJECT(gtkmedia), "destroy", G_CALLBACK( + pidgin_conv_gtkmedia_destroyed), gtkconv); + + gtk_paned_pack2(GTK_PANED(gtkconv->middle_hpaned), + pidgin_media_get_display_widget(gtkmedia), FALSE, TRUE); + + gray_stuff_out(gtkconv); + return TRUE; +} + +#endif + void * pidgin_conversations_get_handle(void) { @@ -7724,7 +7892,10 @@ show_protocol_icons_pref_cb, NULL); purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/im/hide_new", hide_new_pref_cb, NULL); - +#ifdef USE_VV + g_signal_connect(G_OBJECT(purple_media_manager_get()), "init-media", + G_CALLBACK(pidgin_conv_new_media_cb), NULL); +#endif /**********************************************************************
--- a/pidgin/gtkconv.h Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/gtkconv.h Sat Sep 27 04:44:17 2008 +0000 @@ -150,6 +150,7 @@ gpointer depr1; #endif + GtkWidget *middle_hpaned; GtkWidget *lower_hbox; GtkWidget *toolbar; @@ -169,6 +170,8 @@ GtkWidget *infopane; GtkListStore *infopane_model; GtkTreeIter infopane_iter; + GtkWidget *topvbox; + GtkWidget *gtkmedia; /* Used when attaching a PidginConversation to a PurpleConversation * with message history */
--- a/pidgin/gtkconvwin.h Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/gtkconvwin.h Sat Sep 27 04:44:17 2008 +0000 @@ -49,7 +49,11 @@ GtkWidget *menubar; GtkWidget *view_log; - +#ifdef USE_VV + GtkWidget *audio_call; + GtkWidget *video_call; + GtkWidget *audio_video_call; +#endif GtkWidget *send_file; GtkWidget *add_pounce; GtkWidget *get_info;
--- a/pidgin/gtkdebug.c Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/gtkdebug.c Sat Sep 27 04:44:17 2008 +0000 @@ -985,6 +985,13 @@ #ifdef USE_GSTREAMER REGISTER_G_LOG_HANDLER("GStreamer"); #endif +#ifdef USE_VV +#ifdef USE_FARSIGHT + REGISTER_G_LOG_HANDLER("farsight"); + REGISTER_G_LOG_HANDLER("farsight-transmitter"); + REGISTER_G_LOG_HANDLER("farsight-rtp"); +#endif +#endif #ifdef _WIN32 if (!purple_debug_is_enabled())
--- a/pidgin/gtkdialogs.c Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/gtkdialogs.c Sat Sep 27 04:44:17 2008 +0000 @@ -674,6 +674,12 @@ g_string_append(str, " <b>Tk:</b> Disabled<br/>"); } +#ifdef USE_VV + g_string_append(str, " <b>Voice and Video:</b> Enabled<br/>"); +#else + g_string_append(str, " <b>Voice and Video:</b> Disabled<br/>"); +#endif + #ifndef _WIN32 #ifdef USE_SM g_string_append(str, " <b>X Session Management:</b> Enabled<br/>");
--- a/pidgin/gtkimhtmltoolbar.c Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/gtkimhtmltoolbar.c Sat Sep 27 04:44:17 2008 +0000 @@ -40,6 +40,8 @@ #include "gtkthemes.h" #include "gtkutils.h" +#include "debug.h" + #include <gdk/gdkkeysyms.h> static GtkHBoxClass *parent_class = NULL; @@ -449,12 +451,12 @@ static void insert_hr_cb(GtkWidget *widget, GtkIMHtmlToolbar *toolbar) { - GtkTextIter iter; - GtkTextMark *ins; + GtkTextIter iter; + GtkTextMark *ins; GtkIMHtmlScalable *hr; - ins = gtk_text_buffer_get_insert(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml))); - gtk_text_buffer_get_iter_at_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml)), &iter, ins); + ins = gtk_text_buffer_get_insert(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml))); + gtk_text_buffer_get_iter_at_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml)), &iter, ins); hr = gtk_imhtml_hr_new(); gtk_imhtml_hr_add_to(hr, GTK_IMHTML(toolbar->imhtml), &iter); } @@ -1297,6 +1299,7 @@ GtkWidget *insert_button; GtkWidget *font_button; GtkWidget *smiley_button; + GtkWidget *font_menu; GtkWidget *insert_menu; GtkWidget *menuitem;
--- a/pidgin/gtkimhtmltoolbar.h Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/gtkimhtmltoolbar.h Sat Sep 27 04:44:17 2008 +0000 @@ -76,6 +76,7 @@ char *sml; GtkWidget *strikethrough; GtkWidget *insert_hr; + GtkWidget *call; }; struct _GtkIMHtmlToolbarClass {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkmedia.c Sat Sep 27 04:44:17 2008 +0000 @@ -0,0 +1,621 @@ +/** + * @file media.c Account API + * @ingroup core + * + * Pidgin + * + * Pidgin 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 <string.h> +#include "debug.h" +#include "internal.h" +#include "connection.h" +#include "media.h" +#include "pidgin.h" + +#include "gtkmedia.h" + +#ifdef USE_VV + +#include <gst/interfaces/xoverlay.h> + +typedef enum +{ + /* Waiting for response */ + PIDGIN_MEDIA_WAITING = 1, + /* Got request */ + PIDGIN_MEDIA_REQUESTED, + /* Accepted call */ + PIDGIN_MEDIA_ACCEPTED, + /* Rejected call */ + PIDGIN_MEDIA_REJECTED, +} PidginMediaState; + +struct _PidginMediaPrivate +{ + PurpleMedia *media; + GstElement *send_level; + GstElement *recv_level; + + GtkWidget *calling; + GtkWidget *accept; + GtkWidget *reject; + GtkWidget *hangup; + GtkWidget *mute; + + GtkWidget *send_progress; + GtkWidget *recv_progress; + + PidginMediaState state; + + GtkWidget *display; + GtkWidget *local_video; + GtkWidget *remote_video; +}; + +#define PIDGIN_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PIDGIN_TYPE_MEDIA, PidginMediaPrivate)) + +static void pidgin_media_class_init (PidginMediaClass *klass); +static void pidgin_media_init (PidginMedia *media); +static void pidgin_media_finalize (GObject *object); +static void pidgin_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); +static void pidgin_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); +static void pidgin_media_set_state(PidginMedia *gtkmedia, PidginMediaState state); + +static GtkHBoxClass *parent_class = NULL; + + + +enum { + MESSAGE, + LAST_SIGNAL +}; +static guint pidgin_media_signals[LAST_SIGNAL] = {0}; + +enum { + PROP_0, + PROP_MEDIA, + PROP_SEND_LEVEL, + PROP_RECV_LEVEL +}; + +GType +pidgin_media_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(PidginMediaClass), + NULL, + NULL, + (GClassInitFunc) pidgin_media_class_init, + NULL, + NULL, + sizeof(PidginMedia), + 0, + (GInstanceInitFunc) pidgin_media_init, + NULL + }; + type = g_type_register_static(GTK_TYPE_HBOX, "PidginMedia", &info, 0); + } + return type; +} + + +static void +pidgin_media_class_init (PidginMediaClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass*)klass; +/* GtkContainerClass *container_class = (GtkContainerClass*)klass; */ + parent_class = g_type_class_peek_parent(klass); + + gobject_class->finalize = pidgin_media_finalize; + gobject_class->set_property = pidgin_media_set_property; + gobject_class->get_property = pidgin_media_get_property; + + g_object_class_install_property(gobject_class, PROP_MEDIA, + g_param_spec_object("media", + "PurpleMedia", + "The PurpleMedia associated with this media.", + PURPLE_TYPE_MEDIA, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + g_object_class_install_property(gobject_class, PROP_SEND_LEVEL, + g_param_spec_object("send-level", + "Send level", + "The GstElement of this media's send 'level'", + GST_TYPE_ELEMENT, + G_PARAM_READWRITE)); + g_object_class_install_property(gobject_class, PROP_RECV_LEVEL, + g_param_spec_object("recv-level", + "Receive level", + "The GstElement of this media's recv 'level'", + GST_TYPE_ELEMENT, + G_PARAM_READWRITE)); + + pidgin_media_signals[MESSAGE] = g_signal_new("message", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + g_type_class_add_private(klass, sizeof(PidginMediaPrivate)); +} + +static void +pidgin_media_mute_toggled(GtkToggleButton *toggle, PidginMedia *media) +{ + purple_media_mute(media->priv->media, + gtk_toggle_button_get_active(toggle)); +} + +static void +pidgin_media_init (PidginMedia *media) +{ + media->priv = PIDGIN_MEDIA_GET_PRIVATE(media); + media->priv->calling = gtk_label_new_with_mnemonic("Calling..."); + media->priv->hangup = gtk_button_new_with_label("Hangup"); + media->priv->accept = gtk_button_new_with_label("Accept"); + media->priv->reject = gtk_button_new_with_label("Reject"); + media->priv->mute = gtk_toggle_button_new_with_label("Mute"); + + g_signal_connect(media->priv->mute, "toggled", + G_CALLBACK(pidgin_media_mute_toggled), media); + + gtk_box_pack_start(GTK_BOX(media), media->priv->calling, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(media), media->priv->hangup, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(media), media->priv->accept, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(media), media->priv->reject, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(media), media->priv->mute, FALSE, FALSE, 0); + + gtk_widget_show_all(media->priv->accept); + gtk_widget_show_all(media->priv->reject); + + media->priv->display = gtk_vbox_new(TRUE, PIDGIN_HIG_BOX_SPACE); +} + +static gboolean +level_message_cb(GstBus *bus, GstMessage *message, PidginMedia *gtkmedia) +{ + const GstStructure *s; + gchar *name; + + gdouble rms_db; + gdouble percent; + const GValue *list; + const GValue *value; + + GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(message)); + + if (message->type != GST_MESSAGE_ELEMENT) + return TRUE; + + s = gst_message_get_structure(message); + + if (strcmp(gst_structure_get_name(s), "level")) + return TRUE; + + list = gst_structure_get_value(s, "rms"); + + /* Only bother with the first channel. */ + value = gst_value_list_get_value(list, 0); + rms_db = g_value_get_double(value); + + percent = pow(10, rms_db / 20) * 5; + + if(percent > 1.0) + percent = 1.0; + + name = gst_element_get_name(src); + if (!strcmp(name, "sendlevel")) + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(gtkmedia->priv->send_progress), percent); + else + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(gtkmedia->priv->recv_progress), percent); + + g_free(name); + return TRUE; +} + + +static void +pidgin_media_disconnect_levels(PurpleMedia *media, PidginMedia *gtkmedia) +{ + GstElement *element = purple_media_get_pipeline(media); + gulong handler_id = g_signal_handler_find(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element))), + G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, + NULL, G_CALLBACK(level_message_cb), gtkmedia); + if (handler_id) + g_signal_handler_disconnect(G_OBJECT(gst_pipeline_get_bus(GST_PIPELINE(element))), + handler_id); +} + +static void +pidgin_media_finalize (GObject *media) +{ + PidginMedia *gtkmedia = PIDGIN_MEDIA(media); + if (gtkmedia->priv->media) { + pidgin_media_disconnect_levels(gtkmedia->priv->media, gtkmedia); + g_object_unref(gtkmedia->priv->media); + } + if (gtkmedia->priv->send_level) + gst_object_unref(gtkmedia->priv->send_level); + if (gtkmedia->priv->recv_level) + gst_object_unref(gtkmedia->priv->recv_level); + if (gtkmedia->priv->display) + gtk_widget_destroy(gtkmedia->priv->display); +} + +static void +pidgin_media_emit_message(PidginMedia *gtkmedia, const char *msg) +{ + g_signal_emit(gtkmedia, pidgin_media_signals[MESSAGE], 0, msg); +} + +GtkWidget * +pidgin_media_get_display_widget(GtkWidget *gtkmedia) +{ + return PIDGIN_MEDIA_GET_PRIVATE(gtkmedia)->display; +} + +static gboolean +create_window (GstBus *bus, GstMessage *message, PidginMedia *gtkmedia) +{ + char *name; + + if (GST_MESSAGE_TYPE(message) != GST_MESSAGE_ELEMENT) + return TRUE; + + if (!gst_structure_has_name(message->structure, "prepare-xwindow-id")) + return TRUE; + + name = gst_object_get_name(GST_MESSAGE_SRC (message)); + purple_debug_info("gtkmedia", "prepare-xwindow-id object name: %s\n", name); + + /* The XOverlay's name is the sink's name with a suffix */ + if (!strncmp(name, "purplevideosink", strlen("purplevideosink"))) + gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(GST_MESSAGE_SRC(message)), + GDK_WINDOW_XWINDOW(gtkmedia->priv->remote_video->window)); + else if (!strncmp(name, "purplelocalvideosink", strlen("purplelocalvideosink"))) + gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(GST_MESSAGE_SRC(message)), + GDK_WINDOW_XWINDOW(gtkmedia->priv->local_video->window)); + g_free(name); + return TRUE; +} + +static void +pidgin_media_ready_cb(PurpleMedia *media, PidginMedia *gtkmedia) +{ + GstElement *audiosendbin = NULL, *audiosendlevel = NULL; + GstElement *audiorecvbin = NULL, *audiorecvlevel = NULL; + GstElement *videosendbin = NULL; + GstElement *videorecvbin = NULL; + + GList *sessions = purple_media_get_session_names(media); + + for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { + PurpleMediaSessionType type = purple_media_get_session_type(media, sessions->data); + if (type & PURPLE_MEDIA_AUDIO) { + if (!audiosendbin && (type & PURPLE_MEDIA_SEND_AUDIO)) { + purple_media_audio_init_src(&audiosendbin, &audiosendlevel); + purple_media_set_src(media, sessions->data, audiosendbin); + gst_element_set_state(audiosendbin, GST_STATE_PLAYING); + } + if (!audiorecvbin && (type & PURPLE_MEDIA_RECV_AUDIO)) { + purple_media_audio_init_recv(&audiorecvbin, &audiorecvlevel); + purple_media_set_sink(media, sessions->data, audiorecvbin); + gst_element_set_state(audiorecvbin, GST_STATE_READY); + } + } else if (type & PURPLE_MEDIA_VIDEO) { + if (!videosendbin && (type & PURPLE_MEDIA_SEND_VIDEO)) { + purple_media_video_init_src(&videosendbin); + purple_media_set_src(media, sessions->data, videosendbin); + gst_element_set_state(videosendbin, GST_STATE_PLAYING); + } + if (!videorecvbin && (type & PURPLE_MEDIA_RECV_VIDEO)) { + purple_media_video_init_recv(&videorecvbin); + purple_media_set_sink(media, sessions->data, videorecvbin); + gst_element_set_state(videorecvbin, GST_STATE_READY); + } + } + } + + if (audiosendlevel || audiorecvlevel) { + g_object_set(gtkmedia, "send-level", audiosendlevel, + "recv-level", audiorecvlevel, + NULL); + } +} + +static void +pidgin_media_wait_cb(PurpleMedia *media, PidginMedia *gtkmedia) +{ + pidgin_media_set_state(gtkmedia, PIDGIN_MEDIA_WAITING); +} + +/* maybe we should have different callbacks for when we received the accept + and we accepted ourselves */ +static void +pidgin_media_accept_cb(PurpleMedia *media, PidginMedia *gtkmedia) +{ + GtkWidget *send_widget = NULL, *recv_widget = NULL; + + GstElement *pipeline = purple_media_get_pipeline(media); + GstElement *audiosendbin = NULL; + GstElement *audiorecvbin = NULL; + GstElement *videosendbin = NULL; + GstElement *videorecvbin = NULL; + GstBus *bus; + + pidgin_media_emit_message(gtkmedia, _("Call in progress.")); + pidgin_media_set_state(gtkmedia, PIDGIN_MEDIA_ACCEPTED); + + purple_media_get_elements(media, &audiosendbin, &audiorecvbin, + &videosendbin, &videorecvbin); + + if (videorecvbin || audiorecvbin) { + recv_widget = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + gtk_box_pack_start(GTK_BOX(gtkmedia->priv->display), + recv_widget, TRUE, TRUE, 0); + gtk_widget_show(recv_widget); + } + if (videosendbin || audiosendbin) { + send_widget = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + gtk_box_pack_start(GTK_BOX(gtkmedia->priv->display), + send_widget, TRUE, TRUE, 0); + gtk_widget_show(send_widget); + } + + if (videorecvbin) { + GtkWidget *aspect; + GtkWidget *remote_video; + + aspect = gtk_aspect_frame_new(NULL, 0.5, 0.5, 4.0/3.0, FALSE); + gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN); + gtk_box_pack_start(GTK_BOX(recv_widget), aspect, TRUE, TRUE, 0); + + remote_video = gtk_drawing_area_new(); + gtk_container_add(GTK_CONTAINER(aspect), remote_video); + gtk_widget_set_size_request (GTK_WIDGET(remote_video), 100, -1); + gtk_widget_show(remote_video); + gtk_widget_show(aspect); + + gtkmedia->priv->remote_video = remote_video; + gst_element_set_state(videorecvbin, GST_STATE_PLAYING); + } + if (videosendbin) { + GtkWidget *aspect; + GtkWidget *local_video; + GstElement *tee, *queue; + + aspect = gtk_aspect_frame_new(NULL, 0.5, 0.5, 4.0/3.0, FALSE); + gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN); + gtk_box_pack_start(GTK_BOX(send_widget), aspect, TRUE, TRUE, 0); + + local_video = gtk_drawing_area_new(); + gtk_container_add(GTK_CONTAINER(aspect), local_video); + gtk_widget_set_size_request (GTK_WIDGET(local_video), 100, -1); + gtk_widget_show(local_video); + gtk_widget_show(aspect); + + gtkmedia->priv->local_video = local_video; + + tee = gst_bin_get_by_name(GST_BIN(videosendbin), "purplevideosrctee"); + queue = gst_bin_get_by_name(GST_BIN(videosendbin), "purplelocalvideoqueue"); + gst_element_link(tee, queue); + gst_object_unref(tee); + gst_object_unref(queue); + } + + if (audiorecvbin) { + gtkmedia->priv->recv_progress = gtk_progress_bar_new(); + gtk_widget_set_size_request(gtkmedia->priv->recv_progress, 10, 70); + gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(gtkmedia->priv->recv_progress), + GTK_PROGRESS_BOTTOM_TO_TOP); + gtk_box_pack_end(GTK_BOX(recv_widget), + gtkmedia->priv->recv_progress, FALSE, FALSE, 0); + gtk_widget_show(gtkmedia->priv->recv_progress); + gst_element_set_state(audiorecvbin, GST_STATE_PLAYING); + } + if (audiosendbin) { + gtkmedia->priv->send_progress = gtk_progress_bar_new(); + gtk_widget_set_size_request(gtkmedia->priv->send_progress, 10, 70); + gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(gtkmedia->priv->send_progress), + GTK_PROGRESS_BOTTOM_TO_TOP); + gtk_box_pack_end(GTK_BOX(send_widget), + gtkmedia->priv->send_progress, FALSE, FALSE, 0); + gtk_widget_show(gtkmedia->priv->send_progress); + + gtk_widget_show(gtkmedia->priv->mute); + } + + if (audiorecvbin || audiosendbin || videorecvbin || videosendbin) + gtk_widget_show(gtkmedia->priv->display); + + bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); + if (audiorecvbin || audiosendbin) + g_signal_connect(G_OBJECT(bus), "message::element", + G_CALLBACK(level_message_cb), gtkmedia); + if (videorecvbin || videosendbin) + gst_bus_set_sync_handler(bus, (GstBusSyncHandler)create_window, gtkmedia); + gst_object_unref(bus); +} + +static void +pidgin_media_hangup_cb(PurpleMedia *media, PidginMedia *gtkmedia) +{ + pidgin_media_emit_message(gtkmedia, _("You have ended the call.")); + gtk_widget_destroy(GTK_WIDGET(gtkmedia)); +} + +static void +pidgin_media_got_request_cb(PurpleMedia *media, PidginMedia *gtkmedia) +{ + PurpleMediaSessionType type = purple_media_get_overall_type(media); + gchar *message; + gchar *name = purple_media_get_screenname(media); + + if (type & PURPLE_MEDIA_AUDIO && type & PURPLE_MEDIA_VIDEO) { + message = g_strdup_printf(_("%s wishes to start an audio/video session with you."), + name); + } else if (type & PURPLE_MEDIA_AUDIO) { + message = g_strdup_printf(_("%s wishes to start an audio session with you."), + name); + } else if (type & PURPLE_MEDIA_VIDEO) { + message = g_strdup_printf(_("%s wishes to start a video session with you."), + name); + } else { + g_free(name); + return; + } + + g_free(name); + pidgin_media_emit_message(gtkmedia, message); + g_free(message); +} + +static void +pidgin_media_got_hangup_cb(PurpleMedia *media, PidginMedia *gtkmedia) +{ + pidgin_media_emit_message(gtkmedia, _("The call has been terminated.")); + gtk_widget_destroy(GTK_WIDGET(gtkmedia)); +} + +static void +pidgin_media_reject_cb(PurpleMedia *media, PidginMedia *gtkmedia) +{ + pidgin_media_emit_message(gtkmedia, _("You have rejected the call.")); + gtk_widget_destroy(GTK_WIDGET(gtkmedia)); +} + +static void +pidgin_media_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + PidginMedia *media; + g_return_if_fail(PIDGIN_IS_MEDIA(object)); + + media = PIDGIN_MEDIA(object); + switch (prop_id) { + case PROP_MEDIA: + if (media->priv->media) + g_object_unref(media->priv->media); + media->priv->media = g_value_get_object(value); + g_object_ref(media->priv->media); + g_signal_connect_swapped(G_OBJECT(media->priv->accept), "clicked", + G_CALLBACK(purple_media_accept), media->priv->media); + g_signal_connect_swapped(G_OBJECT(media->priv->reject), "clicked", + G_CALLBACK(purple_media_reject), media->priv->media); + g_signal_connect_swapped(G_OBJECT(media->priv->hangup), "clicked", + G_CALLBACK(purple_media_hangup), media->priv->media); + + g_signal_connect(G_OBJECT(media->priv->media), "accepted", + G_CALLBACK(pidgin_media_accept_cb), media); + g_signal_connect(G_OBJECT(media->priv->media) ,"ready", + G_CALLBACK(pidgin_media_ready_cb), media); + g_signal_connect(G_OBJECT(media->priv->media) ,"wait", + G_CALLBACK(pidgin_media_wait_cb), media); + g_signal_connect(G_OBJECT(media->priv->media), "hangup", + G_CALLBACK(pidgin_media_hangup_cb), media); + g_signal_connect(G_OBJECT(media->priv->media), "reject", + G_CALLBACK(pidgin_media_reject_cb), media); + g_signal_connect(G_OBJECT(media->priv->media), "got-request", + G_CALLBACK(pidgin_media_got_request_cb), media); + g_signal_connect(G_OBJECT(media->priv->media), "got-hangup", + G_CALLBACK(pidgin_media_got_hangup_cb), media); + g_signal_connect(G_OBJECT(media->priv->media), "got-accept", + G_CALLBACK(pidgin_media_accept_cb), media); + break; + case PROP_SEND_LEVEL: + if (media->priv->send_level) + gst_object_unref(media->priv->send_level); + media->priv->send_level = g_value_get_object(value); + g_object_ref(media->priv->send_level); + break; + case PROP_RECV_LEVEL: + if (media->priv->recv_level) + gst_object_unref(media->priv->recv_level); + media->priv->recv_level = g_value_get_object(value); + g_object_ref(media->priv->recv_level); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +pidgin_media_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + PidginMedia *media; + g_return_if_fail(PIDGIN_IS_MEDIA(object)); + + media = PIDGIN_MEDIA(object); + + switch (prop_id) { + case PROP_MEDIA: + g_value_set_object(value, media->priv->media); + break; + case PROP_SEND_LEVEL: + g_value_set_object(value, media->priv->send_level); + break; + case PROP_RECV_LEVEL: + g_value_set_object(value, media->priv->recv_level); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +GtkWidget * +pidgin_media_new(PurpleMedia *media) +{ + PidginMedia *gtkmedia = g_object_new(pidgin_media_get_type(), + "media", media, NULL); + return GTK_WIDGET(gtkmedia); +} + +static void +pidgin_media_set_state(PidginMedia *gtkmedia, PidginMediaState state) +{ + gtkmedia->priv->state = state; + switch (state) { + case PIDGIN_MEDIA_WAITING: + gtk_widget_show(gtkmedia->priv->calling); + gtk_widget_hide(gtkmedia->priv->accept); + gtk_widget_hide(gtkmedia->priv->reject); + gtk_widget_show(gtkmedia->priv->hangup); + break; + case PIDGIN_MEDIA_REQUESTED: + gtk_widget_hide(gtkmedia->priv->calling); + gtk_widget_show(gtkmedia->priv->accept); + gtk_widget_show(gtkmedia->priv->reject); + gtk_widget_hide(gtkmedia->priv->hangup); + break; + case PIDGIN_MEDIA_ACCEPTED: + gtk_widget_show(gtkmedia->priv->hangup); + gtk_widget_hide(gtkmedia->priv->calling); + gtk_widget_hide(gtkmedia->priv->accept); + gtk_widget_hide(gtkmedia->priv->reject); + break; + default: + break; + } +} + +#endif /* USE_VV */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkmedia.h Sat Sep 27 04:44:17 2008 +0000 @@ -0,0 +1,70 @@ +/** + * @file media.h Account API + * @ingroup core + * + * Pidgin + * + * Pidgin 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 __GTKMEDIA_H_ +#define __GTKMEDIA_H_ + +#ifdef USE_VV + +#include <gtk/gtk.h> +#include <glib-object.h> + +#include "connection.h" + +G_BEGIN_DECLS + +#define PIDGIN_TYPE_MEDIA (pidgin_media_get_type()) +#define PIDGIN_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), PIDGIN_TYPE_MEDIA, PidginMedia)) +#define PIDGIN_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), PIDGIN_TYPE_MEDIA, PidginMediaClass)) +#define PIDGIN_IS_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PIDGIN_TYPE_MEDIA)) +#define PIDGIN_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PIDGIN_TYPE_MEDIA)) +#define PIDGIN_MEDIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PIDGIN_TYPE_MEDIA, PidginMediaClass)) + +typedef struct _PidginMedia PidginMedia; +typedef struct _PidginMediaClass PidginMediaClass; +typedef struct _PidginMediaPrivate PidginMediaPrivate; + +struct _PidginMediaClass +{ + GtkHBoxClass parent_class; +}; + +struct _PidginMedia +{ + GtkHBox parent; + PidginMediaPrivate *priv; +}; + +GType pidgin_media_get_type(void); + +GtkWidget *pidgin_media_new(PurpleMedia *media); +GtkWidget *pidgin_media_get_display_widget(GtkWidget *gtkmedia); + +G_END_DECLS + +#endif /* USE_VV */ + + +#endif /* __GTKMEDIA_H_ */
--- a/pidgin/gtkprefs.c Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/gtkprefs.c Sat Sep 27 04:44:17 2008 +0000 @@ -28,6 +28,9 @@ #include "pidgin.h" #include "debug.h" +#ifdef USE_VV +#include "mediamanager.h" +#endif #include "notify.h" #include "prefs.h" #include "proxy.h" @@ -145,12 +148,10 @@ if (type == PURPLE_PREF_INT) { int_value = GPOINTER_TO_INT(g_object_get_data(w, "value")); - purple_prefs_set_int(key, int_value); } else if (type == PURPLE_PREF_STRING) { str_value = (const char *)g_object_get_data(w, "value"); - purple_prefs_set_string(key, str_value); } else if (type == PURPLE_PREF_BOOLEAN) { @@ -945,7 +946,7 @@ _("Never"), "never", NULL); gtk_size_group_add_widget(sg, label); - gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); vbox = pidgin_make_frame(ret, _("Conversation Window Hiding")); label = pidgin_prefs_dropdown(vbox, _("_Hide new IM conversations:"), @@ -955,7 +956,7 @@ _("Always"), "always", NULL); gtk_size_group_add_widget(sg, label); - gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); /* All the tab options! */ @@ -990,7 +991,7 @@ #endif NULL); gtk_size_group_add_widget(sg, label); - gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); names = pidgin_conv_placement_get_options(); label = pidgin_prefs_dropdown_from_list(vbox2, _("N_ew conversations:"), @@ -2042,6 +2043,313 @@ return ret; } +#ifdef USE_VV + +/* get a GList of pairs name / device */ +static GList * +get_device_items(const gchar *plugin) +{ + GList *ret = NULL; + GList *devices = purple_media_get_devices(plugin); + GstElement *element = gst_element_factory_make(plugin, NULL); + + if (element == NULL) + return NULL; + + for(; devices ; devices = g_list_delete_link(devices, devices)) { + gchar *name; + g_object_set(G_OBJECT(element), "device", devices->data, NULL); + g_object_get(G_OBJECT(element), "device-name", &name, NULL); + ret = g_list_append(ret, name); + ret = g_list_append(ret, devices->data); + } + + gst_object_unref(element); + return ret; +} + +/* + * Test functions to run video preview + */ +static gboolean +preview_video_bus_call(GstBus *bus, GstMessage *msg, gpointer pipeline) +{ + switch(GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: + purple_debug_info("preview-video", "End of Stream\n"); + break; + case GST_MESSAGE_ERROR: { + gchar *debug = NULL; + GError *err = NULL; + + gst_message_parse_error(msg, &err, &debug); + + purple_debug_error("preview-video", "Error: %s\n", err->message); + g_error_free(err); + + if (debug) { + purple_debug_error("preview-video", "details: %s\n", debug); + g_free (debug); + } + break; + } + default: + return TRUE; + } + + gst_element_set_state(pipeline, GST_STATE_NULL); + gst_object_unref(GST_PIPELINE(pipeline)); + return FALSE; +} + +static void +preview_button_clicked(GtkWidget *widget, gpointer *data) +{ + const char *plugin = purple_prefs_get_string("/purple/media/video/plugin"); + const char *device = purple_prefs_get_string("/purple/media/video/device"); + GstBus *bus; + + /* create a preview window... */ + GstElement *pipeline = NULL; + GError *p_err = NULL; + + gchar *test_pipeline_str = NULL; + + if (strlen(device) > 0) + test_pipeline_str = g_strdup_printf("%s device=\"%s\" !" \ + " ffmpegcolorspace !" \ + " autovideosink", + plugin, device); + else + test_pipeline_str = g_strdup_printf("%s ! ffmpegcolorspace !" \ + " autovideosink", plugin); + + pipeline = gst_parse_launch (test_pipeline_str, &p_err); + + g_free(test_pipeline_str); + + if (pipeline == NULL) { + purple_debug_error("gtkprefs", + "Error starting preview: %s\n", p_err->message); + g_error_free(p_err); + return; + } + + bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); + gst_bus_add_watch(bus, preview_video_bus_call, pipeline); + gst_object_unref(bus); + + gst_element_set_state(pipeline, GST_STATE_PLAYING); +} + +static void +media_plugin_changed_cb(const gchar *name, PurplePrefType type, + gconstpointer value, gpointer data) +{ + GtkWidget *hbox = data; + GtkWidget *dd = NULL; + GtkWidget *preview_button = NULL; + const char *plugin = value; + const char *device = purple_prefs_get_string("/purple/media/video/device"); + GList *video_items = get_device_items(plugin); + GList *list; + + if (video_items == NULL) { + video_items = g_list_prepend(video_items, g_strdup("")); + video_items = g_list_prepend(video_items, g_strdup("Default")); + } + + if (g_list_find(video_items, device) == NULL) + { + purple_prefs_set_string("/purple/media/video/device", + g_list_next(video_items)->data); + } + + list = gtk_container_get_children(GTK_CONTAINER(hbox)); + + while (list) { + gtk_widget_destroy(list->data); + list = g_list_delete_link(list, list); + } + + dd = pidgin_prefs_dropdown_from_list(hbox, _("_Device:"), PURPLE_PREF_STRING, + "/purple/media/video/device", + video_items); + + gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5); + + preview_button = gtk_button_new_with_mnemonic(_("_Preview")); + g_signal_connect(G_OBJECT(preview_button), "clicked", + G_CALLBACK(preview_button_clicked), NULL); + + gtk_container_add(GTK_CONTAINER(hbox), preview_button); + + gtk_widget_show_all(hbox); +} + +static void +prefs_media_input_volume_changed(GtkRange *range) +{ + double val = (double)gtk_range_get_value(GTK_RANGE(range)); + GList *medias = purple_media_manager_get_media(purple_media_manager_get()); + purple_prefs_set_int("/purple/media/audio/volume/input", val); + + val /= 10.0; + for (; medias; medias = g_list_next(medias)) { + PurpleMedia *media = PURPLE_MEDIA(medias->data); + GList *sessions = purple_media_get_session_names(media); + for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { + const gchar *session = sessions->data; + if (purple_media_get_session_type(media, session) + & PURPLE_MEDIA_SEND_AUDIO) { + GstElement *volume = gst_bin_get_by_name( + GST_BIN(purple_media_get_src(media, session)), + "purpleaudioinputvolume"); + g_object_set(volume, "volume", val, NULL); + } + } + } +} + +static void +prefs_media_output_volume_changed(GtkRange *range) +{ + double val = (double)gtk_range_get_value(GTK_RANGE(range)); + GList *medias = purple_media_manager_get_media(purple_media_manager_get()); + purple_prefs_set_int("/purple/media/audio/volume/output", val); + + val /= 10.0; + for (; medias; medias = g_list_next(medias)) { + PurpleMedia *media = PURPLE_MEDIA(medias->data); + GList *sessions = purple_media_get_session_names(media); + for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { + const gchar *session = sessions->data; + if (purple_media_get_session_type(media, session) + & PURPLE_MEDIA_RECV_AUDIO) { + GstElement *volume = gst_bin_get_by_name( + GST_BIN(purple_media_get_sink(media, session)), + "purpleaudiooutputvolume"); + g_object_set(volume, "volume", val, NULL); + } + } + } +} + +static GtkWidget * +media_page() +{ + GtkWidget *ret; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *dd; + GtkWidget *preview_button; + GtkWidget *sw; + GtkSizeGroup *sg, *sg2; + const char *plugin = purple_prefs_get_string("/purple/media/video/plugin"); + const char *device = purple_prefs_get_string("/purple/media/video/device"); + GList *video_items = get_device_items(plugin); + GList *audio_items = get_device_items("alsasrc"); + + if (video_items == NULL) { + video_items = g_list_prepend(video_items, ""); + video_items = g_list_prepend(video_items, "Default"); + } + + if (g_list_find(video_items, device) == NULL) + { + purple_prefs_set_string("/purple/media/video/device", + g_list_next(video_items)->data); + } + + ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); + gtk_container_set_border_width (GTK_CONTAINER (ret), PIDGIN_HIG_BORDER); + + sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + sg2 = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + + vbox = pidgin_make_frame (ret, _("Video Input")); + gtk_size_group_add_widget(sg2, vbox); + + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + dd = pidgin_prefs_dropdown(vbox, _("_Plugin:"), PURPLE_PREF_STRING, + "/purple/media/video/plugin", + _("Default"), "gconfvideosrc", + _("Video4Linux"), "v4lsrc", + _("Video4Linux2"), "v4l2src", + _("Video Test Source"), "videotestsrc", + NULL); + + gtk_size_group_add_widget(sg, dd); + gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5); + + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + dd = pidgin_prefs_dropdown_from_list(hbox, _("Device:"), PURPLE_PREF_STRING, + "/purple/media/video/device", + video_items); + + purple_prefs_connect_callback(prefs, "/purple/media/video/plugin", + media_plugin_changed_cb, hbox); + + g_signal_connect_swapped(hbox, "destroy", + G_CALLBACK(purple_prefs_disconnect_by_handle), hbox); + + gtk_size_group_add_widget(sg, dd); + gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5); + + preview_button = gtk_button_new_with_mnemonic(_("_Preview")); + g_signal_connect(G_OBJECT(preview_button), "clicked", + G_CALLBACK(preview_button_clicked), NULL); + + gtk_container_add(GTK_CONTAINER(hbox), preview_button); + gtk_container_add(GTK_CONTAINER(vbox), hbox); + + vbox = pidgin_make_frame (ret, _("Audio Input")); + gtk_size_group_add_widget(sg2, vbox); + dd = pidgin_prefs_dropdown_from_list(vbox, _("Device:"), PURPLE_PREF_STRING, + "/purple/media/audio/device", + audio_items); + + gtk_size_group_add_widget(sg, dd); + gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5); + + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + /* Input Volume */ + sw = gtk_hscale_new_with_range(0.0, 100.0, 5.0); + gtk_range_set_increments(GTK_RANGE(sw), 5.0, 25.0); + gtk_range_set_value(GTK_RANGE(sw), + purple_prefs_get_int("/purple/media/audio/volume/input")); + g_signal_connect (G_OBJECT (sw), "format-value", + G_CALLBACK (prefs_sound_volume_format), + NULL); + g_signal_connect (G_OBJECT (sw), "value-changed", + G_CALLBACK (prefs_media_input_volume_changed), + NULL); + pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("Volume:"), sg, sw, TRUE, NULL); + + vbox = pidgin_make_frame (ret, _("Audio Output")); + gtk_size_group_add_widget(sg2, vbox); + + /* Output Volume */ + sw = gtk_hscale_new_with_range(0.0, 100.0, 5.0); + gtk_range_set_increments(GTK_RANGE(sw), 5.0, 25.0); + gtk_range_set_value(GTK_RANGE(sw), + purple_prefs_get_int("/purple/media/audio/volume/output")); + g_signal_connect (G_OBJECT (sw), "format-value", + G_CALLBACK (prefs_sound_volume_format), + NULL); + g_signal_connect (G_OBJECT (sw), "value-changed", + G_CALLBACK (prefs_media_output_volume_changed), + NULL); + pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("Volume:"), sg, sw, TRUE, NULL); + + gtk_widget_show_all(ret); + + return ret; +} + +#endif /* USE_VV */ static void set_idle_away(PurpleSavedStatus *status) @@ -2167,6 +2475,10 @@ prefs_notebook_add_page(_("Conversations"), conv_page(), notebook_page++); prefs_notebook_add_page(_("Smiley Themes"), theme_page(), notebook_page++); prefs_notebook_add_page(_("Sounds"), sound_page(), notebook_page++); + +#ifdef USE_VV + prefs_notebook_add_page(_("Media"), media_page(), notebook_page++); +#endif prefs_notebook_add_page(_("Network"), network_page(), notebook_page++); #ifndef _WIN32 /* We use the registered default browser in windows */ @@ -2292,6 +2604,18 @@ purple_prefs_connect_callback(prefs, PIDGIN_PREFS_ROOT "/smileys/theme", smiley_theme_pref_cb, NULL); +#ifdef USE_VV + purple_prefs_add_none("/purple/media"); + purple_prefs_add_none("/purple/media/video"); + purple_prefs_add_string("/purple/media/video/plugin", "gconfvideosrc"); + purple_prefs_add_string("/purple/media/video/device", ""); + purple_prefs_add_none("/purple/media/audio"); + purple_prefs_add_string("/purple/media/audio/device", ""); + purple_prefs_add_none("/purple/media/audio/volume"); + purple_prefs_add_int("/purple/media/audio/volume/input", 10); + purple_prefs_add_int("/purple/media/audio/volume/output", 10); +#endif /* USE_VV */ + pidgin_prefs_update_old(); }
--- a/pidgin/gtkprefs.h Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/gtkprefs.h Sat Sep 27 04:44:17 2008 +0000 @@ -29,6 +29,7 @@ #include "prefs.h" + /** * Initializes all UI-specific preferences. */
--- a/pidgin/pidginstock.c Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/pidginstock.c Sat Sep 27 04:44:17 2008 +0000 @@ -193,6 +193,12 @@ { PIDGIN_STOCK_TOOLBAR_SEND_FILE, "toolbar", "send-file.png", FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, { PIDGIN_STOCK_TOOLBAR_TRANSFER, "toolbar", "transfer.png", FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, +#ifdef USE_VV + { PIDGIN_STOCK_TOOLBAR_AUDIO_CALL, "toolbar", "audio-call.png", FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, + { PIDGIN_STOCK_TOOLBAR_VIDEO_CALL, "toolbar", "video-call.png", FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, + { PIDGIN_STOCK_TOOLBAR_AUDIO_VIDEO_CALL, "toolbar", "audio-video-call.png", FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, +#endif + { PIDGIN_STOCK_TRAY_AVAILABLE, "tray", "tray-online.png", FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL }, { PIDGIN_STOCK_TRAY_INVISIBLE, "tray", "tray-invisible.png", FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL }, { PIDGIN_STOCK_TRAY_AWAY, "tray", "tray-away.png", FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL },
--- a/pidgin/pidginstock.h Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/pidginstock.h Sat Sep 27 04:44:17 2008 +0000 @@ -152,6 +152,11 @@ #define PIDGIN_STOCK_TOOLBAR_SELECT_AVATAR "pidgin-select-avatar" #define PIDGIN_STOCK_TOOLBAR_SEND_FILE "pidgin-send-file" #define PIDGIN_STOCK_TOOLBAR_TRANSFER "pidgin-transfer" +#ifdef USE_VV +#define PIDGIN_STOCK_TOOLBAR_AUDIO_CALL "pidgin-audio-call" +#define PIDGIN_STOCK_TOOLBAR_VIDEO_CALL "pidgin-video-call" +#define PIDGIN_STOCK_TOOLBAR_AUDIO_VIDEO_CALL "pidgin-audio-video-call" +#endif /* Tray icons */ #define PIDGIN_STOCK_TRAY_AVAILABLE "pidgin-tray-available"
--- a/pidgin/pixmaps/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/pixmaps/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -421,6 +421,7 @@ toolbar/16/scalable/font-size-up.svg TOOLBAR_16 = \ + toolbar/16/audio-call.png \ toolbar/16/change-bgcolor.png \ toolbar/16/change-fgcolor.png \ toolbar/16/emote-select.png \ @@ -434,7 +435,8 @@ toolbar/16/plugins.png \ toolbar/16/send-file.png \ toolbar/16/transfer.png \ - toolbar/16/unblock.png + toolbar/16/unblock.png \ + toolbar/16/video-call.png TOOLBAR_22_SCALABLE = \ toolbar/22/scalable/select-avatar.svg
--- a/pidgin/plugins/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/plugins/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -123,6 +123,7 @@ -I$(top_srcdir)/libpurple \ -I$(top_srcdir)/pidgin \ $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(GTK_CFLAGS) \ $(PLUGIN_CFLAGS)
--- a/pidgin/plugins/cap/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/plugins/cap/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -15,7 +15,7 @@ endif -cap_la_LIBADD = $(GTK_LIBS) $(SQLITE3_LIBS) +cap_la_LIBADD = $(GTK_LIBS) $(SQLITE3_LIBS) $(FARSIGHT_LIBS) $(GSTPROPS_LIBS) AM_CPPFLAGS = \ -DDATADIR=\"$(datadir)\" \ @@ -24,6 +24,8 @@ -I$(top_srcdir)/pidgin \ $(DEBUG_CFLAGS) \ $(GTK_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ + $(GSTPROPS_CFLAGS) \ $(SQLITE3_CFLAGS) EXTRA_DIST = Makefile.mingw
--- a/pidgin/plugins/gestures/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/plugins/gestures/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -23,4 +23,5 @@ -I$(top_builddir)/libpurple \ -I$(top_srcdir)/pidgin \ $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(GTK_CFLAGS)
--- a/pidgin/plugins/gevolution/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/plugins/gevolution/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -26,4 +26,5 @@ -I$(top_srcdir)/pidgin \ $(EVOLUTION_ADDRESSBOOK_CFLAGS) \ $(DEBUG_CFLAGS) \ - $(GTK_CFLAGS) + $(GTK_CFLAGS) \ + $(FARSIGHT_CFLAGS)
--- a/pidgin/plugins/musicmessaging/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/plugins/musicmessaging/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -40,5 +40,6 @@ -I$(top_srcdir)/libpurple \ -I$(top_srcdir)/pidgin \ $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(GTK_CFLAGS) \ $(DBUS_CFLAGS)
--- a/pidgin/plugins/ticker/Makefile.am Fri Sep 26 16:26:56 2008 +0000 +++ b/pidgin/plugins/ticker/Makefile.am Sat Sep 27 04:44:17 2008 +0000 @@ -24,4 +24,5 @@ -I$(top_builddir)/libpurple \ -I$(top_srcdir)/pidgin \ $(DEBUG_CFLAGS) \ + $(FARSIGHT_CFLAGS) \ $(GTK_CFLAGS)
--- a/po/POTFILES.in Fri Sep 26 16:26:56 2008 +0000 +++ b/po/POTFILES.in Sat Sep 27 04:44:17 2008 +0000 @@ -8,6 +8,7 @@ finch/gntdebug.c finch/gntft.c finch/gntlog.c +finch/gntmedia.c finch/gntnotify.c finch/gntplugin.c finch/gntpounce.c @@ -86,6 +87,7 @@ libpurple/protocols/jabber/buddy.c libpurple/protocols/jabber/chat.c libpurple/protocols/jabber/jabber.c +libpurple/protocols/jabber/jingle.c libpurple/protocols/jabber/libxmpp.c libpurple/protocols/jabber/message.c libpurple/protocols/jabber/parser.c